From 3e858dd88018841a83bb912214d95ca024957824 Mon Sep 17 00:00:00 2001 From: Daniel Berteaud Date: Thu, 31 Oct 2019 11:16:16 +0100 Subject: [PATCH] Add support for managing domain aliases from the yaml config file --- zmldapsync/README.md | 16 ++++-- zmldapsync/zmldapsync.pl | 106 ++++++++++++++++++++++++++------------- 2 files changed, 85 insertions(+), 37 deletions(-) diff --git a/zmldapsync/README.md b/zmldapsync/README.md index a36f9bf..b3d4d2b 100644 --- a/zmldapsync/README.md +++ b/zmldapsync/README.md @@ -1,4 +1,4 @@ -# LDAP synchronisation +# LDAP synchronisation and domain provisioning This script brings a complete synchronization of user accounts and groups from an external LDAP server. @@ -12,11 +12,17 @@ The goals are : * Handle email alias defined in LDAP, and translate them into aliases in Zimbra * Allow objects (aliase, distribution list) to be created directly in Zimbra. Objects coming from LDAP are synchronized, including alias previously defined in LDAP which aren't anymore are removed from Zimbra. But aliases defined directly in Zimbra won't be touched. Same is true for distribution lists. So you can mix LDAP defined and Zimbra defined configuration +A few other features are included, like : + * Creating domains + * Creating / removing domain aliases + +All from a simple yaml configuration file + ## Configuration -The configuration is stored in a single file in YAML format. The script will look for a config at /opt/zimbra/conf/zmldapsync.yml or trhe one specified in the --config argument. +The configuration is stored in a single file in YAML format. The script will look for a config at /opt/zimbra/conf/zmldapsync.yml or the one specified in the --config argument. -The config has two main section : +The config has two main sections : * general : settings which affects all domains, mainly to configure email notification in case of error * domains : list of domain to sync, and the settings for each of them @@ -142,6 +148,10 @@ domains: # If the domain in Zimbra exists but is not configured # for external auth (either LDAP or AD), should this script configure it ? setup_ldap_auth: True + # If defined, domain aliases will be added to / removed from Zimbra according to this list + domain_aliases: + - mail.corp2.com + - corp4.net ``` ## Command line diff --git a/zmldapsync/zmldapsync.pl b/zmldapsync/zmldapsync.pl index 1c2fbf6..5c3ea37 100644 --- a/zmldapsync/zmldapsync.pl +++ b/zmldapsync/zmldapsync.pl @@ -84,35 +84,34 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) { $conf->{domains}->{$domain} = get_default_conf( $conf->{domains}->{$domain} ); # Search in Zimbra LDAP if the required domain exists - my $zim_domain_search = $zim_ldap->ldap->search( - filter => "(&(objectClass=zimbraDomain)(zimbraDomainName=$domain)(!(zimbraDomainAliasTargetId=*)))", - attrs => [ - 'zimbraDomainName', - 'zimbraDomainType', - 'zimbraId', - 'zimbraAuthMechAdmin', - 'zimbraAuthMech', - 'zimbraAuthLdapSearchBindDn', - 'zimbraAuthLdapSearchBindPassword', - 'zimbraAuthLdapSearchFilter' - ] - ); + my $zim_domain_search = search_zim_domain($domain); + + if ( not defined $zim_domain_search ) { + handle_error( + $domain, + 'Zimbra domain lookup', + 'Search returned an empty object' + ); + next DOMAIN; + } if ( $zim_domain_search->code ) { handle_error( $domain, 'Zimbra domain lookup', $zim_domain_search->error ); - next DOMAIN + next DOMAIN; } # We must have exactly 1 result if ( scalar $zim_domain_search->entries == 0 ) { if ( yaml_bool($conf->{domains}->{$domain}->{zimbra}->{create_if_missing}) ) { log_info( "Creating domain $domain" ); - ZmClient::sendZmprovRequest( "createDomain $domain " . + send_zmprov_cmd( "createDomain $domain " . build_domain_attrs($conf->{domains}->{$domain}) ); + # Now that we have created the domain, lets lookup again + $zim_domain_search = search_zim_domain($domain); } else { handle_error( $domain, @@ -148,23 +147,23 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) { } } - # Now lookup for domain aliases defined in Zimbra - my $zim_domain_alias_search = $zim_ldap->ldap->search( - filter => "(&(objectClass=zimbraDomain)(zimbraDomainAliasTargetId=" . $domain_entry->{zimbraId} . "))" - ); - if ( $zim_domain_alias_search->code ) { - handle_error( - $domain, - 'Zimbra domain alias lookup', - $zim_domain_alias_search->error - ); - next DOMAIN; - } + @{ $domain_entry->{zimbraDomainAliases} } = get_domain_aliases( $domain_entry ); - $domain_entry->{zimbraDomainAliases} = []; - foreach my $alias ( $zim_domain_alias_search->entries ) { - push @{ $domain_entry->{zimbraDomainAliases} }, - $alias->get_value('zimbraDomainName'); + if ( defined $conf->{domains}->{$domain}->{zimbra}->{domain_aliases} ) { + log_verbose( "Comparing domain aliases" ); + my $aliases_diff = Array::Diff->diff( $domain_entry->{zimbraDomainAliases}, $conf->{domains}->{$domain}->{zimbra}->{domain_aliases} ); + foreach my $alias (@{ $aliases_diff->added } ) { + log_info( "Creating domain alias $alias for domain $domain" ); + send_zmprov_cmd( "createAliasDomain $alias $domain" ); + } + foreach my $alias (@{ $aliases_diff->deleted } ) { + log_info( "Removing domain alias $alias for domain $domain" ); + send_zmprov_cmd( "deleteDomain $alias" ); + } + # Make a new lookup if changes were made + if ( $aliases_diff->count > 0 ) { + @{ $domain_entry->{zimbraDomainAliases} } = get_domain_aliases( $domain_entry ); + } } log_verbose( "Trying to connect to " . @@ -747,7 +746,7 @@ sub handle_error { $exit = 255; } -# ldap2hashref takes three args +# ldap2hashref takes four args # * An LDAP search result # * The attribute used as the key of objects # * An optional array of attributes we want as an array, even if there's a single value @@ -868,6 +867,44 @@ sub parse_zimbra_notes { return $return; } +# Search for a specific domain +sub search_zim_domain { + my $dom = shift; + # Search in Zimbra LDAP if the required domain exists + my $zim_domain_search = $zim_ldap->ldap->search( + filter => "(&(objectClass=zimbraDomain)(zimbraDomainName=$dom)(!(zimbraDomainAliasTargetId=*)))", + attrs => [ + 'zimbraDomainName', + 'zimbraDomainType', + 'zimbraId', + 'zimbraAuthMechAdmin', + 'zimbraAuthMech', + 'zimbraAuthLdapSearchBindDn', + 'zimbraAuthLdapSearchBindPassword', + 'zimbraAuthLdapSearchFilter' + ] + ); + return $zim_domain_search; +} + +# Get a list of aliases for a domain +# Takes a hashref representing a domain entry as argument +sub get_domain_aliases { + my $dom = shift; + my @aliases = (); + + # Now lookup for domain aliases defined in Zimbra + my $zim_domain_alias_search = $zim_ldap->ldap->search( + filter => "(&(objectClass=zimbraDomain)(zimbraDomainAliasTargetId=" . $dom->{zimbraId} . "))" + ); + + foreach my $alias ( $zim_domain_alias_search->entries ) { + push @aliases, $alias->get_value('zimbraDomainName'); + } + + return @aliases; +} + # Set default config values if missing sub get_default_conf { my $conf = shift; @@ -999,8 +1036,9 @@ sub get_default_conf { } $defaults->{zimbra} = { - create_if_missing => 0, - setup_ldap_auth => 0 + create_if_missing => 0, + setup_ldap_auth => 0, + domain_aliases => undef, }; # If some attribute mapping is defined in the provided conf