Ключевые слова:postfix, activedirectory, ldap, zimbra, mail, groupware, (найти похожие документы)
From: Буклов Борис <buklov@mail.ru.>
Newsgroups: email
Date: Mon, 15 Feb 2010 17:02:14 +0000 (UTC)
Subject: Миграция с postfix + Active directory + squirrelmail на zimbra
-------------------
Есть такой замечательный продукт Zimbra Collaboration Suite 6.0 - Open Source Edition,
который имеет замечательный веб интерфейс и один в один брат близнец Zimbra Desktop для
тех кто не любит веб интерфейс.
Пример интерфейса можно посмотреть тут http://www.zimbra.com/products/hosted_demo.php
У меня был настроен postfix и dovecot по статье http://www.linuxmail.info/postfix-dovecot-ldap-centos-5/
и группы рассылки по примеру http://www.linuxmail.info/postfix-active-directory-ldap-lookup-howto/
ну и squirrelmail в качестве веб интерфейса , что бы не устанавливать пользователям
ни каких лишних программ.
В виду того что Zimbra внешне выглядит намного лучше и позволяет делать почти все вещи что
и MS Outlook и к тому же бесплано, было принято решение перейти на неё.
Для этих целей была использована программа zmprov(идёт в комплекте) через которую можно делать
очень много операций.
Так как zimbra может использовать Ad только как источник внешней аутентификации ,
и пользователей создавать сама не может был написан скрипт который добавляет всех
пользователей у которых в AD заполнено поле mail и оно имеет формат name@domain,
где домен имеется в виду почтовый а не АД, а так же отключает пользователей которые
есть в zimbra но нету в домене. Это сделано что бы если уволили юзера в ад его убили
или отключили хотелось что бы в почте оно происходило тоже автоматом
#!/usr/bin/perl
use Net::LDAP;
use utf8;
use Encode;
my $domain="mail_domain";
my $zmprov="/opt/zimbra/bin/zmprov -l";
my $AD_server="10.0.0.150";
my $AD_user="jabberd\@domain.ru";
my $AD_pass="passwd";
my $AD_base="dc=domain,dc=ru";
my $LDAP_server="10.0.0.242";
my $LDAP_user="uid=zimbra,cn=admins,cn=zimbra";
my $LDAP_pass="ldap";
my $LDAP_base="dc=mail_domain,dc=ru";
my %LDAP=(),%AD=();
#----------AD
$ldap = Net::LDAP->new($AD_server);
$ldap->bind($AD_user, password=>$AD_pass);
$mesg = $ldap->search(filter=>"(&(objectclass=user)(mail=*\@$domain)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))",
base=>$AD_base,
attrs=> ['sAMAccountName','displayName','sn', 'givenname',
'initials','department','description','userAccountControl','mail','company','telephoneNumber']);
@entries = $mesg->entries;
foreach $entry (@entries) {
my $name,$sn,$mail,$department,$company,$sAMAccountName;
$id=$entry->get_value(sAMAccountName);
$id=lc($id);
$displayName=$entry->get_value(displayName);
$sn=$entry->get_value(sn);
$givenname=$entry->get_value(givenname);
$initials=$entry->get_value(initials);
$department=$entry->get_value(department);
$description=$entry->get_value(description);
$userAccountControl=$entry->get_value(userAccountControl);
$mail=$entry->get_value(mail);
$mail=lc($mail);
$telephoneNumber=$entry->get_value(telephoneNumber);
$company=$entry->get_value(company);
$disabled=false;
if (length($displayName)>0) {
$AD{$id}{displayName} = $displayName;
}
if (length($sn)>0) {
$AD{$id}{sn} = $sn;
}
if (length($givenname)>0) {
$AD{$id}{givenname} = $givenname;
}
if (length($initials)>0) {
$AD{$id}{initials} = $initials;
}
if (length($description)>0) {
$AD{$id}{description} = $description;
}
if (length($disabled)>0) {
$AD{$id}{disabled} = $disabled;
}
if (length($mail)>0) {
$AD{$id}{mail} = $mail;
}
if (length($telephoneNumber)>0) {
$AD{$id}{telephoneNumber} = $telephoneNumber;
}
if (length($company)>0) {
$AD{$id}{company} = $company;
}
}
#---------- zimbra ldap
print "\nZIMBRA\n";
$ldap2 = Net::LDAP->new($LDAP_server);
$ldap2->bind($LDAP_user, password=>$LDAP_pass);
$mesg = $ldap2->search(filter=>"(&(objectClass=zimbraAccount)(!(userPassword=*))(!(zimbraCalResType=*)))",
base=>$LDAP_base, attrs=> ['uid', 'displayName','sn','givenname','sn','mail','initials',
'description','zimbraPrefFromAddress','zimbraMailDeliveryAddress','zimbraAccountStatus','telephoneNumber','company'] );
my $id='',$displayName='',$sn='',$givenname='',$initials='',$department='',$description='',$disabled='',$mail='';
@entries = $mesg->entries;
foreach $entry (@entries) {
$id=$entry->get_value(uid);
# if (($id ne "admin")&&($id ne "admin")&&($id ne "wiki")&&($id ne "spam.2bv4rvsjs")&&($id ne "ham.xdnsnpsmd3")) {
$displayName=$entry->get_value(displayName);
$sn=$entry->get_value(sn);
$givenname=$entry->get_value(givenname);
$sn=$entry->get_value(sn);
$initials=$entry->get_value(initials);
$description=$entry->get_value(description);
$telephoneNumber=$entry->get_value(telephoneNumber);
$company=$entry->get_value(company);
$status=$entry->get_value(zimbraAccountStatus);
if (defined ($AD{$id})) {
if ("$status" ne "active")
{
print "$id status=active\n";
$status="active";
}
else{
$status="";
}
}
else {
if ("$status" eq "active")
{
print "$id status=locked\n";
$status="locked";
}
else{
$status="";
}
}
$mail=$entry->get_value(zimbraPrefFromAddress);
if (length($mail)==0)
{
$mail=$entry->get_value('zimbraMailDeliveryAddress');
}
if (length($displayName)>0) {
$LDAP{$id}{displayName} = $displayName;
}
if (length($sn)) {
$LDAP{$id}{sn} = $sn;
}
if (length($givenname)>0) {
$LDAP{$id}{givenname} = $givenname;
}
if (length($initials)>0) {
$LDAP{$id}{initials} = $initials;
}
if (length($description)>0) {
$LDAP{$id}{description} = $description;
}
if (length($telephoneNumber)>0) {
$LDAP{$id}{telephoneNumber} = $telephoneNumber;
}
if (length($company)>0) {
$LDAP{$id}{company} = $company;
}
if (length($mail)>0) {
$LDAP{$id}{mail} = $mail;
}
if (length($status)>0) {
$AD{$id}{status} = $status;
}
# }
}
print ("\n===AD==\n");
my $id='',$displayName='',$sn='',$givenname='',$initials='',$department='',$description='',$disabled='',$mail='';
print "удаляем одинаковые и несовпадающие с ад поля\n";
my $k2,$v2;
while(my ($k,$v)=each(%AD)) {
#Юзер уже есть
if (defined ($LDAP{$k})) {
while(($k2,$v2)=each(%{$AD{$k}})) {
# print "$k2 # $v2\n";
if ($LDAP{$k}{$k2} ne $AD{$k}{$k2}) {
delete $LDAP{$k}{$k2};
# print "удалено значение не соответсвующее АД значение\n";
}
else {
# print "Удалены идентичные значения\n";
delete $AD{$k}{$k2};
delete $LDAP{$k}{$k2};
}
}
}
}
open(PIPE,'|/opt/zimbra/bin/zmprov -l');
my $create;
my $attr_st;
my $alias_st;
open (FILE, ">commands.list");
while(my ($k,$v)=each(%AD)) {
$create=0;
$attr_st="";
$alias_st="";
$from_st="";
if (!(defined ($LDAP{$k}))) {
$create=1;
}
#обновляем данные юзера
while(my ($k2,$v2)=each(%{$AD{$k}})) {
# print "$k --> $k2:$v2\n";
if ($k2 eq "mail") {
if ("$k\@$domain" ne "$v2" ) {
$alias_st=$v2;
}
}
if ($k2 eq "displayName") {
$attr_st="$attr_st displayName \"$v2\"";
}
if ($k2 eq "sn") {
$attr_st="$attr_st sn \"$v2\"";
}
if ($k2 eq "givenname") {
$attr_st="$attr_st givenname \"$v2\"";
}
if ($k2 eq "initials") {
$attr_st="$attr_st initials \"$v2\"";
}
if ($k2 eq "description") {
$attr_st="$attr_st description \"$v2\"";
}
if ($k2 eq "sn") {
$attr_st="$attr_st sn \"$v2\"";
}
if ($k2 eq "status") {
print "$k zimbraAccountStatus \"$v2\"\n";
$attr_st="$attr_st zimbraAccountStatus \"$v2\"";
}
if ($k2 eq "telephoneNumber") {
print "$k telephoneNumber \"$v2\"\n";
$attr_st="$attr_st telephoneNumber \"$v2\"";
}
if ($k2 eq "company") {
print "$k company \"$v2\"\n";
$attr_st="$attr_st company \"$v2\"";
}
}
if ($create==1) {
print PIPE "ca $k\@$domain '' $attr_st\n";
print FILE "ca $k\@$domain '' $attr_st\n";
}
else {
if (length($attr_st)>0)
{
print PIPE "ma $k\@$domain $attr_st\n";
print FILE "ma $k\@$domain $attr_st\n";
}
}
if (length($alias_st)>0) {
print PIPE "aaa $k\@$domain $alias_st\n";
print FILE "aaa $k\@$domain $alias_st\n";
}
if (length($alias_st)>0) {
print PIPE "ma $k\@$domain zimbraPrefFromAddress $alias_st\n";
print FILE "ma $k\@$domain zimbraPrefFromAddress $alias_st\n";
}
}
close(FILE);
close(PIPE);
Так же примерно переносятся группы рассылки из АД
так как в АД могут быть группы которые не имеют email а они могут быть
включены в группу рассылки , то просто собираем всех юзеров с почтовым
адресом из этой группы и всех групп которых она содержит и добавляем
в группу рассылки zimbra
#!/usr/bin/perl
use Net::LDAP;
use Net::LDAP::Control::Sort;
use Net::LDAP::Constant qw(LDAP_CONTROL_SORTRESULT);
my $domain="mail_domain";
my $AD_server="10.0.0.150";
my $AD_user="jabberd\@domain.ru";
my $AD_pass="passwd";
my $AD_base="dc=domain,dc=ru";
my $LDAP_server="10.0.0.242";
my $LDAP_user="uid=zimbra,cn=admins,cn=zimbra";
my $LDAP_pass="passwd";
my $LDAP_base="dc=mail_domain,dc=ru";
#use utf8;
my %LDAP=(),%AD=();
open(PIPE,'|/opt/zimbra/bin/zmprov -l');
sub PrintMembers
{
my $dn = @_[0];
my $group_mail = @_[1];
# print "dn=$dn group_mail=$group_mail\n";
my $type="user";
my $ldap = Net::LDAP->new($AD_server);
$ldap->bind($AD_user, password=>$AD_pass);
my $sortc = Net::LDAP::Control->new( LDAP_CONTROL_SORTREQUEST,
order => 'name');
my $mesg = $ldap->search(filter=>"(distinguishedName=$dn)",
base=>$AD_base,control => ['name'] ,control => [ $sortc ],
attrs=> ['objectClass','name','distinguishedName','member','mail'] );
@entries = $mesg->entries;
foreach $entry (@entries) {
my $member=$entry->get_value(member);
my $objectClass=$entry->get_value(objectClass);
my $name=$entry->get_value(name);
my $mail=$entry->get_value(mail);
# print "name=$name\n\n";
foreach my $vals ($entry->get_value(objectClass))
{
if ("$vals" eq "group")
{
$type="group";
}
}
if ("$type" eq "group") # если группа распечатать всех
{
foreach my $vals ($entry->get_value(member))
{
PrintMembers($vals,"$group_mail");
}
}
else { #иначе вывести только имена
if (length($mail)>0) {
# print "$group_mail member -- $mail \n";
$AD{$group_mail}{$mail} = $mail;
}
}
}
$ldap->unbind();
return $i;
}
my $ldap = Net::LDAP->new($AD_server);
$ldap->bind($AD_user, password=>$AD_pass);
my $mesg = $ldap->search(filter=>"(&(objectClass=group)(mail=*\@$domain))",
base=>$AD_base,
attrs=> ['member','name','distinguishedName','mail'] );
my @entries = $mesg->entries;
foreach $entry (@entries) {
my $group_mail=$entry->get_value(mail);
my $dn= $entry->get_value(distinguishedName);
my $name=$entry->get_value(name);
# print "--------GROUP $group_mail-------\n";
PrintMembers($dn,"$group_mail");
}
#это просто файл куда пишутся комманды которые выполнились в zmpro для отладки
open (FILE, ">commands.list");
print "\n--------ZIMBRA groups--------\n";
$ldap = Net::LDAP->new($LDAP_server);
$ldap->bind($LDAP_user, password=>$LDAP_pass);
$mesg = $ldap->search(filter=>"(objectClass=zimbraDistributionList)",
base=>$LDAP_base,
attrs=> ['mail', 'cn','zimbraMailForwardingAddress'] );
my $name,$sn,$mail,$department,$company,$sAMAccountName;
@entries = $mesg->entries;
foreach $entry (@entries) {
$cn=$entry->get_value(cn);
$group_mail=$entry->get_value(mail);
@members=$entry->get_value(zimbraMailForwardingAddress);
#delete empty groups
if ($#members==-1) {
print FILE "ddl $group_mail\n";
print PIPE "ddl $group_mail\n";
}
else {
foreach $mail (@members) {
$LDAP{$group_mail}{$mail} = $mail;
# print FILE "LDAP___$group_mail $mail\n";
}
}
}
#print "-------Удаляем одинаковые значения\n";
while(my ($k,$v)=each(%AD)) {
while(my ($k2,$v2)=each(%{$AD{$k}})) {
#print "$k $k2\n";
if (defined ($LDAP{$k})) {
if (defined ($LDAP{$k}{$k2})) {
delete $LDAP{$k}{$k2};
delete $AD{$k}{$k2};
}
}
}
}
#print "------ Удаляем группы которых нету в АД";
while(my ($k,$v)=each(%LDAP)) {
# print "\n$k $k2\n";
if (!(defined ($AD{$k}))) {
print FILE "ddl $k\n";
print PIPE "ddl $k\n";
}
else {
while(my ($k2,$v2)=each(%{$LDAP{$k}})) {
print FILE "rdlm $k $k2\n";
print PIPE "rdlm $k $k2\n";
}
}
}
while(my ($k,$v)=each(%AD)) {
print FILE "cdl $k\n";
print PIPE "cdl $k\n";
while(my ($k2,$v2)=each(%{$AD{$k}})) {
print FILE "adlm $k $k2\n";
print PIPE "adlm $k $k2\n";
}
}
close(FILE);
close(PIPE);
Эти 2 скрипта у меня запускаются каждый час для того что бы в zimbra
всегде был актуальные юзеры и списки.
Теперь приступим к переносу адресных кних пользователей.
В squirrelmail они хранятся в файлах username.abook
этот скрипт загружает адресную книгу одного пользователя
cat convert.pl
#!/usr/bin/perl
use utf8;
open (FILE, "$ARGV[0]");
open(PIPE,'|/opt/zimbra/bin/zmprov');
$username=$ARGV[0];
$username=~s/.*[^\/]+\///;
$username=~s/.abook//;
print PIPE "sm $username\n";
while ($line = <FILE>)
{
$line=~s/\n//;
@contact = split(/\|/,$line);
print PIPE "createContact company \"\" email \"@contact[3]\" firstName \"@contact[1]\" lastName \"@contact[2]\" notes \"@contact[4]\"\n";
}
print PIPE "quit\n";
print PIPE "quit\n";
close (FILE);
close (PIPE);
а этот перебирает все файлы и запускает вышестоящий скрипт
cat convert.sh
#!/bin/bash
for file in /zimbra/address_book/books/*.abook
do
./convert.pl $file
echo "Converting $file"
done
ну и напоследок загружаем все почтовые сообщения в zimbra
#!/usr/bin/perl
$path="/zimbra/messages";
$hostname="hostname";# здесь пишем имя сервера, так как сообщения имеют вид например
# у меня 1232529672.V806I98065M202440.mx.domain_name.ru:2,S
open(PIPE,'|/opt/zimbra/bin/zmprov');
sub load_user {
$username = $_[0];
print PIPE "sm $username\n";
#load inbox
@ms=`find $username -maxdepth 2 -regex '.*$hostname.*'`;
my $mess="";
foreach $message (@ms) {
print PIPE "am inbox $path/$message"
}
@dirs=`find $username/.* -maxdepth 0 -type d`;
foreach $dir (@dirs) {
$dir=~s/($username\/)//;
$dir=~s/(\n)+//;
if (("$dir" eq "..")||("$dir" eq ".")) {
next;
}
$dir=substr($dir,1,length($dir));
print "$dir\n";
print PIPE "createFolder \"/$dir\"\n";
@ms=`find '$username/.$dir' -regex '.*$hostname.*'`;
print "find \'$username/.$dir\' -regex \'.*$hostname.*\'";
foreach $message (@ms) {
$message=~s/(\n)+//;
print PIPE "am \"$dir\" \"$path/$message\"\n";
}
}
print PIPE "quit";
}
$username=$ARGV[0];
load_user($username);
close (PIPE);
этот скрипт загружает одну папку имя которой передаётся в качестве аргумента.
Научил Зимбру взаимодействовать с procmail и использовать внутренний антивирь, дабы не иметь 2-х одинаковых процессов. Если интересно - могу выложить конфиги.
Полезная статья. Использовал при интеграции ZCS 7.0 с AD, но русские имена пользователей переносятся с крякозяблами. Если модифицировать так чтобы запускались команды
zmprov ca acc@domain attr "русские буквы"
zmprov ca acc@domain attr "русские буквы"
то все ок, но очень медленно инициализируется zmprov на каждую команду, хз что делать даже.
День добрый Boris!
Попробовал использовать Ваш скрипт последней версии(т.к. возникла проблема со старым из-за отображения русских имен) -
Получаю соответственно ошибки, где используются символы ╜
Например 26 строка - my %LDAP=(),╜=();
44, 217, 246