Similar to this question, I am trying to perform simple authentication to a 2003 Active Directory using python ldap (CentOS 6.2 x86_64, Python 2.6.6, python-ldap 2.3.10).
Despite following all the usual steps in the init, including
conn.set_option(ldap.OPT_REFERRALS, 0)
if I pass the correct credentials I always get a (97, [])
returned:
>>> import ldap
>>> conn = ldap.initialize('ldap://ad.server.domain.com')
>>> conn.protocol_version = 3
>>> conn.set_option(ldap.OPT_REFERRALS, 0)
>>> conn.simple_bind_s('[email protected]', 'WrongPassword')
ldap.INVALID_CREDENTIALS: {'info': '80090308: LdapErr: DSID-0C090334, comment: AcceptSecurityContext error, data 52e, vece', 'desc': 'Invalid credentials'}
>>> conn.simple_bind_s('[email protected]', 'CorrectPassword')
(97, [])
Error code 97 is not a success; it's the LDAP_REFERRAL_LIMIT_EXCEEDED
error being returned from AD. Nor can I use it as a de facto success indicator, because:
>>> conn.simple_bind_s('', 'CorrectPassword')
(97, [])
>>> conn.simple_bind_s('', '')
(97, [])
Even more frustrating is that this script is a migration from an old Perl script using Net::LDAP, which does return 0 for a successful authenticated bind to the same AD and server.
All the information I can find on python-ldap indicates that what I am doing should Just Work; I would be inclined to think there's something wrong with the AD servers, but the Perl script does return the correct LDAP code on a successful bind.
I have tested python-ldap 2.2.0 and python 2.4.4 on an old CentOS 5.5 box I had lying around and it "fails" in exactly the same way.
Does anyone know what I am missing?
EDIT: Per request, here is the Perl script that works. Net::LDAP
returns the return code from the LDAP server, and the AD server is returning 0x00, "Successful request", AFAICT.
#!/usr/bin/perl -w
use strict;
use Net::LDAP;
## Corporate subdomains
my @domains = ("americas", "asia", "europe");
# AD connect timeout
my $timeout = 10;
# Set AD server info.
my $port = "389";
my $host = "server.domain.com";
my $user = shift @ARGV;
chomp $user;
my $password = <STDIN>;
$password =~ s/\r\n//;
chomp $password;
my $ldap = Net::LDAP->new($host, port => $port, timeout => $timeout ) ||
die "Unable to connect to LDAP server";
my $bind_return = 1;
foreach (@domains) {
my $result = $ldap->bind( "$user\@$_.domain.com", password => $password );
if( $result->code == 0) {
$bind_return = 0;
last;
}
}
## Unbind and return
$ldap->unbind;
if ($bind_return) { print "Authentication Failed. Access Denied\n" }
exit $bind_return;
In order to use LDAP with Python we need to import the Server and the Connection object, and any additional constant we will use in our LDAP. As you might remember from the LDAP Protocol diagram the authentication operation is called Bind.
LDAP v3 supports three types of authentication: anonymous, simple and SASL authentication.
Michael Ströder, the author of the python-ldap library, enlightened me thus:
The 97 is not the LDAP result code. It's the result type ldap.RES_BIND. Normally you don't have to look at the results returned by LDAPObject.simple_bind_s() (unless you want to extract the bind response controls).
If the LDAP result code is not 0 the accompanying exception is raised like ldap.INVALID_CREDENTIALS in your example.
So your code should look like this:
try:
conn.simple_bind_s('[email protected]', 'WrongPassword')
except ldap.INVALID_CREDENTIALS:
user_error_msg('wrong password provided')
The reason for these results:
>>> conn.simple_bind_s('', 'CorrectPassword')
(97, [])
>>> conn.simple_bind_s('', '')
(97, [])
is that out of the box 2003 Active Directory allows anonymous binds. So not providing a user id at all will still pass a simple bind check, if the only thing being tested is whether simple_bind_s()
throws an error.
2003 Active Directory does require authentication for any searches that aren't attributes of the rootDSE, so for our internal purposes we added a trivial search to the try:
block:
try:
conn.simple_bind_s('[email protected]', 'SubmittedPassword')
conn.search_st('DC=domain,DC=com', ldap.SCOPE_SUBTREE, '(objectClass=container)', 'name', 0, 30)
except ldap.INVALID_CREDENTIALS:
user_error_msg('wrong password provided')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With