summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com>2012-12-04 15:19:02 +0000
committerJeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com>2012-12-04 15:19:02 +0000
commitbfe246d821d4a065c4036f00083ce1ddd9df0e8a (patch)
tree90cc6a5a0ba95095e21f3503059cf79ac7636095
parentf868fb47ce41acc21de706ef3d92b30bbd5e261f (diff)
downloadpykolab-bfe246d821d4a065c4036f00083ce1ddd9df0e8a.tar.gz
Add our first version of a plugin for sievemgmt that we can execute the necessary hooks for when needed
-rw-r--r--pykolab/Makefile.am4
-rw-r--r--pykolab/plugins/sievemgmt/__init__.py424
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")
+