From 75a8688456c96619200dc97bfabbde2cd2975378 Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Thu, 4 May 2017 16:31:43 +0200 Subject: [PATCH] Performing configuration and collecting existing and LDAP aliases --- pp_lib/barracuda_sync_app.py | 221 +++++++++++++++++++++++++++++++++-- 1 file changed, 213 insertions(+), 8 deletions(-) diff --git a/pp_lib/barracuda_sync_app.py b/pp_lib/barracuda_sync_app.py index 9d68ee8..0dfc5cf 100644 --- a/pp_lib/barracuda_sync_app.py +++ b/pp_lib/barracuda_sync_app.py @@ -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__": -- 2.39.5