1. Предпосылки
Настраивая сервер zimbra столкнулся с проблемой синхронизации пользователей по группе в Active Directory (AD). Если мы создаем нового пользователя в AD, то он нормально добавлялся, но если мы делаем доступ к почтовому серверу по группе, то первый раз все пользователи синхронизируются. А вот потом, изменения в группе никак не влияют на изменения пользователей почтового сервера zimbra.
Что не понравилось в этих статьях, это использование скрипта на powershell (зачем, если есть ldapsearch) и постоянный вызов утилиты zmprov, и когда идет синхронизация большого количества пользователей, то скрипт выполняется продолжительное время
2. Исходные данные
ОС сервера: CentOS 7
Язык скрипта: bash
Домен Zimbra: test.ru
Сервер Zimbra: zimbra.test.local
Домен Active Directory: test.local
Группа AD для доступа к почте: mail
У пользователя может быть почта отличающаяся от его логина, такую почту вносим в AD в поле mail и по нему создаем алиас в zimbra (например вася пупкин входит в систему под логином vasia, но почту должен отправлять и получать еще как пользователь v.pupkin@test.ru)
3. Схема работы скрипта
- Сохраняем в файл пользователей AD входящих в группу mail
- Сохраняем в файл всех пользователей zimbra со всеми атрибутами
- Разделяем файл со списком всех пользователей zimbra на файлы формата: имя файла — логин пользователя, содержание — атрибуты пользователя
- Убираем из списка пользователей zimbra системные учетные записи (admin, gal,antivirus)
- Сравниваем список пользователей AD и zimbra
- Создаем файл с командами добавления, удаления (в моем случае блокировки пользователя), синхронизации и создания алиасов
- Применяем данные действия в zimbra (один вызов zmprov)
- Отправляем отчет на почту администратору (если есть что отправлять)
- Удаляем временные файлы и каталоги
4. Скрипт синхронизации
У скрипта есть два режима работы — это запуск без параметров, тогда отработают только блокировка и добавление пользователей. И запуск с параметром «all», тогда будут синхронизированны все пользователи группы mail в AD.
Так же необходимо обратить внимание на использование утилиты декодирования base64 в функции синхронизации, ее необходимо использовать для полей AD в которых используются русские символы.
Сам скрипт:
#!/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 searchusersAD()
{
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 alluserattrzimbra()
{
#Создаем файл со списком всех пользователей и их атрибутов
$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 searchuserzimbra()
{
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 diffuserlist()
{
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 blockuser()
{
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
searchusersAD
#4.Создаем список существующих в zimbra пользователей
alluserattrzimbra
#удаляем лишних (системных)
searchuserzimbra
#5.Сравниваем оба списка групп рассылки
diffuserlist
#Блокируем пользователей
blockuser
#Создаем или разблокируем новых пользователей
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. Заключение
В целом скрипт получился довольно шустрый, утилита zmprov используется всего два раза, остальные утилиты и функции отрабатывают намного быстрее.
6. Ссылки
При создании данной статьи использовались идеи и статьи:
1. Сысоева Андрея
2. DruGoeDeLo
3. Мой коментарий к статье 1) со скриптом для синхронизации списков рассылки