diff --git a/kolabd.py b/kolabd.py index ef4500a..0dd270b 100755 --- a/kolabd.py +++ b/kolabd.py @@ -1,39 +1,44 @@ #!/usr/bin/python # # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. - +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + +import os import sys # For development purposes -sys.path = [ '.' ] + sys.path +if os.path.isdir(os.path.join(os.path.dirname(__file__), '.git')): + sys.path.insert(0, '.') from pykolab.translate import _ + try: from pykolab.constants import * -except ImportError, e: - print >> sys.stderr, _("Cannot load pykolab/constants.py:") - print >> sys.stderr, "%s" % e +except ImportError as errmsg: + print(_("Cannot load pykolab/constants.py:"), file=sys.stderr) + print("%s" % (errmsg), file=sys.stderr) sys.exit(1) import kolabd if __name__ == "__main__": kolabd = kolabd.KolabDaemon() kolabd.run() diff --git a/kolabd/__init__.py b/kolabd/__init__.py index 77b4575..cefcc02 100644 --- a/kolabd/__init__.py +++ b/kolabd/__init__.py @@ -1,390 +1,390 @@ # Copyright 2010-2016 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # """ The Kolab daemon. """ from __future__ import print_function import grp import os import pwd import shutil import sys import time import traceback import pykolab from pykolab.auth import Auth from pykolab import constants from pykolab import utils from pykolab.translate import _ as _l from .process import KolabdProcess as Process # pylint: disable=invalid-name log = pykolab.getLogger('pykolab.daemon') conf = pykolab.getConf() class KolabDaemon: def __init__(self): """ The main Kolab Groupware daemon process. """ daemon_group = conf.add_cli_parser_option_group(_l("Daemon Options")) daemon_group.add_option( "--fork", dest="fork_mode", action="store_true", default=False, help=_l("Fork to the background.") ) daemon_group.add_option( "-p", "--pid-file", dest="pidfile", action="store", default="/var/run/kolabd/kolabd.pid", help=_l("Path to the PID file to use.") ) daemon_group.add_option( "-u", "--user", dest="process_username", action="store", default="kolab", help=_l("Run as user USERNAME"), metavar="USERNAME" ) daemon_group.add_option( "-g", "--group", dest="process_groupname", action="store", default="kolab", help=_l("Run as group GROUPNAME"), metavar="GROUPNAME" ) conf.finalize_conf() # pylint: disable=too-many-branches # pylint: disable=too-many-statements def run(self): """Run Forest, RUN!""" exitcode = 0 utils.ensure_directory( os.path.dirname(conf.pidfile), conf.process_username, conf.process_groupname ) try: try: (ruid, _, _) = os.getresuid() (rgid, _, _) = os.getresgid() except AttributeError: ruid = os.getuid() rgid = os.getgid() if ruid == 0: # Means we can setreuid() / setregid() / setgroups() if rgid == 0: # Get group entry details try: (_, _, group_gid, _) = grp.getgrnam(conf.process_groupname) except KeyError: log.error( _l("Group %s does not exist") % (conf.process_groupname) ) sys.exit(1) # Set real and effective group if not the same as current. if not group_gid == rgid: log.debug( _l("Switching real and effective group id to %d") % (group_gid), level=8 ) os.setregid(group_gid, group_gid) if ruid == 0: # Means we haven't switched yet. try: (_, _, user_uid, _, _, _, _) = pwd.getpwnam(conf.process_username) except KeyError: log.error( _l("User %s does not exist") % (conf.process_username) ) sys.exit(1) # Set real and effective user if not the same as current. if not user_uid == ruid: log.debug( _l("Switching real and effective user id to %d") % (user_uid), level=8 ) os.setreuid(user_uid, user_uid) except Exception: log.error(_l("Could not change real and effective uid and/or gid")) try: pid = os.getpid() if conf.fork_mode: pid = os.fork() if pid > 0 and not conf.fork_mode: self.do_sync() elif pid > 0: sys.exit(0) else: # Give up the session, all control, # all open file descriptors, see #5151 os.chdir("/") os.umask(0) os.setsid() pid = os.fork() if pid > 0: sys.exit(0) sys.stderr.flush() sys.stdout.flush() os.close(0) os.close(1) os.close(2) os.open(os.devnull, os.O_RDONLY) os.open(os.devnull, os.O_WRONLY) os.open(os.devnull, os.O_WRONLY) log.remove_stdout_handler() self.set_signal_handlers() self.write_pid() self.do_sync() except SystemExit as errcode: exitcode = errcode except KeyboardInterrupt: exitcode = 1 log.info(_l("Interrupted by user")) except AttributeError: exitcode = 1 traceback.print_exc() print(_l("Traceback occurred, please report a bug"), file=sys.stderr) except TypeError as errmsg: exitcode = 1 traceback.print_exc() log.error(_l("Type Error: %s") % errmsg) except Exception: exitcode = 2 traceback.print_exc() print(_l("Traceback occurred, please report a bug"), file=sys.stderr) sys.exit(exitcode) # pylint: disable=no-self-use # pylint: disable=too-many-branches # pylint: disable=too-many-locals def do_sync(self): domain_auth = {} primary_domain = conf.get('kolab', 'primary_domain') while 1: primary_auth = Auth(primary_domain) connected = False while not connected: try: primary_auth.connect() connected = True except Exception as errmsg: connected = False log.error(_l("Could not connect to LDAP, is it running?")) log.error(_l("Error: %r") % (errmsg)) log.error("Traceback: %r" % (traceback.format_exc())) time.sleep(5) log.debug(_l("Listing domains..."), level=5) try: domains = primary_auth.list_domains() except Exception: time.sleep(60) continue - if domains: + if not domains: log.error(_l("No domains. Not syncing")) time.sleep(5) continue # domains now is a list of key-valye pairs in the format of # {'secondary': 'primary'}, we want the primaries primaries = list(set(domains.values())) # Store the naming contexts for the domains as # # {'domain': 'naming context'} # # and the domain root dns as # # {'domain': 'domain root dn'} # domain_root_dns = {} naming_contexts = {} for primary in primaries: naming_context = primary_auth.domain_naming_context(primary) # pylint: disable=protected-access domain_root_dn = primary_auth._auth._kolab_domain_root_dn(primary) log.debug( _l("Domain %r naming context: %r, root dn: %r") % ( primary, naming_context, domain_root_dn ), level=8 ) domain_root_dns[primary] = domain_root_dn naming_contexts[primary] = naming_context log.debug( _l("Naming contexts to synchronize: %r") % ( list(set(naming_contexts.values())) ), level=8 ) # Find however many naming contexts we have, and what the # corresponding domain name is for them. primary_domains = [x for x, y in naming_contexts.items() if domain_root_dns[x] == y] # Now we can check if any changes happened. added_domains = [] removed_domains = [] # Combine the domains from LDAP with the domain processes # accounted for locally. all_domains = list(set(primary_domains + domain_auth.keys())) log.debug(_l("Result set of domains: %r") % (all_domains), level=8) for domain in all_domains: log.debug(_l("Checking for domain %s") % (domain), level=8) if domain in domain_auth.keys() and domain in primary_domains: if not domain_auth[domain].is_alive(): log.debug(_l("Domain %s isn't alive anymore.") % (domain), level=8) domain_auth[domain].terminate() added_domains.append(domain) else: log.debug(_l("Domain %s already there and alive.") % (domain), level=8) continue elif domain in domain_auth.keys(): log.debug(_l("Domain %s should not exist any longer.") % (domain), level=8) removed_domains.append(domain) else: log.debug(_l("Domain %s does not have a process yet.") % (domain), level=8) added_domains.append(domain) if not removed_domains and not added_domains: try: sleep_between_domain_operations_in_seconds = (float)( conf.get( 'kolab', 'domain_sync_interval' ) ) time.sleep(sleep_between_domain_operations_in_seconds) except ValueError: time.sleep(600) log.debug( _l("added domains: %r, removed domains: %r") % (added_domains, removed_domains), level=8 ) for domain in added_domains: domain_auth[domain] = Process(domain) domain_auth[domain].start() # Pause or hammer your LDAP server to death if len(added_domains) >= 5: time.sleep(10) for domain in removed_domains: domain_auth[domain].terminate() del domain_auth[domain] def reload_config(self, *args, **kw): pass def remove_pid(self, *args, **kw): """ Remove our PID file. Note that multiple processes can attempt to do this very same thing at the same time, and therefore we need to test if the PID file exists, and only try/except removing it. """ if os.access(conf.pidfile, os.R_OK): try: os.remove(conf.pidfile) except Exception: pass raise SystemExit def set_signal_handlers(self): import signal signal.signal(signal.SIGHUP, self.reload_config) signal.signal(signal.SIGTERM, self.remove_pid) def write_pid(self): pid = os.getpid() fp = open(conf.pidfile, 'w') fp.write("%d\n" % (pid)) fp.close() diff --git a/pykolab/auth/__init__.py b/pykolab/auth/__init__.py index 531de1e..ad09130 100644 --- a/pykolab/auth/__init__.py +++ b/pykolab/auth/__init__.py @@ -1,319 +1,319 @@ # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import logging import os import time import pykolab -import pykolab.base +from pykolab.base import Base from pykolab.translate import _ +# pylint: disable=invalid-name log = pykolab.getLogger('pykolab.auth') conf = pykolab.getConf() -class Auth(pykolab.base.Base): + +# pylint: disable=too-many-public-methods +class Auth(Base): """ This is the Authentication and Authorization module for PyKolab. """ def __init__(self, domain=None): """ Initialize the authentication class. """ - pykolab.base.Base.__init__(self, domain=domain) + Base.__init__(self, domain=domain) self._auth = None def authenticate(self, login): """ Verify login credentials supplied in login against the appropriate authentication backend. Login is a simple list of username, password, service and, optionally, the realm. """ if len(login) == 3: # The realm has not been specified. See if we know whether or not # to use virtual_domains, as this may be a cause for the realm not # having been specified separately. use_virtual_domains = conf.get('imap', 'virtual_domains') # TODO: Insert debug statements #if use_virtual_domains == "userid": #print "# Derive domain from login[0]" #elif not use_virtual_domains: #print "# Explicitly do not user virtual domains??" #else: ## Do use virtual domains, derive domain from login[0] #print "# Derive domain from login[0]" if len(login[0].split('@')) > 1: domain = login[0].split('@')[1] elif len(login) >= 4: domain = login[3] else: domain = conf.get("kolab", "primary_domain") # realm overrides domain if len(login) == 4: domain = login[3] retval = self._auth.authenticate(login, domain) return retval def connect(self, domain=None): """ Connect to the domain authentication backend using domain, or fall back to the primary domain specified by the configuration. """ log.debug(_("Called for domain %r") % (domain), level=8) if not self._auth == None: return if domain == None: if not self.domain == None: section = self.domain domain = self.domain else: section = 'kolab' domain = conf.get('kolab', 'primary_domain') else: self.list_domains(domain) section = domain log.debug( - _("Using section %s and domain %s") % (section,domain), - level=8 - ) + _("Using section %s and domain %s") % (section, domain), + level=8 + ) - if not self.domains == None and self.domains.has_key(domain): + if self.domains is not None and domain in self.domains: section = self.domains[domain] domain = self.domains[domain] log.debug( - _("Using section %s and domain %s") % (section,domain), - level=8 - ) + _("Using section %s and domain %s") % (section, domain), + level=8 + ) log.debug( - _("Connecting to Authentication backend for domain %s") % ( - domain - ), - level=8 - ) + _("Connecting to Authentication backend for domain %s") % ( + domain + ), + level=8 + ) if not conf.has_section(section): section = 'kolab' if not conf.has_option(section, 'auth_mechanism'): log.debug( - _("Section %s has no option 'auth_mechanism'") % (section), - level=8 - ) + _("Section %s has no option 'auth_mechanism'") % (section), + level=8 + ) section = 'kolab' else: log.debug( - _("Section %s has auth_mechanism: %r") % ( - section, - conf.get(section,'auth_mechanism') - ), - level=8 - ) + _("Section %s has auth_mechanism: %r") % ( + section, + conf.get(section, 'auth_mechanism') + ), + level=8 + ) # Get the actual authentication and authorization backend. if conf.get(section, 'auth_mechanism') == 'ldap': log.debug(_("Starting LDAP..."), level=8) - from pykolab.auth import ldap - self._auth = ldap.LDAP(self.domain) + from pykolab.auth.ldap import LDAP + self._auth = LDAP(self.domain) - elif conf.get(section, 'auth_mechanism') == 'sql': - from pykolab.auth import sql - self._auth = sql.SQL(self.domain) + # elif conf.get(section, 'auth_mechanism') == 'sql': + # from .sql import SQL + # self._auth = SQL(self.domain) else: log.debug(_("Starting LDAP..."), level=8) - from pykolab.auth import ldap - self._auth = ldap.LDAP(self.domain) + from pykolab.auth.ldap import LDAP + self._auth = LDAP(self.domain) self._auth.connect() def disconnect(self, domain=None): """ Connect to the domain authentication backend using domain, or fall back to the primary domain specified by the configuration. """ - if domain == None: - section = 'kolab' + if domain is None: domain = conf.get('kolab', 'primary_domain') - else: - section = domain - if not self._auth or self._auth == None: + if not self._auth: return self._auth._disconnect() del self._auth self._auth = None def find_folder_resource(self, folder): """ Find one or more resources corresponding to the shared folder name. """ if not self._auth or self._auth == None: self.connect() result = self._auth.find_folder_resource(folder) if isinstance(result, list) and len(result) == 1: return result[0] else: return result def find_recipient(self, address, domain=None, search_attrs=None): """ Find one or more entries corresponding to the recipient address. """ if not domain == None and not self.domain == domain: self.connect(domain=domain) if not self._auth or self._auth == None: self.connect(domain=domain) result = self._auth.find_recipient(address, search_attrs=search_attrs) if isinstance(result, list) and len(result) == 1: return result[0] else: return result def find_resource(self, address): """ Find one or more resources corresponding to the recipient address. """ if not self._auth or self._auth == None: self.connect() result = self._auth.find_resource(address) if isinstance(result, list) and len(result) == 1: return result[0] else: return result def find_user(self, attr, value, **kw): return self._auth.search_entry_by_attribute(attr, value, **kw) def find_user_dn(self, login, kolabuser=False): return self._auth._find_user_dn(login, kolabuser); def list_recipient_addresses(self, user): return self._auth.list_recipient_addresses(user) def extract_recipient_addresses(self, entry): return self._auth.extract_recipient_addresses(entry) def list_delegators(self, user): return self._auth.list_delegators(user) def list_domains(self, domain=None): """ List the domains using the auth_mechanism setting in the kolab section of the configuration file, either ldap or sql or (...). The actual setting would be used by self.connect(), and stuffed into self._auth, for use with self._auth._list_domains() For each domain found, returns a two-part tuple of the primary domain and a list of secondary domains (aliases). """ # Connect to the global namespace self.connect() # Find the domains in the authentication backend. kolab_primary_domain = conf.get('kolab', 'primary_domain') if self.domains == None: try: domains = self._auth._list_domains(domain) except: if not self.domain == kolab_primary_domain: return { self.domain: self.domain } else: domains = {} # If no domains are found, the primary domain is used. if len(domains) < 1: self.domains = { kolab_primary_domain: kolab_primary_domain } else: self.domains = {} for primary, secondaries in domains: self.domains[primary.lower()] = primary.lower() for secondary in secondaries: self.domains[secondary.lower()] = primary.lower() return self.domains def synchronize(self, mode=0, callback=None): self._auth.synchronize(mode=mode, callback=callback) def domain_default_quota(self, domain): return self._auth._domain_default_quota(domain) def domain_naming_context(self, domain): return self._auth._domain_naming_context(domain) def primary_domain_for_naming_context(self, domain): return self._auth._primary_domain_for_naming_context(domain) def get_entry_attribute(self, domain, entry, attribute): return self._auth.get_entry_attribute(entry, attribute) def get_entry_attributes(self, domain, entry, attributes): return self._auth.get_entry_attributes(entry, attributes) def get_user_attribute(self, domain, user, attribute): return self._auth.get_entry_attribute(user, attribute) def get_user_attributes(self, domain, user, attributes): return self._auth.get_entry_attributes(user, attributes) def search_entry_by_attribute(self, attr, value, **kw): return self._auth.search_entry_by_attribute(attr, value, **kw) def search_mail_address(self, domain, mail_address): return self._auth._search_mail_address(domain, mail_address) def set_entry_attribute(self, domain, entry, attribute, value): return self._auth.set_entry_attribute(entry, attribute, value) def set_entry_attributes(self, domain, entry, attributes): return self._auth.set_entry_attributes(entry, attributes) def set_user_attribute(self, domain, user, attribute, value): self._auth._set_user_attribute(user, attribute, value) diff --git a/pykolab/auth/ldap/__init__.py b/pykolab/auth/ldap/__init__.py index c414118..d114789 100644 --- a/pykolab/auth/ldap/__init__.py +++ b/pykolab/auth/ldap/__init__.py @@ -1,3249 +1,3218 @@ # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +# pylint: disable=too-many-lines + from __future__ import print_function import datetime # Catch python-ldap-2.4 changes from distutils import version import logging import time import traceback import ldap import ldap.controls +try: + from ldap.controls import psearch +except ImportError: + pass + +from ldap.dn import explode_dn + import ldap.filter +from six import string_types import _ldap import pykolab -import pykolab.base from pykolab import utils -from pykolab.constants import * +from pykolab.base import Base +from pykolab.constants import SUPPORTED_LDAP_CONTROLS from pykolab.errors import * -from pykolab.translate import _ +from pykolab.translate import _ as _l import auth_cache import cache # pylint: disable=invalid-name log = pykolab.getLogger('pykolab.auth') conf = pykolab.getConf() -if version.StrictVersion('2.4.0') <= version.StrictVersion(ldap.__version__): - LDAP_CONTROL_PAGED_RESULTS = ldap.CONTROL_PAGEDRESULTS -else: - if hasattr(ldap, 'LDAP_CONTROL_PAGE_OID'): - # pylint: disable=no-member - LDAP_CONTROL_PAGED_RESULTS = ldap.LDAP_CONTROL_PAGE_OID - else: - LDAP_CONTROL_PAGED_RESULTS = ldap.controls.SimplePagedResultsControl.controlType - -try: - from ldap.controls import psearch -except ImportError: - log.warning(_("Python LDAP library does not support persistent search")) - - -class SimplePagedResultsControl(ldap.controls.SimplePagedResultsControl): - """ - - Python LDAP 2.4 and later breaks the API. This is an abstraction class - so that we can handle either. - """ - - def __init__(self, page_size=0, cookie=''): - if version.StrictVersion('2.4.0') <= version.StrictVersion(ldap.__version__): - - ldap.controls.SimplePagedResultsControl.__init__( - self, - size=page_size, - cookie=cookie - ) - - else: - ldap.controls.SimplePagedResultsControl.__init__( - self, - LDAP_CONTROL_PAGED_RESULTS, - True, - (page_size, '') - ) - - def cookie(self): - if version.StrictVersion('2.4.0') <= version.StrictVersion(ldap.__version__): - return self.cookie - - return self.controlValue[1] - - def size(self): - if version.StrictVersion('2.4.0') <= version.StrictVersion(ldap.__version__): - return self.size - else: - return self.controlValue[0] - -class LDAP(pykolab.base.Base): +class LDAP(Base): """ Abstraction layer for the LDAP authentication / authorization backend, for use with Kolab. """ def __init__(self, domain=None): """ Initialize the LDAP object for domain. If no domain is specified, domain name space configured as 'kolab'.'primary_domain' is used. """ - pykolab.base.Base.__init__(self, domain=domain) + Base.__init__(self, domain=domain) self.ldap = None self.ldap_priv = None self.bind = None if domain is None: self.domain = conf.get('kolab', 'primary_domain') else: self.domain = domain + # pylint: disable=too-many-branches + # pylint: disable=too-many-locals + # pylint: disable=too-many-return-statements + # pylint: disable=too-many-statements def authenticate(self, login, realm): """ Find the entry corresponding to login, and attempt a bind. login is a tuple with 4 values. In order of appearance; [0] - the login username. [1] - the password [2] - the service (optional) [3] - the realm Called from pykolab.auth.Auth, the realm parameter is derived, while login[3] preserves the originally specified realm. """ try: log.debug( - _("Attempting to authenticate user %s in realm %s") % ( + _l("Attempting to authenticate user %s in realm %s") % ( login[0], realm ), level=8 ) except Exception: pass self.connect(immediate=True) self._bind() # See if we know a base_dn for the domain base_dn = None try: base_dn = auth_cache.get_entry(self.domain) except Exception as errmsg: - log.error(_("Authentication cache failed: %r") % (errmsg)) - pass + log.error(_l("Authentication cache failed: %r") % (errmsg)) if base_dn is None: config_base_dn = self.config_get('base_dn') ldap_base_dn = self._kolab_domain_root_dn(self.domain) if ldap_base_dn is not None and not ldap_base_dn == config_base_dn: base_dn = ldap_base_dn else: base_dn = config_base_dn try: auth_cache.set_entry(self.domain, base_dn) except Exception as errmsg: - log.error(_("Authentication cache failed: %r") % (errmsg)) - pass + log.error(_l("Authentication cache failed: %r") % (errmsg)) try: user_filter = self.config_get_raw('user_filter') % ( {'base_dn': base_dn} ) except TypeError: user_filter = self.config_get_raw('user_filter') _filter = '(&(|' auth_attrs = self.config_get_list('auth_attributes') for attr in auth_attrs: _filter += "(%s=%s)" % (attr, login[0]) _filter += "(%s=%s@%s)" % (attr, login[0], realm) _filter += ')%s)' % (user_filter) entry_dn = None # Attempt to obtain an entry_dn from cache. try: entry_dn = auth_cache.get_entry(_filter) except Exception as errmsg: - log.error(_("Authentication cache failed: %r") % (errmsg)) - pass + log.error(_l("Authentication cache failed: %r") % (errmsg)) retval = False - timeout = self.config_get('timeout', default=10) + timeout = self.config_get('ldap', 'timeout', default=10) if entry_dn is None: _search = self.ldap.search_ext( base_dn, ldap.SCOPE_SUBTREE, filterstr=_filter, attrlist=['entrydn'], attrsonly=True, timeout=timeout ) try: ( _result_type, _result_data, _result_msgid, _result_controls ) = self.ldap.result3(_search) except ldap.INVALID_CREDENTIALS: log.error( - _("Invalid DN, username and/or password for '%s'.") % ( - bind_dn + _l("Invalid DN, username and/or password for '%s'.") % ( + _filter ) ) return False except ldap.NO_SUCH_OBJECT: log.error( - _("Invalid DN, username and/or password for '%s'.") % ( - bind_dn + _l("Invalid DN, username and/or password for '%s'.") % ( + _filter ) ) return False except ldap.SERVER_DOWN as errmsg: - log.error(_("LDAP server unavailable: %r") % (errmsg)) + log.error(_l("LDAP server unavailable: %r") % (errmsg)) log.error(traceback.format_exc()) self._disconnect() return False except ldap.TIMEOUT: - log.error(_("LDAP timeout.")) + log.error(_l("LDAP timeout.")) self._disconnect() return False except Exception as errmsg: - log.error(_("Exception occurred: %r") % (errmsg)) + log.error(_l("Exception occurred: %r") % (errmsg)) log.error(traceback.format_exc()) self._disconnect() return False log.debug( - _("Length of entries found: %r") % ( + _l("Length of entries found: %r") % ( len(_result_data) ), level=8 ) # Remove referrals _result_data = [_e for _e in _result_data if _e[0] is not None] if len(_result_data) == 1: - (entry_dn, entry_attrs) = _result_data[0] + (entry_dn, _) = _result_data[0] elif len(_result_data) > 1: try: log.info( - _("Authentication for %r failed (multiple entries)") % ( + _l("Authentication for %r failed (multiple entries)") % ( login[0] ) ) except Exception: pass self._disconnect() return False else: try: log.info( - _("Authentication for %r failed (no entry)") % ( + _l("Authentication for %r failed (no entry)") % ( login[0] ) ) except Exception: pass self._disconnect() return False if entry_dn is None: try: log.info( - _("Authentication for %r failed (LDAP error?)") % ( + _l("Authentication for %r failed (LDAP error?)") % ( login[0] ) ) except Exception: pass self._disconnect() return False try: # Needs to be synchronous or succeeds and continues setting # retval to True!! retval = self._bind(entry_dn, login[1]) if retval: try: log.info( - _("Authentication for %r succeeded") % ( + _l("Authentication for %r succeeded") % ( login[0] ) ) except Exception: pass else: try: log.info( - _("Authentication for %r failed (error)") % ( + _l("Authentication for %r failed (error)") % ( login[0] ) ) except Exception: pass self._disconnect() return False try: auth_cache.set_entry(_filter, entry_dn) except Exception as errmsg: - log.error(_("Authentication cache failed: %r") % (errmsg)) - pass + log.error(_l("Authentication cache failed: %r") % (errmsg)) except ldap.SERVER_DOWN: - log.error(_("Authentication failed, LDAP server unavailable")) + log.error(_l("Authentication failed, LDAP server unavailable")) self._disconnect() return False except Exception: try: log.debug( - _("Failed to authenticate as user %r") % ( + _l("Failed to authenticate as user %r") % ( login[0] ), level=8 ) except Exception: pass self._disconnect() return False else: try: # Needs to be synchronous or succeeds and continues setting # retval to True!! retval = self._bind(entry_dn, login[1]) if retval: - log.info(_("Authentication for %r succeeded") % (login[0])) + log.info(_l("Authentication for %r succeeded") % (login[0])) else: log.info( - _("Authentication for %r failed (password)") % ( + _l("Authentication for %r failed (password)") % ( login[0] ) ) self._disconnect() return False except ldap.NO_SUCH_OBJECT as errmsg: log.debug( - _("Error occured, there is no such object: %r") % ( + _l("Error occured, there is no such object: %r") % ( errmsg ), level=8 ) self.bind = None try: auth_cache.del_entry(_filter) except Exception: - log.error(_("Authentication cache failed to clear entry")) - pass + log.error(_l("Authentication cache failed to clear entry")) retval = self.authenticate(login, realm) except Exception as errmsg: - log.debug(_("Exception occured: %r") % (errmsg)) + log.debug(_l("Exception occured: %r") % (errmsg)) try: log.debug( - _("Failed to authenticate as user %r") % ( + _l("Failed to authenticate as user %r") % ( login[0] ), level=8 ) except Exception: pass self._disconnect() return False self._disconnect() return retval def connect(self, priv=None, immediate=False): """ Connect to the LDAP server through the uri configured. """ # Already connected if priv is None and self.ldap is not None: return # Already connected if priv is not None and self.ldap_priv is not None: return - log.debug(_("Connecting to LDAP..."), level=8) + log.debug(_l("Connecting to LDAP..."), level=8) uri = self.config_get('ldap_uri') - log.debug(_("Attempting to use LDAP URI %s") % (uri), level=8) + log.debug(_l("Attempting to use LDAP URI %s") % (uri), level=8) trace_level = 0 if conf.debuglevel > 8: trace_level = 1 if immediate: retry_max = 1 retry_delay = 1.0 else: retry_max = 200 retry_delay = 3.0 conn = ldap.ldapobject.ReconnectLDAPObject( uri, trace_level=trace_level, trace_file=pykolab.logger.StderrToLogger(log), retry_max=retry_max, retry_delay=retry_delay ) if immediate: conn.set_option(ldap.OPT_TIMEOUT, 10) conn.protocol_version = 3 conn.supported_controls = [] if priv is None: self.ldap = conn else: self.ldap_priv = conn def entry_dn(self, entry_id): """ Get a entry's distinguished name for an entry ID. The entry ID may be any of: - an entry's value for the configured unique_attribute, - a (syntactically valid) Distinguished Name, - a dictionary such as previously returned as (part of) the result of a search. """ entry_dn = None if self._entry_dn(entry_id): return entry_id if self._entry_dict(entry_id): return entry_id['dn'] unique_attribute = self.config_get('unique_attribute') config_base_dn = self.config_get('base_dn') ldap_base_dn = self._kolab_domain_root_dn(self.domain) if ldap_base_dn is not None and not ldap_base_dn == config_base_dn: base_dn = ldap_base_dn else: base_dn = config_base_dn _filter = "(%s=%s)" % (unique_attribute, ldap.filter.escape_filter_chars(entry_id)) _search = self.ldap.search_ext( base_dn, ldap.SCOPE_SUBTREE, _filter, ['entrydn'] ) ( _result_type, _result_data, _result_msgid, _result_controls ) = self.ldap.result3(_search) if len(_result_data) >= 1: - (entry_dn, entry_attrs) = _result_data[0] + (entry_dn, _) = _result_data[0] return entry_dn def get_entry_attribute(self, entry_id, attribute): """ Get an attribute for an entry. Return the attribute value if successful, or None if not. """ entry_attrs = self.get_entry_attributes(entry_id, [attribute]) if attribute in entry_attrs: return entry_attrs[attribute] - elif attribute.lower() in entry_attrs: + + if attribute.lower() in entry_attrs: return entry_attrs[attribute.lower()] - else: - return None + + return None def get_entry_attributes(self, entry_id, attributes): """ Get multiple attributes for an entry. """ self._bind() - log.debug(_("Entry ID: %r") % (entry_id), level=8) + log.debug(_l("Entry ID: %r") % (entry_id), level=8) entry_dn = self.entry_dn(entry_id) - log.debug(_("Entry DN: %r") % (entry_dn), level=8) + log.debug(_l("Entry DN: %r") % (entry_dn), level=8) log.debug( - _("ldap search: (%r, %r, filterstr='(objectclass=*)', attrlist=[ 'dn' ] + %r") % ( + _l("ldap search: (%r, %r, filterstr='(objectclass=*)', attrlist=[ 'dn' ] + %r") % ( entry_dn, ldap.SCOPE_BASE, attributes ), level=8 ) _search = self.ldap.search_ext( entry_dn, ldap.SCOPE_BASE, filterstr='(objectclass=*)', attrlist=['dn'] + attributes ) ( _result_type, _result_data, _result_msgid, _result_controls ) = self.ldap.result3(_search) if len(_result_data) >= 1: (_entry_dn, _entry_attrs) = _result_data[0] else: return None return utils.normalize(_entry_attrs) def list_recipient_addresses(self, entry_id): """ Give a list of all valid recipient addresses for an LDAP entry identified by its ID. """ mail_attributes = conf.get_list('ldap', 'mail_attributes') entry = self.get_entry_attributes(entry_id, mail_attributes) return self.extract_recipient_addresses(entry) if entry is not None else [] + # pylint: disable=no-self-use def extract_recipient_addresses(self, entry): """ Extact a list of all valid recipient addresses for the given LDAP entry. This includes all attributes configured for ldap.mail_attributes """ recipient_addresses = [] mail_attributes = conf.get_list('ldap', 'mail_attributes') for attr in mail_attributes: if attr in entry: if isinstance(entry[attr], list): recipient_addresses.extend(entry[attr]) - elif isinstance(entry[attr], basestring): + elif isinstance(entry[attr], string_types): recipient_addresses.append(entry[attr]) return recipient_addresses def list_delegators(self, entry_id): """ Get a list of user records the given user is set to be a delegatee """ delegators = [] mailbox_attribute = conf.get('cyrus-sasl', 'result_attribute') if mailbox_attribute is None: mailbox_attribute = 'mail' for __delegator in self.search_entry_by_attribute('kolabDelegate', entry_id): (_dn, _delegator) = __delegator _delegator['dn'] = _dn if mailbox_attribute in _delegator: _delegator['_mailbox_basename'] = _delegator[mailbox_attribute] else: _delegator['_mailbox_basename'] = None if isinstance(_delegator['_mailbox_basename'], list): _delegator['_mailbox_basename'] = _delegator['_mailbox_basename'][0] delegators.append(_delegator) return delegators def find_folder_resource(self, folder="*", exclude_entry_id=None): """ Given a shared folder name or list of folder names, find one or more valid resources. Specify an additional entry_id to exclude to exclude matches. """ self._bind() if exclude_entry_id is not None: __filter_prefix = "(&" __filter_suffix = "(!(%s=%s)))" % ( self.config_get('unique_attribute'), exclude_entry_id ) else: __filter_prefix = "" __filter_suffix = "" resource_filter = self.config_get('resource_filter') if resource_filter is not None: __filter_prefix = "(&%s" % resource_filter __filter_suffix = ")" recipient_address_attrs = self.config_get_list("mail_attributes") result_attributes = recipient_address_attrs result_attributes.append(self.config_get('unique_attribute')) result_attributes.append('kolabTargetFolder') _filter = "(|" - if isinstance(folder, basestring): + if isinstance(folder, string_types): _filter += "(kolabTargetFolder=%s)" % (folder) else: for _folder in folder: _filter += "(kolabTargetFolder=%s)" % (_folder) _filter += ")" _filter = "%s%s%s" % (__filter_prefix, _filter, __filter_suffix) - log.debug(_("Finding resource with filter %r") % (_filter), level=8) + log.debug(_l("Finding resource with filter %r") % (_filter), level=8) if len(_filter) <= 6: return None config_base_dn = self.config_get('resource_base_dn') ldap_base_dn = self._kolab_domain_root_dn(self.domain) if ldap_base_dn is not None and not ldap_base_dn == config_base_dn: resource_base_dn = ldap_base_dn else: resource_base_dn = config_base_dn _results = self.ldap.search_s( resource_base_dn, scope=ldap.SCOPE_SUBTREE, filterstr=_filter, attrlist=result_attributes, attrsonly=True ) _entry_dns = [] for _result in _results: (_entry_id, _entry_attrs) = _result _entry_dns.append(_entry_id) return _entry_dns def find_recipient(self, address="*", exclude_entry_id=None, search_attrs=None): """ Given an address string or list of addresses, find one or more valid recipients. Use this function only to detect whether an address is already in use by any entry in the tree. Specify an additional entry_id to exclude to exclude matches against the current entry. In search_attrs you can specify list of search attributes. By default mail_attributes are used. """ self._bind() if exclude_entry_id is not None: __filter_prefix = "(&" __filter_suffix = "(!(%s=%s)))" % ( self.config_get('unique_attribute'), ldap.filter.escape_filter_chars(exclude_entry_id) ) else: __filter_prefix = "" __filter_suffix = "" if search_attrs is not None: recipient_address_attrs = search_attrs else: recipient_address_attrs = self.config_get_list("mail_attributes") result_attributes = recipient_address_attrs result_attributes.append(self.config_get('unique_attribute')) _filter = "(|" for recipient_address_attr in recipient_address_attrs: - if isinstance(address, basestring): + if isinstance(address, string_types): _filter += "(%s=%s)" % (recipient_address_attr, address) else: for _address in address: _filter += "(%s=%s)" % (recipient_address_attr, _address) _filter += ")" _filter = "%s%s%s" % (__filter_prefix, _filter, __filter_suffix) - log.debug(_("Finding recipient with filter %r") % (_filter), level=8) + log.debug(_l("Finding recipient with filter %r") % (_filter), level=8) if len(_filter) <= 6: return None config_base_dn = self.config_get('base_dn') ldap_base_dn = self._kolab_domain_root_dn(self.domain) if ldap_base_dn is not None and not ldap_base_dn == config_base_dn: base_dn = ldap_base_dn else: base_dn = config_base_dn _results = self.ldap.search_s( base_dn, scope=ldap.SCOPE_SUBTREE, filterstr=_filter, attrlist=result_attributes, attrsonly=True ) _entry_dns = [] for _result in _results: (_entry_id, _entry_attrs) = _result # Prevent Active Directory referrals if _entry_id is not None: _entry_dns.append(_entry_id) return _entry_dns def find_resource(self, address="*", exclude_entry_id=None): """ Given an address string or list of addresses, find one or more valid resources. Specify an additional entry_id to exclude to exclude matches. """ self._bind() if exclude_entry_id is not None: __filter_prefix = "(&" __filter_suffix = "(!(%s=%s)))" % ( self.config_get('unique_attribute'), ldap.filter.escape_filter_chars(exclude_entry_id) ) else: __filter_prefix = "" __filter_suffix = "" resource_filter = self.config_get('resource_filter') if resource_filter is not None: __filter_prefix = "(&%s" % resource_filter __filter_suffix = ")" recipient_address_attrs = self.config_get_list("mail_attributes") result_attributes = recipient_address_attrs result_attributes.append(self.config_get('unique_attribute')) _filter = "(|" for recipient_address_attr in recipient_address_attrs: - if isinstance(address, basestring): + if isinstance(address, string_types): _filter += "(%s=%s)" % (recipient_address_attr, address) else: for _address in address: _filter += "(%s=%s)" % (recipient_address_attr, _address) _filter += ")" _filter = "%s%s%s" % (__filter_prefix, _filter, __filter_suffix) - log.debug(_("Finding resource with filter %r") % (_filter), level=8) + log.debug(_l("Finding resource with filter %r") % (_filter), level=8) if len(_filter) <= 6: return None config_base_dn = self.config_get('resource_base_dn') ldap_base_dn = self._kolab_domain_root_dn(self.domain) if ldap_base_dn is not None and not ldap_base_dn == config_base_dn: resource_base_dn = ldap_base_dn else: resource_base_dn = config_base_dn _results = self.ldap.search_s( resource_base_dn, scope=ldap.SCOPE_SUBTREE, filterstr=_filter, attrlist=result_attributes, attrsonly=True ) # Remove referrals _entry_dns = [_e[0] for _e in _results if _e[0] is not None] return _entry_dns def get_latest_sync_timestamp(self): timestamp = cache.last_modify_timestamp(self.domain) - log.debug(_("Using timestamp %r") % (timestamp), level=8) + log.debug(_l("Using timestamp %r") % (timestamp), level=8) return timestamp def list_secondary_domains(self): """ List alias domain name spaces for the current domain name space. """ if self.domains is not None: return [s for s in self.domains.keys() if s not in self.domains.values()] - else: - return [] + + return [] def recipient_policy(self, entry): """ Apply a recipient policy, if configured. Given an entry, returns the entry's attribute values to be set. """ entry_dn = self.entry_dn(entry) entry_modifications = {} entry_type = self._entry_type(entry) mail_attributes = self.config_get_list('mail_attributes') primary_mail = None primary_mail_attribute = None secondary_mail = None secondary_mail_attribute = None if len(mail_attributes) >= 1: primary_mail_attribute = mail_attributes[0] if len(mail_attributes) >= 2: secondary_mail_attribute = mail_attributes[1] daemon_rcpt_policy = self.config_get('daemon_rcpt_policy') if not utils.true_or_false(daemon_rcpt_policy) and daemon_rcpt_policy is not None: log.debug( - _("Not applying recipient policy for %s (disabled through configuration)") % ( + _l("Not applying recipient policy for %s (disabled through configuration)") % ( entry_dn ), level=1 ) return entry_modifications want_attrs = [] - log.debug(_("Applying recipient policy to %r") % (entry_dn), level=8) + log.debug(_l("Applying recipient policy to %r") % (entry_dn), level=8) # See which mail attributes we would want to control. # # 'mail' is considered for primary_mail, # 'alias' and 'mailalternateaddress' are considered for secondary mail. # primary_mail = self.config_get_raw('%s_primary_mail' % (entry_type)) if primary_mail is None and entry_type == 'user': primary_mail = self.config_get_raw('primary_mail') if secondary_mail_attribute is not None: secondary_mail = self.config_get_raw('%s_secondary_mail' % (entry_type)) if secondary_mail is None and entry_type == 'user': secondary_mail = self.config_get_raw('secondary_mail') log.debug( - _("Using mail attributes: %r, with primary %r and secondary %r") % ( + _l("Using mail attributes: %r, with primary %r and secondary %r") % ( mail_attributes, primary_mail_attribute, secondary_mail_attribute ), level=8 ) for _mail_attr in mail_attributes: - if mail_attr not in entry: - log.debug(_("key %r not in entry") % (_mail_attr), level=8) + if _mail_attr not in entry: + log.debug(_l("key %r not in entry") % (_mail_attr), level=8) if _mail_attr == primary_mail_attribute: - log.debug(_("key %r is the prim. mail attr.") % (_mail_attr), level=8) + log.debug(_l("key %r is the prim. mail attr.") % (_mail_attr), level=8) if primary_mail is not None: - log.debug(_("prim. mail pol. is not empty"), level=8) + log.debug(_l("prim. mail pol. is not empty"), level=8) want_attrs.append(_mail_attr) elif _mail_attr == secondary_mail_attribute: - log.debug(_("key %r is the sec. mail attr.") % (_mail_attr), level=8) + log.debug(_l("key %r is the sec. mail attr.") % (_mail_attr), level=8) if secondary_mail is not None: - log.debug(_("sec. mail pol. is not empty"), level=8) + log.debug(_l("sec. mail pol. is not empty"), level=8) want_attrs.append(_mail_attr) - if len(want_attrs) > 0: + if want_attrs: log.debug( - _("Attributes %r are not yet available for entry %r") % ( + _l("Attributes %r are not yet available for entry %r") % ( want_attrs, entry_dn ), level=8 ) # Also append the preferredlanguage or 'native tongue' configured # for the entry. if 'preferredlanguage' not in entry: want_attrs.append('preferredlanguage') # If we wanted anything, now is the time to get it. - if len(want_attrs) > 0: + if want_attrs: log.debug( - _("Attributes %r are not yet available for entry %r") % ( + _l("Attributes %r are not yet available for entry %r") % ( want_attrs, entry_dn ), level=8 ) attributes = self.get_entry_attributes(entry_dn, want_attrs) for attribute in attributes.keys(): entry[attribute] = attributes[attribute] if 'preferredlanguage' not in entry: entry['preferredlanguage'] = conf.get('kolab', 'default_locale') # Primary mail address if primary_mail is not None: primary_mail_address = conf.plugins.exec_hook( "set_primary_mail", kw={ 'primary_mail': primary_mail, 'entry': entry, 'primary_domain': self.domain } ) if primary_mail_address is None: return entry_modifications i = 1 _primary_mail = primary_mail_address done = False while not done: results = self.find_recipient(_primary_mail, entry['id']) # Length of results should be 0 (no entry found) # or 1 (which should be the entry we're looking at here) - if len(results) == 0: + if not results: log.debug( - _("No results for mail address %s found") % ( + _l("No results for mail address %s found") % ( _primary_mail ), level=8 ) done = True continue if len(results) == 1: log.debug( - _("1 result for address %s found, verifying") % ( + _l("1 result for address %s found, verifying") % ( _primary_mail ), level=8 ) almost_done = True for result in results: if not result == entry_dn: log.debug( - _( + _l( "Too bad, primary email address %s " + "already in use for %s (we are %s)" ) % ( _primary_mail, result, entry_dn ), level=8 ) almost_done = False else: - log.debug(_("Address assigned to us"), level=8) + log.debug(_l("Address assigned to us"), level=8) if almost_done: done = True continue i += 1 _primary_mail = "%s%d@%s" % ( primary_mail_address.split('@')[0], i, primary_mail_address.split('@')[1] ) primary_mail_address = _primary_mail ### # FIXME ### if primary_mail_address is not None: if primary_mail_attribute not in entry: self.set_entry_attribute(entry, primary_mail_attribute, primary_mail_address) entry_modifications[primary_mail_attribute] = primary_mail_address else: if not primary_mail_address == entry[primary_mail_attribute]: self.set_entry_attribute( entry, primary_mail_attribute, primary_mail_address ) entry_modifications[primary_mail_attribute] = primary_mail_address + # pylint: disable=too-many-nested-blocks if secondary_mail is not None: # Execute the plugin hook suggested_secondary_mail = conf.plugins.exec_hook( "set_secondary_mail", kw={ 'secondary_mail': secondary_mail, 'entry': entry, 'domain': self.domain, 'primary_domain': self.domain, 'secondary_domains': self.list_secondary_domains() } ) # end of conf.plugins.exec_hook() call secondary_mail_addresses = [] for _secondary_mail in suggested_secondary_mail: i = 1 __secondary_mail = _secondary_mail done = False while not done: results = self.find_recipient(__secondary_mail, entry['id']) # Length of results should be 0 (no entry found) # or 1 (which should be the entry we're looking at here) - if len(results) == 0: + if not results: log.debug( - _("No results for address %s found") % ( + _l("No results for address %s found") % ( __secondary_mail ), level=8 ) done = True continue if len(results) == 1: log.debug( - _("1 result for address %s found, verifying...") % ( + _l("1 result for address %s found, verifying...") % ( __secondary_mail ), level=8 ) almost_done = True for result in results: if not result == entry_dn: log.debug( - _( + _l( "Too bad, secondary email " + "address %s already in use for " + "%s (we are %s)" ) % ( __secondary_mail, result, entry_dn ), level=8 ) almost_done = False else: - log.debug(_("Address assigned to us"), level=8) + log.debug(_l("Address assigned to us"), level=8) if almost_done: done = True continue i += 1 __secondary_mail = "%s%d@%s" % ( _secondary_mail.split('@')[0], i, _secondary_mail.split('@')[1] ) secondary_mail_addresses.append(__secondary_mail) log.debug( - _( + _l( "Recipient policy composed the following set of secondary email addresses: %r" ) % ( secondary_mail_addresses ), level=8 ) if secondary_mail_attribute in entry: if isinstance(entry[secondary_mail_attribute], list): secondary_mail_addresses.extend(entry[secondary_mail_attribute]) else: secondary_mail_addresses.append(entry[secondary_mail_attribute]) if secondary_mail_addresses is not None: log.debug( - _("Secondary mail addresses that we want is not None: %r") % ( + _l("Secondary mail addresses that we want is not None: %r") % ( secondary_mail_addresses ), level=8 ) secondary_mail_addresses = list(set(secondary_mail_addresses)) # Avoid duplicates while primary_mail_address in secondary_mail_addresses: log.debug( - _( + _l( "Avoiding the duplication of the primary mail " + "address %r in the list of secondary mail " + "addresses" ) % (primary_mail_address), level=8 ) secondary_mail_addresses.pop( secondary_mail_addresses.index(primary_mail_address) ) log.debug( - _("Entry is getting secondary mail addresses: %r") % ( + _l("Entry is getting secondary mail addresses: %r") % ( secondary_mail_addresses ), level=8 ) if secondary_mail_attribute not in entry: log.debug( - _("Entry did not have any secondary mail addresses in %r") % ( + _l("Entry did not have any secondary mail addresses in %r") % ( secondary_mail_attribute ), level=8 ) - if not len(secondary_mail_addresses) == 0: + if secondary_mail_addresses: self.set_entry_attribute( entry, secondary_mail_attribute, secondary_mail_addresses ) entry_modifications[secondary_mail_attribute] = secondary_mail_addresses else: - if isinstance(entry[secondary_mail_attribute], basestring): + if isinstance(entry[secondary_mail_attribute], string_types): entry[secondary_mail_attribute] = [entry[secondary_mail_attribute]] log.debug( - _("secondary_mail_addresses: %r") % (secondary_mail_addresses), + _l("secondary_mail_addresses: %r") % (secondary_mail_addresses), level=8 ) log.debug( - _("entry[%s]: %r") % ( + _l("entry[%s]: %r") % ( secondary_mail_attribute, entry[secondary_mail_attribute] ), level=8 ) secondary_mail_addresses.sort() entry[secondary_mail_attribute].sort() log.debug( - _("secondary_mail_addresses: %r") % (secondary_mail_addresses), + _l("secondary_mail_addresses: %r") % (secondary_mail_addresses), level=8 ) log.debug( - _("entry[%s]: %r") % ( + _l("entry[%s]: %r") % ( secondary_mail_attribute, entry[secondary_mail_attribute] ), level=8 ) smas = list(set(secondary_mail_addresses)) - if not smas == list(set(entry[secondary_mail_attribute])): + if smas != list(set(entry[secondary_mail_attribute])): self.set_entry_attribute( entry, secondary_mail_attribute, smas ) entry_modifications[secondary_mail_attribute] = smas - log.debug(_("Entry modifications list: %r") % (entry_modifications), level=8) + log.debug(_l("Entry modifications list: %r") % (entry_modifications), level=8) return entry_modifications def reconnect(self): bind = self.bind self._disconnect() self.connect() if bind is not None: self._bind(bind['dn'], bind['pw']) def search_entry_by_attribute(self, attr, value, **kw): self._bind() _filter = "(%s=%s)" % (attr, ldap.filter.escape_filter_chars(value)) config_base_dn = self.config_get('base_dn') ldap_base_dn = self._kolab_domain_root_dn(self.domain) if ldap_base_dn is not None and not ldap_base_dn == config_base_dn: base_dn = ldap_base_dn else: base_dn = config_base_dn _results = self._search( base_dn, filterstr=_filter, attrlist=[ '*', ], override_search='_regular_search' ) # Remove referrals _entry_dns = [_e for _e in _results if _e[0] is not None] return _entry_dns def set_entry_attribute(self, entry_id, attribute, value): log.debug( - _("Setting entry attribute %r to %r for %r") % (attribute, value, entry_id), + _l("Setting entry attribute %r to %r for %r") % (attribute, value, entry_id), level=8 ) self.set_entry_attributes(entry_id, {attribute: value}) def set_entry_attributes(self, entry_id, attributes): self._bind() entry_dn = self.entry_dn(entry_id) entry = self.get_entry_attributes(entry_dn, ['*']) attrs = {} for attribute in attributes.keys(): attrs[attribute.lower()] = attributes[attribute] modlist = [] - for attribute in attrs.keys(): + for attribute, value in attrs.items(): if attribute not in entry: entry[attribute] = self.get_entry_attribute(entry_id, attribute) - for attribute in attrs.keys(): if attribute in entry and entry[attribute] is None: - modlist.append((ldap.MOD_ADD, attribute, attrs[attribute])) + modlist.append((ldap.MOD_ADD, attribute, value)) elif attribute in entry and entry[attribute] is not None: - if attrs[attribute] is None: + if value is None: modlist.append((ldap.MOD_DELETE, attribute, entry[attribute])) else: - modlist.append((ldap.MOD_REPLACE, attribute, attrs[attribute])) + modlist.append((ldap.MOD_REPLACE, attribute, value)) dn = entry_dn - if len(modlist) > 0 and self._bind_priv() is True: + if modlist and self._bind_priv() is True: try: self.ldap_priv.modify_s(dn, modlist) except Exception as errmsg: log.error( - _("Could not update dn:\nDN: %r\nModlist: %r\nError Message: %r") % ( + _l("Could not update dn:\nDN: %r\nModlist: %r\nError Message: %r") % ( dn, modlist, errmsg ) ) - import traceback log.error(traceback.format_exc()) def synchronize(self, mode=0, callback=None): """ Synchronize with LDAP """ self._bind() _filter = self._kolab_filter() modified_after = None if hasattr(conf, 'resync'): if not conf.resync: modified_after = self.get_latest_sync_timestamp() else: modifytimestamp_format = conf.get_raw( 'ldap', 'modifytimestamp_format', default="%Y%m%d%H%M%SZ" ).replace('%%', '%') modified_after = datetime.datetime(1900, 1, 1, 00, 00, 00).strftime( modifytimestamp_format ) else: modified_after = self.get_latest_sync_timestamp() _filter = "(&%s(modifytimestamp>=%s))" % (_filter, modified_after) - log.debug(_("Synchronization is using filter %r") % (_filter), level=8) + log.debug(_l("Synchronization is using filter %r") % (_filter), level=8) - if not mode == 0: + if mode != 0: override_search = mode else: override_search = False config_base_dn = self.config_get('base_dn') ldap_base_dn = self._kolab_domain_root_dn(self.domain) if ldap_base_dn is not None and not ldap_base_dn == config_base_dn: base_dn = ldap_base_dn else: base_dn = config_base_dn - log.debug(_("Synchronization is searching against base DN: %s") % (base_dn), level=8) + log.debug(_l("Synchronization is searching against base DN: %s") % (base_dn), level=8) if callback is None: callback = self._synchronize_callback try: self._search( base_dn, filterstr=_filter, attrlist=[ '*', self.config_get('unique_attribute'), conf.get('cyrus-sasl', 'result_attribute'), 'modifytimestamp' ], override_search=override_search, callback=callback, ) except Exception as errmsg: log.error("An error occurred: %r" % (errmsg)) - log.error(_("%s") % (traceback.format_exc())) + log.error(_l("%s") % (traceback.format_exc())) def user_quota(self, entry_id, folder): default_quota = self.config_get('default_quota') quota_attribute = self.config_get('quota_attribute') if quota_attribute is None: return # The default quota may be None, but LDAP quota could still be set if default_quota is None: default_quota = 0 self._bind() entry_dn = self.entry_dn(entry_id) current_ldap_quota = self.get_entry_attribute(entry_dn, quota_attribute) _imap_quota = self.imap.get_quota(folder) if _imap_quota is None: used = None current_imap_quota = None else: (used, current_imap_quota) = _imap_quota log.debug( - _( + _l( "About to consider the user quota for %r (used: %r, " + "imap: %r, ldap: %r, default: %r" ) % ( entry_dn, used, current_imap_quota, current_ldap_quota, default_quota ), level=8 ) new_quota = conf.plugins.exec_hook( "set_user_folder_quota", kw={ 'used': used, 'imap_quota': current_imap_quota, 'ldap_quota': current_ldap_quota, 'default_quota': default_quota } ) try: current_ldap_quota = (int)(current_ldap_quota) except Exception: current_ldap_quota = None # If the new quota is zero, get out if new_quota == 0: return if current_ldap_quota is not None: if not new_quota == (int)(current_ldap_quota): self.set_entry_attribute( entry_dn, quota_attribute, "%s" % (new_quota) ) else: if new_quota is not None: self.set_entry_attribute( entry_dn, quota_attribute, "%s" % (new_quota) ) if current_imap_quota is not None: if not new_quota == current_imap_quota: self.imap.set_quota(folder, new_quota) else: if new_quota is not None: self.imap.set_quota(folder, new_quota) ### # API depth level increasing! ### def _bind(self, bind_dn=None, bind_pw=None): # If we have no LDAP, we have no previous state. if self.ldap is None: self.bind = None self.connect() # If the bind_dn is None and the bind_pw is not... fail if bind_dn is None and bind_pw is not None: - log.error(_("Attempting to bind without a DN but with a password")) + log.error(_l("Attempting to bind without a DN but with a password")) return False # and the same vice-versa if bind_dn is None and bind_pw is not None: - log.error(_("Attempting to bind with a DN but without a password")) + log.error(_l("Attempting to bind with a DN but without a password")) return False # If we are to bind as foo, we have no state. if bind_dn is not None: self.bind = None # Only if we have no state and no bind credentials specified in the # function call. if self.bind is None: if bind_dn is None: bind_dn = self.config_get('service_bind_dn') if bind_pw is None: bind_pw = self.config_get('service_bind_pw') if bind_dn is not None: log.debug( - _("Binding with bind_dn: %s and password: %s") % ( + _l("Binding with bind_dn: %s and password: %s") % ( bind_dn, '*' * len(bind_pw) ), level=8 ) # TODO: Binding errors control try: # Must be synchronous self.ldap.simple_bind_s(bind_dn, bind_pw) self.bind = {'dn': bind_dn, 'pw': bind_pw} return True except ldap.SERVER_DOWN as errmsg: - log.error(_("LDAP server unavailable: %r") % (errmsg)) - log.error(_("%s") % (traceback.format_exc())) + log.error(_l("LDAP server unavailable: %r") % (errmsg)) + log.error(_l("%s") % (traceback.format_exc())) return False except ldap.NO_SUCH_OBJECT: log.error( - _("Invalid DN, username and/or password for '%s'.") % ( + _l("Invalid DN, username and/or password for '%s'.") % ( bind_dn ) ) return False except ldap.INVALID_CREDENTIALS: log.error( - _("Invalid DN, username and/or password for '%s'.") % ( + _l("Invalid DN, username and/or password for '%s'.") % ( bind_dn ) ) return False else: - log.debug(_("bind() called but already bound"), level=8) + log.debug(_l("bind() called but already bound"), level=8) return True def _bind_priv(self): if self.ldap_priv is None: self.connect(True) bind_dn = self.config_get('bind_dn') bind_pw = self.config_get('bind_pw') try: self.ldap_priv.simple_bind_s(bind_dn, bind_pw) return True except ldap.SERVER_DOWN as errmsg: - log.error(_("LDAP server unavailable: %r") % (errmsg)) - log.error(_("%s") % (traceback.format_exc())) + log.error(_l("LDAP server unavailable: %r") % (errmsg)) + log.error(_l("%s") % (traceback.format_exc())) return False except ldap.INVALID_CREDENTIALS: log.error( - _("Invalid DN, username and/or password for '%s'.") % ( + _l("Invalid DN, username and/or password for '%s'.") % ( bind_dn ) ) return False else: - log.debug(_("bind_priv() called but already bound"), level=8) + log.debug(_l("bind_priv() called but already bound"), level=8) return True def _change_add_group(self, entry, change): """ An entry of type group was added. The Kolab daemon has little to do for this type of action on this type of entry. """ pass def _change_add_None(self, entry, change): """ Redirect to _change_add_unknown """ self._change_add_unknown(entry, change) def _change_add_resource(self, entry, change): """ An entry of type resource was added. The Kolab daemon has little to do for this type of action on this type of entry. """ pass def _change_add_role(self, entry, change): """ An entry of type role was added. The Kolab daemon has little to do for this type of action on this type of entry. """ pass def _change_add_sharedfolder(self, entry, change): """ An entry of type sharedfolder was added. """ self.imap.connect(domain=self.domain) server = None # Get some configuration values mailserver_attribute = self.config_get('mailserver_attribute') if mailserver_attribute in entry: server = entry[mailserver_attribute] foldertype_attribute = self.config_get('sharedfolder_type_attribute') if foldertype_attribute is not None: if foldertype_attribute not in entry: entry[foldertype_attribute] = self.get_entry_attribute( entry['id'], foldertype_attribute ) if entry[foldertype_attribute] is not None: entry['kolabfoldertype'] = entry[foldertype_attribute] if 'kolabfoldertype' not in entry: entry['kolabfoldertype'] = self.get_entry_attribute( entry['id'], 'kolabfoldertype' ) # A delivery address is postuser+targetfolder delivery_address_attribute = self.config_get('sharedfolder_delivery_address_attribute') if delivery_address_attribute is None: delivery_address_attribute = 'mail' if delivery_address_attribute not in entry: entry[delivery_address_attribute] = self.get_entry_attribute( entry['id'], delivery_address_attribute ) if entry[delivery_address_attribute] is not None: if len(entry[delivery_address_attribute].split('+')) > 1: entry['kolabtargetfolder'] = entry[delivery_address_attribute].split('+')[1] if 'kolabtargetfolder' not in entry: entry['kolabtargetfolder'] = self.get_entry_attribute( entry['id'], 'kolabtargetfolder' ) if 'kolabtargetfolder' in entry and entry['kolabtargetfolder'] is not None: folder_path = entry['kolabtargetfolder'] else: # TODO: What is *the* way to see if we need to create an @domain # shared mailbox? # TODO^2: self.domain, really? Presumes any mail attribute is # set to the primary domain name space... # TODO^3: Test if the cn is already something@domain result_attribute = conf.get('cyrus-sasl', 'result_attribute') if result_attribute in ['mail']: folder_path = "%s@%s" % (entry['cn'], self.domain) else: folder_path = entry['cn'] if not folder_path.startswith('shared/'): folder_path = "shared/%s" % folder_path folderacl_entry_attribute = self.config_get('sharedfolder_acl_entry_attribute') if folderacl_entry_attribute is None: folderacl_entry_attribute = 'acl' if folderacl_entry_attribute not in entry: entry[folderacl_entry_attribute] = self.get_entry_attribute( entry['id'], folderacl_entry_attribute ) if not self.imap.shared_folder_exists(folder_path): self.imap.shared_folder_create(folder_path, server) if 'kolabfoldertype' in entry and entry['kolabfoldertype'] is not None: self.imap.shared_folder_set_type(folder_path, entry['kolabfoldertype']) entry['kolabfolderaclentry'] = self._parse_acl(entry[folderacl_entry_attribute]) + # pylint: disable=protected-access self.imap._set_kolab_mailfolder_acls(entry['kolabfolderaclentry'], folder_path) if delivery_address_attribute in entry: if entry[delivery_address_attribute] is not None: self.imap.set_acl(folder_path, 'anyone', '+p') # if server is None: # self.entry_set_attribute(mailserver_attribute, server) def _change_add_unknown(self, entry, change): """ An entry has been add, and we do not know of what object type the entry was - user, group, role or sharedfolder. """ + success = None + result_attribute = conf.get('cyrus-sasl', 'result_attribute') if result_attribute not in entry: return None if entry[result_attribute] is None: return None for _type in ['user', 'group', 'role', 'sharedfolder']: try: - eval("self._change_add_%s(entry, change)" % (_type)) + func = getattr(self, '_change_add_%s' % (_type)) + func(entry, change) success = True except Exception: success = False if success: break + return success + def _change_add_user(self, entry, change): """ An entry of type user was added. """ mailserver_attribute = self.config_get('mailserver_attribute') if mailserver_attribute is None: mailserver_attribute = 'mailhost' mailserver_attribute = mailserver_attribute.lower() result_attribute = conf.get('cyrus-sasl', 'result_attribute') if result_attribute is None: result_attribute = 'mail' result_attribute = result_attribute.lower() if mailserver_attribute not in entry: entry[mailserver_attribute] = \ self.get_entry_attribute(entry, mailserver_attribute) rcpt_addrs = self.recipient_policy(entry) for key in rcpt_addrs: entry[key] = rcpt_addrs[key] if result_attribute not in entry: return if entry[result_attribute] is None: return if entry[result_attribute] == '': return cache.get_entry(self.domain, entry) self.imap.connect(domain=self.domain) if not self.imap.user_mailbox_exists(entry[result_attribute].lower()): folder = self.imap.user_mailbox_create( entry[result_attribute], entry[mailserver_attribute] ) else: folder = "user%s%s" % (self.imap.get_separator(), entry[result_attribute].lower()) server = self.imap.user_mailbox_server(folder) log.debug( - _("Entry %s attribute value: %r") % ( + _l("Entry %s attribute value: %r") % ( mailserver_attribute, entry[mailserver_attribute] ), level=8 ) log.debug( - _("imap.user_mailbox_server(%r) result: %r") % ( + _l("imap.user_mailbox_server(%r) result: %r") % ( folder, server ), level=8 ) if not entry[mailserver_attribute] == server: self.set_entry_attribute(entry, mailserver_attribute, server) self.user_quota(entry, folder) def _change_delete_group(self, entry, change): """ An entry of type group was deleted. """ result_attribute = conf.get('cyrus-sasl', 'result_attribute') if result_attribute not in entry: return None if entry[result_attribute] is None: return None - self.imap.cleanup_acls(entry[result_attribute]) + return self.imap.cleanup_acls(entry[result_attribute]) def _change_delete_None(self, entry, change): """ Redirect to _change_delete_unknown """ self._change_delete_unknown(entry, change) def _change_delete_resource(self, entry, change): pass def _change_delete_role(self, entry, change): pass def _change_delete_sharedfolder(self, entry, change): pass def _change_delete_unknown(self, entry, change): """ An entry has been deleted, and we do not know of what object type the entry was - user, group, resource, role or sharedfolder. """ result_attribute = conf.get('cyrus-sasl', 'result_attribute') if result_attribute not in entry: return None if entry[result_attribute] is None: return None success = True for _type in ['user', 'group', 'resource', 'role', 'sharedfolder']: try: - success = eval("self._change_delete_%s(entry, change)" % (_type)) + func = getattr(self, '_change_delete_%s' % (_type)) + success = func(entry, change) except Exception as errmsg: - log.error(_("An error occured: %r") % (errmsg)) - log.error(_("%s") % (traceback.format_exc())) + log.error(_l("An error occured: %r") % (errmsg)) + log.error(_l("%s") % (traceback.format_exc())) success = False if success: break + return success + def _change_delete_user(self, entry, change): """ An entry of type user was deleted. """ result_attribute = conf.get('cyrus-sasl', 'result_attribute') if result_attribute not in entry: return None if entry[result_attribute] is None: return None cache.delete_entry(self.domain, entry) self.imap.user_mailbox_delete(entry[result_attribute]) self.imap.cleanup_acls(entry[result_attribute]) # let plugins act upon this deletion conf.plugins.exec_hook( 'user_delete', kw={ 'user': entry, 'domain': self.domain } ) + return True + def _change_moddn_group(self, entry, change): # TODO: If the rdn attribute is the same as the result attribute... pass def _change_moddn_role(self, entry, change): pass def _change_moddn_user(self, entry, change): old_dn = change['previous_dn'] new_dn = change['dn'] - import ldap.dn - old_rdn = ldap.dn.explode_dn(old_dn)[0].split('=')[0] - new_rdn = ldap.dn.explode_dn(new_dn)[0].split('=')[0] + old_rdn = explode_dn(old_dn)[0].split('=')[0] + new_rdn = explode_dn(new_dn)[0].split('=')[0] result_attribute = conf.get('cyrus-sasl', 'result_attribute') old_canon_attr = None cache_entry = cache.get_entry(self.domain, entry) if cache_entry is not None: old_canon_attr = cache_entry.result_attribute # See if we have to trigger the recipient policy. Only really applies to # situations in which the result_attribute is used in the old or in the # new DN. trigger_recipient_policy = False if old_rdn == result_attribute: if new_rdn == result_attribute: if new_rdn == old_rdn: trigger_recipient_policy = True else: if not new_rdn == old_rdn: trigger_recipient_policy = True else: if new_rdn == result_attribute: if not new_rdn == old_rdn: trigger_recipient_policy = True if trigger_recipient_policy: entry_changes = self.recipient_policy(entry) - for key in entry_changes.keys(): - entry[key] = entry_changes[key] + for key, value in entry_changes.items(): + entry[key] = value if result_attribute not in entry: return if entry[result_attribute] is None: return if entry[result_attribute] == '': return # Now look at entry_changes and old_canon_attr, and see if they're # the same value. if result_attribute in entry_changes: if old_canon_attr is not None: self.imap.user_mailbox_create(entry_changes[result_attribute]) elif not entry_changes[result_attribute] == old_canon_attr: self.imap.user_mailbox_rename(old_canon_attr, entry_changes[result_attribute]) cache.get_entry(self.domain, entry) def _change_moddn_sharedfolder(self, entry, change): pass def _change_modify_None(self, entry, change): pass def _change_modify_group(self, entry, change): pass def _change_modify_role(self, entry, change): pass def _change_modify_sharedfolder(self, entry, change): """ A shared folder was modified. """ self.imap.connect(domain=self.domain) server = None # Get some configuration values mailserver_attribute = self.config_get('mailserver_attribute') if mailserver_attribute in entry: server = entry[mailserver_attribute] foldertype_attribute = self.config_get('sharedfolder_type_attribute') if foldertype_attribute is not None: if foldertype_attribute not in entry: entry[foldertype_attribute] = self.get_entry_attribute( entry['id'], foldertype_attribute ) if entry[foldertype_attribute] is not None: entry['kolabfoldertype'] = entry[foldertype_attribute] if 'kolabfoldertype' not in entry: entry['kolabfoldertype'] = self.get_entry_attribute( entry['id'], 'kolabfoldertype' ) # A delivery address is postuser+targetfolder delivery_address_attribute = self.config_get('sharedfolder_delivery_address_attribute') if delivery_address_attribute is not None: if delivery_address_attribute not in entry: entry[delivery_address_attribute] = self.get_entry_attribute( entry['id'], delivery_address_attribute ) if entry[delivery_address_attribute] is not None: if len(entry[delivery_address_attribute].split('+')) > 1: entry['kolabtargetfolder'] = entry[delivery_address_attribute].split('+')[1] if 'kolabtargetfolder' not in entry: entry['kolabtargetfolder'] = self.get_entry_attribute( entry['id'], 'kolabtargetfolder' ) if 'kolabtargetfolder' in entry and entry['kolabtargetfolder'] is not None: - folder_path = entry['kolabtargetfolder'] + folder_path = entry['kolabtargetfolder'] else: # TODO: What is *the* way to see if we need to create an @domain # shared mailbox? # TODO^2: self.domain, really? Presumes any mail attribute is # set to the primary domain name space... # TODO^3: Test if the cn is already something@domain result_attribute = conf.get('cyrus-sasl', 'result_attribute') if result_attribute in ['mail']: folder_path = "%s@%s" % (entry['cn'], self.domain) else: folder_path = entry['cn'] if not folder_path.startswith('shared/'): folder_path = "shared/%s" % folder_path folderacl_entry_attribute = self.config_get('sharedfolder_acl_entry_attribute') if folderacl_entry_attribute is None: folderacl_entry_attribute = 'acl' if folderacl_entry_attribute not in entry: entry[folderacl_entry_attribute] = self.get_entry_attribute( entry['id'], folderacl_entry_attribute ) if not self.imap.shared_folder_exists(folder_path): self.imap.shared_folder_create(folder_path, server) if 'kolabfoldertype' in entry and entry['kolabfoldertype'] is not None: self.imap.shared_folder_set_type( folder_path, entry['kolabfoldertype'] ) entry['kolabfolderaclentry'] = self._parse_acl(entry[folderacl_entry_attribute]) - self.imap._set_kolab_mailfolder_acls( - entry['kolabfolderaclentry'], folder_path, True - ) + # pylint: disable=protected-access + self.imap._set_kolab_mailfolder_acls(entry['kolabfolderaclentry'], folder_path, True) if delivery_address_attribute in entry and entry[delivery_address_attribute] is not None: self.imap.set_acl(folder_path, 'anyone', '+p') def _change_modify_user(self, entry, change): """ Handle the changes for an object of type user. Expects the new entry. """ # Initialize old_canon_attr (#1701) old_canon_attr = None result_attribute = conf.get('cyrus-sasl', 'result_attribute') _entry = cache.get_entry(self.domain, entry, update=False) # We do not necessarily have a synchronisation cache entry (#1701) if _entry is not None: if 'result_attribute' in _entry.__dict__ and not _entry.result_attribute == '': old_canon_attr = _entry.result_attribute entry_changes = self.recipient_policy(entry) log.debug( - _("Result from recipient policy: %r") % (entry_changes), + _l("Result from recipient policy: %r") % (entry_changes), level=8 ) if result_attribute in entry_changes: if not entry_changes[result_attribute] == old_canon_attr: if old_canon_attr is None: self.imap.user_mailbox_create( entry_changes[result_attribute] ) else: self.imap.user_mailbox_rename( old_canon_attr, entry_changes[result_attribute] ) entry[result_attribute] = entry_changes[result_attribute] cache.get_entry(self.domain, entry) elif result_attribute in entry: if not entry[result_attribute] == old_canon_attr: if old_canon_attr is None: self.imap.user_mailbox_create( entry[result_attribute] ) else: self.imap.user_mailbox_rename( old_canon_attr, entry[result_attribute] ) cache.get_entry(self.domain, entry) else: if not self.imap.user_mailbox_exists(entry[result_attribute]): self.imap.user_mailbox_create( entry[result_attribute] ) self.user_quota( entry, "user%s%s" % ( self.imap.get_separator(), entry[result_attribute] ) ) if conf.has_option(self.domain, 'sieve_mgmt'): sieve_mgmt_enabled = conf.get(self.domain, 'sieve_mgmt') if utils.true_or_false(sieve_mgmt_enabled): conf.plugins.exec_hook( 'sieve_mgmt_refresh', kw={ 'user': entry[result_attribute] } ) def _change_none_group(self, entry, change): """ A group entry as part of the initial search result set. The Kolab daemon has little to do for this type of action on this type of entry. """ pass def _change_none_None(self, entry, change): pass def _change_none_role(self, entry, change): """ A role entry as part of the initial search result set. The Kolab daemon has little to do for this type of action on this type of entry. """ pass def _change_none_sharedfolder(self, entry, change): """ A sharedfolder entry as part of the initial search result set. """ self.imap.connect(domain=self.domain) server = None mailserver_attribute = self.config_get('mailserver_attribute') if mailserver_attribute in entry: server = entry[mailserver_attribute] if 'kolabtargetfolder' not in entry: entry['kolabtargetfolder'] = self.get_entry_attribute( entry['id'], 'kolabtargetfolder' ) if 'kolabfoldertype' not in entry: entry['kolabfoldertype'] = self.get_entry_attribute( entry['id'], 'kolabfoldertype' ) folderacl_entry_attribute = conf.get('ldap', 'sharedfolder_acl_entry_attribute') if folderacl_entry_attribute is None: folderacl_entry_attribute = 'acl' if folderacl_entry_attribute not in entry: entry['kolabfolderaclentry'] = self.get_entry_attribute( entry['id'], folderacl_entry_attribute ) else: entry['kolabfolderaclentry'] = entry[folderacl_entry_attribute] del entry[folderacl_entry_attribute] if 'kolabtargetfolder' in entry and entry['kolabtargetfolder'] is not None: folder_path = entry['kolabtargetfolder'] else: # TODO: What is *the* way to see if we need to create an @domain # shared mailbox? # TODO^2: self.domain, really? Presumes any mail attribute is # set to the primary domain name space... # TODO^3: Test if the cn is already something@domain result_attribute = conf.get('cyrus-sasl', 'result_attribute') if result_attribute in ['mail']: folder_path = "%s@%s" % (entry['cn'], self.domain) else: folder_path = entry['cn'] if not folder_path.startswith('shared/'): folder_path = "shared/%s" % folder_path if not self.imap.shared_folder_exists(folder_path): self.imap.shared_folder_create(folder_path, server) if 'kolabfoldertype' in entry and entry['kolabfoldertype'] is not None: self.imap.shared_folder_set_type( folder_path, entry['kolabfoldertype'] ) entry['kolabfolderaclentry'] = self._parse_acl(entry['kolabfolderaclentry']) self.imap._set_kolab_mailfolder_acls( entry['kolabfolderaclentry'], folder_path, True ) delivery_address_attribute = self.config_get('sharedfolder_delivery_address_attribute') if delivery_address_attribute in entry and \ entry[delivery_address_attribute] is not None: self.imap.set_acl(folder_path, 'anyone', '+p') # if server is None: # self.entry_set_attribute(mailserver_attribute, server) def _change_none_user(self, entry, change): """ A user entry as part of the initial search result set. """ mailserver_attribute = self.config_get('mailserver_attribute') if mailserver_attribute is None: mailserver_attribute = 'mailhost' mailserver_attribute = mailserver_attribute.lower() result_attribute = conf.get('cyrus-sasl', 'result_attribute') if result_attribute is None: result_attribute = 'mail' result_attribute = result_attribute.lower() old_canon_attr = None _entry = cache.get_entry(self.domain, entry, update=False) if _entry is not None and \ 'result_attribute' in _entry.__dict__ and \ not _entry.result_attribute == '': old_canon_attr = _entry.result_attribute entry_changes = self.recipient_policy(entry) if result_attribute in entry and result_attribute in entry_changes: if not entry[result_attribute] == entry_changes[result_attribute]: old_canon_attr = entry[result_attribute] log.debug( - _("Result from recipient policy: %r") % (entry_changes), + _l("Result from recipient policy: %r") % (entry_changes), level=8 ) if result_attribute in entry_changes and old_canon_attr is not None: if not entry_changes[result_attribute] == old_canon_attr: self.imap.user_mailbox_rename( old_canon_attr, entry_changes[result_attribute] ) for key in entry_changes.keys(): entry[key] = entry_changes[key] self.set_entry_attribute(entry, key, entry[key]) cache.get_entry(self.domain, entry) self.imap.connect(domain=self.domain) server = None if mailserver_attribute not in entry: entry[mailserver_attribute] = self.get_entry_attribute(entry, mailserver_attribute) if entry[mailserver_attribute] == "" or entry[mailserver_attribute] is None: server = None else: server = entry[mailserver_attribute].lower() if result_attribute in entry and entry[result_attribute] is not None: if not self.imap.user_mailbox_exists(entry[result_attribute]): folder = self.imap.user_mailbox_create(entry[result_attribute], server=server) server = self.imap.user_mailbox_server(folder) else: folder = "user%s%s" % ( self.imap.get_separator(), entry[result_attribute] ) server = self.imap.user_mailbox_server(folder) self.user_quota(entry, folder) mailserver_attr = self.config_get('mailserver_attribute') if mailserver_attr not in entry: self.set_entry_attribute(entry, mailserver_attr, server) else: if not entry[mailserver_attr] == server: # TODO: Should actually transfer mailbox self.set_entry_attribute(entry, mailserver_attr, server) else: log.warning( - _("Kolab user %s does not have a result attribute %r") % ( + _l("Kolab user %s does not have a result attribute %r") % ( entry['id'], result_attribute ) ) def _disconnect(self): del self.ldap del self.ldap_priv self.ldap = None self.ldap_priv = None self.bind = None def _domain_naming_context(self, domain): self._bind() # The list of naming contexts in the LDAP server attrs = self.get_entry_attributes("", ['namingContexts']) # Lower case of naming contexts - primarily for AD naming_contexts = utils.normalize(attrs['namingcontexts']) - if isinstance(naming_contexts, basestring): + if isinstance(naming_contexts, string_types): naming_contexts = [naming_contexts] log.debug( - _("Naming contexts found: %r") % (naming_contexts), + _l("Naming contexts found: %r") % (naming_contexts), level=8 ) self._kolab_domain_root_dn(domain) log.debug( - _("Domains/Root DNs found: %r") % ( + _l("Domains/Root DNs found: %r") % ( self.domain_rootdns ), level=8 ) # If we have a 1:1 match, continue as planned for naming_context in naming_contexts: if self.domain_rootdns[domain].endswith(naming_context): return naming_context def _primary_domain_for_naming_context(self, naming_context): self._bind() _domain = '.'.join(naming_context.split(',dc='))[3:] _naming_context = self._kolab_domain_root_dn(_domain) if naming_context == _naming_context: return _domain def _entry_dict(self, value): """ Tests if 'value' is a valid entry dictionary with a DN contained within key 'dn'. Returns True or False """ if isinstance(value, dict): if 'dn' in value: return True return False def _entry_dn(self, value): """ Tests if 'value' is a valid DN. Returns True or False """ # Only basestrings can be DNs - if not isinstance(value, basestring): + if not isinstance(value, string_types): return False try: - import ldap.dn - ldap.dn.explode_dn(value) + explode_dn(value) except ldap.DECODING_ERROR: # This is not a DN. return False return True def _entry_type(self, entry_id): """ Return the type of object for an entry. """ self._bind() entry_dn = self.entry_dn(entry_id) config_base_dn = self.config_get('base_dn') ldap_base_dn = self._kolab_domain_root_dn(self.domain) if ldap_base_dn is not None and not ldap_base_dn == config_base_dn: base_dn = ldap_base_dn else: base_dn = config_base_dn for _type in ['user', 'group', 'sharedfolder']: __filter = self.config_get('kolab_%s_filter' % (_type)) if __filter is None: __filter = self.config_get('%s_filter' % (_type)) if __filter is not None: try: result = self._regular_search(entry_dn, filterstr=__filter) except Exception: result = self._regular_search( base_dn, filterstr="(%s=%s)" % ( self.config_get('unique_attribute'), entry_id['id'] ) ) if not result: continue else: return _type + return None + def _find_user_dn(self, login, kolabuser=False): """ Find the distinguished name (DN) for a (Kolab) user entry in LDAP. """ conf_prefix = 'kolab_' if kolabuser else '' user_base_dn = self.config_get(conf_prefix + 'user_base_dn') if user_base_dn is None: user_base_dn = self.config_get('base_dn') auth_attrs = self.config_get_list('auth_attributes') auth_search_filter = ['(|'] for auth_attr in auth_attrs: auth_search_filter.append('(%s=%s)' % (auth_attr, login)) if '@' not in login: auth_search_filter.append( '(%s=%s@%s)' % ( auth_attr, login, self.domain ) ) auth_search_filter.append(')') auth_search_filter = ''.join(auth_search_filter) user_filter = self.config_get(conf_prefix + 'user_filter') search_filter = "(&%s%s)" % ( auth_search_filter, user_filter ) _results = self._search( user_base_dn, filterstr=search_filter, attrlist=['dn'], override_search='_regular_search' ) if len(_results) == 1: (_user_dn, _user_attrs) = _results[0] else: # Retry to find the user_dn with just uid=%s against the root_dn, # if the login is not fully qualified if len(login.split('@')) < 2: search_filter = "(uid=%s)" % (login) _results = self._search( domain, filterstr=search_filter, attrlist=['dn'] ) if len(_results) == 1: (_user_dn, _user_attrs) = _results[0] else: # Overall fail return False else: return False return _user_dn def _kolab_domain_root_dn(self, domain): - log.debug(_("Searching root dn for domain %r") % (domain), level=8) + log.debug(_l("Searching root dn for domain %r") % (domain), level=8) if not hasattr(self, 'domain_rootdns'): self.domain_rootdns = {} if domain in self.domain_rootdns: - log.debug(_("Returning from cache: %r") % (self.domain_rootdns[domain]), level=8) + log.debug(_l("Returning from cache: %r") % (self.domain_rootdns[domain]), level=8) return self.domain_rootdns[domain] self._bind() - log.debug(_("Finding domain root dn for domain %s") % (domain), level=8) + log.debug(_l("Finding domain root dn for domain %s") % (domain), level=8) domain_base_dn = conf.get('ldap', 'domain_base_dn', quiet=True) domain_filter = conf.get('ldap', 'domain_filter') if domain_filter is not None: if domain is not None: domain_filter = domain_filter.replace('*', domain) if not domain_base_dn == "": _results = self._search( domain_base_dn, ldap.SCOPE_SUBTREE, domain_filter, override_search='_regular_search' ) for _domain in _results: (domain_dn, _domain_attrs) = _domain domain_rootdn_attribute = conf.get( 'ldap', 'domain_rootdn_attribute' ) _domain_attrs = utils.normalize(_domain_attrs) if domain_rootdn_attribute in _domain_attrs: log.debug( - _("Setting domain root dn from LDAP for domain %r: %r") % ( + _l("Setting domain root dn from LDAP for domain %r: %r") % ( domain, _domain_attrs[domain_rootdn_attribute] ), level=8 ) self.domain_rootdns[domain] = _domain_attrs[domain_rootdn_attribute] return _domain_attrs[domain_rootdn_attribute] else: domain_name_attribute = self.config_get('domain_name_attribute') if domain_name_attribute is None: domain_name_attribute = 'associateddomain' if isinstance(_domain_attrs[domain_name_attribute], list): domain = _domain_attrs[domain_name_attribute][0] else: domain = _domain_attrs[domain_name_attribute] else: if conf.has_option('ldap', 'base_dn'): return conf.get('ldap', 'base_dn') self.domain_rootdns[domain] = utils.standard_root_dn(domain) return self.domain_rootdns[domain] def _kolab_filter(self): """ Compose a filter using the relevant settings from configuration. """ _filter = "(|" for _type in ['user', 'group', 'resource', 'sharedfolder']: __filter = self.config_get('kolab_%s_filter' % (_type)) if __filter is None: __filter = self.config_get('%s_filter' % (_type)) if __filter is not None: _filter = "%s%s" % (_filter, __filter) _filter = "%s)" % (_filter) return _filter def _list_domains(self, domain=None): """ Find the domains related to this Kolab setup, and return a list of DNS domain names. Returns a list of tuples, each tuple containing the primary domain name and a list of secondary domain names. This function should only be called by the primary instance of Auth. """ - log.debug(_("Listing domains..."), level=8) + log.debug(_l("Listing domains..."), level=8) self.connect() self._bind() domain_base_dn = conf.get('ldap', 'domain_base_dn', quiet=True) if domain_base_dn == "": # No domains are to be found in LDAP, return an empty list. # Note that the Auth() base itself handles this case. return [] # If we haven't returned already, let's continue searching domain_filter = conf.get('ldap', 'domain_filter') if domain is not None: domain_filter = domain_filter.replace('*', domain) if domain_base_dn is None or domain_filter is None: return [] dna = self.config_get('domain_name_attribute') if dna is None: dna = 'associateddomain' try: _search = self._search( domain_base_dn, ldap.SCOPE_SUBTREE, domain_filter, # TODO: Where we use associateddomain is actually # configurable [dna], override_search='_regular_search' ) except Exception: return [] domains = [] for domain_dn, domain_attrs in _search: primary_domain = None secondary_domains = [] domain_attrs = utils.normalize(domain_attrs) # TODO: Where we use associateddomain is actually configurable if type(domain_attrs[dna]) == list: primary_domain = domain_attrs[dna].pop(0).lower() secondary_domains = [x.lower() for x in domain_attrs[dna]] else: primary_domain = domain_attrs[dna].lower() domains.append((primary_domain, secondary_domains)) return domains def _synchronize_callback(self, *args, **kw): """ Determine the characteristics of the callback being placed, and what data is contained within *args and **kw exactly. The exact form and shape of the feedback very much depends on the supportedControl used to even get the data. """ log.debug( "auth.ldap.LDAP._synchronize_callback(args %r, kw %r)" % ( args, kw ), level=8 ) # Typical for Persistent Change Control EntryChangeNotification if 'change_type' in kw: log.debug( - _( + _l( "change_type defined, typical for Persistent Change " + "Control EntryChangeNotification" ), level=5 ) change_dict = { 'change_type': kw['change_type'], 'previous_dn': kw['previous_dn'], 'change_number': kw['change_number'], 'dn': kw['dn'] } entry = utils.normalize(kw['entry']) entry['dn'] = kw['dn'] unique_attr = self.config_get('unique_attribute') entry['id'] = entry[unique_attr] try: entry['type'] = self._entry_type(entry) except Exception: entry['type'] = None - log.debug(_("Entry type: %s") % (entry['type']), level=8) + log.debug(_l("Entry type: %s") % (entry['type']), level=8) if change_dict['change_type'] is None: # This entry was in the start result set eval("self._change_none_%s(entry, change_dict)" % (entry['type'])) else: if isinstance(change_dict['change_type'], int): change = psearch.CHANGE_TYPES_STR[change_dict['change_type']] change = change.lower() else: change = change_dict['change_type'] # See if we can find the cache entry - this way we can get to # the value of a (former, on a deleted entry) result_attribute result_attribute = conf.get('cyrus-sasl', 'result_attribute') if result_attribute not in entry: cache_entry = cache.get_entry(self.domain, entry, update=False) if hasattr(cache_entry, 'result_attribute') and change == 'delete': entry[result_attribute] = cache_entry.result_attribute eval( "self._change_%s_%s(entry, change_dict)" % ( change, entry['type'] ) ) # Typical for Paged Results Control elif 'entry' in kw and isinstance(kw['entry'], list): - log.debug(_("No change_type, typical for Paged Results Control"), level=5) + log.debug(_l("No change_type, typical for Paged Results Control"), level=5) for entry_dn, entry_attrs in kw['entry']: # This is a referral if entry_dn is None: continue entry = {'dn': entry_dn} entry_attrs = utils.normalize(entry_attrs) for attr in entry_attrs.keys(): entry[attr.lower()] = entry_attrs[attr] unique_attr = self.config_get('unique_attribute').lower() entry['id'] = entry[unique_attr] try: entry['type'] = self._entry_type(entry) except Exception: entry['type'] = "unknown" - log.debug(_("Entry type for dn: %s is: %s") % (entry['dn'], entry['type']), level=8) + log.debug(_l("Entry type for dn: %s is: %s") % (entry['dn'], entry['type']), level=8) eval("self._change_none_%s(entry, None)" % (entry['type'])) # result_attribute = conf.get('cyrus-sasl', 'result_attribute') # # rcpt_addrs = self.recipient_policy(entry) # -# log.debug(_("Recipient Addresses: %r") % (rcpt_addrs), level=8) +# log.debug(_l("Recipient Addresses: %r") % (rcpt_addrs), level=8) # # for key in rcpt_addrs.keys(): # entry[key] = rcpt_addrs[key] # # cache.get_entry(self.domain, entry) # # self.imap.connect(domain=self.domain) # # if not self.imap.user_mailbox_exists(entry[result_attribute]): # folder = self.imap.user_mailbox_create( # entry[result_attribute] # ) # # server = self.imap.user_mailbox_server(folder) ### # Backend search functions ### def _persistent_search( self, base_dn, scope=ldap.SCOPE_SUBTREE, filterstr="(objectClass=*)", attrlist=None, attrsonly=0, timeout=-1, callback=False, primary_domain=None, secondary_domains=[] ): psearch_server_controls = [] psearch_server_controls.append( - psearch.PersistentSearchControl( + ldap.controls.psearch.PersistentSearchControl( criticality=True, changeTypes=['add', 'delete', 'modify', 'modDN'], changesOnly=False, returnECs=True ) ) _search = self.ldap.search_ext( base_dn, scope=scope, filterstr=filterstr, attrlist=attrlist, attrsonly=attrsonly, timeout=timeout, serverctrls=psearch_server_controls ) ecnc = psearch.EntryChangeNotificationControl while True: res_type, res_data, res_msgid, _None, _None, _None = self.ldap.result4( _search, all=0, add_ctrls=1, add_intermediates=1, resp_ctrl_classes={ecnc.controlType: ecnc} ) change_type = None previous_dn = None change_number = None for dn, entry, srv_ctrls in res_data: - log.debug(_("LDAP Search Result Data Entry:"), level=8) + log.debug(_l("LDAP Search Result Data Entry:"), level=8) log.debug(" DN: %r" % (dn), level=8) log.debug(" Entry: %r" % (entry), level=8) ecn_ctrls = [ c for c in srv_ctrls if c.controlType == ecnc.controlType ] if ecn_ctrls: change_type = ecn_ctrls[0].changeType previous_dn = ecn_ctrls[0].previousDN change_number = ecn_ctrls[0].changeNumber change_type_desc = psearch.CHANGE_TYPES_STR[change_type] log.debug( - _("Entry Change Notification attributes:"), + _l("Entry Change Notification attributes:"), level=8 ) log.debug( - " " + _("Change Type: %r (%r)") % ( + " " + _l("Change Type: %r (%r)") % ( change_type, change_type_desc ), level=8 ) log.debug( - " " + _("Previous DN: %r") % (previous_dn), + " " + _l("Previous DN: %r") % (previous_dn), level=8 ) if callback: callback( dn=dn, entry=entry, previous_dn=previous_dn, change_type=change_type, change_number=change_number, primary_domain=primary_domain, secondary_domains=secondary_domains ) def _paged_search( self, base_dn, scope=ldap.SCOPE_SUBTREE, filterstr="(objectClass=*)", attrlist=None, attrsonly=0, timeout=-1, callback=False, primary_domain=None, secondary_domains=[] ): page_size = 500 _results = [] - server_page_control = SimplePagedResultsControl(page_size=page_size) + server_page_control = ldap.controls.libldap.SimplePagedResultsControl(size=page_size) _search = self.ldap.search_ext( base_dn, scope=scope, filterstr=filterstr, attrlist=attrlist, attrsonly=attrsonly, serverctrls=[server_page_control] ) pages = 0 while True: pages += 1 try: ( _result_type, _result_data, _result_msgid, _result_controls ) = self.ldap.result3(_search) except ldap.NO_SUCH_OBJECT: log.warning( - _("Object %s searched no longer exists") % (base_dn) + _l("Object %s searched no longer exists") % (base_dn) ) break # Remove referrals _result_data = [_e for _e in _result_data if _e[0] is not None] if callback: callback(entry=_result_data) _results.extend(_result_data) if (pages % 2) == 0: - log.debug(_("%d results...") % (len(_results))) + log.debug(_l("%d results...") % (len(_results))) pctrls = [ c for c in _result_controls if c.controlType == LDAP_CONTROL_PAGED_RESULTS ] if pctrls: if hasattr(pctrls[0], 'size'): size = pctrls[0].size cookie = pctrls[0].cookie else: size, cookie = pctrls[0].controlValue if cookie: server_page_control.cookie = cookie _search = self.ldap.search_ext( base_dn, scope=scope, filterstr=filterstr, attrlist=attrlist, attrsonly=attrsonly, serverctrls=[server_page_control] ) else: # TODO: Error out more verbose break else: # TODO: Error out more verbose print("Warning: Server ignores RFC 2696 control.") break return _results def _vlv_search( self, base_dn, scope=ldap.SCOPE_SUBTREE, filterstr="(objectClass=*)", attrlist=None, attrsonly=0, timeout=-1, callback=False, primary_domain=None, secondary_domains=[] ): pass def _sync_repl( self, base_dn, scope=ldap.SCOPE_SUBTREE, filterstr="(objectClass=*)", attrlist=None, attrsonly=0, timeout=-1, callback=False, primary_domain=None, secondary_domains=[] ): import ldapurl import syncrepl ldap_url = ldapurl.LDAPUrl(self.config_get('ldap_uri')) ldap_sync_conn = syncrepl.DNSync( '/var/lib/kolab/syncrepl_%s.db' % (self.domain), ldap_url.initializeUrl(), trace_level=2, trace_file=pykolab.logger.StderrToLogger(log), callback=self._synchronize_callback ) bind_dn = self.config_get('bind_dn') bind_pw = self.config_get('bind_pw') ldap_sync_conn.simple_bind_s(bind_dn, bind_pw) msgid = ldap_sync_conn.syncrepl_search( base_dn, scope, mode='refreshAndPersist', filterstr=filterstr, attrlist=attrlist, ) try: # Here's where returns need to be taken into account... while ldap_sync_conn.syncrepl_poll(all=1, msgid=msgid): pass except KeyboardInterrupt: pass def _regular_search( self, base_dn, scope=ldap.SCOPE_SUBTREE, filterstr="(objectClass=*)", attrlist=None, attrsonly=0, timeout=None, callback=False, primary_domain=None, secondary_domains=[] ): if timeout is None: - timeout = self.config_get('timeout', 10) + timeout = self.config_get('ldap', 'timeout', 10) - log.debug(_("Searching with filter %r") % (filterstr), level=8) + log.debug(_l("Searching with filter %r") % (filterstr), level=8) _search = self.ldap.search( base_dn, scope=scope, filterstr=filterstr, attrlist=attrlist, attrsonly=attrsonly ) _results = [] _result_type = None while not _result_type == ldap.RES_SEARCH_RESULT: (_result_type, _result) = self.ldap.result(_search, False, 0) if _result is not None: for result in _result: _results.append(result) return _results def _search( self, base_dn, scope=ldap.SCOPE_SUBTREE, filterstr="(objectClass=*)", attrlist=None, attrsonly=0, timeout=None, override_search=False, callback=False, primary_domain=None, secondary_domains=[] ): """ Search LDAP. Use the priority ordered SUPPORTED_LDAP_CONTROLS and use the first one supported. """ if timeout is None: timeout = self.config_get('timeout', default=10) supported_controls = conf.get_list('ldap', 'supported_controls') if supported_controls is not None and not len(supported_controls) < 1: for control_num in [(int)(x) for x in supported_controls]: self.ldap.supported_controls.append( SUPPORTED_LDAP_CONTROLS[control_num]['func'] ) if len(self.ldap.supported_controls) < 1: for control_num in SUPPORTED_LDAP_CONTROLS.keys(): log.debug( - _("Checking for support for %s on %s") % ( + _l("Checking for support for %s on %s") % ( SUPPORTED_LDAP_CONTROLS[control_num]['desc'], self.domain ), level=8 ) _search = self.ldap.search_s( '', scope=ldap.SCOPE_BASE, attrlist=['supportedControl'] ) for (_result, _supported_controls) in _search: supported_controls = _supported_controls.values()[0] for control_num in SUPPORTED_LDAP_CONTROLS.keys(): if SUPPORTED_LDAP_CONTROLS[control_num]['oid'] in \ supported_controls: log.debug( - _("Found support for %s") % ( + _l("Found support for %s") % ( SUPPORTED_LDAP_CONTROLS[control_num]['desc'], ), level=8 ) self.ldap.supported_controls.append( SUPPORTED_LDAP_CONTROLS[control_num]['func'] ) _results = [] if override_search is not False: _use_ldap_controls = [override_search] else: _use_ldap_controls = self.ldap.supported_controls for supported_control in _use_ldap_controls: # Repeat the same supported control until # a failure (Exception) occurs that been # recognized as not an error related to the # supported control (such as ldap.SERVER_DOWN). failed_ok = False while not failed_ok: try: exec( """_results = self.%s( %r, scope=%r, filterstr=%r, attrlist=%r, attrsonly=%r, timeout=%r, callback=callback, primary_domain=%r, secondary_domains=%r )""" % ( supported_control, base_dn, scope, filterstr, attrlist, attrsonly, timeout, primary_domain, secondary_domains ) ) break except ldap.SERVER_DOWN as errmsg: - log.error(_("LDAP server unavailable: %r") % (errmsg)) - log.error(_("%s") % (traceback.format_exc())) - log.error(_("-- reconnecting in 10 seconds.")) + log.error(_l("LDAP server unavailable: %r") % (errmsg)) + log.error(_l("%s") % (traceback.format_exc())) + log.error(_l("-- reconnecting in 10 seconds.")) self._disconnect() time.sleep(10) self.reconnect() except ldap.TIMEOUT: - log.error(_("LDAP timeout in searching for '%s'") % (filterstr)) + log.error(_l("LDAP timeout in searching for '%s'") % (filterstr)) self._disconnect() time.sleep(10) self.reconnect() except Exception as errmsg: failed_ok = True - log.error(_("An error occured using %s: %r") % (supported_control, errmsg)) - log.error(_("%s") % (traceback.format_exc())) + log.error(_l("An error occured using %s: %r") % (supported_control, errmsg)) + log.error(_l("%s") % (traceback.format_exc())) continue return _results def _parse_acl(self, acl): """ Parse LDAP ACL specification for use in IMAP """ results = [] if acl is not None: if not isinstance(acl, list): acl = [acl] for acl_entry in acl: # entry already converted to IMAP format? if acl_entry[0] == "(": results.append(acl_entry) continue acl_access = acl_entry.split()[-1] acl_subject = acl_entry.split(', ') if len(acl_subject) > 1: acl_subject = ', '.join(acl_subject[:-1]) else: acl_subject = acl_entry.split()[0] results.append("(%r, %r)" % (acl_subject, acl_access)) return results diff --git a/pykolab/auth/ldap/auth_cache.py b/pykolab/auth/ldap/auth_cache.py index af65e40..f32779c 100644 --- a/pykolab/auth/ldap/auth_cache.py +++ b/pykolab/auth/ldap/auth_cache.py @@ -1,189 +1,186 @@ # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import datetime import sqlalchemy from sqlalchemy import Column from sqlalchemy import DateTime from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import String from sqlalchemy import Text from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker import pykolab from pykolab.constants import KOLAB_LIB_PATH # pylint: disable=invalid-name conf = pykolab.getConf() log = pykolab.getLogger('pykolab.auth_cache') metadata = MetaData() db = None try: unicode('') except NameError: unicode = str # # Classes # DeclarativeBase = declarative_base() # pylint: disable=too-few-public-methods class Entry(DeclarativeBase): __tablename__ = 'entries' id = Column(Integer, primary_key=True) domain = Column(String(256), index=True, nullable=True) key = Column(Text, index=True, nullable=False) value = Column(Text, nullable=False) last_change = Column(DateTime, nullable=False, default=datetime.datetime.now()) def __init__(self, key, value): self.key = key if not isinstance(value, unicode): self.value = unicode(value, 'utf-8') else: self.value = value # # Functions # def del_entry(key): # pylint: disable=global-statement global db db = init_db() try: db.query(Entry).filter_by(key=key).delete() except sqlalchemy.exc.OperationalError: db = init_db(reinit=True) except sqlalchemy.exc.InvalidRequest: db = init_db(reinit=True) finally: db.query(Entry).filter_by(key=key).delete() db.commit() def get_entry(key): # pylint: disable=global-statement global db db = init_db() try: _entries = db.query(Entry).filter_by(key=key).all() except sqlalchemy.exc.OperationalError: db = init_db(reinit=True) except sqlalchemy.exc.InvalidRequest: db = init_db(reinit=True) finally: _entries = db.query(Entry).filter_by(key=key).all() - if _entries: - return None - - if len(_entries) > 1: + if len(_entries) != 1: return None log.debug("Entry found: %r" % (_entries[0].__dict__)) log.debug("Returning: %r" % (_entries[0].value)) return _entries[0].value.encode('utf-8', 'latin1') def set_entry(key, value): db = init_db() try: _entries = db.query(Entry).filter_by(key=key).all() except sqlalchemy.exc.OperationalError: db = init_db(reinit=True) except sqlalchemy.exc.InvalidRequest: db = init_db(reinit=True) finally: _entries = db.query(Entry).filter_by(key=key).all() if not _entries: db.add(Entry(key, value)) db.commit() elif len(_entries) == 1: if not isinstance(value, unicode): value = unicode(value, 'utf-8') if not _entries[0].value == value: _entries[0].value = value _entries[0].last_change = datetime.datetime.now() db.commit() def purge_entries(db): db.query(Entry).filter( Entry.last_change <= (datetime.datetime.now() - datetime.timedelta(1)) ).delete() db.commit() def init_db(reinit=False): """ Returns a SQLAlchemy Session() instance. """ # pylint: disable=global-statement global db if db is not None and not reinit: return db db_uri = conf.get('ldap', 'auth_cache_uri') if db_uri is None: db_uri = 'sqlite:///%s/auth_cache.db' % (KOLAB_LIB_PATH) if reinit: import os if os.path.isfile('%s/auth_cache.db' % (KOLAB_LIB_PATH)): os.unlink('%s/auth_cache.db' % (KOLAB_LIB_PATH)) echo = conf.debuglevel > 8 engine = create_engine(db_uri, echo=echo) DeclarativeBase.metadata.create_all(engine) Session = sessionmaker(bind=engine) db = Session() purge_entries(db) return db diff --git a/pykolab/auth/ldap/cache.py b/pykolab/auth/ldap/cache.py index 02da350..206be76 100644 --- a/pykolab/auth/ldap/cache.py +++ b/pykolab/auth/ldap/cache.py @@ -1,223 +1,214 @@ # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import datetime from uuid import UUID import sqlalchemy from sqlalchemy import Column from sqlalchemy import DateTime from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import String -from sqlalchemy import Table from sqlalchemy import desc from sqlalchemy import create_engine -from sqlalchemy.orm import mapper +from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker import pykolab from pykolab.constants import KOLAB_LIB_PATH from pykolab.translate import _ # pylint: disable=invalid-name conf = pykolab.getConf() -log = pykolab.getLogger('pykolab.auth_cache') +log = pykolab.getLogger('pykolab.cache') metadata = MetaData() db = {} # # Classes # +DeclarativeBase = declarative_base() + # pylint: disable=too-few-public-methods -class Entry: +class Entry(DeclarativeBase): + __tablename__ = 'entries' + last_change = None + id = Column(Integer, primary_key=True) + uniqueid = Column(String(128), nullable=False) + result_attribute = Column(String(128), nullable=False) + last_change = Column(DateTime, nullable=False, default=datetime.datetime.now()) + def __init__(self, uniqueid, result_attr, last_change): self.uniqueid = uniqueid self.result_attribute = result_attr modifytimestamp_format = conf.get_raw( 'ldap', 'modifytimestamp_format', default="%Y%m%d%H%M%SZ" ).replace('%%', '%') self.last_change = datetime.datetime.strptime( last_change, modifytimestamp_format ) -# -# Tables -# - - -entry_table = Table( - 'entry', metadata, - Column('id', Integer, primary_key=True), - Column('uniqueid', String(128), nullable=False), - Column('result_attribute', String(128), nullable=False), - Column('last_change', DateTime), -) - -# -# Table <-> Class Mappers -# - -mapper(Entry, entry_table) # # Functions # def delete_entry(domain, entry): _db = init_db(domain) _entry = _db.query(Entry).filter_by(uniqueid=entry['id']).first() if _entry is not None: _db.delete(_entry) _db.commit() def get_entry(domain, entry, update=True): result_attribute = conf.get_raw('cyrus-sasl', 'result_attribute') _entry = None _db = init_db(domain) try: _uniqueid = str(UUID(bytes_le=entry['id'])) log.debug( _("Entry uniqueid was converted from binary form to string: %s") % _uniqueid, level=8 ) except ValueError: _uniqueid = entry['id'] try: _entry = _db.query(Entry).filter_by(uniqueid=_uniqueid).first() except sqlalchemy.exc.OperationalError: _db = init_db(domain, reinit=True) except sqlalchemy.exc.InvalidRequestError: _db = init_db(domain, reinit=True) finally: _entry = _db.query(Entry).filter_by(uniqueid=_uniqueid).first() if not update: return _entry if _entry is None: log.debug(_("Inserting cache entry %r") % (_uniqueid), level=8) if result_attribute not in entry: entry[result_attribute] = '' _db.add(Entry(_uniqueid, entry[result_attribute], entry['modifytimestamp'])) _db.commit() _entry = _db.query(Entry).filter_by(uniqueid=_uniqueid).first() else: modifytimestamp_format = conf.get_raw( 'ldap', 'modifytimestamp_format', default="%Y%m%d%H%M%SZ" ).replace('%%', '%') if not _entry.last_change.strftime(modifytimestamp_format) == entry['modifytimestamp']: log.debug(_("Updating timestamp for cache entry %r") % (_uniqueid), level=8) last_change = datetime.datetime.strptime( entry['modifytimestamp'], modifytimestamp_format ) _entry.last_change = last_change _db.commit() _entry = _db.query(Entry).filter_by(uniqueid=_uniqueid).first() if result_attribute in entry: if not _entry.result_attribute == entry[result_attribute]: log.debug(_("Updating result_attribute for cache entry %r") % (_uniqueid), level=8) _entry.result_attribute = entry[result_attribute] _db.commit() _entry = _db.query(Entry).filter_by(uniqueid=_uniqueid).first() return _entry def init_db(domain, reinit=False): """ Returns a SQLAlchemy Session() instance. """ # pylint: disable=global-statement global db if domain in db and not reinit: return db[domain] if reinit: import os if os.path.isfile('sqlite:///%s/%s.db' % (KOLAB_LIB_PATH, domain)): os.unlink('sqlite:///%s/%s.db' % (KOLAB_LIB_PATH, domain)) db_uri = 'sqlite:///%s/%s.db' % (KOLAB_LIB_PATH, domain) echo = conf.debuglevel > 8 try: engine = create_engine(db_uri, echo=echo) - metadata.create_all(engine) + DeclarativeBase.metadata.create_all(engine) except Exception: engine = create_engine('sqlite://') + DeclarativeBase.metadata.create_all(engine) metadata.create_all(engine) Session = sessionmaker(bind=engine) db[domain] = Session() return db[domain] def last_modify_timestamp(domain): modifytimestamp_format = conf.get_raw( 'ldap', 'modifytimestamp_format', "%Y%m%d%H%M%SZ" ).replace('%%', '%') try: _db = init_db(domain) last_change = _db.query(Entry).order_by(desc(Entry.last_change)).first() if last_change is not None: return last_change.last_change.strftime(modifytimestamp_format) return datetime.datetime(1900, 1, 1, 00, 00, 00).strftime(modifytimestamp_format) except Exception: return datetime.datetime(1900, 1, 1, 00, 00, 00).strftime(modifytimestamp_format) diff --git a/pykolab/plugins/__init__.py b/pykolab/plugins/__init__.py index 4975b0c..82bf525 100644 --- a/pykolab/plugins/__init__.py +++ b/pykolab/plugins/__init__.py @@ -1,260 +1,272 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import logging import os import pdb import sys import traceback import pykolab from pykolab.translate import _ log = pykolab.getLogger('pykolab.plugins') conf = pykolab.getConf() + class KolabPlugins(object): """ Detects, loads and interfaces with plugins for different Kolab components. """ def __init__(self): """ Searches the plugin directory for plugins, and loads them into a list. """ self.plugins = {} for plugin_path in [ os.path.dirname(__file__), '/usr/share/pykolab/plugins', './pykolab/plugins' ]: if os.path.isdir(plugin_path): for plugin in os.listdir(plugin_path): - if os.path.isdir('%s/%s/' % (plugin_path,plugin,)): + if os.path.isdir('%s/%s/' % (plugin_path, plugin, )): self.plugins[plugin] = False self.check_plugins() def check_plugins(self): """ Checks all plugins in self.plugins and sets the values to True (loadable) or False -- not enabled, not installed or not loadable. """ for plugin in self.plugins: try: exec("from pykolab.plugins import %s" % (plugin)) self.plugins[plugin] = True self.load_plugins(plugins=[plugin]) except ImportError, e: - log.error(_("ImportError for plugin %s: %s") % (plugin,e)) + log.error(_("ImportError for plugin %s: %s") % (plugin, e)) traceback.print_exc() self.plugins[plugin] = False except RuntimeError, e: - log.error( _("RuntimeError for plugin %s: %s") % (plugin,e)) + log.error( _("RuntimeError for plugin %s: %s") % (plugin, e)) traceback.print_exc() self.plugins[plugin] = False except Exception, e: log.error(_("Plugin %s failed to load (%s: %s)") % (plugin, e.__class__, e)) traceback.print_exc() except: traceback.print_exc() def load_plugins(self, plugins=[]): """ Loads plugins specified by a list of plugins or loads them all """ if len(plugins) < 1: plugins = self.plugins.keys() for plugin in plugins: if self.plugins[plugin]: try: - exec("self.%s = %s.Kolab%s()" % (plugin,plugin,plugin.capitalize())) + exec("self.%s = %s.Kolab%s()" % (plugin, plugin, plugin.capitalize())) except: # TODO: A little better verbosity please! traceback.print_exc() def set_defaults(self, defaults, plugins=[]): """ Test for a function set_defaults() in all available and loaded plugins and execute plugin.set_defaults() """ if len(plugins) < 1: plugins = self.plugins.keys() for plugin in plugins: if not self.plugins[plugin]: continue - if not hasattr(self,plugin): + if not hasattr(self, plugin): continue - if hasattr(getattr(self,plugin),"set_defaults"): + if hasattr(getattr(self, plugin), "set_defaults"): try: - getattr(self,plugin).set_defaults(defaults) + getattr(self, plugin).set_defaults(defaults) except TypeError, e: - log.error(_("Cannot set defaults for plugin %s: %s") % (plugin,e)) + log.error(_("Cannot set defaults for plugin %s: %s") % (plugin, e)) except RuntimeError, e: - log.error(_("Cannot set defaults for plugin %s: %s") % (plugin,e)) + log.error(_("Cannot set defaults for plugin %s: %s") % (plugin, e)) except: log.error(_("Cannot set defaults for plugin %s: Unknown Error") % (plugin)) else: log.debug(_("Not setting defaults for plugin %s: No function 'set_defaults()'") % plugin, level=5) def set_runtime(self, runtime, plugins=[]): """ Set runtime variables from plugins, like 'i_did_all_this' """ if len(plugins) < 1: plugins = self.plugins.keys() for plugin in plugins: if not self.plugins[plugin]: continue - if not hasattr(self,plugin): + if not hasattr(self, plugin): continue - if hasattr(getattr(self,plugin),"set_runtime"): + if hasattr(getattr(self, plugin), "set_runtime"): try: - getattr(self,plugin).set_runtime(runtime) + getattr(self, plugin).set_runtime(runtime) except RuntimeError, e: - log.error(_("Cannot set runtime for plugin %s: %s") % (plugin,e)) + log.error(_("Cannot set runtime for plugin %s: %s") % (plugin, e)) else: log.debug(_("Not setting runtime for plugin %s: No function 'set_runtime()'") % (plugin), level=5) def add_options(self, parser, plugins=[]): """ Add options specified in a plugin to parser. Takes a list of plugin names or does them all """ if len(plugins) < 1: plugins = self.plugins.keys() for plugin in plugins: if not self.plugins[plugin]: continue - if not hasattr(self,plugin): + if not hasattr(self, plugin): continue - if hasattr(getattr(self,plugin),"add_options"): + if hasattr(getattr(self, plugin), "add_options"): try: exec("self.%s.add_options(parser)" % plugin) except RuntimeError, e: - log.error(_("Cannot add options for plugin %s: %s") % (plugin,e)) + log.error(_("Cannot add options for plugin %s: %s") % (plugin, e)) except TypeError, e: - log.error(_("Cannot add options for plugin %s: %s") % (plugin,e)) + log.error(_("Cannot add options for plugin %s: %s") % (plugin, e)) else: log.debug(_("Not adding options for plugin %s: No function 'add_options()'") % plugin, level=5) def check_options(self, plugins=[]): """ Executes plugin.check_plugins() for all enabled plugins or the list of plugin names specified. """ if len(plugins) < 1: plugins = self.plugins.keys() for plugin in plugins: if not self.plugins[plugin]: continue - if not hasattr(self,plugin): + if not hasattr(self, plugin): continue - if hasattr(getattr(self,plugin),"check_options"): + if hasattr(getattr(self, plugin), "check_options"): try: exec("self.%s.check_options()" % plugin) except AttributeError, e: - log.error(_("Cannot check options for plugin %s: %s") % (plugin,e)) + log.error(_("Cannot check options for plugin %s: %s") % (plugin, e)) else: log.debug(_("Not checking options for plugin %s: No function 'check_options()'") % (plugin), level=5) def plugin_check_setting(self, func, option, val, plugins=[]): """ Checks one setting specified by 'option' against the 'val' it is passed by all plugins or by the list of plugins specified """ if len(plugins) < 1: plugins = self.plugins.keys() for plugin in plugins: if not self.plugins[plugin]: continue - if not hasattr(self,plugin): + if not hasattr(self, plugin): continue - if hasattr(getattr(self,plugin),"%s_%s" % (func,option)): - exec("retval = getattr(self,plugin).%s_%s(val)" % (func,option)) + if hasattr(getattr(self, plugin), "%s_%s" % (func, option)): + exec("retval = getattr(self, plugin).%s_%s(val)" % (func, option)) return retval return False def exec_hook(self, hook, plugins=[], kw={}, args=()): """Execute a hook""" retval = None if len(plugins) < 1: plugins = self.plugins.keys() for plugin in plugins: if not self.plugins[plugin]: continue - if not hasattr(self,plugin): + if not hasattr(self, plugin): continue - if hasattr(getattr(self,plugin),hook): + if hasattr(getattr(self, plugin), hook): try: - log.debug(_("Executing hook %s for plugin %s") % (hook,plugin), level=8) - #print "retval = self.%s.%s(%r, %r)" % (plugin,hook, args, kw) - exec("retval = self.%s.%s(*args, **kw)" % (plugin,hook)) - except TypeError, e: - log.error(_("Cannot execute hook %s for plugin %s: %s") % (hook,plugin,e)) - except AttributeError, e: - log.error(_("Cannot execute hook %s for plugin %s: %s") % (hook,plugin,e)) + log.debug(_("Executing hook %s for plugin %s") % (hook, plugin), level=8) + func = getattr(getattr(self, plugin), hook) + retval = func(*args, **kw) + except TypeError as errmsg: + log.error( + _("Cannot execute hook %s for plugin %s: %s") % (hook, plugin, errmsg) + ) + + log.error(traceback.format_exc()) + except AttributeError as errmsg: + log.error( + _("Cannot execute hook %s for plugin %s: %s") % (hook, plugin, errmsg) + ) + + log.error(traceback.format_exc()) return retval def return_true_boolean_from_plugins(self, bool, plugins=[]): - """Given the name of a boolean, walks all specified plugins, or all available plugins, and returns True if a plugin has it set to true""" + """ + Given the name of a boolean, walks all specified plugins, or all available plugins, and + returns True if a plugin has it set to true + """ if len(plugins) < 1: plugins = self.plugins.keys() retval = False for plugin in plugins: if not self.plugins[plugin]: continue - if not hasattr(self,plugin): + if not hasattr(self, plugin): continue - if hasattr(getattr(self,plugin),bool): + if hasattr(getattr(self, plugin), bool): try: - exec("boolval = self.%s.%s" % (plugin,bool)) + exec("boolval = self.%s.%s" % (plugin, bool)) except AttributeError, e: pass else: boolval = None if boolval: retval = True return retval diff --git a/pykolab/utils.py b/pykolab/utils.py index 1e5057c..bda7e76 100644 --- a/pykolab/utils.py +++ b/pykolab/utils.py @@ -1,634 +1,635 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # from __future__ import print_function import base64 import getpass import grp import os import pwd from six import string_types import struct import sys import pykolab from pykolab import constants from pykolab.translate import _ as _l # pylint: disable=invalid-name log = pykolab.getLogger('pykolab.utils') conf = pykolab.getConf() try: # pylint: disable=redefined-builtin input = raw_input except NameError: pass try: unicode('') except NameError: unicode = str # pylint: disable=too-many-branches def ask_question(question, default="", password=False, confirm=False): """ Ask a question on stderr. Since the answer to the question may actually be a password, cover that case with a getpass.getpass() prompt. Accepts a default value, but ignores defaults for password prompts. Usage: pykolab.utils.ask_question("What is the server?", default="localhost") """ if default != "" and default is not None and conf.cli_keywords.answer_default: if not conf.cli_keywords.quiet: print("%s [%s]: " % (question, default)) return default if password: if default == "" or default is None: answer = getpass.getpass("%s: " % (question)) else: answer = getpass.getpass("%s [%s]: " % (question, default)) else: if default == "" or default is None: answer = input("%s: " % (question)) else: answer = input("%s [%s]: " % (question, default)) # pylint: disable=too-many-nested-blocks if not answer == "": if confirm: answer_confirm = None answer_confirmed = False while not answer_confirmed: if password: answer_confirm = getpass.getpass(_l("Confirm %s: ") % (question)) else: answer_confirm = input(_l("Confirm %s: ") % (question)) if not answer_confirm == answer: print(_l("Incorrect confirmation. Please try again."), file=sys.stderr) if password: if default == "" or default is None: answer = getpass.getpass(_l("%s: ") % (question)) else: answer = getpass.getpass(_l("%s [%s]: ") % (question, default)) else: if default == "" or default is None: answer = input(_l("%s: ") % (question)) else: answer = input(_l("%s [%s]: ") % (question, default)) else: answer_confirmed = True if answer == "": return default return answer # pylint: disable=too-many-return-statements def ask_confirmation(question, default="y", all_inclusive_no=True): """ Create a confirmation dialog, including a default option (capitalized), and a "yes" or "no" parsing that can either require an explicit, full "yes" or "no", or take the default or any YyNn answer. """ default_answer = None if default in ["y", "Y"]: default_answer = True default_no = "n" default_yes = "Y" elif default in ["n", "N"]: default_answer = False default_no = "N" default_yes = "y" else: # This is a 'yes' or 'no' question the user # needs to provide the full yes or no for. default_no = "'no'" default_yes = "Please type 'yes'" if conf.cli_keywords.answer_yes \ or (conf.cli_keywords.answer_default and default_answer is not None): if not conf.cli_keywords.quiet: print("%s [%s/%s]: " % (question, default_yes, default_no)) if conf.cli_keywords.answer_yes: return True if conf.cli_keywords.answer_default: return default_answer answer = False while not answer: answer = input("%s [%s/%s]: " % (question, default_yes, default_no)) # Parse answer and set back to False if not appropriate if all_inclusive_no: if answer == "" and default_answer is not None: return default_answer if answer in ["y", "Y", "yes"]: return True if answer in ["n", "N", "no"]: return False answer = False print(_l("Please answer 'yes' or 'no'."), file=sys.stderr) if answer not in ["y", "Y", "yes"]: return False return True # pylint: disable=dangerous-default-value def ask_menu(question, options={}, default=''): if default != '' and conf.cli_keywords.answer_default: if not conf.cli_keywords.quiet: print(question + " [" + default + "]:") return default if default != '': print(question + " [" + default + "]:") else: print(question) answer_correct = False max_key_length = 0 if isinstance(options, list): _options = options options = {} for key in _options: options[key] = key keys = options.keys() keys.sort() while not answer_correct: for key in keys: key_length = len("%s" % key) if key_length > max_key_length: max_key_length = key_length str_format = "%%%ds" % max_key_length if default == '' or default not in options.keys(): for key in keys: if options[key] == key: print(" - " + key) else: print(" - " + str_format % key + ": " + options[key]) answer = input(_l("Choice") + ": ") else: answer = input(_l("Choice (type '?' for options)") + ": ") if answer == '?': for key in keys: if options[key] == key: print(" - " + key) else: print(" - " + str_format % key + ": " + options[key]) continue if answer == '' and default in options.keys(): answer = default if answer in [str(x) for x in options.keys()]: answer_correct = True return answer def decode(key, enc): if key is None: return enc dec = [] enc = base64.urlsafe_b64decode(enc) for i in range(len(enc)): key_c = key[i % len(key)] dec_c = chr((256 + ord(enc[i]) - ord(key_c)) % 256) dec.append(dec_c) return "".join(dec) def encode(key, clear): if key is None: return clear enc = [] for i in range(len(clear)): key_c = key[i % len(key)] enc_c = chr((ord(clear[i]) + ord(key_c)) % 256) enc.append(enc_c) return base64.urlsafe_b64encode("".join(enc)) def ensure_directory(_dir, _user='root', _group='root'): if not os.path.isdir(_dir): os.makedirs(_dir) try: try: (ruid, _, _) = os.getresuid() (rgid, _, _) = os.getresgid() except AttributeError: ruid = os.getuid() rgid = os.getgid() if ruid == 0: # Means we can setreuid() / setregid() / setgroups() if rgid == 0: # Get group entry details try: (_, _, group_gid, _) = grp.getgrnam(_group) except KeyError: print(_l("Group %s does not exist") % (_group), file=sys.stderr) sys.exit(1) # Set real and effective group if not the same as current. if not group_gid == rgid: os.chown(_dir, -1, group_gid) if ruid == 0: # Means we haven't switched yet. try: (_, _, user_uid, _, _, _, _) = pwd.getpwnam(_user) except KeyError: print(_l("User %s does not exist") % (_user), file=sys.stderr) sys.exit(1) # Set real and effective user if not the same as current. if not user_uid == ruid: os.chown(_dir, user_uid, -1) except Exception: print(_l("Could not change the permissions on %s") % (_dir), file=sys.stderr) def generate_password(): import subprocess p1 = subprocess.Popen(['head', '-c', '200', '/dev/urandom'], stdout=subprocess.PIPE) p2 = subprocess.Popen(['tr', '-dc', '_A-Z-a-z-0-9'], stdin=p1.stdout, stdout=subprocess.PIPE) p3 = subprocess.Popen(['head', '-c', '15'], stdin=p2.stdout, stdout=subprocess.PIPE) p1.stdout.close() p2.stdout.close() output = p3.communicate()[0] return output def multiline_message(message): if hasattr(conf, 'cli_keywords') and hasattr(conf.cli_keywords, 'quiet'): if conf.cli_keywords.quiet: return "" column_width = 80 # First, replace all occurences of "\n" message = message.replace(" ", "") message = message.replace("\n", " ") lines = [] line = "" for word in message.split(): if (len(line) + len(word)) > column_width: lines.append(line) line = word else: if line == "": line = word else: line += " %s" % (word) lines.append(line) return "\n%s\n" % ("\n".join(lines)) def stripped_message(message): return "\n" + message.strip() + "\n" def str2unicode(s, encoding='utf-8'): if isinstance(s, unicode): return s try: return unicode(s, encoding) except Exception: pass return s def normalize(_object): if isinstance(_object, list): result = [] elif isinstance(_object, dict): result = {} else: return _object if isinstance(_object, list): for item in _object: result.append(item.lower()) result = list(set(result)) return result if isinstance(_object, dict): def _strip(value): try: return value.strip() except Exception: return value for key in _object: if isinstance(_object[key], list): if _object[key] is None: continue # Dont run strip anything from attributes which # hold byte strings if key.lower() in constants.BINARY_ATTRS: val = _object[key] else: val = map(_strip, _object[key]) if len(val) == 1: result[key.lower()] = ''.join(val) else: result[key.lower()] = val else: if _object[key] is None: continue result[key.lower()] = _strip(_object[key]) if 'objectsid' in result and not result['objectsid'][0] == "S": result['objectsid'] = sid_to_string(result['objectsid']) if 'sn' in result: result['surname'] = result['sn'].replace(' ', '') if 'mail' in result: if isinstance(result['mail'], list): result['mail'] = result['mail'][0] if result['mail']: if len(result['mail'].split('@')) > 1: result['domain'] = result['mail'].split('@')[1] if 'domain' not in result and 'standard_domain' in result: result['domain'] = result['standard_domain'] if 'objectclass' not in result: result['objectclass'] = [] if result['objectclass'] is None: result['objectclass'] = [] if not isinstance(result['objectclass'], list): result['objectclass'] = [result['objectclass']] result['objectclass'] = [x.lower() for x in result['objectclass']] return result def parse_input(_input, splitchars=[' ']): """ Split the input string using the split characters defined in splitchars, and remove the empty list items, then unique the list items. Takes a string as input, and a list of characters the string should be split with (list of delimiter characters). """ _parse_list = _input.split(splitchars.pop()) _output_list = [] for splitchar in splitchars: __parse_list = [] for item in _parse_list: __parse_list.extend(item.split(splitchar)) _parse_list = __parse_list for item in _parse_list: if not item == '': if _output_list.count(item) < 1: _output_list.append(item) return _output_list def parse_ldap_uri(uri): """ Parse an LDAP URI and return it's components. Returns a tuple containing; - protocol (ldap, ldaps), - server (address or None), - base_dn, - attrs (list of attributes length 1, or empty), - scope, - filter or None on failure """ _protocol = uri.split(':')[0] try: try: _ldap_uri, _attr, _scope, _filter = uri.split('?') _server = _ldap_uri.split('//')[1].split('/')[0] _base_dn = _ldap_uri.split('//')[1].split('/')[1] except Exception: _server = uri.split('//')[1].split('/')[0] _attr = None _scope = None _filter = None _base_dn = None if len(_server.split(':')) > 1: _port = _server.split(':')[1] _server = _server.split(':')[0] else: if _protocol == 'ldaps': _port = "636" else: _port = "389" if _server == '': _server = None if _attr == '': _attrs = [] else: _attrs = [_attr] if _scope == '': _scope = 'sub' if _filter == '': _filter = "(objectclass=*)" return (_protocol, _server, _port, _base_dn, _attrs, _scope, _filter) except Exception: return None def pop_empty_from_list(_input_list): _output_list = [] for item in _input_list: if not item == '': _output_list.append(item) def sid_to_string(sid): srl = ord(sid[0]) number_sub_id = ord(sid[1]) iav = struct.unpack('!Q', '\x00\x00' + sid[2:8])[0] sub_ids = [] for i in range(number_sub_id): sub_ids.append(struct.unpack(' 1: (locale_name, locale_charset) = locale.normalize(locale_name).split('.') else: locale_charset = 'utf-8' try: log.debug(_l("Attempting to set locale"), level=8) locale.setlocale(locale.LC_ALL, (locale_name, locale_charset)) log.debug(_l("Success setting locale"), level=8) except Exception: log.debug(_l("Failure to set locale"), level=8) command = ['/usr/bin/iconv', '-f', 'UTF-8', '-t', 'ASCII//TRANSLIT', '-s'] log.debug(_l("Executing '%s | %s'") % (r"%s" % (mystring), ' '.join(command)), level=8) + process = subprocess.Popen( command, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, env={'LANG': locale.normalize(locale_name)} ) try: - print >> process.stdin, r"%s" % mystring + print(r"%s" % (mystring), file=process.stdin) except UnicodeEncodeError: pass result = process.communicate()[0].strip() if '?' in result or (result == '' and not mystring == ''): log.warning(_l("Could not translate %s using locale %s") % (mystring, locale_name)) from pykolab import translit result = translit.transliterate(mystring, locale_name) return result def true_or_false(val): if val is None: return False if isinstance(val, bool): return val if isinstance(val, string_types): val = val.lower() if val in ["true", "yes", "y", "1"]: return True else: return False if isinstance(val, int) or isinstance(val, float): if val >= 1: return True else: return False def is_service(services): """ Checks each item in list services to see if it has a RC script in pykolab.constants.RC_DIR to see if it's a service, and returns the name of the service for the first service it can find. However, it also checks whether the other services exist and issues a warning if more then one service exists. Usage: utils.is_service(['dirsrv', 'ldap']) """ _service = None _other_services = [] for service in services: if os.path.isfile(os.path.join(constants.RC_DIR, service)): if _service == '': _service = service else: _other_services.append(service) return (_service, _other_services)