
1. Пара слов от автора
Статья обновлена. Скрипты изменены. Добавлен скрипт для обновления одного списка рассылки.
В комментариях к прошлой статье мне задали интересный вопрос об автоматическом формировании списков рассылки на основе групп безопасности AD. Есть задача – есть решение. Итак, поехали.
2. Исходные данные
ОС сервера: CentOS 7
По поводу ОС
На самом деле разница между CentOS7 и любой другой системой будет заключаться исключительно в командах серверу на установку пакетов, и, возможно, расположении некоторых файлов. Работа ведется в основном с командлетами Zimbra, так что отличия настройки будут минимальны.
Домен Zimbra: zimbramail.home.local
Путь монтирования шары на хосте Zimbra: /mnt/ZM/
3. Настройка
- Монтируем шару Windows к нашему Linux серверу. Это нужно для упрощения и автоматизации передачи данных из Windows PowerShell в Linux Bash. Процедура монтирования была описана в предыдущей статье. Не буду повторяться.
- Создаем в AD отдельное OU, в котором создаем группы, на основе которых будут созданы списки рассылки в Zimbra. Имя группы = имя списка рассылки.
- Добавляем в группы, созданные в новом OU, пользователей или группы безопасности, на основе которых будут наполняться списки рассылки в Zimbra. Скрипт отрабатывает рекурсивно, что значит, что он соберет все данные о пользователях, состоящих в группах, которые добавлены в группы в целевом OU. Подробнее о выводе команды Get-ADGroupMember.
- Создаем скрипт сбора данных из Active Directory.
- Создаем скрипт добавления листов рассылки и их наполнения пользователями на основе полученных данных в предыдущем скрипте.
- Наслаждаемся.
3.1. По поводу OU
Я создал OU “ZimbraDL” в корне домена и запретил ему наследование групповых политик, чтобы эти группы оставались обособленными. Они не будут участвовать в жизни домена никак, помимо формирования Distribution Lists в Zimbra Collaboration OSE.
4. Скрипт на PowerShell для сбора данных из AD
Скрипт PowerShell
$Path = "C:\ZM\ZimbraDL"
$enc = [system.text.encoding]
function ReCode ( $f, $t, $line )
{
$cp1 = $enc::getencoding( $f )
$cp2 = $enc::getencoding( $t )
$inputbytes = $enc::convert( $cp1, $cp2, $cp2.getbytes( $line ))
$outputstring = $cp2.getstring( $inputbytes )
$outputstring | add-content $OutputFile
}
#Очистка каталога
if(test-path $Path)
{
Remove-Item $Path -Recurse -Force
}
#Создание рабочих директорий
if(!(Test-Path $Path))
{
New-Item -ItemType Directory -Force -Path $Path
}
if(!(Test-Path $Path\Groups))
{
New-Item -ItemType Directory -Force -Path $Path\Groups
}
if(!(Test-Path $Path\Users))
{
New-Item -ItemType Directory -Force -Path $Path\Users
}
if(!(Test-Path $Path\UsersTemp))
{
New-Item -ItemType Directory -Force -Path $Path\UsersTemp
}
#Создание списка групп
Import-Module ActiveDirectory
Get-AdGroup -filter * -SearchBase "OU=ZimbraDL,DC=home,DC=local" | select samaccountname | Out-File $Path\Groups\GetGroupsAD.txt
#Форматирование списка групп
(Get-Content "$Path\Groups\GetGroupsAD.txt") -notmatch "samaccountname" | where {$_ -ne ""} | where {$_ -ne "--"} | Where-Object {$_ -notmatch '-'} | out-file "$Path\Groups\GetGroupsAD.txt"
$File = @(Get-Content $Path\Groups\GetGroupsAD.txt)
foreach ($File1 in $File)
{
$string=$File1.TrimStart('"')
$string=$string.TrimEnd('"')
$string=$string.TrimStart(' ')
$string=$string.TrimEnd(' ') | Out-File $Path\Groups\GroupsListTemp.txt -Append
}
#Удаление временного файла
Remove-Item $Path\Groups\GetGroupsAD.txt -Force
$InputFile = gc $Path\Groups\GroupsListTemp.txt
$OutputFile = "$Path\Groups\GroupsList.txt"
#Исправление кодировки
foreach ($line in $InputFile)
{
ReCode -f "windows-1251" -t "utf-8" $line
}
#Удаление временного файла
Remove-Item $Path\Groups\GroupsListTemp.txt -Force
#Создание файлов со списком пользователей для каждой группы
$GroupName = @(Get-Content $Path\Groups\GroupsList.txt)
Foreach ($Group in $GroupName)
{
Get-ADGroupMember $Group -recursive | ft SamAccountName | out-file "$Path\UsersTemp\$Group.txt" -Append
(get-content "$Path\UsersTemp\$Group.txt") -notmatch "Name" | where {$_ -ne ""} | where {$_ -ne "--"} | Where-Object {$_ -notmatch '-'} | out-file "$Path\UsersTemp\$Group.txt"
$File=@(Get-Content $Path\UsersTemp\$Group.txt)
foreach ($File1 in $File)
{
$string=$File1.TrimStart('"')
$string=$string.TrimEnd('"')
$string=$string.TrimStart(' ')
$string=$String.TrimEnd(' ') | Out-File "$Path\UsersTemp\$Group" -Append
}
$InputFile = gc $Path\UsersTemp\$Group
$OutputFile = "$Path\Users\$Group"
#Исправление кодировки
foreach ($line in $InputFile)
{
ReCode -f "windows-1251" -t "utf-8" $line
}
}
#Очистка лишних временных директорий
Remove-Item "$Path\UsersTemp\" -Recurse -Force
Remove-Item "$Path\Groups" -Recurse -Force4.1. Как работает скрипт
- Сначала происходит проверка существования и удаление рабочей директории, если она существует. Это нужно, чтобы в процессе работы данные не задвоились.
- PoSh смотрит в указанное OU, считывает группы пользователей, находящиеся в нем и записывает их в файл GetGroupsAD.txt
- Отбрасывает из полученного файла все лишнее (PoSh пишет в файл весь свой вывод, так что в изначальном выводе команды первой строкой идет Name, второй строкой разделитель "----", и только после этого перечисляются группы по одной на строку), меняет кодировку с «windows-1251» на utf-8, результатом чего является другой файл GroupsList.txt
- Далее на основе полученного файла считывается информация о пользовтелях групп, содержащихся в файле. Файлы, содержащие в себе имена пользователей (samAccountName)
помещаются в директорию \Users и называются по именам групп
4.2. Скрипт для считывания информации с отдельно взятой группы
Скрипт с возможностью считать данные только одной группы безопасности мало чем отличается от предыдущего, в основм тем, что в не�� есть блок с предложением пользователю ввести имя группы, на основе которой нужно будет обновить список рассылки.
Скрипт PowerShell для запуска руками с возможностью считать данные только одной группы
$Path = "C:\ZM\ZimbraDL"
$enc = [system.text.encoding]
function ReCode ( $f, $t, $line )
{
$cp1 = $enc::getencoding( $f )
$cp2 = $enc::getencoding( $t )
$inputbytes = $enc::convert( $cp1, $cp2, $cp2.getbytes( $line ))
$outputstring = $cp2.getstring( $inputbytes )
$outputstring | add-content $OutputFile
}
#Очистка каталога
if(test-path $Path)
{
Remove-Item $Path -Recurse -Force
}
#Создание рабочих директорий
if(!(test-path $Path))
{
New-Item -ItemType Directory -Force -Path $Path
}
if(!(Test-Path $Path\Groups))
{
New-Item -ItemType Directory -Force -Path $Path\Groups
}
if(!(Test-Path $Path\Users))
{
New-Item -ItemType Directory -Force -Path $Path\Users
}
if(!(Test-Path $Path\UsersTemp))
{
New-Item -ItemType Directory -Force -Path $Path\UsersTemp
}
#Создание списка групп
Import-Module ActiveDirectory
$Groupname = Read-Host 'Введите имя группы, которую нужно обновить, или ALL для обновления всех списков рассылки'
If ($Groupname -eq "ALL")
{
Get-AdGroup -filter * -SearchBase "OU=ZimbraDL,DC=home,DC=local" | select samaccountname | Out-File $path\Groups\GetGroupsAD.txt
}
Else
{
$Groupname > "$Path\Groups\GetGroupsAD.txt"
}
#Форматирование списка групп
(Get-Content "$Path\Groups\GetGroupsAD.txt") -notmatch "samaccountname" | where {$_ -ne ""} | where {$_ -ne "--"} | Where-Object {$_ -notmatch '-'} | out-file "$Path\Groups\GetGroupsAD.txt"
$File = @(Get-Content $Path\Groups\GetGroupsAD.txt)
foreach ($File1 in $File)
{
$string=$File1.TrimStart('"')
$string=$string.TrimEnd('"')
$string=$string.TrimStart(' ')
$string=$string.TrimEnd(' ') | Out-File $Path\Groups\GroupsListTemp.txt -Append
}
Remove-Item $Path\Groups\GetGroupsAD.txt -Force
$InputFile = gc $Path\Groups\GroupsListTemp.txt
$OutputFile = "$Path\Groups\GroupsList.txt"
foreach ($line in $InputFile)
{
ReCode -f "windows-1251" -t "utf-8" $line
}
Remove-Item $Path\Groups\GroupsListTemp.txt -Force
#Создание файлов со списком пользователей для каждой группы
$GroupName = @(Get-Content $Path\Groups\GroupsList.txt)
Foreach ($Group in $GroupName)
{
Get-ADGroupMember $Group -recursive | ft SamAccountName | out-file "$Path\UsersTemp\$Group.txt" -Append
(get-content "$Path\UsersTemp\$Group.txt") -notmatch "Name" | where {$_ -ne ""} | where {$_ -ne "--"} | Where-Object {$_ -notmatch '-'} | out-file "$Path\UsersTemp\$Group.txt"
$File=@(Get-Content $Path\UsersTemp\$Group.txt)
foreach ($File1 in $File)
{
$string=$File1.TrimStart('"')
$string=$string.TrimEnd('"')
$string=$string.TrimStart(' ')
$string=$String.TrimEnd(' ') | Out-File "$Path\UsersTemp\$Group" -Append
}
$InputFile = gc $Path\UsersTemp\$Group
$OutputFile = "$Path\Users\$Group"
foreach ($line in $InputFile)
{
ReCode -f "windows-1251" -t "utf-8" $line
}
}
Remove-Item "$Path\UsersTemp\" -Recurse -Force
Remove-Item "$Path\Groups" -Recurse -Force5. Скрипт на Bash для создания списков рассылки
Оговорюсь по поводу копирования файлов-скриптов, созданных под Windows
В прошлой статье был описан метод форматирования файлов с помощью команды cat, которая, будучи запущенная с определенным ключом, убирает все лишние нечитаемые символы. Ссылка на статью в конце статьи.
Скрипт Bash
#!/bin/bash
#Определение переменных
#Путь к рабочему каталогу
Path="/mnt/ZM/ZimbraDL"
#имя домена Zimbra
Domain="zimbramail.home.local"
#путь к командлету zmprov
zmprov="/opt/zimbra/bin/zmprov"
#путь к лог-файлу
log="/mnt/ZM/DLlog.txt"
#путь ко временному файлу со списком групп
DLnames="/mnt/ZM/DLnames"
#путь ко временному файлу со списком пользователей
UserNames="/mnt/ZM/Usernames"
#конец блока переменных
echo "Запись списка групп..."
ls $Path/Users > $DLnames
if [ $? -eq 0 ]; then
echo -n "$(tput hpa $(tput cols))$(tput cub 6) [OK]"
echo
echo -en "ls directory for Groups correct $(date +%T)\n" >> $log
else
echo -n "$(tput hpa $(tput cols))$(tput cub 6) [FAIL]"
echo
echo -en "ls directory for Groups INcorrect $(date +%T)\n" >> $log
fi
#Удаление списков рассылки
echo "Удаление обновляемых списков рассылки"
for DLname in $( cat $DLnames); do
#Проверка существования списка рассылки
echo "Проверка существования списка рассылки $DLname..."
Result=$($zmprov gdl $DLname@$Domain)
if [ $? -eq 0 ]; then
echo -n "$(tput hpa $(tput cols))$(tput cub 6)[OK]"
echo
echo -en "DL $DLname exist $(date +%T)\n" >> $log
#Удаление списка рассылки
echo -en "Start deleting DL for group $DLname $(date +%T)\n" >> $log
echo "Удаление списка рассылки $DLname..."
$zmprov ddl $DLname@$Domain
if [ $? -eq 0 ]; then
echo -n "$(tput hpa $(tput cols))$(tput cub 6) [OK]"
echo
echo -en "DL for group $DLname is deleted in $(date +%T)\n" >> $log
else
echo -n "$(tput hpa $(tput cols))$(tput cub 6) [FAIL]"
echo
echo -en "DL for group $DLname is NOT deleted in $(date +%T)\n" >> $log
fi
else
echo -n "$(tput hpa $(tput cols))$(tput cub 6)[FAIL]"
echo
echo -en "DL $DLname not exist! $(date +%T)\n" >> $log
fi
done
for DLname in $( cat $DLnames); do
#Создание списка рассылки
echo -en "Start create DL for group $DLname $(date +%T)\n" >> $log
echo "Создание списка рассылки для группы AD $DLname..."
$zmprov cdl $DLname@$Domain
if [ $? -eq 0 ]; then
echo -n "$(tput hpa $(tput cols))$(tput cub 6) [OK]"
echo
echo -en "DL for group $DLname is created in $(date +%T)\n" >> $log
else
echo -n "$(tput hpa $(tput cols))$(tput cub 6) [FAIL]"
echo
echo -en "DL for group $DLname is NOT created in $(date +%T)\n" >> $log
fi
#Наполнение списка рассылки
echo "Наполнение списка рассылки"
for UserName in $( cat $Path/Users/$DLname); do
echo "Проверка существования ящика для пользователя $UserName..."
Result=$($zmprov gmi $UserName@$Domain)
if [ $? -eq 0 ]; then
echo -n "$(tput hpa $(tput cols))$(tput cub 6) [OK]"
echo
echo -en "MilBox for user $UserName exist $(date +%T)\n" >> $log
echo "Добавление пользователя $UserName в список рассылки $DLname@$Domain..."
$zmprov adlm $DLname@$Domain $UserName@$Domain
if [ $? -eq 0 ]; then
echo -n "$(tput hpa $(tput cols))$(tput cub 6) [OK]"
echo
echo -en "User $UserName added in $DLname@$Domain correctly in $(date +%T)\n" >> $log
else
echo -n "$(tput hpa $(tput cols))$(tput cub 6) [FAIL]"
echo
echo -en "DL for group $DLname is NOT created in $(date +%T)\n" >> $log
fi
else
echo -n "$(tput hpa $(tput cols))$(tput cub 6) [FAIL]"
echo
echo -en "MilBox for user $UserName is NOT exist $(date +%T)\n" >> $log
fi
done
done
#Очистка временных файлов
echo "Очистка временных файлов"
echo "Очистка файла со списком групп..."
echo -n > $DLnames
if [ $? -eq 0 ]; then
echo -n "$(tput hpa $(tput cols))$(tput cub 6)[OK]"
echo
echo -en "File $DLnames was successfull cleared in $(date +%T)\n" >> $log
else
echo -n "$(tput hpa $(tput cols))$(tput cub 6) [FAIL]"
echo
echo -en "File $DLnames was NOT cleared in $(date +%T)\n" >> $log
fi
#Удаление директорий, содержащих структуру групп и пользователей
echo "Удаление рабочего каталога $Path для очистки свободного пространства..."
rm -rf $Path
if [ $? -eq 0 ]; then
echo -n "$(tput hpa $(tput cols))$(tput cub 6)[OK]"
echo
echo -en "Directory $Path was seccessfull deleted in $(date +%T)\n" >> $log
else
echo -n "$(tput hpa $(tput cols))$(tput cub 6) [FAIL]"
echo
echo -en "Directory $Path was NOT deleted in $(date +%T)\n" >> $log
fi
#запись в лог-файл времени окончания обновления списков рассылки
echo -en "Job complete in $(date +%T)\n" >> $log
echo -en "____________________________________\n" >> $log
5.1. Как работает скрипт
- Записать в файл список групп
- Проверить существование списка рассылки в Zimbra, при существовании – удалить его
- Поочередно создать списки рассылки на основе списка групп, наполняя каждый из них пользователями (на экран выводится ID списка рассылки, ��то стандартный вывод командлета zmprov при создании DL). При этом проверяется существование почтовых ящиков пользователей в Zimbra, и если ящика не существует, то пользователь не будет добавлен в список рассылки. Можно, конечно, создать пользователю новый почтовый ящик и добавить его в список рассылки, но я исходит из того, что Zimbra autoprov работает в режиме Eager, и если пользователь не создался автоматически, то ему и нечего делать в системе
- Очистить временные файлы
- Удалить рабочую директорию
6. Заключение
В целом, задача не сложная, проблема была лишь в передаче данных из PowerShell в Bash. Я довольно долго пытался найти инструмент для рекодирования текстовых файлов с выводом PoSh в удобоваримый для Bash вид. Результатом многодневных поисков стала функция:
Функция рекодирования
$InputFile = gc File1.txt
$OutputFile = "File2.txt"
$enc = [system.text.encoding]
function ReCode ( $f, $t, $line )
{
$cp1 = $enc::getencoding( $f )
$cp2 = $enc::getencoding( $t )
$inputbytes = $enc::convert( $cp1, $cp2, $cp2.getbytes( $line ))
$outputstring = $cp2.getstring( $inputbytes )
$outputstring | add-content $OutputFile
}
foreach ($line in $InputFile)
{
ReCode -f "windows-1251" -t "utf-8" $line
}Может, кому-то будет полезно.
7. P.S.:
Это третья статья из серии «как я «Zimbra» внедрял». Первая, про внедрение, LDAP-авторизацию и автоматическое создание ящиков для пользователей AD, вот тут. Вторая, про настройку резервного копирования и восстановления Zimbra целиком и отдельными ящиками – тут.
8. P.S.S.:
Есть более простой способ, описанный тут товарищем kiby75
