- big LDAP cleanup
- LDAP now has TLS support
- Opteron support (opteron login by jeffdavey@submersion.com)
-
+ - fixed errors with disable-axfr (Norbert)
+ - improved error reporting in zone2sql (Thom May)
+ - Zone2LDAP updates:
+ Now it's possible to generate ldif files containing a tree or a list of entries.
changes since 2.9.11:
- ldap updates
dnl intro
AC_INIT(pdns/receiver.cc)
-AM_INIT_AUTOMAKE(pdns, 2.9.12)
+AM_INIT_AUTOMAKE(pdns, 2.9.13)
AC_CANONICAL_HOST
AM_CONFIG_HEADER(config.h)
AC_C_BIGENDIAN
+pdns (2.9.13-1) unstable; urgency=low
+
+ * New upstream release
+
+ -- Wichert Akkerman <wakkerma@debian.org> Sun, 21 Sep 2003 14:59:46 +0200
+
pdns (2.9.12-1) unstable; urgency=low
* New upstream release
throw( AhuException( "Unable to connect to ldap server" ) );
}
- L << Logger::Info << backendname << " Ldap connection succeeded" << endl;
+ L << Logger::Notice << backendname << " Ldap connection succeeded" << endl;
}
m_axfrqlen = target.length();
m_adomain = m_adomains.end(); // skip loops in get() first time
- DLOG( L << Logger::Debug << backendname << " List = target: " << target << endl );
filter = "(|(associatedDomain=" + target + ")(associatedDomain=*." + target + "))";
m_msgid = m_pldap->search( getArg("basedn"), LDAP_SCOPE_SUBTREE, filter, (const char**) attrany );
}
catch( LDAPTimeout < )
{
- L << Logger::Error << backendname << " Unable to get zone " + target + " from LDAP directory: " << lt.what() << endl;
+ L << Logger::Warning << backendname << " Unable to get zone " + target + " from LDAP directory: " << lt.what() << endl;
return false;
}
catch( LDAPException &le )
}
catch( exception &e )
{
- L << Logger::Error << backendname << " Caught STL exception: " << e.what() << endl;
+ L << Logger::Error << backendname << " Caught STL exception for target " << target << ": " << e.what() << endl;
return false;
}
catch( ... )
{
- L << Logger::Critical << backendname << " Caught unknown exception" << endl;
+ L << Logger::Critical << backendname << " Caught unknown exception for target " << target << endl;
return false;
}
}
catch( LDAPTimeout < )
{
- L << Logger::Error << backendname << " Unable to search LDAP directory: " << lt.what() << endl;
+ L << Logger::Warning << backendname << " Unable to search LDAP directory: " << lt.what() << endl;
return;
}
catch( LDAPException &le )
}
catch( exception &e )
{
- L << Logger::Error << backendname << " Caught STL exception: " << e.what() << endl;
+ L << Logger::Error << backendname << " Caught STL exception for qname " << qname << ": " << e.what() << endl;
return;
}
catch( ... )
{
- L << Logger::Error << backendname << " Caught unknown exception" << endl;
+ L << Logger::Critical << backendname << " Caught unknown exception for qname " << qname << endl;
return;
}
}
rr.priority = 0;
rr.ttl = m_ttl;
- if( qt.getCode() == QType::MX ) // MX Record, e.g. 10 smtp.example.com
+ if( qt.getCode() == QType::MX || qt.getCode() == QType::SRV ) // Priority, e.g. 10 smtp.example.com
{
- parts.clear();
- stringtok( parts, content, " " );
+ char* endptr;
+ string::size_type first = content.find_first_of( " " );
- if( parts.size() != 2)
+ if( first == string::npos )
{
- L << Logger::Warning << backendname << " Invalid MX record without priority: " << content << endl;
+ L << Logger::Warning << backendname << " Invalid " << attrname << " without priority for " << m_qname << ": " << content << endl;
+ m_value++;
continue;
}
- rr.priority = (u_int16_t) strtol( parts[0].c_str(), NULL, 10 );
- content = parts[1];
+ rr.priority = (u_int16_t) strtoul( (content.substr( 0, first )).c_str(), &endptr, 10 );
+ if( *endptr != '\0' )
+ {
+ L << Logger::Warning << backendname << " Invalid " << attrname << " without priority for " << m_qname << ": " << content << endl;
+ m_value++;
+ continue;
+ }
+
+ content = content.substr( first + 1, content.length() - first - 1 );
}
rr.content = content;
m_value++;
- DLOG( L << Logger::Debug << backendname << " Record = qname: " << rr.qname << ", qtype: " << (rr.qtype).getName() << ", priority: " << rr.priority << ", content: " << rr.content << endl );
+ DLOG( L << Logger::Debug << backendname << " Record = qname: " << rr.qname << ", qtype: " << (rr.qtype).getName() << ", priority: " << rr.priority << ", ttl: " << rr.ttl << ", content: " << rr.content << endl );
return true;
}
m_attribute = m_result.begin();
m_value = m_attribute->second.begin();
}
- m_result.clear();
}
while( m_pldap->getSearchEntry( m_msgid, m_result, false ) && prepareEntry() );
}
catch( LDAPTimeout < )
{
- L << Logger::Error << backendname << " Search failed: " << lt.what() << endl;
+ L << Logger::Warning << backendname << " Search failed: " << lt.what() << endl;
}
catch( LDAPException &le )
{
}
catch( exception &e )
{
- L << Logger::Error << backendname << " Caught STL exception: " << e.what() << endl;
+ L << Logger::Error << backendname << " Caught STL exception for attribute " << attrname << ": " << e.what() << endl;
}
catch( ... )
{
- L << Logger::Error << backendname << " Caught unknown exception" << endl;
+ L << Logger::Critical << backendname << " Caught unknown exception for attribute " << attrname << endl;
}
return false;
{
declare( suffix, "host", "one or more ldap server","localhost:389" );
declare( suffix, "port", "ldap server port (depricated, use ldap-host)","389" );
- declare( suffix, "starttls", "use STARTTLS to encrypt connection", "no" );
+ declare( suffix, "starttls", "use TLS to encrypt connection", "no" );
declare( suffix, "basedn", "search root in ldap tree (must be set)","" );
declare( suffix, "binddn", "user dn for non anonymous binds","" );
declare( suffix, "secret", "user password for non anonymous binds", "" );
-PowerLDAP::PowerLDAP( const string& host, u_int16_t port, bool tls ) : d_timeout( 5 )
+PowerLDAP::PowerLDAP( const string& host, u_int16_t port, bool tls )
{
int protocol = LDAP_VERSION3;
}
-/** Function waits for a result, returns its type and optionally stores the result
- in retresult. If returned via retresult, the caller is responsible for freeing
- it with ldap_msgfree! */
-int PowerLDAP::waitResult(int msgid,LDAPMessage **retresult)
+int PowerLDAP::search( const string& base, int scope, const string& filter, const char** attr )
{
- struct timeval tv;
- tv.tv_sec=d_timeout;
- tv.tv_usec=0;
- LDAPMessage *result;
-
- int rc=ldap_result(d_ld,msgid,0,&tv,&result);
- if(rc==-1)
- throw LDAPException("Error waiting for LDAP result: "+getError());
- if(!rc)
- throw LDAPTimeout();
-
- if(retresult)
- *retresult=result;
-
- if(rc==LDAP_RES_SEARCH_ENTRY || LDAP_RES_SEARCH_RESULT) // no error in that case
- return rc;
-
- int err;
- if((err=ldap_result2error(d_ld, result,0))!=LDAP_SUCCESS) {
- ldap_msgfree(result);
- throw LDAPException("LDAP Server reported error: "+getError(err));
- }
-
- if(!retresult)
- ldap_msgfree(result);
-
- return rc;
+ int msgid;
+ if( ( msgid = ldap_search( d_ld, base.c_str(), scope, filter.c_str(), const_cast<char**> (attr), 0 ) ) == -1 )
+ {
+ throw LDAPException( "Starting LDAP search: " + getError() );
+ }
+
+ return msgid;
}
-int PowerLDAP::search(const string& base, int scope, const string& filter, const char **attr)
+/**
+ * Function waits for a result, returns its type and optionally stores the result.
+ * If the result is returned, the caller is responsible for freeing it with
+ * ldap_msgfree!
+ */
+
+int PowerLDAP::waitResult( int msgid, int timeout, LDAPMessage** result )
{
- int msgid;
+ int rc;
+ struct timeval tv;
+ LDAPMessage* res;
+
+
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+
+ if( ( rc = ldap_result( d_ld, msgid, LDAP_MSG_ONE, &tv, &res ) ) == -1 )
+ {
+ throw LDAPException( "Error waiting for LDAP result: " + getError() );
+ }
+ else if( rc == 0 )
+ {
+ throw LDAPTimeout();
+ }
- if( ( msgid = ldap_search( d_ld, base.c_str(), scope, filter.c_str(),const_cast<char **>(attr),0 ) ) == -1 )
- throw LDAPException("Starting LDAP search: "+getError());
+ if( result == NULL )
+ {
+ ldap_msgfree( res );
+ return rc;
+ }
- return msgid;
+ *result = res;
+ return rc;
}
-bool PowerLDAP::getSearchEntry(int msgid, sentry_t &entry, bool withdn)
+
+bool PowerLDAP::getSearchEntry( int msgid, sentry_t& entry, bool dn, int timeout )
{
- entry.clear();
- int rc=waitResult(msgid,&d_searchresult);
-
- if(rc==LDAP_RES_SEARCH_RESULT) {
- ldap_msgfree(d_searchresult);
- return false;
- }
-
- if(rc!=LDAP_RES_SEARCH_ENTRY)
- throw LDAPException("Search returned non-answer result");
-
- d_searchentry=ldap_first_entry(d_ld, d_searchresult);
-
- // we now have an entry in d_searchentry
-
- if( withdn == true )
- {
- vector<string> dnresult;
- char* dn = ldap_get_dn( d_ld, d_searchentry );
- dnresult.push_back( dn );
- ldap_memfree( dn );
- entry["dn"] = dnresult;
- }
-
- BerElement *ber;
-
- for(char *attr = ldap_first_attribute( d_ld, d_searchresult, &ber ); attr ; attr=ldap_next_attribute(d_ld, d_searchresult, ber)) {
- struct berval **bvals=ldap_get_values_len(d_ld,d_searchentry,attr);
- vector<string> rvalues;
- if(bvals) {
- for(struct berval** bval=bvals;*bval;++bval)
- rvalues.push_back((*bval)->bv_val);
- }
- entry[attr]=rvalues;
- ldap_value_free_len(bvals);
- ldap_memfree(attr);
- }
-
- ber_free(ber,0);
- ldap_msgfree(d_searchresult);
-
- return true;
+ int i;
+ char* attr;
+ BerElement* ber;
+ struct berval** berval;
+ vector<string> values;
+ LDAPMessage* result;
+ LDAPMessage* object;
+
+
+ if( ( i = waitResult( msgid, timeout, &result ) ) == LDAP_RES_SEARCH_RESULT )
+ {
+ ldap_msgfree( result );
+ return false;
+ }
+
+ if( i != LDAP_RES_SEARCH_ENTRY )
+ {
+ ldap_msgfree( result );
+ throw LDAPException( "Search returned an unexpected result" );
+ }
+
+ if( ( object = ldap_first_entry( d_ld, result ) ) == NULL )
+ {
+ ldap_msgfree( result );
+ throw LDAPException( "Couldn't get first result entry: " + getError() );
+ }
+
+ entry.clear();
+
+ if( dn )
+ {
+ attr = ldap_get_dn( d_ld, object );
+ values.push_back( attr );
+ ldap_memfree( attr );
+ entry["dn"] = values;
+ }
+
+ if( ( attr = ldap_first_attribute( d_ld, object, &ber ) ) != NULL )
+ {
+ do
+ {
+ if( ( berval = ldap_get_values_len( d_ld, object, attr ) ) != NULL )
+ {
+ values.clear();
+ for( i = 0; i < ldap_count_values_len( berval ); i++ )
+ {
+ values.push_back( berval[i]->bv_val ); // use berval[i]->bv_len for non string values?
+ }
+
+ entry[attr] = values;
+ ldap_value_free_len( berval );
+ }
+ ldap_memfree( attr );
+ }
+ while( ( attr = ldap_next_attribute( d_ld, object, ber ) ) != NULL );
+
+ ber_free( ber, 0 );
+ }
+
+ ldap_msgfree( result );
+ return true;
}
-void PowerLDAP::getSearchResults(int msgid, sresult_t &result, bool withdn)
+
+void PowerLDAP::getSearchResults( int msgid, sresult_t& result, bool dn, int timeout )
{
- result.clear();
- sentry_t entry;
- while(getSearchEntry(msgid, entry, withdn))
- result.push_back(entry);
+ sentry_t entry;
+
+ result.clear();
+ while( getSearchEntry( msgid, entry, dn, timeout ) )
+ {
+ result.push_back( entry );
+ }
}
}
-const string PowerLDAP::escape(const string &name)
+const string PowerLDAP::escape( const string& str )
{
- string a;
-
- for(string::const_iterator i=name.begin();i!=name.end();++i) {
- if(*i=='*' || *i=='\\')
- a+='\\';
- a+=*i;
- }
- return a;
+ string a;
+ string::const_iterator i;
+
+ for( i = str.begin(); i != str.end(); i++ )
+ {
+ if( *i == '*' || *i == '\\' ) {
+ a += '\\';
+ }
+ a += *i;
+ }
+
+ return a;
}
class PowerLDAP
{
LDAP* d_ld;
- int d_timeout;
- LDAPMessage* d_searchresult;
- LDAPMessage* d_searchentry;
const string getError( int rc = -1 );
- int waitResult( int msgid = LDAP_RES_ANY, LDAPMessage** retresult = 0 );
+ int waitResult( int msgid = LDAP_RES_ANY, int timeout = 0, LDAPMessage** result = NULL );
public:
typedef map<string, vector<string> > sentry_t;
void simpleBind( const string& ldapbinddn = "", const string& ldapsecret = "" );
int search( const string& base, int scope, const string& filter, const char** attr = 0 );
- bool getSearchEntry( int msgid, sentry_t& entry, bool withdn = false );
- void getSearchResults( int msgid, sresult_t& result, bool withdn = false );
+ bool getSearchEntry( int msgid, sentry_t& entry, bool dn = false, int timeout = 5 );
+ void getSearchResults( int msgid, sresult_t& result, bool dn = false, int timeout = 5 );
static const string escape( const string& tobe );
};
Buildroot: /tmp/pdns
Name: pdns-static
-Version: 2.9.12
+Version: 2.9.13
Release: 1
Summary: extremely powerful and versatile nameserver
Copyright: see /usr/doc/pdns/copyright
string g_basedn;
string g_zonename;
map<string,bool> g_objects;
+map<string,bool> g_nodes;
-static void callback( unsigned int domain_id, const string &domain, const string &qtype, const string &content, int ttl, int prio )
+static void callback_list( unsigned int domain_id, const string &domain, const string &qtype, const string &content, int ttl, int prio )
{
+ string host;
vector<string> parts;
string domain2 = ZoneParser::canonic( domain );
string content2 = ZoneParser::canonic( content );
+ host = domain2.substr( 0, domain2.rfind( g_zonename ) );
+ host = ZoneParser::canonic( host );
+
+ cout << "dn: dc=";
+ if( !host.empty() ) { cout << host << ",dc="; }
+ cout << g_zonename << "," << g_basedn << endl;
+
+ if( host.empty() ) { host = g_zonename; }
+
+ if( !g_objects[domain2] )
+ {
+ g_objects[domain2] = true;
+ cout << "changetype: add" << endl;
+ cout << "objectclass: top" << endl;
+ if( domain2 == g_zonename ) { cout << "objectclass: dcobject" << endl; } // only necessary for phpgeneral
+ cout << "objectclass: dnsdomain2" << endl;
+ cout << "objectclass: domainrelatedobject" << endl;
+ cout << "dc: " << host << endl;
+ cout << "dnsttl: " << ttl << endl;
+ cout << "associateddomain: " << domain2 << endl;
+ }
+ else
+ {
+ cout << "changetype: modify" << endl;
+ }
+
+ cout << qtype << "Record: ";
+ if( prio != 0 ) { cout << prio << " "; }
+ cout << content2 << endl << endl;
+}
+
+
+static void callback_tree( unsigned int domain_id, const string &domain, const string &qtype, const string &content, int ttl, int prio )
+{
+ string subnet, net;
+ vector<string> parts, subparts;
+ vector<string>::const_iterator i, j;
+ string domain2 = ZoneParser::canonic( domain );
+ string content2 = ZoneParser::canonic( content );
+
+
+ subnet = domain2.substr( 0, domain2.rfind( g_zonename ) );
+ subnet = ZoneParser::canonic( subnet );
+ stringtok( parts, g_zonename, "." );
+ stringtok( subparts, subnet, "." );
+ net = g_zonename;
+
+ j = subparts.end();
+ while( --j != subparts.begin() )
+ {
+ net = *j + "." + net;
+ if( !g_nodes[net] )
+ {
+ g_nodes[net] = true;
+ cout << "dn: ";
+ for( i = j; i != subparts.end(); i++ )
+ {
+ cout << "dc=" << *i << ",";
+ }
+ for( i = parts.begin(); i != parts.end(); i++ )
+ {
+ cout << "dc=" << *i << ",";
+ }
+ cout << g_basedn << endl;
+
+ cout << "changetype: add" << endl;
+ cout << "objectclass: top" << endl;
+ cout << "objectclass: dcobject" << endl;
+ cout << "objectclass: domainrelatedobject" << endl;
+ cout << "dc: " << *j << endl;
+ cout << "associateddomain: " << net << endl << endl;
+ }
+ }
+
+ parts.clear();
stringtok( parts, domain2, "." );
- if( parts[0] == g_zonename ) {
- cout << "dn: dc=" << g_zonename << "," << g_basedn << endl;
- }else {
- cout << "dn: dc=" << parts[0] << ",dc=" << g_zonename << "," << g_basedn << endl;
+ cout << "dn: ";
+ for( i = parts.begin(); i != parts.end(); i++ )
+ {
+ cout << "dc=" << *i << ",";
}
+ cout << g_basedn << endl;
- if( g_objects[domain2] != true )
+ if( !g_objects[domain2] )
{
g_objects[domain2] = true;
cout << "changetype: add" << endl;
cout << "objectclass: top" << endl;
- if( parts[0] == g_zonename ) cout << "objectclass: dcobject" << endl; // only necessary for phpgeneral, my web based admin interface
- cout << "objectclass: dnsdomain" << endl;
+ if( domain2 == g_zonename ) { cout << "objectclass: dcobject" << endl; } // only necessary for phpgeneral
+ cout << "objectclass: dnsdomain2" << endl;
cout << "objectclass: domainrelatedobject" << endl;
cout << "dc: " << parts[0] << endl;
+ cout << "dnsttl: " << ttl << endl;
cout << "associateddomain: " << domain2 << endl;
}
else
cout << "changetype: modify" << endl;
}
- if( prio != 0 ) {
- cout << qtype << "Record: " << prio << " " << content2 << endl << endl;
- } else {
- cout << qtype << "Record: " << content2 << endl << endl;
- }
+ cout << qtype << "Record: ";
+ if( prio != 0 ) { cout << prio << " "; }
+ cout << content2 << endl << endl;
}
int main( int argc, char* argv[] )
{
- string namedfile = "";
- string zonefile = "";
- vector<string> parts;
BindParser BP;
ZoneParser ZP;
+ vector<string> parts;
- g_basedn = "";
- g_zonename = "";
-
try
{
#if __GNUC__ >= 3
args.setCmd( "help", "Provide a helpful message" );
args.setSwitch( "verbose", "Verbose comments on operation" ) = "no";
args.setSwitch( "resume", "Continue after errors" ) = "no";
- args.set( "zone", "Zonefile with $ORIGIN to parse" ) = "";
- args.set( "zone-name", "Specify an $ORIGIN in case it is not present" ) = "";
args.set( "named-conf", "Bind 8 named.conf to parse" ) = "";
+ args.set( "zone-file", "Zone file to parse" ) = "";
+ args.set( "zone-name", "Specify a zone name if zone is set" ) = "";
args.set( "basedn", "Base DN to store objects below" ) = "dc=example,dc=org";
+ args.set( "layout", "Arrange entries as list or tree" ) = "tree";
args.parse( argc, argv );
}
g_basedn = args["basedn"];
- namedfile = args["named-conf"];
- zonefile = args["zone"];
-
- ZP.setCallback( &callback );
- BP.setVerbose( args.mustDo( "verbose" ) );
- BP.parse( namedfile.empty() ? "./named.conf" : namedfile );
+ ZP.setCallback( &callback_tree );
+ if( args["layout"] == "list" )
+ {
+ ZP.setCallback( &callback_list );
+ }
- if( zonefile.empty() )
+ if( !args["named-conf"].empty() )
{
+ BP.setVerbose( args.mustDo( "verbose" ) );
+ BP.parse( args["named-conf"] );
ZP.setDirectory( BP.getDirectory() );
const vector<BindDomainInfo> &domains = BP.getDomains();
- for( vector<BindDomainInfo>::const_iterator i = domains.begin(); i != domains.end(); ++i )
+ for( vector<BindDomainInfo>::const_iterator i = domains.begin(); i != domains.end(); i++ )
{
try
{
- g_objects.clear();
- if( i->name != "." && i->name != "localhost" && i->name.substr( i->name.length() - 5, 5 ) != ".arpa" && i->name.substr( i->name.length() - 8, 8 ) != ".ip6.int" )
+ if( i->name != "." && i->name != "localhost" && i->name != "0.0.127.in-addr.arpa" )
{
cerr << "Parsing file: " << i->filename << ", domain: " << i->name << endl;
- stringtok( parts, i->name, "." );
- g_zonename = parts[0];
+ g_zonename = i->name;
+ g_nodes.clear();
+ g_objects.clear();
ZP.parse( i->filename, i->name, 0 );
}
}
}
else
{
- stringtok( parts, args["zone-name"], "." );
- g_zonename = parts[0];
+ if( args["zone-file"].empty() || args["zone-name"].empty() )
+ {
+ cerr << "Error: At least zone-file and zone-name are required" << endl;
+ return 1;
+ }
+
+ g_nodes.clear();
g_objects.clear();
+ g_zonename = args["zone-name"];
ZP.setDirectory( "." );
- ZP.parse( zonefile, args["zone-name"], 0 );
+ ZP.parse( args["zone-file"], args["zone-name"], 0 );
}
}
catch( AhuException &ae )
rec.qtype=qtype;
if(!QType::chartocode(qtype.c_str()))
- throw AhuException("Unknown qtype '"+qtype+"' on line "+itoa(d_lineno));
+ throw AhuException("Unknown qtype '"+qtype+"' on line "+itoa(d_lineno)+" of file '"+d_filename+"'");
rec.content=content;
rec.ttl=ttl;
rec.prio=prio;
return true;
}
else {
- throw AhuException("Unhandled command '"+words[0]+"' on line "+itoa(d_lineno)+" of "+d_filename);
+ throw AhuException("Unhandled command '"+words[0]+"' on line "+itoa(d_lineno)+" of '"+d_filename+"'");
}
return false;
</affiliation>
</author>
- <PubDate>v2.1 $Date: 2003/11/23 15:14:57 $</PubDate>
+ <PubDate>v2.1 $Date: 2003/11/29 16:49:42 $</PubDate>
<Abstract>
<para>
file.
</para>
+ <sect2 id="changelog-2-9-13"><title>Version 2.9.13</title>
+ <para>
+ Some bugfixes, some small features. Only one big bugfix, it appears we are heading for stability!
+ </para>
+ <para>
+ Bug fixes:
+ <itemizedlist>
+ <listitem>
+ <para>
+ <command>allow-axfr-ips</command> did not work for individual IP addresses (bug & fix by Norbert Sendetzky)
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ <para>
+ Improvements:
+ <itemizedlist>
+ <listitem>
+ <para>
+ Opteron support! Thanks to Jeff Davey for providing a shell on an Opteron. The fixes should
+ also help PowerDNS on other platforms with a 64 bit userspace.
+ </para>
+ <para>
+ Btw, the PowerDNS team has a strong desire for an Opteron :-)
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ pdns_recursor jumbles answers now. This means that you can do poor man's roundrobin
+ by supplying multiple A, MX or AAAA records for a service, and get a random one on top
+ each time. Interestingly, this feature appeared out of nowhere, this change was made to the
+ authoritative code but due to the wonders of code-reuse had an effect on pdns_recursor too.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Big LDAP cleanup. Support for TLS was added. Zone2LDAP also gained the ability to
+ generate ldif files containing a tree or a list of entries. (Norbert Sendetzky)
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Zone2sql is now somewhat clearer when reporting malformed line errors - it did not always
+ include the name of the file causing a problem, especially for big installations. Problem noted
+ by Thom May.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ pdns_recursor now survives the expiration of all its root records, most often caused by prolonged
+ disconnection from the net.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ </sect2>
+
<sect2 id="changelog-2-9-12"><title>Version 2.9.12</title>
- <para>
+ <para>
Release rich in features. Work on Verisign oddities, addition of SQLite backend, pdns_recursor maturity.
</para>
<para>
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>Q: PowerDNS does not give authoritative answers, how come?</term>
+ <listitem>
+ <para>
+ A: This is almost always not the case. An authoritative answer is recognized by the 'AA' bit being set. Many tools
+ prominently print the number of Authority records included in an answer, leading users to conclude that the
+ absence or presence of these records indicates the authority of an answer. This is not the case.
+ </para>
+ <para>
+ Verily, many misguided country code domain operators have fallen into this trap and demand authority records, even though
+ these are fluff and quite often misleading. Invite such operators to look at section 6.2.1 of RFC 1034, which shows a correct
+ authoritative answer without authority records. In fact, none of the non-deprecated authoritative answers shown have authority
+ records!
+ </para>
+ <para>
+ Sorry for sounding like DJB on this, but we get so many misguided questions about authority..
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term>Q: Which backend should I use? There are so many!</term>
<listitem>