In case the used LDAP client library doesn't support LDAP URIs as connection parameter, use plain host names or IP addresses instead (both may optionally be followed by a colon and the port).
## `ldap-starttls`
-(default "no") : Use TLS encrypted connections to the LDAP server.
-This is only allowed if `ldap-host` is a `ldap://` URI or a host name / IP address.
+(default "no") : Use TLS encrypted connections to the LDAP server. This is only allowed if ldap-host is a <ldap://> URI or a host name / IP address.
-## `ldap-basedn`
-(default "") : The PowerDNS LDAP DNS backend searches below this path for objects containing the specified DNS information.
-The retrieval of attributes is limited to this subtree.
-This option must be set to the path according to the layout of the LDAP tree, e.g. `ou=hosts,o=example,c=net` is the DN to my objects containing the DNS information.
+## `ldap-authmethod`
+(default: "simple") : How to authenticate to the LDAP server. Actually only two methods are supported: "simple", which uses the classical DN / password, or "gssapi", which requires a Kerberos keytab.
## `ldap-binddn`
-(default "") : Path to the object to authenticate against.
-Should only be used if the LDAP server doesn't support anonymous binds.
+(default "") : Path to the object to authenticate against. Should only be used, if the LDAP server doesn't support anonymous binds and with the "simple" authmethod.
## `ldap-secret`
-(default "") : Password for authentication against the object specified by `ldap-binddn`.
+(default "") : Password for authentication against the object specified by ldap-binddn. Only used when "authmethod" is "simple".
+
+## `ldap-krb5-keytab`
+(default: "") : Full path to the keytab file to use to authenticate. This is only used when "authmethod" is set to "gssapi". The keytab must, ideally, contain only one principal (or to put it otherwise, only the first principal found in the keytab will be used).
+
+## `ldap-krb5-ccache`
+(default: "") : Full path to the Kerberos credential cache file to use. Actually only files are supported, and the "FILE:" prefix must not be set. The PowerDNS process must be able to write to this file and it *must* be the only one able to read it.
+
+## `ldap-basedn`
+(default "") : The PowerDNS LDAP DNS backend searches below this path for objects containing the specified DNS information. The retrieval of attributes is limited to this subtree. This option must be set to the path according to the layout of your LDAP tree, e.g. ou=hosts,o=linuxnetworks,c=de is the DN to my objects containing the DNS information.
## `ldap-method`
(default "simple") :
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#include <krb5.h>
#include <pdns/logger.hh>
#include "ldapauthenticator_p.hh"
#include "ldaputils.hh"
{
lastError = ldapGetError( conn, code );
}
+
+/*****************************
+ *
+ * LdapGssapiAuthenticator
+ *
+ ****************************/
+
+static int ldapGssapiAuthenticatorSaslInteractCallback( LDAP *conn, unsigned flags, void *defaults, void *in )
+{
+ return LDAP_SUCCESS;
+}
+
+LdapGssapiAuthenticator::LdapGssapiAuthenticator( const std::string& kt, const std::string &ccache, int tmout )
+ : keytabFile( kt ), cCacheFile( ccache ), timeout( tmout )
+{
+}
+
+bool LdapGssapiAuthenticator::authenticate( LDAP *conn )
+{
+ int code = attemptAuth( conn );
+
+ if ( code == -1 ) {
+ return false;
+ }
+ else if ( code == -2 ) {
+ // Here it may be possible to retry after obtainting a fresh ticket
+ L<<Logger::Debug << "LDAP GSSAPI" << "No TGT found, trying to acquire a new one" << std::endl;
+ code = updateTgt();
+
+ if ( attemptAuth( conn ) != 0 ) {
+ L<<Logger::Error << "LDAP GSSAPI" << "Failed to acquire a TGT" << std::endl;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+std::string LdapGssapiAuthenticator::getError() const
+{
+ return lastError;
+}
+
+int LdapGssapiAuthenticator::attemptAuth( LDAP *conn )
+{
+ // Create SASL defaults
+ SaslDefaults defaults;
+ char *ldapOption = 0;
+
+ ldap_get_option( conn, LDAP_OPT_X_SASL_MECH, ldapOption );
+ if ( !ldapOption )
+ defaults.mech = std::string( "GSSAPI" );
+ else
+ defaults.mech = std::string( ldapOption );
+ ldap_memfree( ldapOption );
+
+ ldap_get_option( conn, LDAP_OPT_X_SASL_REALM, ldapOption );
+ if ( ldapOption )
+ defaults.realm = std::string( ldapOption );
+ ldap_memfree( ldapOption );
+
+ ldap_get_option( conn, LDAP_OPT_X_SASL_AUTHCID, ldapOption );
+ if ( ldapOption )
+ defaults.authcid = std::string( ldapOption );
+ ldap_memfree( ldapOption );
+
+ ldap_get_option( conn, LDAP_OPT_X_SASL_AUTHZID, ldapOption );
+ if ( ldapOption )
+ defaults.authzid = std::string( ldapOption );
+ ldap_memfree( ldapOption );
+
+ // And now try to bind
+ int rc = ldap_sasl_interactive_bind_s( conn, "", defaults.mech.c_str(),
+ NULL, NULL, LDAP_SASL_QUIET,
+ ldapGssapiAuthenticatorSaslInteractCallback, &defaults );
+ L<<Logger::Debug << "LDAP GSSAPI" << "ldap_sasl_interactive_bind_s returned " << rc << std::endl;
+
+ if ( rc == LDAP_LOCAL_ERROR ) {
+ // This may mean that the ticket has expired, so let the caller know
+ lastError = ldapGetError( conn, rc );
+ return -2;
+ }
+ else if ( rc != LDAP_SUCCESS ) {
+ lastError = ldapGetError( conn, rc );
+ return -1;
+ }
+
+ return rc;
+}
+
+int LdapGssapiAuthenticator::updateTgt()
+{
+ krb5_error_code code;
+ krb5_context context;
+ krb5_creds credentials;
+ krb5_keytab keytab;
+ krb5_principal principal;
+ krb5_ccache ccache;
+ krb5_get_init_creds_opt *options;
+
+ if ( ( code = krb5_init_context( &context ) ) != 0 ) {
+ L<<Logger::Error << "LDAP GSSAPI" << "Failed to init krb5 context" << std::endl;
+ return code;
+ }
+
+ if ( !keytabFile.empty() ) {
+ std::string keytabStr( "FILE:" + keytabFile );
+ code = krb5_kt_resolve( context, keytabStr.c_str(), &keytab );
+ }
+ else {
+ code = krb5_kt_default( context, &keytab );
+ }
+
+ if ( code != 0 ) {
+ L<<Logger::Error << "LDAP GSSAPI" << "krb5 error: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+ return code;
+ }
+
+ // Extract the principal name from the keytab
+ krb5_kt_cursor cursor;
+ if ( ( code = krb5_kt_start_seq_get( context, keytab, &cursor ) ) != 0 ) {
+ L<<Logger::Error << "LDAP GSSAPI" << "krb5 error: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+ krb5_kt_close( context, keytab );
+ return code;
+ }
+
+ krb5_keytab_entry entry;
+ if ( ( code = krb5_kt_next_entry( context, keytab, &entry, &cursor ) ) == 0 ) {
+ code = krb5_copy_principal( context, entry.principal, &principal );
+ krb5_kt_free_entry( context, &entry );
+ }
+
+ krb5_kt_end_seq_get( context, keytab, &cursor );
+ if ( code != 0 ) {
+ L<<Logger::Error << "LDAP GSSAPI" << "krb5 error: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+ krb5_kt_close( context, keytab );
+ krb5_free_principal( context, principal );
+ return code;
+ }
+
+ // Locate the credentials cache file
+ if ( !cCacheFile.empty() ) {
+ std::string cCacheStr( "FILE:" + cCacheFile );
+ code = krb5_cc_resolve( context, cCacheStr.c_str(), &ccache );
+ }
+ else {
+ code = krb5_cc_default( context, &ccache );
+ }
+
+ if ( code != 0 ) {
+ L<<Logger::Error << "LDAP GSSAPI" << "krb5 error: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+ krb5_kt_close( context, keytab );
+ krb5_free_principal( context, principal );
+ return code;
+ }
+
+ // Initialize the credentials cache file
+ if ( ( code = krb5_cc_initialize( context, ccache, principal ) ) != 0 ) {
+ L<<Logger::Error << "LDAP GSSAPI" << "krb5 error: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+ krb5_kt_close( context, keytab );
+ krb5_free_principal( context, principal );
+ return code;
+ }
+
+ if ( ( code = krb5_get_init_creds_opt_alloc( context, &options ) ) != 0 ) {
+ L<<Logger::Error << "LDAP GSSAPI" << "krb5 error: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+ krb5_kt_close( context, keytab );
+ krb5_free_principal( context, principal );
+ return code;
+ }
+ krb5_get_init_creds_opt_set_default_flags( context, "pdns", NULL, options );
+
+ // And finally get the TGT!
+ code = krb5_get_init_creds_keytab( context, &credentials, principal, keytab, 0, NULL, options );
+ krb5_get_init_creds_opt_free( context, options );
+ krb5_kt_close( context, keytab );
+ krb5_free_principal( context, principal );
+
+ if ( code == 0 ) {
+ L<<Logger::Error << "LDAP GSSAPI" << "krb5 error: " << std::string( krb5_get_error_message( context, code ) ) << std::endl;
+ code = krb5_cc_store_cred( context, ccache, &credentials );
+ krb5_free_cred_contents( context, &credentials );
+ krb5_cc_close( context, ccache );
+ }
+
+ krb5_free_context( context );
+ return code;
+}
m_pldap = new PowerLDAP( hoststr.c_str(), LDAP_PORT, mustDo( "starttls" ) );
m_pldap->setOption( LDAP_OPT_DEREF, LDAP_DEREF_ALWAYS );
- m_pldap->bind( getArg( "binddn" ), getArg( "secret" ), LDAP_AUTH_SIMPLE, getArgAsNum( "timeout" ) );
- m_authenticator = new LdapSimpleAuthenticator( getArg( "binddn" ), getArg( "secret" ), getArgAsNum( "timeout" ) );
+
+ string bindmethod = getArg( "bindmethod" );
+ if ( bindmethod == "gssapi" ) {
+ m_authenticator = new LdapGssapiAuthenticator( getArg( "krb5-keytab" ), getArg( "krb5-ccache" ), getArgAsNum( "timeout" ) );
+ }
+ else {
+ m_authenticator = new LdapSimpleAuthenticator( getArg( "binddn" ), getArg( "secret" ), getArgAsNum( "timeout" ) );
+ }
m_pldap->bind( m_authenticator );
L << Logger::Notice << m_myname << " Ldap connection succeeded" << endl;
declare( suffix, "starttls", "Use TLS to encrypt connection (unused for LDAP URIs)", "no" );
declare( suffix, "basedn", "Search root in ldap tree (must be set)","" );
declare( suffix, "basedn-axfr-override", "Override base dn for AXFR subtree search", "no" );
+ declare( suffix, "bindmethod", "Bind method to use (simple or gssapi)", "simple" );
declare( suffix, "binddn", "User dn for non anonymous binds","" );
declare( suffix, "secret", "User password for non anonymous binds", "" );
+ declare( suffix, "krb5-keytab", "The keytab to use for GSSAPI authentication", "" );
+ declare( suffix, "krb5-ccache", "The credentials cache used for GSSAPI authentication", "" );
declare( suffix, "timeout", "Seconds before connecting to server fails", "5" );
declare( suffix, "method", "How to search entries (simple, strict or tree)", "simple" );
declare( suffix, "filter-axfr", "LDAP filter for limiting AXFR results", "(:target:)" );
int PowerLDAP::search( const string& base, int scope, const string& filter, const char** attr )
{
- int msgid, rc;
+ int msgid, rc;
- if( ( rc = ldap_search_ext( d_ld, base.c_str(), scope, filter.c_str(), const_cast<char**> (attr), 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msgid ) ) != LDAP_SUCCESS )
- {
- throw LDAPException( "Starting LDAP search: " + getError( rc ) );
- }
+ if ( ( rc = ldap_search_ext( d_ld, base.c_str(), scope, filter.c_str(), const_cast<char**> (attr), 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msgid ) ) ) {
+ throw LDAPException( "Starting LDAP search: " + getError( rc ) );
+ }
- return msgid;
+ return msgid;
}
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" );
- }
+ bool hasResult = false;
+
+ while ( !hasResult ) {
+ i = waitResult( msgid, timeout, &result );
+ // Here we deliberately ignore LDAP_RES_SEARCH_REFERENCE as we don't follow them.
+ // Instead we get the next result.
+ // If the function returned an error (i <= 0) we'll deal with after this loop too.
+ if ( i == LDAP_RES_SEARCH_ENTRY || i == LDAP_RES_SEARCH_RESULT || i <= 0 )
+ hasResult = true;
+ }
+
+ if ( i == -1 ) {
+ // Error while retrieving the message
+ throw LDAPException( "Error when retrieving LDAP result: " + getError() );
+ }
+
+ if ( i == 0 ) {
+ // Timeout expired before the message could be retrieved
+ throw LDAPTimeout();
+ }
+
+ if ( i == LDAP_RES_SEARCH_RESULT ) {
+ // We're done with this request
+ ldap_msgfree( result );
+ return false;
+ }
if( ( object = ldap_first_entry( d_ld, result ) ) == NULL )
{