Poking Windows from PHP

This is a rather old article that I posted to Facebook a bit over a year ago now, but I thought it would make a good first entry to this blog.

I want to a bit of bragging, since I’m mentally useless for much else until after lunch.

Background – we have a brand new NetApp filer. We use OpenLDAP for authentication. The two don’t like each other all that much for authenticating CIFS sessions – the NetApp is going to replace Samba, which does talk with OpenLDAP just fine for authenticating CIFS sessions, but had *maybe* 5% of the NetApp’s functionality.

So that’s fact 1. Fact 2 is that we have a number of Window based projects coming in over the summer. Fact 3 is that I flatly refuse to manage 8 different user lists with seven of them being one-server lists.

This leads to a need for Active Directory. AD, at it’s core is nothing but LDAP with a different name, but it does some things strangely. Like storing passwords in Kerberos and making them difficult to change anywhere other than the CTRL-ALT-DEL screen from a domain client.

I didn’t like that, since the OpenLDAP password is still the primary password for our environment. So, screw you Microsoft, I’ll extend my password change page to modify AD passwords.

Except PHP no likey talking to AD for password changes… poo.

Python talks to OpenLDAP… Python talks to AD… Python talks web stuff through mod_wsgi – throw in some CherryPy and Kid, and we’re really gonna have a good time!

Well, there’s still that wierdness with the AD password format… and then there were issues with binding to the LDAP instances with sufficient permissions to perform modifications… and of course you can’t forget the fact that CherryPy doesn’t give you helpful error tracebacks out of the box… this leads to two weeks of banging my head against a wall fighting this stuff.

Today, however, I finally won… everything finally came together like it should, and my nice new fancy AJAX-y WSGI web site can change OpenLDAP and AD passwords simultaneously.

Since it was a tin-plated bitch finding all of the information I needed to do this right, I’ll post the relevant parts of the code here. I should probably post this on some sort of blog, but I don’t have a work-type blog and refuse to pollute my photography blog with computer code.

First, enabling useful error tracebacks in CherryPy version 3:
At the top of the module, do the following:
import cherrypy

I can’t tell you how much aggravation and time that one little config change saved me…

To modify passwords, you’ll need to use an SSL’ed connection, so you need to do two things. First, turn on LDAPS on your AD instance – it isn’t on by default. The Microsoft instructions at http://support.microsoft.com/kb/321051 actually work as advertised (or did for me on Windows 2003 Server). Second, turn on LDAPS on your OpenLDAP instance – this is documented widely enough that I’ll not waste the space here re-documenting it.

Once you’ve done that, add the following line to your /etc/ldap.conf file to avoid potential issues with trust chains not being verifiable:

Verify you can use SSL with and ldapsearch -ZZ command – this is widely enough documented that I’ll not waste the space here to document it again.

Now for the good parts… the code.
This is it, semi-simplified and santized for usernames and passwords, with comments about what the username or other params should be at various points:

# Import necessary libraries
import ldap
import sha
from base64 import b64encode

# Connect to your LDAPS servers
openldap = ldap.initalize(“ldaps://openldap-server-fqdn:636/”)
adldap = ldap.initialize(“ldaps://ad-domain-controller-fqdn:636/”)

# Bind to OpenLDAP as the LDAP “root” user (not the same as Linux root!) – use the
# full DN, which will look something like:
# uid=ldapadmin,ou=people,dc=mycompany,dc=org
openldap.simple_bind_s(“<ldaproot>”, “<admin-password>”)

# Bind to AD as a Domain Admin, again using the full DN
# <adminname> is the login name of the domain admin
# <domain> is the name of the AD domain
adldap.simple_bind_s(“<adminname>@<domain>”, “<password>”)

# Find the DN’s you wish to change. The capitalization of sAMAccountName
# in the adldap search is VERY IMPORTANT
search = openldap.search_s(“ou=people,dc=mycompany,dc=org”, \
ldap.SCOPE_SUBTREE, “uid=<username>”)
linDN = search[0][0]
search = adldap.search_s(“cn=Users,dc=<domain>”, \
ldap.SCOPE_SUBTREE, “sAMAccountName=<username>”)
winDN = search[0][0]

# Generate the new passwords
newLinPass = “{SHA} + b64encode(sha.new(“<newpass>”).digest())
newWinPass = unicode(“\”<newpass>\””, “iso-8859-1″).encode(utf-16-le”)

# Set up modification records
linMod = [(ldap.MOD_REPLACE, “userPassword”, [newLinPass])]
winMod = [(ldap.MOD_REPLACE, “unicodePwd”, [newWinPass])]

# Do the actual modification
openldap.modify_s(linDN, linMod)
adldap.modify_s(winDN, winMod)

It’s left as an exercise for the reader to perform sufficient error checking and handling. 🙂

Comments are closed.