summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Boddie <paul@boddie.org.uk>2014-08-07 12:27:48 (GMT)
committerPaul Boddie <paul@boddie.org.uk>2014-08-07 12:27:48 (GMT)
commit6358a0341f9e7afbcad0fa470d9a9ad873eea897 (patch)
tree323d88f96087001941ba9ea339de08a6027c89ab
parent62b1ad3a00c52ba370a4a5395bfc543c39e35ffe (diff)
downloadpykolab-6358a0341f9e7afbcad0fa470d9a9ad873eea897.tar.gz
Added ejabberd support.
-rw-r--r--pykolab/setup/setup_ejabberd.py250
1 files changed, 250 insertions, 0 deletions
diff --git a/pykolab/setup/setup_ejabberd.py b/pykolab/setup/setup_ejabberd.py
new file mode 100644
index 0000000..195c1f4
--- /dev/null
+++ b/pykolab/setup/setup_ejabberd.py
@@ -0,0 +1,250 @@
+# -*- coding: utf-8 -*-
+# Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com)
+# Copyright 2014 Paul Boddie <paul@boddie.org.uk>
+#
+# 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, 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 <http://www.gnu.org/licenses/>.
+#
+
+from os.path import join, isfile
+from shutil import copy
+import re
+
+import components
+
+import pykolab
+
+from pykolab import utils
+from pykolab.constants import *
+from pykolab.setup.services import *
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.setup')
+conf = pykolab.getConf()
+
+def __init__():
+ components.register(
+ 'ejabberd',
+ execute,
+ description=description(),
+ after=['ldap']
+ )
+
+def description():
+ return _("Setup ejabberd.")
+
+class symbol:
+ "A symbol, not a string, for the configuration file."
+ def __init__(self, s):
+ self.s = s
+ def __str__(self):
+ return self.s
+
+def execute(*args, **kw):
+
+ prefix = "/etc/ejabberd"
+ config = join(prefix, "ejabberd.cfg")
+ backup = join(prefix, "ejabberd.cfg.orig")
+
+ if not isfile(config):
+ if conf.check_only:
+ utils.setup_status("ejabberd", _("not installed"))
+ return
+ else:
+ log.error(_("ejabberd is not installed on this system"))
+ sys.exit(1)
+ elif not isfile(backup):
+ copy(config, backup)
+
+ # Define the settings values.
+ # See: http://www.process-one.net/docs/ejabberd/guide_en.html#htoc35
+ # See: http://www.process-one.net/docs/ejabberd/guide_en.html#htoc60
+
+ # NOTE: May want to add other hostnames for which the server should respond.
+
+ hosts = ["localhost", conf.get('kolab', 'primary_domain')]
+
+ settings = [
+ ('auth_method', symbol('ldap')),
+ ('ldap_uids', [('alias', '%%u@%s' % conf.get('kolab', 'primary_domain'))]),
+ ('ldap_servers', [get_host_from_url(conf.get('ldap', 'ldap_uri'))]),
+ ('ldap_encrypt', None),
+ ('ldap_rootdn', conf.get('ldap', 'service_bind_dn')),
+ ('ldap_password', conf.get('ldap', 'service_bind_pw')),
+ ('ldap_base', conf.get('ldap', 'kolab_user_base_dn')),
+ ('ldap_filter', conf.get('ldap', 'kolab_user_filter')),
+ ('hosts', hosts),
+ ]
+
+ # Define required modules.
+
+ modules = [
+ ('mod_http_bind', []),
+ ('mod_shared_roster_ldap', [
+ (symbol('ldap_base'), 'ou=People,dc=example,dc=com'),
+ (symbol('ldap_rfilter'), '(objectClass=kolabinetorgperson)'),
+ (symbol('ldap_memberattr'), 'uid'),
+ (symbol('ldap_userdesc'), 'cn'),
+ (symbol('ldap_filter'), '(objectClass=kolabinetorgperson)'),
+ (symbol('ldap_useruid'), 'uid')
+ ]),
+ ]
+
+ # Determine whether the configuration details are already present.
+
+ f = open(config)
+ try:
+ text = f.read()
+ finally:
+ f.close()
+
+ # Replace the top-level settings.
+
+ have_settings, text = replace_settings(settings, text)
+
+ # Find the module setting.
+
+ modules_def = find_modules(text)
+
+ if not modules_def:
+ log.error(_("Modules setting not found in ejabberd."))
+ else:
+ # Replace individual module settings.
+
+ (modules_start, modules_end), modules_text = modules_def
+ have_module_settings, modules_text = replace_settings(modules, modules_text, False)
+
+ # Replace the modules definition if changed.
+
+ if not have_module_settings:
+ text = "%s{modules,\n [%s\n ]}.%s" % (text[:modules_start], modules_text, text[modules_end:])
+ have_settings = False
+
+ if conf.check_only:
+ utils.setup_status("ejabberd", have_settings and _("setup done") or _("needs setup"))
+ return
+
+ if not have_settings:
+ f = open(config, "w")
+ try:
+ f.write(text)
+ finally:
+ f.close()
+
+def to_erlang_value(value, indent=""):
+
+ """
+ See: http://www.erlang.org/doc/reference_manual/introduction.html#id62972
+ See: http://www.erlang.org/doc/reference_manual/data_types.html
+ """
+
+ if isinstance(value, symbol):
+ return '%s' % value
+ elif isinstance(value, str):
+ return '"%s"' % value.replace('"', '\\"')
+ elif isinstance(value, unicode):
+ return '"%s"' % value.replace('"', '\\"').encode("utf-8")
+ elif isinstance(value, tuple):
+ return '{%s}' % ", ".join([to_erlang_value(v) for v in value])
+ elif isinstance(value, list):
+ if len(value) > 1:
+ return '[\n%s\n%s ]' % (",\n".join([("%s %s" % (indent, to_erlang_value(v))) for v in value]), indent)
+ else:
+ return '[%s]' % ", ".join([to_erlang_value(v) for v in value])
+ elif isinstance(value, bool):
+ return value and 'true' or 'false'
+ elif isinstance(value, (int, float)):
+ return str(value)
+ elif value is None:
+ return 'none'
+ else:
+ raise ValueError, value
+
+def replace_settings(settings, text, top_level=True):
+ have_settings = True
+ new_settings = []
+
+ for name, value in settings:
+ value = to_erlang_value(value, top_level and "" or " ")
+
+ setting_text = (
+ top_level and ejabberd_top_level_setting or ejabberd_setting
+ ) % {
+ "name" : name,
+ "value" : value
+ }
+ details = (top_level and find_setting or find_module_setting)(name, text)
+ if details is None:
+ have_settings = False
+ new_settings.append(setting_text)
+ else:
+ (start, end), indent, comment, _name, _value = details
+ if comment or value != _value:
+ have_settings = False
+ text = text[:start] + setting_text + text[end:]
+
+ # Add right on the end of any settings list.
+
+ if not top_level:
+ text = text.rstrip()
+ need_comma = text and not text.endswith(",")
+ else:
+ need_comma = False
+
+ # Add each new setting to the end of the file or list.
+
+ for setting_text in new_settings:
+ if need_comma:
+ text += ",\n"
+ need_comma = False
+ else:
+ text += "\n"
+ text += setting_text
+
+ # Prevent trailing commas: Erlang doesn't like them.
+
+ if not top_level:
+ text = text.rstrip(",")
+
+ return have_settings, text
+
+# Short of writing an Erlang parser, here are some functions to find top-level
+# settings, the modules setting, and individual module settings.
+
+def find_setting(name, text):
+ m = re.search(r"^(?P<comment>%%[ \t]*)?{(?P<name>" + name + "),\s*(?P<value>.*?)}\.", text, re.MULTILINE | re.DOTALL)
+ if m:
+ return m.span(), "", m.group("comment"), m.group("name"), m.group("value")
+ else:
+ return None
+
+def find_modules(text):
+ m = re.search(r"^{modules,\s*\[(?P<modules>.*?)\]}\.", text, re.MULTILINE | re.DOTALL)
+ if m:
+ return m.span(), m.group("modules")
+ else:
+ return None
+
+def find_module_setting(name, text):
+ m = re.search(r"^(?P<indent>[ \t]*)(?P<comment>%%[ \t]*)?{(?P<name>" + name + "),\s*(?P<values>\[.*?\])},?$", text, re.MULTILINE | re.DOTALL)
+ if m:
+ return m.span(), m.group("indent"), m.group("comment"), m.group("name"), m.group("values")
+ else:
+ return None
+
+# Data used by the above code.
+
+ejabberd_top_level_setting = "{%(name)s, %(value)s}."
+ejabberd_setting = " {%(name)s, %(value)s},"