]> Frank Brehm's Git Trees - pixelpark/admin-tools.git/commitdiff
Performing configuration and collecting existing and LDAP aliases
authorFrank Brehm <frank.brehm@pixelpark.com>
Thu, 4 May 2017 14:31:43 +0000 (16:31 +0200)
committerFrank Brehm <frank.brehm@pixelpark.com>
Thu, 4 May 2017 14:31:43 +0000 (16:31 +0200)
pp_lib/barracuda_sync_app.py

index 9d68ee8d297d3d446e05678334671471ed494371..0dfc5cfc7d94951577d2fdcffc00c463009405c3 100644 (file)
@@ -18,6 +18,7 @@ import traceback
 import textwrap
 import copy
 import shutil
+import shlex
 import stat
 
 # Third party modules
@@ -26,6 +27,7 @@ import six
 import ldap3
 
 from ldap3 import ObjectDef, AttrDef, Reader, Writer
+from ldap3 import BASE, LEVEL, SUBTREE
 
 from ldap3.core.exceptions import LDAPKeyError
 
@@ -42,7 +44,7 @@ from .ldap_app import PpLdapAppError, PpLdapApplication
 
 from .mailaddress import MailAddress
 
-__version__ = '0.2.0'
+__version__ = '0.3.1'
 LOG = logging.getLogger(__name__)
 
 
@@ -62,24 +64,26 @@ class PpBarracudaSyncApp(PpLdapApplication):
     postfix_config_dir = os.sep + os.path.join('etc', 'postfix')
     postfix_maps_dir = os.path.join(postfix_config_dir, 'maps')
 
-    default_aliases_files = [
-        os.path.join(postfix_maps_dir, 'aliases'),
-    ]
-
     default_virtaliases_files = [
         os.path.join(postfix_maps_dir, 'virtual-aliases'),
     ]
 
     default_origin = 'pixelpark.com'
 
-    re_aliases_line = re.compile(r'^([^#\s:]+):', re.MULTILINE)
     re_virtaliases_line = re.compile(r'^([^#\s:]+)\s', re.MULTILINE)
+    re_root_address = re.compile(r'^root(?:@.*)?\s*$', re.IGNORECASE)
+
+    open_args = {}
+    if six.PY3:
+        open_args = {
+            'encoding': 'utf-8',
+            'errors': 'surrogateescape',
+        }
 
     # -------------------------------------------------------------------------
     def __init__(self, appname=None, version=__version__):
 
         self.barracuda_base_dn = self.default_barracuda_base_dn
-        self.aliases_files = copy.copy(self.default_aliases_files)
         self.virtaliases_files = copy.copy(self.default_virtaliases_files)
         self.origin = self.default_origin
 
@@ -91,7 +95,7 @@ class PpBarracudaSyncApp(PpLdapApplication):
         self._show_simulate_opt = True
 
         description = textwrap.dedent('''\
-            Synchronization of existing aliases and virtual aliases
+            Synchronization of existing virtual aliases
             with alias definitions in LDAP for Barracuda.
             ''').strip()
 
@@ -102,6 +106,124 @@ class PpBarracudaSyncApp(PpLdapApplication):
 
         self.initialized = True
 
+    # -------------------------------------------------------------------------
+    def as_dict(self, short=True):
+        """
+        Transforms the elements of the object into a dict
+
+        @param short: don't include local properties in resulting dict.
+        @type short: bool
+
+        @return: structure as dict
+        @rtype:  dict
+        """
+
+        res = super(PpBarracudaSyncApp, self).as_dict(short=short)
+        res['default_barracuda_base_dn'] = self.default_barracuda_base_dn
+        res['postfix_config_dir'] = self.postfix_config_dir
+        res['postfix_maps_dir'] = self.postfix_maps_dir
+        res['default_virtaliases_files'] = self.default_virtaliases_files
+        res['default_origin'] = self.default_origin
+        res['open_args'] = self.open_args
+
+        return res
+
+    # -------------------------------------------------------------------------
+    def init_arg_parser(self):
+        """
+        Method to initiate the argument parser.
+
+        This method should be explicitely called by all init_arg_parser()
+        methods in descendant classes.
+        """
+
+        self.arg_parser.add_argument(
+            '-P', '--postfix-dir',
+            metavar="DIR", dest='postfix_dir',
+            help="Configuration directory for Postfix (default: {!r}).".format(
+                self.postfix_config_dir)
+        )
+
+        super(PpBarracudaSyncApp, self).init_arg_parser()
+
+    # -------------------------------------------------------------------------
+    def perform_config(self):
+
+        super(PpBarracudaSyncApp, self).perform_config()
+
+        aliases_files = None
+        virtaliases_files = None
+
+        for section_name in self.cfg.keys():
+
+            if self.verbose > 2:
+                LOG.debug("Checking config section {!r} ...".format(section_name))
+
+            if section_name.lower() not in ('barracuda-sync', 'barracuda_sync', 'barracudasync') :
+                continue
+
+            section = self.cfg[section_name]
+            if self.verbose > 2:
+                LOG.debug("Evaluating config section {n!r}:\n{s}".format(
+                    n=section_name, s=pp(section)))
+
+            if 'postfix_dir' in section:
+                self._init_postfix_dir(section['postfix_dir'])
+
+            if 'virtaliases_files' in section:
+                v = section['virtaliases_files']
+                files = shlex.split(v)
+                if files:
+                    if virtaliases_files is None:
+                        virtaliases_files = []
+                    for f in files:
+                        if f not in virtaliases_files:
+                            virtaliases_files.append(f)
+
+            if 'base_dn' in section:
+                v = section['base_dn'].strip()
+                if v:
+                    self.barracuda_base_dn = v
+
+            if 'origin' in section:
+                v = section['origin'].strip()
+                if v:
+                    self.origin = v
+
+        if hasattr(self.args, 'postfix_dir') and self.args.postfix_dir:
+            self._init_postfix_dir(self.args.postfix_dir)
+
+        self._init_virtaliases_files(virtaliases_files)
+
+    # -------------------------------------------------------------------------
+    def _init_virtaliases_files(self, virtaliases_files):
+
+        self.virtaliases_files = copy.copy(self.default_virtaliases_files)
+        if virtaliases_files is None:
+            return
+
+        self.virtaliases_files = []
+        for afile in virtaliases_files:
+            if not os.path.isabs(afile):
+                afile = os.path.join(self.postfix_config_dir, afile)
+            afile = os.path.normpath(afile)
+            if afile not in self.virtaliases_files:
+                self.virtaliases_files.append(afile)
+
+    # -------------------------------------------------------------------------
+    def _init_postfix_dir(self, value):
+
+        if os.path.isdir(value):
+            d = os.path.abspath(value)
+            self.postfix_config_dir = d
+            self.postfix_maps_dir = os.path.join(d, 'maps')
+            self.default_aliases_files = [
+                os.path.join(self.postfix_maps_dir, 'aliases'),
+            ]
+            self.default_virtaliases_files = [
+                os.path.join(self.postfix_maps_dir, 'virtual-aliases'),
+            ]
+
     # -------------------------------------------------------------------------
     def pre_run(self):
         """
@@ -117,6 +239,89 @@ class PpBarracudaSyncApp(PpLdapApplication):
 
         LOG.info("Starting ...")
 
+        self.read_virtaliases_files()
+        self.existing_aliases.sort(key=str.lower)
+        LOG.info("Found {} existing aliases.".format(len(self.existing_aliases)))
+        if self.verbose > 1:
+            LOG.debug("Existing aliases:\n{}".format(pp(self.existing_aliases)))
+
+        self.read_ldap_aliases()
+
+        self.eval_diffs()
+
+    # -------------------------------------------------------------------------
+    def read_virtaliases_files(self):
+
+        LOG.info("Reading all virtual aliases files ...")
+        for afile in self.virtaliases_files:
+            if self.verbose > 1:
+                LOG.debug("Checking for virtaliases file {!r} ...".format(afile))
+            if not os.path.isfile(afile):
+                continue
+            content = ''
+            LOG.debug("Reading virtaliases file {!r} ...".format(afile))
+            with open(afile, 'r', **self.open_args) as fh:
+                content = fh.read()
+            aliases = self.re_virtaliases_line.findall(content)
+            for alias in aliases:
+                if self.re_root_address.match(alias):
+                    continue
+                if alias not in self.existing_aliases:
+                    if self.verbose > 2:
+                        LOG.debug("Registring existing alias {!r}.".format(alias))
+                    self.existing_aliases.append(alias)
+
+    # -------------------------------------------------------------------------
+    def read_ldap_aliases(self):
+
+        LOG.info("Reading all aliases from LDAP ...")
+
+        alias = ObjectDef(['mailRecipient'])
+        alias += ['cn', 'mail']
+
+        query_filter = '(&(objectclass=mailRecipient)(mail=*))'
+
+        entries = self.ldap_search_subtree(alias, query_filter, base=self.barracuda_base_dn)
+
+        for entry in entries:
+            dn = entry.entry_dn
+            cn = entry['cn'][0]
+            mail = entry['mail'][0]
+            if self.verbose > 3:
+                LOG.debug("Found LDAP alias, DN: {dn!r}, CN: {cn!r}, Mail: {m!r}.".format(
+                    dn=dn, cn=cn, m=mail))
+
+            if not cn:
+                continue
+            if cn not in self.ldap_aliases:
+                if self.verbose > 2:
+                    LOG.debug("Registring LDAP alias {!r}.".format(cn))
+                self.ldap_aliases.append(cn)
+
+        self.ldap_aliases.sort(key=str.lower)
+        LOG.info("Found {} LDAP aliases.".format(len(self.ldap_aliases)))
+        if self.verbose > 1:
+            LOG.debug("LDAP aliases:\n{}".format(pp(self.ldap_aliases)))
+
+    # -------------------------------------------------------------------------
+    def eval_diffs(self):
+
+        LOG.info("Evaluating differences ...")
+
+        self.aliases_to_create = []
+        self.aliases_to_remove = []
+
+        for alias in self.existing_aliases:
+            if alias not in self.ldap_aliases and alias not in self.aliases_to_create:
+                self.aliases_to_create.append(alias)
+
+        for alias in self.ldap_aliases:
+            if alias not in self.existing_aliases and alias not in self.aliases_to_remove:
+                self.aliases_to_remove.append(alias)
+
+        LOG.info("Aliases to create in LDAP:\n{}".format(pp(self.aliases_to_create)))
+        LOG.info("Aliases to remove from LDAP:\n{}".format(pp(self.aliases_to_remove)))
+
 # =============================================================================
 
 if __name__ == "__main__":