diff -Nru pdns-2.9.21.orig/configure.in pdns-2.9.21/configure.in --- pdns-2.9.21.orig/configure.in 2007-04-21 15:56:36.000000000 +0200 +++ pdns-2.9.21/configure.in 2007-11-05 15:55:52.000000000 +0100 @@ -145,6 +145,11 @@ dynmodules="$withval" ]) +mxselector="" +AC_ARG_WITH(mxselector, [ --with-mxselector Map types to use for selective serving of MX records ], +[ + mxselector="$withval" +]) AC_SUBST(socketdir) @@ -492,6 +497,39 @@ moduledirs="$moduledirs ${a}backend" done +for a in $mxselector +do + case "$a" in + bdb ) + AC_CHECK_LIB(c, dbopen, mxselector_map_bdb1=yes,) + ;; + cdb ) + AC_CHECK_LIB(cdb, cdb_init, mxselector_map_cdb=yes,) + ;; + sqlite ) + AC_CHECK_LIB(sqlite3, sqlite3_open, mxselector_map_sqlite3=yes,) + ;; + esac +done + +if test "$mxselector_map_bdb1" +then + AC_DEFINE(HAVE_BDB, 1, [If BerkeleyDB engine for MXSelector should be enabled]) +fi + +if test "$mxselector_map_cdb" +then + AC_DEFINE(HAVE_CDB, 1, [If CDB engine for MXSelector should be enabled]) + LDFLAGS="$LDFLAGS -lcdb" +fi + +if test "$mxselector_map_sqlite3" +then + AC_DEFINE(HAVE_SQLITE3, 1, [If SQLite engine for MXSelector should be enabled]) + LDFLAGS="$LDFLAGS -lsqlite3" +fi + + AC_SUBST(LIBS) export moduledirs moduleobjects modulelibs diff -Nru pdns-2.9.21.orig/pdns/Makefile.am pdns-2.9.21/pdns/Makefile.am --- pdns-2.9.21.orig/pdns/Makefile.am 2007-04-21 15:56:36.000000000 +0200 +++ pdns-2.9.21/pdns/Makefile.am 2007-11-06 13:21:37.000000000 +0100 @@ -38,6 +38,7 @@ backends/gsql/gsqlbackend.hh backends/gsql/ssql.hh \ base64.cc sillyrecords.cc \ base64.hh zoneparser-tng.cc dnsrecords.cc dnswriter.cc \ +mxselector.hh mxselector.cc \ rcpgenerator.cc dnsparser.cc # diff -Nru pdns-2.9.21.orig/pdns/backends/bind/Makefile.am pdns-2.9.21/pdns/backends/bind/Makefile.am --- pdns-2.9.21.orig/pdns/backends/bind/Makefile.am 2007-04-21 15:56:36.000000000 +0200 +++ pdns-2.9.21/pdns/backends/bind/Makefile.am 2007-11-06 16:08:46.000000000 +0100 @@ -15,6 +15,7 @@ ../../arguments.cc ../../logger.cc zone2sql.cc ../../statbag.cc ../../misc.cc \ ../../unix_utility.cc ../../qtype.cc ../../dnspacket.cc \ ../../zoneparser-tng.cc ../../dnsrecords.cc ../../sillyrecords.cc \ +../../mxselector.cc \ ../../dnswriter.cc ../../rcpgenerator.cc ../../dnsparser.cc ../../base64.cc zone2ldap_SOURCES=bindparser.yy bindlexer.l \ diff -Nru pdns-2.9.21.orig/pdns/common_startup.cc pdns-2.9.21/pdns/common_startup.cc --- pdns-2.9.21.orig/pdns/common_startup.cc 2007-04-21 15:56:36.000000000 +0200 +++ pdns-2.9.21/pdns/common_startup.cc 2007-11-06 11:54:10.000000000 +0100 @@ -17,6 +17,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common_startup.hh" +#include "mxselector.hh" typedef Distributor DNSDistributor; @@ -30,6 +31,7 @@ UDPNameserver *N; int avg_latency; TCPNameserver *TN; +extern MXSelector *MXS; ArgvMap &arg() { @@ -89,6 +91,11 @@ arg().setSwitch("strict-rfc-axfrs","Perform strictly rfc compliant axfrs (very slow)")="no"; arg().setSwitch("send-root-referral","Send out old-fashioned root-referral instead of ServFail in case of no authority")="no"; + arg().setSwitch("mxselector","Do selective serving of MX records")="no"; + arg().set("mxselector-map","Map of clients to MX records")="dummy:dummy"; + arg().set("mxselector-domains","List of domains that use MX selector")=""; + arg().set("mxselector-cache-size","Maximum number of entries in selector cache")=500; + arg().setSwitch("webserver","Start a webserver for monitoring")="no"; arg().setSwitch("webserver-print-arguments","If the webserver should print arguments")="no"; arg().set("webserver-address","IP Address of webserver to listen on")="127.0.0.1"; @@ -216,6 +223,7 @@ int &numanswered6=*S.getPointer("udp6-answers"); numreceived=-1; int diff; + bool noCache; for(;;) { if(!((numreceived++)%50)) { // maintenance tasks @@ -237,7 +245,16 @@ S.ringAccount("queries", P->qdomain+"/"+P->qtype.getName()); S.ringAccount("remotes",P->getRemote()); - if((P->d.opcode != Opcode::Notify) && PC.get(P,&cached)) { // short circuit - does the PacketCache recognize this question? + noCache=false; + + if (MXS && MXS->active) + if ((P->qtype.getCode()==QType::MX) || (P->qtype.getCode()==QType::ANY)) + noCache=true; + + if (P->d.opcode==Opcode::Notify) + noCache=true; + + if((!noCache) && PC.get(P,&cached)) { // short circuit - does the PacketCache recognize this question? cached.setRemote(&P->remote); // inlined cached.setSocket(P->getSocket()); // inlined cached.spoofID(P->d.id); // inlined @@ -294,6 +311,10 @@ DP->onlyFrom(arg()["allow-recursion"]); DP->go(); } + + if (arg().mustDo("mxselector")) + MXS = new MXSelector(arg()["mxselector-map"], arg()["mxselector-domains"], arg().asNum("mxselector-cache-size")); + // NOW SAFE TO CREATE THREADS! dl->go(); diff -Nru pdns-2.9.21.orig/pdns/dnspacket.cc pdns-2.9.21/pdns/dnspacket.cc --- pdns-2.9.21.orig/pdns/dnspacket.cc 2007-04-21 15:56:36.000000000 +0200 +++ pdns-2.9.21/pdns/dnspacket.cc 2007-11-07 13:14:47.000000000 +0100 @@ -38,6 +38,9 @@ #include "arguments.hh" #include "dnswriter.hh" #include "dnsparser.hh" +#include "mxselector.hh" + +MXSelector *MXS = NULL; DNSPacket::DNSPacket() { @@ -153,6 +156,10 @@ return; } + if (rr.qtype.getCode()==QType::MX) + if (MXS && (! MXS->check(getRemote(), rr.priority, rr.qname))) + return; + rrs.push_back(rr); } diff -Nru pdns-2.9.21.orig/pdns/mxselector.cc pdns-2.9.21/pdns/mxselector.cc --- pdns-2.9.21.orig/pdns/mxselector.cc 1970-01-01 01:00:00.000000000 +0100 +++ pdns-2.9.21/pdns/mxselector.cc 2007-11-07 13:36:55.000000000 +0100 @@ -0,0 +1,493 @@ +/* + Selective serving of MX records for PowerDNS (http://www.powerdns.com/) + + Copyright (C) 2007 Daniel Bilik (daniel.bilik@neosystem.cz) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include "config.h" +#include "arguments.hh" +#include "logger.hh" +#include "statbag.hh" +#include "mxselector.hh" + +extern StatBag S; + +pthread_mutex_t map_lookup_lock; + +vector map_dummy_mxs_local; +vector map_dummy_mxs_remote; +vector map_dummy_priv_subnet; + +bool +map_open_dummy(const vector &map_source) +{ + map_dummy_mxs_local.push_back(10); + map_dummy_mxs_remote.push_back(20); + + map_dummy_priv_subnet.push_back("127"); + map_dummy_priv_subnet.push_back("10"); + map_dummy_priv_subnet.push_back("172.16"); + map_dummy_priv_subnet.push_back("172.17"); + map_dummy_priv_subnet.push_back("172.18"); + map_dummy_priv_subnet.push_back("172.19"); + map_dummy_priv_subnet.push_back("172.20"); + map_dummy_priv_subnet.push_back("172.21"); + map_dummy_priv_subnet.push_back("172.22"); + map_dummy_priv_subnet.push_back("172.23"); + map_dummy_priv_subnet.push_back("172.24"); + map_dummy_priv_subnet.push_back("172.25"); + map_dummy_priv_subnet.push_back("172.26"); + map_dummy_priv_subnet.push_back("172.27"); + map_dummy_priv_subnet.push_back("172.28"); + map_dummy_priv_subnet.push_back("172.29"); + map_dummy_priv_subnet.push_back("172.30"); + map_dummy_priv_subnet.push_back("172.31"); + map_dummy_priv_subnet.push_back("192.168"); + + return(true); +} + +void +map_close_dummy(void) +{ + map_dummy_mxs_local.clear(); + map_dummy_mxs_remote.clear(); + map_dummy_priv_subnet.clear(); +} + +bool +map_lookup_dummy(const string &remote, vector *mx) +{ + vector::const_iterator i; + + if (remote == "*") + *mx = map_dummy_mxs_remote; + else + for (i = map_dummy_priv_subnet.begin(); i != map_dummy_priv_subnet.end(); i++) + if (remote == *i) { + *mx = map_dummy_mxs_local; + break; + } + + return(true); +} + +#ifdef HAVE_BDB +#include + +DB *map_bdb_handle; + +bool +map_open_bdb(const vector &map_source) +{ + if ((map_bdb_handle = dbopen(map_source[0].c_str(), O_RDONLY, 0, DB_HASH, NULL)) == NULL) { + L << Logger::Error << "Opening BDB map failed: " << strerror(errno) << endl; + return(false); + } + + return(true); +} + +void +map_close_bdb(void) +{ + if (map_bdb_handle != NULL) + (map_bdb_handle->close)(map_bdb_handle); +} + +bool +map_lookup_bdb(const string &remote, vector *mx) +{ + DBT bdb_key, bdb_data; + char *bdb_value = NULL; + vector parts; + unsigned int x; + + DLOG(L << "Going to query BDB for key '" << remote << "'" << endl); + + bdb_key.data = (void *)remote.c_str(); + bdb_key.size = remote.length(); + + pthread_mutex_lock(&map_lookup_lock); + + switch ((map_bdb_handle->get)(map_bdb_handle, &bdb_key, &bdb_data, 0)) { + case -1: + pthread_mutex_unlock(&map_lookup_lock); + L << Logger::Error << "BDB query failed: " << strerror(errno) << endl; + return(false); + case 0: + if ((bdb_value = (char *)malloc(bdb_data.size + 1)) == NULL) { + pthread_mutex_unlock(&map_lookup_lock); + L << Logger::Error << "Failed to allocate memory for BDB result: " << strerror(errno) << endl; + return(false); + } + memcpy(bdb_value, bdb_data.data, bdb_data.size); + pthread_mutex_unlock(&map_lookup_lock); + bdb_value[bdb_data.size] = '\0'; + DLOG(L << "BDB returned: " << bdb_value << endl); + stringtok(parts, bdb_value, ","); + for (x = 0; x < parts.size(); x++) + (*mx).push_back(atoi(parts[x].c_str())); + free(bdb_value); + break; + default: + pthread_mutex_unlock(&map_lookup_lock); + break; + } + + return(true); +} +#endif + +#ifdef HAVE_CDB +#include + +struct cdb map_cdb_handle; + +bool +map_open_cdb(const vector &map_source) +{ + int x; + + if ((x = open(map_source[0].c_str(), O_RDONLY)) == -1) { + L << Logger::Error << "Opening CDB map failed: " << strerror(errno) << endl; + return(false); + } + + cdb_init(&map_cdb_handle, x); + + close(x); + + return(true); +} + +void +map_close_cdb(void) +{ + cdb_free(&map_cdb_handle); +} + +bool +map_lookup_cdb(const string &remote, vector *mx) +{ + uint32_t cdb_key_pos, cdb_key_len; + char *cdb_key = (char *)remote.c_str(); + char *cdb_value = NULL; + vector parts; + unsigned int x; + + DLOG(L << "Going to query CDB for key '" << remote << "'" << endl); + + pthread_mutex_lock(&map_lookup_lock); + + switch (cdb_find(&map_cdb_handle, cdb_key, strlen(cdb_key))) { + case -1: + pthread_mutex_unlock(&map_lookup_lock); + L << Logger::Error << "CDB query failed: " << strerror(errno) << endl; + return(false); + case 1: + cdb_key_pos = cdb_datapos(&map_cdb_handle); + cdb_key_len = cdb_datalen(&map_cdb_handle); + pthread_mutex_unlock(&map_lookup_lock); + if ((cdb_value = (char *)malloc(cdb_key_len + 1)) == NULL) { + L << Logger::Error << "Failed to allocate memory for CDB result: " << strerror(errno) << endl; + return(false); + } + if (cdb_read(&map_cdb_handle, cdb_value, cdb_key_len, cdb_key_pos) == -1) { + L << Logger::Error << "Failed to get result for CDB query: " << strerror(errno) << endl; + free(cdb_value); + return(false); + } + cdb_value[cdb_key_len] = '\0'; + DLOG(L << "CDB returned: " << cdb_value << endl); + stringtok(parts, cdb_value, ","); + for (x = 0; x < parts.size(); x++) + (*mx).push_back(atoi(parts[x].c_str())); + free(cdb_value); + break; + default: + pthread_mutex_unlock(&map_lookup_lock); + break; + } + + return(true); +} +#endif + +#ifdef HAVE_SQLITE3 +#include + +sqlite3 *map_sqlite_handle; +string map_sqlite_table; + +bool +map_open_sqlite(const vector &map_source) +{ + if (sqlite3_open(map_source[0].c_str(), &map_sqlite_handle) != SQLITE_OK) { + L << Logger::Error << "Opening SQLite map failed: " << sqlite3_errmsg(map_sqlite_handle) << endl; + return(false); + } + + map_sqlite_table = "SELECT mx FROM " + map_source[1] + " WHERE remote='"; + + return(true); +} + +void +map_close_sqlite(void) +{ + if (map_sqlite_handle != NULL) + sqlite3_close(map_sqlite_handle); +} + +static int +map_sqlite_callback(void *i, int argc, char **argv, char **colname) +{ + vector *mxs = (vector *)i; + vector parts; + unsigned int x; + + if (argc == 0) + return(0); + + DLOG(L << "SQLite returned: " << argv[0] << endl); + + stringtok(parts, argv[0], ","); + + for (x = 0; x < parts.size(); x++) + (*mxs).push_back(atoi(parts[x].c_str())); + + return(0); +} + +bool +map_lookup_sqlite(const string &remote, vector *mxs) +{ + string query; + char *errmsg; + + query = map_sqlite_table + remote + "'"; + + DLOG(L << "Going to query SQLite: " << query << endl); + + pthread_mutex_lock(&map_lookup_lock); + + if (sqlite3_exec(map_sqlite_handle, query.c_str(), map_sqlite_callback, mxs, &errmsg) != SQLITE_OK) { + L << Logger::Error << "SQLite query failed: " << errmsg << endl; + sqlite3_free(errmsg); + pthread_mutex_unlock(&map_lookup_lock); + return(false); + } + + pthread_mutex_unlock(&map_lookup_lock); + + return(true); +} +#endif + +MXSelector::MXSelector(const string &source, const string &domains, const int cache) +{ + vector::const_iterator i; + + active = false; + map_type = UNKNOWN; + + if ((pthread_mutex_init(&map_cache_lock, NULL)) != 0) { + L << Logger::Warning << "Unable to initialize map cache mutex, MX selector not active" << endl; + return; + } + + if ((pthread_mutex_init(&map_lookup_lock, NULL)) != 0) { + L << Logger::Warning << "Unable to initialize map lookup mutex, MX selector not active" << endl; + return; + } + + stringtok(map_source, source, ":"); + + if (map_source.size() < 2) { + L << Logger::Warning << "Unable to parse '" << source << "', MX selector not active" << endl; + map_source.clear(); + return; + } + + if (map_source[0] == "dummy") { + map_type = DUMMY; + map_open = map_open_dummy; + map_close = map_close_dummy; + map_lookup = map_lookup_dummy; + } +#ifdef HAVE_BDB + if (map_source[0] == "bdb") { + map_type = BDB; + map_open = map_open_bdb; + map_close = map_close_bdb; + map_lookup = map_lookup_bdb; + } +#endif +#ifdef HAVE_CDB + if (map_source[0] == "cdb") { + map_type = CDB; + map_open = map_open_cdb; + map_close = map_close_cdb; + map_lookup = map_lookup_cdb; + } +#endif +#ifdef HAVE_SQLITE3 + if (map_source[0] == "sqlite") { + map_type = SQLITE; + map_open = map_open_sqlite; + map_close = map_close_sqlite; + map_lookup = map_lookup_sqlite; + } +#endif + + if (map_type == UNKNOWN) { + L << Logger::Warning << "Unknown map type '" << map_source[0] << "', MX selector not active" << endl; + map_source.clear(); + return; + } + + map_source.erase(map_source.begin()); + + if (! map_open(map_source)) { + L << Logger::Error << "Failed to open map source, MX selector not active" << endl; + map_source.clear(); + return; + } + + stringtok(mxs_domains, domains, ", \t"); + + if (mxs_domains.size() == 0) { + L << Logger::Warning << "Unable to parse domains, MX selector not active" << endl; + map_source.clear(); + return; + } + + S.declare("mxselector-cache-hit", "Number of lookups done from cache"); + S.declare("mxselector-cache-miss", "Number of lookups passed to backend"); + S.declare("mxselector-cache-size", "Number of cached map entries"); + + stat_map_cache_hit = S.getPointer("mxselector-cache-hit"); + stat_map_cache_miss = S.getPointer("mxselector-cache-miss"); + stat_map_cache_size = S.getPointer("mxselector-cache-size"); + map_cache_max = cache; + active = true; + + L << Logger::Info << "MX selector activated for domain(s): "; + for (i = mxs_domains.begin(); i != mxs_domains.end(); i++) + L << Logger::Info << *i << " "; + + L << Logger::Info << endl << "MX selector is using map '" << source << "', maximum cache size is " << map_cache_max << endl; + + if (! map_lookup("*", &mxs_default)) + L << Logger::Warning << "MX selector failed to query the map for default MX records" << endl; +} + +MXSelector::~MXSelector() +{ + if (! active) + return; + + active = false; + + pthread_mutex_lock(&map_cache_lock); + pthread_mutex_lock(&map_lookup_lock); + + mxs_domains.clear(); + map_source.clear(); + map_cache.clear(); + map_close(); + + pthread_mutex_destroy(&map_cache_lock); + pthread_mutex_destroy(&map_lookup_lock); +} + +bool +MXSelector::check(const string &remote, int mx, const string &domain) +{ + string remote_subnet; + map >::const_iterator i; + vector::const_iterator j; + vector mxs; + unsigned int x; + + if (! active) + return(true); + + if (find(mxs_domains.begin(), mxs_domains.end(), domain) == mxs_domains.end()) { + DLOG(L << "Not doing MX selection for '" << domain << "'" << endl); + return(true); + } + + pthread_mutex_lock(&map_cache_lock); + + if ((! map_cache.empty()) && ((i = map_cache.find(remote)) != map_cache.end())) { + if ((*stat_map_cache_hit)++ > *stat_map_cache_hit) { + *stat_map_cache_hit = 0; + *stat_map_cache_miss = 0; + } + DLOG(L << "Entry for remote '" << remote << "' found in map cache" << endl); + } else { + if ((*stat_map_cache_miss)++ > *stat_map_cache_miss) { + *stat_map_cache_hit = 0; + *stat_map_cache_miss = 0; + } + DLOG(L << "Remote '" << remote << "' not found in map cache" << endl); + pthread_mutex_unlock(&map_cache_lock); + remote_subnet = string(remote); + while (mxs.empty()) { + DLOG(L << "Doing map lookup for '" << remote_subnet << "'" << endl); + if (! map_lookup(remote_subnet, &mxs)) { + L << Logger::Warning << "MX selector failed to query the map, using defaults" << endl; + mxs = mxs_default; + continue; + } + if ((x = remote_subnet.rfind('.')) != string::npos) + remote_subnet = remote_subnet.substr(0, x); + else if (mxs.empty()) { + DLOG(L << "MX selector failed to match the map, using defaults" << endl); + if (! mxs_default.empty()) + mxs = mxs_default; + else { + L << Logger::Warning << "MX selector has no defaults, MX record blindly acknowledged" << endl; + return(true); + } + } + } + DLOG(L << "Inserting map cache entry for '" << remote << "'" << endl); + pthread_mutex_lock(&map_cache_lock); + if (map_cache.size() == map_cache_max) { + DLOG(L << "Map cache is full, flushing all entries" << endl); + map_cache.clear(); + (*stat_map_cache_size) = 0; + } + i = map_cache.insert(map_cache.begin(), make_pair(remote, mxs)); + (*stat_map_cache_size)++; + } + + if ((j = find((i->second).begin(), (i->second).end(), mx)) != (i->second).end()) { + DLOG(L << "MX record of preference " << mx << " acknowledged to remote '" << remote << "'" << endl); + pthread_mutex_unlock(&map_cache_lock); + return(true); + } else { + DLOG(L << "MX record of preference " << mx << " denied to remote '" << remote << "'" << endl); + pthread_mutex_unlock(&map_cache_lock); + return(false); + } +} diff -Nru pdns-2.9.21.orig/pdns/mxselector.hh pdns-2.9.21/pdns/mxselector.hh --- pdns-2.9.21.orig/pdns/mxselector.hh 1970-01-01 01:00:00.000000000 +0100 +++ pdns-2.9.21/pdns/mxselector.hh 2007-11-06 13:05:14.000000000 +0100 @@ -0,0 +1,56 @@ +/* + Selective serving of MX records for PowerDNS (http://www.powerdns.com/) + + Copyright (C) 2007 Daniel Bilik (daniel.bilik@neosystem.cz) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef PDNS_MXSELECTOR +#define PDNS_MXSELECTOR + +using namespace std; + +class MXSelector +{ + public: + MXSelector(const string &source, const string &domains, const int cache); + ~MXSelector(); + bool active; + bool check(const string &remote, int mx, const string &domain); + + private: + typedef bool MapOpen(const vector &map_source); + typedef void MapClose(void); + typedef bool MapLookup(const string &remote, vector *mx); + + MapOpen *map_open; + MapClose *map_close; + MapLookup *map_lookup; + + vector mxs_domains; + vector mxs_default; + + enum { UNKNOWN, DUMMY, BDB, CDB, SQLITE } map_type; + vector map_source; + pthread_mutex_t map_cache_lock; + map > map_cache; + uint16_t map_cache_max; + + int *stat_map_cache_hit; + int *stat_map_cache_miss; + int *stat_map_cache_size; +}; + +#endif