@ -92,7 +92,11 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
]
]
) ;
) ;
if ( $ zim_domain_search - > code ) {
if ( $ zim_domain_search - > code ) {
handle_error ( $ domain , 'Zimbra domain lookup' , $ zim_domain_search - > error ) ;
handle_error (
$ domain ,
'Zimbra domain lookup' ,
$ zim_domain_search - > error
) ;
next DOMAIN
next DOMAIN
}
}
@ -100,13 +104,23 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
if ( scalar $ zim_domain_search - > entries == 0 ) {
if ( scalar $ zim_domain_search - > entries == 0 ) {
if ( yaml_bool ( $ conf - > { domains } - > { $ domain } - > { zimbra } - > { create_if_missing } ) ) {
if ( yaml_bool ( $ conf - > { domains } - > { $ domain } - > { zimbra } - > { create_if_missing } ) ) {
log_info ( "Creating domain $domain" ) ;
log_info ( "Creating domain $domain" ) ;
ZmClient:: sendZmprovRequest ( "createDomain $domain " . build_domain_attrs ( $ conf - > { domains } - > { $ domain } ) ) ;
ZmClient:: sendZmprovRequest ( "createDomain $domain " .
build_domain_attrs ( $ conf - > { domains } - > { $ domain } )
) ;
} else {
} else {
handle_error ( $ domain , 'Zimbra domain lookup' , "Domain $domain doesn't exist in Zimbra" ) ;
handle_error (
$ domain ,
'Zimbra domain lookup' ,
"Domain $domain doesn't exist in Zimbra"
) ;
next DOMAIN ;
next DOMAIN ;
}
}
} elsif ( scalar $ zim_domain_search - > entries gt 1 ) {
} elsif ( scalar $ zim_domain_search - > entries gt 1 ) {
handle_error ( $ domain , 'Zimbra domain lookup' , "Found several matches for domain $domain" ) ;
handle_error (
$ domain ,
'Zimbra domain lookup' ,
"Found several matches for domain $domain"
) ;
next DOMAIN ;
next DOMAIN ;
}
}
@ -114,11 +128,18 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
my $ domain_entry = ldap2hashref ( $ zim_domain_search , 'zimbraDomainName' ) - > { $ domain } ;
my $ domain_entry = ldap2hashref ( $ zim_domain_search , 'zimbraDomainName' ) - > { $ domain } ;
# Check if auth is set to ad or ldap
# Check if auth is set to ad or ldap
if ( not defined $ domain_entry - > { zimbraAuthMech } or $ domain_entry - > { zimbraAuthMech } !~ m/^ad|ldap$/i ) {
if (
not defined $ domain_entry - > { zimbraAuthMech } or
$ domain_entry - > { zimbraAuthMech } !~ m/^ad|ldap$/i
) {
if ( yaml_bool ( $ conf - > { domains } - > { $ domain } - > { zimbra } - > { setup_ldap_auth } ) ) {
if ( yaml_bool ( $ conf - > { domains } - > { $ domain } - > { zimbra } - > { setup_ldap_auth } ) ) {
send_zmprov_cmd ( "modifyDomain $domain " . build_domain_attrs ( $ conf - > { domains } - > { $ domain } ) ) ;
send_zmprov_cmd ( "modifyDomain $domain " . build_domain_attrs ( $ conf - > { domains } - > { $ domain } ) ) ;
} else {
} else {
handle_error ( $ domain , 'Domain external auth check' , "domain $domain must be configured for LDAP or AD authentication first" ) ;
handle_error (
$ domain ,
'Domain external auth check' ,
"domain $domain must be configured for LDAP or AD authentication first"
) ;
next DOMAIN ;
next DOMAIN ;
}
}
}
}
@ -128,16 +149,22 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
filter = > "(&(objectClass=zimbraDomain)(zimbraDomainAliasTargetId=" . $ domain_entry - > { zimbraId } . "))"
filter = > "(&(objectClass=zimbraDomain)(zimbraDomainAliasTargetId=" . $ domain_entry - > { zimbraId } . "))"
) ;
) ;
if ( $ zim_domain_alias_search - > code ) {
if ( $ zim_domain_alias_search - > code ) {
handle_error ( $ domain , 'Zimbra domain alias lookup' , $ zim_domain_alias_search - > error ) ;
handle_error (
$ domain ,
'Zimbra domain alias lookup' ,
$ zim_domain_alias_search - > error
) ;
next DOMAIN ;
next DOMAIN ;
}
}
$ domain_entry - > { zimbraDomainAliases } = [] ;
$ domain_entry - > { zimbraDomainAliases } = [] ;
foreach my $ alias ( $ zim_domain_alias_search - > entries ) {
foreach my $ alias ( $ zim_domain_alias_search - > entries ) {
push @ { $ domain_entry - > { zimbraDomainAliases } } , $ alias - > get_value ( 'zimbraDomainName' ) ;
push @ { $ domain_entry - > { zimbraDomainAliases } } ,
$ alias - > get_value ( 'zimbraDomainName' ) ;
}
}
log_verbose ( "Trying to connect to " . join ( ' or ' , @ { $ conf - > { domains } - > { $ domain } - > { ldap } - > { servers } } ) ) ;
log_verbose ( "Trying to connect to " .
join ( ' or ' , @ { $ conf - > { domains } - > { $ domain } - > { ldap } - > { servers } } ) ) ;
my $ ext_ldap = Net::LDAP - > new ( [ @ { $ conf - > { domains } - > { $ domain } - > { ldap } - > { servers } } ] ) ;
my $ ext_ldap = Net::LDAP - > new ( [ @ { $ conf - > { domains } - > { $ domain } - > { ldap } - > { servers } } ] ) ;
if ( not $ ext_ldap ) {
if ( not $ ext_ldap ) {
@ -185,14 +212,21 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
]
]
) ;
) ;
if ( $ zim_aliases_search - > code ) {
if ( $ zim_aliases_search - > code ) {
handle_error ( $ domain , 'Zimbra user and distribution lists alias lookup' , $ zim_aliases_search - > error ) ;
handle_error (
$ domain ,
'Zimbra user and distribution lists alias lookup' ,
$ zim_aliases_search - > error
) ;
next DOMAIN ;
next DOMAIN ;
}
}
$ zim_aliases - > { $ domain_alias } = ldap2hashref ( $ zim_aliases_search , 'uid' ) ;
$ zim_aliases - > { $ domain_alias } = ldap2hashref ( $ zim_aliases_search , 'uid' ) ;
}
}
log_verbose ( "Searching for potential users in " . $ conf - > { domains } - > { $ domain } - > { users } - > { base } . " matching filter " . $ conf - > { domains } - > { $ domain } - > { users } - > { filter } ) ;
log_verbose ( "Searching for potential users in " .
$ conf - > { domains } - > { $ domain } - > { users } - > { base } .
" matching filter " .
$ conf - > { domains } - > { $ domain } - > { users } - > { filter } ) ;
# List of attributes to fetch from LDAP
# List of attributes to fetch from LDAP
# First, we want all the attributes which are mapped to Zimbra fields
# First, we want all the attributes which are mapped to Zimbra fields
@ -207,6 +241,7 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
push $ fetch_attrs , $ conf - > { domains } - > { $ domain } - > { users } - > { $ _ } ;
push $ fetch_attrs , $ conf - > { domains } - > { $ domain } - > { users } - > { $ _ } ;
}
}
# Now we can run the lookup
my $ ext_user_search = $ ext_ldap - > search (
my $ ext_user_search = $ ext_ldap - > search (
base = > $ conf - > { domains } - > { $ domain } - > { users } - > { base } ,
base = > $ conf - > { domains } - > { $ domain } - > { users } - > { base } ,
filter = > $ conf - > { domains } - > { $ domain } - > { users } - > { filter } ,
filter = > $ conf - > { domains } - > { $ domain } - > { users } - > { filter } ,
@ -214,11 +249,16 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
) ;
) ;
if ( $ ext_user_search - > code ) {
if ( $ ext_user_search - > code ) {
handle_error ( $ domain , 'External LDAP user lookup' , $ ext_user_search - > error ) ;
handle_error (
$ domain ,
'External LDAP user lookup' ,
$ ext_user_search - > error
) ;
next DOMAIN ;
next DOMAIN ;
}
}
log_verbose ( "Found " . scalar $ ext_user_search - > entries . " users in external LDAP" ) ;
log_verbose ( "Found " . scalar $ ext_user_search - > entries .
" users in external LDAP" ) ;
log_verbose ( "Searching for users in Zimbra" ) ;
log_verbose ( "Searching for users in Zimbra" ) ;
@ -231,23 +271,37 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
'(mail=' . $ zim_ldap - > global - > get_value ( 'zimbraAmavisQuarantineAccount' ) . ')' .
'(mail=' . $ zim_ldap - > global - > get_value ( 'zimbraAmavisQuarantineAccount' ) . ')' .
'(uid=galsync*)(uid=admin))))' ,
'(uid=galsync*)(uid=admin))))' ,
attrs = > [
attrs = > [
( map { $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ _ } } keys $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } ) ,
( map { $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ _ } }
( 'uid' , 'zimbraAccountStatus' , 'zimbraAuthLdapExternalDn' , 'zimbraMailAlias' , 'mail' , 'zimbraNotes' )
keys $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } ) ,
( 'uid' ,
'zimbraAccountStatus' ,
'zimbraAuthLdapExternalDn' ,
'zimbraMailAlias' ,
'mail' ,
'zimbraNotes' )
]
]
) ;
) ;
if ( $ zim_user_search - > code ) {
if ( $ zim_user_search - > code ) {
handle_error ( $ domain , 'Zimbra users lookup' , $ zim_user_search - > error ) ;
handle_error (
$ domain ,
'Zimbra users lookup' ,
$ zim_user_search - > error
) ;
next DOMAIN ;
next DOMAIN ;
}
}
log_verbose ( "Found " . scalar $ zim_user_search - > entries . " users in Zimbra" ) ;
log_verbose ( "Found " . scalar $ zim_user_search - > entries .
" users in Zimbra" ) ;
log_verbose ( "Comparing the accounts" ) ;
log_verbose ( "Comparing the accounts" ) ;
my $ ext_users = ldap2hashref (
my $ ext_users = ldap2hashref (
$ ext_user_search ,
$ ext_user_search ,
$ conf - > { domains } - > { $ domain } - > { users } - > { key } ,
$ conf - > { domains } - > { $ domain } - > { users } - > { key } ,
( $ conf - > { domains } - > { $ domain } - > { users } - > { mail_attr } , $ conf - > { domains } - > { $ domain } - > { users } - > { alias_attr } )
(
$ conf - > { domains } - > { $ domain } - > { users } - > { mail_attr } ,
$ conf - > { domains } - > { $ domain } - > { users } - > { alias_attr }
)
) ;
) ;
my $ zim_users = ldap2hashref (
my $ zim_users = ldap2hashref (
$ zim_user_search ,
$ zim_user_search ,
@ -264,14 +318,17 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
# User exists in Zimbra, lets check its attribute are up to date
# User exists in Zimbra, lets check its attribute are up to date
foreach my $ attr ( keys $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } ) {
foreach my $ attr ( keys $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } ) {
if ( not defined $ ext_users - > { $ user } - > { $ attr } and not defined $ zim_users - > { $ user } - > { $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } } ) {
if ( not defined $ ext_users - > { $ user } - > { $ attr } and
not defined $ zim_users - > { $ user } - > { $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } } ) {
# Attr does not exist in external LDAP and in Zimbra, no need to continue comparing them
# Attr does not exist in external LDAP and in Zimbra, no need to continue comparing them
next ;
next ;
}
}
if ( $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } ne 'sn' and not defined $ ext_users - > { $ user } - > { $ attr } ) {
if ( $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } ne 'sn' and
not defined $ ext_users - > { $ user } - > { $ attr } ) {
# If the attribute doesn't exist in external LDAP, we must remove it from Zimbra.
# If the attribute doesn't exist in external LDAP, we must remove it from Zimbra.
# Except for sn which is mandatory in Zimbra
# Except for sn which is mandatory in Zimbra
$ attrs . = '-' . $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } . " '" . $ zim_users - > { $ user } - > { $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } } . "' " ;
$ attrs . = '-' . $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } . " " .
zim_attr_value ( $ zim_users - > { $ user } - > { $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } } ) ;
} elsif (
} elsif (
( $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } ne 'sn' and
( $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } ne 'sn' and
$ ext_users - > { $ user } - > { $ attr } ne ( $ zim_users - > { $ user } - > { $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } } || '' )
$ ext_users - > { $ user } - > { $ attr } ne ( $ zim_users - > { $ user } - > { $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } } || '' )
@ -280,12 +337,12 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
defined $ ext_users - > { $ user } - > { $ attr } and
defined $ ext_users - > { $ user } - > { $ attr } and
$ ext_users - > { $ user } - > { $ attr } ne ( $ zim_users - > { $ user } - > { $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } } || '' )
$ ext_users - > { $ user } - > { $ attr } ne ( $ zim_users - > { $ user } - > { $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } } || '' )
) {
) {
$ attrs . = " " . $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } . " " . zim_attr_value ( $ ext_users - > { $ user } - > { $ attr } ) ;
$ attrs . = " " . $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } . " " .
zim_attr_value ( $ ext_users - > { $ user } - > { $ attr } ) ;
log_verbose ( "Attribute $attr for user $user changed from " .
log_verbose ( "Attribute $attr for user $user changed from " .
$ zim_users - > { $ user } - > { $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } } .
$ zim_users - > { $ user } - > { $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } } .
" to " .
" to " .
$ ext_users - > { $ user } - > { $ attr }
$ ext_users - > { $ user } - > { $ attr } ) ;
) ;
}
}
}
}
@ -308,7 +365,8 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
foreach my $ attr ( keys $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } ) {
foreach my $ attr ( keys $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } ) {
next if ( not defined $ ext_users - > { $ user } - > { $ attr } or $ ext_users - > { $ user } - > { $ attr } eq '' ) ;
next if ( not defined $ ext_users - > { $ user } - > { $ attr } or $ ext_users - > { $ user } - > { $ attr } eq '' ) ;
$ attrs . = ' ' . $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } . " " . zim_attr_value ( $ ext_users - > { $ user } - > { $ attr } ) ;
$ attrs . = ' ' . $ conf - > { domains } - > { $ domain } - > { users } - > { attr_map } - > { $ attr } . " " .
zim_attr_value ( $ ext_users - > { $ user } - > { $ attr } ) ;
}
}
# The password won't be used because Zimbra is set to use external LDAP/AD auth
# The password won't be used because Zimbra is set to use external LDAP/AD auth
# But better to set it to a random value
# But better to set it to a random value
@ -318,11 +376,10 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
my @ ext_aliases = ( ) ;
my @ ext_aliases = ( ) ;
foreach my $ mail_attr ( qw( mail_attr alias_attr ) ) {
foreach my $ mail_attr ( qw( mail_attr alias_attr ) ) {
next if (
next if ( not defined $ conf - > { domains } - > { $ domain } - > { users } - > { $ mail_attr } or
not defined $ conf - > { domains } - > { $ domain } - > { users } - > { $ mail_attr } or
not defined $ ext_users - > { $ user } - > { $ conf - > { domains } - > { $ domain } - > { users } - > { $ mail_attr } } ) ;
not defined $ ext_users - > { $ user } - > { $ conf - > { domains } - > { $ domain } - > { users } - > { $ mail_attr } }
push @ ext_aliases ,
) ;
@ { $ ext_users - > { $ user } - > { $ conf - > { domains } - > { $ domain } - > { users } - > { $ mail_attr } } } ;
push @ ext_aliases , @ { $ ext_users - > { $ user } - > { $ conf - > { domains } - > { $ domain } - > { users } - > { $ mail_attr } } } ;
}
}
@ ext_aliases = sort @ ext_aliases ;
@ ext_aliases = sort @ ext_aliases ;
@ -335,23 +392,27 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
}
}
# On each sync, we register the list of LDAP aliases into Zimbra's LDAP in the zimbraNotes attribute
# On each sync, we register the list of LDAP aliases into Zimbra's LDAP in the zimbraNotes attribute
# We can compare if it has changed, and an d/remove the aliases accordingly
# We can compare if it has changed, and ad d/remove the aliases accordingly
# This is not very clean, but at least allows the script to be "stateless"
# This is not very clean, but at least allows the script to be "stateless"
# and only relies on LDAP content on both sides
# and only relies on LDAP content on both sides. If only zimbraAlias objectClass allowed zimbraNotes attribute
# it'd be easier
my $ ext_prev_aliases = parse_zimbra_notes ( $ zim_users - > { $ user } - > { zimbraNotes } || '' ) - > { LDAP_Aliases } ;
my $ ext_prev_aliases = parse_zimbra_notes ( $ zim_users - > { $ user } - > { zimbraNotes } || '' ) - > { LDAP_Aliases } ;
my @ ext_prev_aliases = ( defined $ ext_prev_aliases ) ? sort @ { $ ext_prev_aliases } : ( ) ;
my @ ext_prev_aliases = ( defined $ ext_prev_aliases ) ? sort @ { $ ext_prev_aliases } : ( ) ;
my $ alias_diff = Array::Diff - > diff ( \ @ ext_prev_aliases , \ @ ext_aliases ) ;
my $ alias_diff = Array::Diff - > diff ( \ @ ext_prev_aliases , \ @ ext_aliases ) ;
foreach my $ alias ( @ { $ alias_diff - > deleted } ) {
foreach my $ alias ( @ { $ alias_diff - > deleted } ) {
my ( $ al , $ dom ) = split /\@/ , $ alias ;
my ( $ al , $ dom ) = split /\@/ , $ alias ;
next if ( not defined $ zim_aliases - > { $ dom } or not defined $ zim_aliases - > { $ dom } - > { $ al } ) ;
next if ( not defined $ zim_aliases - > { $ dom } or
log_verbose ( "Removing LDAP alias $alias from user $user as it doesn't exist in LDAP anymore" ) ;
not defined $ zim_aliases - > { $ dom } - > { $ al } ) ;
log_verbose ( "Removing LDAP alias $alias from user $user " .
"as it doesn't exist in LDAP anymore" ) ;
send_zmprov_cmd ( "removeAccountAlias $user\@$domain $alias" ) ;
send_zmprov_cmd ( "removeAccountAlias $user\@$domain $alias" ) ;
}
}
my $ note = $ sync_from_ldap . "|LDAP_Aliases=" . join ( ',' , @ ext_aliases ) ;
my $ note = $ sync_from_ldap . "|LDAP_Aliases=" . join ( ',' , @ ext_aliases ) ;
if ( $ note ne ( $ zim_users - > { $ user } - > { zimbraNotes } || '' ) ) {
if ( $ note ne ( $ zim_users - > { $ user } - > { zimbraNotes } || '' ) ) {
send_zmprov_cmd ( "modifyAccount $user\@$domain zimbraNotes " . zim_attr_value ( $ note ) ) ;
send_zmprov_cmd ( "modifyAccount $user\@$domain zimbraNotes " .
zim_attr_value ( $ note ) ) ;
}
}
}
}
@ -364,12 +425,13 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
$ zim_users - > { $ user } - > { zimbraNotes } =~ m/^$sync_from_ldap/ and
$ zim_users - > { $ user } - > { zimbraNotes } =~ m/^$sync_from_ldap/ and
defined $ zim_users - > { $ user } - > { zimbraAccountStatus } and
defined $ zim_users - > { $ user } - > { zimbraAccountStatus } and
$ zim_users - > { $ user } - > { zimbraAccountStatus } =~ m/^active|lockout$/ ) {
$ zim_users - > { $ user } - > { zimbraAccountStatus } =~ m/^active|lockout$/ ) {
log_verbose ( "User $user doesn't exist in external LDAP anymore, locking it in Zimbra" ) ;
log_verbose ( "User $user doesn't exist in external LDAP anymore, " .
"locking it in Zimbra" ) ;
send_zmprov_cmd ( "modifyAccount $user\@$domain zimbraAccountStatus locked" ) ;
send_zmprov_cmd ( "modifyAccount $user\@$domain zimbraAccountStatus locked" ) ;
}
}
}
}
# Now, we try to sync groups in external LDAP into distribution list in Zimbra
# Now, we try to sync groups in external LDAP into distribution lists in Zimbra
if ( defined $ conf - > { domains } - > { $ domain } - > { groups } ) {
if ( defined $ conf - > { domains } - > { $ domain } - > { groups } ) {
log_verbose ( "Searching for potential groups in " .
log_verbose ( "Searching for potential groups in " .
@ -397,7 +459,8 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
next DOMAIN ;
next DOMAIN ;
}
}
log_verbose ( "Found " . scalar $ ext_group_search - > entries . " groups in external LDAP" ) ;
log_verbose ( "Found " . scalar $ ext_group_search - > entries .
" groups in external LDAP" ) ;
log_verbose ( "Searching for distribution lists in Zimbra" ) ;
log_verbose ( "Searching for distribution lists in Zimbra" ) ;
@ -406,23 +469,37 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
base = > 'ou=people,' . $ domain_entry - > { dn } ,
base = > 'ou=people,' . $ domain_entry - > { dn } ,
filter = > "(objectClass=zimbraDistributionList)" ,
filter = > "(objectClass=zimbraDistributionList)" ,
attrs = > [
attrs = > [
( map { $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } - > { $ _ } } keys $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } ) ,
( map { $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } - > { $ _ } }
( 'uid' , 'zimbraDistributionListSubscriptionPolicy' , 'zimbraDistributionListUnsubscriptionPolicy' ,
keys $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } ) ,
'zimbraMailForwardingAddress' , 'zimbraNotes' , 'zimbraMailStatus' , 'mail' )
(
'uid' ,
'zimbraDistributionListSubscriptionPolicy' ,
'zimbraDistributionListUnsubscriptionPolicy' ,
'zimbraMailForwardingAddress' ,
'zimbraNotes' ,
'zimbraMailStatus' ,
'mail'
)
]
]
) ;
) ;
if ( $ zim_dl_search - > code ) {
if ( $ zim_dl_search - > code ) {
handle_error ( $ domain , 'Zimbra distribution lists lookup' , $ zim_dl_search - > error ) ;
handle_error (
$ domain ,
'Zimbra distribution lists lookup' ,
$ zim_dl_search - > error
) ;
next DOMAIN ;
next DOMAIN ;
}
}
log_verbose ( "Found " . scalar $ zim_dl_search - > entries . " distribution list(s) in Zimbra" ) ;
log_verbose ( "Found " . scalar $ zim_dl_search - > entries .
" distribution list(s) in Zimbra" ) ;
log_verbose ( "Comparing groups with distribution lists" ) ;
log_verbose ( "Comparing groups with distribution lists" ) ;
my $ ext_groups = ldap2hashref (
my $ ext_groups = ldap2hashref (
$ ext_group_search ,
$ ext_group_search ,
$ conf - > { domains } - > { $ domain } - > { groups } - > { key } ,
$ conf - > { domains } - > { $ domain } - > { groups } - > { key } ,
( $ conf - > { domains } - > { $ domain } - > { groups } - > { members_attr } ,
(
$ conf - > { domains } - > { $ domain } - > { groups } - > { members_attr } ,
$ conf - > { domains } - > { $ domain } - > { groups } - > { mail_attr } ,
$ conf - > { domains } - > { $ domain } - > { groups } - > { mail_attr } ,
$ conf - > { domains } - > { $ domain } - > { groups } - > { alias_attr }
$ conf - > { domains } - > { $ domain } - > { groups } - > { alias_attr }
)
)
@ -434,53 +511,49 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
) ;
) ;
# Build a dn2id hashref to lookup users or groups by their DN
# Build a dn2id hashref to lookup users or groups by their DN
my $ dn2id = { } ;
my $ dn2id = { } ;
$ dn2id - > { $ ext_users - > { $ _ } - > { dn } } = $ _ foreach ( keys $ ext_users ) ;
$ dn2id - > { $ ext_users - > { $ _ } - > { dn } } = $ _ foreach ( keys $ ext_users ) ;
$ dn2id - > { $ ext_groups - > { $ _ } - > { dn } } = $ _ foreach ( keys $ ext_groups ) ;
$ dn2id - > { $ ext_groups - > { $ _ } - > { dn } } = $ _ foreach ( keys $ ext_groups ) ;
# First loop, check if every group in LDAP exist as a DL in Zimbra
# First loop, check if every group in LDAP exists as a DL in Zimbra
foreach my $ group ( keys $ ext_groups ) {
foreach my $ group ( keys $ ext_groups ) {
if ( defined $ zim_dl - > { $ group } ) {
if ( defined $ zim_dl - > { $ group } ) {
# A group match an existing DL, we must check its attributes
# A group match an existing DL, we must check its attributes
my $ attrs = '' ;
my $ attrs = '' ;
foreach my $ attr ( keys $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } ) {
foreach my $ attr ( keys $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } ) {
if (
if ( not defined $ ext_groups - > { $ group } - > { $ attr } and
not defined $ ext_groups - > { $ group } - > { $ attr } and
not defined $ zim_dl - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } - > { $ attr } } ) {
not defined $ zim_dl - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } - > { $ attr } }
) {
# Attr does not exist in external LDAP and in Zimbra, not need to continue
# Attr does not exist in external LDAP and in Zimbra, not need to continue
next ;
next ;
} elsif ( not defined $ ext_groups - > { $ group } - > { $ attr } ) {
} elsif ( not defined $ ext_groups - > { $ group } - > { $ attr } ) {
# Attr doesn't exist in external LDAP, but exists in Zimbra. We must remove it
# Attr doesn't exist in external LDAP, but exists in Zimbra. We must remove it
$ attrs = ' -' . $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } - > { $ attr } . " " . zim_attr_value ( $ zim_dl - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } - > { $ attr } } ) ;
$ attrs = ' -' . $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } - > { $ attr } . " " .
zim_attr_value ( $ zim_dl - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } - > { $ attr } } ) ;
} elsif ( $ ext_groups - > { $ group } - > { $ attr } ne $ zim_dl - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } - > { $ attr } } ) {
} elsif ( $ ext_groups - > { $ group } - > { $ attr } ne $ zim_dl - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } - > { $ attr } } ) {
# Attr exists in both but doesn't match
# Attr exists in both but doesn't match
$ attrs . = " " . $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } - > { $ attr } . " " . zim_attr_value ( $ ext_groups - > { $ group } - > { $ attr } ) ;
$ attrs . = " " . $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } - > { $ attr } . " " .
log_verbose ( $ ext_groups - > { $ group } - > { $ attr } . " vs " . $ zim_dl - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } - > { $ attr } } ) ;
zim_attr_value ( $ ext_groups - > { $ group } - > { $ attr } ) ;
log_verbose ( $ ext_groups - > { $ group } - > { $ attr } . " vs " .
$ zim_dl - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } - > { $ attr } } ) ;
}
}
}
}
# Users cannot subscribe or unsubscribe from LDAP group
# Users cannot subscribe or unsubscribe from LDAP group
if (
if ( not defined $ zim_dl - > { $ group } - > { zimbraDistributionListSubscriptionPolicy } or
not defined $ zim_dl - > { $ group } - > { zimbraDistributionListSubscriptionPolicy } or
$ zim_dl - > { $ group } - > { zimbraDistributionListSubscriptionPolicy } ne 'REJECT' ) {
$ zim_dl - > { $ group } - > { zimbraDistributionListSubscriptionPolicy } ne 'REJECT'
) {
$ attrs . = " zimbraDistributionListSubscriptionPolicy REJECT" ;
$ attrs . = " zimbraDistributionListSubscriptionPolicy REJECT" ;
}
}
if (
if ( not defined $ zim_dl - > { $ group } - > { zimbraDistributionListUnsubscriptionPolicy } or
not defined $ zim_dl - > { $ group } - > { zimbraDistributionListUnsubscriptionPolicy } or
$ zim_dl - > { $ group } - > { zimbraDistributionListUnsubscriptionPolicy } ne 'REJECT' ) {
$ zim_dl - > { $ group } - > { zimbraDistributionListUnsubscriptionPolicy } ne 'REJECT'
) {
$ attrs . = " zimbraDistributionListUnsubscriptionPolicy REJECT" ;
$ attrs . = " zimbraDistributionListUnsubscriptionPolicy REJECT" ;
}
}
# If the group in LDAP has a mail defined, enable mail delivery in Zimbra. Else, disable it
# If the group in LDAP has a mail defined, enable mail delivery in Zimbra. Else, disable it
my $ mail_status = ( defined $ ext_groups - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { mail_attr } } ) ? 'enabled' : 'disabled' ;
my $ mail_status = ( defined $ ext_groups - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { mail_attr } } ) ?
if (
'enabled' : 'disabled' ;
not defined $ zim_dl - > { $ group } - > { zimbraMailStatus } or
if ( not defined $ zim_dl - > { $ group } - > { zimbraMailStatus } or
$ zim_dl - > { $ group } - > { zimbraMailStatus } ne $ mail_status
$ zim_dl - > { $ group } - > { zimbraMailStatus } ne $ mail_status ) {
) {
$ attrs . = " zimbraMailStatus $mail_status" ;
$ attrs . = " zimbraMailStatus $mail_status" ;
}
}
@ -495,11 +568,15 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
log_verbose ( "Found a new group : $group. Creating it in Zimbra" ) ;
log_verbose ( "Found a new group : $group. Creating it in Zimbra" ) ;
my $ attrs = '' ;
my $ attrs = '' ;
foreach my $ attr ( keys $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } ) {
foreach my $ attr ( keys $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } ) {
next if ( not defined $ ext_groups - > { $ group } - > { $ attr } or $ ext_groups - > { $ group } - > { $ attr } eq '' ) ;
next if ( not defined $ ext_groups - > { $ group } - > { $ attr } or
$ attrs . = ' ' . $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } - > { $ attr } . " " . zim_attr_value ( $ ext_groups - > { $ group } - > { $ attr } ) ;
$ ext_groups - > { $ group } - > { $ attr } eq '' ) ;
$ attrs . = ' ' . $ conf - > { domains } - > { $ domain } - > { groups } - > { attr_map } - > { $ attr } . " " .
zim_attr_value ( $ ext_groups - > { $ group } - > { $ attr } ) ;
}
}
$ attrs . = " zimbraDistributionListUnsubscriptionPolicy REJECT zimbraDistributionListSubscriptionPolicy REJECT" ;
$ attrs . = " zimbraDistributionListUnsubscriptionPolicy REJECT"
my $ mail_status = ( defined $ ext_groups - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { mail_attr } } ) ? 'enabled' : 'disabled' ;
$ attrs . = " zimbraDistributionListSubscriptionPolicy REJECT" ;
my $ mail_status = ( defined $ ext_groups - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { mail_attr } } ) ?
'enabled' : 'disabled' ;
$ attrs . = " zimbraMailStatus $mail_status" ;
$ attrs . = " zimbraMailStatus $mail_status" ;
send_zmprov_cmd ( "createDistributionList $group\@$domain $attrs" ) ;
send_zmprov_cmd ( "createDistributionList $group\@$domain $attrs" ) ;
}
}
@ -509,30 +586,40 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
my @ ext_members = ( ) ;
my @ ext_members = ( ) ;
if ( not yaml_bool ( $ conf - > { domains } - > { $ domain } - > { groups } - > { members_as_dn } ) ) {
if ( not yaml_bool ( $ conf - > { domains } - > { $ domain } - > { groups } - > { members_as_dn } ) ) {
# If members are not listed as full DN, but by uid, simply concat it with the domain
foreach my $ member ( $ ext_groups - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { members_attr } } ) {
foreach my $ member ( $ ext_groups - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { members_attr } } ) {
next if ( not defined $ ext_users - > { $ member } and not defined $ ext_groups - > { $ member } ) ;
next if ( not defined $ ext_users - > { $ member } and
not defined $ ext_groups - > { $ member } ) ;
push @ ext_members , $ member . '@' . $ domain ;
push @ ext_members , $ member . '@' . $ domain ;
}
}
} else {
} else {
# If members are listed as full DN, we need to lookup in the dn2id we prepared earlier
foreach my $ member ( @ { $ ext_groups - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { members_attr } } } ) {
foreach my $ member ( @ { $ ext_groups - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { members_attr } } } ) {
next if not defined $ dn2id - > { $ member } ;
next if ( not defined $ dn2id - > { $ member } ) ;
push @ ext_members , $ dn2id - > { $ member } . '@' . $ domain ;
push @ ext_members , $ dn2id - > { $ member } . '@' . $ domain ;
}
}
}
}
@ ext_members = sort @ ext_members ;
@ ext_members = sort @ ext_members ;
my @ zim_members = ( defined $ zim_dl - > { $ group } - > { zimbraMailForwardingAddress } ) ? sort @ { $ zim_dl - > { $ group } - > { zimbraMailForwardingAddress } } : ( ) ;
my @ zim_members = ( defined $ zim_dl - > { $ group } - > { zimbraMailForwardingAddress } ) ?
sort @ { $ zim_dl - > { $ group } - > { zimbraMailForwardingAddress } } : ( ) ;
# Now we can compare members for this group in external LDAP and Zimbra
my $ diff = Array::Diff - > diff ( \ @ ext_members , \ @ zim_members ) ;
my $ diff = Array::Diff - > diff ( \ @ ext_members , \ @ zim_members ) ;
if ( scalar @ { $ diff - > deleted } gt 0 ) {
if ( scalar @ { $ diff - > deleted } gt 0 ) {
send_zmprov_cmd ( "addDistributionListMember $group\@$domain " . join ( ' ' , @ { $ diff - > deleted } ) ) ;
send_zmprov_cmd ( "addDistributionListMember $group\@$domain " .
join ( ' ' , @ { $ diff - > deleted } ) ) ;
}
}
if ( scalar @ { $ diff - > added } gt 0 ) {
if ( scalar @ { $ diff - > added } gt 0 ) {
send_zmprov_cmd ( "removeDistributionListMember $group\@$domain $_ " ) foreach ( @ { $ diff - > added } ) ;
send_zmprov_cmd ( "removeDistributionListMember $group\@$domain $_" )
foreach ( @ { $ diff - > added } ) ;
}
}
my @ ext_aliases = ( ) ;
my @ ext_aliases = ( ) ;
foreach my $ mail_attr ( qw( mail_attr alias_attr ) ) {
foreach my $ mail_attr ( qw( mail_attr alias_attr ) ) {
next if ( not defined $ conf - > { domains } - > { $ domain } - > { groups } - > { $ mail_attr } or not defined $ ext_groups - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { $ mail_attr } } ) ;
next if ( not defined $ conf - > { domains } - > { $ domain } - > { groups } - > { $ mail_attr } or
push @ ext_aliases , @ { $ ext_groups - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { $ mail_attr } } } ;
not defined $ ext_groups - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { $ mail_attr } } ) ;
push @ ext_aliases ,
@ { $ ext_groups - > { $ group } - > { $ conf - > { domains } - > { $ domain } - > { groups } - > { $ mail_attr } } } ;
}
}
foreach my $ alias ( @ ext_aliases ) {
foreach my $ alias ( @ ext_aliases ) {
next if ( not alias_matches_domain ( $ alias , $ domain_entry ) ) ;
next if ( not alias_matches_domain ( $ alias , $ domain_entry ) ) ;
@ -548,22 +635,27 @@ DOMAIN: foreach my $domain ( keys $conf->{domains} ) {
foreach my $ alias ( @ { $ alias_diff - > deleted } ) {
foreach my $ alias ( @ { $ alias_diff - > deleted } ) {
my ( $ al , $ dom ) = split /\@/ , $ alias ;
my ( $ al , $ dom ) = split /\@/ , $ alias ;
next if ( not defined $ zim_aliases - > { $ dom } or not defined $ zim_aliases - > { $ dom } - > { $ al } ) ;
next if ( not defined $ zim_aliases - > { $ dom } or
log_verbose ( "Removing LDAP alias $alias from distribution list $group as it doesn't exist in LDAP anymore" ) ;
not defined $ zim_aliases - > { $ dom } - > { $ al } ) ;
log_verbose ( "Removing LDAP alias $alias from distribution list $group " .
"as it doesn't exist in LDAP anymore" ) ;
send_zmprov_cmd ( "removeDistributionListAlias $group\@$domain $alias" ) ;
send_zmprov_cmd ( "removeDistributionListAlias $group\@$domain $alias" ) ;
}
}
my $ note = $ sync_from_ldap . "|LDAP_Aliases=" . join ( ',' , @ ext_aliases ) ;
my $ note = $ sync_from_ldap . "|LDAP_Aliases=" . join ( ',' , @ ext_aliases ) ;
if ( $ note ne ( $ zim_dl - > { $ group } - > { zimbraNotes } || '' ) ) {
if ( $ note ne ( $ zim_dl - > { $ group } - > { zimbraNotes } || '' ) ) {
send_zmprov_cmd ( "modifyDistributionList $group\@$domain zimbraNotes " . zim_attr_value ( $ note ) ) ;
send_zmprov_cmd ( "modifyDistributionList $group\@$domain zimbraNotes " .
zim_attr_value ( $ note ) ) ;
}
}
}
}
# Now, look at all the distribution list which were created from LDAP but doesn't exist anymore in LDAP
# Now, look at all the distribution list which were created from LDAP but doesn't exist anymore in LDAP
foreach my $ dl ( keys $ zim_dl ) {
foreach my $ dl ( keys $ zim_dl ) {
next if ( not defined $ zim_dl - > { $ dl } - > { zimbraNotes } or $ zim_dl - > { $ dl } - > { zimbraNotes } !~ m/^$sync_from_ldap/ ) ;
next if ( not defined $ zim_dl - > { $ dl } - > { zimbraNotes } or
$ zim_dl - > { $ dl } - > { zimbraNotes } !~ m/^$sync_from_ldap/ ) ;
next if ( defined $ ext_groups - > { $ dl } ) ;
next if ( defined $ ext_groups - > { $ dl } ) ;
log_verbose ( "Group $dl doesn't exist in LDAP anymore, removing the corresponding distribution list" ) ;
log_verbose ( "Group $dl doesn't exist in LDAP anymore, " .
"removing the corresponding distribution list" ) ;
send_zmprov_cmd ( "deleteDistributionList $dl\@$domain" ) ;
send_zmprov_cmd ( "deleteDistributionList $dl\@$domain" ) ;
}
}
}
}
@ -633,7 +725,9 @@ sub handle_error {
} ,
} ,
body_str = > "LDAP synchronisation for domain $domain failed at step '$step'. The error was\n$err\n" ,
body_str = > "LDAP synchronisation for domain $domain failed at step '$step'. The error was\n$err\n" ,
) ;
) ;
my $ transport = Email::Sender::Transport::Sendmail - > new ( { sendmail = > '/opt/zimbra/common/sbin/sendmail' } ) ;
my $ transport = Email::Sender::Transport::Sendmail - > new ( {
sendmail = > '/opt/zimbra/common/sbin/sendmail'
} ) ;
sendmail ( $ mail , { transport = > $ transport } ) ;
sendmail ( $ mail , { transport = > $ transport } ) ;
}
}
$ exit = 255 ;
$ exit = 255 ;
@ -642,7 +736,7 @@ sub handle_error {
# ldap2hashref takes three args
# ldap2hashref takes three args
# * An LDAP search result
# * An LDAP search result
# * The attribute used as the key of objects
# * The attribute used as the key of objects
# * a n optional array of attributes we want as an array, even if there's a single value
# * A n optional array of attributes we want as an array, even if there's a single value
# It'll return a hashref. The key will be unaccentuated and lower cased.
# It'll return a hashref. The key will be unaccentuated and lower cased.
sub ldap2hashref {
sub ldap2hashref {
@ -675,17 +769,21 @@ sub yaml_bool {
# Takes the domain conf hashref as only arg
# Takes the domain conf hashref as only arg
sub build_domain_attrs {
sub build_domain_attrs {
my $ domain_conf = shift ;
my $ domain_conf = shift ;
my $ attrs = "zimbraAuthMech " . zim_attr_value ( $ domain_conf - > { ldap } - > { type } ) ;
my $ type = ( $ domain_conf - > { ldap } - > { schema } eq =~ m/^ad/i ) ? 'ad' : 'ldap' ;
$ attrs . = " zimbraAuthMechAdmin " . zim_attr_value ( $ domain_conf - > { ldap } - > { type } ) ;
my $ attrs = "zimbraAuthMech " . zim_attr_value ( $ type ) ;
if ( defined $ domain_conf - > { ldap } - > { bind_dn } and defined $ domain_conf - > { ldap } - > { bind_pass } ) {
$ attrs . = " zimbraAuthMechAdmin " . zim_attr_value ( $ type ) ;
if ( defined $ domain_conf - > { ldap } - > { bind_dn } and
defined $ domain_conf - > { ldap } - > { bind_pass } ) {
$ attrs . = " zimbraAuthLdapSearchBindDn " . zim_attr_value ( $ domain_conf - > { ldap } - > { bind_dn } ) ;
$ attrs . = " zimbraAuthLdapSearchBindDn " . zim_attr_value ( $ domain_conf - > { ldap } - > { bind_dn } ) ;
$ attrs . = " zimbraAuthLdapSearchBindPassword " . zim_attr_value ( $ domain_conf - > { ldap } - > { bind_pass } ) ;
$ attrs . = " zimbraAuthLdapSearchBindPassword " . zim_attr_value ( $ domain_conf - > { ldap } - > { bind_pass } ) ;
}
}
# if ( defined $domain_conf->{users}->{filter} ) {
# if ( defined $domain_conf->{users}->{filter} ) {
# $attrs = " zimbraAuthLdapSearchFilter " . zim_attr_value( "(&(|(" . $domain_conf->{users}->{key} . "=%u)(" . $domain_conf->{users}->{key} . "=%n))(" . $domain_conf->{users}->{filter} . ")" );
# $attrs = " zimbraAuthLdapSearchFilter " . zim_attr_value( "(&(|(" . $domain_conf->{users}->{key} . "=%u)(" . $domain_conf->{users}->{key} . "=%n))(" . $domain_conf->{users}->{filter} . ")" );
# }
# }
$ attrs . = " zimbraAuthLdapURL " . join ( ' +zimbraAuthLdapURL' , zim_attr_value ( $ domain_conf - > { ldap } - > { servers } ) ) ;
$ attrs . = " zimbraAuthLdapURL " .
if ( defined $ domain_conf - > { ldap } - > { start_tls } and yaml_bool ( $ domain_conf - > { ldap } - > { start_tls } ) ) {
join ( ' +zimbraAuthLdapURL' , zim_attr_value ( $ domain_conf - > { ldap } - > { servers } ) ) ;
if ( defined $ domain_conf - > { ldap } - > { start_tls } and
yaml_bool ( $ domain_conf - > { ldap } - > { start_tls } ) ) {
$ attrs . = " zimbraAuthLdapStartTlsEnabled TRUE" ;
$ attrs . = " zimbraAuthLdapStartTlsEnabled TRUE" ;
} else {
} else {
$ attrs . = " zimbraAuthLdapStartTlsEnabled FALSE" ;
$ attrs . = " zimbraAuthLdapStartTlsEnabled FALSE" ;