17

Good day.

In advance, I apologize for my English, my national forums and resources did not help.

I am making a script that either changes or creates a user's password in AD

After studying the issue, it became clear that

  1. Password to assign or change can only establish an encrypted connection to the server
  2. Sending the password is only necessary for the encoding utf-16-le

In general there is no problem with the second, but the first has the following problem:

$ python ldap-test-starttls.py 
Traceback (most recent call last):
  File "ldap-test-starttls.py", line 9, in <module>
    l.simple_bind_s( "cn=admin,ou=users,dc=test,dc=ru", "password" )
  File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 206, in simple_bind_s
    msgid = self.simple_bind(who,cred,serverctrls,clientctrls)
  File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 200, in simple_bind
    return    self._ldap_call(self._l.simple_bind,who,cred,EncodeControlTuples(serverctrls),EncodeControlTuples(clientctrls))
  File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 96, in _ldap_call
    result = func(*args,**kwargs)
 ldap.SERVER_DOWN: {'info': 'A TLS packet with unexpected length was received.', 'desc': "Can't contact LDAP server"}

Script code

import ldap
host = 'ldaps://ldap:636'
l = ldap.initialize(host)
l.set_option( ldap.OPT_X_TLS_DEMAND, True )
l.set_option( ldap.OPT_DEBUG_LEVEL, 255 )
username = 'someUser'
new_pass = 'ne$wP4assw0rd3!'
new_password = ('"%s"' % new_pass).encode("utf-16-le")
l.simple_bind_s( "cn=admin,ou=users,dc=test,dc=ru", "password" )
mod_attrs = [(ldap.MOD_REPLACE, 'unicodePwd', new_password)],[( ldap.MOD_REPLACE, 'unicodePwd', new_password)]
l.modify_s('CN=%s,dc=users,dc=test,dc=ru' % username, mod_attrs)
l.unbind_s()
print "Successfully changed password."

Chances are someone has already solved a similar problem. Yes, the script is running on CentOS and using py32win is not possible.

Josh Correia
  • 3,807
  • 3
  • 33
  • 50
ellerseer
  • 556
  • 1
  • 4
  • 8

3 Answers3

34

After looking into it more I was able to come up with a solution:

ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
l = ldap.initialize("ldaps://ldap:636")
l.set_option(ldap.OPT_REFERRALS, 0)
l.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
l.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_DEMAND)
l.set_option(ldap.OPT_X_TLS_DEMAND, True)
l.set_option(ldap.OPT_DEBUG_LEVEL, 255)
l.simple_bind_s("admin@tester.com", "password")
phoenix
  • 7,988
  • 6
  • 39
  • 45
ellerseer
  • 556
  • 1
  • 4
  • 8
  • 2
    The first line in this answer is the one that did it for me. I don't know how necessary the rest is for the others. Its seems like this is necessary to **disalble** TLS so that you actually will use SSL. (If thats what you wanted) – SpiRail Sep 18 '12 at 13:30
  • Hi I tried with your answer but no luck. I've posted my question http://stackoverflow.com/questions/38603236/update-active-directory-password-using-ldap-python if you can help – Kartik Domadiya Jul 27 '16 at 04:07
4

I also think OPT_X_TLS_NEVER will disable TLS, so please don't use that.

set_option(ldap.OPT_X_TLS_NEWCTX, ldap.OPT_ON): LDAP_OPT_X_TLS_NEWCTX has to be called after calling ldap_set_option() to set the TLS attributes, if it's called prior to setting the attributes (as is the current code) then the TLS attributes are not copied into the new TLS context.

So my solution is

l = ldap.initialize("ldaps://ldap:636")
l.set_option(ldap.OPT_REFERRALS, 0)
l.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
l.set_option(ldap.OPT_X_TLS,ldap.OPT_X_TLS_DEMAND)
l.set_option(ldap.OPT_X_TLS_DEMAND, True)
l.set_option(ldap.OPT_DEBUG_LEVEL, 255)
# This must be the last tls setting to create TLS context.
l.set_option(ldap.OPT_X_TLS_NEWCTX, ldap.OPT_ON)
l.simple_bind_s("admin@tester.com","password")

@see Explain TLS/SSL gotchas

@see TLS does not work for ldap, incorrect TLS & Debug attribute setting in rlm_ldap

phoenix
  • 7,988
  • 6
  • 39
  • 45
WheelChen
  • 41
  • 4
  • Other guides for this suggest `l.set_option(ldap.OPT_X_TLS_NEWCTX, 0)`, but in your solution [`ldap.OPT_ON` is defined as `1`](https://github.com/python-ldap/python-ldap/blob/febaf561b241a0d0cbe8be6ea5f786f4276c30ae/Modules/constants.c#L205). Is there a particular reason for this? – phoenix May 09 '22 at 11:49
0

PAY much attention to your protocol and port in your connection string:

Using TLS with python-ldap:

# TLS uses string uri 'ldaP://' (NO 's')
# then the method start_tls_s() will transfer to a secure connection
l = ldap.initialize('ldap://localhost:1390',trace_level=ldapmodule_trace_level,trace_file=ldapmodule_trace_file)

# Set LDAP protocol version used
l.protocol_version=ldap.VERSION3

# Force cert validation
l.set_option(ldap.OPT_X_TLS_REQUIRE_CERT,ldap.OPT_X_TLS_DEMAND)
# Set path name of file containing all trusted CA certificates
l.set_option(ldap.OPT_X_TLS_CACERTFILE,CACERTFILE)
# Force libldap to create a new SSL context (must be last TLS option!)
l.set_option(ldap.OPT_X_TLS_NEWCTX,0)

# Now try StartTLS extended operation
l.start_tls_s()

print('***ldap.OPT_X_TLS_VERSION',l.get_option(ldap.OPT_X_TLS_VERSION))
print('***ldap.OPT_X_TLS_CIPHER',l.get_option(ldap.OPT_X_TLS_CIPHER))

# Try an explicit anon bind to provoke failure
l.simple_bind_s('','')

# Close connection
l.unbind_s()

SSL uses 'ldapS://' directly!

And it doesn't use the start_tls_s()

# Create LDAPObject instance
l = ldap.initialize('ldaps://localhost:1391',trace_level=ldapmodule_trace_level,trace_file=ldapmodule_trace_file)

# Set LDAP protocol version used
l.protocol_version=ldap.VERSION3

# Force cert validation
l.set_option(ldap.OPT_X_TLS_REQUIRE_CERT,ldap.OPT_X_TLS_DEMAND)
# Set path name of file containing all trusted CA certificates
l.set_option(ldap.OPT_X_TLS_CACERTFILE,CACERTFILE)
# Force libldap to create a new SSL context (must be last TLS option!)
l.set_option(ldap.OPT_X_TLS_NEWCTX,0)

# Try an explicit anon bind to provoke failure
l.simple_bind_s('','')

print('***ldap.OPT_X_TLS_VERSION',l.get_option(ldap.OPT_X_TLS_VERSION))
print('***ldap.OPT_X_TLS_CIPHER',l.get_option(ldap.OPT_X_TLS_CIPHER))

# Close connection
l.unbind_s()

source: original developers github demo initialize.py