Этот документ описывает процедуру установки почтового сервера на базе Ubuntu 10.04 LTS. В документе использовано следующее форматирование:

строки с таким выделением

это команды, которые мы задаем в терминале.

строки с таким выделением

это ответы, которые мы получаем на введенные нами команды

строки с таким выделением

это содержимое различного рода конфигурационных файлов.

Будьте предельно внимательны при копировании команд!

Вся процедура создания почтового сервера проверена, однако, естественно, в документе возможны ошибки и неточности. Если у Вас что-то не получается, или получается не так, как следует/описано, то:

  • еще раз проверьте, все ли Вы делаете правильно
  • анализируйте соответствующие лог-файлы, расположенные в /var/log/
  • ищите решение Ваших проблем в гугле
  • консультируйтесь на форуме (старайтесь быть как можно более конкретным, и точно сообщите, в чем именно у Вас возникла проблема)

В процессе создания документа использована куча различного рода информации, доступной в Интернет. К сожалению, не представляется возможным перечислить все источники информации, однако хочется выразить признательность всем авторам, чья работа так или иначе была использована при написании этого документа.

Постановка задачи

Мы установим на чистую машину на Ubuntu 10.04 LTS server почтовый сервер на базе postfix. Для «забора» почты по протоколам pop3/imap будем использовать dovecot с поддержкой квот для почтовых ящиков пользователей. На нашем сервере будет располагаться почта для двух разных доменов - aaa.ru (наш «основной» домен) и bbb.ru. Для настройки почты будем использовать mysql. Для аутентификации пользователей будем использовать два механизма - mysql для домена bbb.ru и openldap для пользователей нашего основного домена aaa.ru. Кроме этого, почтовый сервер будет проверять входящую почту с помощью amavis на вирусы (clamav) и спам (spamassassin). Сделаем также лист рассылки (mailman) и автоответчик для пользователей. Установим систему для доступа к почте через web — horde/imp. Мы будем использовать horde/imp, т.к. это самый продвинутый веб-клиент, позволяющий полностью использовать все современные возможности почты, включая подписывание почты по протоколам S/MIME и прочие. Кроме этого, установим munin для рисования красивых графиков работы нашего почтового сервера.

Итак, предположим, что у нас есть только что установленная 10.04 LTS. Предположим также, что компьютер зовут «oban», он имеет IP адреc 10.0.0.6, находится в DMZ за NAT. Для доступа извне на наш почтовый сервер мы пробросим нужные порты по протоколу TCP – 80, 443 (для доступа к веб-серверу), 25 (для доступа к SMTP-серверу), 110, 143, 993, 995 (для “забора” почты снаружи по протоколам POP3 и IMAP). Если нужно, откроем еще порт 22 для доступа по SSH (хотя этого делать не стоит – лучше иметь доступ к серверу только изнутри нашей сети).

Пользователь, за которым мы «сидим» - это, конечно, не root, а toor (Вы же не сидите за рутом, правда?).

Установка системы

Вначале установим «чистую» систему Ubuntu 10.04 LTS server. В процессе установки ничего дополнительно устанавливать не будем, все нужное установим позже.

Первое что мы сделаем после перезагрузки, еще не отходя от терминала - установим ssh-сервер, чтобы все остальное делать удаленно:

sudo apt-get install openssh-server

После этого со спокойной совестью выходим и идем к нашему собственному компьютеру. Чтобы не набирать каждый раз пароль при входе на наш сервер, сделаем вход по ключу:

ssh-copy-id -i ~/.ssh/id_rsa.pub toor@10.0.0.6

Нас спросят, уверены ли мы, что хотим соединиться с этим сервером

Are you sure you want to continue connecting (yes/no)?

Ответим

yes

Нас спросят пароль пользователя toor на oban

toor@10.0.0.6's password:

Введем. Все, теперь для захода на наш сервер с нашего компьютера достаточно набрать

ssh toor@10.0.0.6

и мы там. Заметьте, что это не то же самое, что попытка входа как

ssh toor@oban

так что если хотите - проделайте это еще раз уже для такого варианта.

Итак, вначале установим все обновления (их наверняка набралось уже порядочно):

sudo apt-get update 
sudo apt-get dist-upgrade

Мы используем dist-upgrade а не просто upgrade чтобы получить обновления в том числе и для ядра.

Естественно, соглашаемся с предложенным списком и (если были обновления ядра или подобные важные обновления) перезагружаемся

sudo reboot

Начинаем установку.

Установим нужные пакеты

sudo apt-get install postfix postfix-mysql postfix-ldap postfix-doc postfix-tls libsasl2-2 libsasl2-modules libsasl2-modules-sql sasl2-bin libpam-mysql dovecot-imapd dovecot-pop3d dovecot-common mysql-client mysql-server apache2 libapache2-mod-php5 php5 php5-mysql

(здесь и далее соглашаемся с установкой дополнительных пакетов).

Отвечаем на вопросы

Новый пароль для MySQL пользователя «root»:

Обязательно устанавливаем пароль для пользователя root в mysql (это - не пароль пользователя root на компьютере, не путайте!). Здесь и далее это -

<mysql_password>
Повторите ввод пароля для MySQL пользователя «root»:
<mysql_password>
Выберите тип настройки почтового сервера, который оптимально удовлетворяет ваши требования.

Здесь просто жмем <OK>.

Общий тип почтовой настройки:

Выбираем

Internet - сайт
Системное почтовое имя:
oban.aaa.ru

После окончания установки начнем конфигурацию.

Создадим базу данных mysql для хранения всей информации для почтового сервера

mysqladmin -u root -p create mail

Нас спросят пароль пользователя root mysql

Enter password:

Введем выбранный на этапе установки пароль

<mysql_password>

Перейдем в оболочку mysql (опять вводя тот же пароль)

mysql -u root -p

Создадим специального пользователя mail_admin и паролем <mail_admin_password> (замените!) для доступа к нашей базе данных mail с привилегиями SELECT, INSERT, UPDATE и DELETE. Доступ ему будет разрешен только с локального компьютера (т.е. с самого сервера):

GRANT SELECT, INSERT, UPDATE, DELETE ON mail.* TO 'mail_admin'@'localhost' IDENTIFIED BY '<mail_admin_password>'; 
GRANT SELECT, INSERT, UPDATE, DELETE ON mail.* TO 'mail_admin'@'localhost.localdomain' IDENTIFIED BY '<mail_admin_password>';
FLUSH PRIVILEGES;

Теперь создадим нужные нам таблицы в базе данных mail

use mail;
CREATE TABLE domains ( 
domain varchar(50) NOT NULL, 
PRIMARY KEY (domain) ) 
TYPE=MyISAM; 
 
CREATE TABLE forwardings ( 
source varchar(80) NOT NULL, 
destination TEXT NOT NULL, 
PRIMARY KEY (source) ) 
TYPE=MyISAM; 
 
CREATE TABLE transport ( 
domain varchar(128) NOT NULL DEFAULT '', 
transport varchar(128) NOT NULL DEFAULT '', 
UNIQUE KEY domain (`domain`) 
) ENGINE=MyISAM; 
 
CREATE TABLE users ( 
email varchar(80) NOT NULL, 
password varchar(20) NOT NULL, 
quota varchar(20) DEFAULT '0',
PRIMARY KEY (email) 
) TYPE=MyISAM; 

(здесь мы задаем квоту для пользователей по умолчанию без лимита - 0)

и выйдем из оболочки mysql

quit;

Настройка Postfix

Сейчас нам необходимо указать Postfix, где ему искать информацию в базе данных. Для этого создадим шесть текстовых файлов. Как вы можете заметить, я указываю Postfix соединяться с MySQL через IP адрес 127.0.0.1 вместо localhost. Это связано с тем, что Postfix запущенный в chroot окружении не сможет иметь доступа к MySQL сокету, если будет пытаться использовать localhost для подключения.

Проверим, что mysql «слушает» локальный IP адрес

cat /etc/mysql/my.cnf | grep bind
bind-address = 127.0.0.1

Если пришлось поменять /etc/mysql/my.cnf, то перезапустим mysql

sudo service mysql restart

и проверяем, что он действительно слушает этот адрес:

sudo netstat -tap | grep mysql 
tcp 0 0 localhost:mysql *:* LISTEN 5908/mysqld

(здесь 5908- это номер процесса, у Вас будет другой).

Теперь создадим файлы для того, чтобы postfix знал, где что искать в нашей базе данных:

sudo nano /etc/postfix/mysql-virtual_domains.cf
user = mail_admin 
password = <mail_admin_password> 
dbname = mail 
query = SELECT domain AS virtual FROM domains WHERE domain='%s' 
hosts = 127.0.0.1
sudo nano /etc/postfix/mysql-virtual_forwardings.cf
user = mail_admin 
password = <mail_admin_password> 
dbname = mail 
query = SELECT destination FROM forwardings WHERE source='%s' 
hosts = 127.0.0.1
sudo nano /etc/postfix/mysql-virtual_mailboxes.cf
user = mail_admin 
password = <mail_admin_password> 
dbname = mail 
query = SELECT CONCAT(SUBSTRING_INDEX(email,'@',-1),'/',SUBSTRING_INDEX(email,'@',1),'/') FROM users WHERE email='%s' 
hosts = 127.0.0.1
sudo nano /etc/postfix/mysql-virtual_email2email.cf
user = mail_admin 
password = <mail_admin_password> 
dbname = mail 
query = SELECT email FROM users WHERE email='%s' 
hosts = 127.0.0.1
sudo nano /etc/postfix/mysql-virtual_transports.cf
user = mail_admin 
password = <mail_admin_password> 
dbname = mail 
query = SELECT transport FROM transport WHERE domain='%s' 
hosts = 127.0.0.1

Т.к. в этих файлах у нас лежит пароль для доступа к базе данных, меняем права доступа к ним (разрешаем чтение только группе postfix, в которую входит наш почтовый сервер postfix):

sudo chmod o= /etc/postfix/mysql-virtual_*.cf 
sudo chgrp postfix /etc/postfix/mysql-virtual_*.cf

Проверяем права доступа к этим файлам:

ls -al /etc/postfix/mysql-virtual*.cf
-rw-r—– 1 root postfix 134 2010-11-26 11:24 /etc/postfix/mysql-virtual_domains.cf
-rw-r—– 1 root postfix 119 2010-11-26 11:25 /etc/postfix/mysql-virtual_email2email.cf
-rw-r—– 1 root postfix 132 2010-11-26 11:24 /etc/postfix/mysql-virtual_forwardings.cf
-rw-r—– 1 root postfix 188 2010-11-26 11:25 /etc/postfix/mysql-virtual_mailboxes.cf
-rw-r—– 1 root postfix 128 2010-11-26 11:25 /etc/postfix/mysql-virtual_transports.cf

Создаем нового пользователя и группу с названием vmail с домашней директорией /home/vmail , где будут находится почтовые ящики:

sudo groupadd -g 5000 vmail \\
sudo useradd -g vmail -u 5000 vmail -d /home/vmail -m

Предварительная настройка postfix (нам еще придется ее менять чуть позже). Не забудьте поменять oban.aaa.ru на Ваше реальное полное имя сервера, а то postfix не будет работать!

sudo postconf -e 'myhostname = oban.aaa.ru' 
sudo postconf -e 'mydestination = oban.aaa.ru, localhost, localhost.localdomain' 
sudo postconf -e 'mynetworks = 127.0.0.0/8' 
sudo postconf -e 'virtual_alias_domains =' 
sudo postconf -e 'virtual_alias_maps = proxy:mysql:/etc/postfix/mysql-virtual_forwardings.cf, mysql:/etc/postfix/mysql-virtual_email2email.cf' 
sudo postconf -e 'virtual_mailbox_domains = proxy:mysql:/etc/postfix/mysql-virtual_domains.cf' 
sudo postconf -e 'virtual_mailbox_maps = proxy:mysql:/etc/postfix/mysql-virtual_mailboxes.cf' 
sudo postconf -e 'virtual_mailbox_base = /home/vmail' 
sudo postconf -e 'virtual_uid_maps = static:5000' 
sudo postconf -e 'virtual_gid_maps = static:5000' 
sudo postconf -e 'smtpd_sasl_auth_enable = yes' 
sudo postconf -e 'broken_sasl_auth_clients = yes' 
sudo postconf -e 'smtpd_sasl_authenticated_header = yes' 
sudo postconf -e 'smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination' 
sudo postconf -e 'smtpd_use_tls = yes' 
sudo postconf -e 'smtpd_tls_cert_file = /etc/postfix/smtp.crt' 
sudo postconf -e 'smtpd_tls_key_file = /etc/postfix/smtp.key' 
sudo postconf -e 'transport_maps = proxy:mysql:/etc/postfix/mysql-virtual_transports.cf' 
sudo postconf -e 'proxy_read_maps = $local_recipient_maps $mydestination $virtual_alias_maps $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks' 

Создание SSL-сертификатов

Теперь создадим SSL сертификаты для TLS. Лучше всего (хотя и не обязательно) создать вначале свой CA (Certificate Authority), а потом подписать от его имени все сертификаты (нам еще понадобятся сертификаты для dovecot). В этом случае клиентам нужно будет добавить доверие к этому CA, а не каждому сертификату отдельно.

Создаем CA.

Создадим папку, в которой будем генерить все сертификаты

mkdir ~/CA_clean

Нам нужно вначале подготовить конфигурационный файл для создания CA

nano ca.conf
[req] 
distinguished_name =req_distinguished_name 
x509_extensions = v3_ca 
prompt = no 
[req_distinguished_name] 
C= RU 
ST = Moscow Region 
L = Moscow 
O = AAA Ltd. 
OU = ROOTCA 
CN = aaa.ru 
emailAddress = admin@aaa.ru 
[v3_ca] 
basicConstraints	= CA:true 
nsComment = "CA certificate of AAA Ltd." 
nsCertType = sslCA, emailCA 
subjectKeyIdentifier=hash 
authorityKeyIdentifier=keyid:always,issuer:always 
[ usr_cert ] 
nsComment = "Certificate issued by AAA Ltd." 
;nsBaseUrl = http://ca.aaa.ru/ 
;nsRevocationUrl = http://ca.aaa.ru/crl.crl 
;issuerAltName = URI:http://ca.aaa.ru/ca.crt 
;crlDistributionPoints = URI:http://ca.aaa.ru/crl.crl

Здесь и далее (замените на нужное Вам):

C = RU (страна)

ST = Moscow Region (регион)

L = Moscow (город)

O = AAA Ltd. (название компании)

CN = aaa.ru (имя сервера, для которого выдается ключ; в случае CA - имя домена)

emailAddress = admin@aaa.ru (почтовый адрес администратора)

(закоментаренные строки нужны только, если Вы действительно хотите иметь более-менее нормальный CA с листами отзыва ключей и т.п., но это выходит за рамки этого how-to)

Создаем частный ключ ключ CA

sudo openssl genrsa -des3 -out ca.key 4096
Enter pass phrase for ca.key:

Введите пароль для файла ca.key (два раза для подтверждения) и не забывайте его!

Verifying - Enter pass phrase for ca.key:

Он будет нужен для подписывания всех ключей (здесь используем sudo т.к. при этом переписывается состояние random_state компьютера)

Создаем открытый ключ CA. Мы говорим, что ключи нашего CA имеют «срок жизни» 10 лет (-days 3650).

openssl req -new -x509 -nodes -sha1 -days 3650 -key ca.key -out ca.crt -config ca.conf
Enter pass phrase for ca.key:

(вводим выбранный нами на предыдущем шаге пароль закрытого ключа CA)

Создаем сертификат для подписывания:

openssl pkcs12 -export -in ca.cer -inkey ca.key -out ca.pfx

openssl x509 -trustout -inform PEM -in ca.crt -outform DER -out ca.pfx

Создадим директорию, в которой у нас будут лежать все ключи для всех серверов (если мы в дальнейшем будем создавать и подписывать ключи для других серверов; например, захотим, чтобы ключи были разные для smtp.aaa.ru pop3.aaa.ru и imap.aaa.ru) и, соответственно, директории для всех имен серверов (в нашем случае — oban.aaa.ru)

mkdir SERVERS 
mkdir SERVERS/oban.aaa.ru

Создаем файлы конфигураций для ключей серверов (в каждой директории - свой файл, т.к. в нем записано имя сервера). В случае одного имени (замените на нужные Вам значения):

nano SERVERS/oban.aaa.ru/openssl.conf
[ req ] 
default_bits = 2048 
distinguished_name  = req_distinguished_name 
prompt = no 
req_extensions = v3_req 
[ req_distinguished_name ] 
C = RU 
ST = Moscow Region 
L = Moscow 
O = AAA Ltd. 
CN = oban.aaa.ru 
emailAddress = admin@aaa.ru 
[ v3_req ] 
basicConstraints = CA:FALSE 
subjectKeyIdentifier = hash 

Теперь сгенерим ключи для нашего сервера (заметьте, что мы опять используем здесь sudo):

sudo openssl genrsa -passout pass:1234 -des3 -out SERVERS/oban.aaa.ru/server.key.1 2048

Здесь 1234 — парольная фраза для промежуточного ключа. Она нам нужна только временно, т.к. мы в результате хотим получить ключ без пароля (требование postfix).

Убираем из ключа парольную фразу:

openssl rsa -passin pass:1234 -in SERVERS/oban.aaa.ru/server.key.1 -out SERVERS/oban.aaa.ru/server.key

Генерим запрос на подпись нашего ключа

openssl req -config SERVERS/oban.aaa.ru/openssl.conf -new -key SERVERS/oban.aaa.ru/server.key -out SERVERS/oban.aaa.ru/server.csr 

и удаляем промежуточный ключ

rm -f SERVERS/oban.aaa.ru/server.key.1

Теперь нам нужно подписать наш созданный ключ от имени своего CA

Сделаем конфигурационный файл для подписи (срок действия подписи 5 лет - 1828 дней):

nano sign.config 
[ ca ] 
default_ca = CA_own 
[ CA_own ] 
certs = . 
new_certs_dir = ca.db.certs 
database = ca.db.index 
serial = ca.db.serial 
RANDFILE = ca.db.rand 
certificate = ca.crt 
private_key = ca.key 
default_days = 1825 
default_crl_days = 1 
default_md = sha1 
preserve = no 
policy = policy_anything 
x509_extensions = usr_cert 
[ policy_anything ] 
countryName = optional 
stateOrProvinceName = optional 
localityName = optional 
organizationName = optional 
organizationalUnitName = optional 
commonName = supplied 
emailAddress = optional 
[usr_cert] 
basicConstraints = CA:false 
subjectKeyIdentifier = hash 
authorityKeyIdentifier = keyid:always,issuer:always 

и создадим директорию для хранения сертификатов подписей:

mkdir ca.db.certs

Для первого подписанного ключа создаем его номер и формируем индексный файл (для остальных - не нужно!)

echo '01' > ca.db.serial 
cp /dev/null ca.db.index 

и подписываем (обратите внимание, что мы опять используем sudo)

sudo openssl ca -batch -config sign.config -out SERVERS/oban.aaa.ru/server.crt -infiles SERVERS/oban.aaa.ru/server.csr
Enter pass phrase for ca.key:

(введем пароль закрытого ключа CA)

Проверим подпись (на всякий случай):

openssl verify -CAfile ca.crt SERVERS/oban.aaa.ru/server.crt

Мы должны получить

SERVERS/oban.aaa.ru/server.crt: OK

Номер ключа и индексный файл автоматически обновились, удалим старые файлы

rm -f ca.db.serial.old 
rm -rf ca.db.index.old

Скопируем ключи в директорию /etc/ssl

sudo cp SERVERS/oban.aaa.ru/server.key /etc/ssl/private/oban.key 
sudo cp SERVERS/oban.aaa.ru/server.crt /etc/ssl/certs/oban.crt

и сменим права доступа к закрытому ключу

sudo chmod og= /etc/ssl/private/oban.key

Теперь установим доверие к новому CA. Для этого создадим папку для него

sudo mkdir /usr/share/ca-certificates/aaa 

и скопируем туда наш сертификат CA

sudo cp ca.crt /usr/share/ca-certificates/aaa/ 

После этого переконфигурируем наши корневые сертификаты

sudo dpkg-reconfigure ca-certificates 

(ответим, что мы хотим доверять новым сертификатам и выберем в списке наш новый сертификат для активации)

Нашим клиентам, которые будут связываться с нашим сервером с использованием шифрования (TLS, SSL) или с нашим веб-сервером по протоколу HTTPS нужно установить доверие к нашему CA. Тем или иным способом передайте им открытый ключ нашего CA - ca.crt.

Внимание! Никогда никому не передавайте созданные приватные ключи *.key и не делайте их доступными!

Настройка saslauthd

Авторизация почтовых пользователей на нашем сервере будет происходить через pam, к которому будет обращаться демон авторизации sasl.

Сначала выполним следующую команду:

sudo mkdir -p /var/spool/postfix/var/run/saslauthd 

Затем отредактируем файл /etc/default/saslauthd

sudo nano /etc/default/saslauthd

Установим параметр START в yes и заменим строку OPTIONS=«-c -m /var/run/saslauthd» на OPTIONS=«-c -m /var/spool/postfix/var/run/saslauthd -r»

Создадим файл /etc/pam.d/smtp

sudo nano /etc/pam.d/smtp
auth sufficient pam_mysql.so user=mail_admin passwd=<mail_admin_password> host=127.0.0.1 db=mail table=users usercolumn=email passwdcolumn=password crypt=1 
account sufficient pam_mysql.so user=mail_admin passwd=<mail_admin_password> host=127.0.0.1 db=mail table=users usercolumn=email passwdcolumn=password crypt=1

и сменим его права доступа

sudo chmod o= /etc/pam.d/smtp

Создаем файл /etc/postfix/sasl/smtpd.conf

sudo nano /etc/postfix/sasl/smtpd.conf
pwcheck_method: saslauthd 
mech_list: plain login 
allow_plaintext: true

и меняем ему владельца и права доступа

sudo chown postfix /etc/postfix/sasl/smtpd.conf 
sudo chmod og= /etc/postfix/sasl/smtpd.conf

Добавляем пользователя postfix в группу sasl (это даст Postfix права доступа к saslauthd):

sudo adduser postfix sasl 

Перезапускаем Postfix и Saslauthd:

sudo /etc/init.d/postfix restart 
sudo /etc/init.d/saslauthd restart

Для проверки авторизации создадим пользователя admin@bbb.ru в базе данных (заодно вначале в таблице domains зададим оба наших почтовых домена — aaa.ru и bbb.ru):

mysql -u root -p
Enter password:

Введем пароль mysql

<mysql_password>
USE mail; 
INSERT INTO `domains` (`domain`) VALUES ('aaa.ru'); 
INSERT INTO `domains` (`domain`) VALUES ('bbb.ru'); 
INSERT INTO `users` (`email`, `password`, `quota`) VALUES ('admin@bbb.ru', ENCRYPT('secret'), '10M'); 
quit;

(здесь secret - это пароль нашего пользователя, выберите более подходящий; квоту пользователю мы задаем в размере 10Мбайт)

Для проверки авторизации нам нужно сгенерить строку, которую нужно передать при авторизации (обратите внимание на обратные слеши перед @ и 0):

perl -MMIME::Base64 -e 'print encode_base64("admin\@bbb.ru\0admin\@bbb.ru\0secret");' 
YWRtaW5AYmJiLnJ1AGFkbWluQGJiYi5ydQBzZWNyZXQ=

Теперь проверим авторизацию

telnet 127.0.0.1 25
Trying 127.0.0.1…
Connected to 127.0.0.1.
Escape character is '^]'.
220 oban.aaa.ru ESMTP Postfix (Ubuntu)
EHLO testing
250-oban.aaa.ru
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-AUTH PLAIN LOGIN
250-AUTH=PLAIN LOGIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
AUTH PLAIN YWRtaW5AYmJiLnJ1AGFkbWluQGJiYi5ydQBzZWNyZXQ=

(здесь вставляем полученную ранее строку авторизации)

235 2.7.0 Authentication successful
QUIT 
221 2.0.0 Bye
Connection closed by foreign host.

Если мы получим ответ, отличный от «Authentication successful», то мы где-то ошиблись. Проверить, в чем именно, можно, посмотрев файлы /var/log/auth.log и /var/log/mail.log

Теперь сделаем аутентификацию по openldap. Установим нужные пакеты:

sudo apt-get install libpam-ldap

и ответим на вопросы:

LDAP server Uniform Resource Identifier:
ldap://10.0.0.3/
Обратите внимание — не ldapi!
Distinguished name of the search base:
dc=aaa,dc=ru
LDAP version to use:
3
Make local root Database admin:
Да
Does the LDAP database require login?
Нет
LDAP account for root:
cn=admin,dc=aaa,dc=ru
LDAP root account password:
<ldap_root_password>

(конечно, нужное замените на свои параметры). Здесь мы предполагаем, что:

  • ldap-сервер доступен по IP 10.0.0.3
  • корень расположен в dc=aaa,dc=ru
  • администратор ldap-сервера это cn=admin,dc=aaa,dc=ru
  • он имеет пароль <ldap_root_password>

Редактируем файл

sudo nano /etc/ldap.conf

и добавляем в него одну строку после закомментаренной #bind_policy hard

bind_policy soft

Редактируем файл

sudo nano /etc/pam.d/smtp

и добавляем в него строки

auth sufficient pam_ldap.so 
account sufficient pam_ldap.so

Теперь можем проверить аутентификацию ldap-пользователя (предполагаем, что в ldap есть пользователь admin с паролем secret (у которого, кстати, почтовый адрес admin@aaa.ru, но это сейчас неважно; важно понимать, что это — другой пользователь, а не admin@bbb.ru):

perl -MMIME::Base64 -e 'print encode_base64("admin\0admin\0secret");' 
YWRtaW4AYWRtaW4Ac2VjcmV0
telnet 127.0.0.1 25 
Trying 127.0.0.1…
Connected to 127.0.0.1.
Escape character is '^]'.
220 oban.aaa.ru ESMTP Postfix (Ubuntu)
EHLO testing 
250-oban.aaa.ru
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-AUTH PLAIN LOGIN
250-AUTH=PLAIN LOGIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
AUTH PLAIN YWRtaW4AYWRtaW4Ac2VjcmV0
235 2.7.0 Authentication successful
QUIT 
221 2.0.0 Bye
Connection closed by foreign host.

Для почтовых ящиков локальных пользователей будем использовать LDAP. Заметьте, что мы конфигурируем их как виртуальных пользователей, т.е. локальный домен (aaa.ru) мы занесли в базу данных. Предположим, что локальные пользователи в LDAP расположены в ou=Users,dc=aaa,dc=ru причем их имена записаны в uid а почтовые адреса - в mail.

Создаем файл /etc/postfix/ldap-mailboxes.cf

sudo nano /etc/postfix/ldap-mailboxes.cf
server_host = 10.0.0.3 
server_port = 389 
version = 3 
bind = yes 
bind_dn = cn=admin,dc=aaa,dc=ru 
bind_pw = <ldap_root_password> 
search_base = ou=Users,dc=aaa,dc=ru 
query_filter = (mail=%s) 
result_attribute = mail 
result_filter = %d/%u/

Меняем права доступа

sudo chgrp postfix /etc/postfix/ldap-mailboxes.cf 
sudo chmod o= /etc/postfix/ldap-mailboxes.cf 

и меняем строку virtual_mailbox_maps = … в файле /etc/postfix/main.cf

sudo nano /etc/postfix/main.cf
virtual_mailbox_maps = proxy:mysql:/etc/postfix/mysql-virtual_mailboxes.cf ldap:/etc/postfix/ldap-mailboxes.cf

Для проверки в том же файле временно меняем строку

#smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination 
smtpd_recipient_restrictions = permit_sasl_authenticated, reject_unauth_destination 

(это запрещает доставку писем с локального IP-адреса не в наши виртуальные домены)

Перезапускаем postfix

sudo /etc/init.d/postfix restart 

и проверяем работу обоих наших тестовых адресов - test@bbb.ru (который записан в базе данных) и betatest@aaa.ru (который записан в LDAP у пользователя betatest)

telnet 127.0.0.1 25 
Trying 127.0.0.1…
Connected to 127.0.0.1.
Escape character is '^]'.
220 oban.aaa.ru ESMTP Postfix (Ubuntu)
HELO test 
250 oban.aaa.ru
MAIL FROM: aaa@aa.aa 
250 2.1.0 Ok
RCPT TO: admin@bbb.ru
250 2.1.5 Ok
RCPT TO: admin@aaa.ru 
250 2.1.5 Ok

(если мы попробуем передать на несуществующий адрес, мы получим:)

RCPT TO: aaa@bbb.ru
550 5.1.1 <aaa@bbb.ru>: Recipient address rejected: User unknown in virtual mailbox table
QUIT 
221 2.0.0 Bye
Connection closed by foreign host.

Вернем обратно строку в файле /etc/postfix/main.cf:

sudo nano /etc/postfix/main.cf
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination

Для запрета аутентификации в postfix с передачей данных открытым текстом, вставляем в файл /etc/postfix/main.cf

sudo nano /etc/postfix/main.cf 
smtpd_tls_auth_only = yes 
smtpd_tls_cert_file = /etc/ssl/certs/oban.crt 
smtpd_tls_key_file = /etc/ssl/private/oban.key 
smtpd_use_tls = yes

и перезапускаем postfix

sudo /etc/init.d/postfix restart

Откроем и изменим /etc/aliases

sudo nano /etc/aliases

Сделайте так, чтобы postmaster указывал на root, а root указывал на ваше имя пользователя или ваш почтовый адрес, у вас должно получится примерно так:

postmaster: root 
clamav: root
root: admin@aaa.ru 

После изменения /etc/aliases вы должны запустить команду

sudo newaliases 

Настройка dovecot

Скопируем файл

sudo cp /etc/pam.d/smtp /etc/pam.d/dovecot

(мы будем для dovecot использовать тоже аутентификацию через pam)

Отредактируем файл /etc/dovecot/dovecot-ldap.conf

sudo nano /etc/dovecot/dovecot-ldap.conf
uris = ldap://10.0.0.3/ 
dn = cn=admin,dc=aaa,dc=ru 
dnpass = <ldap_root_password> 
ldap_version = 3 
base = ou=Users,dc=aaa,dc=ru 
deref = searching 
scope = subtree 
user_attrs = mail=mail=maildir:/home/vmail/aaa.ru/%n/ 
user_filter = (&(uid=%n))

Мы не будем задавать квоту для локальных пользователей, чтобы у них была неограниченная квота (напомним, что мы задали по умолчанию квоту без лимита).

Отредактируем файл /etc/dovecot/dovecot-sql.conf

sudo nano /etc/dovecot/dovecot-sql.conf
driver = mysql 
connect = host=127.0.0.1 user=mail_admin password=<mail_admin_password> dbname=mail 
user_query = SELECT email, CONCAT('/home/vmail/',CONCAT(SUBSTRING_INDEX(email,'@',-1),'/',SUBSTRING_INDEX(email,'@',1))) AS home, concat('*:storage=', quota) as quota_rule  FROM users WHERE email='%u'

Отредактируем файл /etc/dovecot/dovecot.conf, изменив следующие параметры:

sudo nano /etc/dovecot/dovecot.conf
disable_plaintext_auth = yes 
ssl = yes
ssl_cert_file = /etc/ssl/certs/oban.crt 
ssl_key_file = /etc/ssl/private/oban.key
mail_location = maildir:/home/vmail/%d/%n
mail_uid = vmail 
mail_gid = vmail 
maildir_copy_with_hardlinks = yes 

В блоке auth default { должно быть выбрано

  mechanisms = plain 

и раскомментарено

  passdb pam { 
…
  } 

закомментарим

#  userdb passwd { 
…
#  } 

и раскомментарим

  # SQL database </usr/share/doc/dovecot-common/wiki/AuthDatabase.SQL.txt> 
  userdb sql { 
    # Path for SQL configuration file 
    args = /etc/dovecot/dovecot-sql.conf 
  } 
 # LDAP database </usr/share/doc/dovecot-common/wiki/AuthDatabase.LDAP.txt> 
  userdb ldap { 
    # Path for LDAP configuration file 
    args = /etc/dovecot/dovecot-ldap.conf 
  } 

В блоке protocol imap { добавляем строку

  mail_plugins = quota imap_quota 

В блоке protocol pop3 { добавляем строку

  mail_plugins = quota 

В блоке plugin { добавляем строки

 quota = maildir:Quota 
 quota_rule = *:storage=0 
 quota_rule2 = Trash:ignore 
 quota_warning = storage=95%% /usr/local/bin/quota-warning.sh 95 
 quota_warning2 = storage=80%% /usr/local/bin/quota-warning.sh 80 

Создадим скрипт, который будет посылать пользователям напоминания при превышении ими квоты

sudo nano /usr/local/bin/quota-warning.sh
#!/bin/sh 
PERCENT=$1 
FROM="admin@aaa.ru" 
qwf="/tmp/quota.warning.$$" 
echo "From: $FROM 
To: $USER 
To: admin@aaa.ru 
Subject: Your email quota is $PERCENT% full 
Content-Type: text/plain; charset="UTF-8" 
Your mailbox is now $PERCENT% full." >> $qwf 
cat $qwf | /usr/sbin/sendmail -f $FROM "$USER" 
rm -f $qwf 
exit 0 

(здесь мы посылаем сообщение от имени admin@aaa.ru, причем копию сообщения направляем на admin@aaa.ru)

и сделаем его выполняемым

sudo chmod a+x /usr/local/bin/quota-warning.sh

Не ждите от dovecot немедленной реакции на достижение квоты пороговых значений: dovecot пересчитывает квоту периодически.

Сменим права файлам, в которых записана информация о доступе к mysql и ldap

sudo chown root.root /etc/dovecot/dovecot-ldap.conf 
sudo chown root.root /etc/dovecot/dovecot-sql.conf 
sudo chmod og= /etc/dovecot/dovecot-ldap.conf 
sudo chmod og= /etc/dovecot/dovecot-sql.conf

Перезапустим dovecot

sudo /etc/init.d/dovecot restart

и проверим аутентификацию dovecot для обоих наших пользователей:

telnet 127.0.0.1 110
Trying 127.0.0.1…
Connected to 127.0.0.1.
Escape character is '^]'.
+OK Dovecot ready.
user admin@bbb.ru
+OK
pass secret

(здесь, конечно, вводим пароль пользователя admin@bbb.ru)

+OK Logged in.
quit
+OK Logging out.
Connection closed by foreign host.

и

telnet 127.0.0.1 110 
Trying 127.0.0.1…
Connected to 127.0.0.1.
Escape character is '^]'.
+OK Dovecot ready.
user admin
+OK
pass secret

(здесь, конечно, вводим пароль пользователя admin)

+OK Logged in.
quit
+OK Logging out.
Connection closed by foreign host.

После этой проверки, кстати, автоматически создадутся папки для хранения почты:

sudo ls -R /home/vmail/
/home/vmail:
aaa.ru bbb.ru
/home/vmail/aaa.ru:
admin
/home/vmail/aaa.ru/admin:
cur dovecot.index.log dovecot-uidlist dovecot-uidvalidity dovecot-uidvalidity.4cf4e5c7 new tmp
/home/vmail/aaa.ru/admin/cur:
/home/vmail/aaa.ru/admin/new:
/home/vmail/aaa.ru/admin/tmp:
/home/vmail/bbb.ru:
admin
/home/vmail/bbb.ru/admin:
cur dovecot.index.log dovecot-uidlist dovecot-uidvalidity dovecot-uidvalidity.4cf4e5ba new tmp
/home/vmail/bbb.ru/admin/cur:
/home/vmail/bbb.ru/admin/new:
/home/vmail/bbb.ru/admin/tmp:

Установка horde

Установим нужные пакеты

sudo apt-get install horde3 imp4 turba2 sork-passwd-h3

Разрешим доступ к нашей web-почте только через ssl. Для этого вначале отредактируем файл /etc/apache2/sites-available/default-ssl

sudo nano /etc/apache2/sites-available/default-ssl 

Заменяем

DocumentRoot /var/www 
<Directory /> 
	Options FollowSymLinks 
	AllowOverride None 
</Directory> 
<Directory /var/www/> 
	Options Indexes FollowSymLinks MultiViews 
	AllowOverride None 
	Order allow,deny 
	allow from all 
</Directory> 
ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ 
<Directory "/usr/lib/cgi-bin"> 
	AllowOverride None 
	Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch 
	Order allow,deny 
	Allow from all 
</Directory> 

на

DocumentRoot /usr/share/horde3 
<Directory /> 
	Options FollowSymLinks 
	AllowOverride Limit 
</Directory> 
<Directory /usr/share/horde3> 
	Options Indexes FollowSymLinks MultiViews 
	AllowOverride Limit 
	Order allow,deny 
	allow from all 
</Directory> 
Alias /horde3 /usr/share/horde3 

и

SSLCertificateFile	/etc/ssl/certs/ssl-cert-snakeoil.pem 
SSLCertificateKeyFile	/etc/ssl/private/ssl-cert-snakeoil.key 

на

SSLCertificateFile	/etc/ssl/certs/oban.crt 
SSLCertificateKeyFile	/etc/ssl/private/oban.key

Подключаем модуль ssl, разрешаем сайт default-ssl и перезапускаем apache

sudo a2enmod ssl 
sudo a2ensite default-ssl 
sudo /etc/init.d/apache2 restart

Редактируем файл /etc/horde/horde3/conf.php и закомментарим строки

sudo nano /etc/horde/horde3/conf.php
//echo "Horde3 configuration disabled by default because the administration/install wizard gives the whole world too much access to the system. Read /usr/share/doc/horde3/README.Debian.gz on how to allow access."; 
//exit (0);

Распаковываем скрипт для создания базы данных horde

sudo gzip -d /usr/share/doc/horde3/examples/scripts/sql/create.mysql.sql.gz 

и меняем пароль в файле /usr/share/doc/horde3/examples/scripts/sql/create.mysql.sql

sudo nano /usr/share/doc/horde3/examples/scripts/sql/create.mysql.sql 
-- IMPORTANT: Change this password. 
	PASSWORD('horde') 

на другой (далее будем называть его <horde_password>)

Исполняем скрипт с параметрами администратора mysql

sudo mysql --user=root --password=<mysql_password> < /usr/share/doc/horde3/examples/scripts/sql/create.mysql.sql

и заходим браузером на https://oban.aaa.ru

В первый раз нас пустят с администраторскими привилегиями. Настроим систему аутентификации. Вначале войдем в Управление - Приложения - Портал (horde) и выберем закладку Authentication.

Зададим список пользователей-администраторов, перечислив двух наших пользователей — admin@bbb.ru, admin (через запятую) в поле «Which users should be treated as administrators (root, super-user) by Horde?».

Затем выберем способ аутентификации - через IMAP (выберем IMAP authentication в поле «What backend should we use for authenticating users to Horde?»). Страница обновится, дав нам настройки для выбранного нами способа аутентификации.

В поле «Configuration type» выберем Separate values.

В поле «The hostname or IP address of the server» оставим localhost

В поле «The server port to which we will connect. IMAP is generally 143, while IMAP-SSL is generally 993.» выберем 143 (в данном случае мы используем локальный IMAP-сервер, так что это нам подходит)

В поле «The connection protocol» выберем imap/notls

Переходим во вкладку Database и конфигурируем доступ к созданной ранее базе данных.

В поле «What database backend should we use?» выбираем MySQL

В поле «Username to connect to the database as» вводим horde

В поле «Password to connect with» вводим пароль, который мы вводили в скрипте создания базы данных- <horde_password>

В поле «How should we connect to the database?» выбираем TCP/IP

В поле «Database server/host» вводим localhost

В поле «Port the DB is running on, if non-standard» оставляем 3306

В поле «Database name to use» вводим horde

В поле «Internally used charset» оставляем utf-8

Переходим в закладку Mailer и настраиваем доступ к SMTP-серверу:

В поле «What method should we use for sending mail?» выбираем Use a SMTP server

В поле «SMTP authentication» выбираем PLAIN

Переходим в закладку Preference System и выбираем SQL Database в поле «What preferences driver should we use?».

Переходим в закладку Datatree System и выбираем SQL Database в поле «What backend should we use for Horde DataTree storage?».

Переходим в закладку Cache System и выбираем Store objects in filesystem в поле «If you want to enable the Horde Cache, select a driver here. This is used to speed up portions of Horde by storing commonly processed objects.». В поле «The location to store the cached files» выбираем /tmp

Переходим в закладку Virtual File Storage и выбираем Files on the local system в закладке «What VFS driver should we use?»

Переходим в закладку Custom Session Handler и настраиваем доступ к созданной ранее базе данных:

В поле «What sessionhandler driver should we use?» выбираем MySQL based sessions

В поле «What protocol will we use to connect to the database?» выбираем TCP/IP

В поле «What password do we authenticate to the database server with?» вводим пароль, который мы вводили в скрипте создания базы данных - <horde_password>

Остальные поля оставляем по умолчанию.

Теперь нажмем кнопку Generate Портал Configuration. Получим сообщение «Невозможно сохранить резервную копию конфигурации в файл /usr/share/horde3/config/conf.bak.php.
Невозможно сохранить файл конфигурации /usr/share/horde3/config/conf.php. Вы можете использовать опции для сохранения кода обратно в Приложения или скопировать вручную код, приведенный ниже в /usr/share/horde3/config/conf.php.»

Это нормально, т.к. мы не хотим, чтобы возможно было менять конфигурацию через веб. Скопируем показанный внизу на странице сгенеренный код и запишем его в файл /etc/horde/horde3/conf.php (важно скопировать ВЕСЬ текст, заменив ВЕСЬ текст, имеющийся в файле).

sudo nano /etc/horde/horde3/conf.php

(здесь вставляем скопированный текст со страницы броузера)

Закроем окно броузера.

Отредактируем файл /etc/horde/imp4/servers.php

sudo nano /etc/horde/imp4/servers.php
$servers['imap'] = array( 
    'name' => 'IMAP Server', 
    'server' => '127.0.0.1', 
    'hordeauth' => full, 
    'protocol' => 'imap/notls', 
    'port' => 143, 
    'maildomain' => '', 
    'smtphost' => '127.0.0.1', 
    'smtpport' => 25, 
    'realm' => '', 
    'preferred' => '', 
    'quota' => array('driver'=>imap),
);

и закомментируем все остальные примеры ниже.

Войдем заново на https://oban.aaa.ru уже под одним из введенных нами пользователей с правами администратора.

Зайдем в Управление - Приложения - Почта (imp) и выберем закладку Server.

В поле «Should we display a list of servers (defined in config/servers.php) for users to choose from? The options are 'shown', 'hidden', and 'none'. If the server list is hidden then you can use the 'preferred' mechanism to auto-select from it based on an HTTP virtualhost or another piece of data. If it is shown, the user will be able to pick from any of the options. If none, no server list will be shown and the defaults will be used unless another mechanism changes them.» выберем Hidden.

Нажмем кнопку Generate Почта Configuration, скопируем сгенеренный текст конфигурации и запишем в файл /etc/horde/imp4/conf.php

sudo nano /etc/horde/imp4/conf.php

(здесь вставляем скопированный текст со страницы броузера)

Отредактируем файл /etc/horde/passwd3/backends.php

sudo nano /etc/horde/passwd3/backends.php

Закомментируем все кроме

$backends['sql'] = array ( 
    'name' => 'Exampe SQL Server', 
    'preferred' => '', 
    'password policy' => array( 
        'minLength' => 3, 
        'maxLength' => 8, 
        'maxSpace' => 0, 
        'minUpper' => 0, 
        'minLower' => 0, 
        'minNumeric' => 0, 
        'minSymbols' => 0 
    ), 
    'driver' => 'sql', 
    'params' => array( 
        'phptype'    => 'mysql', 
        'hostspec'   => 'localhost', 
        'username'   => 'mail_admin', 
        'password'   => '<mail_admin_password>', 
        'encryption' => 'crypt', 
        'database'   => 'mail', 
        'table'      => 'users', 
        'user_col'   => 'email', 
        'pass_col'   => 'password', 
        'show_encryption' => false 
        // The following two settings allow you to specify custom queries for 
        // lookup and modify functions if special functions need to be 
        // performed.  In places where a username or a password needs to be 
        // used, refer to this placeholder reference: 
        //    %d -> gets substituted with the domain 
        //    %u -> gets substituted with the user 
        //    %U -> gets substituted with the user without a domain part 
        //    %p -> gets substituted with the plaintext password 
        //    %e -> gets substituted with the encrypted password 
        // 
        // 'query_lookup' => 'SELECT user_pass FROM horde_users WHERE user_uid = %u', 
        // 'query_modify' => 'UPDATE horde_users SET user_pass = %e WHERE user_uid = %u', 
    ) 
); 

(здесь вместо <mail_admin_password> вставьте пароль mysql пользователя mail_admin, а также можете изменить политику паролей — какие пароли допустимы:

'minLength' — минимальная длина паролей

'maxLength' — максимальная длина паролей

'minUpper' — минимальное количество заглавных букв в паролях

'minLower' — минимальное количество прописных букв в паролях

'minNumeric' — минимальное количество цифровых символов в паролях

'minSymbols' — минимальное количество прочих символов букв в паролях)

Зайдем в Управление - Приложения - Пароль (passwd) и в поле «Should we display a list of backends (defined in config/backends.php) for users to choose from? The options are 'shown', 'hidden'. If the backend list is hidden then you can use the 'preferred' mechanism to auto-select from it based on an HTTP virtualhost or another piece of data. If it is shown, the user will be able to pick from any of the options.» выберем Hidden.

Нажмем кнопку Generate Пароль Configuration, скопируем сгенеренный текст конфигурации и запишем в файл /etc/horde/passwd3/conf.php

sudo nano /etc/horde/passwd3/conf.php 

(здесь вставляем скопированный текст со страницы броузера)

Отредактируем файл /etc/horde/horde3/registry.php

sudo nano /etc/horde/horde3/registry.php

и для приложений imp, passwd и turba заменим

    'status' => 'inactive', 

на

    'status' => 'active', 

Учтите, что для пользователей, которые входят с использованием ldap мы не настроили смену пароля (это лучше делать другими средствами - внутри нашей сети, а не давать им менять пароль через веб). Кроме этого, этим пользователям (т.к. они входят, вводя только имя, а не имя@домен), нужно задавать свой почтовый адрес в поле «Ваш адрес отправителя:« в Настройки - Почта — Личные.

Для настройки адресной книги выполним

sudo mysql -u horde -p horde < /usr/share/doc/turba2/examples/scripts/sql/turba.sql
Enter password:

Введем пароль пользователя horde

<horde_password>

Зайдем в Управление - Приложения — Адресная книга (turba) и нажмем кнопку Create Адресная книга Configuration, скопируем сгенеренный текст конфигурации и запишем в файл /etc/horde/turba2/conf.php

sudo nano /etc/horde/turba2/conf.php 

(здесь вставляем скопированный текст со страницы броузера)

Основное мы уже установили и настроили. Теперь установим ряд дополнений.

Установка amavisd-new, SpamAssassin и ClamAV

Для установки amavisd-new, spamassassin и clamav, выполним следующую команду:

sudo apt-get install amavisd-new spamassassin clamav clamav-daemon zoo unzip bzip2 libnet-ph-perl libnet-snpp-perl libnet-telnet-perl nomarch lzop pax 

Включим ClamAV и SpamAssassin отредактировав /etc/amavis/conf.d/15-content_filter_mode

sudo nano /etc/amavis/conf.d/15-content_filter_mode

Раскомментируем

@bypass_virus_checks_maps = ( 
   \%bypass_virus_checks, \@bypass_virus_checks_acl, \$bypass_virus_checks_re); 

и

@bypass_spam_checks_maps = ( 
   \%bypass_spam_checks, \@bypass_spam_checks_acl, \$bypass_spam_checks_re); 

Отредактируем /etc/amavis/conf.d/50-user

sudo nano /etc/amavis/conf.d/50-user 

Добавим посередке параметр

$pax='pax'; 

и

@lookup_sql_dsn = (['DBI:mysql:mail; host=127.0.0.1; port=3306', 'mail_admin' ,'<mail_admin_password>']); 
$sql_select_policy = 'SELECT "Y" as local FROM domains WHERE CONCAT("@",domain) IN (%k)';

(нам нужно указать amavis, что виртуальные домены являются локальными. Иначе он не будет вставлять заголовки и проверять на спам, т.к. будет считать, что письма, посланные на виртуальные домены — исходящие) .

Не забудьте подставить свой пароль пользователя mysql mail_admin.

Откроем файл /etc/amavis/conf.d/20-debian_defaults

sudo nano /etc/amavis/conf.d/20-debian_defaults

Дальше — список параметров, которые используются у меня с кратким пояснением, зачем и почему.

$enable_db = 1;              # enable use of BerkeleyDB/libdb (SNMP and nanny) 
$enable_global_cache = 1;    # enable use of libdb-based cache if $enable_db=1 
$spam_check_negative_ttl = 30*60; 
$spam_check_positive_ttl = 30*60; 

Это позволяет держать небольшой кеш уже проверенных писем и не проверять точно такие же письма заново, если они уже есть в кеше. Проверка идет по сигнатуре, которую amavis генерирует сам. Полезно, когда, в частности, один и тот же спам валится на несколько адресов. ttl-параметры - это срок жизни сигнатуры в кеше (с положительным и отрицательным результатом проверки на спам). В моем случае, процент попадания в кеш порядка 8.

$sa_spam_subject_tag = '***SPAM*** '; 
$sa_spam_report_header = 1; 
$sa_tag_level_deflt  = undef;  # add spam info headers if at, or above that level 
$sa_tag2_level_deflt = 4.5;  # add 'spam detected' headers at that level 
$sa_kill_level_deflt = 6.9;  # triggers spam evasive actions 
$sa_dsn_cutoff_level = 6.9;    # spam level beyond which a DSN is not sent 
$sa_quarantine_cutoff_level = 11.9; 

$sa_spam_subject_tag - добавление указанной строки в начало темы письма. Удобно для получателей.

$sa_spam_report_header - добавление заголовков с подробным отчетом о результатах проверки. Учтите только, что эти заголовки будут вставлены только в письма, признанные спамом. Подробный отчет - это объяснения каждого сработавшего правила spamassassin, типа вот такого:

X-Spam-Report:
* 1.4 MSGID_MULTIPLE_AT Message-ID contains multiple '@' characters
* 0.0 HTML_MESSAGE BODY: HTML included in message
* 0.0 BAYES_50 BODY: Bayesian spam probability is 40 to 60%
* [score: 0.5000]
* 1.8 MIME_BASE64_TEXT RAW: Message text disguised using base64 encoding
* 1.3 AWL AWL: From: address is in the auto white-list

$sa_tag_level_deflt - добавлять заголовки с результатом проверки на спам в сообщения с указанной оценкой и выше. Здесь - всегда добавлять эти заголовки. Заголовки вот такие:

Х-Spam-Flag: YES
X-Spam-Score: 4.54
X-Spam-Level:

X-Spam-Status: Yes, score=4.54 required=4.5 tests=[AWL=1.336, BAYES_50=0.001,
HTML_MESSAGE=0.001, MIME_BASE64_TEXT=1.753, MSGID_MULTIPLE_AT=1.449]

если опознан спам и

X-Spam-Flag: NO
X-Spam-Score: -0.708
X-Spam-Level:
X-Spam-Status: No, score=-0.708 required=4.5 tests=[AWL=0.403, BAYES_05=-1.11,
SPF_PASS=-0.001]

если сообщение «чистое». Еще раз подчеркну, что в этом случае заголовка «X-Spam-Report:» не будет. Если нужен - нужно править сам amavis.

$sa_tag2_level_deflt - уровень, начиная с которого сообщение признается спамом.

$sa_kill_level_deflt - уровень, начиная с которого выполняется правило по обработке спама (описание ниже)

$sa_dsn_cutoff_level - уровень, начиная с которого не отсылается сообщение о невозможности доставки. Очень советую его иметь равным $sa_kill_level_deflt, иначе Вас, скорее всего, внесут в какой-нибудь черный список. Особенно это любит делать, например, att.com

$sa_quarantine_cutoff_level - уровень начиная с которого письмо не сохраняется в карантине (/var/lib/amavis/virusmails/…)

$final_virus_destiny      = D_DISCARD;  # (data not lost, see virus quarantine) 
$final_banned_destiny     = D_BOUNCE;   # D_REJECT when front-end MTA 
$final_spam_destiny       = D_DISCARD; 
$final_bad_header_destiny = D_PASS;     # False-positive prone (for spam) 
$virus_admin = "admin\@aaa.ru"; # due to D_DISCARD default 

Параметры задают правила обработки «нехороших» сообщений:

$final_virus_destiny - просто игнорировать (сами письма сохраняются в карантине)

$final_banned_destiny - письма с запрещенными вложениями по типу файлов: сообщить отправителю о невозможности доставки

$final_spam_destiny - игнорировать (это и есть то самое правило обработки спама, о котором шла речи выше)

$final_bad_header_destiny - некорректные заголовки писем пропускать.

$virus_admin - адрес, куда доставлять сообщения о вирусах и отраженных письмах с запрещенными вложениями (postmaster), чтобы можно было проверить содержимое писем в карантине. О письмах со спамом, попавшим в карантин, сообщений не будет.

Настройки, связанные с вирусами и запрещенными вложениями лучше оставить «по умолчанию».

Конкретные уровни оценки выставьте сами (я использую именно такие, и они меня полностью удовлетворяют, по крайней мере, по прошествии довольно большого времени и накопленной bayes-овской базы и с регулярными обновлениями правил spamassassin). Недавно специально проверял - за три дня ложных срабатываний было 0 (менял D_DISCARD на D_PASS у $final_spam_destiny).

Сделаем так, чтобы amavis не проверял исходящие письма на спам, а только входящие (на вирусы он будет проверять все письма). Вставим строки

# add these for SASL SA bypass 
$policy_bank{'MYUSERS'} = {  # mail from submission and smtps ports 
    originating => 1,   # Since amavisd-new 2.5.0 
         # declare that mail was submitted by our smtp client 
    bypass_spam_checks_maps   => [1],  # don't spam-check this mail 
    bypass_banned_checks_maps => [1],  # don't banned-check this mail 
    bypass_header_checks_maps => [1],  # don't header-check this mail  
}; 

Отредактируйте файл /etc/amavis/conf.d/21-ubuntu_defaults

sudo nano /etc/amavis/conf.d/21-ubuntu_defaults

и закомментируйте строки

#$final_virus_destiny      = D_DISCARD; # (defaults to D_BOUNCE) 
#$final_banned_destiny     = D_DISCARD;  # (defaults to D_BOUNCE) 
#$final_spam_destiny       = D_DISCARD;  # (defaults to D_REJECT) 
#$final_bad_header_destiny = D_PASS;  # (defaults to D_PASS), D_BOUNCE suggested 
#$virus_admin = undef; 
#$spam_admin = undef;

Добавим пользователя clamav в группу amavis и перезапустим amavisd-new и ClamAV:

sudo adduser clamav amavis 
sudo /etc/init.d/amavis restart 
sudo /etc/init.d/clamav-daemon restart 
sudo /etc/init.d/clamav-freshclam restart

Укажем, чтобы postfix проверял почту через amavis

sudo postconf -e 'content_filter = amavis:[127.0.0.1]:10024' 
sudo postconf -e 'receive_override_options = no_address_mappings' 

Отредактируем файл /etc/postfix/master.cf

sudo nano /etc/postfix/master.cf

и добавим в конец следующие строки

amavis unix - - - - 2 smtp 
        -o smtp_data_done_timeout=1200 
        -o smtp_send_xforward_command=yes 
127.0.0.1:10025 inet n - - - - smtpd 
        -o content_filter= 
        -o local_recipient_maps= 
        -o relay_recipient_maps= 
        -o smtpd_restriction_classes= 
        -o smtpd_client_restrictions= 
        -o smtpd_helo_restrictions= 
        -o smtpd_sender_restrictions= 
        -o smtpd_recipient_restrictions=permit_mynetworks,reject 
        -o mynetworks=127.0.0.0/8 
        -o strict_rfc821_envelopes=yes 
        -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks 
        -o smtpd_bind_address=127.0.0.1

Перезапустим postfix

sudo /etc/init.d/postfix restart

Для периодической очистки карантина вставим в cron

sudo crontab -e
1 0 * * * find /var/lib/amavis/virusmails/* -mtime +60 | xargs rm &> /dev/null

(ежедневная очистка всех файлов из карантина, старше 60 дней)

Установка Razor, Pyzor And DCC и настройка SpamAssassin

Установим нужные пакеты

sudo apt-get install razor pyzor

DCC не содержится в репозитории Ubuntu, поэтому мы его установим так:

cd /tmp 
wget http://launchpadlibrarian.net/11564361/dcc-server_1.3.42-5_i386.deb 
wget http://launchpadlibrarian.net/11564359/dcc-common_1.3.42-5_i386.deb 
sudo dpkg -i dcc-common_1.3.42-5_i386.deb 
sudo dpkg -i dcc-server_1.3.42-5_i386.deb
cd ~

(Для работы клиента DCC нам нужно пробросить на шлюзе UDP порт 6277 на наш почтовый сервер)

Скажем spamassassin использовать эти три программы. Отредактируем /etc/spamassassin/local.cf добавив несколько строчек в конец:

sudo nano /etc/spamassassin/local.cf
#dcc 
use_dcc 1 
dcc_path /usr/bin/dccproc 
#pyzor 
use_pyzor 1 
pyzor_path /usr/bin/pyzor 
#razor 
use_razor2 1 
razor_config /etc/razor/razor-agent.conf 
#bayes 
use_bayes 1 
use_bayes_rules 1 
bayes_auto_learn 1

Мы должны включить плагин DCC в SpamAssassin. Откроем /etc/spamassassin/v310.pre

sudo nano /etc/spamassassin/v310.pre

и раскомментируем такую строчку

loadplugin Mail::SpamAssassin::Plugin::DCC 

Проверим конфигурацию spamassassin

spamassassin --lint 

Мы не должны получить никакого сообщения, если у нас все в норме.

Обновим набор правил для SpamAssassin следующим образом:

sudo sa-update --no-gpg

Добавим в cron задание, чтобы набор правил обновлялся регулярно. Запустим

sudo crontab -e

чтобы открыть редактор cron и добавим в него следующее задание:

 23 4 */2 * * /usr/bin/sa-update --no-gpg &> /dev/null 

Это позволит обновлять набор правил каждый 2 день в 4 часа 23 минуты.

Для работы razor нам нужно сформировать для него домашнюю директорию и зарегистрироваться, для того, чтобы иметь возможность сообщать о спаме. Т. к. spamassassin работает от имени пользователя amavis, сделаем следующее (по умолчанию домашней директорией пользователя amavis является /var/lib/amavis):

sudo su amavis
cd
razor-admin -create
razor-admin -register
Register successful. Identity stored in /var/lib/amavis/.razor/identity-XXXXXXXXXX
exit

После этого у нас появится директория /var/lib/amavis/.razor/ в которой будет лежать информация о серверах razor и о имени/пароле для доступа к ним (сгенеренные автоматически).

Зададим правила отчета о спаме в horde. Зайдем броузером на https://oban.aaa.ru под административной записью и зайдя в Управление - Приложения - Почта (imp) и выберем закладку Message and Spam.

В поле «Should we report the spam message via an external program (e.g. /usr/local/bin/spamassassin -r)? If you include the placeholder %u in this string, it will be replaced with the current username. If you include the placeholder %l in this string, it will be replaced with the current short username. If you include the placeholder %d in this string, it will be replaced with the current domain name.» введем /usr/local/bin/spamassassin -r а в поле «Should we report the innocent message via an external program (e.g. /usr/local/bin/spamassassin -k)? If you include the placeholder %u in this string, it will be replaced with the current username» введем /usr/local/bin/spamassassin -k

При приеме писем postfix проверки на спам проводятся amavisd-new пользователем amavis, а при работе horde — пользователем www-data. Поэтому создаем ссылки

sudo ln -s /var/lib/amavis/.pyzor /var/www/.pyzor 
sudo ln -s /var/lib/amavis/.razor /var/www/.razor 
sudo ln -s  /var/lib/amavis/.spamassassin /var/www/.spamassassin

и меняем права

sudo chmod -R g+rw /var/lib/amavis/.pyzor
sudo chmod -R g+rw /var/lib/amavis/.razor
sudo chmod -R g+rw /var/lib/amavis/.spamassassin
sudo chmod g+x /var/lib/amavis/.pyzor
sudo chmod g+x /var/lib/amavis/.razor
sudo chmod g+x /var/lib/amavis/.spamassassin

и вставляем пользователя www-data в группу amavis (чтобы разрешить доступ к bayes-базе при работе в horde)

sudo adduser www-data amavis

и перезапустим apache

sudo /etc/init.d/apache2 restart

Отредактируем файл

sudo nano /etc/mail/spamassassin/local.cf

и вставим туда строку

bayes_path /var/lib/amavis/.spamassassin/bayes

Вставим периодическое обновление нашей bayes-базы (будем делать это каждую ночь). Отредактируем файл

sudo nano /etc/cron.d/amavisd-new

и вставим строку

1 0 	* * *	amavis	sa-learn --force-expire --sync 

Таким образом, при приеме писем postfix при помощи amavis будет проверять письма в том числе по bayes-базе, которая формируется автоматически (обучение идет из писем с достаточно большим значением уровня спама; письма с очень малым показателем также участвуют в обучении — они помечаются, как ham, т. е. не-спам; письма со средним значением уровня спама в автоматическом обучении не участвуют). Кроме этого, в пополнении базы данных принимают участие и пользователи, когда помечают письма, как спам или не-спам (innocent). Чем больше писем участвует в обучении (и как спам, и как ham), тем лучше работает bayes-база данных спама.

Дополнительные правила для защиты от спама

Множество спам-хостов находится на компьютерах-зомби. К счастью, в большинстве случаев эти компьютеры не настроены должным образом, как мейл-серверы, что дает возможность их с легкостью отсекать еще на начальном этапе получения писем.

Дело в том, что процесс передачи/получения письма начинается с представления серверов друг другу. После соединения по порту 25 (SMTP), компьютер - инициатор выдает команду

HELO hostname.domain.ltd

Принимающий сервер уже на этом этапе может отсечь кучу спама. Как это сделать? Очень просто. Нормально сконфигурированный сервер обязан в сообщении HELO передать свое истинное имя; причем это имя должно соответствовать IP-адресу, с которого осуществляется соединение; а IP-адрес должен резолвится в обратной зоне в это имя. Если это не так - Вы имеете полное право отсечь соединение прямо на этом этапе. Плюсы: не тратится лишний трафик на прием сообщений, которые заведомо являются спамом; экономятся вычислительные затраты на дальнейшие проверки писем на спам и вирусы. Необходим какой-то трафик для проведения проверок по DNS (но он заведомо меньше трафика на прием писем). Отсечь «нормальное» письмо бояться не стоит: неверная настройка почтового сервера, который Вы отсекли - не Ваша головная боль.

Для того, чтобы такие проверки проводились на этапе приемки писем, в файл /etc/postfix/main.cf нужно вставить следующие строки:

sudo nano /etc/postfix/main.cf
strict_rfc821_envelopes = yes 
disable_vrfy_command = yes 
smtpd_delay_reject = yes 
smtpd_helo_required = yes 
smtpd_client_restrictions = sleep 1, reject_unauth_pipelining, permit_sasl_authenticated, permit_mynetworks,  reject_unknown_client_hostname, permit 
smtpd_helo_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_helo_hostname, permit 
smtpd_sender_restrictions =  permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_sender, reject_unknown_sender_domain, permit 
smtpd_recipient_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unlisted_recipient, check_policy_service inet:127.0.0.1:10023, permit

(отредактируйте существующие и добавьте отсутствующие) Если Вы не собираетесь использовать postgrey (я рекомендую его использовать), не вставляйте check_policy_service inet:127.0.0.1:10023,

Подробнее об этих параметрах:

strict_rfc821_envelopes - запрет на использование заголовков в стиле RFC822. Если кратко - этот стандарт не имеет отношения к SMTP, а к формату самих писем, и не должен использоваться на этапе общения между серверами. Даже их названия об этом говорят: 821: SIMPLE MAIL TRANSFER PROTOCOL, 822: STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES.

disable_vrfy_command - запрет на использование команды VRFY. Эта команда зачастую используется для сбора с сервера существующих адресов почты для дальнейшей рассылки на них спама.

smtpd_delay_reject - подождать команды RCPT TO перед (возможным) отказом от приема письма. Нужно из-за того, что некоторые сервера неверно реагируют на отказ от приема писем до посылки ими команды RCPT TO

smtpd_helo_required - требовать от сервера обязательно представиться командой HELO. Это дает возможность проведения нужных нам проверок.

Проверки на каждом этапе происходят последовательно, в указанном в каждой команде порядке. Если условие выполнено, дальнейшие проверки не производятся.

smtpd_client_restrictions - ограничения на этапе установления связи по протоколу SMTP

sleep 1 - пауза на 1 секунду для отсекания совсем нетерпеливых спам-хостов (обычно они просто сразу же рвут соединение - им нужно разослать как можно больше спама, поэтому ждать им - как серпом…)

reject_unauth_pipelining - отсечь хосты, которые пытаются слать команды в конвейере, даже не проверив, поддерживает ли это наш сервер. Это часто делают именно спам-хосты для увеличения скорости передачи

permit_sasl_authenticated - разрешить для авторизованных клиентов (пользователи)

permit_mynetworks - разрешить при соединении из моей подсети

reject_unknown_client_hostname -отказать в случае, если 1) не удалось сопоставить IP адрес имени хоста (по PTR-записи в DNS), 2) не удалось сопоставить имя IP-адресу хоста (прямой запрос DNS), 3) адрес, полученный из DNS по имени не совпадает с IP-адресом , с которого идет соединение. Отсекается ОЧЕНЬ много спам-хостов (в моем случае - процентов 80)

permit - в другом случае продолжать прием

smtpd_helo_restrictions - ограничения на этапе команды HELO:

permit_mynetworks - разрешить при соединении из моей подсети

permit_sasl_authenticated - разрешить при аутентификации (пользователи)

reject_invalid_helo_hostname - отказать при нарушении синтаксиса имени хоста в HELO (случается очень редко, тем не менее…)

reject_non_fqdn_helo_hostname - отказать, если имя хоста в HELO не в полной форме (не должно быть просто server, а должно быть типа server.domain.ltd)

reject_unknown_helo_hostname - отказать, если у хоста, указанного в команде HELO, в DNS нет записи типа MX или A (множество зобми-хостов)

permit - в другом случае продолжать прием

smtpd_sender_restrictions - проверки по команде MAIL FROM (имя отправителя)

permit_mynetworks - разрешить при соединении из моей подсети

permit_sasl_authenticated - разрешить при аутентификации (пользователи)

reject_non_fqdn_sender - отказать, если имя отправителя не в полной форме (должно быть не name или name@domain а name@domain.ltd)

reject_unknown_sender_domain - отказать, если наш сервер не является «родным» для отправителя и домен отправителя не имеет в DNS записи MX или A, или если он имеет неверную запись MX (например, пустую)

permit - в другом случае продолжать прием

smtpd_recipient_restrictions - проверки по команде RCPT TO (имя получателя)

reject_unauth_pipelining - отсечь хосты, которые пытаются слать команды в конвейере

permit_sasl_authenticated - разрешить для авторизованных клиентов (пользователи)

permit_mynetworks - разрешить при соединении из моей подсети

reject_unauth_destination - отказать, если домен-адресат: 1) не перечислен в списке доменов, для которых мы форвардим почту ($relay_domains) и не содержит команд переадресации (типа user@another@domain); 2) не является «нашим» (т.е. не перечислен в списках $mydestination, $inet_interfaces, $proxy_interfaces, $virtual_alias_domains, или $virtual_mailbox_domains ) и не содержит команд переадресации (типа user@another@domain). Это - традиционные правила, чтобы наш сервер не служил т.н. Open Relay, через который спамеры рассылают почту третьим лицам.

reject_non_fqdn_recipient - отказать, если имя получателя не в полной форме (должно быть не name или name@domain а name@domain.ltd)

reject_unknown_recipient_domain - отказать, если мы не являемся «родным» сервером для получателя и домен получателя не имеет в DNS записи MX или A, или если он имеет неверную запись MX (например, пустую)

reject_unlisted_recipient - отказать, если адрес получателя не указан в списках получателей домена (не перечислен в $local_recipient_maps или $virtual_alias_maps или $virtual_mailbox_maps или $relay_recipient_maps, в зависимости от того, в какой именно таблице найден домен получателя)

check_policy_service inet:127.0.0.1:10023 - проверка у стороннего сервиса (в данном случае - postgrey, о нем чуть позже)

permit - продолжать прием

Такая конфигурация позволяет отсечь просто КУЧУ спама.

Установка postgrey

Теперь о postgrey. Это еще один способ борьбы со спамом. Многим он не нравится, мне - так очень. Работает он так. При приходе письма (конечно, если оно «прорвалось» через все проверки, указанные выше) сервер запоминает три параметра (так называемый триплет): от кого оно послано, кому послано, и кем (т.е. адрес сервера, который его посылает), и сообщает передающему серверу «Я сейчас занят, повторите чуть позже», после чего начинает отсчет времени для конкретного триплета. Большинство спамеров, как мы знаем, ждать не любят, и либо пытаются повторить посылку сразу же (на что получают тот же ответ), либо просто прекращают свои попытки. Наш же сервер, если обнаружит повторное письмо с тем же триплетом по прошествии некоторого времени (по умолчанию - 5 минут), спокойно его примет. При этом этот триплет будет запомнен на какое-то время (35 дней), так что следующие письма с тем же триплетом пройдут без задержки. Если это Ваш постоянный корреспондент, то задержек больше не будет вообще.

Установить postgrey очень просто:

sudo apt-get install postgrey

(в main.cf обращение к postgrey у нас уже добавлено)

Если Вы хотите изменить параметры postgrey, то это делается в файле /etc/default/postgrey.

sudo nano /etc/default/postgrey

Отредактируйте строку

POSTGREY_OPTS="--inet=127.0.0.1:10023" 

добавив параметры –delay=N (в секундах; по умолчанию 300) и –max-age=N (в сутках; по умолчанию 35):

POSTGREY_OPTS="--inet=127.0.0.1:10023 --delay=300 --max-age=35" 

Стоит заглянуть еще в два файла: это /etc/postgrey/whitelist_clients и /etc/postgrey/whitelist_recipients. В первом перечислены домены-отправители, письма от которых принимаются автоматом (по разным причинам), во втором - адреса-получатели, письма на которые также принимаются сразу всегда (например, abuse@). Можете при необходимости дописать в эти файлы свои строки.

Установка spf, , sender id, dkim, domainkey

И еще немного о борьбе со спамом.

Зачастую спамеры пытаются «замаскироваться» под обычных отправителей, т.е. подделывают обратный адрес (mail from:).

Есть способ а) обезопасить себя от таких «подделок» и б) проверять на этапе приема, не подделан ли обратный адрес.

Для этого в настоящее время существует четыре варианта. Советую использовать все.

Основная идея у всех вариантов похожа: в запись DNS Вашего домена вставляется некая информация, проверив которую принимающий сервер может определить, есть ли у отправителя разрешение на отсылку писем от имени указанного домена.

Использование SPF

В DNS-запись своего домена нужно вставить следующую строку:

aaa.ru. IN TXT "v=spf1 a mx -all"

(здесь aaa.ru — Ваш домен)

Если сервер обслуживает несколько доменов, нужно вставить соответствующие записи во все зоны.

Эта запись означает, что домен aaa.ru использует версию SPF1 и разрешает от своего имени посылать почту серверу, указанному в записи MX и A домена, и запрещает всем остальным.

На этапе приема сервер запросит в DNS запись MX и A Вашего домена, и, если полученный IP-адрес не совпадает с IP-адресом отправителя, то письмо будет отклонено.

Вместо -all на этапе тестирования можно вставить ~all (в этом случае письмо будет не отклонено, а сервер-получатель получит «предупреждение» о возможной подделке адреса отправителя - т.н. softfail).

Вообще говоря, запись для SPF в домене можно сгенерировать автоматически, ответив на несколько вопросов вот тут: http://www.openspf.org/

Для того, чтобы Ваш почтовый сервер производил проверку по SPF, нужно установить пакеты:

sudo apt-get install python-policyd-spf python-spf

Отредактируйте файл /etc/postfix/main.cf

sudo nano /etc/postfix/main.cf

и вставьте строку

spf-policyd_time_limit = 3600s

В строку smtpd_recipient_restrictions вставьте

check_policy_service unix:private/policy-spf,

(эту проверку следует вставить после reject_unauth_destination чтобы не было риска использовать Вас, как open relay)

Отредактируйте файл /etc/postfix/master.cf

sudo nano /etc/postfix/master.cf

и вставьте строки

policy-spf  unix  -       n       n       -       -       spawn 
     user=nobody argv=/usr/bin/policyd-spf

Проверка на SPF нужна только для входящих писем, исходящие письма с Вашего сервера проверять не надо.

Перезагрузите postfix

sudo /etc/init.d/postfix reload

Использование Sender ID

Технология Sender ID - это вариант от Microsoft. И, хотя мы не собираемся проверять входящую почту на Sender ID, мы уже сконфигурировали все, что необходимо для отправки писем с использованием Sender ID: эта технология использует ту же запись в DNS, которую мы внесли. Единственно что можно еще сделать - это зарегистрировать нашу запись в Microsoft вот здесь: https://support.msn.com/eform.aspx?productKey=senderid&page=support_senderid_options_form_byemail&ct=eformts

Использование Domainkey и DKIM

Эта технология несколько отличается от SPF. DKIM - это несколько улучшенный вариант Domainkey, однако лучше использовать оба сервиса, тем более, что установка очень похожа. Для использования Domankey и DKIM установим пакеты

sudo apt-get install dk-filter dkim-filter

Выбирайте все опции «по умолчанию» при установке - сейчас мы сконфигурируем их вручную. Вначале сгенерим ключи (вместо aaa.ru используйте свой домен):

dkim-genkey -d aaa.ru -s mail

У Вас в текущей директории появится два файла: mail.private и mail.txt

Создадим директорию для хранения ключа и перепишем ключ в нее под именем mail (опять же, используйте свое имя домена), изменим разрешения для него и переименуем файл mail.txt:

sudo mkdir /etc/mail/aaa.ru
sudo mv mail.private /etc/mail/aaa.ru/mail
sudo chown root /etc/mail/aaa.ru/mail
sudo chmod 644 /etc/mail/aaa.ru/mail
mv mail.txt aaa.ru.txt

(при необходимости повторим для другого домена)

Отредактируем файл /etc/default/dk-filter

sudo nano /etc/default/dk-filter

и вставим туда строку

DAEMON_OPTS="$DAEMON_OPTS -d aaa.ru -k -s /etc/mail/dk-keys.conf -S mail" 

(если нужно несколько доменов — перечисляем их через запятую)

и строку

SOCKET="inet:8892@localhost"

Отредактируем файл /etc/default/dkim-filter

sudo nano /etc/default/dkim-filter

Строка

SOCKET="inet:8891@localhost" # Ubuntu default - listen on loopback on port 8891 

должна быть раскомментарена.

Отредактируем файл конфигурации /etc/dkim-filter.conf

sudo nano /etc/dkim-filter.conf

и поменяем в нем строку

Domain                  aaa.ru

Если нужно подписывать почту от нескольких доменов, вставляем список этих доменов:

Domain                  aaa.ru, bbb.ru

и следующие строки

Selector	mail
AutoRestart	no
Background	yes
Canonicalization	simple
DNSTimeout	5
Mode	sv
SignatureAlgorithm	rsa-sha256
SubDomains	yes
X-Header	no
AlwaysAddARHeader	yes
On-BadSignature	reject
Statistics	/var/log/dkim-filter/dkim-stats
KeyList	/etc/mail/dkim-keys.conf

Создадим файл /etc/mail/dkim-keys.conf

sudo nano /etc/mail/dkim-keys.conf
*@aaa.ru:aaa.ru:/etc/mail/aaa.ru/mail

(вставляем строки с соответствующими параметрами для каждого домена)

Создадим файл /etc/mail/dk-keys.conf

sudo nano /etc/mail/dk-keys.conf
*@aaa.ru:/etc/mail/aaa.ru/mail

(вставляем строки с соответствующими параметрами для каждого домена)

Создаем директорию для записи протокола работы dkim и меняем ее владельца

sudo mkdir /var/log/dkim-filter
sudo chown dkim-filter /var/log/dkim-filter

Отредактируем файл /etc/postfix/main.cf

sudo nano /etc/postfix/main.cf

и добавим в него строки

milter_default_action = accept 
milter_protocol = 2 
smtpd_milters = inet:localhost:8891,inet:localhost:8892
non_smtpd_milters = inet:localhost:8891,inet:localhost:8892 

Проверьте, что файл

sudo nano /etc/amavis/conf.d/21-ubuntu_defaults

содержит раскомментаренную строку

$enable_dkim_verification = 1; 

Отредактируем файл /etc/postfix/master.cf

sudo nano /etc/postfix/master.cf

и в блоке

127.0.0.1:10025 inet n - - - - smtpd 
        -o content_filter= 
        -o local_recipient_maps= 
        -o relay_recipient_maps= 
        -o smtpd_restriction_classes= 
        -o smtpd_client_restrictions= 
        -o smtpd_helo_restrictions= 
        -o smtpd_sender_restrictions= 
        -o smtpd_recipient_restrictions=permit_mynetworks,reject 
        -o mynetworks=127.0.0.0/8 
        -o strict_rfc821_envelopes=yes 
        -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
        -o smtpd_bind_address=127.0.0.1 

изменим строку

        -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters

иначе наши исходящие письма будут подписываться DKIM два раза.

Последнее, что нам осталось сделать - это внести изменения в DNS-записи нашего домена (доменов):

mail._domainkey.aaa.ru. IN TXT "g=*; k=rsa; p=XXXXXXXXXX" ; ----- DKIM mail for aaa.ru 
_domainkey.aaa.ru.      IN TXT "o=-" 

Здесь вместо aaa.ru - Ваш домен, а вместо XXXXXXXXXX - значение, записанное в «p=» в файле aaa.ru.txt для соответствующего домена, который появился при генерации ключей.

Важное замечание: в записи _domainkey.aaa.ru. вместо «o=-» на этапе тестирования можно вставить «o=~». Первое означает, что ВСЕ письма от вашего домена подписываются, а второе - что подписываются только некоторые письма.

Кроме этого, на этапе тестирования в запись mail._domainkey.aaa.ru. (внутри кавычек) можно добавить ключ t=y; :

mail._domainkey.aaa.ru. IN TXT "g=*; k=rsa; t=y; p=XXXXXXXXXX" ; ----- DKIM mail for aaa.ru
_domainkey.aaa.ru.      IN TXT "o=~"
ВАЖНО: При внесении изменений в зону DNS не забудьте увеличить номер зоны в записи SOA!

Перезапускаем сервисы dk-filter и dkim-filter

sudo /etc/init.d/dk-filter restart 
sudo /etc/init.d/dkim-filter restart 

Если они запустились нормально, перезапускаем postfix

sudo /etc/init.d/postfix restart

Протестировать только что установленные сервисы можно послав тестовое письмо на адрес check-auth@verifier.port25.com

В ответ Вы получите письмо с результатами проверки всех четырех сервисов. Если в ответе будет примерно следующее:

==========================================================
Summary of Results
==========================================================
SPF check: pass
DomainKeys check: pass
DKIM check: pass
Sender-ID check: pass

то все работает нормально.

Проверяем работу антиспама и антивируса

Для проверки работы антивируса пошлите себе с внешнего почтового аккаунта (например, gmail) письмо, содержащее следующую строку (это - не вирус, а стандартное тестовое письмо EICAR для проверки антивируса):

X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

и проверьте логи /etc/log/mail.log

cat /var/log/mail.log | grep "Blocked INFECTED"

Вы должны увидеть примерно такую запись:

Nov 30 22:02:22 oban amavis[21994]: (21994-01) Blocked INFECTED (Eicar-Test-Signature(69630e4574ec6798239b091cda43dca0:69)), [209.85.161.169] [209.85.161.169] <username@gmail.com> -> <admin@aaa.ru>, quarantine: n/virus-nGy-KxAsczIP, Message-ID: <AANLkTinxbYap5wcw8pstUQiLCuR1GjQQNPcWT_SCKtAo@mail.gmail.com>, mail_id: nGy-KxAsczIP, Hits: -, size: 2159, dkim_id=@gmail.com,username@gmail.com, 157 ms

Это письмо попадет в карантин (в нашем случае — в /var/lib/amavis/n/virus-nGy-KxAsczIP)

Для проверки антиспама пошлите себе с внешнего почтового аккаунта (например, gmail) письмо, содержащее следующую строку (это стандартный антиспам-тест GTUBE):

XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X

и проверьте логи /etc/log/mail.log

cat /var/log/mail.log | grep "Blocked SPAM"
Nov 30 22:37:29 oban amavis[22916]: (22916-01) Blocked SPAM, [209.85.161.41] [209.85.161.41] <username@gmail.com> -> <admin@aaa.ru>, Message-ID: <AANLkTinwyt0y-nk2yynQ40LfGmv9kO5vv082zubwwrk+@mail.gmail.com>, mail_id: ZD4iIEb5EZHD, Hits: 1002.473, size: 2168, dkim_id=@gmail.com,username@gmail.com, 2727 ms

(обратите внимание, что это тестовое письмо получит «вес» около 1000)

Установка fail2ban

Еще одно добавление к серверу. Дело в том, что все сервера так или иначе «торчащие» в интернет, становятся целью для атак, и наш почтовый сервер - не исключение. Одна из атак - попытка «взлома» паролей пользователей. Такая атака обычно проводится с перебором паролей, и рано или поздно может быть удачной. В нашем случае - это атака на аутентификацию пользователей по IMAP, POP3, SMTP.

Бороться достаточно просто - есть прекрасная программа fail2ban, которая после N неудачных попыток просто прописывает в файрволле правила, запрещающие доступ с атакующего IP-адреса на определенный срок (чаще всего - на час через 3 неудачных попытки). Такие «паузы» делают подобные атаки практически бессмысленными.

Для защиты устанавливаем пакет fail2ban

sudo apt-get install fail2ban

Создаем файл /etc/fail2ban/filter.d/dovecot.conf

sudo nano /etc/fail2ban/filter.d/dovecot.conf
[Definition] 
failregex = (?: pop3-login|imap-login): (?:Authentication failure|Aborted login \(auth failed|Aborted login \(tried to use disabled|Disconnected \(auth failed).*rip=(?P<host>\S*),.* 
ignoreregex =

Скопируем на всякий случай файл /etc/fail2ban/jail.conf

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.conf_orig

В нем есть примеры множества сервисов, нас же интересуют только часть из них. Отредактируем нужные строки в файле /etc/fail2ban/jail.conf

sudo nano /etc/fail2ban/jail.conf
ignoreip = 127.0.0.1 10.0.0.0/16
bantime  = 3600 
destemail = admin@aaa.ru

Здесь мы говорим, чтобы fail2ban игнорировал атаки с адресов 127.0.0.1 и сети 10.0.0.0/255.255.0.0 (предполагаем, что это наша внутренняя сеть, и из нее могут быть разве что ошибочные попытки набора паролей пользователями); после 3 неудачных попыток аутентификации мы ставим бан на 3600 секунд (1 час); информацию о банах будем посылать на admin@company.ru (поменяйте на свой).

[ssh] 
enabled = true 
port   = ssh 
filter   = sshd 
banaction = iptables-allports
            sendmail-whois 
logpath  = /var/log/auth.log
maxretry = 3
[dovecot] 
enabled  = true 
port    = imap2,imap3,imaps,pop3,pop3s 
filter   = dovecot 
logpath  = /var/log/mail.log 
banaction = iptables-allports
            sendmail-whois
maxretry = 3
[sasl] 
enabled  = true 
port    = smtp,ssmtp
filter   = sasl 
banaction = iptables-allports
            sendmail-whois 
logpath  = /var/log/mail.warn
maxretry = 3

По правилам, описанным в файле /etc/fail2ban/filter.d/sshd.conf мы:

  • анализируем лог-файл /var/log/auth.log
  • баним доступ по всем портам и посылаем письмо о возможной атаке через 3 неверные попытки аутентификации
  • сами правила в файле sshd.conf оставляем «по умолчанию»

Пример записи в /etc/log/auth.log которая будет «поймана»

Nov 25 10:19:41 oban sshd[30769]: Failed password for root from AA.BB.CC.DD port 60031 ssh2

По правилам, описанным в файле /etc/fail2ban/filter.d/dovecot.conf мы:

  • анализируем лог-файл /var/log/mail.log
  • баним доступ по всем портам и посылаем письмо о возможной атаке через 3 неверные попытки аутентификации
  • сами правила в файле dovecot.conf мы только что составили

Примеры записей в /etc/log/mail.log которые будут «пойманы»

Nov 27 23:41:57 oban dovecot: imap-login: Aborted login (no auth attempts): rip=AA.BB.CC.DD, lip=10.0.0.6
Nov 27 23:40:18 oban dovecot: pop3-login: Disconnected (no auth attempts): rip=AA.BB.CC.DD, lip=10.0.0.6, TLS handshaking: SSL_accept() failed: error:14094418:SSL routines:SSL3_READ_BYTES:tlsv1 alert unknown ca

По правилам, описанным в файле /etc/fail2ban/filter.d/sasl.conf мы:

  • анализируем лог-файл /var/log/mail.warn
  • баним доступ по всем портам и посылаем письмо о возможной атаке через 3 неверные попытки аутентификации
  • правило в файле /etc/fail2ban/filter.d/sasl.conf чуть изменим (с правилом «по умолчанию» срабатываний не будет):
sudo nano /etc/fail2ban/filter.d/sasl.conf
#failregex = (?i): warning: [-._\w]+\[<HOST>\]: SASL (?:LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(: [A-Za-z0-9+/]*={0,2})?$ 
failregex = (?i): warning: [-._\w]+\[<HOST>\]: SASL (?:LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed

Примеры записей в /etc/log/mail.log которые будут «пойманы»

Nov 24 19:21:20 oban postfix/smtpd[458]: warning: unknown[AA.BB.CC.DD]: SASL LOGIN authentication failed: generic failure
Nov 24 22:32:35 oban postfix/smtpd[2593]: warning: unknown[AA.BB.CC.DD]: SASL LOGIN authentication failed: authentication failure

Можно проверить срабатывание написанных нами правил:

fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf 
fail2ban-regex /var/log/mail.log /etc/fail2ban/filter.d/dovecot.conf 
fail2ban-regex /var/log/mail.warn /etc/fail2ban/filter.d/sasl.conf

Если в лог-файлах были попытки неверной аутентификации, мы получим по ним «отчет».

Когда убедимся, что все работает - перезапустим сервис

sudo /etc/init.d/fail2ban restart

и наслаждаемся попадающими к нам «хакерами».

Дополнительные настройки

Для того, чтобы наш сервер нормально воспринимали другие сервера, нам нужно сделать еще две вещи.

Первая - правильно и полно прописать DNS-зоны. В идеале MX запись должна указывать на имя сервера, а IP сервера в свою очередь, должен резолвиться в то же имя сервера. Тогда все будет «в шоколаде».

Т.е., например:

host aaa.ru
aaa.ru has address 78.108.81.68
aaa.ru mail is handled by 10 mxs.majordomo.ru.
host mxs.majordomo.ru
mxs.majordomo.ru has address 78.108.81.249
host 78.108.81.249
249.81.108.78.in-addr.arpa domain name pointer mxs.majordomo.ru.

Вторая - если Ваш почтовый сервер расположен за NAT, а Вы используете алиас на шлюзе, то он будет виден другим серверам не со своим IP-адресом (адресом алиаса), а с IP-адресом шлюза, что нехорошо (Ваши письма могут быть «отражены» адресатом на этом основании). Чтобы Ваш почтовый сервер был виден под своим IP-адресом (т.е. IP-адресом алиаса на шлюзе), нужно вставить на шлюзе что-то типа

#Added for 1:1 NAT — BEGIN 
/sbin/iptables -t nat -I POSTROUTING -o eth1 -s 10.0.0.6 -j SNAT --to-source AA.BB.CC.DD 
#Added for 1:1 NAT - END 

(здесь eth1 - внешний интерфейс шлюза, 10.0.0.6 - IP-адрес почтового сервера, AA.BB.CC.DD - IP-адрес алиаса на шлюзе)

Не забудьте обеспечить, чтобы эта строка выполнялась при перезагрузке шлюза

Установка mailman

Установим и настроим пакет mailman

sudo apt-get install mailman

Ответим на вопросы

Поддерживаемые языки:

Выберем русский и английский (en и ru)

Язык по умолчанию для Mailman:

ru (Russian)

Нас предупредят, что Отсутствует список рассылки сайта. Мы его создадим чуть позже.

Т.к. mailman не поддерживает utf8, часть операций с mailman производим с указанием LANG=C

Исправим ошибки (они неизбежны при установке) и проверим:

LANG=C sudo check_perms -f
LANG=C sudo check_perms

Мы получим что-то типа такого списка

/var/lib/mailman/mail bad group (has: root, expected list)
/var/lib/mailman/templates bad group (has: root, expected list)
/var/lib/mailman/locks bad group (has: root, expected list)
/var/lib/mailman/logs bad group (has: root, expected list)
/var/lib/mailman/bin bad group (has: root, expected list)
/var/lib/mailman/cgi-bin bad group (has: root, expected list)
/var/lib/mailman/cron bad group (has: root, expected list)
/var/lib/mailman/scripts bad group (has: root, expected list)
/var/lib/mailman/icons bad group (has: root, expected list)
/var/lib/mailman/Mailman bad group (has: root, expected list)
Problems found: 10
Re-run as list (or root) with -f flag to fix

Это нормально, т.к. все это — ссылки.

Сменим права доступа к директории с архивами списков рассылки для доступа к ним через веб:

sudo chown -R list /var/lib/mailman/archives/* 
sudo chmod o+x/var/lib/mailman/archives/private 

Отредактируем нужные строки в файле /etc/mailman/mm_cfg.py

sudo nano /etc/mailman/mm_cfg.py
DEFAULT_EMAIL_HOST = 'aaa.ru' 
DEFAULT_URL_HOST   = 'lists.aaa.ru' 
MTA=None   # Misnomer, suppresses alias output on newlist

Проверим содержимое файла /etc/postfix/master.cf

sudo nano /etc/postfix/master.cf

Если в нем строка mailman имеет вид

mailman unix - - n - - pipe

то изменим ее на

mailman unix - n n - - pipe

Настроим apache. Создадим файл /etc/apache2/sites-available/mailman

sudo nano /etc/apache2/sites-available/mailman
<VirtualHost *:80> 
ServerName lists.aaa.ru 
Alias /images/ /usr/share/images/ 
RedirectMatch ^/$ http://lists.aaa.ru/cgi-bin/mailman/listinfo 
ScriptAlias /mailman/ /usr/lib/cgi-bin/mailman/ 
ScriptAlias /cgi-bin/mailman/ /usr/lib/cgi-bin/mailman/ 
<Directory /usr/lib/cgi-bin/mailman/> 
 AllowOverride None 
 Options ExecCGI 
 Order allow,deny 
 Allow from all 
</Directory> 
Alias /pipermail/ /var/lib/mailman/archives/public/ 
<Directory /var/lib/mailman/archives/public> 
 AddDefaultCharset KOI8-R 
 Options Indexes MultiViews FollowSymLinks 
 AllowOverride None 
 Order allow,deny 
 Allow from all 
</Directory> 
Alias /archives/ /var/lib/mailman/archives/public/ 
<Directory /var/lib/mailman/archives/public> 
 AddDefaultCharset KOI8-R 
 DirectoryIndex index.html 
 Options Indexes MultiViews FollowSymLinks 
 AllowOverride None 
 Order allow,deny 
 Allow from all 
</Directory> 
CustomLog /var/log/apache2/lists.log combined 
<Directory /var/lib/mailman/archives/> 
Options Indexes FollowSymLinks 
AllowOverride None 
</Directory> 
</VirtualHost>

и разрешим запуск этого сайта

sudo a2ensite mailman

и перезапустим apache

sudo /etc/init.d/apache2 restart

Сконфигурируем postfix

Вставим запись в таблицу transport postfix

mysql -u mail_admin -p
Enter password:

Введем пароль

<mail_admin_password>
use mail;
INSERT INTO `transport` (`domain`, `transport`) VALUES ('lists.aaa.ru', 'mailman:'); 
quit;

Проверим содержимое файла /etc/postfix/master.cf

sudo nano /etc/postfix/master.cf

и убедимся, что он содержит

mailman   unix  -       n       n       -       -       pipe 
 flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py ${nexthop} ${user}

(здесь должно быть две строки)

Отредактируем файл /etc/postfix/main.cf

sudo nano /etc/postfix/main.cf

и добавим в него

# For mailman 
relay_domains = lists.aaa.ru 
mailman_destination_recipient_limit = 1

Теперь перезапустим сервисы и запустим mailman

sudo /etc/init.d/apache2 restart 
sudo /etc/init.d/postfix reload 
LANG=C sudo /etc/init.d/mailman start

Создание листа рассылки

Выполним

LANG=C sudo newlist mailman
Enter the email of the person running the list:

Введем адрес администратора листа рассылки

admin@aaa.ru
Initial mailman password:

Введем пароль, который будет использоваться при администрировании листа рассылки (замените на свой)

<mailman_list_password>
Hit enter to notify mailman owner…

Нажмите Enter.

Добавим в таблицу forwardings базы данных mail следующие записи:

source destination
mailman@aaa.ru mailman@lists.aaa.ru
mailman-admin@aaa.ru mailman-admin@lists.aaa.ru
mailman-bounces@aaa.ru mailman-bounces@lists.aaa.ru
mailman-confirm@aaa.ru mailman-confirm@lists.aaa.ru
mailman-join@aaa.ru mailman-join@lists.aaa.ru
mailman-leave@aaa.ru mailman-leave@lists.aaa.ru
mailman-owner@aaa.ru mailman-owner@lists.aaa.ru
mailman-request@aaa.ru mailman-request@lists.aaa.ru
mailman-subscribe@aaa.ru mailman-subscribe@lists.aaa.ru
mailman-unsubscribe@aaa.ru mailman-unsubscribe@lists.aaa.ru
mysql -u mail_admin -p
Enter password:

Введем пароль

<mail_admin_password>
use mail;
INSERT INTO `forwardings` (`source`, `destination`) VALUES ('mailman@aaa.ru', 'mailman@lists.aaa.ru '); 
INSERT INTO `forwardings` (`source`, `destination`) VALUES ('mailman-admin@aaa.ru', 'mailman-admin@lists.aaa.ru ');
INSERT INTO `forwardings` (`source`, `destination`) VALUES ('mailman-bounces@aaa.ru', 'mailman-bounces@lists.aaa.ru '); 
INSERT INTO `forwardings` (`source`, `destination`) VALUES ('mailman-confirm@aaa.ru', 'mailman-confirm@lists.aaa.ru '); 
INSERT INTO `forwardings` (`source`, `destination`) VALUES ('mailman-join@aaa.ru', 'mailman-join@lists.aaa.ru '); 
INSERT INTO `forwardings` (`source`, `destination`) VALUES ('mailman-leave@aaa.ru', 'mailman-leave@lists.aaa.ru '); 
INSERT INTO `forwardings` (`source`, `destination`) VALUES ('mailman-owner@aaa.ru', 'mailman-owner@lists.aaa.ru '); 
INSERT INTO `forwardings` (`source`, `destination`) VALUES ('mailman-request@aaa.ru', 'mailman-request@lists.aaa.ru '); 
INSERT INTO `forwardings` (`source`, `destination`) VALUES ('mailman-subscribe@aaa.ru', 'mailman-subscribe@lists.aaa.ru '); 
INSERT INTO `forwardings` (`source`, `destination`) VALUES ('mailman-unsubscribe@aaa.ru', 'mailman-unsubscribe@lists.aaa.ru ); 
quit;

Перезапуcтим mailman

LANG=C sudo /etc/init.d/mailman stop
LANG=C sudo /etc/init.d/mailman start

Нам нужно вставить в DNS-зону для нашего домена запись типа

lists	CNAME	oban.aaa.ru.

чтобы у нас был доступ к нашим спискам рассылки через веб.

Не забудьте увеличить порядковый номер в записи SOA зоны

Теперь мы можем зайти с паролем администратора листа mailman браузером на http://lists.aaa.ru для дальнейшей настройки листа рассылки.

Прочие листы рассылки добавляются аналогично.

Установка автоответчика

Создадим файл ./autoresponse следующего содержания (отличие от варианта, лежащего на http://nefaria.com/project_index/autoresponse/ в том, что для совместимости с mailman мы используем -autoresponse вместо +autoresponse):

nano ~/autoresponse
#!/bin/bash 
#+--------------------------------------------------------+ 
#|autoresponse 1.6.3 - an autoresponder script for postfix| 
#|        Charles Hamilton - musashi@nefaria.com          | 
#|           This program is GNU/GPL software             | 
#+--------------------------------------------------------+ 
 
shopt -s -o nounset 
shopt -s extglob 
 
if [ "${#}" -eq "0" ]; then 
	printf "%s\n" "Autoresponse v. 1.6.2" 
	printf "%s\n" "Type -h for help" 
	exit 0 
fi 
 
declare RECIPIENT="unset" 
declare SENDER="unset" 
declare SASL_USERNAME="unset" 
declare CLIENT_IP="unset" 
declare AUTHENTICATED="unset" 
declare AUTORESPONSE_MESSAGE="unset" 
declare DISABLE_AUTORESPONSE="unset" 
declare ENABLE_AUTORESPONSE="unset" 
declare DELETE_AUTORESPONSE="unset" 
declare SEND_RESPONSE="unset" 
declare RESPONSES_DIR="/var/spool/autoresponse/responses" 
declare SENDMAIL="/usr/sbin/sendmail" 
declare RATE_LOG_DIR="/var/spool/autoresponse/log" 
declare LOGGER="/usr/bin/logger" 
#There are two different modes of operation: 
#	MODE="0" represents the actions that can not be executed from the command line 
#	MODE="1" represents the actions that can be executed from the command line 
declare MODE="0" 
#Time limit, in seconds that determines how often an 
#autoresponse will be sent, per e-mail address (3600 = 1 hour, 86400 = 1 day) 
declare RESPONSE_RATE="86400" 
 
while getopts "r:s:S:C:e:d:E:D:h" SWITCH; do 
	case "${SWITCH}" in 
		r) #Set the recipient's address 
			RECIPIENT="`echo ${OPTARG} | tr '[:upper:]' '[:lower:]'`" 
			SEND_RESPONSE="1" 
		;; 
		s) #Set the sender's address 
			SENDER="`echo ${OPTARG} | tr '[:upper:]' '[:lower:]'`" 
			SEND_RESPONSE="1" 
		;; 
		S) #If SASL_USERNAME exists then the user was authenticated       
			SASL_USERNAME="${OPTARG}" 
			if [ -z "${SASL_USERNAME}" ]; then 
				AUTHENTICATED="0" 
			else 
				AUTHENTICATED="1" 
			fi 
		;; 
		C) #IP address of client (sender) 
			CLIENT_IP="${OPTARG}" 
		;; 
		e) #Set the filename of the user's autoresponse message 
		   #This is used for creating/editing new autoresponse messages 
			AUTORESPONSE_MESSAGE="${OPTARG}" 
			MODE="1" 
		;; 
		d) #Disable an existing autoresponse message 
			DISABLE_AUTORESPONSE="${OPTARG}" 
			MODE="1" 
		;; 
		E) #Enable an existing autoresponse message 
			ENABLE_AUTORESPONSE="${OPTARG}" 
			MODE="1" 
		;; 
		D) #Delete an existing autoresponse message 
			DELETE_AUTORESPONSE="${OPTARG}" 
			MODE="1" 
		;; 
		h|*) #Print the help dialog and exit 
			echo -e "\n${0} [-r {recipient email} -s {sender email} -S {sasl username} -C {client ip}] [-e {email address}] [-d {email address}] [-E {email address}] [-D {email address}] [-h]\n" 
			echo -e "   -r, -s, -S, and optionally -C must be used together to specify a recipient, sender, sasl username, and client IP of an autoresponse message." 
			echo -e "   Normally you configure these in postfix's \"master.cf\" but they can be used from the terminal as well (only for testing purposes!)." 
			echo -e "   If this is executed from a terminal, you'll need to hit CTRL-D when you are finished typing your autoresponse message.\n" 
			echo "   -e is used to create a new autoresponse or edit an existing one for the specified user." 
			echo -e "   If a disabled autoresponse message exists, it will be ignored and a new message will be created.\n" 
			echo -e "   -d is used to disable an existing active autoresponse message.\n" 
			echo "   -E is used to enable an existing autoresponse message. If both a disabled AND and an active autoresponse message exist," 
			echo -e "   the active message will be overwritten by the disabled one.\n" 
			echo -e "   -D is used to delete an existing autoresponse message, it will not delete disabled autoresponse messages.\n" 
			echo -e "   -h prints this help menu\n" 
			exit 0 
		;; 
	esac 
done 
 
#If a SASL authenticated user wants to set their autoresponse message via e-mail... 
if [ "${AUTHENTICATED}" = "1" ] && [ "${RECIPIENT/@*/}" = "${SENDER/@*/-autoresponse}" ] && [ "${MODE}" = "0" ]; then 
	if [ -f "${RESPONSES_DIR}/${SENDER}" ]; then 
		#Delete a user's existing autoresponse message. 
		(${0} -D "${SENDER}") 
		if [ ! -f "${RESPONSES_DIR}/${SENDER}" ]; then 
			${LOGGER} -i -t autoresponse -p mail.notice "Autoresponse disabled for address: ${SENDER} by SASL authenticated user: ${SASL_USERNAME} from: ${CLIENT_IP}" 
			(echo -e "From: ${RECIPIENT}\nTo: ${SENDER}\nSubject: Out of Office\n\n" 
			echo "Autoresponse disabled for ${SENDER} by SASL authenticated user: ${SASL_USERNAME} from: ${CLIENT_IP}") | ${SENDMAIL} -i -f "${RECIPIENT}" "${SENDER}" 
		else 
		${LOGGER} -i -t autoresponse -p mail.notice "Autoresponse could not be disabled for address: ${SENDER}" 
		fi 
	elif  [ ! -f "${RESPONSES_DIR}/${SENDER}" ]; then 
		#Read from STDIN and save this as the user's autoresponse message. 
		#This will overwrite any pre-existing autoresponse messages! 
		cat > "${RESPONSES_DIR}/${SENDER}" 
		if [ -f "${RESPONSES_DIR}/${SENDER}" ]; then 
			${LOGGER} -i -t autoresponse -p mail.notice "Autoresponse enabled for address: ${SENDER} by SASL authenticated user: ${SASL_USERNAME} from: ${CLIENT_IP}" 
			(echo -e "From: ${RECIPIENT}\nTo: ${SENDER}\nSubject: Out of Office\n\n" 
			echo "Autoresponse enabled for ${SENDER} by SASL authenticated user: ${SASL_USERNAME} from: ${CLIENT_IP}") | ${SENDMAIL} -i -f "${RECIPIENT}" "${SENDER}" 
		else 
			${LOGGER} -i -t autoresponse -p mail.notice "Autoresponse could not be enabled for address: ${SENDER}" 
		fi 
	fi 
#Log any unauthenticated shenanigans. We're attempting to prevent two scenarios here: 
# 
#(1) A user sends an e-mail to user-autoresponse@domain.tld from user@domain.tld through an open relay 
#    in an unauthorized attempt to set an autoresponse for the real user@domain.tld. The open relay 
#    will relay the message but because it will not authenticate with the mail server for domain.tld 
#    AUTHENTICATED will equal 0 and the user portion of the recipient address will equal user-autoresponse. 
#    Since we do not allow unauthenticated users to set autoresponse messages, we log this attempt as 
#    suspicious and exit cleanly so that postfix doesn't generate a bounce message. 
# 
#(2) A user sends e-mail to user-autoresponse@domain.tld from user@domain.tld through a mail server 
#    that requires authentication, (but allows relaying) and has autoresponse configured. This will result in 
#    an autoresponse toggle message being sent to the real user@domain.tld, notifying them that their 
#    autoresponse message has been enabled or disabled when in fact it has not. This scenario is rarer 
#    than the first and it is mainly meant to protect against compromised accounts and/or potential abuse 
#    by legitimate users of the rogue mail server. 
# 
elif [ "${AUTHENTICATED}" = "0" ] && [ "${RECIPIENT/@*/}" = "${SENDER/@*/-autoresponse}" ] || [ "${SENDER/@*/-autoresponse}" = "${RECIPIENT/@*/-autoresponse-autoresponse}" ] && [ "${MODE}" = "0" ]; then 
	${LOGGER} -i -t autoresponse -p mail.warning "Unauthenticated attempt to set autoresponse message for ${SENDER/-autoresponse/} from ${CLIENT_IP}!" 
	exit 0 
#Finally, if the user recipient address does not equal user-autoresponse then we assume that it's 
#just a normal message. We check to see if the recipient has an autoresponse message set; if one 
#has not already been sent to the sender within our timeframe, we send it and then we deliver the 
#original message to the original recipient. 
elif [ "${RECIPIENT/@*/}" != "${SENDER/@*/-autoresponse}" ] && [ "${MODE}" = "0" ]; then 
	rate_log_check() { 
		#Only send one autoresponse per e-mail address per the time limit (in seconds) designated by the RESPONSE_RATE variable 
		if [ -f "${RATE_LOG_DIR}/${RECIPIENT}/${SENDER}" ]; then 
			declare ELAPSED_TIME=`echo $[\`date +%s\` - \`stat -c %X "${RATE_LOG_DIR}/${RECIPIENT}/${SENDER}"\`]` 
			if [ "${ELAPSED_TIME}" -lt "${RESPONSE_RATE}" ]; then 
				${LOGGER} -i -t autoresponse -p mail.notice "An autoresponse has already been sent from ${RECIPIENT} to ${SENDER} within the last ${RESPONSE_RATE} seconds" 
				SEND_RESPONSE=0 
			fi 
		fi 
	} 
	if [ -f "${RESPONSES_DIR}/${RECIPIENT}" ]; then 
		rate_log_check 
		#If SEND_RESPONSE still equals "1" after the rate_log_check function, send an autoresponse. 
		if [ "${SEND_RESPONSE}" = "1" ] && [ "${RECIPIENT}" != "${SENDER}" ]; then 
			(cat "${RESPONSES_DIR}/${RECIPIENT}") | sed -e "0,/^$/ { s/^To:.*/To: <${SENDER}>/ }" -e '0,/^$/ { /^Date:/ d }' | ${SENDMAIL} -i -f "${RECIPIENT}" "${SENDER}" 
			mkdir -p "${RATE_LOG_DIR}/${RECIPIENT}" 
			touch "${RATE_LOG_DIR}/${RECIPIENT}/${SENDER}"  
			${LOGGER} -i -t autoresponse -p mail.notice "Autoresponse sent from ${RECIPIENT} to ${SENDER}" 
		fi 
	fi 
	exec ${SENDMAIL} -i -f "${SENDER}" "${RECIPIENT}" 
fi 
#Check to see if we are editing or creating a new autoresponse for a user. 
#This should only be used from the command line unlike -D, -d, and -E which 
#could be used via postfix pipe or in other areas where no user interaction 
#is required. 
if [ "${AUTORESPONSE_MESSAGE}" != "unset" ] && [ "${MODE}" = "1" ]; then 
	#Check to see if an autoresponse message exists for the email address specified by the "AUTORESPONSE_MESSAGE" parameter, if one exists 
	#then edit the existing one, if one does not exist, create a new one. This action will ignore any disabled autoresponse messages. 
	if [ -f "${RESPONSES_DIR}/${AUTORESPONSE_MESSAGE}" ]; then 
		vi "${RESPONSES_DIR}/${AUTORESPONSE_MESSAGE}" 
	elif ! [ -f "${RESPONSES_DIR}/${AUTORESPONSE_MESSAGE}" ]; then 
		vi "${RESPONSES_DIR}/${AUTORESPONSE_MESSAGE}" 
		if [ -f "${RESPONSES_DIR}/${AUTORESPONSE_MESSAGE}" ]; then 
			#Insert our mail headers; people who will be setting autoresponses from the command line 
			#hopefully will know better than to screw with these when editing an existing autoresponse. 
			sed -i "1i\From: ${AUTORESPONSE_MESSAGE}\nTo: THIS GETS REPLACED\nSubject: Out Of Office\n\n" "${RESPONSES_DIR}/${AUTORESPONSE_MESSAGE}" 
		fi 
	fi 
	if [ -f "${RESPONSES_DIR}/${AUTORESPONSE_MESSAGE}" ]; then 
		chown autoresponse.autoresponse "${RESPONSES_DIR}/${AUTORESPONSE_MESSAGE}" 
		chmod 600 "${RESPONSES_DIR}/${AUTORESPONSE_MESSAGE}" 
	else 
			echo "Editing ${RESPONSES_DIR}/${AUTORESPONSE_MESSAGE} aborted!" 
			exit 1 
	fi 
#Are we disabling an existing autoresponse message? 
elif [ "${DISABLE_AUTORESPONSE}" != "unset" ] && [ "${MODE}" = "1" ]; then 
	if [ -f "${RESPONSES_DIR}/${DISABLE_AUTORESPONSE}" ]; then 
		mv -i "${RESPONSES_DIR}/${DISABLE_AUTORESPONSE}" "${RESPONSES_DIR}/${DISABLE_AUTORESPONSE}_DISABLED" 
	elif ! [ -f "${RESPONSES_DIR}/${DISABLE_AUTORESPONSE}" ]; then 
		echo "${RESPONSES_DIR}/${DISABLE_AUTORESPONSE} does not exist thus, it cannot be disabled!" 
		exit 1 
	fi 
#Are we enabling an existing autoresponse message? 
elif [ "${ENABLE_AUTORESPONSE}" != "unset" ] && [ "${MODE}" = "1" ]; then 
	if [ -f "${RESPONSES_DIR}/${ENABLE_AUTORESPONSE}_DISABLED" ]; then 
		mv -i "${RESPONSES_DIR}/${ENABLE_AUTORESPONSE}_DISABLED" "${RESPONSES_DIR}/${ENABLE_AUTORESPONSE}" 
	elif ! [ -f "${RESPONSES_DIR}/${ENABLE_AUTORESPONSE}_DISABLED" ]; then 
		echo "There is no disabled autoresponse for ${ENABLE_AUTORESPONSE}" 
		exit 1 
	fi 
#Are we deleting an existing autoresponse message (this does not delete disabled autoresponse messages)? 
elif [ "${DELETE_AUTORESPONSE}" != "unset" ] && [ "${MODE}" = "1" ]; then 
	if [ -f "${RESPONSES_DIR}/${DELETE_AUTORESPONSE}" ]; then 
		rm "${RESPONSES_DIR}/${DELETE_AUTORESPONSE}" 
	elif ! [ -f "${RESPONSES_DIR}/${DELETE_AUTORESPONSE}" ]; then 
		echo "${RESPONSES_DIR}/${DELETE_AUTORESPONSE} does not exist thus, it cannot be deleted!" 
		exit 1 
	fi 
fi 
#===КОНЕЦ ФАЙЛА===

Выполним

sudo useradd -d /var/spool/autoresponse -s `which nologin` autoresponse 
sudo mkdir -p /var/spool/autoresponse/log /var/spool/autoresponse/responses 
sudo cp ~/autoresponse /usr/local/sbin/ 
sudo chown -R autoresponse.autoresponse /var/spool/autoresponse 
sudo chmod -R 0770 /var/spool/autoresponse

Откроем файл /etc/postfix/master.cf

sudo nano /etc/postfix/master.cf

и найдем строку

smtp      inet  n       -       -       -       -       smtpd 

сразу после нее вставим

   -o content_filter=autoresponder:dummy

(она должна начинаться хотя бы с одного пробела)

В конце файла вставляем

autoresponder unix - n n - - pipe 
     flags=Fq user=autoresponse argv=/usr/local/sbin/autoresponse -s ${sender} -r ${recipient} -S ${sasl_username} -C ${client_address} 

(это две строки, причем вторая строка должна начинаться хотя бы с одного пробела)

Выполняем команды

sudo postconf -e 'autoresponder_destination_recipient_limit = 1' 
sudo postconf -e 'recipient_delimiter = - ' 

и перезагружаем postfix

sudo /etc/init.d/postfix restart

Теперь для того, чтобы при Вашем отсутствии на работе (например, отпуск) всем, кто послал Вам письмо, автоматически отправлялось сообщение о Вашем отсутствии, нужно послать по адресу <ваш логин>-autoresponse@aaa.ru то письмо, которое Вы хотите установить в качестве автоответчика, например admin-autoresponse@aaa.ru (здесь и далее замените aaa.ru на Ваш домен).

Если все прошло нормально, Вы получите ответ с темой «Out Of Office» следующего содержания: «Autoresponse enabled for admin@aaa.ru by SASL authenticated user: admin@aaa.ru from: x.x.x.x», где x.x.x.x - IP-адрес хоста, с которого было отправлено письмо.

Теперь при посылке Вам письма все автоматически в ответ будут получать то письмо, которое вы установили в качестве автоответчика, сами письма будут сохраняться у Вас в папке «Входящие» как обычно.

Для того, чтобы убрать сообщение автоответчка, пошлите еще раз любое письмо на адрес <ваш логин>-autoresponse@aaa.ru и Вы получите в ответ письмо с темой «Out Of Office» следующего содержания: «Autoresponse disabled for admin@aaa.ru by SASL authenticated user: admin@aaa.ru from: x.x.x.x», где x.x.x.x - IP-адрес хоста, с которого было отправлено письмо.

Каждому корреспонденту автоответ будет отсылаться не чаще, чем раз в сутки, даже если он пошлет Вам несколько писем.

Письма для включения/выключения автоответчика должны быть посланы с Вашего адреса, иначе они не «сработают».

Установка munin

Установим нужные пакеты

sudo apt-get install munin-node munin logtail

(если у Вас уже установлен munin на каком-нибудь сервере, то установите только munin-node)

Отредактируем файл

sudo nano /etc/apache2/conf.d/munin

и поменяем (если необходимо) строку

#        Allow from localhost 127.0.0.0/8 ::1 

на

        Allow from all

Перезапустим apache

sudo /etc/init.d/apache2 restart

Нас интересуют более подробные графики того, что происходит у нас с почтой. Поэтому создаем файлы

sudo nano /usr/share/munin/plugins/amavis-debian
#!/bin/sh 
# 
# Plugin to monitor the amavis mail filter for Debian 
# (based upon a plugin authored by Geoffroy Desvernay) 
# 
# This plugin is built and tested on Debian Etch using: 
# 	munin 1.2.5-1 
#	amavisd-new 2.4.2-6.1 
# 
# With some minor modification it should also work on non-debian systems 
# This, however, is up to you 
# 
# Munin graph will sum up: Passed CLEAN, Blocked VIRUS, Blocked SPAM, Other 
# 
# Parameters understood: 
# 	config   (required) 
# 	autoconf (optional) 
# 
# Config variables: 
#       AMAVIS_LOG	- file where amavis logs are written 
#	STATEFILE	- file which is needed to keep track of AMAVIS_LOG 
#	LOGTAIL		- location of logtail 
#	BC		- location of bc 
# 
# Enjoy! 
# Fili Wiese 
# 
 
AMAVIS_LOG=${logfile:-/var/log/mail.log} 
STATEFILE=/var/lib/munin/plugin-state/amavis.offset 
LOGTAIL=${logtail:-`which logtail`} 
BC=${bc:-`which bc`} 
 
mktempfile () { 
	mktemp 
}       
 
if [ "$1" = "autoconf" ]; then 
        if [ -f "${AMAVIS_LOG}" -a -n "${LOGTAIL}" -a -x "${LOGTAIL}" -a -n "${BC}" -a -x "${BC}" ] ; then 
		echo yes 
		exit 0 
	else 
		echo no 
		exit 1 
	fi 
fi 
 
if [ "$1" = "config" ]; then 
	echo 'graph_title Amavis filter statistics' 
	echo 'graph_category postfix' 
#	echo 'graph_order total clean spam virus other' 
	echo 'graph_order sent clean spammy header spam virus' 
	echo 'graph_vlabel Mails filtered' 
	echo 'graph_scale no' 
#	echo 'total.label Total' 
#	echo 'total.draw AREA' 
#	echo 'total.colour DDDDDD' 
	echo 'sent.label Sent BYPASS' 
	echo 'sent.draw LINE1' 
	echo 'sent.colour 0099FF' 
	echo 'clean.label Passed CLEAN' 
	echo 'clean.draw LINE1' 
	echo 'clean.colour 32FA00' 
	echo 'spammy.label Passed SPAMMY' 
	echo 'spammy.draw LINE1' 
	echo 'spammy.colour FFCC00' 
	echo 'header.label Passed BAD-HEADER' 
	echo 'header.draw LINE1' 
	echo 'header.colour 99CC00' 
	echo 'spam.label Detected SPAM' 
	echo 'spam.draw LINE1' 
	echo 'spam.colour FF0000' 
	echo 'virus.label Blocked VIRUS' 
	echo 'virus.draw LINE1' 
	echo 'virus.colour 880088' 
#	echo 'other.label Other' 
#	echo 'other.draw LINE1' 
#	echo 'other.colour 0099FF' 
	exit 0 
fi 
 
 
sent=0 
clean=0 
virus=0 
spam=0 
spammy=0 
header=0 
other=0 
total=0 
 
ARGS=0 
`$LOGTAIL /etc/hosts 2>/dev/null >/dev/null` 
if [ $? = 66 ]; then 
    if [ ! -n "$logtail" ]; then 
	ARGS=1 
    fi 
fi 
 
TEMP_FILE=`mktempfile munin-amavis.XXXXXX` 
 
if [ -n "$TEMP_FILE" -a -f "$TEMP_FILE" ] 
then 
	if [ $ARGS != 0 ]; then 
	    $LOGTAIL ${AMAVIS_LOG} $STATEFILE | grep 'amavis\[.*\]:' | grep -v 'TIMED OUT' > ${TEMP_FILE} 
	else 
	    $LOGTAIL ${AMAVIS_LOG} $STATEFILE | grep 'amavis\[.*\]:' | grep -v 'TIMED OUT' > ${TEMP_FILE} 
	fi 
#	total=`cat ${TEMP_FILE} | wc -l` 
	sent=`grep 'Passed CLEAN, MYUSERS' ${TEMP_FILE} | wc -l` 
	clean=`grep 'Passed CLEAN,' ${TEMP_FILE} | wc -l` 
	clean=`echo ${clean}-${sent} | ${BC}`
	spammy=`grep 'Passed SPAMMY,' ${TEMP_FILE} | wc -l` 
	header=`grep 'Passed BAD-HEADER' ${TEMP_FILE} | wc -l` 
	spam=`grep 'Blocked SPAM,' ${TEMP_FILE} | wc -l` 
	virus=`grep 'INFECTED' ${TEMP_FILE} | wc -l` 
#	other=`echo ${total}-${clean}-${virus}-${other}-${spam} | ${BC}` 
 
	/bin/rm -f $TEMP_FILE 
fi 
 
echo "sent.value ${sent}" 
echo "clean.value ${clean}" 
echo "spammy.value ${spammy}" 
echo "header.value ${header}" 
echo "spam.value ${spam}" 
echo "virus.value ${virus}" 
#echo "other.value ${other}" 
#echo "total.value ${total}" 
udo nano /usr/share/munin/plugins/amavis_
#!/usr/bin/perl -w 
# 
# Plugin to monitor amavisd-new statistics.  Values are retrieved by querying 
# the BerkeleyDB database 'snmp.db', in which amavisd-new stores its 
# statistics. 
# 
# The plugin requires the Perl module BerkeleyDB. 
# 
# To use, setup /etc/munin/plugin-conf.d/amavis e.g. as follows: 
# 
#   [amavis_*] 
#   env.amavis_db_home /var/lib/amavis/db 
#   user amavis 
# 
# Where env.amavis_db_home is the path to the amavisd-new BerkeleyDB files 
# (/var/amavis/db by default). 
# 
# Then create symlinks in the Munin plugin directory named "amavis_time", 
# "amavis_cache" and "amavis_content", or use munin-node-configure. 
# 
# Parameters: 
# 
#	config 
#	autoconf 
#	suggest 
# 
# Config variables: 
# 
#       amavis_db_home   - where the amavisd-new berkeley db files are located 
# 
# Magic markers 
#%# family=auto 
#%# capabilities=autoconf 
#%# capabilities=suggest 
 
use strict; 
no warnings 'uninitialized'; 
 
use BerkeleyDB; 
 
my($dbfile) = 'snmp.db'; 
my($db_home) =  # DB databases directory 
  defined $ENV{'amavis_db_home'} ? $ENV{'amavis_db_home'} : '/var/amavis/db'; 
 
if ($ARGV[0] and $ARGV[0] eq "autoconf") { 
	if (-x "/usr/sbin/amavisd-agent") { 
		print "yes\n"; 
		exit 0; 
	} else { 
		print "no (/usr/sbin/amavisd-agent not found or not executable)\n"; 
		exit 1; 
	} 
} elsif ($ARGV[0] and $ARGV[0] eq "suggest") { 
	print "time\n"; 
	print "cache\n"; 
	print "content\n"; 
	exit 0; 
} 
 
my $stats_type = ""; 
if ($0 =~ /^(?:|.*\/)amavis_(cache|content|time)$/) { 
	$stats_type = $1; 
} else { 
	print "You need to create a symlink to this plugin called either amavis_cache, amavis_time or amavis_content to be able to use it.\n"; 
	exit 2; 
} 
 
if ($ARGV[0] and $ARGV[0] eq "config") { 
	if ($stats_type eq "cache") { 
		print "graph_title Amavis cache hit / miss ratio\n"; 
		print "graph_args --lower-limit 0 --upper-limit 100 --rigid\n"; 
		print "graph_category mail\n"; 
		print "graph_info The ratio of cache hits and misses for AMaViSd-new.\n"; 
		print "graph_order hits misses\n"; 
		print "graph_scale no\n"; 
		print "graph_vlabel %\n"; 
		print "hits.label Cache hits\n"; 
		print "hits.draw AREA\n"; 
		print "hits.max 100\n"; 
		print "hits.min 0\n"; 
		print "misses.label Cache misses\n"; 
		print "misses.draw STACK\n"; 
		print "misses.max 100\n"; 
		print "misses.min 0\n"; 
	} elsif ($stats_type eq "content") { 
		print "graph_title Amavis scanned mails\n"; 
		print "graph_category mail\n"; 
		print "graph_period minute\n"; 
		print "graph_vlabel msgs / \${graph_period}\n"; 
		foreach my $type (qw(total clean spam spammy virus)) { 
			print "$type.label " . ucfirst($type) . " mails \n"; 
			print "$type.type DERIVE\n"; 
			print "$type.min 0\n"; 
		} 
		print "clean.info Legitimate mail.\n"; 
		print "spammy.info Mails with a spam score above the tag2 level.\n"; 
		print "spam.info Mails with a spam score above the kill level for spam.\n"; 
		print "virus.info Mails with a virus.\n"; 
		print "total.info Total number of scanned mails.\n"; 
	} elsif ($stats_type eq "time") { 
		print "graph_title Amavis average scan time\n"; 
		print "graph_info Average time spent in each phase of the mail scanning process, per mail.\n"; 
		print "graph_category mail\n"; 
		print "graph_vlabel sec / mail\n"; 
		print "graph_scale no\n"; 
 
		print "msgs.label Total number of messages\n"; 
		print "msgs.graph no\n"; 
		print "msgs.type DERIVE\n"; 
		print "msgs.min 0\n"; 
 
		foreach my $type (qw(decoding receiving sending spamcheck viruscheck total)) { 
			print "${type}.label " . ucfirst($type) . "\n"; 
			print "${type}.type DERIVE\n"; 
			print "${type}.min 0\n"; 
			print "${type}.cdef ${type},1000,/,msgs,/\n"; 
		} 
	} 
	exit 0; 
} 
 
 
my ($env, $db, @dbstat, $cursor); 
 
@dbstat = stat("$db_home/$dbfile"); 
my $errn = @dbstat ? 0 : 0+$!; 
$errn == 0 or die "stat $db_home/$dbfile: $!"; 
 
$env = BerkeleyDB::Env->new( 
	-Home => $db_home, 
	-Flags => DB_INIT_CDB | DB_INIT_MPOOL, 
	-ErrFile => \*STDOUT, 
	-Verbose => 1, 
); 
defined $env or die "BDB no env: $BerkeleyDB::Error $!"; 
 
$db = BerkeleyDB::Hash->new(-Filename => $dbfile, -Env => $env); 
defined $db or die "BDB no db: $BerkeleyDB::Error $!"; 
 
my %values = (); 
my ($eval_stat, $stat, $key, $val); 
 
$cursor = $db->db_cursor;  # obtain read lock 
defined $cursor or die "db_cursor error: $BerkeleyDB::Error"; 
 
while (($stat = $cursor->c_get($key, $val, DB_NEXT)) == 0) { 
	$values{$key} = $val; 
} 
 
$stat == DB_NOTFOUND  or die "c_get: $BerkeleyDB::Error $!"; 
$cursor->c_close == 0 or die "c_close error: $BerkeleyDB::Error"; 
$cursor = undef; 
 
$eval_stat = $@; 
 
if ($eval_stat ne '') { chomp($eval_stat); die "BDB $eval_stat\n"; } 
 
for my $k (sort keys %values) { 
	if ($values{$k} =~ /^(?:C32|C64) (.*)\z/) { 
		$values{$k} = $1; 
	} 
} 
 
if ($stats_type eq "cache") { 
	my $hits = $values{'CacheHits'}; 
	my $misses = $values{'CacheMisses'}; 
	my $misses_ratio = $misses * 100.00 / ($hits + $misses); 
	my $hits_ratio = $hits * 100.00 / ($hits + $misses); 
 
	printf("hits.value %.1f\n", $hits_ratio); 
	printf("misses.value %.1f\n", $misses_ratio); 
} elsif ($stats_type eq "content") { 
	printf("total.value %d\n", $values{'InMsgs'}); 
	my $clean = $values{'ContentCleanMsgs'}; 
	if (defined($values{'ContentCleanTagMsgs'})) { 
		$clean += $values{'ContentCleanTagMsgs'}; 
	} 
	printf("clean.value %d\n", $clean); 
	printf("spam.value %d\n", $values{'ContentSpamMsgs'}); 
	printf("spammy.value %d\n", $values{'ContentSpammyMsgs'}); 
	printf("virus.value %d\n", $values{'ContentVirusMsgs'}); 
} elsif ($stats_type eq "time") { 
	printf("decoding.value %d\n", $values{'TimeElapsedDecoding'}); 
	printf("receiving.value %d\n", $values{'TimeElapsedReceiving'}); 
	printf("sending.value %d\n", $values{'TimeElapsedSending'}); 
	printf("spamcheck.value %d\n", $values{'TimeElapsedSpamCheck'}); 
	printf("viruscheck.value %d\n", $values{'TimeElapsedVirusCheck'}); 
	printf("total.value %d\n", $values{'TimeElapsedTotal'}); 
	printf("msgs.value %d\n", $values{'InMsgs'}); 
} 
 
$db->db_close == 0 or die "BDB db_close error: $BerkeleyDB::Error $!"; 
sudo nano /usr/share/munin/plugins/postgrey
#!/bin/bash 
# 
# Plugin to monitor incoming Postgrey 
# 
# Parameters understood: 
# 
# 	config   (required) 
# 	autoconf (optional) 
# 
 
 
mktempfile () { 
mktemp -t 
}       
 
MAIL_LOG=${logfile:-/var/log/mail.log} 
STATEFILE=/var/lib/munin/plugin-state/postgrey.offset 
LOGTAIL=${logtail:-`which logtail`} 
 
if [ "$1" = "autoconf" ]; then 
        if [ -f "${MAIL_LOG}"  -a -n "${LOGTAIL}" -a -x "${LOGTAIL}" ] ; then 
		echo yes 
		exit 0 
	else 
		echo no 
		exit 1 
	fi 
fi 
 
if [ "$1" = "config" ]; then 
	echo 'graph_title Postgrey daily filtering' 
	echo 'graph_order delayed passed whitelisted' 
	echo 'graph_category mail' 
	echo 'graph_vlabel Count' 
	echo 'graph_scale no' 
 
##	echo 'graph_args --base 1000 -l 0' 
	echo 'delayed.label delayed' 
#	echo 'delayed.type DERIVE' 
	echo 'passed.label passed' 
#	echo 'passed.type DERIVE' 
	echo 'whitelisted.label whitelisted' 
#	echo 'whitelisted.type DERIVE' 
 
        exit 0 
fi 
 
 
delayed=0 
passed=0 
whitelisted=0 
 
ARGS=0 
`$LOGTAIL /etc/hosts 2>/dev/null >/dev/null` 
if [ $? = 66 ]; then 
    if [ ! -n "$logtail" ]; then 
	ARGS=1 
    fi 
fi 
 
TEMP_FILE=`mktempfile munin-postgrey.XXXXXX` 
 
if [ -n "$TEMP_FILE" -a -f "$TEMP_FILE" ] 
then 
	if [ $ARGS != 0 ]; then 
	    $LOGTAIL ${MAIL_LOG} $STATEFILE | grep 'post[fix|grey]' > ${TEMP_FILE} 
	else 
	    $LOGTAIL ${MAIL_LOG} $STATEFILE | grep 'post[fix|grey]' > ${TEMP_FILE} 
	fi 
 
	delayed=`grep 'Recipient address rejected.*Greylisted' ${TEMP_FILE} | wc -l` 
#	passed=`grep 'postgrey\[[0-9]*\]: delayed [0-9]* seconds:' ${TEMP_FILE} | wc -l` 
	passed=`grep 'postgrey\[[0-9]*\]: action=pass' ${TEMP_FILE} | wc -l` 
	whitelisted=`grep 'postgrey\[[0-9]*\]: whitelisted:' ${TEMP_FILE} | wc -l` 
 
	/bin/rm -f $TEMP_FILE 
fi 
 
echo "delayed.value ${delayed}" 
echo "passed.value ${passed}" 
echo "whitelisted.value ${whitelisted}" 
sudo nano /usr/share/munin/plugins/postfix_filtered_awk
#!/bin/bash 
# 
# Plugin to monitor incoming Postfix mail. 
# 
# Parameters understood: 
# 
# 	config   (required) 
# 	autoconf (optional) 
# 
 
# requires logtail 
 
# If you are using a postfix policy daemon (such as policyd) to track certain block conditions, place a line 
# in your /etc/munin/plugin-conf.d/munin-node like: 
# 
# [postfix_filtered] 
# env.policy my policy string 
# 
# When env.policy is set, this plugin will match the string you supply as env.policy and return the number of instances 
# of that string as an output called "policy.value". 
# 
# If you are NOT using a postfix policy daemon, as above, use the line 
# 
# [postfix_filtered] 
# env.policy none 
# 
# and this plugin will suppress output of policy.value 
 
POLICY='' 
[ "${policy}" = 'none' ] || POLICY="${policy}" 
export POLICY 
 
 
 
#LOGDIR=${logdir:-/var/log/mail} 
#MAIL_LOG=$LOGDIR/${logfile:-info} 
MAIL_LOG=/var/log/mail.info 
LOGTAIL=${logtail:-`which logtail`} 
STATEFILE=/var/lib/munin/plugin-state/postfix_mailfiltered_test.offset 
 
if [ "$1" = "autoconf" ]; then 
        if [ -f "${MAIL_LOG}" -a -n "${LOGTAIL}" -a -x "${LOGTAIL}" ] ; then 
		echo yes 
		exit 0 
	else 
		echo no 
		exit 1 
	fi 
fi 
 
if [ "$1" = "config" ]; then 
	echo 'graph_title Postfix message filtering' 
 
	echo 'graph_category mail' 
	echo 'graph_vlabel Mails per second' 
#	echo 'graph_args --base 1000 --logarithmic' 
	echo 'graph_args --base 1000 -l 0' 
 
	if [ -z "$POLICY" ] 
		then 
			echo 'graph_order rbl helo client sender recipient relay allowed' 
 
		else 
			echo 'graph_order rbl policy helo client sender recipient relay allowed' 
			echo 'policy.label policy blocked' 
			echo 'policy.min 0' 
			echo 'policy.draw LINE1' 
			echo 'policy.type ABSOLUTE' 
	fi 
 
 
	echo 'allowed.draw LINE2' 
	echo 'allowed.type ABSOLUTE' 
	echo 'allowed.colour 00ff00' 
	echo 'rbl.draw LINE2' 
	echo 'rbl.type ABSOLUTE' 
	echo 'rbl.colour 1010ff' 
 
	for i in helo client sender recipient relay; 
	do 
		echo "$i.min 0" 
		echo "$i.type ABSOLUTE" 
		echo "$i.draw LINE1"; 
	done 
 
	echo 'allowed.label allowed' 
	echo 'rbl.label RBL blocked' 
	echo 'helo.label HELO rejected' 
	echo 'client.label Client rejected' 
	echo 'sender.label Sender rejected' 
	echo 'recipient.label recipient unknown' 
	echo 'relay.label relay denied' 
 
        exit 0 
 
fi 
 
$LOGTAIL ${MAIL_LOG} $STATEFILE | \ 
awk 'BEGIN { na= 0; nb= 0; nc= 0; nd= 0; ne= 0; nf= 0; ng= 0; nh= 0 ; st= ENVIRON["POLICY"] } 
 
	{       
               if (index($0, "queued as")) { na++ } 
               else if (index($0, "Relay access denied")) { nb++ }    
               else if (index($0, "blocked using")) { nc++ } 
               else if (index($0, "Helo command rejected")) { nd++ } 
               else if (index($0, "Client host rejected")) { ne++ } 
               else if (index($0, "Sender address rejected")) { nf++ } 
               else if (index($0, "Recipient address rejected")) { ng++ } 
	       else if (st && index($0, st)) { nh++ } 
	} 
	END { print "allowed.value " na"\nrelay.value " nb"\nrbl.value " nc"\nhelo.value " nd"\nclient.value " ne"\nsender.value " nf"\nrecipient.value " ng ; if (st) print "policy.value " nh }' 

Сделаем их исполняемыми

cd /usr/share/munin/plugins
sudo chmod a+x amavis_ amavis-debian postgrey postfix_filtered_awk
cd ~

И создадим нужные ссылки

sudo ln -s /usr/share/munin/plugins/amavis_ /etc/munin/plugins/amavis_cache 
sudo ln -s /usr/share/munin/plugins/amavis_ /etc/munin/plugins/amavis_content 
sudo ln -s /usr/share/munin/plugins/amavis_ /etc/munin/plugins/amavis_time 
sudo ln -s /usr/share/munin/plugins/amavis-debian /etc/munin/plugins/amavis-debian 
sudo ln -s /usr/share/munin/plugins/postgrey /etc/munin/plugins/postgrey 
sudo ln -s /usr/share/munin/plugins/postfix_mailstats /etc/munin/plugins/postfix_mailstats 
sudo ln -s /usr/share/munin/plugins/postfix_filtered_awk /etc/munin/plugins/postfix_filtered_awk
sudo ln -s /usr/share/munin/plugins/fail2ban /etc/munin/plugins/fail2ban

Отредактируем файл /etc/munin/plugin-conf.d/munin-node

sudo nano /etc/munin/plugin-conf.d/munin-node

и вставим в него строки

[amavis-debian] 
user root 
group adm 
[postgrey] 
group adm 
[amavis_*] 
env.amavis_db_home /var/lib/amavis/db 
user amavis 
[postfix_mailstats] 
group adm
[postfix_filtered_awk] 
group adm 
[fail2ban]
user root 

Перезапустим munin-node

service munin-node restart

и через 5-10 минут получим графики. Они обновляются раз в пять минут.

Заключение

Все установленные пакеты находятся в работоспособном состоянии, причем уровень безопасности вполне достаточен. Дальнейшую настройку параметров всех пакетов и (при необходимости) ужесточение мер безопасности проводите, руководствуясь доступными в Интернет рекомендациями и описаниями соответствующих пакетов.

Имеющиеся проблемы

При полной установке почтового сервера по этому документу есть следующие нерешенные проблемы:

1. Установленный автоответчик не дает возможности работать обходу проверки на спам для исходящих писем, т.к. не срабатывает $policy_bank{'MYUSERS'} в amavis. Соответственно, munin не считает отосланные наружу письма. Решения пока нет.

Приложения

Приложение 1 — управление пользователями через веб

ВНИМАНИЕ! Это только примеры, используйте их на свой страх и риск. Ни в коем случае не открывайте общий доступ к этим скриптам!

Создадим новую директорию:

sudo mkdir /var/www/control

Отредактируем файл

sudo nano /etc/apache2/sites-available/default

и вставим туда строки

    Alias /control /var/www/control 
    <Directory /var/www/control> 
        AllowOverride All 
    </Directory> 

Отредактируем файл

sudo nano /etc/apache2/sites-available/default-ssl

и вставим туда строки

    Alias /control /var/www/control 
    <Directory /var/www/control> 
    Options Indexes MultiViews FollowSymLinks
        AllowOverride All 
    </Directory> 

Создадим файл

sudo nano /var/www/control/.htaccess

и добавим в него строки

AuthType Basic  
AuthName "Restricted access" 
AuthUserFile  /var/www/control/.htpasswd 
require valid-user 
SSLOptions +StrictRequire 
SSLRequireSSL 
SSLRequire %{HTTP_HOST} eq "oban.aaa.ru" 
ErrorDocument 403 https://oban.aaa.ru 
Options +FollowSymLinks 

(замените oban.aaa.ru на имя своего сервера)

Мы, во-первых, разрешаем доступ к этой директории только для пользователей, чьи имена и пароли записаны в файле /var/www/control/.htpasswd (мы сейчас его создадим) и, во-вторых, требуем, чтобы доступ к этим скриптам шел только с использованием ssl.

Дадим команду

htpasswd -c /var/www/control/.htpasswd admin
New password:

Введем пароль пользователя admin, которому мы даем доступ к скриптам

Re-type new password:

Повторим тот же пароль.

После этого у нас в директории /var/www/control/ появится файл .htpasswd, содержащий в зашифрованном виде наш пароль.

Создадим файл

sudo nano /var/www/control/index.html

следующего содержания

<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
<BODY BGCOLOR="#ffffff" text="#000000" link="#000000" vlink="#000000" 
alink="#000000"> 
<CENTER> 
<p><b>Управление пользователями почты</b></p> 
</center> 
<table> 
<tr><td valign=top><p><b>Новый пользователь</b></p> 
<form method=post action=control.php> 
e-mail&nbsp;пользователя:&nbsp;<input type="text" name="email" value="" size="50"><br> 
Пароль&nbsp;пользователя:&nbsp;<input type="password" name="password" value="" size="50"><br> 
Повторите&nbsp;пароль:&nbsp;<input type="password" name="password1" value="" size="50"><br> 
Квота&nbsp;пользователя:&nbsp;<input type="text" name="quota" value="0" size="50"><br> 
<input type="hidden" name="op" value="nu"> 
<input type="submit" value="New user" class="control"> 
</form></td> 
<td valign=top><p><b>Удаление пользователя</b></p> 
<form method=post action=control.php> 
e-mail&nbsp;пользователя:&nbsp;<input type="text" name="email" value="" size="50"><br> 
<input type="hidden" name="op" value="du"> 
<input type="submit" value="Delete user" class="control"> 
</form></td></tr> 
<tr><td valign=top><p><b>Задание пароля пользователя</b></p> 
<form method=post action=control.php> 
e-mail&nbsp;пользователя:<input type="text" name="email" value="" size="50"><br> 
Пароль&nbsp;пользователя:&nbsp;<input type="password" name="password" value="" size="50"><br> 
Повторите&nbsp;пароль:&nbsp;<input type="password" name="password1" value="" size="50"><br> 
<input type="hidden" name="op" value="pw"> 
<input type="submit" value="Set password" class="control"> 
</form></td></tr> 
<tr><td valign=top><p><b>Задание алиаса пользователя</b></p> 
<form method=post action=control.php> 
e-mail&nbsp;пользователя:&nbsp;<input type="text" name="destination" value="" size="50"><br> 
Алиас&nbsp;пользователя:&nbsp;<input type="text" name="source" value="" size="50"><br> 
<input type="hidden" name="op" value="na"> 
<input type="submit" value="Set alias" class="control"> 
</form></td> 
<td valign=top><p><b>Удаление алиаса пользователя</b></p> 
<form method=post action=control.php> 
Алиас&nbsp;пользователя:&nbsp;<input type="text" name="source" value="" size="50"><br> 
<input type="hidden" name="op" value="da"> 
<input type="submit" value="Delete alias" class="control"> 
</form></td></tr> 
<tr><td valign=top><p><b>Задание транспорта</b></p> 
<form method=post action=control.php> 
Источник:&nbsp;<input type="text" name="source" value="" size="50"><br> 
Транспорт:&nbsp;<input type="text" name="destination" value="" size="50"><br> 
<input type="hidden" name="op" value="nt"> 
<input type="submit" value="Set transport" class="control"> 
</form></td> 
<td valign=top><p><b>Удаление транспорта</b></p> 
<form method=post action=control.php> 
Источник:&nbsp;<input type="text" name="source" value="" size="50"><br> 
<input type="hidden" name="op" value="dt"> 
<input type="submit" value="Delete transport" class="control"> 
</form></td> 
<tr><td valign=top><p><b>Задание виртуального домена</b></p> 
<form method=post action=control.php> 
Виртуальный&nbsp;домен:&nbsp;<input type="text" name="source" value="" size="50"><br> 
<input type="hidden" name="op" value="nd"> 
<input type="submit" value="Set domain" class="control"> 
</form></td> 
<td valign=top><p><b>Удаление виртуального домена</b></p> 
<form method=post action=control.php> 
Виртуальный&nbsp;домен:&nbsp;<input type="text" name="source" value="" size="50"><br> 
<input type="hidden" name="op" value="dd"> 
<input type="submit" value="Delete domain" class="control"> 
</form></td></tr> 
</table> 
<BODY> 
</HTML> 
</body></html> 

Создадим файл

sudo nano /var/www/control/control.php

следующего содержания

<?php 
$headers = getallheaders(); 
$mysql_host="127.0.0.1"; 
$mysql_user = "mail_admin"; 
$mysql_password ="<mail_admin_password>"; 
$mydb_name = "mail"; 
$password=$_POST["password"]; 
$password1=$_POST["password1"]; 
$email=strtolower($_POST["email"]); 
$quota=$_POST["quota"]; 
$source=strtolower($_POST["source"]); 
$destination=strtolower($_POST["destination"]); 
$op=$_POST["op"]; 
$link = mysql_connect($mysql_host, $mysql_user, $mysql_password) 
       or die("Could not connect : " . mysql_error()); 
mysql_select_db($mydb_name) or die("Could not select database"); 
 switch ($op) { 
    case "nu": 
	if(!isemail($email)) { 
	    print <<< INVALIDEMAIL 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Введенный email некорректен. 
</body></html> 
INVALIDEMAIL; 
	    mysql_close($link); 
	    exit(1); 
	} 
	$query ="SELECT * FROM users WHERE email='$email'"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	$row=mysql_fetch_array($result); 
	if($row) { 
	    print <<< USEREXISTS 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Пользователь с указанным email уже существует. 
</body></html> 
USEREXISTS; 
	    mysql_close($link); 
	    exit(1); 
	} 
        if($password<>$password1) { 
	    print <<< WRONGPASS 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Введенные пароли не совпадают. 
</body></html> 
WRONGPASS; 
	    mysql_close($link); 
	    exit(1); 
	} 
	$query ="INSERT INTO users (email, password, quota) VALUES ('$email', ENCRYPT('$password'), '$quota');"; 
	$result=mysql_query($query) or die("Query failed : " . mysql_error()); 
	if($result) { 
	    print <<< USERADDED 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Пользователь добавлен. 
</body></html> 
USERADDED; 
	    mysql_close($link); 
	    exit(1); 
	} 
	break; 
    case "du": 
	$query ="SELECT * FROM users WHERE email='$email'"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	$row=mysql_fetch_array($result); 
	if(!$row) { 
	    print <<< NOSUCHUSER 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Пользователя с указанным email не существует. 
</body></html> 
NOSUCHUSER; 
	    mysql_close($link); 
	    exit(1); 
	} 
	$query ="DELETE FROM users WHERE email='$email'"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	print <<< USERDELETED 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Пользователь удален. 
</body></html> 
USERDELETED; 
	mysql_close($link); 
	exit(1); 
	break; 
    case "pw": 
	$query ="SELECT * FROM users WHERE email='$email'"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	$row=mysql_fetch_array($result); 
	if(!$row) { 
	    print <<< NOSUCHUSERP 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Пользователь с указанным email не существует. 
</body></html> 
NOSUCHUSERP; 
	    mysql_close($link); 
	    exit(1); 
	} 
	if($password<>$password1) { 
	    print <<< WRONGPASSP 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Введенные пароли не совпадают. 
</body></html> 
WRONGPASSP; 
	    mysql_close($link); 
	    exit(1); 
	} 
	$query ="UPDATE users SET password =  ENCRYPT('$password') WHERE email='$email';"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	if($result) { 
	    print <<< PASSWDSET 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Новый пароль установлен. 
</body></html> 
PASSWDSET; 
	    mysql_close($link); 
	    exit(1); 
	} 
	break; 
    case "na": 
	if(!isemail($source)) { 
	    print <<< INVALIDSRC 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Введенный алиас некорректен. 
</body></html> 
INVALIDSRC; 
	    mysql_close($link); 
	    exit(1); 
	} 
	if(!isemail($destination)) { 
	    print <<< INVALIDDST 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Введенный адрес пересылки некорректен. 
</body></html> 
INVALIDDST; 
	    mysql_close($link); 
	    exit(1); 
	} 
	$query ="SELECT * FROM forwardings WHERE source='$source'"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	$row=mysql_fetch_array($result); 
	if($row) { 
	    print <<< ALIASEXISTS 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Указанный алиас уже существует. 
</body></html> 
ALIASEXISTS; 
	    mysql_close($link); 
	    exit(1); 
	} 
	$query ="INSERT INTO forwardings (source, destination) VALUES ('$source', '$destination');"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	if($result) { 
	    print <<< ALIASADDED 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Алиас добавлен. 
</body></html> 
ALIASADDED; 
	    mysql_close($link); 
	    exit(1); 
	} 
	break; 
    case "da": 
	$query ="SELECT * FROM forwardings WHERE source='$source'"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	$row=mysql_fetch_array($result); 
	if(!$row) { 
	    print <<< NOSUCHALIAS 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Указанного алиаса не существует. 
</body></html> 
NOSUCHALIAS; 
	    mysql_close($link); 
	    exit(1); 
	} 
	$query ="DELETE FROM forwardings WHERE source='$source'"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	print <<< ALIASDELETED 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Алиас удален. 
</body></html> 
ALIASDELETED; 
	mysql_close($link); 
	exit(1); 
	break; 
    case "nt": 
	$query ="SELECT * FROM transport WHERE domain='$source'"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	$row=mysql_fetch_array($result); 
	if($row) { 
	    print <<< TRANSPEXISTS 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Указанный транспорт уже существует. 
</body></html> 
TRANSPEXISTS; 
	    mysql_close($link); 
	    exit(1); 
	} 
	$query ="INSERT INTO transport (domain, transport) VALUES ('$source', '$destination');"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	if($result) { 
	    print <<< TRANSPADDED 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Транспорт добавлен. 
</body></html> 
TRANSPADDED; 
	    mysql_close($link); 
	    exit(1); 
	} 
	break; 
    case "dt": 
	$query ="SELECT * FROM transport WHERE domain='$source'"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	$row=mysql_fetch_array($result); 
	if(!$row) { 
	    print <<< NOSUCHTRANSP 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Указанного транспорта не существует. 
</body></html> 
NOSUCHTRANSP; 
	    mysql_close($link); 
	    exit(1); 
	} 
	$query ="DELETE FROM transport WHERE domain='$source'"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	print <<< TRANSPDELETED 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Транспорт удален. 
</body></html> 
TRANSPDELETED; 
	mysql_close($link); 
	exit(1); 
	break; 
    case "nd": 
	$query ="SELECT * FROM domains WHERE domain='$source'"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	$row=mysql_fetch_array($result); 
	if($row) { 
	    print <<< DOMAINEXISTS 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Указанный домен уже существует. 
</body></html> 
DOMAINEXISTS; 
	    mysql_close($link); 
	    exit(1); 
	} 
	$query ="INSERT INTO domains (domain) VALUES ('$source');"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	if($result) { 
	    print <<< DOMAINADDED 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Домен добавлен. 
</body></html> 
DOMAINADDED; 
	    mysql_close($link); 
	    exit(1); 
	} 
	break; 
    case "dd": 
	$query ="SELECT * FROM domains WHERE domain='$source'"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	$row=mysql_fetch_array($result); 
	if(!$row) { 
	    print <<< NOSUCHDOMAIN 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Указанного домена не существует. 
</body></html> 
NOSUCHDOMAIN; 
	    mysql_close($link); 
	    exit(1); 
	} 
	$query ="DELETE FROM domains WHERE domain='$source'"; 
	$result = mysql_query($query) or die("Query failed : " . mysql_error()); 
	print <<< DOMAINDELETED 
<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"></head><body> 
Домен удален. 
</body></html> 
DOMAINDELETED; 
	mysql_close($link); 
	exit(1); 
	break; 
} 
function isemail($email) { 
    return preg_match('|^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]{2,})+$|i', $email); 
    } 
?> 
Не забудьте поменять пароль пользователя <mail_admin_password>

Изменим права доступа на эти файлы

sudo chown -R www-data /var/www/control
sudo chmod -R o-r /var/www/control

и перезапустим apache

sudo /etc/init.d/apache2 restart

После этого, зайдя браузером на https://oban.aaa.ru/control и введя имя пользователя admin и пароль <mail_admin_password> (который мы ранее для него задали), мы сможем создавать пользователей, удалять их, задавать им пароль (если они его забыли), а также создавать и удалять новые алиасы.

Приложение 2 — фильтрация писем на сервере

Если у Вас есть необходимость использовать фильтрацию писем на сервере, а не по правилам, которые могут устанавливать сами пользователи в horde, то это можно сделать с помощью procmail.

Установим procmail

sudo apt-get install procmail

Отредактируем файл

sudo nano /etc/postfix/master.cf

и вставим в него следующие строки:

procmail unix - n n - - pipe 
  -o flags=RO user=vmail argv=/usr/bin/procmail -t -m USER=${user} EXTENSION=${extension} NEXTHOP=${nexthop} /etc/postfix/procmail.common 

(вторая строка должна начинаться хотя бы с одного пробела)

Отредактируем файл

sudo nano /etc/postfix/main.cf

и вставим в него строку

procmail_destination_recipient_limit = 1 

Создадим файл

sudo nano /etc/postfix/procmail.common
MAILDIR="$HOME/$NEXTHOP/$USER" 
DEFAULT="$MAILDIR/" 
#VERBOSE=ON 
#each user will set his own log file 
#LOGFILE="/home/vmail/$NEXTHOP/$USER/procmail.log" 
NL=" 
" 
WS="	 " 
SWITCHRC="$HOME/$NEXTHOP/$USER/.procmail" 

(значение переменной WS — два символа: пробел и табуляция. Это нужно, чтобы проще было писать правила)

Если хотите, чтобы у каждого пользователя велся собственный протокол фильтрации, раскомментируйте строку #LOGFILE="/home/vmail/$NEXTHOP/$USER/procmail.log" (уберите символ # из начала строки).

Предположим, что у нас есть пользователь, почту которого мы хотим фильтровать по правилам на сервере — test@aaa.ru. Вставим в таблицу transport запись, которая говорит, что почта, поступившая на адрес получателя test@aaa.ru должна передаваться procmail для фильтрации:

mysql -u mail_admin -p
Enter password:

Введем пароль

<mail_admin_password>
use mail;
INSERT INTO `transport` (`domain`, `transport`) VALUES ('test@aaa.ru', 'procmail:'); 
quit;

Перезапускаем postfix

sudo /etc/init.d/postfix restart

И создаем в папке пользователя test@aaa.ru файл .procmail с нужными нам фильтрами, например:

sudo nano /home/vmail/aaa.ru/test/.procmail
:0
* ^Subject:.*\<testing
.testing/

Это значит, что при поступлении письма с темой, в которой есть слово testing, письмо будет перемещено в папку .testing. Заметьте, что точка перед названием папки — обязательна, чтобы папка была видна при доступе через IMAP.

Нужная папка создастся сама при первом срабатывании фильтра (т. к. у нас пользователь vmail, от имени которого запускается procmail, имеет право на создание папок в директориях пользователей).

Информацию, как именно писать фильтры для procmail можно найти в Интернет.

Приложение 3 — пользователь без почты

Зачастую необходимо создание такого пользователя, у которого вся приходящая почта будет уничтожаться (например, от имени такого пользователя обычно рассылаются сообщения, на которые ответы не ожидаются) — назовем его noreply@aaa.ru .

Сначала заведем этого пользователя в нашей таблице users (мы предполагаем, что такой пользователь нам в нашей ldap-таблице пользователей не нужен):

mysql -u mail_admin -p
Enter password:

Введем пароль

<mail_admin_password>
use mail;
INSERT INTO `users` (`email`, `password`, `quota`) VALUES ('noreply@aaa.ru', ENCRYPT('secret'), '0'); 

(вместо secret выберите пароль, который Вам все равно не понадобится, т. к. никакой почты этот пользователь получать не будет)

Для того, чтобы указать, что почту, приходящую этому пользователю, нужно просто уничтожать, используем таблицу transport:

INSERT INTO `transport` (`domain`, `transport`) VALUES ('noreply@aaa.ru', 'discard:'); 
quit;

Теперь вся почта, приходящая на адрес noreply@aaa.ru будет просто уничтожаться.

Приложение 4 — фильтрация писем на сервере при помощи sieve

Есть еще одна возможность фильтровать письма на сервере — так называемые скрипты sieve. Их отличие в том, что несмотря на то, что фильтрация происходит на сервере, пользователи могут сами создавать и редактировать правила фильтрации, причем не только при помощи horde: плагины для управления скриптами sieve (протокол управления называется managesieve — не правда ли, неожиданно?) существуют для многих почтовых клиентов (например, thunderbird).

Скрипты sieve поддерживаются dovecot, правда, для этого нам придется переложить на dovecot задачу раскладывания писем по папкам (у нас этим занимался postfix).

Отредактируем файл /etc/dovecot/dovecot.conf

sudo nano /etc/dovecot/dovecot.conf

изменив следующие параметры

protocols = imap imaps pop3 pop3s managesieve
protocol managesieve {
  listen = *:2000
  login_executable = /usr/lib/dovecot/managesieve-login
  mail_executable = /usr/lib/dovecot/managesieve
  managesieve_max_line_length = 65536
  managesieve_logout_format = bytes=%i/%o
  managesieve_implementation_string = Cyrus timsieved v2.2.13
}
protocol lda {
  postmaster_address = postmaster@aaa.ru
  mail_plugins = sieve
}

В блоке auth_default { расскомментируем (и отредактируем) строки

  socket listen {
    master {
      path = /var/run/dovecot/auth-master
      mode = 0600
      user = vmail
      #group = 
    }
    client {
     path = /var/run/dovecot/auth-client
     mode = 0660
   }
 } 

В блоке plugin { добавим строки

  sieve = ~/sieve/.dovecot.sieve
  sieve_dir = ~/sieve
  sieve_extensions = +imapflags

Здесь мы задаем расположение скриптов sieve относительно «домашней» директории наших пользователей (а также разрешаем использовать устаревшие команды imapflags — они нам нужны для управления фильтрами с помощью horde). В каждый момент времени может быть активным только один файл со скриптами, на него будет (автоматом, при активации файла в том или ином плагине) делаться ссылка .dovecot.sieve. У нас все пользователи — виртуальные, причем часть из них — ldap, остальные же — mysql пользователи, поэтому нам нужно задать домашнюю директорию для обоих типов.

Отредактируем файл

sudo nano /etc/dovecot/dovecot-ldap.conf

и отредактируем следующие строки

user_attrs = mail=mail=maildir:/home/vmail/aaa.ru/%n/,homeDirectory=home=/home/vmail/aaa.ru/%n\\
user_filter = (&(uid=%n))

Здесь мы явно задаем наш домен aaa.ru, т. к. пользователи этого домена авторизуются в ldap без указания домена, а только своим именем.

Отредактируем файл

sudo nano /etc/dovecot/dovecot-sql.conf

и отредактируем строку

user_query = SELECT email, CONCAT('/home/vmail/',CONCAT(SUBSTRING_INDEX(email,'@',-1),'/',SUBSTRING_INDEX(email,'@',1))) AS home, concat('*:storage=', quota) as quota_rule  FROM users WHERE email='%u'

Теперь настроим postfix на использование dovecot для локальной рассылки писем.

Отредактируем файл

sudo nano /etc/posfix/master.cf

и вставим строки

dovecot   unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${recipient}

(вторая строка начинается хотя бы с одного пробела)

Отредактируем файл

sudo nano /etc/postfix/main.cf

и вставим строки

dovecot_destination_recipient_limit = 1
virtual_transport = dovecot

Перезапустим dovecot и postfix

sudo /etc/init.d/dovecot restart
sudo /etc/init.d/postfix restart
Не забудьте на шлюзе пробросить TCP порт 2000 на почтовый сервер, чтобы клиенты снаружи могли использовать протокол managesieve.

Для управления sieve в horde установим необходимые пакеты:

sudo apt-get install php-net-sieve ingo1

К сожалению, в текущей версии php-net-sieve есть ошибка, которая приводит к выдаче отладочной информации, поэтому нам нужно отредактировать файл

sudo nano /usr/share/horde3/ingo/lib/Driver/timsieved.php

и закомментировать строку (в текущей версии файла это 83-я строка)

//            $this->_sieve->setDebug(true, array($this, '_debug'));

Теперь настроим ingo. Отредактируем файл

sudo nano /etc/horde/ingo1/backends.php

Закомментируем все, кроме следующих строк (отредактировав соответственно приведенному примеру):

/* Sieve */ 
$backends['sieve'] = array( 
    'driver' => 'timsieved', 
    'preferred' => 'localhost', 
    'hordeauth' => full, 
    'params' => array( 
        // Hostname of the timsieved server 
        'hostspec' => 'localhost', 
        // Login type of the server 
        'logintype' => 'PLAIN', 
        // Enable/disable TLS encryption 
        'usetls' => false, 
        // Port number of the timsieved server 
        'port' => 2000, 
        // Name of the sieve script 
        'scriptname' => 'ingo', 
        // The following settings can be used to specify an administration 
        // user to update all users' scripts. If you want to use an admin 
        // user, you also need to disable 'hordeauth' above. You have to use 
        // an admin user if you want to use shared rules. 
        // 'username' => 'cyrus', 
        // 'password' => '*****', 
    ), 
    'script' => 'sieve', 
    'scriptparams' => array(), 
    'shares' => false 
); 

Зайдем пользователем-адинистратором на https://oban.aaa.ru и войдем в Управление - Приложения - Фильтры (ingo). В поле «What storage driver should we use?» выберем Preference System.

Нажмем кнопку Generate Фильтры Configuration, скопируем сгенеренный текст конфигурации и запишем в файл /etc/horde/ingo1/conf.php

sudo nano /etc/horde/ingo1/conf.php

(здесь вставляем скопированный текст со страницы браузера)

Зайдем пользователем, у которого мы хотим использовать скрипты, войдем в Настройки - Фильтры, выберем (установим птичку) параметр «Automatically update the script after each change?» и нажмем кнопку Сохранить настройки.

Теперь в разделе Почта — Фильтры можно активировать, редактировать имеющиеся фильтры или добавлять новые. Все правила, которые Вы будете задавать через horde (ingo) будут записываться в файл правил с именем ingo. Этот файл будет доступен для редактирования с помощью любого другого плагина sieve (например, к thunderbird).

FAQ

Q: В этой статье написано, как настроить LDAP?
A: Нет. Настройка LDAP - это своя большая тема, которая лежит за рамками этого howto. Здесь просто показано, как использовать уже имеющийся LDAP. Если его нет (и нет AD) - используйте только mysql.

Q: Как сделать такой сервер, но без LDAP?
A: Просто пропускайте все, что связано с LDAP и делайте только то, что связано с mysql.

Ссылки