From 4f7b304f5358e3634564b7038b7ab284c18294d3 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 28 Nov 2012 21:27:02 +0000 Subject: [PATCH] Initial code to do authoritative DNS. --- Makefile | 2 +- bld/Android.mk | 2 +- src/auth.c | 307 ++++++++++++++++++++++++++++++++++++++++++++++ src/cache.c | 8 +- src/config.h | 6 +- src/dhcp-common.c | 2 +- src/dhcp.c | 2 +- src/dnsmasq.c | 22 +++- src/dnsmasq.h | 39 +++++- src/forward.c | 296 +++++++++++++++++++++++--------------------- src/network.c | 49 +++++--- src/option.c | 120 +++++++++++++++++- src/radv.c | 2 +- src/rfc1035.c | 35 +++--- src/tftp.c | 4 +- 15 files changed, 705 insertions(+), 191 deletions(-) create mode 100644 src/auth.c diff --git a/Makefile b/Makefile index d524924..5cbc94b 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ version = -DVERSION='\"`$(top)/bld/get-version $(top)`\"' objs = cache.o rfc1035.o util.o option.o forward.o network.o \ dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \ helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \ - dhcp-common.o outpacket.o radv.o slaac.o + dhcp-common.o outpacket.o radv.o slaac.o auth.o hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ dns-protocol.h radv-protocol.h diff --git a/bld/Android.mk b/bld/Android.mk index 4a54b61..b93afaf 100644 --- a/bld/Android.mk +++ b/bld/Android.mk @@ -8,7 +8,7 @@ LOCAL_SRC_FILES := bpf.c cache.c dbus.c dhcp.c dnsmasq.c \ netlink.c network.c option.c rfc1035.c \ rfc2131.c tftp.c util.c conntrack.c \ dhcp6.c rfc3315.c dhcp-common.c outpacket.c \ - radv.c slaac.c + radv.c slaac.c auth.c LOCAL_MODULE := dnsmasq diff --git a/src/auth.c b/src/auth.c new file mode 100644 index 0000000..cbc9c21 --- /dev/null +++ b/src/auth.c @@ -0,0 +1,307 @@ +/* dnsmasq is Copyright (c) 2000-2012 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, 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 . +*/ + +#include "dnsmasq.h" + +static int filter_zone(struct auth_zone *zone, int flag, struct all_addr *addr_u) +{ + struct subnet *subnet; + + for (subnet = zone->subnet; subnet; subnet = subnet->next) + { + if (subnet->is6 && (flag & F_IPV4)) + continue; + + if (!subnet->is6) + { + struct in_addr addr = addr_u->addr.addr4; + struct in_addr mask; + + mask.s_addr = (1 << (32 - subnet->prefixlen)) - 1; + + if (is_same_net(addr, subnet->addr4, mask)) + return 1; + } +#ifdef HAVE_IPV6 + else if (is_same_net6(&(addr_u->addr.addr6), &subnet->addr6, subnet->prefixlen)) + return 1; +#endif + + } + return 0; +} + + + +size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t now) +{ + char *name = daemon->namebuff; + unsigned char *p, *ansp; + int qtype, qclass; + unsigned int nameoffset; + int q, anscount = 0, authcount = 0; + struct crec *crecp; + int auth = 1, trunc = 0, nxdomain = 1, soa = 0; + struct auth_zone *zone = NULL; + + if (ntohs(header->qdcount) == 0 || OPCODE(header) != QUERY ) + return 0; + + /* determine end of question section (we put answers there) */ + if (!(ansp = skip_questions(header, qlen))) + return 0; /* bad packet */ + + /* now process each question, answers go in RRs after the question */ + p = (unsigned char *)(header+1); + + for (q = ntohs(header->qdcount); q != 0; q--) + { + size_t domainlen, namelen; + unsigned short flag; + int found = 0; + + /* save pointer to name for copying into answers */ + nameoffset = p - (unsigned char *)header; + + /* now extract name as .-concatenated string into name */ + if (!extract_name(header, qlen, &p, name, 1, 4)) + return 0; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + + if (qclass != C_IN) + continue; + + if (qtype == T_PTR) + { + struct all_addr addr; + + if (!(flag = in_arpa_name_2_addr(name, &addr))) + continue; + + for (zone = daemon->auth_zones; zone; zone = zone->next) + if (filter_zone(zone, flag, &addr)) + break; + + if (!zone) + { + auth = 0; + continue; + } + + if ((crecp = cache_find_by_addr(NULL, &addr, now, flag))) + do { + strcpy(name, cache_get_name(crecp)); + + if (crecp->flags & F_DHCP && !option_bool(OPT_DHCP_FQDN)) + { + char *p = strchr(name, '.'); + if (p) + *p = 0; /* must be bare name */ + + /* add external domain */ + strcat(name, "."); + strcat(name, zone->domain); + log_query(flag | F_DHCP | F_REVERSE, name, &addr, record_source(crecp->uid)); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, NULL, + T_PTR, C_IN, "d", name)) + { + anscount++; + found = 1; + } + } + else if (crecp->flags & (F_DHCP | F_HOSTS)) + { + domainlen = strlen(zone->domain); + namelen = strlen(name); + + if (namelen <= domainlen + 1 || + name[namelen - domainlen - 1] != '.' || + !hostname_isequal(zone->domain, &name[namelen - domainlen])) + continue; /* wrong domain */ + + log_query(crecp->flags & ~F_FORWARD, name, &addr, record_source(crecp->uid)); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, NULL, + T_PTR, C_IN, "d", name)) + { + anscount++; + found = 1; + } + } + else + continue; + + } while ((crecp = cache_find_by_addr(crecp, &addr, now, flag))); + + if (!found) + log_query(flag | F_NEG | F_NXDOMAIN | F_REVERSE | F_AUTH, NULL, &addr, NULL); + + continue; + } + + namelen = strlen(name); + + for (zone = daemon->auth_zones; zone; zone = zone->next) + { + domainlen = strlen(zone->domain); + if (namelen >= domainlen && + hostname_isequal(zone->domain, &name[namelen - domainlen])) + break; + } + + if (!zone) + { + auth = 0; + continue; + } + + if (namelen > domainlen && name[namelen - domainlen - 1] != '.') + continue; + + if (qtype == T_A) + flag = F_IPV4; +#ifdef HAVE_IPV6 + else if (qtype == T_AAAA) + flag = F_IPV6; +#endif + else + { + if (qtype == T_SOA) + { + soa = 1; /* inhibits auth section */ + found = 1; + log_query(F_RRNAME | F_AUTH, zone->domain, NULL, ""); + if (add_resource_record(header, limit, &trunc, 0, &ansp, + daemon->auth_ttl, NULL, T_SOA, C_IN, "ddlllll", + zone->domain, daemon->authserver, daemon->hostmaster, + daemon->soa_sn, daemon->soa_refresh, + daemon->soa_retry, daemon->soa_expiry, + daemon->auth_ttl)) + anscount++; + } + else if (qtype == T_NS) + { + soa = 1; /* inhibits auth section */ + found = 1; + log_query(F_RRNAME | F_AUTH, zone->domain, NULL, ""); + if (add_resource_record(header, limit, &trunc, 0, &ansp, + daemon->auth_ttl, NULL, T_NS, C_IN, "d", zone->domain, daemon->authserver)) + anscount++; + } + + continue; + } + + if (!option_bool(OPT_DHCP_FQDN) && namelen > domainlen + 1) + { + name[namelen - domainlen - 1] = 0; /* remove domain part */ + + if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6))) + { + if (crecp->flags & F_DHCP) + do + { + nxdomain = 0; + if ((crecp->flags & flag) && filter_zone(zone, flag, &(crecp->addr.addr))) + { + name[namelen - domainlen - 1] = '.'; /* restore domain part */ + log_query(crecp->flags, name, &crecp->addr.addr, record_source(crecp->uid)); + name[namelen - domainlen - 1] = 0; /* remove domain part */ + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, NULL, qtype, C_IN, + qtype == T_A ? "4" : "6", &crecp->addr)) + { + anscount++; + found = 1; + } + } + } while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4 | F_IPV6))); + } + + name[namelen - domainlen - 1] = '.'; /* restore domain part */ + } + + if (!found && (crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6))) + { + if ((crecp->flags & F_HOSTS) || (((crecp->flags & F_DHCP) && option_bool(OPT_DHCP_FQDN)))) + do + { + nxdomain = 0; + if ((crecp->flags & flag) && filter_zone(zone, flag, &(crecp->addr.addr))) + { + log_query(crecp->flags, name, &crecp->addr.addr, record_source(crecp->uid)); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, NULL, qtype, C_IN, + qtype == T_A ? "4" : "6", &crecp->addr)) + { + anscount++; + found = 1; + } + } + } while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4 | F_IPV6))); + } + + if (!found) + log_query(flag | F_NEG | (nxdomain ? F_NXDOMAIN : 0) | F_FORWARD | F_AUTH, name, NULL, NULL); + + } + + /* Add auth section */ + if (auth && !soa) + { + if (anscount != 0 && add_resource_record(header, limit, &trunc, 0, &ansp, + daemon->auth_ttl, NULL, T_NS, C_IN, "d", zone->domain, daemon->authserver)) + authcount++; + + if (anscount == 0 && add_resource_record(header, limit, &trunc, 0, &ansp, + daemon->auth_ttl, NULL, T_SOA, C_IN, "ddlllll", + zone->domain, daemon->authserver, daemon->hostmaster, + daemon->soa_sn, daemon->soa_refresh, + daemon->soa_retry, daemon->soa_expiry, + daemon->auth_ttl)) + authcount++; + } + + /* done all questions, set up header and return length of result */ + /* clear authoritative and truncated flags, set QR flag */ + header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC)) | HB3_QR; + /* clear RA flag */ + header->hb4 &= ~HB4_RA; + + /* authoritive */ + if (auth) + header->hb3 |= HB3_AA; + + /* truncation */ + if (trunc) + header->hb3 |= HB3_TC; + + if (anscount == 0 && auth && nxdomain) + SET_RCODE(header, NXDOMAIN); + else + SET_RCODE(header, NOERROR); /* no error */ + header->ancount = htons(anscount); + header->nscount = htons(authcount); + return ansp - (unsigned char *)header; +} + + + + + diff --git a/src/cache.c b/src/cache.c index ddbc5c0..8bf0c55 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1248,14 +1248,14 @@ char *record_source(int index) return ""; } -void querystr(char *str, unsigned short type) +void querystr(char *desc, char *str, unsigned short type) { unsigned int i; - sprintf(str, "query[type=%d]", type); + sprintf(str, "%s[type=%d]", desc, type); for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++) if (typestr[i].type == type) - sprintf(str,"query[%s]", typestr[i].name); + sprintf(str,"%s[%s]", desc, typestr[i].name); } void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) @@ -1316,6 +1316,8 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg) source = arg; else if (flags & F_UPSTREAM) source = "reply"; + else if (flags & F_AUTH) + source = "auth"; else if (flags & F_SERVER) { source = "forwarded"; diff --git a/src/config.h b/src/config.h index 1a08e95..31fb1cc 100644 --- a/src/config.h +++ b/src/config.h @@ -42,7 +42,11 @@ #define EDNS0_OPTION_MAC 5 /* dyndns.org temporary assignment */ #define DNSMASQ_SERVICE "uk.org.thekelleys.dnsmasq" /* Default - may be overridden by config */ #define DNSMASQ_PATH "/uk/org/thekelleys/dnsmasq" - +#define AUTH_TTL 600 /* default TTL for auth DNS */ +#define SOA_REFRESH 1200 /* SOA refresh default */ +#define SOA_RETRY 180 /* SOA retry default */ +#define SOA_EXPIRY 1209600 /* SOA expiry default */ + /* compile-time options: uncomment below to enable or do eg. make COPTS=-DHAVE_BROKEN_RTC diff --git a/src/dhcp-common.c b/src/dhcp-common.c index 75076d4..30aad56 100644 --- a/src/dhcp-common.c +++ b/src/dhcp-common.c @@ -366,7 +366,7 @@ static int join_multicast_worker(struct in6_addr *local, int prefix, close(fd); /* Are we doing DHCP on this interface? */ - if (!iface_check(AF_INET6, (struct all_addr *)local, ifrn_name)) + if (!iface_check(AF_INET6, (struct all_addr *)local, ifrn_name, NULL)) return 1; for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) diff --git a/src/dhcp.c b/src/dhcp.c index 75a4427..3410020 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -262,7 +262,7 @@ void dhcp_packet(time_t now, int pxe_fd) parm.current = NULL; parm.ind = iface_index; - if (!iface_check(AF_INET, (struct all_addr *)&iface_addr, ifr.ifr_name)) + if (!iface_check(AF_INET, (struct all_addr *)&iface_addr, ifr.ifr_name, NULL)) { /* If we failed to match the primary address of the interface, see if we've got a --listen-address for a secondary */ diff --git a/src/dnsmasq.c b/src/dnsmasq.c index 0023a4f..6e3a7c2 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -84,6 +84,7 @@ int main (int argc, char **argv) daemon->addrbuff = safe_malloc(ADDRSTRLEN); + #ifdef HAVE_DHCP if (!daemon->lease_file) { @@ -150,6 +151,14 @@ int main (int argc, char **argv) rand_init(); now = dnsmasq_time(); + + /* Create a serial at startup is not configured. */ + if (daemon->authinterface && daemon->soa_sn == 0) +#ifdef HAVE_BROKEN_RTC + die(_("zone serial must be configured in --auth-soa")); +#else + daemon->soa_sn = now; +#endif #ifdef HAVE_DHCP if (daemon->dhcp || daemon->dhcp6) @@ -1440,11 +1449,18 @@ static void check_dns_listeners(fd_set *set, time_t now) struct server *s; int flags; struct in_addr netmask; + int auth_dns; if (iface) - netmask = iface->netmask; + { + netmask = iface->netmask; + auth_dns = iface->dns_auth; + } else - netmask.s_addr = 0; + { + netmask.s_addr = 0; + auth_dns = 0; + } #ifndef NO_FORK /* Arrange for SIGALARM after CHILD_LIFETIME seconds to @@ -1463,7 +1479,7 @@ static void check_dns_listeners(fd_set *set, time_t now) if ((flags = fcntl(confd, F_GETFL, 0)) != -1) fcntl(confd, F_SETFL, flags & ~O_NONBLOCK); - buff = tcp_request(confd, now, &tcp_addr, netmask); + buff = tcp_request(confd, now, &tcp_addr, netmask, auth_dns); shutdown(confd, SHUT_RDWR); close(confd); diff --git a/src/dnsmasq.h b/src/dnsmasq.h index c715e58..91c71c4 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -278,6 +278,20 @@ struct cname { struct cname *next; }; +struct auth_zone { + char *domain; + struct subnet { + int is6, prefixlen; + struct in_addr addr4; +#ifdef HAVE_IPV6 + struct in6_addr addr6; +#endif + struct subnet *next; + } *subnet; + struct auth_zone *next; +}; + + struct host_record { struct name_list { char *name; @@ -357,6 +371,8 @@ struct crec { #define F_SERVER (1u<<18) #define F_QUERY (1u<<19) #define F_NOERR (1u<<20) +#define F_AUTH (1u<<21) + /* composites */ #define F_TYPE (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS) /* Only one may be set */ @@ -412,7 +428,7 @@ struct server { struct irec { union mysockaddr addr; struct in_addr netmask; /* only valid for IPv4 */ - int tftp_ok, dhcp_ok, mtu, done, dad; + int tftp_ok, dhcp_ok, mtu, done, dad, dns_auth; char *name; struct irec *next; }; @@ -733,11 +749,13 @@ extern struct daemon { struct ptr_record *ptr; struct host_record *host_records, *host_records_tail; struct cname *cnames; + struct auth_zone *auth_zones; struct interface_name *int_names; char *mxtarget; char *lease_file; char *username, *groupname, *scriptuser; char *luascript; + char *authserver, *authinterface, *hostmaster; int group_set, osport; char *domain_suffix; struct cond_domain *cond_domain; @@ -751,7 +769,7 @@ extern struct daemon { int max_logs; /* queue limit */ int cachesize, ftabsize; int port, query_port, min_port; - unsigned long local_ttl, neg_ttl, max_ttl, max_cache_ttl; + unsigned long local_ttl, neg_ttl, max_ttl, max_cache_ttl, auth_ttl; struct hostsfile *addn_hosts; struct dhcp_context *dhcp, *dhcp6, *ra_contexts; struct dhcp_config *dhcp_conf; @@ -778,6 +796,7 @@ extern struct daemon { unsigned int duid_enterprise, duid_config_len; unsigned char *duid_config; char *dbus_name; + unsigned long soa_sn, soa_refresh, soa_retry, soa_expiry; /* globally used stuff for DNS */ char *packet; /* packet buffer */ @@ -835,7 +854,7 @@ extern struct daemon { void cache_init(void); void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg); char *record_source(int index); -void querystr(char *str, unsigned short type); +void querystr(char *desc, char *str, unsigned short type); struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr, time_t now, unsigned short prot); @@ -879,6 +898,16 @@ unsigned int questions_crc(struct dns_header *header, size_t plen, char *buff); size_t resize_packet(struct dns_header *header, size_t plen, unsigned char *pheader, size_t hlen); size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysockaddr *l3); +int add_resource_record(struct dns_header *header, char *limit, int *truncp, + unsigned int nameoffset, unsigned char **pp, unsigned long ttl, + unsigned int *offset, unsigned short type, unsigned short class, char *format, ...); +unsigned char *skip_questions(struct dns_header *header, size_t plen); +int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, + char *name, int isExtract, int extrabytes); +int in_arpa_name_2_addr(char *namein, struct all_addr *addrp); + +/* auth.c */ +size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t now); /* util.c */ void rand_init(void); @@ -935,7 +964,7 @@ char *parse_server(char *arg, union mysockaddr *addr, void reply_query(int fd, int family, time_t now); void receive_query(struct listener *listen, time_t now); unsigned char *tcp_request(int confd, time_t now, - union mysockaddr *local_addr, struct in_addr netmask); + union mysockaddr *local_addr, struct in_addr netmask, int auth_dns); void server_gone(struct server *server); struct frec *get_new_frec(time_t now, int *wait); int send_from(int fd, int nowild, char *packet, size_t len, @@ -953,7 +982,7 @@ int enumerate_interfaces(); void create_wildcard_listeners(void); void create_bound_listeners(int die); int is_dad_listeners(void); -int iface_check(int family, struct all_addr *addr, char *name); +int iface_check(int family, struct all_addr *addr, char *name, int *auth_dns); int fix_fd(int fd); struct in_addr get_ifaddr(char *intr); #ifdef HAVE_IPV6 diff --git a/src/forward.c b/src/forward.c index f672194..603dbd9 100644 --- a/src/forward.c +++ b/src/forward.c @@ -642,6 +642,7 @@ void receive_query(struct listener *listen, time_t now) size_t m; ssize_t n; int if_index = 0; + int auth_dns = 0; struct iovec iov[1]; struct msghdr msg; struct cmsghdr *cmptr; @@ -664,17 +665,20 @@ void receive_query(struct listener *listen, time_t now) /* packet buffer overwritten */ daemon->srv_save = NULL; - if (listen->iface && listen->family == AF_INET && option_bool(OPT_NOWILD)) + dst_addr_4.s_addr = 0; + netmask.s_addr = 0; + + if (listen->iface && option_bool(OPT_NOWILD)) { - dst_addr_4 = listen->iface->addr.in.sin_addr; - netmask = listen->iface->netmask; + auth_dns = listen->iface->dns_auth; + + if (listen->family == AF_INET) + { + dst_addr_4 = listen->iface->addr.in.sin_addr; + netmask = listen->iface->netmask; + } } - else - { - dst_addr_4.s_addr = 0; - netmask.s_addr = 0; - } - + iov[0].iov_base = daemon->packet; iov[0].iov_len = daemon->edns_pktsz; @@ -767,7 +771,7 @@ void receive_query(struct listener *listen, time_t now) /* enforce available interface configuration */ if (!indextoname(listen->fd, if_index, ifr.ifr_name) || - !iface_check(listen->family, &dst_addr, ifr.ifr_name)) + !iface_check(listen->family, &dst_addr, ifr.ifr_name, &auth_dns)) return; if (listen->family == AF_INET && option_bool(OPT_LOCALISE)) @@ -803,7 +807,7 @@ void receive_query(struct listener *listen, time_t now) { char types[20]; - querystr(types, type); + querystr(auth_dns ? "auth" : "query", types, type); if (listen->family == AF_INET) log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, @@ -815,19 +819,30 @@ void receive_query(struct listener *listen, time_t now) #endif } - m = answer_request (header, ((char *) header) + PACKETSZ, (size_t)n, - dst_addr_4, netmask, now); - if (m >= 1) + if (auth_dns) { - send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), - (char *)header, m, &source_addr, &dst_addr, if_index); - daemon->local_answer++; + m = answer_auth(header, ((char *) header) + PACKETSZ, (size_t)n, now); + if (m >= 1) + send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), + (char *)header, m, &source_addr, &dst_addr, if_index); } - else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index, - header, (size_t)n, now, NULL)) - daemon->queries_forwarded++; else - daemon->local_answer++; + { + m = answer_request(header, ((char *) header) + PACKETSZ, (size_t)n, + dst_addr_4, netmask, now); + + if (m >= 1) + { + send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), + (char *)header, m, &source_addr, &dst_addr, if_index); + daemon->local_answer++; + } + else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index, + header, (size_t)n, now, NULL)) + daemon->queries_forwarded++; + else + daemon->local_answer++; + } } /* The daemon forks before calling this: it should deal with one connection, @@ -835,7 +850,7 @@ void receive_query(struct listener *listen, time_t now) about resources for debug mode, when the fork is suppressed: that's done by the caller. */ unsigned char *tcp_request(int confd, time_t now, - union mysockaddr *local_addr, struct in_addr netmask) + union mysockaddr *local_addr, struct in_addr netmask, int auth_dns) { size_t size = 0; int norebind = 0; @@ -877,7 +892,7 @@ unsigned char *tcp_request(int confd, time_t now, { char types[20]; - querystr(types, qtype); + querystr(auth_dns ? "auth" : "query", types, qtype); if (peer_addr.sa.sa_family == AF_INET) log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, @@ -894,137 +909,142 @@ unsigned char *tcp_request(int confd, time_t now, else dst_addr_4.s_addr = 0; - /* m > 0 if answered from cache */ - m = answer_request(header, ((char *) header) + 65536, (unsigned int)size, - dst_addr_4, netmask, now); - - /* Do this by steam now we're not in the select() loop */ - check_log_writer(NULL); - - if (m == 0) + if (auth_dns) + m = answer_auth(header, ((char *) header) + 65536, (size_t)size, now); + else { - unsigned int flags = 0; - struct all_addr *addrp = NULL; - int type = 0; - char *domain = NULL; - - if (option_bool(OPT_ADD_MAC)) - size = add_mac(header, size, ((char *) header) + 65536, &peer_addr); - - if (gotname) - flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); + /* m > 0 if answered from cache */ + m = answer_request(header, ((char *) header) + 65536, (size_t)size, + dst_addr_4, netmask, now); - if (type != 0 || option_bool(OPT_ORDER) || !daemon->last_server) - last_server = daemon->servers; - else - last_server = daemon->last_server; - - if (!flags && last_server) + /* Do this by steam now we're not in the select() loop */ + check_log_writer(NULL); + + if (m == 0) { - struct server *firstsendto = NULL; - unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff); - - /* Loop round available servers until we succeed in connecting to one. - Note that this code subtley ensures that consecutive queries on this connection - which can go to the same server, do so. */ - while (1) - { - if (!firstsendto) - firstsendto = last_server; - else - { - if (!(last_server = last_server->next)) - last_server = daemon->servers; - - if (last_server == firstsendto) - break; - } + unsigned int flags = 0; + struct all_addr *addrp = NULL; + int type = 0; + char *domain = NULL; - /* server for wrong domain */ - if (type != (last_server->flags & SERV_TYPE) || - (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain))) - continue; - - if (last_server->tcpfd == -1) + if (option_bool(OPT_ADD_MAC)) + size = add_mac(header, size, ((char *) header) + 65536, &peer_addr); + + if (gotname) + flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); + + if (type != 0 || option_bool(OPT_ORDER) || !daemon->last_server) + last_server = daemon->servers; + else + last_server = daemon->last_server; + + if (!flags && last_server) + { + struct server *firstsendto = NULL; + unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff); + + /* Loop round available servers until we succeed in connecting to one. + Note that this code subtley ensures that consecutive queries on this connection + which can go to the same server, do so. */ + while (1) { - if ((last_server->tcpfd = socket(last_server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1) + if (!firstsendto) + firstsendto = last_server; + else + { + if (!(last_server = last_server->next)) + last_server = daemon->servers; + + if (last_server == firstsendto) + break; + } + + /* server for wrong domain */ + if (type != (last_server->flags & SERV_TYPE) || + (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain))) continue; - if ((!local_bind(last_server->tcpfd, &last_server->source_addr, last_server->interface, 1) || - connect(last_server->tcpfd, &last_server->addr.sa, sa_len(&last_server->addr)) == -1)) + if (last_server->tcpfd == -1) + { + if ((last_server->tcpfd = socket(last_server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1) + continue; + + if ((!local_bind(last_server->tcpfd, &last_server->source_addr, last_server->interface, 1) || + connect(last_server->tcpfd, &last_server->addr.sa, sa_len(&last_server->addr)) == -1)) + { + close(last_server->tcpfd); + last_server->tcpfd = -1; + continue; + } + +#ifdef HAVE_CONNTRACK + /* Copy connection mark of incoming query to outgoing connection. */ + if (option_bool(OPT_CONNTRACK)) + { + unsigned int mark; + struct all_addr local; +#ifdef HAVE_IPV6 + if (local_addr->sa.sa_family == AF_INET6) + local.addr.addr6 = local_addr->in6.sin6_addr; + else +#endif + local.addr.addr4 = local_addr->in.sin_addr; + + if (get_incoming_mark(&peer_addr, &local, 1, &mark)) + setsockopt(last_server->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); + } +#endif + } + + c1 = size >> 8; + c2 = size; + + if (!read_write(last_server->tcpfd, &c1, 1, 0) || + !read_write(last_server->tcpfd, &c2, 1, 0) || + !read_write(last_server->tcpfd, packet, size, 0) || + !read_write(last_server->tcpfd, &c1, 1, 1) || + !read_write(last_server->tcpfd, &c2, 1, 1)) { close(last_server->tcpfd); last_server->tcpfd = -1; continue; - } - -#ifdef HAVE_CONNTRACK - /* Copy connection mark of incoming query to outgoing connection. */ - if (option_bool(OPT_CONNTRACK)) - { - unsigned int mark; - struct all_addr local; -#ifdef HAVE_IPV6 - if (local_addr->sa.sa_family == AF_INET6) - local.addr.addr6 = local_addr->in6.sin6_addr; - else -#endif - local.addr.addr4 = local_addr->in.sin_addr; - - if (get_incoming_mark(&peer_addr, &local, 1, &mark)) - setsockopt(last_server->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); - } -#endif - } - - c1 = size >> 8; - c2 = size; - - if (!read_write(last_server->tcpfd, &c1, 1, 0) || - !read_write(last_server->tcpfd, &c2, 1, 0) || - !read_write(last_server->tcpfd, packet, size, 0) || - !read_write(last_server->tcpfd, &c1, 1, 1) || - !read_write(last_server->tcpfd, &c2, 1, 1)) - { - close(last_server->tcpfd); - last_server->tcpfd = -1; - continue; - } - - m = (c1 << 8) | c2; - if (!read_write(last_server->tcpfd, packet, m, 1)) - return packet; - - if (!gotname) - strcpy(daemon->namebuff, "query"); - if (last_server->addr.sa.sa_family == AF_INET) - log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&last_server->addr.in.sin_addr, NULL); + } + + m = (c1 << 8) | c2; + if (!read_write(last_server->tcpfd, packet, m, 1)) + return packet; + + if (!gotname) + strcpy(daemon->namebuff, "query"); + if (last_server->addr.sa.sa_family == AF_INET) + log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&last_server->addr.in.sin_addr, NULL); #ifdef HAVE_IPV6 - else - log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&last_server->addr.in6.sin6_addr, NULL); + else + log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&last_server->addr.in6.sin6_addr, NULL); #endif - - /* There's no point in updating the cache, since this process will exit and - lose the information after a few queries. We make this call for the alias and - bogus-nxdomain side-effects. */ - /* If the crc of the question section doesn't match the crc we sent, then - someone might be attempting to insert bogus values into the cache by - sending replies containing questions and bogus answers. */ - if (crc == questions_crc(header, (unsigned int)m, daemon->namebuff)) - m = process_reply(header, now, last_server, (unsigned int)m, - option_bool(OPT_NO_REBIND) && !norebind, checking_disabled); - - break; + + /* There's no point in updating the cache, since this process will exit and + lose the information after a few queries. We make this call for the alias and + bogus-nxdomain side-effects. */ + /* If the crc of the question section doesn't match the crc we sent, then + someone might be attempting to insert bogus values into the cache by + sending replies containing questions and bogus answers. */ + if (crc == questions_crc(header, (unsigned int)m, daemon->namebuff)) + m = process_reply(header, now, last_server, (unsigned int)m, + option_bool(OPT_NO_REBIND) && !norebind, checking_disabled); + + break; + } } + + /* In case of local answer or no connections made. */ + if (m == 0) + m = setup_reply(header, (unsigned int)size, addrp, flags, daemon->local_ttl); } - - /* In case of local answer or no connections made. */ - if (m == 0) - m = setup_reply(header, (unsigned int)size, addrp, flags, daemon->local_ttl); } - + check_log_writer(NULL); c1 = m>>8; diff --git a/src/network.c b/src/network.c index fda50bd..0235cec 100644 --- a/src/network.c +++ b/src/network.c @@ -107,13 +107,24 @@ int indextoname(int fd, int index, char *name) #endif -int iface_check(int family, struct all_addr *addr, char *name) +int iface_check(int family, struct all_addr *addr, char *name, int *auth) { struct iname *tmp; int ret = 1; /* Note: have to check all and not bail out early, so that we set the "used" flags. */ + + if (auth) + { + if (daemon->authinterface && strcmp(daemon->authinterface, name) == 0) + { + *auth = 1; + return 1; + } + else + *auth = 0; + } if (daemon->if_names || daemon->if_addrs) { @@ -153,6 +164,7 @@ static int iface_allowed(struct irec **irecp, int if_index, struct ifreq ifr; int tftp_ok = !!option_bool(OPT_TFTP); int dhcp_ok = 1; + int auth_dns = 0; #ifdef HAVE_DHCP struct iname *tmp; #endif @@ -210,25 +222,31 @@ static int iface_allowed(struct irec **irecp, int if_index, } if (addr->sa.sa_family == AF_INET && - !iface_check(AF_INET, (struct all_addr *)&addr->in.sin_addr, ifr.ifr_name)) + !iface_check(AF_INET, (struct all_addr *)&addr->in.sin_addr, ifr.ifr_name, &auth_dns)) return 1; - -#ifdef HAVE_DHCP - for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) - if (tmp->name && (strcmp(tmp->name, ifr.ifr_name) == 0)) - { - tftp_ok = 0; - dhcp_ok = 0; - } -#endif - + #ifdef HAVE_IPV6 if (addr->sa.sa_family == AF_INET6 && - !iface_check(AF_INET6, (struct all_addr *)&addr->in6.sin6_addr, ifr.ifr_name)) + !iface_check(AF_INET6, (struct all_addr *)&addr->in6.sin6_addr, ifr.ifr_name, &auth_dns)) return 1; #endif - - + +#ifdef HAVE_DHCP + /* No DHCP where we're doing auth DNS. */ + if (auth_dns) + { + tftp_ok = 0; + dhcp_ok = 0; + } + else + for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) + if (tmp->name && (strcmp(tmp->name, ifr.ifr_name) == 0)) + { + tftp_ok = 0; + dhcp_ok = 0; + } +#endif + /* add to list */ if ((iface = whine_malloc(sizeof(struct irec)))) { @@ -236,6 +254,7 @@ static int iface_allowed(struct irec **irecp, int if_index, iface->netmask = netmask; iface->tftp_ok = tftp_ok; iface->dhcp_ok = dhcp_ok; + iface->dns_auth = auth_dns; iface->mtu = mtu; iface->dad = dad; iface->done = 0; diff --git a/src/option.c b/src/option.c index 714f2c2..0cad369 100644 --- a/src/option.c +++ b/src/option.c @@ -121,6 +121,10 @@ struct myoption { #define LOPT_RR 310 #define LOPT_CLVERBIND 311 #define LOPT_MAXCTTL 312 +#define LOPT_AUTHZONE 313 +#define LOPT_AUTHSERV 314 +#define LOPT_AUTHTTL 315 +#define LOPT_AUTHSOA 316 #ifdef HAVE_GETOPT_LONG static const struct option opts[] = @@ -247,6 +251,10 @@ static const struct myoption opts[] = { "dhcp-duid", 1, 0, LOPT_DUID }, { "host-record", 1, 0, LOPT_HOST_REC }, { "bind-dynamic", 0, 0, LOPT_CLVERBIND }, + { "auth-zone", 1, 0, LOPT_AUTHZONE }, + { "auth-server", 1, 0, LOPT_AUTHSERV }, + { "auth-ttl", 1, 0, LOPT_AUTHTTL }, + { "auth-soa", 1, 0, LOPT_AUTHSOA }, { NULL, 0, 0, 0 } }; @@ -378,7 +386,11 @@ static struct { { LOPT_DUID, ARG_ONE, ",", gettext_noop("Specify DUID_EN-type DHCPv6 server DUID"), NULL }, { LOPT_HOST_REC, ARG_DUP, ",
", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL }, { LOPT_RR, ARG_DUP, ",,[]", gettext_noop("Specify arbitrary DNS resource record"), NULL }, - { LOPT_CLVERBIND, OPT_CLEVERBIND, NULL, gettext_noop("Bind to interfaces in use - check for new interfaces"), NULL}, + { LOPT_CLVERBIND, OPT_CLEVERBIND, NULL, gettext_noop("Bind to interfaces in use - check for new interfaces"), NULL }, + { LOPT_AUTHSERV, ARG_ONE, ",", gettext_noop("Export local names to global DNS"), NULL }, + { LOPT_AUTHZONE, ARG_DUP, ",[,]", gettext_noop("Domain to export to global DNS"), NULL }, + { LOPT_AUTHTTL, ARG_ONE, "", gettext_noop("Set TTL for authoritative replies"), NULL }, + { LOPT_AUTHSOA, ARG_ONE, "[,...]", gettext_noop("Set authoritive zone information"), NULL }, { 0, 0, NULL, NULL, NULL } }; @@ -1521,6 +1533,95 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma break; } + case LOPT_AUTHSERV: /* --auth-server */ + comma = split(arg); + if (!comma) + ret_err(gen_err); + + daemon->authinterface = opt_string_alloc(arg); + arg = comma; + comma = split(arg); + daemon->authserver = opt_string_alloc(arg); + + break; + + case LOPT_AUTHZONE: /* --auth-zone */ + { + struct auth_zone *new; + + comma = split(arg); + if (!comma) + ret_err(gen_err); + + new = safe_malloc(sizeof(struct auth_zone)); + new->domain = opt_string_alloc(arg); + new->subnet = NULL; + new->next = daemon->auth_zones; + daemon->auth_zones = new; + + while ((arg = comma)) + { + int prefixlen = 0; + char *prefix; + struct subnet *subnet = safe_malloc(sizeof(struct subnet)); + + subnet->next = new->subnet; + new->subnet = subnet; + + comma = split(arg); + prefix = split_chr(arg, '/'); + + if (prefix && !atoi_check(prefix, &prefixlen)) + ret_err(gen_err); + + if (inet_pton(AF_INET, arg, &subnet->addr4)) + { + subnet->prefixlen = (prefixlen == 0) ? 24 : prefixlen; + subnet->is6 = 0; + } +#ifdef HAVE_IPV6 + else if (inet_pton(AF_INET6, arg, &subnet->addr6)) + { + subnet->prefixlen = (prefixlen == 0) ? 64 : prefixlen; + subnet->is6 = 1; + } +#endif + else + ret_err(gen_err); + } + break; + } + + case LOPT_AUTHSOA: /* --auth-soa */ + comma = split(arg); + atoi_check(arg, (int *)&daemon->soa_sn); + if (comma) + { + arg = comma; + comma = split(arg); + daemon->hostmaster = opt_string_alloc(arg); + if (comma) + { + arg = comma; + comma = split(arg); + atoi_check(arg, (int *)&daemon->soa_refresh); + if (comma) + { + arg = comma; + comma = split(arg); + atoi_check(arg, (int *)&daemon->soa_retry); + if (comma) + { + arg = comma; + comma = split(arg); + atoi_check(arg, (int *)&daemon->soa_expiry); + } + } + } + } + + break; + case 's': /* --domain */ if (strcmp (arg, "#") == 0) set_option_bool(OPT_RESOLV_DOMAIN); @@ -1933,6 +2034,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma case LOPT_NEGTTL: /* --neg-ttl */ case LOPT_MAXTTL: /* --max-ttl */ case LOPT_MAXCTTL: /* --max-cache-ttl */ + case LOPT_AUTHTTL: /* --auth-ttl */ { int ttl; if (!atoi_check(arg, &ttl)) @@ -1943,6 +2045,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma daemon->max_ttl = (unsigned long)ttl; else if (option == LOPT_MAXCTTL) daemon->max_cache_ttl = (unsigned long)ttl; + else if (option == LOPT_AUTHTTL) + daemon->auth_ttl = (unsigned long)ttl; else daemon->local_ttl = (unsigned long)ttl; break; @@ -3613,6 +3717,10 @@ void read_opts(int argc, char **argv, char *compile_opts) daemon->tftp_max = TFTP_MAX_CONNECTIONS; daemon->edns_pktsz = EDNS_PKTSZ; daemon->log_fac = -1; + daemon->auth_ttl = AUTH_TTL; + daemon->soa_refresh = SOA_REFRESH; + daemon->soa_retry = SOA_RETRY; + daemon->soa_expiry = SOA_EXPIRY; add_txt("version.bind", "dnsmasq-" VERSION ); add_txt("authors.bind", "Simon Kelley"); add_txt("copyright.bind", COPYRIGHT); @@ -3720,7 +3828,15 @@ void read_opts(int argc, char **argv, char *compile_opts) tmp->addr.in6.sin6_port = htons(daemon->port); #endif /* IPv6 */ } - + + /* create default, if not specified */ + if (daemon->authserver && !daemon->hostmaster) + { + strcpy(buff, "hostmaster."); + strcat(buff, daemon->authserver); + daemon->hostmaster = opt_string_alloc(buff); + } + /* only one of these need be specified: the other defaults to the host-name */ if (option_bool(OPT_LOCALMX) || daemon->mxnames || daemon->mxtarget) { diff --git a/src/radv.c b/src/radv.c index d1f5268..6fa4b35 100644 --- a/src/radv.c +++ b/src/radv.c @@ -149,7 +149,7 @@ void icmp6_packet(void) if (!indextoname(daemon->icmp6fd, if_index, interface)) return; - if (!iface_check(AF_LOCAL, NULL, interface)) + if (!iface_check(AF_LOCAL, NULL, interface, NULL)) return; for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) diff --git a/src/rfc1035.c b/src/rfc1035.c index 67325ca..5896a93 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -16,10 +16,6 @@ #include "dnsmasq.h" -static int add_resource_record(struct dns_header *header, char *limit, int *truncp, - unsigned int nameoffset, unsigned char **pp, - unsigned long ttl, unsigned int *offset, unsigned short type, - unsigned short class, char *format, ...); #define CHECK_LEN(header, pp, plen, len) \ ((size_t)((pp) - (unsigned char *)(header) + (len)) <= (plen)) @@ -27,8 +23,8 @@ static int add_resource_record(struct dns_header *header, char *limit, int *trun #define ADD_RDLEN(header, pp, plen, len) \ (!CHECK_LEN(header, pp, plen, len) ? 0 : (((pp) += (len)), 1)) -static int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, - char *name, int isExtract, int extrabytes) +int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, + char *name, int isExtract, int extrabytes) { unsigned char *cp = (unsigned char *)name, *p = *pp, *p1 = NULL; unsigned int j, l, hops = 0; @@ -173,7 +169,7 @@ static int extract_name(struct dns_header *header, size_t plen, unsigned char ** /* Max size of input string (for IPv6) is 75 chars.) */ #define MAXARPANAME 75 -static int in_arpa_name_2_addr(char *namein, struct all_addr *addrp) +int in_arpa_name_2_addr(char *namein, struct all_addr *addrp) { int j; char name[MAXARPANAME+1], *cp1; @@ -333,7 +329,7 @@ static unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, return ansp; } -static unsigned char *skip_questions(struct dns_header *header, size_t plen) +unsigned char *skip_questions(struct dns_header *header, size_t plen) { int q; unsigned char *ansp = (unsigned char *)(header+1); @@ -1189,8 +1185,8 @@ int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, return 0; } -static int add_resource_record(struct dns_header *header, char *limit, int *truncp, unsigned int nameoffset, unsigned char **pp, - unsigned long ttl, unsigned int *offset, unsigned short type, unsigned short class, char *format, ...) +int add_resource_record(struct dns_header *header, char *limit, int *truncp, unsigned int nameoffset, unsigned char **pp, + unsigned long ttl, unsigned int *offset, unsigned short type, unsigned short class, char *format, ...) { va_list ap; unsigned char *sav, *p = *pp; @@ -1201,8 +1197,19 @@ static int add_resource_record(struct dns_header *header, char *limit, int *trun if (truncp && *truncp) return 0; + + va_start(ap, format); /* make ap point to 1st unamed argument */ + + if (nameoffset != 0) + { + PUTSHORT(nameoffset | 0xc000, p); + } + else + { + p = do_rfc1035_name(p, va_arg(ap, char *)); + *p++ = 0; + } - PUTSHORT(nameoffset | 0xc000, p); PUTSHORT(type, p); PUTSHORT(class, p); PUTLONG(ttl, p); /* TTL */ @@ -1210,8 +1217,6 @@ static int add_resource_record(struct dns_header *header, char *limit, int *trun sav = p; /* Save pointer to RDLength field */ PUTSHORT(0, p); /* Placeholder RDLength */ - va_start(ap, format); /* make ap point to 1st unamed argument */ - for (; *format; format++) switch (*format) { @@ -1857,7 +1862,3 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, return ansp - (unsigned char *)header; } - - - - diff --git a/src/tftp.c b/src/tftp.c index 82bbccc..4ce10b3 100644 --- a/src/tftp.c +++ b/src/tftp.c @@ -193,12 +193,12 @@ void tftp_request(struct listener *listen, time_t now) #ifdef HAVE_IPV6 if (listen->family == AF_INET6) { - if (!iface_check(AF_INET6, (struct all_addr *)&addr.in6.sin6_addr, name)) + if (!iface_check(AF_INET6, (struct all_addr *)&addr.in6.sin6_addr, name, NULL)) return; } else #endif - if (!iface_check(AF_INET, (struct all_addr *)&addr.in.sin_addr, name)) + if (!iface_check(AF_INET, (struct all_addr *)&addr.in.sin_addr, name, NULL)) return; #ifdef HAVE_DHCP