Active Directory Based Zimbra Collaboration OSE User Synchronization

1. Background


When configuring a zimbra server, I encountered the problem of synchronizing users by group in Active Directory (AD). If we create a new user in AD, then it is normally added, but if we make access to the mail server by group, then for the first time all users are synchronized. But then, the changes in the group do not affect the changes in the users of the zimbra mail server.

What did not like in these articles is the use of a powershell script (why, if there is ldapsearch) and the constant use of the zmprov utility, and when a large number of users are synchronized, the script runs for a long time

2. The source data


Server OS: CentOS 7
Script language: bash
Zimbra domain: test.ru
Zimbra server: zimbra.test.local
Active Directory domain: test.local
AD group for access to mail: mail

A user may have mail different from his login, such mail add it to AD in the mail field and use it to create an alias in zimbra (for example, Vasya Pupkin logs into the system using the username vasia, but mail must be sent and received as user v.pupkin@test.ru)

3. Scheme of the script


  1. Save to the AD user file included in the mail group
  2. We save all zimbra users with all attributes to a file
  3. We divide the file with a list of all zimbra users into format files: file name - user login, content - user attributes
  4. We remove from the list of users zimbra system accounts (admin, gal, antivirus)
  5. Compare AD and zimbra user list
  6. Create a file with the commands to add, delete (in my case, block the user), synchronize and create aliases
  7. Apply these actions in zimbra (one call to zmprov)
  8. Send a report to the administrator by mail (if you have something to send)
  9. Delete temporary files and directories

4. Sync script


The script has two operating modes - this is launching without parameters, then only blocking and adding users will work. And start with the โ€œallโ€ parameter, then all users of the mail group in AD will be synchronized.

It is also necessary to pay attention to the use of the base64 decoding utility in the synchronization function, it must be used for AD fields in which Russian characters are used.

The script itself:
#!/bin/bash
#
#1.  
#
#1.1  
#   
path="/mnt/zimbra/user-sync"
# 
timestamp=`date +%F-%H-%M`
#   
tmp_dir=$path/tmp
#      zimbra
zim_us=$tmp_dir/zim-us
#   
log_dir=$path/log
# -
log=$log_dir/grouplog_$timestamp.txt
#      
usname=$tmp_dir/usname
#          zmprov
zmcmdfile=$tmp_dir/zmcmdfile
#        AD 
userfil=$tmp_dir/userfil
# 
mutt="/usr/bin/mutt"

#
#1.2   zimbra
#  Zimbra
domain="test.ru"
#   zmprov
zmprov="/opt/zimbra/bin/zmprov"
#
#1.3    AD  LDAP
#LDAP search
ldapsearch=/opt/zimbra/common/bin/ldapsearch
#    ( ,   (   ))
ldap_server="ldap://test.local:389"
# OU 
basedn="DC=test,DC=local"
#      AD  LDAP
binddn="CN=zimbra,CN=Users,DC=test,DC=local"
bindpw="qwe123" #user password 
#  -     mail
filter="(memberof=cn=mail,cn=users,dc=test,dc=local)"
#    (,             )
fields="sAMAccountName mail description displayName givenName cn sn department title"
#  

# 

# 
#   
function err_log()
{
if [ $1 -eq 0 ]; then
		#echo -n "$(tput hpa $(tput cols))$(tput cub 6)[OK]"
		#echo
		echo $2" [Ok]"  >> $log
	else
		#echo -n "$(tput hpa $(tput cols))$(tput cub 6) [FAIL]"
		#echo
		echo  $2" [Fail]" >> $log
	fi
}

#  
function if_path ()
{
	#      
	if [ ! -d $1 ]; then
	#   
	echo "  $1..." >> $log
	mkdir -p $1
	err_log $? "  $1"
	
else
	echo "  $1 " >> $log	
fi
}

#      AD
function search_users_AD()
{
	echo "     AD..." >> $log
	$ldapsearch -x -o ldif-wrap=no -H $ldap_server -D $binddn -w $bindpw -b $basedn $filter $fields | 
	grep sAMAccountName | 
	egrep -v '^#|^$' | 
	awk '{print $2}' |
	sort > $usname.ad
	echo "Found () "`cat $usname.ad | wc -l`" Group in AD (  AD)" >> $log
}

function all_user_attr_zimbra()
{
	#        
	$zmprov -l gaa -v $domain > $usname.gaa
	#         
	cd $zim_us
	#      ,     
	csplit $usname.gaa --prefix='user.' --suffix-format='%03d.zim' --elide-empty-files -s /"# name"/ '{*}'

	#   .      zimbra  
	for i in $( ls $zim_us )
	do
		nam=`grep "# name" $zim_us/$i | awk '{ print $3}' | sed 's/@.*//g'`
		mv -f $zim_us/$i $zim_us/$nam
	done
	cd $path
}

#      zimbra 
function search_user_zimbra()
{
	echo "    zimbra..." >> $log
	ls $zim_us | sort > $usname.tem
	#    .  $path/system.acc    
	diff -u -i $usname.tem $path/system.acc  | sed 1,3d | grep ^- | cut -c 2- | sort > $usname.zim
	#rm -f $usname.tem
	echo "Found () "`cat $usname.zim | wc -l`" Group in Zimbra (  Zimbra)" >> $log
	
}

#    (   
function diff_user_list()
{
	diff -u -i $usname.zim $usname.ad | sed 1,3d | sed '/@.*/d' > $usname.diff
}

# 
function adduser()
{
#    	
adddif=`grep ^+ $usname.diff | sed '1!d'`
	if [ -n $adddif ];
	then
		for addus in $( grep ^+ $usname.diff | cut -c 2- )
		do
			#       zimbra (  - ,  - )
                        ifclos=`grep "zimbraAccountStatus:" $zim_us/$addus | awk '{print $2}' | cut -c -1`
			if [ $ifclos = "c" ];
			then
				echo "ma $addus@$domain zimbraAccountStatus active" >> $zmcmdfile
				echo " $addus " >> $tmp_dir/send.txt
				if [ $addus != "" ];
				then
					sync_one_user $addus
				fi
			else
				#123456 -  ,    .   ,         AD
				echo "ca $addus@$domain 123456" >> $zmcmdfile
				echo " $addus " >> $tmp_dir/send.txt
				if [ $addus != "" ];
				then
					sync_one_user $addus
				fi
			fi
		done
		
	fi
}

#   
function block_user()
{
	deldif==`grep ^- $usname.diff | sed '1!d'`
	if [ -n $deldif ];
	then
		for delus in $( grep ^- $usname.diff | cut -c 2- )
		do
			#zimbraAccountStatus closed
			if [ $delus != "" ];
			then
				ifclos=`grep "zimbraAccountStatus:" $zim_us/$delus | awk '{print $2}'`
				if [ "$ifclos" != "closed" ];
				then
					echo "user closed - $delus"
					echo "ma $delus@$domain zimbraAccountStatus closed" >> $zmcmdfile
					echo " $delus !     !" >> $tmp_dir/send.txt
					echo $delus >> $path/close.1
					cat $path/close.1 | sort > $path/close.diff
					echo "$delus" 
				fi
			fi
		done
	fi
}

#   
function ifattr()
{
	if1char=`echo $2 | cut -c -1`
	if [[ -n $2 && $if1char != "" ]];
	#if [ $2 != "" ];
	then 
	    #echo $2
	    echo -n " $1 \"$2\""  >> $zmcmdfile
	fi
}

#  
function sync_one_user()
{
	echo "  $1..." >> $log
	$ldapsearch -x -o ldif-wrap=no -H $ldap_server -D $binddn -w $bindpw -b $basedn "(sAMAccountName=$1)" $fields > $userfil/$1.ad
	
	#   
	echo -n  "ma "$1 >> $zmcmdfile
	
	#samacc=`grep "sAMAccountName:" $userfil/$1 | awk '{print $2}'`  
		
	description=`grep "description:" $userfil/$1.ad | awk '{split ($0, a, ": "); print a[2]}'`
	#echo $description
	ifattr "description" "$description"
		
	displayName=`grep "displayName:" $userfil/$1.ad | awk '{split ($0, a, ": "); print a[2]}' | base64 -d`
	ifattr "displayName" "$displayName"
	
	givenName=`grep "givenName:" $userfil/$1.ad | awk '{split ($0, a, ": "); print a[2]}' | base64 -d`
	ifattr "givenName" "$givenName"
	
	cn=`grep "cn:" $userfil/$1.ad | awk '{split ($0, a, ": "); print a[2]}'`
	ifattr "cn" "$cn"
	
	sn=`grep "sn:" $userfil/$1.ad | awk '{split ($0, a, ": "); print a[2]}' | base64 -d`
	ifattr "sn" "$sn"
	
	department=`grep "department:" $userfil/$1.ad | awk '{split ($0, a, ": "); print a[2]}' | base64 -d`
	ifattr "company" "$department"
	
	title=`grep "title:" $userfil/$1.ad | awk '{split ($0, a, ": "); print a[2]}' | base64 -d`
	ifattr "title" "$title"
	
	#    
	echo >> $zmcmdfile
	# 
	mailnew=`grep "mail:" $userfil/$1.ad | awk '{print $2}'`
	if [ "$mailnew" != "" ];
	then 
	# [ -n $mailnew ] 
	#    echo $2
		#    
		#${1,,} -      
		useralias=`grep "zimbraMailAlias:" $zim_us/${1,,} | awk '{print $2}'`
	    if [ $useralias != $mailnew ];
		then
			echo "aaa \"$1@$domain\" \"$mailnew\""  >> $zmcmdfile
		fi
	fi
	#ifattr "mail" $mailnew
#	echo $mailnew
	echo "  $1 " >> $tmp_dir/send.txt 
	
#	echo " $1 - $atrruser"
	
	#echo "Found () "`cat $usname.ad | wc -l`" Group in AD (  AD)" >> $log
}


# 
date +%F-%H-%M
#2.  
#  
if_path $path
#  
if_path $tmp_dir
# -
if_path $log_dir
#   
if_path $userfil
#    
if_path $zim_us

#         zmprov
:> $zmcmdfile
#    
:> $tmp_dir/send.txt
#3.     AD 
search_users_AD

#4.    zimbra 
all_user_attr_zimbra
#  ()
search_user_zimbra

#5.    
diff_user_list
# 
block_user
#    
adduser

#tckb    "all"  ,        mail AD
if [[ -n $1 && $1 = "all" ]];
then
	for us in $(cat $usname.ad );
	do
	#	echo $us
		sync_one_user $us
	done
fi
#      zmprov  
$zmprov -f $zmcmdfile
#       
cat $zmcmdfile >> $log
#     (  )
if [ -s $tmp_dir/send.txt ];
then
	$mutt  -s "   $timestamp" admins@test.ru -a $log < $tmp_dir/send.txt
fi

#    
rm -R -f $tmp_dir


5. Conclusion


In general, the script turned out to be pretty fast, the zmprov utility is used only twice, the rest of the utilities and functions work out much faster.

6. References


When creating this article, ideas and articles were used:

1. Andrey Sysoev
2. DruGoeDeLo
3. My comment on article 1) with a script for synchronizing mailing lists

All Articles