diff options
-rw-r--r-- | pykolab/Makefile.am | 4 | ||||
-rw-r--r-- | pykolab/plugins/sievemgmt/__init__.py | 424 |
2 files changed, 428 insertions, 0 deletions
diff --git a/pykolab/Makefile.am b/pykolab/Makefile.am index 5ef6974..a3488c1 100644 --- a/pykolab/Makefile.am +++ b/pykolab/Makefile.am @@ -53,6 +53,10 @@ pykolab_plugins_recipientpolicydir = $(pythondir)/$(PACKAGE)/plugins/recipientpo pykolab_plugins_recipientpolicy_PYTHON = \ plugins/recipientpolicy/__init__.py +pykolab_plugins_sievemgmtdir = $(pythondir)/$(PACKAGE)/plugins/sievemgmt +pykolab_plugins_sievemgmt_PYTHON = \ + plugins/sievemgmt/__init__.py + pykolab_setupdir = $(pythondir)/$(PACKAGE)/setup pykolab_setup_PYTHON = \ setup/components.py \ diff --git a/pykolab/plugins/sievemgmt/__init__.py b/pykolab/plugins/sievemgmt/__init__.py new file mode 100644 index 0000000..f341eae --- /dev/null +++ b/pykolab/plugins/sievemgmt/__init__.py @@ -0,0 +1,424 @@ +# -*- coding: utf-8 -*- +# Copyright 2010-2012 Kolab Systems AG (http://www.kolabsys.com) +# +# Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen a kolabsys.com> +# +# 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; version 3 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 Library General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +import pykolab + +from pykolab import utils +from pykolab.auth import Auth +from pykolab.translate import _ + +conf = pykolab.getConf() +log = pykolab.getLogger('pykolab.plugin_sievemgmt') + +import sys +import time +from urlparse import urlparse + +class KolabSievemgmt(object): + """ + Plugin to manage Sieve scripts according to KEP #14. + """ + + def __init__(self): + pass + + def add_options(self, *args, **kw): + pass + + def sieve_mgmt_refresh(self, *args, **kw): + """ + The arguments passed to the 'sieve_mgmt_refresh' hook: + + user - the user identifier + """ + if not len(kw.keys()) == 1 or not kw.has_key('user'): + log.error(_("Wrong number of arguments for sieve management plugin")) + return + else: + address = kw['user'] + + auth = Auth() + auth.connect() + + user = auth.find_recipient(address) + + print user, address + log.info("User for address %r: %r" % (address, user)) + + # Get the main, default backend + backend = conf.get('kolab', 'imap_backend') + + if len(address.split('@')) > 1: + domain = address.split('@')[1] + else: + domain = conf.get('kolab', 'primary_domain') + + if conf.has_section(domain) and conf.has_option(domain, 'imap_backend'): + backend = conf.get(domain, 'imap_backend') + + if conf.has_section(domain) and conf.has_option(domain, 'imap_uri'): + uri = conf.get(domain, 'imap_uri') + else: + uri = conf.get(backend, 'uri') + + hostname = None + port = None + + result = urlparse(uri) + + if hasattr(result, 'hostname'): + hostname = result.hostname + else: + scheme = uri.split(':')[0] + (hostname, port) = uri.split('/')[2].split(':') + + port = 4190 + + # Get the credentials + admin_login = conf.get(backend, 'admin_login') + admin_password = conf.get(backend, 'admin_password') + + import sievelib.managesieve + + sieveclient = sievelib.managesieve.Client(hostname, port, conf.debuglevel > 8) + sieveclient.connect(None, None, True) + sieveclient._plain_authentication(admin_login, admin_password, address) + sieveclient.authenticated = True + + result = sieveclient.listscripts() + + if result == None: + active = None + scripts = [] + else: + active, scripts = result + + + print "Found the following scripts: %s" % (','.join(scripts)) + print "And the following script is active: %s" % (active) + + mgmt_required_extensions = [] + + mgmt_script = """# +# MANAGEMENT +# +""" + + user = auth.get_entry_attributes(domain, user, ['*']) + + # + # Vacation settings (a.k.a. Out of Office) + # + vacation_active = None + vacation_text = None + vacation_uce = None + vacation_noreact_domains = None + vacation_react_domains = None + + vacation_active_attr = conf.get('sieve', 'vacation_active_attr') + vacation_text_attr = conf.get('sieve', 'vacation_text_attr') + vacation_uce_attr = conf.get('sieve', 'vacation_uce_attr') + vacation_noreact_domains_attr = conf.get('sieve', 'vacation_noreact_domains_attr') + vacation_react_domains_attr = conf.get('sieve', 'vacation_react_domains_attr') + + if not vacation_text_attr == None: + + if user.has_key(vacation_active_attr): + vacation_active = utils.true_or_false(user[vacation_active_attr]) + else: + vacation_active = False + + if user.has_key(vacation_text_attr): + vacation_text = user[vacation_text_attr] + else: + vacation_active = False + + if user.has_key(vacation_uce_attr): + vacation_uce = utils.true_or_false(user[vacation_uce_attr]) + else: + vacation_uce = False + + if user.has_key(vacation_react_domains_attr): + if isinstance(user[vacation_react_domains_attr], list): + vacation_react_domains = user[vacation_react_domains_attr] + else: + vacation_react_domains = [ user[vacation_react_domains_attr] ] + else: + if user.has_key(vacation_noreact_domains_attr): + if isinstance(user[vacation_noreact_domains_attr], list): + vacation_noreact_domains = user[vacation_noreact_domains_attr] + else: + vacation_noreact_domains = [ user[vacation_noreact_domains_attr] ] + else: + vacation_noreact_domains = [] + + # + # Delivery to Folder + # + dtf_active_attr = conf.get('sieve', 'deliver_to_folder_active') + if not dtf_active_attr == None: + if user.has_key(dtf_active_attr): + dtf_active = utils.true_or_false(user[dtf_active_attr]) + else: + dtf_active = False + else: + # TODO: Not necessarily de-activated, the *Active attributes are + # not supposed to charge this - check the deliver_to_folder_attr + # attribute value for a value. + dtf_active = False + + if dtf_active: + dtf_folder_name_attr = conf.get('sieve', 'deliver_to_folder_attr') + if not dtf_folder_name_attr == None: + if user.has_key(dtf_folder_name_attr): + dtf_folder = user[dtf_folder_name_attr] + else: + log.warning(_("Delivery to folder active, but no folder name attribute available for user %r") % (user)) + dtf_active = False + else: + log.error(_("Delivery to folder active, but no folder name attribute configured")) + dtf_active = False + + # + # Folder name to delivery spam to. + # + # Global or local. + # + sdf_filter = True + sdf = conf.get('sieve', 'spam_global_folder') + + if sdf == None: + sdf = conf.get('sieve', 'spam_personal_folder') + if sdf == None: + sdf_filter = False + + # + # Mail forwarding + # + forward_active = None + forward_addresses = [] + forward_keepcopy = None + forward_uce = None + + forward_active_attr = conf.get('sieve', 'forward_address_active') + if not forward_active_attr == None: + if user.has_key(forward_active_attr): + forward_active = utils.true_or_false(user[forward_active_attr]) + else: + forward_active = False + + if not forward_active == False: + forward_address_attr = conf.get('sieve', 'forward_address_attr') + if user.has_key(forward_address_attr): + if isinstance(user[forward_address_attr], basestring): + forward_addresses = [ user[forward_address_attr] ] + elif isinstance(user[forward_address_attr], str): + forward_addresses = [ user[forward_address_attr] ] + else: + forward_addresses = user[forward_address_attr] + + if len(forward_addresses) == 0: + forward_active = False + + forward_keepcopy_attr = conf.get('sieve', 'forward_keepcopy_active') + if not forward_keepcopy_attr == None: + if user.has_key(forward_keepcopy_attr): + forward_keepcopy = utils.true_or_false(user[forward_keepcopy_attr]) + else: + forward_keepcopy = False + + forward_uce_attr = conf.get('sieve', 'forward_uce_active') + if not forward_uce_attr == None: + if user.has_key(forward_uce_attr): + forward_uce = utils.true_or_false(user[forward_uce_attr]) + else: + forward_uce = False + + if vacation_active: + mgmt_required_extensions.append('vacation') + mgmt_required_extensions.append('envelope') + + if dtf_active: + mgmt_required_extensions.append('fileinto') + + if forward_active and (len(forward_addresses) > 1 or forward_keepcopy): + mgmt_required_extensions.append('copy') + + if sdf_filter: + mgmt_required_extensions.append('fileinto') + + import sievelib.factory + + mgmt_script = sievelib.factory.FiltersSet("MANAGEMENT") + + for required_extension in mgmt_required_extensions: + mgmt_script.require(required_extension) + + if vacation_active: + if len(vacation_react_domains) > 0: + mgmt_script.addfilter( + 'vacation', + [('envelope', ':domain', ":is", "from", vacation_react_domains)], + [ + ( + "vacation", + ":days", 1, + ":subject", + "Out of Office", + # ":handle", see http://tools.ietf.org/html/rfc5230#page-4 + # ":mime", to indicate the reason is in fact MIME + vacation_text + ) + ] + ) + + elif len(vacation_noreact_domains) > 0: + mgmt_script.addfilter( + 'vacation', + [('not', ('envelope', ':domain', ":is", "from", vacation_noreact_domains))], + [ + ( + "vacation", + ":days", 1, + ":subject", + "Out of Office", + # ":handle", see http://tools.ietf.org/html/rfc5230#page-4 + # ":mime", to indicate the reason is in fact MIME + vacation_text + ) + ] + ) + + else: + mgmt_script.addfilter( + 'vacation', + [('true',)], + [ + ( + "vacation", + ":days", 1, + ":subject", + "Out of Office", + # ":handle", see http://tools.ietf.org/html/rfc5230#page-4 + # ":mime", to indicate the reason is in fact MIME + vacation_text + ) + ] + ) + + if forward_active: + forward_rules = [] + + # Principle can be demonstrated by: + # + # python -c "print ','.join(['a','b','c'][:-1])" + # + for forward_copy in forward_addresses[:-1]: + forward_rules.append(("redirect", ":copy", forward_copy)) + + if forward_keepcopy: + # Principle can be demonstrated by: + # + # python -c "print ','.join(['a','b','c'][-1])" + # + if forward_uce: + rule_name = 'forward-uce-keepcopy' + else: + rule_name = 'forward-keepcopy' + + forward_rules.append(("redirect", ":copy", forward_addresses[-1])) + else: + if forward_uce: + rule_name = 'forward-uce' + else: + rule_name = 'forward' + + forward_rules.append(("redirect", forward_addresses[-1])) + + if forward_uce: + mgmt_script.addfilter(rule_name, ['true'], forward_rules) + + else: + mgmt_script.addfilter(rule_name, [("X-Spam-Status", ":matches", "No,*")], forward_rules) + + if sdf_filter: + mgmt_script.addfilter('spam_delivery_folder', [("X-Spam-Status", ":matches", "Yes,*")], [("fileinto", "INBOX/Spam"), ("stop")]) + + mgmt_script = mgmt_script.__str__() + + result = sieveclient.putscript("MANAGEMENT", mgmt_script) + + if not result: + print "Putting in script MANAGEMENT failed...?" + else: + print "Putting in script MANAGEMENT succeeded" + + user_script = """# +# User +# + +require ["include"]; +""" + + for script in scripts: + if not script in [ "MASTER", "MANAGEMENT", "USER" ]: + print "Including script %s in USER" % (script) + user_script = """%s + + include :personal "%s"; + """ % (user_script, script) + + result = sieveclient.putscript("USER", user_script) + if not result: + print "Putting in script USER failed...?" + else: + print "Putting in script USER succeeded" + + result = sieveclient.putscript("MASTER", """# +# MASTER +# +# This file is authoritative for your system and MUST BE KEPT ACTIVE. +# +# Altering it is likely to render your account dysfunctional and may +# be violating your organizational or corporate policies. +# +# For more information on the mechanism and the conventions behind +# this script, see http://wiki.kolab.org/KEP:14 +# + +require ["include"]; + +# OPTIONAL: Includes for all or a group of users +# include :global "all-users"; +# include :global "this-group-of-users"; + +# The script maintained by the general management system +include :personal "MANAGEMENT"; + +# The script(s) maintained by one or more editors available to the user +include :personal "USER"; +""") + + if not result: + print "Putting in script MASTER failed...?" + else: + print "Putting in script MASTER succeeded" + + sieveclient.setactive("MASTER") + |