#!/usr/bin/env python import logging from ldapuserdir import LdapUserDir, __version__ as libversion, LdapUserDirError import ldap import sys import os import pwd from optparse import OptionParser import configparser import getpass class MyOptParser(OptionParser): """OptionParser derived class for enabling us to give out a nicer help text where text can follow the option section""" def format_epilog(self, formatter): return self.epilog.replace('%prog',os.path.basename(sys.argv[0])) def read_cfg(filename): cfg = configparser.ConfigParser() try: mylogger.debug('reading config from %s' % filename) cfg.read(filename) config = { 'serverurl' : cfg.get('Ldap','serverurl'), 'user_ou' : cfg.get('Ldap','user_ou'), 'group_ou' : cfg.get('Ldap','group_ou'), 'default_user_dn' : cfg.get('Ldap','default_user_dn'), 'default_user_pw' : cfg.get('Ldap','default_user_pw'), } except Exception as err: sys.stderr.write("Error in reading configuration from %s\n" % filename) sys.stderr.write(str(err)+'\n') sys.exit(1) try: config['default_group_filter'] = cfg.get('Ldap','default_group_filter') except: config['default_group_filter'] = '*' return config #Defaults cfgfile_loc = [os.path.expanduser('~/.ldapuserdir-ctl.cfg'), '/etc/ldapuserdir-ctl.cfg'] config = { 'serverurl' : 'ldaps://xyzdir.example.com:636', 'user_ou' : 'OU=Users,DC=example.com,DC=ch', 'group_ou' : 'OU=Groups,DC=example.com,DC=ch', 'default_user_dn' : 'CN=minpriv_user,OU=Services,DC=example.com,DC=ch', 'default_user_pw' : 'dummypwd', 'default_group_filter' : 'svc-cluster_merlin*' } flag_needprivileges = False userfilter = '-' user_pw = '' mode = 'grouplist' verbosity = 0 mylogger = logging.getLogger(os.path.basename(sys.argv[0])) mylogger.setLevel(logging.DEBUG) formatter = logging.Formatter('%(name)s %(levelname)s: %(message)s') ch = logging.StreamHandler() ch.setLevel(logging.WARNING) ch.setFormatter(formatter) mylogger.addHandler(ch) ################################################ # OPTION PARSING usage = """%prog [options] groupname [usernames] Shows or changes members of a group in Active Directory. Also can be used to investigate users and their group memberships. User and group names can be given as fully distinguished names or just as the short system names (the tool will try to figure out the full names based on the standard OU extensions in the config and a lookup) The configuration is read from a configuration file. Default locations for the file: """ usage += "\n\t* " + "\n\t* ".join(cfgfile_loc) + '\n' usage += """ Accessing the user information in AD requires an account with limited permissions that needs to be set in the configuration file's default_user_dn and default_user_pw options.""" usage_epilog = """ Examples: List group members %prog svc-cluster_merlin5 %prog 'svc-cluster_*' Get group memberships for user mueller (optionally with a group filter) %prog -g mueller %prog -g mueller 'svc-cluster_merlin5' %prog -g mueller 'svc-cluster_*' Add/delete users to/from a group (requires access rights defined in LDAP service!) %prog -a svc-ra_x06sa user1 user2 user3 %prog -d svc-ra_x06sa user1 user2 List users matching a pattern %prog -u 'mueller*' List users matching a mail address pattern %prog -m '*mueller@psi*' Author: 2013-19 D. Feichtinger """ examplecfg = """# Configuration file example: [Ldap] # URL for contacting the LDAP server serverurl = ldaps://d.psi.ch:636 # base ldap path under which all users are found user_ou = OU=Users,OU=PSI,DC=d,DC=psi,DC=ch # base ldap path under which groups are found group_ou = ou=Groups,ou=PSI,dc=d,dc=psi,dc=ch # minimally privileged Ldap user and password for running normal # lookup queries default_user_dn = CN=linux_ldap,OU=Services,OU=IT,DC=d,DC=psi,DC=ch default_user_pw = DEFaultPassword # Optional: # default filter to be used for group searches default_group_filter = svc-ra* """ #parser = OptionParser(usage = usage, epilog = usage_epilog) parser = MyOptParser(usage = usage, epilog = usage_epilog) parser.add_option('-a', action = 'store_true', dest = 'flag_add', help = 'add group members', ) parser.add_option('-d', action = 'store_true', dest = 'flag_del', help = 'delete group members', ) parser.add_option('-g', action = 'store', dest = 'user_to_group', help = 'get group memberships for this user', default = '' ) parser.add_option('-u', action = 'store', dest = 'userfilter', help = 'list all ldap users matching the filter expression' ) parser.add_option('-m', '--mail', action = 'store', dest = 'mailfilter', help = 'list all ldap users based on the mail address filter expression' ) parser.add_option('--dn', action = 'store_true', dest = 'flag_showdn', help = 'show full DNs in the output', default = False ) parser.add_option('-c', action = 'store', dest = 'cfgfile', help = 'path of a config file (else tries default locations)', default = '' ) parser.add_option('-D', action = 'store', dest = 'user_dn', help = 'DN or CN of ldap user for binding to the AD server (%s)' % config['default_user_dn'], default = None ) parser.add_option('-f', action = 'store', dest = 'pwfile', help = 'path to password file (without this pwd will be prompted for)', default = '' ) parser.add_option('-v', action = 'count', dest = 'verbosity', help = 'use more verbose output (for group and user lists. Verbosity can be increased by multiple -v flags)', default = False ) parser.add_option('--user-ou', action = 'store', dest = 'user_ou', help = 'default OU for users (%s)' % config['user_ou'], default = None ) parser.add_option('--group-ou', action = 'store', dest = 'group_ou', help = 'default OU for groups (%s)' % config['group_ou'], default = None ) parser.add_option('-n', '--allow-no-mssfu', action = 'store_false', dest = 'flag_mssfu', help = 'do not restrict to entries with unix (msSFU) mappings', default = True ) parser.add_option('--debug', action = 'store_true', dest = 'flag_debug', help = 'debug mode: log messages at debug level', ) parser.add_option('--showconf', action = 'store_true', dest = 'flag_examplecfg', help = 'show an example configuration file', default= False ) parser.add_option('-V', action = 'store_true', dest = 'flag_version', help = 'show version information', default = False ) parser.add_option('-R', action = 'store_false', dest = 'recursive', help = "Don't recursively resolve groups", default = True ) (options, args) = parser.parse_args() if options.flag_debug: ch.setLevel(logging.DEBUG) if options.verbosity: verbosity = options.verbosity userfilter = options.userfilter mailfilter = options.mailfilter flag_mssfu = options.flag_mssfu flag_showdn = options.flag_showdn if options.flag_version: sys.stdout.write('Library version: ' + libversion + '\n') sys.exit(0) if options.flag_examplecfg: sys.stdout.write(examplecfg) sys.exit(0) cfgfile = None if(options.cfgfile != ''): cfgfile = options.cfgfile else: for tmp in cfgfile_loc: if os.path.exists(tmp): cfgfile = tmp break; if cfgfile == None: sys.stderr.write('Error: You must provide a config file (default locations ' + ', '.join(cfgfile_loc) + ' or explicitely)\n') sys.exit(1) config = read_cfg(cfgfile) user_dn = config['default_user_dn'] if options.user_dn: user_dn = options.user_dn if options.group_ou: config['group_ou'] = options.group_ou if options.user_ou: config['user_ou'] = options.user_ou if options.flag_del: mode = 'del' flag_needprivileges = True if options.flag_add: mode = 'add' flag_needprivileges = True if userfilter: mode = 'userlist' if mailfilter: mode = 'maillist' if options.user_to_group: mode = "user_to_group" user_to_group = options.user_to_group # this we should actually do with systemuser2dn if ',' not in user_dn: user_dn = 'CN=' + user_dn + ',' + config['user_ou'] if flag_needprivileges and user_dn == config['default_user_dn']: loginname = pwd.getpwuid(os.getuid())[0] try: l_unpriv = LdapUserDir(config['serverurl'], config['default_user_dn'], config['default_user_pw'], user_ou = config['user_ou'], logger = mylogger) try: user_dn = l_unpriv.systemuser2dn(loginname) except LdapUserDirError as err: if str(err) == 'No such user': sys.stderr.write(''' Error: Need priviledged user and cannot map your system user "%s" to LDAP DN for binding (you may want to use the explicit -D user_dn option) ''' % loginname) sys.exit(1) else: sys.stderr.write('Uncaught Error: %s' % str(err)) except ldap.LDAPError as e: sys.stderr.write('LDAP error: %s\n' % str(e)) sys.exit(1) if user_dn == config['default_user_dn']: user_pw = config['default_user_pw'] if options.pwfile != '': pwf = open(options.pwfile) user_pw = pwf.readline().rstrip('\n') if user_pw == '': user_pw = getpass.getpass() if user_pw == '': sys.stderr.write('Error: No empty passwd allowed\n') # Note: AD accepts empty passwds, but will give anonymous access # So, we want to catch that case sys.exit(1) try: ldapdir = LdapUserDir(config['serverurl'], user_dn, user_pw, group_ou = config['group_ou'], user_ou = config['user_ou'], logger=mylogger) if mode == 'grouplist': sfilter = config['default_group_filter'] if args: sfilter = args.pop(0) ldapdir.list_groups(sfilter, mssfu=flag_mssfu, returndn=flag_showdn, verbose=verbosity, recursive=options.recursive) elif mode == 'userlist': records = ldapdir.get_users(userfilter, config['user_ou'], mssfu=flag_mssfu) ldapdir.list_users_etcpwd(records, verbose = verbosity) elif mode == 'maillist': records = ldapdir.get_users_by_mailaddr(mailfilter, config['user_ou'], mssfu=flag_mssfu) ldapdir.list_users_etcpwd(records, verbose = verbosity) elif mode == 'user_to_group': sfilter = None if args: sfilter = args.pop(0) try: sys.stdout.write("\n".join(ldapdir.get_groups_for_user(user_to_group, gfilter=sfilter, mssfu=flag_mssfu, returndn=flag_showdn, recursive=options.recursive)) + '\n') #sys.stdout.write("\n".join(ldapdir.get_groups_for_user(user_to_group)) # + "\n") except LdapUserDirError as err: if str(err) == "No such user": sys.stderr.write('Error: No such user (%s)\n' % user_to_group) else: sys.stderr.write('Error: ' + str(err) +'\n') sys.exit(1) elif mode == 'add': if len(args) < 2: sys.stderr.write("Error: Not enough arguments\n") sys.exit(1) group = args.pop(0) ldapdir.add_groupmembers(group, args) elif mode == 'del': if len(args) < 2: sys.stderr.write("Error: Not enough arguments\n") sys.exit(1) group = args.pop(0) ldapdir.del_groupmembers(group, args) except ldap.INVALID_CREDENTIALS as e: sys.exit(1) except ldap.LDAPError as e: sys.stderr.write('Unhandled LDAP error: %s\n' % str(e)) sys.exit(1) # except Exception, err: # sys.stderr.write('Unhandled Exception (%s): %s\n' % (type(err), str(err))) # sys.exit(1) sys.exit(0)