diff --git a/bin/ldapuserdir-ctl b/bin/ldapuserdir-ctl index 91914fa..e974748 100755 --- a/bin/ldapuserdir-ctl +++ b/bin/ldapuserdir-ctl @@ -186,11 +186,11 @@ parser.add_option('--group-ou', help = 'default OU for groups (%s)' % config['group_ou'], default = None ) -parser.add_option('--no-msSFU', - action = 'store_true', - dest = 'flag_nosfu', +parser.add_option('-n', '--allow-no-mssfu', + action = 'store_false', + dest = 'flag_mssfu', help = 'do not restrict to entries with unix (msSFU) mappings', - default = False + default = True ) parser.add_option('-V', action = 'store_true', @@ -210,6 +210,8 @@ if len(args) > 0: if options.flag_debug: ch.setLevel(logging.DEBUG) +flag_mssfu = options.flag_mssfu + if options.flag_version: sys.stdout.write('Library version: ' + libversion + '\n') sys.exit(0) @@ -243,7 +245,6 @@ if options.user_ou: config['user_ou'] = options.user_ou flag_verbose = options.flag_verbose -flag_sfu = not options.flag_nosfu userfilter = options.userfilter if options.flag_del: @@ -283,7 +284,7 @@ to LDAP DN for binding (you may want to use the explicit -D user_dn option) ''' % os.getlogin() ) sys.exit(1) else: - sys.stderr.write('Uncaught Error: %s' % str(err)) + sys.stderr.write('Uncaught Error: %s' % str(err)) except ldap.LDAPError, e: sys.stderr.write('LDAP error: %s\n' % str(e)) @@ -305,32 +306,33 @@ if user_pw == '': sys.exit(1) try: - l = LdapUserDir(config['serverurl'], - user_dn, - user_pw, - config['group_ou'], - config['user_ou'], - sfu = flag_sfu, - logger=mylogger) + ldapdir = LdapUserDir(config['serverurl'], + user_dn, + user_pw, + group_ou = config['group_ou'], + user_ou = config['user_ou'], + logger=mylogger) if mode == 'list': sfilter = config['default_group_filter'] if group: sfilter = group - l.list_groups(sfilter) + ldapdir.list_groups(sfilter, mssfu=flag_mssfu) elif mode == 'userlist': - l.list_users_etcpwd(userfilter, verbose = flag_verbose) + ldapdir.list_users_etcpwd(userfilter, verbose = flag_verbose) elif mode == 'user_to_group': try: - sys.stdout.write("\n".join(l.get_groups_for_user(user_to_group)) - + "\n") + dn = ldapdir.systemuser2dn(user_to_group) + sys.stdout.write("\n".join(ldapdir.get_memberof(dn, mssfu=flag_mssfu)) + '\n') + #sys.stdout.write("\n".join(ldapdir.get_groups_for_user(user_to_group)) + # + "\n") except LdapUserDirError, err: sys.stderr.write('Error: ' + str(err) +'\n') sys.exit(1) elif mode == 'add': - l.add_groupmembers(group, usernames) + ldapdir.add_groupmembers(group, usernames) elif mode == 'del': - l.del_groupmembers(group, usernames) + ldapdir.del_groupmembers(group, usernames) except ldap.INVALID_CREDENTIALS, e: sys.exit(1) except ldap.LDAPError, e: diff --git a/etc/ldapuserdir-ctl.cfg b/etc/ldapuserdir-ctl.cfg index 8e58e91..8be4a30 100644 --- a/etc/ldapuserdir-ctl.cfg +++ b/etc/ldapuserdir-ctl.cfg @@ -2,6 +2,9 @@ # URL for contacting the LDAP server serverurl = ldaps://d.psi.ch:636 +# base ldap path for global searches: not yet used +top_ou = OU=PSI,DC=d,DC=psi,DC=ch + # base ldap path under which all users are found user_ou = OU=Users,OU=PSI,DC=d,DC=psi,DC=ch diff --git a/ldapuserdir/ldapuserdir.py b/ldapuserdir/ldapuserdir.py index 200d9f5..5986842 100644 --- a/ldapuserdir/ldapuserdir.py +++ b/ldapuserdir/ldapuserdir.py @@ -15,26 +15,10 @@ import ldap import os import sys import re +from glob import fnmatch import logging import time -############################################################## -# definitions of the search strings - -# restrict to entries with msSFU mappings -searches_mssfu= dict({ -'get_users' : '(&(objectClass=user)(!(objectClass=computer))(msSFU30UidNumber=*)(msSFU30HomeDirectory=*)(cn=%s))', -'systemuser2dn' : '(&(objectClass=user)(!(objectClass=computer))(msSFU30UidNumber=*)(msSFU30HomeDirectory=*)(cn=%s))', -'get_groups_struct' : '(&(objectClass=Group)(msSFU30GidNumber=*)(cn=%s))', -'get_groups_for_user' : '(&(objectClass=Group)(msSFU30GidNumber=*)(cn=%s)(member=%s))'}) - -# allow entries without msSFU mappings -searches_nomssfu= dict({ -'get_users' : '(&(objectClass=user)(!(objectClass=computer))(cn=%s))', -'systemuser2dn' : '(&(objectClass=user)(!(objectClass=computer))(msSFU30UidNumber=*)(msSFU30HomeDirectory=*)(cn=%s))', -'get_groups_struct' : '(&(objectClass=Group)(cn=%s))', -'get_groups_for_user' : '(&(objectClass=Group)(cn=%s)(member=%s))'}) -############################################################## class LdapUserDirError(Exception): def __init__(self, errmsg): @@ -50,22 +34,14 @@ class LdapUserDir(object): user_pw, group_ou = 'ou=example.com', user_ou = 'ou=example.com', - sfu = True, - logger = None): + logger = None, +): self.serverurl = serverurl self.group_ou = group_ou self.user_ou = user_ou self.user_dn = user_dn self.user_pw = user_pw - # whether to only search for entries with msSFU mappings - # i.e. with existing unix attributes - if sfu: - self.searches = searches_mssfu - else: - self.searches = searches_nomssfu - - if logger == None: self.logger = logging.getLogger('LdapUserDir') self.logger.setLevel(logging.WARNING) @@ -100,6 +76,15 @@ class LdapUserDir(object): except ldap.LDAPError, e: raise + @staticmethod + def has_dn_format(name): + """returns true if name has the format of a distinguished name + """ + # currently we are satisfied with a very primitive check + if ',' in name: + return True + return False + def search_s_reconn(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, recon_attempts = 2): """wrapper of standard ldap.search_s synchronous search that @@ -166,17 +151,24 @@ class LdapUserDir(object): #def search_s(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0): - def get_users(self, filter='*', ou=None): + def get_users(self, filter='*', ou=None, mssfu=False): """get the names of all users from the directory service @param filter A filter expression used for the cn part of the ldap dn @param ou The organisational unit to be used in the ldap search + @param mssfu Whether to only show users with mssfu mappings @returns A dictionary of the matching users { dn1:list1, ... } """ if ou == None: user_ou = self.user_ou + + if mssfu: + srch = '(&(objectClass=user)(!(objectClass=computer))(msSFU30UidNumber=*)(msSFU30HomeDirectory=*)(cn=%s))' + else: + srch = '(&(objectClass=user)(!(objectClass=computer))(cn=%s))' + #try: r = self.search_s_reconn(user_ou, ldap.SCOPE_SUBTREE, - self.searches['get_users'] % filter) + srch % filter) #except ldap.LDAPError, e: # print e # return @@ -187,7 +179,7 @@ class LdapUserDir(object): @param filter A filter expression used for the cn part of the ldap dn @param ou The organisational unit to be used in the ldap search """ - r = self.get_users(filter, ou) + r = self.get_users(filter, ou, mssfu=True) for dn, entry in r: # MUST fields try: @@ -220,7 +212,8 @@ class LdapUserDir(object): @exception may throw an ldap.LDAPError or LdapUserDir("No such user") """ #try: - srch = self.searches['systemuser2dn'] % uname + srch = '(&(objectClass=user)(!(objectClass=computer))(msSFU30UidNumber=*)(msSFU30HomeDirectory=*)(cn=%s))' % uname + self.logger.debug('systemuser2dn: %s' % srch) r = self.search_s_reconn(self.user_ou, ldap.SCOPE_SUBTREE, srch) #except ldap.LDAPError, e: @@ -232,25 +225,95 @@ class LdapUserDir(object): self.logger.debug('systemuser2dn: dn = %s' % r[0][0]) return r[0][0] - def get_groups_struct(self, gfilter='*', ou = None): - """searches for groups + def get_groups_struct(self, gfilter='*', ou = None, mssfu=False): + """searches for groups that match filter returns the full ldap search result structure for the search with the optional filter applied to the cn field @param filter A filter expression used for the cn part of the ldap dn @param ou The organisational unit to be used in the ldap search + @param mssfu Whether to only show users with mssfu mappings @returns A dictionary of the matching groups { dn1:list1, ... } """ if ou == None: group_ou = self.group_ou - #try: - srch = self.searches['get_groups_struct'] % gfilter + + if mssfu: + srch = '(&(objectClass=Group)(msSFU30GidNumber=*)(cn=%s))' % gfilter + else: + srch = '(&(objectClass=Group)(cn=%s))' % gfilter self.logger.debug('get_groups_struct: %s' % srch) + #try: r = self.search_s_reconn(group_ou, ldap.SCOPE_SUBTREE, srch) #except ldap.LDAPError, e: # print e return r - def get_groups_for_user(self, user, gfilter='*', ou=None, returndn = False): + def get_memberof(self, dn, gfilter=None, returndn = False, + recursive = True, mssfu = False): + """Get all memberOf attributes for an dn (optionally recursively) + + This routine relies on the LDAP directory keeping memberOf attributes + in the individual entries + + Parameters + ---------- + dn : distinguished name + gfilter : filter expression used for the cn part of the results, optional + returndn : If True then return DNs, optional + recursive : recurse into hierarchical groups, optional + mssfu : whether to only return entries with MSsfu attributes, optional + + Returns + ------- + List of groups + """ + self.logger.debug('get_memberof for %s' % dn) + + srch = '(&)' + if mssfu: + srch = '(|(msSFU30GidNumber=*)(msSFU30UidNumber=*))' + + r = self.search_s_reconn(dn, ldap.SCOPE_BASE, srch, + attrlist=['memberOf']) + + if r == []: + return [] + + grplist = [] + if 'memberOf' in r[0][1]: + grplist = r[0][1]['memberOf'] + + if mssfu: + srch = '(msSFU30GidNumber=*)' + tmplist = [] + for g in grplist: + self.logger.debug('testing msSFU for %s' % g) + r2 = self.search_s_reconn(g, ldap.SCOPE_BASE, srch) + if len(r2): + tmplist.append(g) + grplist = tmplist + + if recursive: + tmplist = [] + for g in grplist: + tmplist.extend(self.get_memberof(g)) + grplist.extend(tmplist) + + if gfilter: + grplist = [i for i in grplist if fnmatch.fnmatch(i,'CN=%s' % gfilter)] + + return grplist + + + # reslist = []defu + # for dn, entry in r: + # reslist.append(dn) + + # if returndn: + # return reslist + + def get_groups_for_user(self, user, gfilter='*', ou=None, returndn = False, + mssfu=False): """Get groups for a particular user from LDAP. The function will try to determine whether it receives a DN or a system username that needs to be converted to a DN first. @@ -258,16 +321,19 @@ class LdapUserDir(object): @param gfilter A filter expression used for the cn part of the ldap dn @param ou The organisational unit to be used in the ldap search @param returndn If set True the function will return DN, otherwise CN + @param mssfu Whether to only show users with mssfu mappings @returns list of group names """ if ou == None: group_ou = self.group_ou - if not ',' in user: + if not self.has_dn_format(user): dnname = self.systemuser2dn(user) else: dnname = user - srch = self.searches['get_groups_for_user'] % (gfilter, dnname) + if mssfu: + srch = '(&(objectClass=Group)(msSFU30GidNumber=*)(cn=%s)(member=%s))' % (gfilter, dnname) + srch = '(&(objectClass=Group)(cn=%s)(member=%s))' % (gfilter, dnname) self.logger.debug('get_groups_for_user: %s' % srch) r = self.search_s_reconn(group_ou, ldap.SCOPE_SUBTREE, srch) @@ -290,12 +356,12 @@ class LdapUserDir(object): return cnlist - def list_groups(self, filter = '*', ou = None): + def list_groups(self, filter = '*', ou = None, mssfu=False): """Prints a list of groups from the LDAP directory @param filter A filter expression used for the cn part of the ldap dn @param ou The organisational unit to be used in the ldap search """ - r = self.get_groups_struct(filter, ou) + r = self.get_groups_struct(filter, ou, mssfu) if len(r) == 0: sys.stderr.write("Error: no groups found (filter: %s)\n" % filter) return 0 @@ -312,11 +378,11 @@ class LdapUserDir(object): @param dngroup DN of the group @param usernames List of usernames (system names or DNs) """ - if not ',' in dngroup: + if not self.has_dn_format(dngroup): dngroup = ''.join(['cn=', dngroup, ',', self.group_ou]) dnlist = [] for name in usernames: - if not ',' in name: + if not self.has_dn_format(name): dnname = self.systemuser2dn(name) if dnname == '': raise RuntimeError('Error: No such ldap user: %s' % name) diff --git a/ldapuserdir/version.py b/ldapuserdir/version.py index f00b8e8..8c0d5d5 100644 --- a/ldapuserdir/version.py +++ b/ldapuserdir/version.py @@ -1 +1 @@ -__version__ = "1.20" +__version__ = "2.0.0" diff --git a/setup.py b/setup.py index 3ec3eb4..f0eb7e8 100644 --- a/setup.py +++ b/setup.py @@ -7,8 +7,8 @@ from setuptools import setup, find_packages # we get the package version from inside our package, since we then # can use it also from within the package -from ldapuserdir.version import __version__ -#execfile('ldapuserdir/version.py') +#from ldapuserdir.version import __version__ +execfile('ldapuserdir/version.py') setup( name = "ldapuserdir",