diff --git a/.flake8 b/.flake8 index 07259cb..2fc42ad 100644 --- a/.flake8 +++ b/.flake8 @@ -1,18 +1,20 @@ [flake8] exclude = .git, __pycache__, docs/source/conf.py ignore = + # "Too complex"? Sure, for people that eat Hawaii pizza... + C901, # 'something' imported but unused F401, # 'from module import *' used: unable to detect undefined names F403, # name may be undefined, or defined from star imports: module F405, # line break before binary operator W503 max-complexity = 18 max-line-length = 100 diff --git a/conf/kolab.conf b/conf/kolab.conf index afe318f..97a2a3b 100644 --- a/conf/kolab.conf +++ b/conf/kolab.conf @@ -1,494 +1,497 @@ [kolab] ; Set this to the primary domain name space served within this Kolab Groupware ; deployment. primary_domain = example.org ; This is the primary authentication mechanism used, and contains the list of ; domain name spaces for this deployment. Each domain name space may have its ; own auth_mechanism setting. ; ; Valid options currently include only 'ldap'. auth_mechanism = ldap ; The IMAP backend to use - currently supported values include only ; 'cyrus-imap'. imap_backend = cyrus-imap ; The default locale for this Kolab Groupware installation default_locale = en_US ; Synchronization interval - describes the number of seconds to wait in ; between non-persistent synchronization attempts. Relevant only for ; deployments that lack persistent search and syncrepl ldap controls. sync_interval = 300 ; Synchronization interval for domains - describes the number of seconds ; to wait in between polls for new and deleted domain name spaces. domain_sync_interval = 600 ; The policy to use when originally composing the uid attribute value. ; Normally '%(surname)s.lower()', the transliterated value of the 'sn', ; in all lower-case. ; ; Other examples include: ; ; policy_uid = '%(givenname)s'[0:1]%(surname)s.lower() ; policy_uid = %(givenname)s policy_uid = %(surname)s.lower() ; Primary and secondary recipient address policies. This is called the ; recipient policy as documented in: ; ; http://docs.kolab.org/administrator-guide/configuring-the-kolab-server.html#recipient-policy ; ; Note this is the global default, and each [$domain] section can have ; their own (as in this default configuration, see [example.org]). primary_mail = %(surname)s@%(domain)s secondary_mail = { 0: { "{0}.{1}@{2}": "format('%(givenname)s'[0:1].capitalize(), '%(surname)s', '%(domain)s')" }, 1: { "{0}@{1}": "format('%(uid)s', '%(domain)s')" }, 2: { "{0}@{1}": "format('%(givenname)s.%(surname)s', '%(domain)s')" } } ; To disable the application of the recipient policy by the daemon ('kolabd' service), ; uncomment the next line. ;daemon_rcpt_policy = False ; A global default for folders to create in addition to the INBOX ; folder. autocreate_folders = { 'Archive': { 'quota': 0, 'partition': 'archive' }, 'Calendar': { 'annotations': { '/private/vendor/kolab/folder-type': "event.default", '/shared/vendor/kolab/folder-type': "event", }, }, 'Calendar/Personal Calendar': { 'annotations': { '/shared/vendor/kolab/folder-type': "event", }, }, 'Configuration': { 'annotations': { '/private/vendor/kolab/folder-type': "configuration.default", '/shared/vendor/kolab/folder-type': "configuration", }, }, 'Contacts': { 'annotations': { '/private/vendor/kolab/folder-type': "contact.default", '/shared/vendor/kolab/folder-type': "contact", }, }, 'Contacts/Personal Contacts': { 'annotations': { '/shared/vendor/kolab/folder-type': "contact", }, }, 'Drafts': { 'annotations': { '/private/vendor/kolab/folder-type': "mail.drafts", }, }, 'Files': { 'annotations': { '/private/vendor/kolab/folder-type': "file.default", '/shared/vendor/kolab/folder-type': "file", }, }, 'Journal': { 'annotations': { '/private/vendor/kolab/folder-type': "journal.default", '/shared/vendor/kolab/folder-type': "journal", }, }, 'Notes': { 'annotations': { '/private/vendor/kolab/folder-type': 'note.default', '/shared/vendor/kolab/folder-type': 'note', }, }, 'Sent': { 'annotations': { '/private/vendor/kolab/folder-type': "mail.sentitems", }, }, 'Spam': { 'annotations': { '/private/vendor/kolab/folder-type': "mail.junkemail", }, }, 'Tasks': { 'annotations': { '/private/vendor/kolab/folder-type': "task.default", '/shared/vendor/kolab/folder-type': "task", }, }, 'Trash': { 'annotations': { '/private/vendor/kolab/folder-type': "mail.wastebasket", }, }, } [autodiscover] ; service hosts (%d will be replaced by user domain) ; for imap/pop3/smtp protocol prefix and port is required activesync = activesync.%d imap = ssl://imap.%d:993 pop3 = ssl://pop3.%d:995 smtp = ssl://smtp.%d:465 ; LDAP attribute used as login login_attribute = mail ; optional service name service_name = Kolab Groupware service_short = Kolab ; enables HTTP/LDAP debugging ;debug_mode = trace [imap] virtual_domains = userid [ldap] ; The URI to LDAP ldap_uri = ldap://localhost:389 +; A timeout, in seconds, for regular searches such as authentication requests. +timeout = 10 + ; A list of integers containing supported controls, to increase the efficiency ; of individual short-lived connections with LDAP. supported_controls = 0,2,3 ; The base dn for the deployment. Note that this is the highest level in the ; tree Kolab will ever go. Should your OU structure allow it, you could set this ; to ou=Kolab,ou=Not-So-Private,dc=example,dc=org. base_dn = dc=example,dc=org ; The (administrative) bind dn and corresponding password. ; ; Feel free to set this to a DN with only read permissions on the tree. These ; credentials are used by the Kolab Daemon only, as it might need to set ; additional attributes in order to apply plugins successfully. Such attributes ; could include the first two values in the 'mail_attributes' list (see further ; down) to complete the 'recipient_policy' (see further down), mail quota, ; the mail server attribute, and others. bind_dn = cn=Directory Manager bind_pw = Welcome123 ; Bind DN and password used for services. The DN should have read and search ; privileges only, but should be able to read all relevant parts of the tree. ; ; These credentials are used by, among others, Postfix, Wallace, programs that ; need to find the user DN before binding as the user (including the webadmin ; API, Roundcube, Syncroton). service_bind_dn = uid=kolab-service,ou=Special Users,%(base_dn)s service_bind_pw = wc18bqshFmifGtN ; The base DN, search scope and filter to use when searching for users of any ; type. User types are of primary purpose to the web admin (API), but the ; generic base DN, scope and filter allow us to configure other services as ; well, including Address Books in Roundcube and for Syncroton, the list of ; users in the web admin (API), etc. user_base_dn = ou=People,%(base_dn)s user_scope = sub user_filter = (objectclass=inetorgperson) ; The base DN, scope and filter to use when searching for users of the 'kolab' ; type. This filter is preferred when searching for Kolab users specifically, ; such as in the synchronisation between LDAP and IMAP. Also, it is ; (preferrably) only Kolab users that are allowed to login, use the SMTP server, ; etc. ; ; Note that all user_* settings are valid, and those not available with a kolab_ ; prefix fall back to using the generic user_* equivalent setting. kolab_user_base_dn = ou=People,%(base_dn)s kolab_user_filter = (objectclass=kolabinetorgperson) ; Add additional _user_base_dn, _user_scope and _user_filter. ; Useful for configuring sub-address books, and for the webadmin API when adding ; new users of the example type key 'posix' - the new user will be added in the ; OU configured below. ;posix_user_base_dn = ou=POSIX Accounts,ou=People,%(base_dn)s ;posix_user_scope = one ;posix_user_filter = (&(objectclass=posixaccount)(uidnumber>=1000)) ; The same as for users, but applicable to groups group_base_dn = ou=Groups,%(base_dn)s group_filter = (|(objectclass=groupofuniquenames)(objectclass=groupofurls)) group_scope = sub kolab_group_filter = (|(objectclass=kolabgroupofuniquenames)(objectclass=kolabgroupofurls)) ; Same again sharedfolder_base_dn = ou=Shared Folders,%(base_dn)s sharedfolder_filter = (objectclass=kolabsharedfolder) ; The attribute entry name that controls the ACLs set on a shared folder sharedfolder_acl_entry_attribute = acl ; Same again. Resources live in a different OU structure or; ; ; - They would appear in the address book(s) as distribution lists or individual contacts, ; - Groups or individual users would appear to be Resources. ; resource_base_dn = ou=Resources,%(base_dn)s resource_filter = (|%(group_filter)s(objectclass=kolabsharedfolder)) ; The base DN, scope and filter to use when searching for additional domain ; name spaces in this environment. domain_base_dn = cn=kolab,cn=config domain_filter = (&(associatedDomain=*)) domain_name_attribute = associateddomain ; Attribute that holds the root dn for the domain name space. If this attribute ; does not exist, a standard root dn is formed from the primary domain name ; space (the value in the RDN), as follows: ; ; 'dc=' + ',dc='.join(domainname.split('.')) ; ; or, in example: ; ; domain: example.org ; root dn: dc=example,dc=org domain_rootdn_attribute = inetdomainbasedn ; The attribute that holds the quota. quota_attribute = mailquota ; The format of the modifytimestamp attribute values modifytimestamp_format = %Y%m%d%H%M%SZ ; A unique attribute that can be used to identify the entry beyond renames and ; moves. Note that 'nsuniqueid' is specific to all Netscape-based directory ; services. ; ; For OpenLDAP, use 'entrydn' - the 'entryUUID' can regrettably not be searched ; with. ; ; For Active Directory, use 'objectsid'. unique_attribute = nsuniqueid ; Attribute names that hold valid, internal recipient addresses. Note the use ; of mail and alias frees up the use of mailAlternateAddress to contain a user's ; external email address. ; ; Syntax is a comma- or comma-space separated list. ; ; The first value is used for the purpose of a single "primary" email address, ; that could be subject to a recipient policy, the second is used for the ; purpose of one or more secondary mail addresses, that could also be subject to ; a recipient policy. mail_attributes = mail, alias ; The attribute that holds the FQDN to the mail server the folder exists on mailserver_attribute = mailhost ; Attributes that hold valid authentication login names. Use 'mail', 'alias' and ; optionally 'uid' (the uid is marked as an auth_attribute automatically), so ; that a user can login with; ; ; - uid (i.e. 'jdoe'), ; - mail, fully qualified and localpart only (i.e. "john.doe" and ; "john.doe@example.org"), ; - alias, fully qualified and localpart only (i.e. "j.doe" and ; "j.doe@example.org). auth_attributes = mail, alias, uid ; Virtual List View control, and Server-Side Sorting control configuration. ; ; Configure these to allow the Web Administration Panel (API) to not have to ; search a non-database cn=config for the VLV configuration. ; ;vlv = [ ; { ; 'ou=People,dc=example,dc=org': { ; 'scope': 'sub', ; 'filter': '(objectclass=inetorgperson)', ; 'sort' : [ ; [ ; 'displayname', ; 'sn', ; 'givenname', ; 'cn' ; ] ; ] ; } ; }, ; { ; 'ou=Groups,dc=example,dc=org': { ; 'scope': 'sub', ; 'filter': '(objectclass=groupofuniquenames)', ; 'sort' : [ ; [ ; 'cn' ; ] ; ] ; } ; }, ; ] [kolab_smtp_access_policy] cache_uri = mysql://user:pass@localhost/database cache_retention = 86400 ; To allow users to also send using email addresses in domain name spaces not ; in their own parent and/or alias domains, add 'mailalternateaddress' to this ; list. address_search_attrs = mail, alias ; Prepend the Sender: and/or X-Sender header(s) if the user authenticated is a ; designated delegatee of the envelope sender address? delegate_sender_header = True ; Prepend the Sender: and/or X-Sender header(s) if the user authenticated ; is using an envelope sender address that is a secondary recipient email ; address (attached to the object entry) of the user authenticated? alias_sender_header = True ; Prepend the Sender: header? Only relevant if delegate_sender_header or ; alias_sender_header is set to True. sender_header = True ; Prepend the X-Sender: header? Only relevant if delegate_sender_header or ; alias_sender_header is set to True. xsender_header = True ; "Encrypt" -- read, "obscure" -- the contents of the 'Sender:' and/or ; 'X-Sender:' header(s). Note that this invalidates client's use of the header ; value, and therefore replaces both headers with 'X-Authenticated-As'. ; ; Example: 'vanmeeuwen@kolabsys.com' becomes '6crb3dHK6ODS3qzQ4tXO0t_e5pfQ39k=' ; ; sender_header_enc_key = 'simple' ; Allow hosts in these networks to submit messages with empty envelope senders, ; such as web-clients responding to MDN requests. empty_sender_hosts = 3.2.1.0/24, 6.6.6.0/24 ; Section for Hosted client interface settings. This is not enabled by default. ;[kolab_hosting] ; ;; Set the default domain name space for the list of domain name spaces (if more ;; than one) that new users that register are allowed to select. ;primary_domain = somedomain.tld ; ;; The following bind credentials should be allowed to search ;; "ldap/domain_base_dn" (i.e. cn=kolab,cn=config), but should not be allowed to ;; read any domain name space LDAP entry that users are not eligible to select. ;; ;; Note that the bind credentials usually live in the upper ;; "kolab/primary_domain". ;bind_dn = uid=hosted-service,ou=Special Users,dc=kolab,dc=net ;bind_pw = bla ;recaptcha_private_key = bla ;recaptcha_public_key = bla [kolab_wap] skin = default sql_uri = mysql://user:pass@localhost/database ; Use the following setting to indicate the API is installed on a different ; system, or in a non-standard location. ;api_url = http://localhost/kolab-webadmin/api ; Configure SSL should you want to have the web admin panel (client interface) ; use the API over HTTPS. ; ; By default, httpd and coconspirators are setup to use self-signed certificates, ; so the following two settings are set to false by default. ssl_verify_peer = false ssl_verify_host = false ;ssl_cafile = /path/to/ca/file ;ssl_capath = /path/to/ca/dir ;ssl_local_cert = /path/to/local/cert ;ssl_passphrase = MyPassword [cyrus-imap] ; The URI to use to connect to IMAP. Note that pykolab itself can detect whether ; or not Cyrus IMAP is deployed in a Murder topology, and should be able to ; connect to individual backends as well. uri = imaps://localhost:993 ; The login username to use for global administration. admin_login = cyrus-admin ; The corresponding password. admin_password = Welcome123 [cyrus-sasl] ; The user canonification result attribute. result_attribute = mail [wallace] ; List the modules to load and apply, in order. ; ; Available modules include; ; ; * resources ; * invitationpolicy ; * footer ; * signature ; modules = resources, invitationpolicy ; Footer module settings ;footer_text = /etc/kolab/footer.text ;footer_html = /etc/kolab/footer.html ; Signature module settings ; ; Two modes: write out the exact signature in /etc/kolab/ as html and/or text, ; or use rules. ; ; If files are configured, rules do not apply. If files are configured, a ; fallback is /etc/kolab/signature_default.{html,txt}. ; ; signature_file_html = /etc/kolab/signature.d/%(mail)s.html ; signature_file_text = /etc/kolab/signature.d/%(mail)s.txt ; ; A list of dicts, with each dict holding an attribute name ("o", "cn", ; "entrydn"), and a regular expression to be matched against the attribute ; value. ; ; The module takes the first match, and uses the "html" and "text" files as ; templates. ; ;signature_rules = [ ; { ; "entrydn": "uid=.*,ou=IT,ou=People,dc=example,dc=org", ; "html": "/etc/kolab/signature_IT.html", ; "text": "/etc/kolab/signature_IT.txt" ; }, ; { ; "entrydn": "uid=.*,ou=Finance,ou=People,dc=example,dc=org", ; "html": "/etc/kolab/signature_Finance.html", ; "text": "/etc/kolab/signature_Finance.txt" ; } ; ] ; default settings for kolabInvitationPolicy LDAP attribute on user records kolab_invitation_policy = ACT_ACCEPT_IF_NO_CONFLICT:example.org, ACT_MANUAL ; automatically update the participant status from iTip REPLY messages ; in the personal calendars of all other listed participants. invitationpolicy_autoupdate_other_attendees_on_reply = false ; number of days past their end resource calendar events should be kept resource_calendar_expire_days = 100 ; This is a domain name space specific section, that enables us to override ; all settings, for example, the LDAP URI, base and bind DNs, scopes, filters, ; etc. Note that overriding the LDAP settings for the primary domain name space ; does not make any sense. [example.org] default_quota = 1048576 primary_mail = %(givenname)s.%(surname)s@%(domain)s diff --git a/pykolab/auth/ldap/__init__.py b/pykolab/auth/ldap/__init__.py index f43d183..5087bb6 100644 --- a/pykolab/auth/ldap/__init__.py +++ b/pykolab/auth/ldap/__init__.py @@ -1,3174 +1,3243 @@ # 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 datetime +# Catch python-ldap-2.4 changes +from distutils import version import _ldap import ldap -import ldap.async import ldap.controls import ldap.filter import logging import time import traceback import pykolab import pykolab.base from pykolab import utils from pykolab.constants import * from pykolab.errors import * from pykolab.translate import _ -log = pykolab.getLogger('pykolab.auth') -conf = pykolab.getConf() - import auth_cache import cache -# Catch python-ldap-2.4 changes -from distutils import version +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: LDAP_CONTROL_PAGED_RESULTS = ldap.LDAP_CONTROL_PAGE_OID try: from ldap.controls import psearch -except: +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__ - ): + if version.StrictVersion('2.4.0') <= version.StrictVersion(ldap.__version__): ldap.controls.SimplePagedResultsControl.__init__( - self, - size=page_size, - cookie=cookie - ) + self, + size=page_size, + cookie=cookie + ) + else: ldap.controls.SimplePagedResultsControl.__init__( - self, - LDAP_CONTROL_PAGED_RESULTS, - True, - (page_size, '') - ) + self, + LDAP_CONTROL_PAGED_RESULTS, + True, + (page_size, '') + ) def cookie(self): - if version.StrictVersion( - '2.4.0' - ) <= version.StrictVersion( - ldap.__version__ - ): - + if version.StrictVersion('2.4.0') <= version.StrictVersion(ldap.__version__): return self.cookie else: return self.controlValue[1] def size(self): - if version.StrictVersion( - '2.4.0' - ) <= version.StrictVersion( - ldap.__version__ - ): - + if version.StrictVersion('2.4.0') <= version.StrictVersion(ldap.__version__): return self.size else: return self.controlValue[0] + class LDAP(pykolab.base.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) self.ldap = None self.ldap_priv = None self.bind = None - if domain == None: + if domain is None: self.domain = conf.get('kolab', 'primary_domain') else: self.domain = domain 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") % ( - login[0], - realm - ), - level=8 - ) - except: + _("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, errmsg: + except Exception as errmsg: log.error(_("Authentication cache failed: %r") % (errmsg)) pass - if base_dn == None: + if base_dn is None: config_base_dn = self.config_get('base_dn') ldap_base_dn = self._kolab_domain_root_dn(self.domain) - if not ldap_base_dn == None and not ldap_base_dn == config_base_dn: + 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, errmsg: + except Exception as errmsg: log.error(_("Authentication cache failed: %r") % (errmsg)) pass try: user_filter = self.config_get_raw('user_filter') % ( - {'base_dn': base_dn} - ) + {'base_dn': base_dn} + ) - except TypeError, errmsg: + 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, errmsg: + except Exception as errmsg: log.error(_("Authentication cache failed: %r") % (errmsg)) pass retval = False + timeout = self.config_get('timeout', 10) if entry_dn is None: _search = self.ldap.search_ext( - base_dn, - ldap.SCOPE_SUBTREE, - _filter, - ['entrydn'] - ) + 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.SERVER_DOWN, errmsg: - log.error(_("LDAP server unavailable: %r") % (errmsg)) - log.error(_("%s") % (traceback.format_exc())) - self._disconnect() + _result_type, + _result_data, + _result_msgid, + _result_controls + ) = self.ldap.result3(_search) - return False - - except ldap.NO_SUCH_OBJECT: + except ldap.INVALID_CREDENTIALS: log.error( _("Invalid DN, username and/or password for '%s'.") % ( bind_dn ) ) return False - except ldap.INVALID_CREDENTIALS: + except ldap.NO_SUCH_OBJECT: log.error( _("Invalid DN, username and/or password for '%s'.") % ( bind_dn ) ) return False - except Exception, errmsg: + except ldap.SERVER_DOWN as errmsg: + log.error(_("LDAP server unavailable: %r") % (errmsg)) + log.error(_("%s") % (traceback.format_exc())) + self._disconnect() + + return False + + except ldap.TIMEOUT: + log.error(_("LDAP timeout.")) + self._disconnect() + + return False + + except Exception as errmsg: log.error(_("Exception occurred: %r") % (errmsg)) log.error(_("%s") % (traceback.format_exc())) self._disconnect() return False log.debug( - _("Length of entries found: %r") % ( - len(_result_data) - ), - level=8 - ) + _("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] elif len(_result_data) > 1: try: log.info( - _("Authentication for %r failed " + - "(multiple entries)") % ( - login[0] - ) + _("Authentication for %r failed (multiple entries)") % ( + login[0] ) - except: + ) + + except Exception: pass self._disconnect() return False else: try: log.info( - _("Authentication for %r failed (no entry)") % ( - login[0] - ) + _("Authentication for %r failed (no entry)") % ( + login[0] ) - except: + ) + + except Exception: pass self._disconnect() return False if entry_dn is None: try: log.info( - _("Authentication for %r failed (LDAP error?)") % ( - login[0] - ) + _("Authentication for %r failed (LDAP error?)") % ( + login[0] ) - except: + ) + + 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") % ( - login[0] - ) + _("Authentication for %r succeeded") % ( + login[0] ) + ) - except: + except Exception: pass else: try: log.info( - _("Authentication for %r failed (error)") % ( - login[0] - ) + _("Authentication for %r failed (error)") % ( + login[0] ) - except: + ) + + except Exception: pass self._disconnect() return False try: auth_cache.set_entry(_filter, entry_dn) - except Exception, errmsg: + except Exception as errmsg: log.error(_("Authentication cache failed: %r") % (errmsg)) pass - except ldap.SERVER_DOWN, errmsg: + except ldap.SERVER_DOWN: log.error(_("Authentication failed, LDAP server unavailable")) self._disconnect() return False - except Exception, errmsg: + except Exception: try: log.debug( - _("Failed to authenticate as user %r") % ( - login[0] - ), - level=8 - ) - except: + _("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])) else: log.info( - _("Authentication for %r failed (password)") % ( - login[0] - ) + _("Authentication for %r failed (password)") % ( + login[0] ) + ) self._disconnect() return False - except ldap.NO_SUCH_OBJECT, errmsg: + except ldap.NO_SUCH_OBJECT as errmsg: log.debug( - _("Error occured, there is no such object: %r") % ( - errmsg - ), - level=8 - ) + _("Error occured, there is no such object: %r") % ( + errmsg + ), + level=8 + ) self.bind = None try: auth_cache.del_entry(_filter) - except: + except Exception: log.error(_("Authentication cache failed to clear entry")) pass retval = self.authenticate(login, realm) - except Exception, errmsg: - log.debug(_("Exception occured: %r") %(errmsg)) + except Exception as errmsg: + log.debug(_("Exception occured: %r") % (errmsg)) try: log.debug( - _("Failed to authenticate as user %r") % ( - login[0] - ), - level=8 - ) + _("Failed to authenticate as user %r") % ( + login[0] + ), + level=8 + ) - except: + 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) uri = self.config_get('ldap_uri') log.debug(_("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 - ) + 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 not ldap_base_dn == None and not ldap_base_dn == config_base_dn: + 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'] - ) + base_dn, + ldap.SCOPE_SUBTREE, + _filter, + ['entrydn'] + ) ( - _result_type, - _result_data, - _result_msgid, - _result_controls - ) = self.ldap.result3(_search) + _result_type, + _result_data, + _result_msgid, + _result_controls + ) = self.ldap.result3(_search) if len(_result_data) >= 1: (entry_dn, entry_attrs) = _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 entry_attrs.has_key(attribute): + if attribute in entry_attrs: return entry_attrs[attribute] - elif entry_attrs.has_key(attribute.lower()): + elif attribute.lower() in entry_attrs: return entry_attrs[attribute.lower()] else: 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) entry_dn = self.entry_dn(entry_id) log.debug(_("Entry DN: %r") % (entry_dn), level=8) log.debug( - _("ldap search: (%r, %r, filterstr='(objectclass=*)', attrlist=[ 'dn' ] + %r") % ( - entry_dn, - ldap.SCOPE_BASE, - attributes - ), - level=8 - ) - - _search = self.ldap.search_ext( + _("ldap search: (%r, %r, filterstr='(objectclass=*)', attrlist=[ 'dn' ] + %r") % ( entry_dn, ldap.SCOPE_BASE, - filterstr='(objectclass=*)', - attrlist=[ 'dn' ] + attributes - ) + 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) + _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 [] 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 entry.has_key(attr): + if attr in entry: if isinstance(entry[attr], list): recipient_addresses.extend(entry[attr]) elif isinstance(entry[attr], basestring): 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 == None: + 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; - _delegator['_mailbox_basename'] = _delegator[mailbox_attribute] if _delegator.has_key(mailbox_attribute) else None + _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 not exclude_entry_id == None: + if exclude_entry_id is not None: __filter_prefix = "(&" __filter_suffix = "(!(%s=%s)))" % ( - self.config_get('unique_attribute'), - exclude_entry_id - ) + self.config_get('unique_attribute'), + exclude_entry_id + ) else: __filter_prefix = "" __filter_suffix = "" resource_filter = self.config_get('resource_filter') - if not resource_filter == None: + 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): _filter += "(kolabTargetFolder=%s)" % (folder) else: for _folder in folder: _filter += "(kolabTargetFolder=%s)" % (_folder) _filter += ")" - _filter = "%s%s%s" % (__filter_prefix,_filter,__filter_suffix) + _filter = "%s%s%s" % (__filter_prefix, _filter, __filter_suffix) log.debug(_("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 not ldap_base_dn == None and not ldap_base_dn == config_base_dn: + 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 - ) + 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 not exclude_entry_id == None: + 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) - ) + self.config_get('unique_attribute'), + ldap.filter.escape_filter_chars(exclude_entry_id) + ) else: __filter_prefix = "" __filter_suffix = "" - kolab_filter = self._kolab_filter() - 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): _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) + _filter = "%s%s%s" % (__filter_prefix, _filter, __filter_suffix) log.debug(_("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 not ldap_base_dn == None and not ldap_base_dn == config_base_dn: + 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 - ) + 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 not _entry_id == None: + 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 not exclude_entry_id == None: + 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) - ) + 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 not resource_filter == None: + 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): _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) + _filter = "%s%s%s" % (__filter_prefix, _filter, __filter_suffix) log.debug(_("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 not ldap_base_dn == None and not ldap_base_dn == config_base_dn: + 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 - ) + 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) return timestamp def list_secondary_domains(self): """ List alias domain name spaces for the current domain name space. """ - if not self.domains == None: - return [s for s in self.domains.keys() if not s in self.domains.values()] + if self.domains is not None: + return [s for s in self.domains.keys() if s not in self.domains.values()] else: 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 not daemon_rcpt_policy == None: + 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)" - ) % (entry_dn), - level=1 - ) + _("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) # 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 == None and entry_type == 'user': + if primary_mail is None and entry_type == 'user': primary_mail = self.config_get_raw('primary_mail') - if not secondary_mail_attribute == None: + if secondary_mail_attribute is not None: secondary_mail = self.config_get_raw('%s_secondary_mail' % (entry_type)) - if secondary_mail == None and entry_type == 'user': + 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") % ( - mail_attributes, - primary_mail_attribute, - secondary_mail_attribute - ), - level=8 - ) + _("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 not entry.has_key(_mail_attr): + if mail_attr not in entry: log.debug(_("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) - if not primary_mail == None: + if primary_mail is not None: log.debug(_("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) - if not secondary_mail == None: + if secondary_mail is not None: log.debug(_("sec. mail pol. is not empty"), level=8) want_attrs.append(_mail_attr) if len(want_attrs) > 0: - log.debug(_("Attributes %r are not yet available for entry %r") % ( - want_attrs, - entry_dn - ), - level=8 - ) + log.debug( + _("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 not entry.has_key('preferredlanguage'): + 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: - log.debug(_("Attributes %r are not yet available for entry %r") % ( - want_attrs, - entry_dn - ), - level=8 - ) + log.debug( + _("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 not entry.has_key('preferredlanguage'): + if 'preferredlanguage' not in entry: entry['preferredlanguage'] = conf.get('kolab', 'default_locale') # Primary mail address - if not primary_mail == None: + 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 - } - ) + "set_primary_mail", + kw={ + 'primary_mail': primary_mail, + 'entry': entry, + 'primary_domain': self.domain + } + ) - if primary_mail_address == None: + 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: log.debug( - _("No results for mail address %s found") % ( - _primary_mail - ), - level=8 - ) + _("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") % ( - _primary_mail - ), - level=8 - ) + _("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( - _("Too bad, primary email address %s " + \ - "already in use for %s (we are %s)") % ( - _primary_mail, - result, - entry_dn - ), - level=8 - ) + _( + "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) 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.split('@')[0], + i, + primary_mail_address.split('@')[1] + ) primary_mail_address = _primary_mail ### - ### FIXME + # FIXME ### - if not primary_mail_address == None: - if not entry.has_key(primary_mail_attribute): + 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) + self.set_entry_attribute( + entry, + primary_mail_attribute, + primary_mail_address + ) entry_modifications[primary_mail_attribute] = primary_mail_address - if not secondary_mail == None: + 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 + "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: log.debug( - _("No results for address %s found") % ( - __secondary_mail - ), - level=8 - ) + _("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...") % ( - __secondary_mail - ), - level=8 - ) + _("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( - _("Too bad, secondary email " + \ - "address %s already in use for " + \ - "%s (we are %s)") % ( - __secondary_mail, - result, - entry_dn - ), - level=8 - ) + _( + "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) if almost_done: done = True continue i += 1 __secondary_mail = "%s%d@%s" % ( - _secondary_mail.split('@')[0], - i, - _secondary_mail.split('@')[1] - ) + _secondary_mail.split('@')[0], + i, + _secondary_mail.split('@')[1] + ) secondary_mail_addresses.append(__secondary_mail) - log.debug(_("Recipient policy composed the following set of secondary " + \ - "email addresses: %r") % (secondary_mail_addresses), level=8) + log.debug( + _( + "Recipient policy composed the following set of secondary email addresses: %r" + ) % ( + secondary_mail_addresses + ), + level=8 + ) - if entry.has_key(secondary_mail_attribute): + 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 not secondary_mail_addresses == None: + if secondary_mail_addresses is not None: log.debug( - _("Secondary mail addresses that we want is not None: %r") % ( - secondary_mail_addresses - ), - level=8 - ) + _("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( - _("Avoiding the duplication of the primary mail " + \ - "address %r in the list of secondary mail " + \ - "addresses") % (primary_mail_address), - level=8 - ) + _( + "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) - ) + secondary_mail_addresses.index(primary_mail_address) + ) log.debug( - _("Entry is getting secondary mail addresses: %r") % ( - secondary_mail_addresses - ), - level=8 - ) + _("Entry is getting secondary mail addresses: %r") % ( + secondary_mail_addresses + ), + level=8 + ) - if not entry.has_key(secondary_mail_attribute): + if secondary_mail_attribute not in entry: log.debug( - _("Entry did not have any secondary mail " + \ - "addresses in %r") % (secondary_mail_attribute), - level=8 - ) + _("Entry did not have any secondary mail addresses in %r") % ( + secondary_mail_attribute + ), + level=8 + ) if not len(secondary_mail_addresses) == 0: self.set_entry_attribute( - entry, - secondary_mail_attribute, - secondary_mail_addresses - ) + entry, + secondary_mail_attribute, + secondary_mail_addresses + ) entry_modifications[secondary_mail_attribute] = secondary_mail_addresses else: if isinstance(entry[secondary_mail_attribute], basestring): entry[secondary_mail_attribute] = [entry[secondary_mail_attribute]] - log.debug(_("secondary_mail_addresses: %r") % (secondary_mail_addresses), level=8) - log.debug(_("entry[%s]: %r") % (secondary_mail_attribute,entry[secondary_mail_attribute]), level=8) + log.debug( + _("secondary_mail_addresses: %r") % (secondary_mail_addresses), + level=8 + ) + + log.debug( + _("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), level=8) - log.debug(_("entry[%s]: %r") % (secondary_mail_attribute,entry[secondary_mail_attribute]), level=8) + log.debug( + _("secondary_mail_addresses: %r") % (secondary_mail_addresses), + level=8 + ) - if not list(set(secondary_mail_addresses)) == list(set(entry[secondary_mail_attribute])): + log.debug( + _("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])): self.set_entry_attribute( - entry, - secondary_mail_attribute, - list(set(secondary_mail_addresses)) - ) + entry, + secondary_mail_attribute, + smas + ) - entry_modifications[secondary_mail_attribute] = list(set(secondary_mail_addresses)) + entry_modifications[secondary_mail_attribute] = smas log.debug(_("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 not ldap_base_dn == None and not ldap_base_dn == config_base_dn: + 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' - ) + 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), level=8) - self.set_entry_attributes(entry_id, { attribute: value }) + log.debug( + _("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(): - if not entry.has_key(attribute): + if attribute not in entry: entry[attribute] = self.get_entry_attribute(entry_id, attribute) for attribute in attrs.keys(): - if entry.has_key(attribute) and entry[attribute] == None: + if attribute in entry and entry[attribute] is None: modlist.append((ldap.MOD_ADD, attribute, attrs[attribute])) - elif entry.has_key(attribute) and not entry[attribute] == None: - if attrs[attribute] == None: + elif attribute in entry and entry[attribute] is not None: + if attrs[attribute] is None: modlist.append((ldap.MOD_DELETE, attribute, entry[attribute])) else: modlist.append((ldap.MOD_REPLACE, attribute, attrs[attribute])) dn = entry_dn if len(modlist) > 0 and self._bind_priv() is True: try: self.ldap_priv.modify_s(dn, modlist) - except Exception, errmsg: - log.error(_("Could not update dn:\nDN: %r\nModlist: %r\nError Message: %r") % (dn, modlist, errmsg)) + except Exception as errmsg: + log.error( + _("Could not update dn:\nDN: %r\nModlist: %r\nError Message: %r") % ( + dn, + modlist, + errmsg + ) + ) + import traceback log.error("%s" % (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, 01, 01, 00, 00, 00).strftime( + 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) if not 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) - if callback == None: + 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, errmsg: + 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())) def user_quota(self, entry_id, folder): default_quota = self.config_get('default_quota') quota_attribute = self.config_get('quota_attribute') - if quota_attribute == None: + if quota_attribute is None: return # The default quota may be None, but LDAP quota could still be set - if default_quota == None: + 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 == None: + if _imap_quota is None: used = None current_imap_quota = None else: (used, current_imap_quota) = _imap_quota log.debug( - _("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 - ) + _( + "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 - } - ) + 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: + except Exception: current_ldap_quota = None # If the new quota is zero, get out if new_quota == 0: return - if not current_ldap_quota == None: + 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) - ) + entry_dn, + quota_attribute, + "%s" % (new_quota) + ) else: - if not new_quota == None: + if new_quota is not None: self.set_entry_attribute( - entry_dn, - quota_attribute, - "%s" % (new_quota) - ) + entry_dn, + quota_attribute, + "%s" % (new_quota) + ) - if not current_imap_quota == None: + if current_imap_quota is not None: if not new_quota == current_imap_quota: self.imap.set_quota(folder, new_quota) else: - if not new_quota == None: + if new_quota is not None: self.imap.set_quota(folder, new_quota) ### - ### API depth level increasing! + # 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")) return False # and the same vice-versa - if bind_dn is not None and bind_pw is None: + if bind_dn is None and bind_pw is not None: log.error(_("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") % ( 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, errmsg: + except ldap.SERVER_DOWN as errmsg: log.error(_("LDAP server unavailable: %r") % (errmsg)) log.error(_("%s") % (traceback.format_exc())) return False except ldap.NO_SUCH_OBJECT: log.error( _("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'.") % ( bind_dn ) ) return False else: log.debug(_("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, errmsg: + except ldap.SERVER_DOWN as errmsg: log.error(_("LDAP server unavailable: %r") % (errmsg)) log.error(_("%s") % (traceback.format_exc())) return False except ldap.INVALID_CREDENTIALS: log.error( _("Invalid DN, username and/or password for '%s'.") % ( bind_dn ) ) return False else: log.debug(_("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 entry.has_key(mailserver_attribute): + if mailserver_attribute in entry: server = entry[mailserver_attribute] foldertype_attribute = self.config_get('sharedfolder_type_attribute') - if not foldertype_attribute == None: - if not entry.has_key(foldertype_attribute): + if foldertype_attribute is not None: + if foldertype_attribute not in entry: entry[foldertype_attribute] = self.get_entry_attribute( - entry['id'], - foldertype_attribute - ) + entry['id'], + foldertype_attribute + ) - if not entry[foldertype_attribute] == None: + if entry[foldertype_attribute] is not None: entry['kolabfoldertype'] = entry[foldertype_attribute] - if not entry.has_key('kolabfoldertype'): + if 'kolabfoldertype' not in entry: entry['kolabfoldertype'] = self.get_entry_attribute( - entry['id'], - 'kolabfoldertype' - ) + entry['id'], + 'kolabfoldertype' + ) # A delivery address is postuser+targetfolder delivery_address_attribute = self.config_get('sharedfolder_delivery_address_attribute') - if delivery_address_attribute == None: + if delivery_address_attribute is None: delivery_address_attribute = 'mail' - if not entry.has_key(delivery_address_attribute): + if delivery_address_attribute not in entry: entry[delivery_address_attribute] = self.get_entry_attribute( - entry['id'], - delivery_address_attribute - ) + entry['id'], + delivery_address_attribute + ) - if not entry[delivery_address_attribute] == None: + 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 not entry.has_key('kolabtargetfolder'): - entry['kolabtargetfolder'] = self.get_entry_attribute( - entry['id'], - 'kolabtargetfolder' - ) - - if entry.has_key('kolabtargetfolder') and \ - not entry['kolabtargetfolder'] == None: + 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 == None: + if folderacl_entry_attribute is None: folderacl_entry_attribute = 'acl' - if not entry.has_key(folderacl_entry_attribute): + if folderacl_entry_attribute not in entry: entry[folderacl_entry_attribute] = self.get_entry_attribute( - entry['id'], - folderacl_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 entry.has_key('kolabfoldertype') and \ - not entry['kolabfoldertype'] == None: + if 'kolabfoldertype' in entry and entry['kolabfoldertype'] is not None: - self.imap.shared_folder_set_type( - folder_path, - entry['kolabfoldertype'] - ) + 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 - ) + self.imap._set_kolab_mailfolder_acls(entry['kolabfolderaclentry'], folder_path) - if entry.has_key(delivery_address_attribute) and \ - not entry[delivery_address_attribute] == None: - self.imap.set_acl(folder_path, 'anyone', '+p') + if delivery_address_attribute in entry: + if entry[delivery_address_attribute] is not None: + self.imap.set_acl(folder_path, 'anyone', '+p') - #if server == None: - #self.entry_set_attribute(mailserver_attribute, server) + # 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. """ result_attribute = conf.get('cyrus-sasl', 'result_attribute') - if not entry.has_key(result_attribute): + if result_attribute not in entry: return None - if entry[result_attribute] == None: + if entry[result_attribute] is None: return None - for _type in ['user','group','role','sharedfolder']: + for _type in ['user', 'group', 'role', 'sharedfolder']: try: eval("self._change_add_%s(entry, change)" % (_type)) success = True - except: + except Exception: success = False if success: break def _change_add_user(self, entry, change): """ An entry of type user was added. """ mailserver_attribute = self.config_get('mailserver_attribute') - if mailserver_attribute == None: + if mailserver_attribute is None: mailserver_attribute = 'mailhost' mailserver_attribute = mailserver_attribute.lower() result_attribute = conf.get('cyrus-sasl', 'result_attribute') - if result_attribute == None: + if result_attribute is None: result_attribute = 'mail' result_attribute = result_attribute.lower() - if not entry.has_key(mailserver_attribute): + 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 not entry.has_key(result_attribute): + if result_attribute not in entry: return - if entry[result_attribute] == None: + 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] - ) + entry[result_attribute], + entry[mailserver_attribute] + ) else: - folder = "user%s%s" % (self.imap.get_separator(),entry[result_attribute].lower()) + 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") % ( - mailserver_attribute, - entry[mailserver_attribute] - ), - level=8 - ) + _("Entry %s attribute value: %r") % ( + mailserver_attribute, + entry[mailserver_attribute] + ), + level=8 + ) log.debug( - _("imap.user_mailbox_server(%r) result: %r") % ( - folder, - server - ), - level=8 - ) + _("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 not entry.has_key(result_attribute): + if result_attribute not in entry: return None - if entry[result_attribute] == None: + if entry[result_attribute] is None: return None 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 not entry.has_key(result_attribute): + if result_attribute not in entry: return None - if entry[result_attribute] == None: + if entry[result_attribute] is None: return None success = True - for _type in ['user','group','resource','role','sharedfolder']: + for _type in ['user', 'group', 'resource', 'role', 'sharedfolder']: try: success = eval("self._change_delete_%s(entry, change)" % (_type)) - except Exception, errmsg: + except Exception as errmsg: log.error(_("An error occured: %r") % (errmsg)) log.error(_("%s") % (traceback.format_exc())) success = False if success: break def _change_delete_user(self, entry, change): """ An entry of type user was deleted. """ result_attribute = conf.get('cyrus-sasl', 'result_attribute') - if not entry.has_key(result_attribute): + if result_attribute not in entry: return None - if entry[result_attribute] == 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 = { + kw={ 'user': entry, 'domain': self.domain } ) 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] result_attribute = conf.get('cyrus-sasl', 'result_attribute') old_canon_attr = None cache_entry = cache.get_entry(self.domain, entry) - if not cache_entry == None: + 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] - if not entry.has_key(result_attribute): + if result_attribute not in entry: return - if entry[result_attribute] == None: + 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 entry_changes.has_key(result_attribute): - if not old_canon_attr == None: - self.imap.user_mailbox_create( - entry_changes[result_attribute] - ) + 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] - ) + 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 entry.has_key(mailserver_attribute): + if mailserver_attribute in entry: server = entry[mailserver_attribute] foldertype_attribute = self.config_get('sharedfolder_type_attribute') - if not foldertype_attribute == None: - if not entry.has_key(foldertype_attribute): + if foldertype_attribute is not None: + if foldertype_attribute not in entry: entry[foldertype_attribute] = self.get_entry_attribute( - entry['id'], - foldertype_attribute - ) + entry['id'], + foldertype_attribute + ) - if not entry[foldertype_attribute] == None: + if entry[foldertype_attribute] is not None: entry['kolabfoldertype'] = entry[foldertype_attribute] - if not entry.has_key('kolabfoldertype'): + if 'kolabfoldertype' not in entry: entry['kolabfoldertype'] = self.get_entry_attribute( - entry['id'], - 'kolabfoldertype' - ) + entry['id'], + 'kolabfoldertype' + ) # A delivery address is postuser+targetfolder delivery_address_attribute = self.config_get('sharedfolder_delivery_address_attribute') - if not delivery_address_attribute == None: - if not entry.has_key(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 - ) + entry['id'], + delivery_address_attribute + ) - if not entry[delivery_address_attribute] == None: + 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 not entry.has_key('kolabtargetfolder'): + if 'kolabtargetfolder' not in entry: entry['kolabtargetfolder'] = self.get_entry_attribute( - entry['id'], - 'kolabtargetfolder' - ) - - if entry.has_key('kolabtargetfolder') and \ - not entry['kolabtargetfolder'] == None: + entry['id'], + 'kolabtargetfolder' + ) - folder_path = entry['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 == None: + if folderacl_entry_attribute is None: folderacl_entry_attribute = 'acl' - if not entry.has_key(folderacl_entry_attribute): + if folderacl_entry_attribute not in entry: entry[folderacl_entry_attribute] = self.get_entry_attribute( - entry['id'], - folderacl_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 entry.has_key('kolabfoldertype') and \ - not entry['kolabfoldertype'] == None: - + if 'kolabfoldertype' in entry and entry['kolabfoldertype'] is not None: self.imap.shared_folder_set_type( - folder_path, - entry['kolabfoldertype'] - ) + folder_path, + entry['kolabfoldertype'] + ) entry['kolabfolderaclentry'] = self._parse_acl(entry[folderacl_entry_attribute]) self.imap._set_kolab_mailfolder_acls( - entry['kolabfolderaclentry'], folder_path, True - ) + entry['kolabfolderaclentry'], folder_path, True + ) - if entry.has_key(delivery_address_attribute) and \ - not entry[delivery_address_attribute] == None: + 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') + 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 not _entry == None: - if _entry.__dict__.has_key('result_attribute') and not _entry.result_attribute == '': + 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), - level=8 - ) + _("Result from recipient policy: %r") % (entry_changes), + level=8 + ) - if entry_changes.has_key(result_attribute): + if result_attribute in entry_changes: if not entry_changes[result_attribute] == old_canon_attr: - if old_canon_attr == None: + if old_canon_attr is None: self.imap.user_mailbox_create( - entry_changes[result_attribute] - ) + entry_changes[result_attribute] + ) else: self.imap.user_mailbox_rename( - old_canon_attr, - entry_changes[result_attribute] - ) + old_canon_attr, + entry_changes[result_attribute] + ) entry[result_attribute] = entry_changes[result_attribute] cache.get_entry(self.domain, entry) - elif entry.has_key(result_attribute): + elif result_attribute in entry: if not entry[result_attribute] == old_canon_attr: - if old_canon_attr == None: + if old_canon_attr is None: self.imap.user_mailbox_create( - entry[result_attribute] - ) + entry[result_attribute] + ) else: self.imap.user_mailbox_rename( - old_canon_attr, - entry[result_attribute] - ) + 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] ) + + 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] - } - ) + '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 entry.has_key(mailserver_attribute): + if mailserver_attribute in entry: server = entry[mailserver_attribute] - if not entry.has_key('kolabtargetfolder'): + if 'kolabtargetfolder' not in entry: entry['kolabtargetfolder'] = self.get_entry_attribute( - entry['id'], - 'kolabtargetfolder' - ) + entry['id'], + 'kolabtargetfolder' + ) - if not entry.has_key('kolabfoldertype'): + if 'kolabfoldertype' not in entry: entry['kolabfoldertype'] = self.get_entry_attribute( - entry['id'], - 'kolabfoldertype' - ) + entry['id'], + 'kolabfoldertype' + ) folderacl_entry_attribute = conf.get('ldap', 'sharedfolder_acl_entry_attribute') - if folderacl_entry_attribute == None: + if folderacl_entry_attribute is None: folderacl_entry_attribute = 'acl' - if not entry.has_key(folderacl_entry_attribute): + if folderacl_entry_attribute not in entry: entry['kolabfolderaclentry'] = self.get_entry_attribute( - entry['id'], - folderacl_entry_attribute - ) + entry['id'], + folderacl_entry_attribute + ) else: entry['kolabfolderaclentry'] = entry[folderacl_entry_attribute] del entry[folderacl_entry_attribute] - if entry.has_key('kolabtargetfolder') and \ - not entry['kolabtargetfolder'] == None: + 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 entry.has_key('kolabfoldertype') and \ - not entry['kolabfoldertype'] == None: + if 'kolabfoldertype' in entry and entry['kolabfoldertype'] is not None: self.imap.shared_folder_set_type( - folder_path, - entry['kolabfoldertype'] - ) + folder_path, + entry['kolabfoldertype'] + ) entry['kolabfolderaclentry'] = self._parse_acl(entry['kolabfolderaclentry']) self.imap._set_kolab_mailfolder_acls( - entry['kolabfolderaclentry'], folder_path, True - ) + entry['kolabfolderaclentry'], folder_path, True + ) delivery_address_attribute = self.config_get('sharedfolder_delivery_address_attribute') - if entry.has_key(delivery_address_attribute) and \ - not entry[delivery_address_attribute] == None: + if delivery_address_attribute in entry and \ + entry[delivery_address_attribute] is not None: self.imap.set_acl(folder_path, 'anyone', '+p') - #if server == None: - #self.entry_set_attribute(mailserver_attribute, server) + # 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 == None: + if mailserver_attribute is None: mailserver_attribute = 'mailhost' mailserver_attribute = mailserver_attribute.lower() result_attribute = conf.get('cyrus-sasl', 'result_attribute') - if result_attribute == None: + 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 not _entry == None and _entry.__dict__.has_key('result_attribute') and not _entry.result_attribute == '': + 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 entry.has_key(result_attribute) and entry_changes.has_key(result_attribute): + 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), - level=8 - ) + _("Result from recipient policy: %r") % (entry_changes), + level=8 + ) - if entry_changes.has_key(result_attribute) and not old_canon_attr == None: + 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] - ) + 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 not entry.has_key(mailserver_attribute): + if mailserver_attribute not in entry: entry[mailserver_attribute] = self.get_entry_attribute(entry, mailserver_attribute) - if entry[mailserver_attribute] == "" or entry[mailserver_attribute] == None: + if entry[mailserver_attribute] == "" or entry[mailserver_attribute] is None: server = None else: server = entry[mailserver_attribute].lower() - if entry.has_key(result_attribute) and \ - not entry.has_key(result_attribute) == None: - + 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] - ) + 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 not entry.has_key(mailserver_attr): + 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") % ( - entry['id'], - result_attribute - ) + _("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): - naming_contexts = [ naming_contexts ] + naming_contexts = [naming_contexts] log.debug( - _("Naming contexts found: %r") % (naming_contexts), - level=8 - ) + _("Naming contexts found: %r") % (naming_contexts), + level=8 + ) self._kolab_domain_root_dn(domain) log.debug( - _("Domains/Root DNs found: %r") % ( - self.domain_rootdns - ), - level=8 - ) + _("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 value.has_key('dn'): + 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): return False try: import ldap.dn - ldap_dn = ldap.dn.explode_dn(value) + ldap.dn.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 not ldap_base_dn == None and not ldap_base_dn == config_base_dn: + 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 == None: + if __filter is None: __filter = self.config_get('%s_filter' % (_type)) - if not __filter == None: + if __filter is not None: try: result = self._regular_search(entry_dn, filterstr=__filter) - except: + except Exception: result = self._regular_search( - base_dn, - filterstr="(%s=%s)" %( - self.config_get('unique_attribute'), - entry_id['id']) - ) + base_dn, + filterstr="(%s=%s)" % ( + self.config_get('unique_attribute'), + entry_id['id'] + ) + ) if not result: continue else: return _type 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 '' - domain_root_dn = self._kolab_domain_root_dn(self.domain) user_base_dn = self.config_get(conf_prefix + 'user_base_dn') - if user_base_dn == None: + 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 = [ '(|' ] + 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)' % (auth_attr, login)) + if '@' not in login: auth_search_filter.append( - '(%s=%s@%s)' % ( - auth_attr, - login, - self.domain - ) + '(%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 - ) + auth_search_filter, + user_filter + ) _results = self._search( - user_base_dn, - filterstr=search_filter, - attrlist=[ 'dn' ], - override_search='_regular_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' ] - ) + 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) if not hasattr(self, 'domain_rootdns'): self.domain_rootdns = {} - if self.domain_rootdns.has_key(domain): + if domain in self.domain_rootdns: log.debug(_("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) domain_base_dn = conf.get('ldap', 'domain_base_dn', quiet=True) domain_filter = conf.get('ldap', 'domain_filter') - if not domain_filter == None: - if not domain == None: + 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' - ) - - domains = [] + 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' - ) + 'ldap', + 'domain_rootdn_attribute' + ) _domain_attrs = utils.normalize(_domain_attrs) - if _domain_attrs.has_key(domain_rootdn_attribute): - log.debug(_("Setting domain root dn from LDAP for domain %r: %r") % (domain, _domain_attrs[domain_rootdn_attribute]), level=8) + if domain_rootdn_attribute in _domain_attrs: + log.debug( + _("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 == None: + 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 == None: + if __filter is None: __filter = self.config_get('%s_filter' % (_type)) - if not __filter == None: - _filter = "%s%s" % (_filter,__filter) + 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) 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 not domain == None: + if domain is not None: domain_filter = domain_filter.replace('*', domain) - if domain_base_dn == None or domain_filter == None: + if domain_base_dn is None or domain_filter is None: return [] dna = self.config_get('domain_name_attribute') - if dna == None: + 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: + 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)) + 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 - ) + "auth.ldap.LDAP._synchronize_callback(args %r, kw %r)" % ( + args, + kw + ), + level=8 + ) # Typical for Persistent Change Control EntryChangeNotification - if kw.has_key('change_type'): - - log.debug(_("change_type defined, typical for Persistent Change Control EntryChangeNotification"), level=5) - - change_type = None + if 'change_type' in kw: + log.debug( + _( + "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'] - } + '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: + except Exception: entry['type'] = None log.debug(_("Entry type: %s") % (entry['type']), level=8) - if change_dict['change_type'] == None: + 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 not entry.has_key(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'] - ) + "self._change_%s_%s(entry, change_dict)" % ( + change, + entry['type'] ) + ) # Typical for Paged Results Control - elif kw.has_key('entry') and isinstance(kw['entry'], list): - + elif 'entry' in kw and isinstance(kw['entry'], list): log.debug(_("No change_type, typical for Paged Results Control"), level=5) - for entry_dn,entry_attrs in kw['entry']: + for entry_dn, entry_attrs in kw['entry']: # This is a referral - if entry_dn == None: + if entry_dn is None: continue - entry = { 'dn': entry_dn } + 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: + except Exception: entry['type'] = "unknown" log.debug(_("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) # # 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 + # 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=[] - ): - - _results = [] + 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( - criticality=True, - changeTypes=[ 'add', 'delete', 'modify', 'modDN' ], - changesOnly=False, - returnECs=True - ) + psearch_server_controls.append( + 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 - ) + 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} - ) + 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: + for dn, entry, srv_ctrls in res_data: log.debug(_("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 - ] + 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:"), - level=8 - ) + _("Entry Change Notification attributes:"), + level=8 + ) log.debug( - " " + _("Change Type: %r (%r)") % ( - change_type, - change_type_desc - ), - level=8 - ) + " " + _("Change Type: %r (%r)") % ( + change_type, + change_type_desc + ), + level=8 + ) log.debug( - " " + _("Previous DN: %r") % (previous_dn), - level=8 - ) + " " + _("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 - ) + 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=[] - ): + 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 - critical = True _results = [] server_page_control = SimplePagedResultsControl(page_size=page_size) _search = self.ldap.search_ext( - base_dn, - scope=scope, - filterstr=filterstr, - attrlist=attrlist, - attrsonly=attrsonly, - serverctrls=[server_page_control] - ) + 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) + _result_type, + _result_data, + _result_msgid, + _result_controls + ) = self.ldap.result3(_search) - except ldap.NO_SUCH_OBJECT, e: + except ldap.NO_SUCH_OBJECT: log.warning( - _("Object %s searched no longer exists") % (base_dn) - ) + _("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))) pctrls = [ - c for c in _result_controls - if c.controlType == LDAP_CONTROL_PAGED_RESULTS - ] + 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] - ) + 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." + 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=[] - ): + 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=[] - ): + 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 - ) + '/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, - ) + 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=-1, - callback=False, - primary_domain=None, - secondary_domains=[] - ): + 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) log.debug(_("Searching with filter %r") % (filterstr), level=8) _search = self.ldap.search( - base_dn, - scope=scope, - filterstr=filterstr, - attrlist=attrlist, - attrsonly=attrsonly - ) + base_dn, + scope=scope, + filterstr=filterstr, + attrlist=attrlist, + attrsonly=attrsonly, + timeout=timeout + ) _results = [] _result_type = None while not _result_type == ldap.RES_SEARCH_RESULT: (_result_type, _result) = self.ldap.result(_search, False, 0) - if not _result == None: + 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=-1, - override_search=False, - callback=False, - primary_domain=None, - secondary_domains=[] - ): + 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', 10) + supported_controls = conf.get_list('ldap', 'supported_controls') - if not supported_controls == None and not len(supported_controls) < 1: + 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'] - ) + 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") % ( - SUPPORTED_LDAP_CONTROLS[control_num]['desc'], - self.domain - ), - level=8 - ) + _("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'] - ) + '', + scope=ldap.SCOPE_BASE, + attrlist=['supportedControl'] + ) - for (_result,_supported_controls) in _search: + 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") % ( - SUPPORTED_LDAP_CONTROLS[control_num]['desc'], - ), - level=8 - ) + log.debug( + _("Found support for %s") % ( + SUPPORTED_LDAP_CONTROLS[control_num]['desc'], + ), + level=8 + ) self.ldap.supported_controls.append( - SUPPORTED_LDAP_CONTROLS[control_num]['func'] - ) + SUPPORTED_LDAP_CONTROLS[control_num]['func'] + ) _results = [] - if not override_search == False: - _use_ldap_controls = [ override_search ] + 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( + 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 - ) + supported_control, + base_dn, + scope, + filterstr, + attrlist, + attrsonly, + timeout, + primary_domain, + secondary_domains ) + ) break - except ldap.SERVER_DOWN, errmsg: + 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.")) + self._disconnect() + + time.sleep(10) + self.reconnect() + + except ldap.TIMEOUT: + log.error(_("LDAP timeout in searching for '%s'") % (filterstr)) + + self._disconnect() + time.sleep(10) self.reconnect() - except Exception, errmsg: + except Exception as errmsg: failed_ok = True log.error(_("An error occured using %s: %r") % (supported_control, errmsg)) log.error(_("%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 ] + 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/conf/__init__.py b/pykolab/conf/__init__.py index 9ea503b..64dfbe5 100644 --- a/pykolab/conf/__init__.py +++ b/pykolab/conf/__init__.py @@ -1,683 +1,794 @@ # -*- 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 logging import os import sys from optparse import OptionParser from ConfigParser import SafeConfigParser import pykolab from pykolab.conf.defaults import Defaults from pykolab.constants import * from pykolab.translate import _ log = pykolab.getLogger('pykolab.conf') + class Conf(object): def __init__(self): """ self.cli_args == Arguments passed on the CLI self.cli_keywords == Parser results (again, CLI) self.cli_parser == The actual Parser (from OptionParser) self.plugins == Our Kolab Plugins """ self.cli_parser = None self.cli_args = None self.cli_keywords = None self.entitlement = None self.changelog = {} try: from pykolab.conf.entitlement import Entitlement entitlements = True - except: + except Exception: entitlements = False pass if entitlements: self.entitlement = Entitlement().get() self.plugins = None # The location where our configuration parser is going to end up self.cfg_parser = None # Create the options self.create_options() - def finalize_conf(self,fatal=True): + def finalize_conf(self, fatal=True): self.create_options_from_plugins() self.parse_options(fatal=fatal) # The defaults can some from; # - a file we ship with the packages # - a customly supplied file (by customer) # - a file we write out # - this python class # # Look, we want defaults self.defaults = Defaults(self.plugins) # But, they should be available in our class as well for option in self.defaults.__dict__.keys(): - log.debug(_("Setting %s to %r (from defaults)") % (option, self.defaults.__dict__[option]), level=8) - setattr(self,option,self.defaults.__dict__[option]) + log.debug( + _("Setting %s to %r (from defaults)") % ( + option, + self.defaults.__dict__[option] + ), + level=8 + ) + + setattr(self, option, self.defaults.__dict__[option]) # This is where we check our parser for the defaults being set there. self.set_defaults_from_cli_options() self.options_set_from_config() # Also set the cli options - if hasattr(self,'cli_keywords') and not self.cli_keywords == None: + if hasattr(self, 'cli_keywords') and self.cli_keywords is not None: for option in self.cli_keywords.__dict__.keys(): retval = False if hasattr(self, "check_setting_%s" % (option)): - exec("retval = self.check_setting_%s(%r)" % (option, self.cli_keywords.__dict__[option])) - - # The warning, error or confirmation dialog is in the check_setting_%s() function + exec( + "retval = self.check_setting_%s(%r)" % ( + option, + self.cli_keywords.__dict__[option] + ) + ) + + # The warning, error or confirmation dialog is in the check_setting_%s() + # function if not retval: continue - log.debug(_("Setting %s to %r (from CLI, verified)") % (option, self.cli_keywords.__dict__[option]), level=8) - setattr(self,option,self.cli_keywords.__dict__[option]) + log.debug( + _("Setting %s to %r (from CLI, verified)") % ( + option, + self.cli_keywords.__dict__[option] + ), + level=8 + ) + + setattr(self, option, self.cli_keywords.__dict__[option]) else: - log.debug(_("Setting %s to %r (from CLI, not checked)") % (option, self.cli_keywords.__dict__[option]), level=8) - setattr(self,option,self.cli_keywords.__dict__[option]) + log.debug( + _("Setting %s to %r (from CLI, not checked)") % ( + option, + self.cli_keywords.__dict__[option] + ), + level=8 + ) + + setattr(self, option, self.cli_keywords.__dict__[option]) def load_config(self, config): """ Given a SafeConfigParser instance, loads a configuration file and checks, then sets everything it can find. """ for section in self.defaults.__dict__.keys(): if section == 'testing': continue if not config.has_section(section): continue for key in self.defaults.__dict__[section].keys(): retval = False if not config.has_option(section, key): continue if isinstance(self.defaults.__dict__[section][key], int): - value = config.getint(section,key) + value = config.getint(section, key) elif isinstance(self.defaults.__dict__[section][key], bool): - value = config.getboolean(section,key) + value = config.getboolean(section, key) elif isinstance(self.defaults.__dict__[section][key], str): - value = config.get(section,key) + value = config.get(section, key) elif isinstance(self.defaults.__dict__[section][key], list): - value = eval(config.get(section,key)) + value = eval(config.get(section, key)) elif isinstance(self.defaults.__dict__[section][key], dict): - value = eval(config.get(section,key)) + value = eval(config.get(section, key)) - if hasattr(self,"check_setting_%s_%s" % (section,key)): - exec("retval = self.check_setting_%s_%s(%r)" % (section,key,value)) + if hasattr(self, "check_setting_%s_%s" % (section, key)): + exec("retval = self.check_setting_%s_%s(%r)" % (section, key, value)) if not retval: # We just don't set it, check_setting_%s should have # taken care of the error messages continue if not self.defaults.__dict__[section][key] == value: if key.count('password') >= 1: - log.debug(_("Setting %s_%s to '****' (from configuration file)") % (section,key), level=8) + log.debug( + _("Setting %s_%s to '****' (from configuration file)") % ( + section, + key + ), + level=8 + ) + else: - log.debug(_("Setting %s_%s to %r (from configuration file)") % (section,key,value), level=8) - setattr(self,"%s_%s" % (section,key),value) + log.debug( + _("Setting %s_%s to %r (from configuration file)") % ( + section, + key, + value + ), + level=8 + ) + + setattr(self, "%s_%s" % (section, key), value) def options_set_from_config(self): """ Sets the default configuration options from a configuration file. Configuration file may be customized using the --config CLI option """ log.debug(_("Setting options from configuration file"), level=4) # Check from which configuration file we should get the defaults # Other then default? self.config_file = self.defaults.config_file - if hasattr(self,'cli_keywords') and not self.cli_keywords == None: + if hasattr(self, 'cli_keywords') and self.cli_keywords is not None: if not self.cli_keywords.config_file == self.defaults.config_file: self.config_file = self.cli_keywords.config_file config = self.check_config() self.load_config(config) def set_options_from_testing_section(self): """ Go through the options in the [testing] section if it exists. """ config = self.check_config() if not config.has_section('testing'): return for key in config.options('testing'): retval = False if isinstance(self.defaults.__dict__['testing'][key], int): - value = config.getint('testing',key) + value = config.getint('testing', key) elif isinstance(self.defaults.__dict__['testing'][key], bool): - value = config.getboolean('testing',key) + value = config.getboolean('testing', key) elif isinstance(self.defaults.__dict__['testing'][key], str): - value = config.get('testing',key) + value = config.get('testing', key) elif isinstance(self.defaults.__dict__['testing'][key], list): - value = eval(config.get('testing',key)) + value = eval(config.get('testing', key)) elif isinstance(self.defaults.__dict__['testing'][key], dict): - value = eval(config.get('testing',key)) + value = eval(config.get('testing', key)) - if hasattr(self,"check_setting_%s_%s" % ('testing',key)): - exec("retval = self.check_setting_%s_%s(%r)" % ('testing',key,value)) + if hasattr(self, "check_setting_%s_%s" % ('testing', key)): + exec("retval = self.check_setting_%s_%s(%r)" % ('testing', key, value)) if not retval: # We just don't set it, check_setting_%s should have # taken care of the error messages continue - setattr(self,"%s_%s" % ('testing',key),value) + setattr(self, "%s_%s" % ('testing', key), value) if key.count('password') >= 1: - log.debug(_("Setting %s_%s to '****' (from configuration file)") % ('testing',key), level=8) + log.debug( + _("Setting %s_%s to '****' (from configuration file)") % ('testing', key), + level=8 + ) + else: - log.debug(_("Setting %s_%s to %r (from configuration file)") % ('testing',key,value), level=8) + log.debug( + _("Setting %s_%s to %r (from configuration file)") % ('testing', key, value), + level=8 + ) def check_config(self, val=None): """ Checks self.config_file or the filename passed using 'val' and returns a SafeConfigParser instance if everything is OK. """ - if not val == None: + if val is not None: config_file = val else: config_file = self.config_file if not os.access(config_file, os.R_OK): log.error(_("Configuration file %s not readable") % config_file) config = SafeConfigParser() log.debug(_("Reading configuration file %s") % config_file, level=8) try: config.read(config_file) - except: + except Exception: log.error(_("Invalid configuration file %s") % config_file) if not config.has_section("kolab"): - log.warning(_("No master configuration section [kolab] in configuration file %s") % config_file) + log.warning( + _("No master configuration section [kolab] in configuration file %s") % config_file + ) return config def add_cli_parser_option_group(self, name): return self.cli_parser.add_option_group(name) def create_options_from_plugins(self): """ Create options from plugins. This function must be called separately from Conf.__init__(), or the configuration store is not yet done initializing when the plugins class and the plugins themselves go look for it. """ import pykolab.plugins self.plugins = pykolab.plugins.KolabPlugins() self.plugins.add_options(self.cli_parser) def create_options(self, load_plugins=True): """ Create the OptionParser for the options passed to us from runtime Command Line Interface. """ # Enterprise Linux 5 does not have an "epilog" parameter to OptionParser try: self.cli_parser = OptionParser(epilog=epilog) - except: + except Exception: self.cli_parser = OptionParser() - ## - ## Runtime Options - ## + # + # Runtime Options + # runtime_group = self.cli_parser.add_option_group(_("Runtime Options")) - runtime_group.add_option( "-c", "--config", - dest = "config_file", - action = "store", - default = "/etc/kolab/kolab.conf", - help = _("Configuration file to use")) - - runtime_group.add_option( "-d", "--debug", - dest = "debuglevel", - type = 'int', - default = 0, - help = _("Set the debugging " + \ - "verbosity. Maximum is 9, tracing " + \ - "protocols like LDAP, SQL and IMAP.")) - - runtime_group.add_option( "-e", "--default", - dest = "answer_default", - action = "store_true", - default = False, - help = _("Use the default answer to all questions.")) - - runtime_group.add_option( "-l", - dest = "loglevel", - type = 'str', - default = "CRITICAL", - help = _("Set the logging level. " + \ - "One of info, warn, error, " + \ - "critical or debug")) - - runtime_group.add_option( "--logfile", - dest = "logfile", - action = "store", - default = "/var/log/kolab/pykolab.log", - help = _("Log file to use")) - - runtime_group.add_option( "-q", "--quiet", - dest = "quiet", - action = "store_true", - default = False, - help = _("Be quiet.")) - - runtime_group.add_option( "-y", "--yes", - dest = "answer_yes", - action = "store_true", - default = False, - help = _("Answer yes to all questions.")) + runtime_group.add_option( + "-c", "--config", + dest="config_file", + action="store", + default="/etc/kolab/kolab.conf", + help=_("Configuration file to use") + ) + + runtime_group.add_option( + "-d", "--debug", + dest="debuglevel", + type='int', + default=0, + help=_( + "Set the debugging verbosity. Maximum is 9, tracing protocols LDAP, SQL and IMAP." + ) + ) + + runtime_group.add_option( + "-e", "--default", + dest="answer_default", + action="store_true", + default=False, + help=_("Use the default answer to all questions.") + ) + + runtime_group.add_option( + "-l", + dest="loglevel", + type='str', + default="CRITICAL", + help=_("Set the logging level. One of info, warn, error, critical or debug") + ) + + runtime_group.add_option( + "--logfile", + dest="logfile", + action="store", + default="/var/log/kolab/pykolab.log", + help=_("Log file to use") + ) + + runtime_group.add_option( + "-q", "--quiet", + dest="quiet", + action="store_true", + default=False, + help=_("Be quiet.") + ) + + runtime_group.add_option( + "-y", "--yes", + dest="answer_yes", + action="store_true", + default=False, + help=_("Answer yes to all questions.") + ) def parse_options(self, fatal=True): """ Parse options passed to our call. """ if fatal: (self.cli_keywords, self.cli_args) = self.cli_parser.parse_args() def run(self): """ Run Forest, RUN! """ - - exitcode = 0 - if self.cli_args: if len(self.cli_args) >= 1: - if hasattr(self,"command_%s" % self.cli_args[0].replace('-','_')): - exec("self.command_%s(%r)" % (self.cli_args[0].replace('-','_'), self.cli_args[1:])) + if hasattr(self, "command_%s" % self.cli_args[0].replace('-', '_')): + exec( + "self.command_%s(%r)" % ( + self.cli_args[0].replace('-', '_'), + self.cli_args[1:] + ) + ) + else: - print >> sys.stderr, _("No command supplied") + print(_("No command supplied"), file=sys.stderr) def command_dump(self, *args, **kw): """ Dumps applicable, valid configuration that is not defaults. """ if not self.cfg_parser: self.read_config() if not self.cfg_parser.has_section('kolab'): - print "No section found for kolab" + print("No section found for kolab", file=sys.stderr) sys.exit(1) # Get the sections, and then walk through the sections in a # sensible way. items = self.cfg_parser.options('kolab') items.sort() for item in items: - mode = self.cfg_parser.get('kolab',item) - print "%s = %s" % (item,mode) + mode = self.cfg_parser.get('kolab', item) + print("%s = %s" % (item, mode)) if not self.cfg_parser.has_section(mode): - print "WARNING: No configuration section %s for item %s" % (mode,item) + print("WARNING: No configuration section %s for item %s" % (mode, item)) continue keys = self.cfg_parser.options(mode) keys.sort() if self.cfg_parser.has_option(mode, 'leave_this_one_to_me'): - print "Ignoring section %s" % (mode,) + print("Ignoring section %s" % (mode)) continue for key in keys: - print "%s_%s = %s" % (mode, key ,self.cfg_parser.get(mode,key)) + print("%s_%s = %s" % (mode, key, self.cfg_parser.get(mode, key))) def read_config(self, value=None): """ Reads the configuration file, sets a self.cfg_parser. """ if not value: value = self.defaults.config_file - if hasattr(self, 'cli_keywords') and not self.cli_keywords == None: + if hasattr(self, 'cli_keywords') and self.cli_keywords is not None: value = self.cli_keywords.config_file self.cfg_parser = SafeConfigParser() self.cfg_parser.read(value) if hasattr(self, 'cli_keywords') and hasattr(self.cli_keywords, 'config_file'): self.cli_keywords.config_file = value self.defaults.config_file = value self.config_file = value def command_get(self, *args, **kw): """ Get a configuration option. Pass me a section and key please. """ exec("args = %r" % args) - print "%s/%s: %r" % (args[0],args[1],self.get(args[0], args[1])) + print("%s/%s: %r" % (args[0], args[1], self.get(args[0], args[1]))) # if len(args) == 3: # # Return non-zero if no match # # Return zero if match # # Improvised "check" function def command_set(self, *args, **kw): """ Set a configuration option. Pass me a section, key and value please. Note that the section should already exist. TODO: Add a strict parameter TODO: Add key value checking """ if not self.cfg_parser: self.read_config() if not len(args) == 3: log.error(_("Insufficient options. Need section, key and value -in that order.")) if not self.cfg_parser.has_section(args[0]): log.error(_("No section '%s' exists.") % (args[0])) if '%' in args[2]: value = args[2].replace('%', '%%') else: value = args[2] self.cfg_parser.set(args[0], args[1], value) if hasattr(self, 'cli_keywords') and hasattr(self.cli_keywords, 'config_file'): fp = open(self.cli_keywords.config_file, "w+") self.cfg_parser.write(fp) fp.close() else: fp = open(self.config_file, "w+") self.cfg_parser.write(fp) fp.close() def create_logger(self): """ Create a logger instance using cli_options.debuglevel """ + global log - if not self.cli_keywords.debuglevel == None: + if self.cli_keywords.debuglevel is not None: loglevel = logging.DEBUG else: loglevel = logging.INFO self.cli_keywords.debuglevel = 0 self.debuglevel = self.cli_keywords.debuglevel # Initialize logger - log = pykolab.logger.Logger(loglevel=loglevel, debuglevel=self.cli_keywords.debuglevel, logfile=self.cli_keywords.logfile) + log = pykolab.logger.Logger( + loglevel=loglevel, + debuglevel=self.cli_keywords.debuglevel, + logfile=self.cli_keywords.logfile + ) def set_defaults_from_cli_options(self): for long_opt in self.cli_parser.__dict__['_long_opt'].keys(): if long_opt == "--help": continue - setattr(self.defaults,self.cli_parser._long_opt[long_opt].dest,self.cli_parser._long_opt[long_opt].default) + + setattr( + self.defaults, + self.cli_parser._long_opt[long_opt].dest, + self.cli_parser._long_opt[long_opt].default + ) # But, they should be available in our class as well for option in self.cli_parser.defaults.keys(): - log.debug(_("Setting %s to %r (from the default values for CLI options)") % (option, self.cli_parser.defaults[option]), level=8) - setattr(self,option,self.cli_parser.defaults[option]) + log.debug( + _("Setting %s to %r (from the default values for CLI options)") % ( + option, + self.cli_parser.defaults[option] + ), + level=8 + ) + + setattr(self, option, self.cli_parser.defaults[option]) def has_section(self, section): if not self.cfg_parser: self.read_config() return self.cfg_parser.has_section(section) def has_option(self, section, option): if not self.cfg_parser: self.read_config() return self.cfg_parser.has_option(section, option) def get_list(self, section, key): """ Gets a comma and/or space separated list from the configuration file and returns a list. """ values = [] untrimmed_values = [] setting = self.get_raw(section, key) - if setting == None: + if setting is None: return [] raw_values = setting.split(',') - if raw_values == None: + if raw_values is None: return [] for raw_value in raw_values: untrimmed_values.extend(raw_value.split(' ')) for value in untrimmed_values: if not value.strip() == "": values.append(value.strip().lower()) return values def get_raw(self, section, key, default=None): if not self.cfg_parser: self.read_config() if self.cfg_parser.has_option(section, key): - return self.cfg_parser.get(section,key, 1) + return self.cfg_parser.get(section, key, 1) return default - def get(self, section, key, quiet=False): + def get(self, section, key, default=None, quiet=False): """ Get a configuration option from our store, the configuration file, or an external source if we have some sort of function for it. TODO: Include getting the value from plugins through a hook. """ retval = False if not self.cfg_parser: self.read_config() - #log.debug(_("Obtaining value for section %r, key %r") % (section, key), level=8) + # log.debug(_("Obtaining value for section %r, key %r") % (section, key), level=8) if self.cfg_parser.has_option(section, key): try: return self.cfg_parser.get(section, key) - except: + except Exception: self.read_config() return self.cfg_parser.get(section, key) - if hasattr(self, "get_%s_%s" % (section,key)): + if hasattr(self, "get_%s_%s" % (section, key)): try: - exec("retval = self.get_%s_%s(quiet)" % (section,key)) - except Exception, e: - log.error(_("Could not execute configuration function: %s") % ("get_%s_%s(quiet=%r)" % (section,key,quiet))) - return None + exec("retval = self.get_%s_%s(quiet)" % (section, key)) + except Exception: + log.error( + _("Could not execute configuration function: %s") % ( + "get_%s_%s(quiet=%r)" % ( + section, + key, + quiet + ) + ) + ) + + return default return retval if quiet: return "" else: - log.warning(_("Option %s/%s does not exist in config file %s, pulling from defaults") % (section, key, self.config_file)) - if hasattr(self.defaults, "%s_%s" % (section,key)): - return getattr(self.defaults, "%s_%s" % (section,key)) + log.warning( + _("Option %s/%s does not exist in config file %s, pulling from defaults") % ( + section, + key, + self.config_file + ) + ) + + if hasattr(self.defaults, "%s_%s" % (section, key)): + return getattr(self.defaults, "%s_%s" % (section, key)) elif hasattr(self.defaults, "%s" % (section)): if key in getattr(self.defaults, "%s" % (section)): _dict = getattr(self.defaults, "%s" % (section)) return _dict[key] else: log.warning(_("Option does not exist in defaults.")) - return None + return default else: log.warning(_("Option does not exist in defaults.")) - return None + return default def check_setting_config_file(self, value): if os.path.isfile(value): if os.access(value, os.R_OK): self.read_config(value=value) self.config_file = value return True else: log.error(_("Configuration file %s not readable.") % (value)) return False else: log.error(_("Configuration file %s does not exist.") % (value)) return False def check_setting_debuglevel(self, value): if value < 0: - log.info(_("WARNING: A negative debug level value does not make this program be any more silent.")) + log.info( + _( + "WARNING: A negative debug level value does not " + + "make this program be any more silent." + ) + ) + elif value == 0: return True elif value <= 9: return True else: log.warning(_("This program has 9 levels of verbosity. Using the maximum of 9.")) return True def check_setting_saslauth_mode(self, value): if value: # TODO: I suppose this is platform specific if os.path.isfile("/var/run/saslauthd/mux"): if os.path.isfile("/var/run/saslauthd/saslauthd.pid"): log.error(_("Cannot start SASL authentication daemon")) return False else: try: os.remove("/var/run/saslauthd/mux") - except IOError, e: + except IOError: log.error(_("Cannot start SASL authentication daemon")) return False elif os.path.isfile("/var/run/sasl2/mux"): if os.path.isfile("/var/run/sasl2/saslauthd.pid"): log.error(_("Cannot start SASL authentication daemon")) return False else: try: os.remove("/var/run/sasl2/mux") - except IOError, e: + except IOError: log.error(_("Cannot start SASL authentication daemon")) return False return True def check_setting_use_imap(self, value): if value: try: import imaplib self.use_imap = value return True except ImportError: log.error(_("No imaplib library found.")) return False def check_setting_use_lmtp(self, value): if value: try: from smtplib import LMTP self.use_lmtp = value return True except ImportError: log.error(_("No LMTP class found in the smtplib library.")) return False def check_setting_use_mail(self, value): if value: try: from smtplib import SMTP self.use_mail = value return True except ImportError: log.error(_("No SMTP class found in the smtplib library.")) return False def check_setting_test_suites(self, value): # Attempt to load the suite, # Get the suite's options, # Set them here. - if not hasattr(self,'test_suites'): + if not hasattr(self, 'test_suites'): self.test_suites = [] if "zpush" in value: selectively = False - for item in [ 'calendar', 'contacts', 'mail' ]: + for item in ['calendar', 'contacts', 'mail']: if self.cli_keywords.__dict__[item]: - log.debug(_("Found you specified a specific set of items to test: %s") % (item), level=8) + log.debug( + _("Found you specified a specific set of items to test: %s") % (item), + level=8 + ) + selectively = item if not selectively: self.calendar = True self.contacts = True self.mail = True else: log.debug(_("Selectively selecting: %s") % (selectively), level=8) setattr(self, selectively, True) self.test_suites.append('zpush') def check_setting_calendar(self, value): if self.cli_parser._long_opt['--calendar'].default == value: return False else: return True def check_setting_contacts(self, value): if self.cli_parser._long_opt['--contacts'].default == value: return False else: return True def check_setting_mail(self, value): if self.cli_parser._long_opt['--mail'].default == value: return False else: return True diff --git a/pykolab/conf/defaults.py b/pykolab/conf/defaults.py index 3df9709..6dc5220 100644 --- a/pykolab/conf/defaults.py +++ b/pykolab/conf/defaults.py @@ -1,42 +1,45 @@ # -*- 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 + class Defaults(object): def __init__(self, plugins=None): self.loglevel = logging.CRITICAL self.imap_virtual_domains = 'userid' # An integer or float to indicate the interval at which the Cyrus IMAP # library should try to retrieve annotations self.cyrus_annotations_retry_interval = 1 self.address_search_attrs = ['mail', 'alias'] self.mail_attributes = ['mail', 'alias'] self.mailserver_attribute = 'mailhost' - # when you want a new domain to be added in a short time, you should reduce this value to 10 seconds + # when you want a new domain to be added in a short time, you should reduce this value to + # 10 seconds self.kolab_domain_sync_interval = 600 self.kolab_default_locale = 'en_US' + self.ldap_timeout = 10 self.ldap_unique_attribute = 'nsuniqueid' - self.wallace_resource_calendar_expire_days = 100 \ No newline at end of file + self.wallace_resource_calendar_expire_days = 100 diff --git a/wallace/module_resources.py b/wallace/module_resources.py index 724bf0c..798577d 100644 --- a/wallace/module_resources.py +++ b/wallace/module_resources.py @@ -1,1603 +1,1773 @@ # -*- coding: utf-8 -*- # pylint: disable=too-many-lines # Copyright 2010-2015 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 base64 import datetime from email import message_from_string from email.parser import Parser from email.utils import formataddr from email.utils import getaddresses import os import random import re import signal +from six import string_types import time import uuid from dateutil.tz import tzlocal import modules import kolabformat import pykolab from pykolab.auth import Auth from pykolab.conf import Conf from pykolab.imap import IMAP from pykolab.logger import LoggerAdapter from pykolab.itip import events_from_message from pykolab.itip import check_event_conflict from pykolab.translate import _ from pykolab.xml import to_dt from pykolab.xml import utils as xmlutils from pykolab.xml import event_from_message from pykolab.xml import participant_status_label # define some contstants used in the code below COND_NOTIFY = 256 ACT_MANUAL = 1 ACT_ACCEPT = 2 ACT_REJECT = 8 ACT_ACCEPT_AND_NOTIFY = ACT_ACCEPT + COND_NOTIFY # noqa: E241 policy_name_map = { 'ACT_MANUAL': ACT_MANUAL, # noqa: E241 'ACT_ACCEPT': ACT_ACCEPT, # noqa: E241 'ACT_REJECT': ACT_REJECT, # noqa: E241 'ACT_ACCEPT_AND_NOTIFY': ACT_ACCEPT_AND_NOTIFY } # pylint: disable=invalid-name log = pykolab.getLogger('pykolab.wallace/resources') extra_log_params = {'qid': '-'} log = LoggerAdapter(log, extra_log_params) conf = pykolab.getConf() mybasepath = '/var/spool/pykolab/wallace/resources/' auth = None imap = None def __init__(): modules.register('resources', execute, description=description(), heartbeat=heartbeat) def accept(filepath): new_filepath = os.path.join( mybasepath, 'ACCEPT', os.path.basename(filepath) ) cleanup() os.rename(filepath, new_filepath) filepath = new_filepath exec('modules.cb_action_ACCEPT(%r, %r)' % ('resources', filepath)) def description(): return """Resource management module.""" def cleanup(): global auth, imap, extra_log_params log.debug("cleanup(): %r, %r" % (auth, imap), level=8) extra_log_params['qid'] = '-' auth.disconnect() del auth # Disconnect IMAP or we lock the mailbox almost constantly imap.disconnect() del imap # pylint: disable=inconsistent-return-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals # pylint: disable=too-many-return-statements # pylint: disable=too-many-statements -def execute(*args, **kw): # noqa: C901 +def execute(*args, **kw): global auth, imap, extra_log_params # TODO: Test for correct call. filepath = args[0] extra_log_params['qid'] = os.path.basename(filepath) # (re)set language to default pykolab.translate.setUserLanguage(conf.get('kolab', 'default_locale')) if not os.path.isdir(mybasepath): os.makedirs(mybasepath) for stage in ['incoming', 'ACCEPT', 'REJECT', 'HOLD', 'DEFER']: if not os.path.isdir(os.path.join(mybasepath, stage)): os.makedirs(os.path.join(mybasepath, stage)) log.debug(_("Resource Management called for %r, %r") % (args, kw), level=8) auth = Auth() imap = IMAP() if 'stage' in kw: log.debug( _("Issuing callback after processing to stage %s") % ( kw['stage'] ), level=8 ) log.debug(_("Testing cb_action_%s()") % (kw['stage']), level=8) if hasattr(modules, 'cb_action_%s' % (kw['stage'])): log.debug( _("Attempting to execute cb_action_%s()") % (kw['stage']), level=8 ) exec( 'modules.cb_action_%s(%r, %r)' % ( kw['stage'], 'resources', filepath ) ) return filepath else: # Move to incoming new_filepath = os.path.join( mybasepath, 'incoming', os.path.basename(filepath) ) if not filepath == new_filepath: log.debug("Renaming %r to %r" % (filepath, new_filepath)) os.rename(filepath, new_filepath) filepath = new_filepath # parse full message message = Parser().parse(open(filepath, 'r')) # invalid message, skip if not message.get('X-Kolab-To'): return filepath recipients = [address for displayname, address in getaddresses(message.get_all('X-Kolab-To'))] sender_email = [ address for displayname, address in getaddresses(message.get_all('X-Kolab-From')) ][0] any_itips = False any_resources = False possibly_any_resources = False reference_uid = None # An iTip message may contain multiple events. Later on, test if the message # is an iTip message by checking the length of this list. try: itip_events = events_from_message(message, ['REQUEST', 'REPLY', 'CANCEL']) # pylint: disable=broad-except except Exception as errmsg: log.error(_("Failed to parse iTip events from message: %r" % (errmsg))) itip_events = [] if not len(itip_events) > 0: log.info("Message is not an iTip message or does not contain any (valid) iTip.") else: any_itips = True log.debug( "iTip events attached to this message contain the following information: %r" % ( itip_events ), level=8 ) if any_itips: # See if any iTip actually allocates a resource. if (len([x['resources'] for x in itip_events if 'resources' in x]) > 0 or len([x['attendees'] for x in itip_events if 'attendees' in x]) > 0): possibly_any_resources = True if possibly_any_resources: auth.connect() for recipient in recipients: # extract reference UID from recipients like resource+UID@domain.org if re.match(r'.+\+[A-Za-z0-9=/-]+@', recipient): try: (prefix, host) = recipient.split('@') (local, uid) = prefix.split('+') reference_uid = base64.b64decode(uid, '-/') recipient = local + '@' + host # pylint: disable=broad-except except Exception: continue if not len(resource_record_from_email_address(recipient)) == 0: resource_recipient = recipient any_resources = True if any_resources: if not any_itips: log.debug( _("Not an iTip message, but sent to resource nonetheless. Reject message"), level=5 ) reject(filepath) return False else: # Continue. Resources and iTips. We like. pass else: if not any_itips: log.debug(_("No itips, no resources, pass along %r") % (filepath), level=5) return filepath else: log.debug(_("iTips, but no resources, pass along %r") % (filepath), level=5) return filepath # A simple list of merely resource entry IDs that hold any relevance to the # iTip events resource_dns = resource_records_from_itip_events(itip_events, resource_recipient) # check if resource attendees match the envelope recipient if len(resource_dns) == 0: log.info( _("No resource attendees matching envelope recipient %s, Reject message") % ( resource_recipient ) ) log.debug("%r" % (itip_events), level=8) reject(filepath) return False # Get the resource details, which includes details on the IMAP folder # This may append resource collection members to recource_dns resources = get_resource_records(resource_dns) log.debug(_("Resources: %r; %r") % (resource_dns, resources), level=8) imap.connect() done = False receiving_resource = resources[resource_dns[0]] for itip_event in itip_events: if itip_event['method'] == 'REPLY': done = True # find initial reservation referenced by the reply if reference_uid: (event, master) = find_existing_event( reference_uid, itip_event['recurrence-id'], receiving_resource ) log.debug( _("iTip REPLY to %r, %r; matches %r") % ( reference_uid, itip_event['recurrence-id'], type(event) ), level=8 ) if event: try: sender_attendee = itip_event['xml'].get_attendee_by_email(sender_email) owner_reply = sender_attendee.get_participant_status() log.debug( _("Sender Attendee: %r => %r") % (sender_attendee, owner_reply), level=8 ) # pylint: disable=broad-except - except Exception as e: - log.error(_("Could not find envelope sender attendee: %r") % (e)) + except Exception as errmsg: + log.error(_("Could not find envelope sender attendee: %r") % (errmsg)) continue # compare sequence number to avoid outdated replies if not itip_event['sequence'] == event.get_sequence(): log.info( - _("The iTip reply sequence (%r) doesn't match the referred event version (%r). Ignoring.") % ( + _( + "The iTip reply sequence (%r) doesn't match the " + + "referred event version (%r). Ignoring." + ) % ( itip_event['sequence'], event.get_sequence() ) ) continue # forward owner response comment comment = itip_event['xml'].get_comment() if comment: event.set_comment(str(comment)) _itip_event = dict(xml=event, uid=event.get_uid(), _master=master) _itip_event['recurrence-id'] = event.get_recurrence_id() if owner_reply == kolabformat.PartAccepted: event.set_status(kolabformat.StatusConfirmed) accept_reservation_request(_itip_event, receiving_resource, confirmed=True) elif owner_reply == kolabformat.PartDeclined: decline_reservation_request(_itip_event, receiving_resource) else: - log.info(_("Invalid response (%r) received from resource owner for event %r") % ( - sender_attendee.get_participant_status(True), reference_uid - )) + log.info( + _( + "Invalid response (%r) received from resource owner for event %r" + ) % ( + sender_attendee.get_participant_status(True), + reference_uid + ) + ) else: log.info( _("Event referenced by this REPLY (%r) not found in resource calendar") % ( reference_uid ) ) else: log.info(_("No event reference found in this REPLY. Ignoring.")) # exit for-loop break # else: try: receiving_attendee = itip_event['xml'].get_attendee_by_email( receiving_resource['mail'] ) log.debug( _("Receiving Resource: %r; %r") % (receiving_resource, receiving_attendee), level=8 ) # pylint: disable=broad-except - except Exception as e: - log.error(_("Could not find envelope attendee: %r") % (e)) + except Exception as errmsg: + log.error(_("Could not find envelope attendee: %r") % (errmsg)) continue # ignore updates and cancellations to resource collections who already delegated the event att_delegated = (len(receiving_attendee.get_delegated_to()) > 0) att_nonpart = (receiving_attendee.get_role() == kolabformat.NonParticipant) att_rsvp = receiving_attendee.get_rsvp() if (att_delegated or att_nonpart) and not att_rsvp: done = True log.debug( _("Recipient %r is non-participant, ignoring message") % ( receiving_resource['mail'] ), level=8 ) # process CANCEL messages if not done and itip_event['method'] == "CANCEL": for resource in resource_dns: r_emails = [a.get_email() for a in itip_event['xml'].get_attendees()] _resource = resources[resource] if _resource['mail'] in r_emails and 'kolabtargetfolder' in _resource: (event, master) = find_existing_event( itip_event['uid'], itip_event['recurrence-id'], _resource ) if not event: continue # remove entire event if master is None: log.debug( _("Cancellation for entire event %r: deleting") % (itip_event['uid']), level=8 ) delete_resource_event( itip_event['uid'], resources[resource], event._msguid ) # just cancel one single occurrence: add exception with status=cancelled else: log.debug( _("Cancellation for a single occurrence %r of %r: updating...") % ( itip_event['recurrence-id'], itip_event['uid'] ), level=8 ) event.set_status('CANCELLED') event.set_transparency(True) _itip_event = dict(xml=event, uid=event.get_uid(), _master=master) _itip_event['recurrence-id'] = event.get_recurrence_id() save_resource_event(_itip_event, resources[resource]) done = True if done: os.unlink(filepath) cleanup() return # do the magic for the receiving attendee (available_resource, itip_event) = check_availability( itip_events, resource_dns, resources, receiving_attendee ) _reject = False resource = None original_resource = None # accept reservation if available_resource is not None: atts = [a.get_email() for a in itip_event['xml'].get_attendees()] if available_resource['mail'] in atts: # check if reservation was delegated if available_resource['mail'] != receiving_resource['mail']: if receiving_attendee.get_participant_status() == kolabformat.PartDelegated: original_resource = receiving_resource resource = available_resource else: # This must have been a resource collection originally. # We have inserted the reference to the original resource # record in 'memberof'. - if available_resource.has_key('memberof'): + if 'memberof' in available_resource: original_resource = resources[available_resource['memberof']] atts = [a.get_email() for a in itip_event['xml'].get_attendees()] if original_resource['mail'] in atts: # # Delegate: # - delegator: the original resource collection # - delegatee: the target resource # itip_event['xml'].delegate( original_resource['mail'], available_resource['mail'], available_resource['cn'] ) # set delegator to NON-PARTICIPANT and RSVP=FALSE delegator = itip_event['xml'].get_attendee_by_email(original_resource['mail']) delegator.set_role(kolabformat.NonParticipant) delegator.set_rsvp(False) log.debug( _("Delegate invitation for resource collection %r to %r") % ( original_resource['mail'], available_resource['mail'] ), level=8 ) resource = available_resource # Look for ACT_REJECT policy if resource is not None: invitationpolicy = get_resource_invitationpolicy(resource) log.debug(_("Apply invitation policies %r") % (invitationpolicy), level=8) if invitationpolicy is not None: for policy in invitationpolicy: if policy & ACT_REJECT: _reject = True break if resource is not None and not _reject: log.debug( _("Accept invitation for individual resource %r / %r") % ( resource['dn'], resource['mail'] ), level=8 ) accept_reservation_request( itip_event, resource, original_resource, False, invitationpolicy ) else: resource = resources[resource_dns[0]] # this is the receiving resource record log.debug( _("Decline invitation for individual resource %r / %r") % ( resource['dn'], resource['mail'] ), level=8 ) decline_reservation_request(itip_event, resource) cleanup() os.unlink(filepath) def heartbeat(lastrun): global imap # run archival job every hour only now = int(time.time()) if lastrun == 0 or now - heartbeat._lastrun < 3600: return log.debug(_("module_resources.heartbeat(%d)") % (heartbeat._lastrun), level=8) # get a list of resource records from LDAP auth = Auth() auth.connect() resource_dns = auth.find_resource('*') # Remove referrals resource_dns = [dn for dn in resource_dns if dn is not None] # filter by resource_base_dn resource_base_dn = conf.get('ldap', 'resource_base_dn', None) if resource_base_dn is not None: resource_dns = [dn for dn in resource_dns if resource_base_dn in dn] if len(resource_dns) > 0: imap = IMAP() imap.connect() for resource_dn in resource_dns: resource_attrs = auth.get_entry_attributes(None, resource_dn, ['kolabtargetfolder']) - if resource_attrs.has_key('kolabtargetfolder'): + + if 'kolabtargetfolder' in resource_attrs: try: expunge_resource_calendar(resource_attrs['kolabtargetfolder']) # pylint: disable=broad-except - except Exception as e: - log.error(_("Expunge resource calendar for %s (%s) failed: %r") % ( - resource_dn, resource_attrs['kolabtargetfolder'], e - )) + except Exception as errmsg: + log.error( + _("Expunge resource calendar for %s (%s) failed: %r") % ( + resource_dn, + resource_attrs['kolabtargetfolder'], + errmsg + ) + ) imap.disconnect() auth.disconnect() heartbeat._lastrun = now + heartbeat._lastrun = 0 def expunge_resource_calendar(mailbox): """ Cleanup routine to remove events older than 100 days from the given resource calendar """ global imap days = int(conf.get('wallace', 'resource_calendar_expire_days')) now = datetime.datetime.now(tzlocal()) expire_date = now - datetime.timedelta(days=days) log.debug( _("Expunge events in resource folder %r older than %d days") % (mailbox, days), level=8 ) # might raise an exception, let that bubble targetfolder = imap.folder_quote(mailbox) - imap.set_acl(targetfolder, conf.get(conf.get('kolab', 'imap_backend'), 'admin_login'), "lrswipkxtecda") + + imap.set_acl( + targetfolder, + conf.get(conf.get('kolab', 'imap_backend'), 'admin_login'), + "lrswipkxtecda" + ) + imap.imap.m.select(targetfolder) typ, data = imap.imap.m.search(None, 'UNDELETED') for num in data[0].split(): log.debug( _("Fetching message ID %r from folder %r") % (num, mailbox), level=8 ) typ, data = imap.imap.m.fetch(num, '(RFC822)') try: event = event_from_message(message_from_string(data[0][1])) # pylint: disable=broad-except - except Exception as e: - log.error(_("Failed to parse event from message %s/%s: %r") % (mailbox, num, e)) + except Exception as errmsg: + log.error(_("Failed to parse event from message %s/%s: %r") % (mailbox, num, errmsg)) continue if event: dt_end = to_dt(event.get_end()) # consider recurring events and get real end date if event.is_recurring(): dt_end = to_dt(event.get_last_occurrence()) if dt_end is None: # skip if recurring forever continue if dt_end and dt_end < expire_date: age = now - dt_end - log.debug(_("Flag event %s from message %s/%s as deleted (age = %d days)") % (event.uid, mailbox, num, age.days), level=8) + + log.debug( + _("Flag event %s from message %s/%s as deleted (age = %d days)") % ( + event.uid, + mailbox, + num, + age.days + ), + level=8 + ) + imap.imap.m.store(num, '+FLAGS', '\\Deleted') imap.imap.m.expunge() def check_availability(itip_events, resource_dns, resources, receiving_attendee=None): """ For each resource, determine if any of the events in question are in conflict. """ # Store the (first) conflicting event(s) alongside the resource information. start = time.time() num_messages = 0 available_resource = None for resource in resources.keys(): # skip this for resource collections - if not resources[resource].has_key('kolabtargetfolder'): + if 'kolabtargetfolder' not in resources[resource]: continue # sets the 'conflicting' flag and adds a list of conflicting events found try: num_messages += read_resource_calendar(resources[resource], itip_events) # pylint: disable=broad-except except Exception as e: log.error(_("Failed to read resource calendar for %r: %r") % (resource, e)) end = time.time() - log.debug(_("start: %r, end: %r, total: %r, messages: %d") % (start, end, (end-start), num_messages), level=8) - + log.debug( + _("start: %r, end: %r, total: %r, messages: %d") % ( + start, + end, + (end - start), + num_messages + ), + level=8 + ) # For each resource (collections are first!) # check conflicts and either accept or decline the reservation request for resource in resource_dns: log.debug(_("Polling for resource %r") % (resource), level=8) - if not resources.has_key(resource): + if resource not in resources: log.debug(_("Resource %r has been popped from the list") % (resource), level=8) continue - if not resources[resource].has_key('conflicting_events'): + if 'conflicting_events' not in resources[resource]: log.debug(_("Resource is a collection"), level=8) # check if there are non-conflicting collection members - conflicting_members = [x for x in resources[resource]['uniquemember'] if resources[x]['conflict']] + conflicting_members = [ + x for x in resources[resource]['uniquemember'] + if resources[x]['conflict'] + ] # found at least one non-conflicting member, remove the conflicting ones and continue if len(conflicting_members) < len(resources[resource]['uniquemember']): for member in conflicting_members: - resources[resource]['uniquemember'] = [x for x in resources[resource]['uniquemember'] if x != member] + resources[resource]['uniquemember'] = [ + x for x in resources[resource]['uniquemember'] + if x != member + ] + del resources[member] log.debug(_("Removed conflicting resources from %r: (%r) => %r") % ( resource, conflicting_members, resources[resource]['uniquemember'] ), level=8) else: # TODO: shuffle existing bookings of collection members in order # to make one available for the requested time pass continue if len(resources[resource]['conflicting_events']) > 0: - log.debug(_("Conflicting events: %r for resource %r") % (resources[resource]['conflicting_events'], resource), level=8) + log.debug( + _("Conflicting events: %r for resource %r") % ( + resources[resource]['conflicting_events'], + resource + ), + level=8 + ) done = False # This is the event being conflicted with! for itip_event in itip_events: # do not re-assign single occurrences to another resource if itip_event['recurrence-id'] is not None: continue + _eas = [a.get_email() for a in itip_event['xml'].get_attendees()] # Now we have the event that was conflicting - if resources[resource]['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]: + if resources[resource]['mail'] in _eas: # this resource initially was delegated from a collection ? - if receiving_attendee and receiving_attendee.get_email() == resources[resource]['mail'] \ + if receiving_attendee \ + and receiving_attendee.get_email() == resources[resource]['mail'] \ and len(receiving_attendee.get_delegated_from()) > 0: + for delegator in receiving_attendee.get_delegated_from(): collection_data = get_resource_collection(delegator.email()) if collection_data is not None: # check if another collection member is available - (available_resource, dummy) = check_availability(itip_events, collection_data[0], collection_data[1]) + (available_resource, dummy) = check_availability( + itip_events, + collection_data[0], + collection_data[1] + ) + break if available_resource is not None: - log.debug(_("Delegate to another resource collection member: %r to %r") % \ - (resources[resource]['mail'], available_resource['mail']), level=8) + log.debug( + _("Delegate to another resource collection member: %r to %r") % ( + resources[resource]['mail'], + available_resource['mail'] + ), + level=8 + ) # set this new resource as delegate for the receiving_attendee - itip_event['xml'].delegate(resources[resource]['mail'], available_resource['mail'], available_resource['cn']) + itip_event['xml'].delegate( + resources[resource]['mail'], + available_resource['mail'], + available_resource['cn'] + ) # set delegator to NON-PARTICIPANT and RSVP=FALSE receiving_attendee.set_role(kolabformat.NonParticipant) receiving_attendee.set_rsvp(False) receiving_attendee.setDelegatedFrom([]) # remove existing_events as we now delegated back to the collection if len(resources[resource]['existing_events']) > 0: for existing in resources[resource]['existing_events']: - delete_resource_event(existing.uid, resources[resource], existing._msguid) + delete_resource_event( + existing.uid, + resources[resource], + existing._msguid + ) done = True if done: break else: # No conflicts, go accept for itip_event in itip_events: # directly invited resource - if resources[resource]['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]: + _eas = [a.get_email() for a in itip_event['xml'].get_attendees()] + if resources[resource]['mail'] in _eas: available_resource = resources[resource] done = True else: # This must have been a resource collection originally. # We have inserted the reference to the original resource # record in 'memberof'. - if resources[resource].has_key('memberof'): + if 'memberof' in resources[resource]: original_resource = resources[resources[resource]['memberof']] # Randomly select a target resource from the resource collection. - available_resource = resources[original_resource['uniquemember'][random.randint(0,(len(original_resource['uniquemember'])-1))]] + _selected = random.randint(0, (len(original_resource['uniquemember']) - 1)) + + available_resource = resources[original_resource['uniquemember'][_selected]] + done = True if done: break # end for resource in resource_dns: return (available_resource, itip_event) def read_resource_calendar(resource_rec, itip_events): """ Read all booked events from the given resource's calendar and check for conflicts with the given list if itip events """ global imap resource_rec['conflict'] = False resource_rec['conflicting_events'] = [] resource_rec['existing_events'] = [] mailbox = resource_rec['kolabtargetfolder'] log.debug( _("Checking events in resource folder %r") % (mailbox), level=8 ) # set read ACLs for admin user imap.set_acl(mailbox, conf.get(conf.get('kolab', 'imap_backend'), 'admin_login'), "lrs") # might raise an exception, let that bubble imap.imap.m.select(imap.folder_quote(mailbox)) typ, data = imap.imap.m.search(None, 'UNDELETED') num_messages = len(data[0].split()) for num in data[0].split(): # For efficiency, makes the routine non-deterministic if resource_rec['conflict']: continue log.debug( _("Fetching message UID %r from folder %r") % (num, mailbox), level=8 ) typ, data = imap.imap.m.fetch(num, '(UID RFC822)') try: msguid = re.search(r"\WUID (\d+)", data[0][0]).group(1) # pylint: disable=broad-except except Exception: log.error(_("No UID found in IMAP response: %r") % (data[0][0])) continue try: event = event_from_message(message_from_string(data[0][1])) # pylint: disable=broad-except except Exception as e: log.error(_("Failed to parse event from message %s/%s: %r") % (mailbox, num, e)) continue if event: for itip in itip_events: conflict = check_event_conflict(event, itip) if event.get_uid() == itip['uid']: setattr(event, '_msguid', msguid) if event.is_recurring() or itip['recurrence-id']: resource_rec['existing_master'] = event else: resource_rec['existing_events'].append(event) if conflict: log.info( _("Event %r conflicts with event %r") % ( itip['xml'].get_uid(), event.get_uid() ) ) resource_rec['conflicting_events'].append(event.get_uid()) resource_rec['conflict'] = True return num_messages def find_existing_event(uid, recurrence_id, resource_rec): """ Search the resources's calendar folder for the given event (by UID) """ global imap event = None master = None mailbox = resource_rec['kolabtargetfolder'] log.debug(_("Searching %r for event %r") % (mailbox, uid), level=8) try: imap.imap.m.select(imap.folder_quote(mailbox)) typ, data = imap.imap.m.search(None, '(UNDELETED HEADER SUBJECT "%s")' % (uid)) # pylint: disable=broad-except - except Exception as e: - log.error(_("Failed to access resource calendar:: %r") % (e)) + except Exception as errmsg: + log.error(_("Failed to access resource calendar:: %r") % (errmsg)) return event for num in reversed(data[0].split()): typ, data = imap.imap.m.fetch(num, '(UID RFC822)') try: msguid = re.search(r"\WUID (\d+)", data[0][0]).group(1) # pylint: disable=broad-except - except Exception as e: + except Exception: log.error(_("No UID found in IMAP response: %r") % (data[0][0])) continue try: event = event_from_message(message_from_string(data[0][1])) # find instance in a recurring series if recurrence_id and (event.is_recurring() or event.has_exceptions()): master = event event = master.get_instance(recurrence_id) setattr(master, '_msguid', msguid) # return master, even if instance is not found if not event and master.uid == uid: return (event, master) # compare recurrence-id and skip to next message if not matching - elif recurrence_id and not xmlutils.dates_equal(recurrence_id, event.get_recurrence_id()): - log.debug(_("Recurrence-ID not matching on message %s, skipping: %r != %r") % ( - msguid, recurrence_id, event.get_recurrence_id() - ), level=8) - continue + elif recurrence_id: + if not xmlutils.dates_equal(recurrence_id, event.get_recurrence_id()): + log.debug( + _("Recurrence-ID not matching on message %s, skipping: %r != %r") % ( + msguid, + recurrence_id, + event.get_recurrence_id() + ), + level=8 + ) + + continue if event is not None: setattr(event, '_msguid', msguid) # pylint: disable=broad-except - except Exception as e: - log.error(_("Failed to parse event from message %s/%s: %r") % (mailbox, num, e)) + except Exception as errmsg: + log.error(_("Failed to parse event from message %s/%s: %r") % (mailbox, num, errmsg)) event = None master = None continue if event and event.uid == uid: return (event, master) return (event, master) -def accept_reservation_request(itip_event, resource, delegator=None, confirmed=False, invitationpolicy=None): +def accept_reservation_request( + itip_event, + resource, + delegator=None, + confirmed=False, + invitationpolicy=None +): """ Accepts the given iTip event by booking it into the resource's calendar. Then set the attendee status of the given resource to ACCEPTED and sends an iTip reply message to the organizer. """ owner = get_resource_owner(resource) confirmation_required = False if not confirmed and owner: if invitationpolicy is None: invitationpolicy = get_resource_invitationpolicy(resource) log.debug(_("Apply invitation policies %r") % (invitationpolicy), level=8) if invitationpolicy is not None: for policy in invitationpolicy: if policy & ACT_MANUAL and owner['mail']: confirmation_required = True break partstat = 'TENTATIVE' if confirmation_required else 'ACCEPTED' - itip_event['xml'].set_transparency(False); + itip_event['xml'].set_transparency(False) itip_event['xml'].set_attendee_participant_status( itip_event['xml'].get_attendee_by_email(resource['mail']), partstat ) saved = save_resource_event(itip_event, resource) log.debug( _("Adding event to %r: %r") % (resource['kolabtargetfolder'], saved), level=8 ) if saved: send_response(delegator['mail'] if delegator else resource['mail'], itip_event, owner) if owner and confirmation_required: send_owner_confirmation(resource, owner, itip_event) elif owner: send_owner_notification(resource, owner, itip_event, saved) def decline_reservation_request(itip_event, resource): """ Set the attendee status of the given resource to DECLINED and send an according iTip reply to the organizer. """ itip_event['xml'].set_attendee_participant_status( itip_event['xml'].get_attendee_by_email(resource['mail']), "DECLINED" ) # update master event if resource.get('existing_master') is not None or itip_event.get('_master') is not None: save_resource_event(itip_event, resource) # remove old copy of the reservation elif resource.get('existing_events', []) and len(resource['existing_events']) > 0: for existing in resource['existing_events']: delete_resource_event(existing.uid, resource, existing._msguid) # delete old event referenced by itip_event (from owner confirmation) elif hasattr(itip_event['xml'], '_msguid'): delete_resource_event(itip_event['xml'].uid, resource, itip_event['xml']._msguid) # send response and notification owner = get_resource_owner(resource) send_response(resource['mail'], itip_event, owner) if owner: send_owner_notification(resource, owner, itip_event, True) def save_resource_event(itip_event, resource): """ Append the given event object to the resource's calendar """ try: save_event = itip_event['xml'] targetfolder = imap.folder_quote(resource['kolabtargetfolder']) # add exception to existing recurring main event if resource.get('existing_master') is not None: save_event = resource['existing_master'] save_event.add_exception(itip_event['xml']) elif itip_event.get('_master') is not None: save_event = itip_event['_master'] save_event.add_exception(itip_event['xml']) # remove old copy of the reservation (also sets ACLs) - if resource.has_key('existing_events') and len(resource['existing_events']) > 0: + if 'existing_events' in resource and len(resource['existing_events']) > 0: for existing in resource['existing_events']: delete_resource_event(existing.uid, resource, existing._msguid) # delete old version referenced save_event elif hasattr(save_event, '_msguid'): delete_resource_event(save_event.uid, resource, save_event._msguid) else: - imap.set_acl(targetfolder, conf.get(conf.get('kolab', 'imap_backend'), 'admin_login'), "lrswipkxtecda") + imap.set_acl( + targetfolder, + conf.get(conf.get('kolab', 'imap_backend'), 'admin_login'), + "lrswipkxtecda" + ) # append new version result = imap.imap.m.append( targetfolder, None, None, save_event.to_message(creator="Kolab Server ").as_string() ) return result # pylint: disable=broad-except except Exception as e: log.error(_("Failed to save event to resource calendar at %r: %r") % ( resource['kolabtargetfolder'], e )) return False def delete_resource_event(uid, resource, msguid=None): """ Removes the IMAP object with the given UID from a resource's calendar folder """ targetfolder = imap.folder_quote(resource['kolabtargetfolder']) try: - imap.set_acl(targetfolder, conf.get(conf.get('kolab', 'imap_backend'), 'admin_login'), "lrswipkxtecda") + imap.set_acl( + targetfolder, + conf.get(conf.get('kolab', 'imap_backend'), 'admin_login'), + "lrswipkxtecda" + ) + imap.imap.m.select(targetfolder) # delete by IMAP UID if msguid is not None: log.debug(_("Delete resource calendar object from %r by UID %r") % ( targetfolder, msguid ), level=8) imap.imap.m.uid('store', msguid, '+FLAGS', '(\\Deleted)') else: typ, data = imap.imap.m.search(None, '(HEADER SUBJECT "%s")' % uid) log.debug(_("Delete resource calendar object %r in %r: %r") % ( uid, resource['kolabtargetfolder'], data ), level=8) for num in data[0].split(): imap.imap.m.store(num, '+FLAGS', '\\Deleted') imap.imap.m.expunge() return True # pylint: disable=broad-except except Exception as e: log.error(_("Failed to delete calendar object %r from folder %r: %r") % ( uid, targetfolder, e )) return False def reject(filepath): new_filepath = os.path.join( - mybasepath, - 'REJECT', - os.path.basename(filepath) - ) + mybasepath, + 'REJECT', + os.path.basename(filepath) + ) os.rename(filepath, new_filepath) filepath = new_filepath - exec('modules.cb_action_REJECT(%r, %r)' % ('resources',filepath)) + exec('modules.cb_action_REJECT(%r, %r)' % ('resources', filepath)) def resource_record_from_email_address(email_address): """ Resolves the given email address to a resource entity """ global auth if not auth: auth = Auth() auth.connect() resource_records = [] local_domains = auth.list_domains() - if not local_domains == None: + if local_domains is not None: local_domains = list(set(local_domains.keys())) if not email_address.split('@')[1] in local_domains: return [] log.debug( _("Checking if email address %r belongs to a resource (collection)") % (email_address), level=8 ) resource_records = auth.find_resource(email_address) if isinstance(resource_records, list): if len(resource_records) > 0: log.debug(_("Resource record(s): %r") % (resource_records), level=8) else: log.debug(_("No resource (collection) records found for %r") % (email_address), level=8) - elif isinstance(resource_records, basestring): - resource_records = [ resource_records ] + elif isinstance(resource_records, string_types): + resource_records = [resource_records] log.debug(_("Resource record: %r") % (resource_records), level=8) return resource_records def resource_records_from_itip_events(itip_events, recipient_email=None): """ Given a list of itip_events, determine which resources have been invited as attendees and/or resources. """ global auth if not auth: auth = Auth() auth.connect() resource_records = [] log.debug(_("Raw itip_events: %r") % (itip_events), level=8) attendees_raw = [] - for list_attendees_raw in [x for x in [y['attendees'] for y in itip_events if y.has_key('attendees') and isinstance(y['attendees'], list)]]: + + _lars = [ + x for x in [ + y['attendees'] for y in itip_events + if 'attendees' in y and isinstance(y['attendees'], list) + ] + ] + + for list_attendees_raw in _lars: attendees_raw.extend(list_attendees_raw) - for list_attendees_raw in [y['attendees'] for y in itip_events if y.has_key('attendees') and isinstance(y['attendees'], basestring)]: + _lars = [ + y['attendees'] for y in itip_events + if 'attendees' in y and isinstance(y['attendees'], string_types) + ] + + for list_attendees_raw in _lars: attendees_raw.append(list_attendees_raw) log.debug(_("Raw set of attendees: %r") % (attendees_raw), level=8) # TODO: Resources are actually not implemented in the format. We reset this # list later. resources_raw = [] - for list_resources_raw in [x for x in [y['resources'] for y in itip_events if y.has_key('resources')]]: + _lrrs = [x for x in [y['resources'] for y in itip_events if 'resource' in y]] + + for list_resources_raw in _lrrs: resources_raw.extend(list_resources_raw) log.debug(_("Raw set of resources: %r") % (resources_raw), level=8) # consider organizer (in REPLY messages), too - organizers_raw = [re.sub('\+[A-Za-z0-9=/-]+@', '@', str(y['organizer'])) for y in itip_events if y.has_key('organizer')] + organizers_raw = [ + re.sub(r'\+[A-Za-z0-9=/-]+@', '@', str(y['organizer'])) for y in itip_events + if 'organizer' in y + ] log.debug(_("Raw set of organizers: %r") % (organizers_raw), level=8) - # TODO: We expect the format of an attendee line to literally be: # # ATTENDEE:RSVP=TRUE;ROLE=REQ-PARTICIPANT;MAILTO:lydia.bossers@kolabsys.com # # which makes the attendees_raw contain: # # RSVP=TRUE;ROLE=REQ-PARTICIPANT;MAILTO:lydia.bossers@kolabsys.com # attendees = [x.split(':')[-1] for x in attendees_raw + organizers_raw] # Limit the attendee resources to the one that is actually invited # with the current message. Considering all invited resources would result in # duplicate responses from every iTip message sent to a resource. if recipient_email is not None: attendees = [a for a in attendees if a == recipient_email] for attendee in attendees: log.debug(_("Checking if attendee %r is a resource (collection)") % (attendee), level=8) _resource_records = auth.find_resource(attendee) if isinstance(_resource_records, list): if len(_resource_records) > 0: resource_records.extend(_resource_records) log.debug(_("Resource record(s): %r") % (_resource_records), level=8) else: log.debug(_("No resource (collection) records found for %r") % (attendee), level=8) - elif isinstance(_resource_records, basestring): + elif isinstance(_resource_records, string_types): resource_records.append(_resource_records) log.debug(_("Resource record: %r") % (_resource_records), level=8) else: log.warning(_("Resource reservation made but no resource records found")) # Escape the non-implementation of the free-form, undefined RESOURCES # list(s) in iTip. if len(resource_records) == 0: # TODO: We don't know how to handle this yet! # We expect the format of an resource line to literally be: # RESOURCES:MAILTO:resource-car@kolabsys.com resources_raw = [] resources = [x.split(':')[-1] for x in resources_raw] # Limit the attendee resources to the one that is actually invited # with the current message. if recipient_email is not None: resources = [a for a in resources if a == recipient_email] for resource in resources: - log.debug(_("Checking if resource %r is a resource (collection)") % (resource), level=8) + log.debug( + _("Checking if resource %r is a resource (collection)") % (resource), + level=8 + ) _resource_records = auth.find_resource(resource) if isinstance(_resource_records, list): if len(_resource_records) > 0: resource_records.extend(_resource_records) log.debug(_("Resource record(s): %r") % (_resource_records), level=8) else: - log.debug(_("No resource (collection) records found for %r") % (resource), level=8) + log.debug( + _("No resource (collection) records found for %r") % (resource), + level=8 + ) - elif isinstance(_resource_records, basestring): + elif isinstance(_resource_records, string_types): resource_records.append(_resource_records) log.debug(_("Resource record: %r") % (_resource_records), level=8) else: log.warning(_("Resource reservation made but no resource records found")) - - log.debug(_("The following resources are being referred to in the " + \ - "iTip: %r") % (resource_records), level=8) + log.debug( + _("The following resources are being referred to in the iTip: %r") % (resource_records), + level=8 + ) return resource_records def get_resource_records(resource_dns): """ Get the resource details, which includes details on the IMAP folder """ global auth resources = {} for resource_dn in list(set(resource_dns)): # Get the attributes for the record # See if it is a resource collection # If it is, expand to individual resources # If it is not, ... resource_attrs = auth.get_entry_attributes(None, resource_dn, ['*']) resource_attrs['dn'] = resource_dn parse_kolabinvitationpolicy(resource_attrs) - if not 'kolabsharedfolder' in [x.lower() for x in resource_attrs['objectclass']]: - if resource_attrs.has_key('uniquemember'): + if 'kolabsharedfolder' not in [x.lower() for x in resource_attrs['objectclass']]: + if 'uniquemember' in resource_attrs: if not isinstance(resource_attrs['uniquemember'], list): - resource_attrs['uniquemember'] = [ resource_attrs['uniquemember'] ] + resource_attrs['uniquemember'] = [resource_attrs['uniquemember']] resources[resource_dn] = resource_attrs for uniquemember in resource_attrs['uniquemember']: member_attrs = auth.get_entry_attributes( - None, - uniquemember, - ['*'] - ) + None, + uniquemember, + ['*'] + ) if 'kolabsharedfolder' in [x.lower() for x in member_attrs['objectclass']]: member_attrs['dn'] = uniquemember parse_kolabinvitationpolicy(member_attrs, resource_attrs) resources[uniquemember] = member_attrs resources[uniquemember]['memberof'] = resource_dn - if not member_attrs.has_key('owner') and resources[resource_dn].has_key('owner'): + + if 'owner' not in member_attrs and 'owner' in resources[resource_dn]: resources[uniquemember]['owner'] = resources[resource_dn]['owner'] + resource_dns.append(uniquemember) else: resources[resource_dn] = resource_attrs return resources def parse_kolabinvitationpolicy(attrs, parent=None): - if attrs.has_key('kolabinvitationpolicy'): + if 'kolabinvitationpolicy' in attrs: if not isinstance(attrs['kolabinvitationpolicy'], list): attrs['kolabinvitationpolicy'] = [attrs['kolabinvitationpolicy']] - attrs['kolabinvitationpolicy'] = [policy_name_map[p] for p in attrs['kolabinvitationpolicy'] if policy_name_map.has_key(p)] - elif isinstance(parent, dict) and parent.has_key('kolabinvitationpolicy'): + attrs['kolabinvitationpolicy'] = [ + policy_name_map[p] for p in attrs['kolabinvitationpolicy'] if p in policy_name_map + ] + + elif isinstance(parent, dict) and 'kolabinvitationpolicy' in parent: attrs['kolabinvitationpolicy'] = parent['kolabinvitationpolicy'] def get_resource_collection(email_address): """ - + Obtain a resource collection object from an email address. """ resource_dns = resource_record_from_email_address(email_address) if len(resource_dns) == 1: resource_attrs = auth.get_entry_attributes(None, resource_dns[0], ['objectclass']) - if not 'kolabsharedfolder' in [x.lower() for x in resource_attrs['objectclass']]: + if 'kolabsharedfolder' not in [x.lower() for x in resource_attrs['objectclass']]: resources = get_resource_records(resource_dns) return (resource_dns, resources) return None def get_resource_owner(resource): """ Get this resource's owner record """ global auth if not auth: auth = Auth() auth.connect() owners = [] - if resource.has_key('owner'): + if 'owner' in resource: if not isinstance(resource['owner'], list): - owners = [ resource['owner'] ] + owners = [resource['owner']] else: owners = resource['owner'] else: # get owner attribute from collection collections = auth.search_entry_by_attribute('uniquemember', resource['dn']) if not isinstance(collections, list): - collections = [ collections ] + collections = [collections] - for dn,collection in collections: - if collection.has_key('owner') and isinstance(collection['owner'], list): + for dn, collection in collections: + if 'owner' in collection and isinstance(collection['owner'], list): owners += collection['owner'] - elif collection.has_key('owner'): + elif 'owner' in collection: owners.append(collection['owner']) for dn in owners: - owner = auth.get_entry_attributes(None, dn, ['cn','mail','telephoneNumber']) + owner = auth.get_entry_attributes(None, dn, ['cn', 'mail', 'telephoneNumber']) if owner is not None: return owner return None def get_resource_invitationpolicy(resource): """ Get this resource's kolabinvitationpolicy configuration """ global auth - if not resource.has_key('kolabinvitationpolicy') or resource['kolabinvitationpolicy'] is None: + if 'kolabinvitationpolicy' not in resource or resource['kolabinvitationpolicy'] is None: if not auth: auth = Auth() auth.connect() # get kolabinvitationpolicy attribute from collection collections = auth.search_entry_by_attribute('uniquemember', resource['dn']) if not isinstance(collections, list): - collections = [ (collections['dn'],collections) ] + collections = [(collections['dn'], collections)] - log.debug(_("Check collections %r for kolabinvitationpolicy attributes") % (collections), level=8) + log.debug( + _("Check collections %r for kolabinvitationpolicy attributes") % (collections), + level=8 + ) - for dn,collection in collections: + for dn, collection in collections: # ldap.search_entry_by_attribute() doesn't return the attributes lower-cased - if collection.has_key('kolabInvitationPolicy'): + if 'kolabInvitationPolicy' in collection: collection['kolabinvitationpolicy'] = collection['kolabInvitationPolicy'] - if collection.has_key('kolabinvitationpolicy'): + if 'kolabinvitationpolicy' in collection: parse_kolabinvitationpolicy(collection) resource['kolabinvitationpolicy'] = collection['kolabinvitationpolicy'] break - return resource['kolabinvitationpolicy'] if resource.has_key('kolabinvitationpolicy') else None + return resource['kolabinvitationpolicy'] if 'kolabinvitationpolicy' in resource else None def send_response(from_address, itip_events, owner=None): """ Send the given iCal events as a valid iTip response to the organizer. In case the invited resource coolection was delegated to a concrete resource, this will send an additional DELEGATED response message. """ if isinstance(itip_events, dict): - itip_events = [ itip_events ] + itip_events = [itip_events] for itip_event in itip_events: attendee = itip_event['xml'].get_attendee_by_email(from_address) participant_status = itip_event['xml'].get_ical_attendee_participant_status(attendee) # TODO: look-up event organizer in LDAP and change localization to its preferredlanguage message_text = reservation_response_text(participant_status, owner) subject_template = _("Reservation Request for %(summary)s was %(status)s") # Extra actions to take: send delegated reply if participant_status == "DELEGATED": - delegatee = [a for a in itip_event['xml'].get_attendees() if from_address in a.get_delegated_from(True)][0] + delegatee = [ + a for a in itip_event['xml'].get_attendees() + if from_address in a.get_delegated_from(True) + ][0] delegated_message_text = _(""" *** This is an automated response, please do not reply! *** Your reservation was delegated to "%s" which is available for the requested time. """) % (delegatee.get_name()) - pykolab.itip.send_reply(from_address, itip_event, delegated_message_text, - subject=subject_template) + pykolab.itip.send_reply( + from_address, + itip_event, + delegated_message_text, + subject=subject_template + ) # adjust some vars for the regular reply from the delegatee message_text = reservation_response_text(delegatee.get_participant_status(True), owner) from_address = delegatee.get_email() time.sleep(2) - pykolab.itip.send_reply(from_address, itip_event, message_text, - subject=subject_template) + pykolab.itip.send_reply( + from_address, + itip_event, + message_text, + subject=subject_template + ) def reservation_response_text(status, owner): message_text = _(""" *** This is an automated response, please do not reply! *** - + We hereby inform you that your reservation was %s. """) % (participant_status_label(status)) if owner: - message_text += _(""" - If you have questions about this reservation, please contact - %s <%s> %s - """) % (owner['cn'], owner['mail'], owner['telephoneNumber'] if owner.has_key('telephoneNumber') else '') - + message_text += _( + """ + If you have questions about this reservation, please contact + %s <%s> %s + """ + ) % ( + owner['cn'], + owner['mail'], + owner['telephoneNumber'] if 'telephoneNumber' in owner else '' + ) + return message_text def send_owner_notification(resource, owner, itip_event, success=True): """ Send a reservation notification to the resource owner """ from pykolab import utils from email.MIMEText import MIMEText from email.Utils import formatdate # encode unicode strings with quoted-printable from email import charset charset.add_charset('utf-8', charset.SHORTEST, charset.QP) notify = False status = itip_event['xml'].get_attendee_by_email(resource['mail']).get_participant_status(True) invitationpolicy = get_resource_invitationpolicy(resource) if invitationpolicy is not None: for policy in invitationpolicy: # TODO: distingish ACCEPTED / DECLINED status notifications? if policy & COND_NOTIFY and owner['mail']: notify = True break if notify or not success: log.debug( _("Sending booking notification for event %r to %r from %r") % ( itip_event['uid'], owner['mail'], resource['cn'] ), level=8 ) # change gettext language to the preferredlanguage setting of the resource owner - if owner.has_key('preferredlanguage'): + if 'preferredlanguage' in owner: pykolab.translate.setUserLanguage(owner['preferredlanguage']) message_text = owner_notification_text(resource, owner, itip_event['xml'], success) msg = MIMEText(utils.stripped_message(message_text), _charset='utf-8') msg['To'] = owner['mail'] msg['From'] = resource['mail'] msg['Date'] = formatdate(localtime=True) msg['Subject'] = utils.str2unicode(_('Booking for %s has been %s') % ( resource['cn'], participant_status_label(status) if success else _('failed') )) seed = random.randint(0, 6) alarm_after = (seed * 10) + 60 log.debug(_("Set alarm to %s seconds") % (alarm_after), level=8) signal.alarm(alarm_after) result = modules._sendmail(resource['mail'], owner['mail'], msg.as_string()) log.debug(_("Owner notification was sent successfully: %r") % result, level=8) signal.alarm(0) + def owner_notification_text(resource, owner, event, success): organizer = event.get_organizer() status = event.get_attendee_by_email(resource['mail']).get_participant_status(True) if success: - message_text = _(""" - The resource booking for %(resource)s by %(orgname)s <%(orgemail)s> has been %(status)s for %(date)s. + message_text = _( + """ + The resource booking for %(resource)s by %(orgname)s <%(orgemail)s> has been + %(status)s for %(date)s. - *** This is an automated message, sent to you as the resource owner. *** - """) + *** This is an automated message, sent to you as the resource owner. *** + """ + ) else: - message_text = _(""" - A reservation request for %(resource)s could not be processed automatically. - Please contact %(orgname)s <%(orgemail)s> who requested this resource for %(date)s. Subject: %(summary)s. + message_text = _( + """ + A reservation request for %(resource)s could not be processed automatically. + + Please contact %(orgname)s <%(orgemail)s> who requested this resource for %(date)s. + + Subject for the event: %(summary)s. - *** This is an automated message, sent to you as the resource owner. *** - """) + *** This is an automated message, sent to you as the resource owner. *** + """ + ) return message_text % { 'resource': resource['cn'], 'summary': event.get_summary(), 'date': event.get_date_text(), 'status': participant_status_label(status), 'orgname': organizer.name(), 'orgemail': organizer.email() } def send_owner_confirmation(resource, owner, itip_event): """ - Send a reservation request to the resource owner for manual confirmation (ACCEPT or DECLINE) + Send a reservation request to the resource owner for manual confirmation (ACCEPT or + DECLINE). - This clones the given invtation with a new UID and setting the resource as organizer in order to - receive the reply from the owner. + This clones the given invtation with a new UID and setting the resource as organizer in + order to receive the reply from the owner. """ uid = itip_event['uid'] event = itip_event['xml'] organizer = event.get_organizer() - event_attendees = [a.get_displayname() for a in event.get_attendees() if not a.get_cutype() == kolabformat.CutypeResource] + event_attendees = [ + a.get_displayname() for a in event.get_attendees() + if not a.get_cutype() == kolabformat.CutypeResource + ] log.debug( _("Clone invitation for owner confirmation: %r from %r") % ( itip_event['uid'], event.get_organizer().email() ), level=8 ) # generate new UID and set the resource as organizer (mail, domain) = resource['mail'].split('@') event.set_uid(str(uuid.uuid4())) event.set_organizer(mail + '+' + base64.b64encode(uid, '-/') + '@' + domain, resource['cn']) itip_event['uid'] = event.get_uid() # add resource owner as (the sole) attendee event._attendees = [] - event.add_attendee(owner['mail'], owner['cn'], rsvp=True, role=kolabformat.Required, participant_status=kolabformat.PartNeedsAction) + event.add_attendee( + owner['mail'], + owner['cn'], + rsvp=True, + role=kolabformat.Required, + participant_status=kolabformat.PartNeedsAction + ) # flag this iTip message as confirmation type event.add_custom_property('X-Kolab-InvitationType', 'CONFIRMATION') - message_text = _(""" - A reservation request for %(resource)s requires your approval! - Please either accept or decline this invitation without saving it to your calendar. + message_text = _( + """ + A reservation request for %(resource)s requires your approval! + Please either accept or decline this invitation without saving it to your calendar. - The reservation request was sent from %(orgname)s <%(orgemail)s>. + The reservation request was sent from %(orgname)s <%(orgemail)s>. - Subject: %(summary)s. - Date: %(date)s - Participants: %(attendees)s + Subject: %(summary)s. + Date: %(date)s + Participants: %(attendees)s - *** This is an automated message, please don't reply by email. *** - """)% { + *** This is an automated message, please don't reply by email. *** + """ + ) % { 'resource': resource['cn'], 'orgname': organizer.name(), 'orgemail': organizer.email(), 'summary': event.get_summary(), 'date': event.get_date_text(), 'attendees': ",\n+ ".join(event_attendees) } - pykolab.itip.send_request(owner['mail'], itip_event, message_text, + pykolab.itip.send_request( + owner['mail'], + itip_event, + message_text, subject=_('Booking request for %s requires confirmation') % (resource['cn']), - direct=True) - - + direct=True + )