From 7ce6543ce6f78cfa946b9fd0e6a82bf442dbade2 Mon Sep 17 00:00:00 2001 From: David Bartley Date: Tue, 27 Nov 2007 19:20:37 -0500 Subject: [PATCH] Add sasl support --- pylib/csc/adm/accounts.py | 17 ++++++----- pylib/csc/adm/members.py | 13 +++++---- pylib/csc/backends/ldapi.py | 58 +++++++++++++++++++++++++++---------- 3 files changed, 61 insertions(+), 27 deletions(-) diff --git a/pylib/csc/adm/accounts.py b/pylib/csc/adm/accounts.py index 2b28750..3d1e96a 100644 --- a/pylib/csc/adm/accounts.py +++ b/pylib/csc/adm/accounts.py @@ -4,7 +4,7 @@ UNIX Accounts Administration This module contains functions for creating, deleting, and manipulating UNIX user accounts and account groups in the CSC LDAP directory. """ -import re, pwd, grp, os +import re, pwd, grp, os, pwd from csc.common import conf from csc.common.excep import InvalidArgument from csc.backends import ldapi, krb @@ -18,14 +18,14 @@ cfg = {} def configure(): """Helper to load the accounts configuration. You need not call this.""" - + string_fields = [ 'member_shell', 'member_home', 'member_desc', 'member_group', 'club_shell', 'club_home', 'club_desc', 'club_group', 'admin_shell', 'admin_home', 'admin_desc', 'admin_group', 'group_desc', 'username_regex', 'groupname_regex', 'shells_file', 'server_url', 'users_base', 'groups_base', - 'admin_bind_dn', 'admin_bind_pw', 'realm', 'admin_principal', - 'admin_keytab' ] + 'sasl_mech', 'sasl_realm', 'admin_bind_keytab', 'admin_bind_dn', + 'admin_bind_userid', 'realm', 'admin_principal', 'admin_keytab' ] numeric_fields = [ 'member_min_id', 'member_max_id', 'club_min_id', 'club_max_id', 'admin_min_id', 'admin_max_id', 'group_min_id', 'group_max_id', 'min_password_length' ] @@ -78,7 +78,7 @@ class NoSuchGroup(AccountException): self.account, self.source = account, source def __str__(self): return 'Account "%s" not found in %s' % (self.account, self.source) - + ### Connection Management ### @@ -86,13 +86,16 @@ class NoSuchGroup(AccountException): ldap_connection = ldapi.LDAPConnection() krb_connection = krb.KrbConnection() -def connect(): +def connect(auth_callback): """Connect to LDAP and Kerberos and load configuration. You must call before anything else.""" configure() # connect to the LDAP server - ldap_connection.connect(cfg['server_url'], cfg['admin_bind_dn'], cfg['admin_bind_pw'], cfg['users_base'], cfg['groups_base']) + ldap_connection.connect_sasl(cfg['server_url'], cfg['admin_bind_dn'], + cfg['sasl_mech'], cfg['sasl_realm'], cfg['admin_bind_userid'], + ('keytab', cfg['admin_bind_keytab']), cfg['users_base'], + cfg['groups_base']) # connect to the Kerberos master server krb_connection.connect(cfg['admin_principal'], cfg['admin_keytab']) diff --git a/pylib/csc/adm/members.py b/pylib/csc/adm/members.py index 9f4b64e..16fc2b0 100644 --- a/pylib/csc/adm/members.py +++ b/pylib/csc/adm/members.py @@ -9,7 +9,7 @@ Transactions are used in each method that modifies the database. Future changes to the members database that need to be atomic must also be moved into this module. """ -import re, ldap +import re, ldap, os, pwd from csc.adm import terms from csc.backends import ldapi from csc.common import conf @@ -26,7 +26,8 @@ def load_configuration(): """Load Members Configuration""" string_fields = [ 'realname_regex', 'server_url', 'users_base', - 'groups_base', 'admin_bind_dn', 'admin_bind_pw' ] + 'groups_base', 'sasl_mech', 'sasl_realm', 'admin_bind_dn', + 'admin_bind_keytab', 'admin_bind_userid' ] # read configuration file cfg_tmp = conf.read(CONFIG_FILE) @@ -74,12 +75,14 @@ class NoSuchMember(MemberException): # global directory connection ldap_connection = ldapi.LDAPConnection() -def connect(): +def connect(auth_callback): """Connect to LDAP.""" load_configuration() - ldap_connection.connect(cfg['server_url'], cfg['admin_bind_dn'], cfg['admin_bind_pw'], cfg['users_base'], cfg['groups_base']) - + ldap_connection.connect_sasl(cfg['server_url'], cfg['admin_bind_dn'], + cfg['sasl_mech'], cfg['sasl_realm'], cfg['admin_bind_userid'], + ('keytab', cfg['admin_bind_keytab']), cfg['users_base'], + cfg['groups_base']) def disconnect(): """Disconnect from LDAP.""" diff --git a/pylib/csc/backends/ldapi.py b/pylib/csc/backends/ldapi.py index 3da2314..f1ee71d 100644 --- a/pylib/csc/backends/ldapi.py +++ b/pylib/csc/backends/ldapi.py @@ -13,7 +13,7 @@ have an LDAP entry, even if the account does not log in directly. This module makes use of python-ldap, a Python module with bindings to libldap, OpenLDAP's native C client library. """ -import ldap.modlist +import ldap.modlist, ipc, os class LDAPException(Exception): @@ -41,36 +41,39 @@ class LDAPConnection(object): def __init__(self): self.ldap = None - - def connect(self, server, bind_dn, bind_pw, user_base, group_base): + + def connect_anon(self, uri, user_base, group_base): """ Establish a connection to the LDAP Server. Parameters: - server - connection string (e.g. ldap://foo.com, ldaps://bar.com) - bind_dn - distinguished name to bind to - bind_pw - password of bind_dn + uri - connection string (e.g. ldap://foo.com, ldaps://bar.com) user_base - base of the users subtree group_base - baes of the group subtree Example: connect('ldaps:///', 'cn=ceo,dc=csclub,dc=uwaterloo,dc=ca', 'secret', 'ou=People,dc=csclub,dc=uwaterloo,dc=ca', 'ou=Group,dc=csclub,dc=uwaterloo,dc=ca') - + """ - if bind_pw is None: bind_pw = '' + # open the connection + self.ldap = ldap.initialize(uri) - try: + # authenticate + self.ldap.simple_bind_s('', '') - # open the connection - self.ldap = ldap.initialize(server) + self.user_base = user_base + self.group_base = group_base - # authenticate - self.ldap.simple_bind_s(bind_dn, bind_pw) + def connect_sasl(self, uri, bind_dn, mech, realm, userid, password, user_base, group_base): - except ldap.LDAPError, e: - raise LDAPException("unable to connect: %s" % e) + # open the connection + self.ldap = ldap.initialize(uri) + + # authenticate + sasl = Sasl(mech, realm, userid, password) + self.ldap.sasl_interactive_bind_s(bind_dn, sasl) self.user_base = user_base self.group_base = group_base @@ -659,6 +662,31 @@ class LDAPConnection(object): return mlist +class Sasl: + + CB_USER = 0x4001 + bind_dn = 'dn:uid=%s,cn=%s,cn=%s,cn=auth' + + def __init__(self, mech, realm, userid, password): + self.mech = mech + self.bind_dn = self.bind_dn % (userid, realm, mech) + + if mech == 'GSSAPI': + type, arg = password + kinit = '/usr/bin/kinit' + kinit_args = [ 'kinit', '%s@%s' % (userid, realm) ] + if type == 'keytab': + kinit_args += [ '-k', '-t', arg ] + pid, kinit_out, kinit_in = ipc.popeni(kinit, kinit_args) + os.waitpid(pid, 0) + + def callback(self, id, challenge, prompt, defresult): + if id == self.CB_USER: + return self.bind_dn + else: + return None + + ### Tests ### if __name__ == '__main__':