Add sasl support

This commit is contained in:
David Bartley 2007-11-27 19:20:37 -05:00
parent a2f7888d5d
commit 7ce6543ce6
3 changed files with 61 additions and 27 deletions

View File

@ -4,7 +4,7 @@ UNIX Accounts Administration
This module contains functions for creating, deleting, and manipulating This module contains functions for creating, deleting, and manipulating
UNIX user accounts and account groups in the CSC LDAP directory. 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 import conf
from csc.common.excep import InvalidArgument from csc.common.excep import InvalidArgument
from csc.backends import ldapi, krb from csc.backends import ldapi, krb
@ -18,14 +18,14 @@ cfg = {}
def configure(): def configure():
"""Helper to load the accounts configuration. You need not call this.""" """Helper to load the accounts configuration. You need not call this."""
string_fields = [ 'member_shell', 'member_home', 'member_desc', string_fields = [ 'member_shell', 'member_home', 'member_desc',
'member_group', 'club_shell', 'club_home', 'club_desc', 'member_group', 'club_shell', 'club_home', 'club_desc',
'club_group', 'admin_shell', 'admin_home', 'admin_desc', 'club_group', 'admin_shell', 'admin_home', 'admin_desc',
'admin_group', 'group_desc', 'username_regex', 'groupname_regex', 'admin_group', 'group_desc', 'username_regex', 'groupname_regex',
'shells_file', 'server_url', 'users_base', 'groups_base', 'shells_file', 'server_url', 'users_base', 'groups_base',
'admin_bind_dn', 'admin_bind_pw', 'realm', 'admin_principal', 'sasl_mech', 'sasl_realm', 'admin_bind_keytab', 'admin_bind_dn',
'admin_keytab' ] 'admin_bind_userid', 'realm', 'admin_principal', 'admin_keytab' ]
numeric_fields = [ 'member_min_id', 'member_max_id', 'club_min_id', numeric_fields = [ 'member_min_id', 'member_max_id', 'club_min_id',
'club_max_id', 'admin_min_id', 'admin_max_id', 'group_min_id', 'club_max_id', 'admin_min_id', 'admin_max_id', 'group_min_id',
'group_max_id', 'min_password_length' ] 'group_max_id', 'min_password_length' ]
@ -78,7 +78,7 @@ class NoSuchGroup(AccountException):
self.account, self.source = account, source self.account, self.source = account, source
def __str__(self): def __str__(self):
return 'Account "%s" not found in %s' % (self.account, self.source) return 'Account "%s" not found in %s' % (self.account, self.source)
### Connection Management ### ### Connection Management ###
@ -86,13 +86,16 @@ class NoSuchGroup(AccountException):
ldap_connection = ldapi.LDAPConnection() ldap_connection = ldapi.LDAPConnection()
krb_connection = krb.KrbConnection() krb_connection = krb.KrbConnection()
def connect(): def connect(auth_callback):
"""Connect to LDAP and Kerberos and load configuration. You must call before anything else.""" """Connect to LDAP and Kerberos and load configuration. You must call before anything else."""
configure() configure()
# connect to the LDAP server # 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 # connect to the Kerberos master server
krb_connection.connect(cfg['admin_principal'], cfg['admin_keytab']) krb_connection.connect(cfg['admin_principal'], cfg['admin_keytab'])

View File

@ -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 Future changes to the members database that need to be atomic
must also be moved into this module. must also be moved into this module.
""" """
import re, ldap import re, ldap, os, pwd
from csc.adm import terms from csc.adm import terms
from csc.backends import ldapi from csc.backends import ldapi
from csc.common import conf from csc.common import conf
@ -26,7 +26,8 @@ def load_configuration():
"""Load Members Configuration""" """Load Members Configuration"""
string_fields = [ 'realname_regex', 'server_url', 'users_base', 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 # read configuration file
cfg_tmp = conf.read(CONFIG_FILE) cfg_tmp = conf.read(CONFIG_FILE)
@ -74,12 +75,14 @@ class NoSuchMember(MemberException):
# global directory connection # global directory connection
ldap_connection = ldapi.LDAPConnection() ldap_connection = ldapi.LDAPConnection()
def connect(): def connect(auth_callback):
"""Connect to LDAP.""" """Connect to LDAP."""
load_configuration() 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(): def disconnect():
"""Disconnect from LDAP.""" """Disconnect from LDAP."""

View File

@ -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 This module makes use of python-ldap, a Python module with bindings
to libldap, OpenLDAP's native C client library. to libldap, OpenLDAP's native C client library.
""" """
import ldap.modlist import ldap.modlist, ipc, os
class LDAPException(Exception): class LDAPException(Exception):
@ -41,36 +41,39 @@ class LDAPConnection(object):
def __init__(self): def __init__(self):
self.ldap = None 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. Establish a connection to the LDAP Server.
Parameters: Parameters:
server - connection string (e.g. ldap://foo.com, ldaps://bar.com) uri - connection string (e.g. ldap://foo.com, ldaps://bar.com)
bind_dn - distinguished name to bind to
bind_pw - password of bind_dn
user_base - base of the users subtree user_base - base of the users subtree
group_base - baes of the group subtree group_base - baes of the group subtree
Example: connect('ldaps:///', 'cn=ceo,dc=csclub,dc=uwaterloo,dc=ca', Example: connect('ldaps:///', 'cn=ceo,dc=csclub,dc=uwaterloo,dc=ca',
'secret', 'ou=People,dc=csclub,dc=uwaterloo,dc=ca', 'secret', 'ou=People,dc=csclub,dc=uwaterloo,dc=ca',
'ou=Group,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.user_base = user_base
self.ldap = ldap.initialize(server) self.group_base = group_base
# authenticate def connect_sasl(self, uri, bind_dn, mech, realm, userid, password, user_base, group_base):
self.ldap.simple_bind_s(bind_dn, bind_pw)
except ldap.LDAPError, e: # open the connection
raise LDAPException("unable to connect: %s" % e) 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.user_base = user_base
self.group_base = group_base self.group_base = group_base
@ -659,6 +662,31 @@ class LDAPConnection(object):
return mlist 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 ### ### Tests ###
if __name__ == '__main__': if __name__ == '__main__':