ISP-MAIL-HOWTO. v1.2.

Данный документ описывает установку и настройку почтовой системы на основе Postfix, Cyrus-SASL, MySQL, Courier-IMAP, SquirrelMail и DrWeb.

[Вячеслав Калошин (multik@multik.ru)]

ISP-MAIL-HOWTO. v1.2.

(с) 2002 Вячеслав Калошин, multik@multik.ru

Данный документ описывает установку и настройку почтовой системы на основе Postfix, Cyrus-SASL, MySQL, Courier-IMAP, SquirrelMail, DrWeb. В итоге должна получиться легко масштабируемая и управляемая система, которая без проблем как со стороны админа, так и со стороны железа спокойно будет тянуть по 5-10 тысяч почтовых пользователей. При этом нет разницы, сколько почтовых доменов заведено в системе, как называются пользователи и так далее. С системными они никак не коррелируют.

Пользователи же получат стандартный набор сервисов: SMTP с аунтефикацией, pop и imap сервисы, доступ к почте через браузер. Плюс чистую от вирусов почту.

Теперь оговорки. Данный текст написан в расчете на тех, кто уже понимает механизмы, происходящие внутри Linux. Если вы неделю как поставили Linux и желаете с помощью этого документа поставить свой hotmail.com, то я ни за что как обычно не отвечаю. Все логи и прочее были взяты с моей рабочей системы. Я ничего не выдумывал и не придумывал - это все работает реально.

В качестве базовой системы использовался RedHat 7.2. Но аналогично построенные системы работают на RedHat 6.2 и ASPLinux 7.2. Поэтому не вижу причин, почему бы им не работать на других системах. Все программы и пакеты вы сможете найти в окрестностях freshmeat.net.

Если вы желаете задать мне вопрос, задавайте по мылу, но прежде прочтите хотя бы PostfixFAQ на www.postfix.org - ответы на 90% вопросов, на которые я не отвечаю, есть там. Еще одно условие - сначала обдумайте и прочтите все сообщения системы в /var/log/messages и /var/log/maillog - обычно там есть исчерпывающая информация, почему не работает что-либо. Работать переводчиком (обьяснение и разжевывание каждой строчки лога с обьяснением, почему это так случилось) я готов за $50/строчка.

Для нормальных ;-): Просто мне ОЧЕНЬ надоели письма вида "А чего оно не работает, когда я сделал все так, как ты написал?".

Итак, еще немного лирики:

Почему я выбрал postfix? Ведь есть стандарт-де-факто sendmail. Есть еще exim, qmail и куча других почтовых серверов.

Ну sendmail я вынужден сразу cкинуть со счетов. То, что описывается ниже, sendmail неспособен выполнить. Или способен, но с очень большими усилиями администратора системы. На творения djb я почему-то не могу смотреть органически. Ну а exim я просто не видел.

Courier-IMAP выбран по другой причине. Где-то с полгода назад он оказался единственным IMAP сервером, который смог собраться на моей машине и работать под нагрузкой, не требуя к себе внимания.

DrWEB. Ну просто хороший антивирус. Нет такой толпы глюков, на которые я почему-то переодически напарывюсь, когда гляжу на AVP. Тем более, он даже с триальной лицензией спокойно выполняет работу почтового фильтра. Да, с купленной лицензией будет гораздо спокойнее - вирусы даже в архивах не пройдут. Но - если нет лицензии, то - нет.

SquirrelMail - это просто единственная система Web-based почты, которая не вызывает брезгливой реакции при первом и последующих взглядах. Плюс она приемлимо работает с русским языком.

Итак, начнем.

Для начала установите MySQL, если он не установлен. Авторы MySQL рекомендуют собирать MySQL определенным способом (компилятор определенной версии и так далее) и предупреждают, что самособранные и дистрибутивные MySQL могут и не работать так, как задумывалось. Я в этом им почему-то верю (были преценденты) и поэтому теперь забираю MySQL уже собранный с mysql.com.

# rpm -i MySQL*
Preparing db table
Preparing host table
Preparing user table
Preparing func table
Preparing tables_priv table
Preparing columns_priv table
Installing all prepared tables
020628 14:29:10  /usr/sbin/mysqld: Shutdown Complete

PLEASE REMEMBER TO SET A PASSWORD FOR THE MySQL root USER !
This is done with:
/usr/bin/mysqladmin -u root -p password 'new-password'
/usr/bin/mysqladmin -u root -h multik.ip-tel.int -p password 'new-password'
See the manual for more instructions.

Please report any problems with the /usr/bin/mysqlbug script!

The latest information about MySQL is available on the web at
http://www.mysql.com
Support MySQL by buying support/licenses at https://order.mysql.com

Starting mysqld daemon with databases from /var/lib/mysql
Вот MySQL и установлен. Не будем подвергать других искушению и сменим пароль администратора MySQL (к системному root не имеет никакого отношения!)
# /usr/bin/mysqladmin -u root -p password 'password'
mysqladmin спросит пароль - в первый раз он пустой - просто нажмите Enter.

Теперь устанавливаем SASL. К сожалению, установить его из RPM не получится. Все, что я видел до этого, собрано либо не так, либо не туда. В общем, неработоспособное. Но вы можете попытать удачу.

# tar zxvf cyrus-sasl-1.5.27.tar.gz
# cd cyrus-sasl-1.5.27
Заберите патч для поддержки MySQL и LDAP отсюда:

http://www.surf.org.uk/downloads/sasl-1.5.27-ldap-ssl-filter-mysql-patch4.tgz

Рапакуйте полученный архив и положите sasl-ldap+mysql.patch в корень дерева исходников SASL. Затем выполните следующие команды:

# patch -b -p1 < sasl-ldap+mysql.patch 
# autoheader
# autoconf
# automake -i
Все, патч установлен. Давайте сконфигурируем SASL:
# ./configure --with-mysql=/usr/include/mysql --enable-login
Соберем и установим его:
# make
# make install
# ln -s /usr/local/lib/sasl /usr/lib/sasl
# echo /usr/lib/sasl >> /etc/ld.so.conf
# ldconfig
# cd ..
Устанавливаем методы аунтефикации.
# cat > /usr/local/lib/sasl/smtpd.conf
pwcheck_method: mysql                                                           
mysql_user: postfix                                                             
mysql_passwd: postfix                                                           
mysql_host: localhost                                                           
mysql_database: mail                                                            
mysql_table: aliases                                                            
mysql_uidcol: alias                                                             
mysql_pwdcol: password
^D
А это для других почтовых демонов.
# ln -s /usr/local/lib/sasl/smtpd.conf /usr/local/lib/sasl/imapd.conf
# ln -s /usr/local/lib/sasl/smtpd.conf /usr/local/lib/sasl/pop3d.conf
Теперь пришла очередь postfix. Здесь ситуация та же. В принципе по сети бродит много rpm, которые содержат в себе откомпилированный postfix с поддержкой MySQL. Я предпочитаю собрать его сам и быть чуть-чуть увереннным в том, что я знаю, что собрал.
# tar zxvf postfix-1.1.11.tar.gz
# cd postfix-1.1.11
Следующие строчки - это одна команда. Ее следует вводить в один прием.
# make -f Makefile.init makefiles 'CCARGS=-DHAS_MYSQL -DUSE_SASL_AUTH 
-I/usr/include/mysql -I/usr/local/include -L /usr/local/lib -lsasl 
-lmysqlclient'
Собираем и устанавливаем:
# make
# adduser postfix
# groupadd postdrop
# make install
Вот ответы, которые я давал на вопросы инсталлятора:
install_root: [/]
tempdir: [/usr/src/post/postfix-1.1.11] /tmp
config_directory: [/etc/postfix]
daemon_directory: [/usr/libexec/postfix]
command_directory: [/usr/sbin]
queue_directory: [/var/spool/postfix]
sendmail_path: [/usr/sbin/sendmail]
newaliases_path: [/usr/bin/newaliases]
mailq_path: [/usr/bin/mailq]
mail_owner: [postfix]
setgid_group: [postdrop]
manpage_directory: [/usr/local/man]
sample_directory: [/etc/postfix]
readme_directory: [no]
Ура. Postfix встал. Теперь наша задача его отконфигурировать.
# cd /etc/postfix/
# mcedit main.cf ( Вместо mcedit может быть vi, emacs или любой другой
предпочитаемый вами текстовый редактор - это некритично ;-) ) 
Редактируем главный файл конфигурации postfix, обращая внимание на следующие строчки:
broken_sasl_auth_clients = yes
smtpd_sasl_auth_enable = yes
transport_maps = mysql:/etc/postfix/transport.cf
virtual_mailbox_base = /
virtual_uid_maps = mysql:/etc/postfix/ids.cf
virtual_gid_maps = mysql:/etc/postfix/gids.cf
virtual_mailbox_maps = mysql:/etc/postfix/aliases.cf
virtual_maps = mysql:/etc/postfix/remote_aliases.cf
relay_domains = $transport_maps
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated,check_relay_domains                                                                
disable_vrfy_command = yes
Что они означают и каково их действие, вы можете прочитать в документации по postfix или в FAQ.

Теперь разбираемся, где и что у нас лежит.

# cat > transport.cf
user = postfix
password = postfix
dbname = mail
table = transport
select_field = transport
where_field = domain
hosts = localhost
^D

# cat > ids.cf
user = postfix
password = postfix
dbname = mail
table = aliases
select_field = id
where_field = alias
hosts = localhost
^D

# cat > gids.cf
user = postfix
password = postfix
dbname = mail
table = aliases
select_field = gid
where_field = alias
hosts = localhost
^D
Обратите внимание на отсутствие лишних пробелов и других невидимых знаков в концах строчек - это важно!.

Как наверное, стало понятно из вышеприведенных файлов, я указал postfix искать MySQL на localhost, подключаться пользователем postfix с паролем postfix, использовать базу данных mail. Конечно, эти данные даны лишь для примера - вы должны или изменить их, или понять чем грозит использование паролей, сходными с логинами. Понятно, эти файлы незачем читать всем (намек на правильный chmod).

Проверяем, все ли в порядке.

# postfix check
Команда должна отработать без каких-либо сообщений об ошибках. Если что-то она вывела - разбирайтесь, что системе не понравилось.

Теперь необходимо создать пользователя и все необходимые таблицы с помощью вызова mysql -p:

mysql> create database mail;
Query OK, 1 row affected (0.62 sec)
mysql> grant insert,select,delete,update on mail.* to postfix@localhost identified by 'postfix';
Query OK, 0 rows affected (0.72 sec)
mysql> use mail;
Database changed
mysql> create table transport (domain varchar(255) PRIMARY KEY, transport char(8));
Query OK, 0 rows affected (0.00 sec)

mysql> create table aliases (id int(6), gid int(6), alias varchar(255) PRIMARY KEY,\
maildir varchar(255),password varchar(128), info varchar(128));
Query OK, 0 rows affected (0.00 sec)

mysql> create table remote_aliases (alias varchar(255) PRIMARY KEY, rcpt varchar(255));
Query OK, 0 rows affected (0.02 sec)
В конце у вас должно получиться следующее:

Здесь будет храниться информация о доменах, обслуживаемых postfix

mysql> desc transport;
+-----------+--------------+------+-----+---------+-------+
| Field     | Type         | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| domain    | varchar(255) |      | PRI |         |       |
| transport | varchar(8)   | YES  |     | NULL    |       |
+-----------+--------------+------+-----+---------+-------+
2 rows in set (0.05 sec)
Здесь информация о почтовых пользователях системы:
mysql> desc aliases;
+----------+--------------+------+-----+---------+-------+
| Field    | Type         | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| id       | int(6)       | YES  |     | NULL    |       |
| gid      | int(6)       | YES  |     | NULL    |       |
| alias    | varchar(255) |      | PRI |         |       |
| maildir  | varchar(255) | YES  |     | NULL    |       |
| password | varchar(128) | YES  |     | NULL    |       |
| info     | varchar(128) | YES  |     | NULL    |       |
+----------+--------------+------+-----+---------+-------+
6 rows in set (0.06 sec)
А здесь информация о почтовых переадресациях и прочем. Небольшие списки рассылки тоже включать можно сюда.
mysql> desc remote_aliases;
+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| alias | varchar(255) |      | PRI |         |       |
| rcpt  | varchar(255) | YES  |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
Проверьте, имеет ли пользователь postfix доступ к MySQL
# mysql -u postfix -p
Запускаем postfix
# postfix start
postfix/postfix-script: starting the Postfix mail system
И в консоли MySQL добавляем домен test.ru
mysql> insert into transport values ('test.ru','virtual:');
И пользователя multik@test.ru. Обратите внимание на путь к почтовому каталогу пользователя и на завершающий / в конце строки.
mysql> insert into aliases values (1000,12,'multik@test.ru','/var/spool/vmail/test.ru_multik/','testpassword','info');
Число 1000 я взял из головы - главное, что бы оно было больше последнего UID в системе. В RedHat пользовательские UID начинаются с 500, поэтому я думаю, что 500 локальных пользователей - вполне достаточно. А 12 - это GID группы mail на моей системе.

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

Создаем каталог, где будет храниться почта и устанавлиаем на него права.

# mkdir /var/spool/vmail
# chown nobody.mail /var/spool/vmail
# chmod 770 /var/spool/vmail
И проверяем, как у нас работает прием почты:
$ telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 mail.test.ru ESMTP Postfix
mail from: multik@test.ru
250 Ok
rcpt to: multik@test.ru
250 Ok
data
354 End data with <CR><LF>.<CR><LF>
hello
.
250 Ok: queued as 252BFEEAE6
В /var/log/messages должны появиться аналогичные строчки:
Jun 28 16:24:21 multik postfix/smtpd[21863]: connect from multik.ip-tel.int[127.0.0.1]
Jun 28 16:24:23 multik postfix/smtpd[21863]: 252BFEEAE6: client=multik.ip-tel.int[127.0.0.1]
Jun 28 16:24:32 multik postfix/cleanup[21919]: 252BFEEAE6: message-id=<20020628122423.252BFEEAE6@mail.test.ru>
Jun 28 16:24:32 multik postfix/qmgr[21762]: 252BFEEAE6: from=<multik@test.ru>, size=340, nrcpt=1 (queue active)
Jun 28 16:24:32 multik postfix/virtual[21921]: 252BFEEAE6: to=<multik@test.ru>, relay=virtual, delay=9, status=sent (maildir)
Если мы посмотрим в почтовый спул, то увидим, что письмо принято и дожидается своей очереди.
# ls -lR /var/spool/vmail
/var/spool/vmail:
итого 4
drwx------    5 1000     mail         4096 Июн 28 16:26 test.ru_multik

/var/spool/vmail/test.ru_multik:
итого 12
drwx------    2 1000     mail         4096 Июн 28 16:26 cur
drwx------    2 1000     mail         4096 Июн 28 16:26 new
drwx------    2 1000     mail         4096 Июн 28 16:26 tmp

/var/spool/vmail/test.ru_multik/cur:
итого 0

/var/spool/vmail/test.ru_multik/new:
итого 4
-rw-------    1 1000     mail          389 Июн 28 16:26 1025267217.21935_0.multik.ip-tel.int

/var/spool/vmail/test.ru_multik/tmp:
итого 0
Теперь подошла очередь установки IMAP и POP3 демонов. Иначе пользователям не через что будет получать почту. (Из одного письма: А чего у меня sendmail не отдает по pop3 почту Outlook'у ?)

Распаковываем и собираем Courier-IMAP

# tar zxvf courier-imap-1.5.1.tar.gz
# adduser courier
# chown courier.courier courier-imap-1.5.1
# cd courier-imap-1.5.1
# su - courier
$ cd /{куда распаковывали}/courier-imap-1.5.1
$ ./configure
$ make
Тут останавливаемся и проверяем, что у нас есть из демонов, которые будут почту проверять:
$ authlib/authinfo
AUTHENTICATION_MODULES="authdaemon"
AUTHDAEMONMODULELIST="authcustom authcram authmysql authuserdb authpam"
SASL_AUTHENTICATION_MODULES="CRAM-SHA1 CRAM-MD5 PLAIN LOGIN"
Обращаем внимание на наличие authmysql. Иначе разбираемся, почему не так.

Выходим из-под пользователя courier и устанавливаем демонов:

$ exit
# make install
# make install_configure
А теперь отдадим дань конфигурированию:
# cd /usr/lib/courier-imap/etc/
# cp authdaemonrc.dist authdaemonrc
Редактируем authdaemonrc - находим строчку
authmodulelist="authcustom authcram authuserdb authmysql authpam"
и оставляем от нее только такой вот огрызок.
 
authmodulelist="authmysql"
Другие строки НЕ ТРОГАЕМ.

Теперь указываем, где искать MySQL и информацию о пользователях:

# cat > authmysqlrc
MYSQL_SERVER            localhost
MYSQL_USERNAME          postfix
MYSQL_PASSWORD          postfix
MYSQL_PORT              3306
MYSQL_DATABASE          mail
MYSQL_USER_TABLE        aliases
MYSQL_CLEAR_PWFIELD     password
DEFAULT_DOMAIN          test.ru
MYSQL_UID_FIELD         id
MYSQL_GID_FIELD         gid 
MYSQL_LOGIN_FIELD       alias
MYSQL_HOME_FIELD        maildir
MYSQL_NAME_FIELD        info
MYSQL_MAILDIR_FIELD     maildir
^D
Опция DEFAULT_DOMAIN указывает, что добавлять к логину, если пользователь пытается ввести логин без доменной части. Остальное, я думаю, понятно из названия и описаний.

И запускаем pop3.

/usr/lib/courier-imap/libexec/pop3d.rc start
Должен запуститься и не ругаться ни на что.

Проверяем:

# telnet localhost 110
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
+OK Hello there.
user multik@test.ru
+OK Password required.
pass testpassword
+OK logged in.
list
+OK POP3 clients that break here, they violate STD53.
1 400
.
quit
+OK Bye-bye.
Connection closed by foreign host.
Как видите, система нас пустила. Наше отправленное письмо лежит и дожидается нас.

В maillog должны быть записи похожие на эти:

Jun 28 17:27:17 multik pop3d: LOGIN, user=multik@test.ru, ip=[::ffff:127.0.0.1]
Jun 28 17:27:24 multik pop3d: LOGOUT, user=multik@test.ru, ip=[::ffff:127.0.0.1], top=0, retr=0
Радуемся - базовая функциональность достигнута. Можно смело запускать imap сервер аналогично pop, раздавать пользователей и совершать другие необходимые телодвижения.

Но лично мне этого мало. Я не хочу видеть вирусы в своем почтовом ящике и в ящиках своих работников и клиентов. Ставим DrWeb.

Забираем с drweb.ru файлы:

 
drweb-4.28.1-linux.tgz 
drweb-postfix-4.28.4-linux.tgz
И распаковываем их. Хотя могли бы взять rpm файлы - их содержание абсолютно идентично.

Проверяем, то ли мы распокавали и туда ли.

# cd /opt/drweb
# ./drweb
Key file: /opt/drweb/drweb.key
Registration info:
0100003942
Evaluation Key (ID Anti-Virus Lab. Ltd, St.Petersburg) 
This is an EVALUATION version with limited functionality!
To get your registration key, call regional dealer.
Loading /var/drweb/bases/drwebase.vdb - Ok, virus records: 29405
Как видите, drweb работает в ограниченом режиме. То есть он может только находить вирусы, но не лечить их. Плюс он не смотрит внутрь архивов. Но для функциональности почтового шлюза этого вполне хватит. Но лучше купите лицензию - будет еще возможность проверять и лечить пользовательские файлы.

Тщательно прочитываем /opt/drweb/doc/postfix/readme.rus

И добавляем drweb в качестве фильтра к postfix:

# adduser drweb
# mkdir /var/spool/drweb
# chown drweb.drweb /var/spool/drweb
# chmod 770 /var/spool/drweb
Редактируем /etc/postfix/master.cf, как указано в документации к DrWeb:
  smtp          inet  n       -       n       -       50      smtpd -o content_filter=filter:dummy
и добавляем в конец следующее:
filter    unix  -       n       n       -       -       pipe                    
  flags=R user=drweb argv=/opt/drweb/drweb-postfix -f ${sender} -- ${recipient}
Затем тщательно читаем и редактируем /etc/drweb/drweb_postfix.conf

Лично я изменил следующие параметры:

SkipObject = pass
Я хочу пропускать те обьекты, которые drweb не переваривает.
MailbombObject = pass
Один из доменов, который обслуживается у меня, принадлежит дизайнерам и прочему художественному люду. Они очень обожают кидаться друг в друга архивами с картинками, которые сжаты очень сильно. DrWeb обычно считает такие письма за мэйлбомбы.
AdminMail = root@test.ru
Кто тут у нас администратор
FilterMail = DrWeb-DAEMON@ip-tel.ru
И от кого будет приходить почта с руганью и прочими сообщениями.

Далее везде:

SenderNotify = no
Не надо извещать посылателя писем - в 90% случаев это безполезно и лишь забивает почтовые каналы. Если отправитель известен адресату, то он сам напишет ему гневное письмо.

Перемещаем /etc/rc.d/drwebd в /etc/init.d/drwebd и с помощью ntsysv или chkconfig включаем автостарт drweb при запуске системы. Тем, кто ставил drweb через rpm, этого делать не надо. Просто посмотрите, все ли на месте.

Запрещаем всяким лазить куда не следует:

cd /var/drweb
chown -R drweb.drweb *
И запускаем демона drweb:

/etc/init.d/drwebd start Starting Dr. Web daemon...Key file: /opt/drweb/drwebd.key Registration info: 0100003943 Evaluation Key (ID Anti-Virus Lab. Ltd, St.Petersburg) This is an EVALUATION version with limited functionality! To get your registration key, call regional dealer. Loading /var/drweb/bases/drwebase.vdb - Ok, virus records: 29405 Daemon is installed, TCP socket created on port 3000

Работоспособность проверяем простым запуском /opt/drweb/drweb-postfix. Он должен запуститься без какого-либо писка и висеть, томительно выжидая и занимая консоль. А в логах должно появиться следующее:

Jun 29 13:41:08 multik drweb-postfix: load configuration from /etc/drweb/drweb_postfix.conf
Jun 29 13:41:08 multik drweb-postfix: Actions: infected=Q, suspicious=Q, skip=P, mailbomb=P, scanning_error=T, processing_error=R, empty_from=C, spam_filter=P
Jun 29 13:41:08 multik drweb-postfix: dwlib: read_conf(/etc/drweb/drweb_postfix.conf): successfully loaded
Jun 29 13:41:08 multik drweb-postfix: dwlib: startup: set timeout for whole session to 60000 milliseconds (-1 means infinite)
Jun 29 13:41:08 multik drweb-postfix: drweb-pipe: [2250] started ...
Все. Значит все работает. Перезапускайте postfix и drweb встанет на стражу вашей почты. Можете проверить, послав какой-нить вирус. Вы получите лишь уведомление о том, что вы выирус посылали.

Но просто защиты мне мало. Мне необходима еще и свежая защита. А для свежей защиты необходимо обновлять базы данных о вирусах. Для этого у DrWeb есть обновлялка, написанная на perl. Для нее нужен модуль String::CRC32. Делающие все правильно могут вспомнить что написано в man CPAN и с помощью install установить этот модуль. Мне оказалось проще и быстрее сделать все вручную:

Файл я взял с http://www.cpan.org/modules/by-module/String/String-CRC32-1.2.tar.gz

И установил:

# tar zxvf String-CRC32-1.2.tar.gz
# cd String-CRC32-1.2
# perl Makefile.PL
# make
# make test
# make install
Проверяем:
# cd /opt/drweb/update
# ./update.pl
update.pl должен сходить в инет на сайт DrWeb, забрать все обновления и перезапустить drwebd, если он есть. В логах или после запуска вы должны увидеть следующее:
Key file: /opt/drweb/drweb.key
Registration info:
0100003942
Evaluation Key (ID Anti-Virus Lab. Ltd, St.Petersburg) 
This is an EVALUATION version with limited functionality!
To get your registration key, call regional dealer.
Loading /var/drweb/bases/drwtoday.vdb - Ok, virus records: 173
Loading /var/drweb/bases/drw42807.vdb - Ok, virus records: 33
Loading /var/drweb/bases/drw42806.vdb - Ok, virus records: 57
Loading /var/drweb/bases/drw42805.vdb - Ok, virus records: 133
Loading /var/drweb/bases/drw42804.vdb - Ok, virus records: 123
Loading /var/drweb/bases/drw42803.vdb - Ok, virus records: 73
Loading /var/drweb/bases/drw42802.vdb - Ok, virus records: 143
Loading /var/drweb/bases/drw42801.vdb - Ok, virus records: 76
Loading /var/drweb/bases/drwebase.vdb - Ok, virus records: 29405
Видите, появились свежие обновления для drweb с новыми вирусами. Теперь со спокойной душой запихиваем вызов update.pl в crontab. У меня он вызывается каждую ночь. Одно НО: Необходимо переодически вручную отслеживать выход новых версий drweb. Потому что как только выйдет новый DrWeb, ваш автоматически перестанет получать новые вирусные дополнения.

И последний шаг - установка www-почты:

Я взял последнюю версию SquirellMail с http://www.squirrelmail.org/ и установил согласно прилагающимся инструкциям. Для ее работы необходим настроенный Apache c PHP. Как это делать я уже писал несколько раз. Да и в сети куча документов, посвященным этому вопросу. Установка простая, поэтому я тут останавливаться не буду. Не забудьте - для SquirellMail необходим запущенный imap демон - так что не оплошайте.

Для завершения нашей эпопеи осталось всего два шага:

  1. Проверьте, все ли запустится при запуске системы. Если есть возможность - перезагрузитесь и проверьте, запустился ли drweb, postfix, courier pop3 и/или imap c mysql. Отрабатывает ли update'р новые обновления и так далее. Просмотрите все конфигурационные файлы еще раз - не оставили ли вы где-нить ляпов или "соплей"? Для самостоятельной работы можете посмотреть на антиспамерные возможности drweb.
  2. проверьте, прикрыт ли MySQL и DrWeb от посторонних людей.
В общем все. Можете откинуться на спинку кресла и наблюдать, как работает почта.

Но для меня еще не все опять. Раз все равно стоит для пользователей Apache с PHP, то я написал простенькую WWW - утилитку для управления почтовыми пользователями. Для ее работы необходимо в MySQL создать такую таблицу:

mysql> desc admins;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| login    | varchar(20) | YES  |     | NULL    |       |
| password | varchar(20) | YES  |     | NULL    |       |
| rights   | int(6)      | YES  |     | NULL    |       |
+----------+-------------+------+-----+---------+-------+
3 rows in set (0.04 sec)
И руками занести туда значения вроде 'admin','password',0.

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

В конце этого howto приведет исходный код трех скриптов: auth.php global.php и index.php.txt.

Просто положите эти три файла в один каталог, доступный вам. Поправьте значения в global.php. Все, можете вызывать и пользоваться. Логин и пароль - те, которые вы вручную добавили в таблицу admins.

Ну а дальше я думаю вы поймете. Утилитка писалась "на коленке", поэтому я вполне понимаю, что можно написать лучше и красивее. Пишите.

Удачи !

А тем, кто до сюда дочитал - маленький бонус.

Если вы поглядите на это:

$ telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 mail.test.ru ESMTP Postfix
ehlo multik
250-mail.test.ru
250-PIPELINING
250-SIZE 10240000
250-ETRN
250-AUTH LOGIN PLAIN
250-AUTH=LOGIN PLAIN
250-XVERP
250 8BITMIME
К вашему большому сожалению вы не увидите строчек:
 
250-AUTH LOGIN PLAIN DIGEST-MD5 CRAM-MD5
250-AUTH=LOGIN PLAIN DIGEST-MD5 CRAM-MD5
Либо увидите эти строчки, но почему-то нормальные почтовые клиенты (к примеру, TheBat или stuphead) не могут авторизоваться для отправки почты. Только для отправки! В чем же дело ?

Дело в одной маленькой библиотеке, называемой Cyrus-SASL. К сожалению, ее писали .... люди, которые писали ее неправильно. Метод sasldb в ней важнее всех. Внимание:

Если /etc/sasldb не пустой (смотреть вывод sasldblistusers) то в выводе postfix появляются строчки про DIGEST-MD5 и CRAM-MD5. Если пустой - то не появляются.

Если в sasldb есть хоть одна запись, то аунтефикация с участием методов LOGIN, DIGEST-MD5 и CRAM-MD5 идет через sasldb, НЕВЗИРАЯ на то, что написано после pwcheck_method. Поэтому, если у вас есть много мигрирующие пользователи с нормальными почтовыми клиентами (Outlook и Mozilla - не подходят - они оба используют только метод PLAIN), то для отправки почты с таких клиентов их надо заводить вручную, используюя команду аналогичную этой:

saslpasswd -c -u `postconf -h myhostname` username
Вот такой вот SASL. Правда, обещают, что в v2 будет все по честному, но пока v2 не выбралась из бета-состояния, да и postfix в стабильных версиях не поддерживает Cyrus-SASL v2.

Courier-IMAP использует SASL по другому, поэтому с ним все в порядке.

Вот теперь точно все. Удачи!

(c) 2002 Вячеслав Калошин multik@multik.ru

auth.php

<?
// simple function to authorize users via http
function authenticate_user()
 { 
  Header("HTTP/1.0 401 Unauthorized"); 
  Header("WWW-Authenticate: Basic realm=\"Member Area\""); 
    echo "sorry - you no allowed to this site";
  exit; 
 } 

function auth_user()
{
 global $HTTP_SERVER_VARS, $db_user, $db_pass, $db_host, $db_name;
 if(!isset($HTTP_SERVER_VARS["PHP_AUTH_USER"]))
 {
  authenticate_user();
 } else {
 $usr=mysql_escape_string($HTTP_SERVER_VARS["PHP_AUTH_USER"]);
 $pww=mysql_escape_string($HTTP_SERVER_VARS["PHP_AUTH_PW"]);
 $link=mysql_char_connect($db_host,$db_user,$db_pass);
 mysql_select_db ($db_name,$link);
 $query="select rights from admins where login='".$usr."' and password='".$pww."'";
 $res=mysql_query($query,$link);
 $col=mysql_num_rows($res);
 $result=mysql_fetch_row($res);
 if($col==0)
  {
  authenticate_user();
  exit;
  }
 }
mysql_close($link);
return $result[0];
}

?>

global.php

<?
// user names, etc
$db_user="postfix";
$db_pass="password";
$db_host="localhost";
$db_name="mail";

// administrative email for all new created domains
$main_email="root";

function get_head($title)
{
echo "<html><head><title>$title</title></head><body>\n";
}

function get_foot()
{
echo "</body></html>";
}

?>

index.php

Примечание: скрипт был слегка изменен по сравнению с оригинальной версией (в целях повышения удобочитаемости, длинные строки в echo были разбиты на более короткие)
<?
include("global.php");
include("auth.php");

$lev=auth_user();

get_head("Administration");

echo "<table><tr><td valign=top bgcolor=#cccccc>";
echo "<table><tr><td><b><small>Choice</small></b></td><tr>";
echo "<tr><td><a href=\"index.php?mode=1\">Domains</a></td></tr>";
echo "<tr><td><a href=\"index.php?mode=3\">Non-domain Aliases</a></td></tr>";
echo "</table>";
echo "</td><td>";

$alias=$_POST["alias"];
if(strlen($alias)>0) // add or edit aliases
    {
    $alias=mysql_escape_string($_POST["alias"]);
    $rcpt=mysql_escape_string($_POST["rcpt"]);
    $domain=mysql_escape_string($_POST["domain"]);
    $link=mysql_char_connect($db_host,$db_user, $db_pass);
    mysql_select_db($db_name,$link);
    $query="delete from remote_aliases where alias='".$alias."'";
    $res=mysql_query($query);
    $query="insert into remote_aliases(alias,rcpt) values('".$alias."', '".$rcpt."')";
    $res=mysql_query($query);
    mysql_close($link);
    if(!isset($_POST["local"]))
    {
    echo "Done. <a href=\"index.php?mode=11&domain=$domain\">Look</a>";
    }
    else
    {
    echo "Done. <a href=\"index.php?mode=3\">Look</a>";
    }
}
$uid=$_POST["uid"];
if(strlen($uid)>0) //update info about user
    {
    $email=mysql_escape_string($_POST["email"]);
    $spool=mysql_escape_string($_POST["spool"]);
    $pass=mysql_escape_string($_POST["pass"]);
    $info=mysql_escape_string($_POST["info"]);
    
    $link=mysql_char_connect($db_host,$db_user, $db_pass);
    mysql_select_db($db_name,$link);
    $query="update aliases set alias='".$email."', maildir='".$spool."', password='".$pass."', info='".$info."' where id=$uid";
    $res=mysql_query($query);
    mysql_close($link);
    echo "Updated";
    }


$uid=$_POST["id"];
if(strlen($uid)>0) //add info about user
    {
    $domain=mysql_escape_string($_POST["domain"]);
    $gid=mysql_escape_string($_POST["gid"]);
    $email=mysql_escape_string($_POST["email"]);
    $spool=mysql_escape_string($_POST["spool"]);
    $pass=mysql_escape_string($_POST["pass"]);
    $info=mysql_escape_string($_POST["info"]);
    
    $link=mysql_char_connect($db_host,$db_user, $db_pass);
    mysql_select_db($db_name,$link);
    $query="insert into aliases values('".$uid."','".$gid."','".$email."','".$spool."','".$pass."','".$info."')";
    $res=mysql_query($query);
    mysql_close($link);
    $message="Welcome to account ".$email.". If you receive this message, all settings are ok.";
    mail($email,"Test Message", $message, "From: root@$domain\r\n");
    echo "Inserted <a href=\"index.php?mode=11&domain=$domain\">Next</a>";
    }


$mode=$_GET["mode"];
if($mode==1) // show domains
    {
    $link=mysql_char_connect($db_host,$db_user, $db_pass);
    mysql_select_db($db_name,$link);
    $res=mysql_query("select * from transport order by domain");
    echo "<table bgcolor=#cccccc><tr><td><b><small>".
    "Domain</small></b></td><tr>";
    while($i=mysql_fetch_row($res))
    {
    echo "<tr><td bgcolor=#ffffff><a href=\"index.php?mode=11&domain=$i[0]\"".
    ">$i[0]</a></td></tr>";
    }
    
    echo "<tr><td bgcolor=#ff9999><small><b><".
    "a href=\"index.php?mode=12\">Add Domain</a></b></small></td></tr>";
    echo "</table>";
    mysql_close($link);
    }


if($mode==11) // show users in domain
    {
    $domain=$_GET["domain"];
    $link=mysql_char_connect($db_host,$db_user,$db_pass);
    mysql_select_db($db_name,$link);
    $res=mysql_query("select * from aliases where alias like '%@".$domain."' order by id");
    echo "<table bgcolor=#cccccc><tr><td><small>".
    "<b> UID </b></small></td><td><b><small>".
    " GID </small><b></td><td><b><small> ".
    "Login </small></b></td><td><b><small> ".
    "MailBox </small></b></td><td><b><small> ".
    "Info </small></b></td></tr>";
    while($i=mysql_fetch_row($res))
	{
	echo "<tr><td bgcolor=#ffffff> $i[0] </td><td bgcolor=#ffffff>".
	" $i[1] </td><td bgcolor=#ffffff><a href=\"index.php?mode=21&uid=$i[0]\">".
	" $i[2] </a></td><td bgcolor=#ffffff> $i[3] </td>".
	"<td bgcolor=#ffffff> $i[5] </td></tr>";
	}
    echo "<tr bgcolor=#ff9999><td colspan=5><b><small><".
    "a href=\"index.php?mode=23&domain=$domain\">Add User</a></small></b></td></tr>";
    echo "<tr><td colspan=5><small><b> Aliases</b></small></td></tr>";	
    $res=mysql_query("select * from remote_aliases where alias like '%@".$domain."'");	
    while($i=mysql_fetch_row($res))
	{
	echo "<tr bgcolor=#ffcccc><td colspan=3>$i[0]</a></td><td>$i[1]</td>".
	"<td> <a href=\"index.php?mode=32&alias=$i[0]&domain=$domain\">Edit</a> ".
	"<a href=\"index.php?mode=33&alias=$i[0]&domain=$domain\">Delete</a></td></tr>\n";
	}
    echo "<tr bgcolor=#ff9999><td colspan=5><b><small>".
    "<a href=\"index.php?mode=31&domain=$domain\">Add Alias</a></small></b></td></tr>";

    echo "</table>";
    mysql_close($link);
    }


if($mode==12) //add domain
    {
    echo "<form action=index.php method=get>";
    echo "<input type=hidden name=mode value=13>";
    echo "<input type=text name=domain>";
    echo "<input type=submit value=\"Add\">";
    echo "</form>";
    }
if($mode==13) // final add domain
    {
    $domain=mysql_escape_string($_GET["domain"]);
    $link=mysql_char_connect($db_host,$db_user,$db_pass);
    mysql_select_db($db_name,$link);
    $res=mysql_query("insert into transport values('".$domain."','virtual:')");
    $res=mysql_query("insert into remote_aliases values('root@".$domain."','".$main_email."')");
    $res=mysql_query("insert into remote_aliases values('postmaster@".$domain."','".$main_email."')");
    $res=mysql_query("insert into remote_aliases values('abuse@".$domain."','".$main_email."')");
    $res=mysql_query("insert into remote_aliases values('MAILER-DAEMON@".$domain."','".$main_email."')");
    mysql_close($link);
    }
    
if($mode==21) // edit user info
    {
    $uid=$_GET["uid"];
    $link=mysql_char_connect($db_host,$db_user,$db_pass);
    mysql_select_db($db_name,$link);
    $res=mysql_query("select * from aliases where id=".$uid);
    $i=mysql_fetch_row($res);
    echo "<form action=index.php method=post>\n";
    echo "Uid: $i[0]<br><input type=hidden name=uid value=\"$i[0]\">\n";
    echo "Gid: $i[1]<br>\n";
    echo "Email: <input type=text name=email value=\"$i[2]\"><br>\n";
    echo "Spool: <input type=text name=spool value=\"$i[3]\" size=40><br>\n";
    echo "Password: <input type=text name=pass value=\"$i[4]\"><br>\n";
    echo "Info: <input type=text name=info value=\"$i[5]\"><br>\n";
    echo "<input type=submit value=\"Change\"> <a href=\"index.php?mode=22&uid=$i[0]\">\
    <font color=red><b>Erase User</b></font></a>";
    echo "</form>";
    mysql_close($link);
    }
if($mode==22) // erase user
    {
    $uid=$_GET["uid"];
    $link=mysql_char_connect($db_host,$db_user,$db_pass);
    mysql_select_db($db_name,$link);
    $res=mysql_query("delete from aliases where id=".$uid);
    echo "User with id=$uid was deleted.";
    mysql_close($link);
    }
    
if($mode==23) // create user
    {
    $domain=$_GET["domain"];
    $link=mysql_char_connect($db_host,$db_user,$db_pass);
    mysql_select_db($db_name,$link);
    $res=mysql_query("select max(id) from aliases");
    $i=mysql_fetch_row($res);
    $maxid=$i[0]+1;
    $res=mysql_query("select max(gid) from aliases");
    $i=mysql_fetch_row($res);
    $maxgid=$i[0];
    echo "<form action=index.php method=post>\n";
    echo "<input type=hidden name=domain value=\"$domain\">";
    echo "Uid: <input type=text name=id value=\"$maxid\"><br>\n";
    echo "Gid: <input type=text name=gid value=\"$maxgid\"><br>\n";
    echo "Email: <input type=text name=email value=\"USERNAME@$domain\"><br>\n";
    echo "Spool: <input type=text name=spool value=\"/var/spool/vmail/".$domain."/USERNAME/\" size=40><br>\n";
    echo "Password: <input type=text name=pass value=\"\"><br>\n";
    echo "Info: <input type=text name=info value=\"\"><br>\n";
    echo "<input type=submit value=\"Add\">";
    echo "</form>";
    mysql_close($link);
    }

if($mode==3) // show all aliases
    {
    $link=mysql_char_connect($db_host,$db_user,$db_pass);
    mysql_select_db($db_name,$link);
    echo "<table bgcolor=#cccccc>";
    echo "<tr><td colspan=5><small><b> Aliases</b></small></td></tr>";	
    $res=mysql_query("select * from remote_aliases where alias not like '%@%' order by alias");	
    while($i=mysql_fetch_row($res))
	{
	echo "<tr bgcolor=#ffcccc><td colspan=3>$i[0]</a></td><td>$i[1]</td>".
	"<td> <a href=\"index.php?mode=32&alias=$i[0]&local=1\">Edit</a> ".
	"<a href=\"index.php?mode=33&alias=$i[0]&local=1\">Delete</a></td></tr>\n";
	}
    echo "<tr bgcolor=#ff9999><td colspan=5><b><small>".
    "<a href=\"index.php?mode=31&local=1\">Add Alias</a></small></b></td></tr>";

    echo "</table>";
    mysql_close($link);
    }


if($mode==31) // add alias first stage
   {
    $domain=$_GET["domain"];
   echo "<form action=index.php method=post>\n";
   echo "Alias: <input type=text name=alias value=\"ALIAS@$domain\"><br>";
   echo "Rcpt: <textarea rows=6 cols=30 wrap=virtual name=rcpt>RCPT</textarea><br>";
   if(isset($_GET["local"]))
   {
   echo "<input type=hidden name=local value=\"1\">";
   }
   echo "<input type=hidden name=domain value=\"".$domain."\">";
   echo "<input type=submit value=\"Add\">";
   echo "</form>";
   }

if($mode==32) // edit alias first stage
   {
    $domain=$_GET["domain"];
    $alias=mysql_escape_string($_GET["alias"]);
    $link=mysql_char_connect($db_host,$db_user,$db_pass);
    mysql_select_db($db_name,$link);
    $res=mysql_query("select rcpt from remote_aliases where alias='".$alias."'");
    $rcpt_o=mysql_fetch_row($res);
    $rcpt=$rcpt_o[0];
    mysql_close($link);
   echo "<form action=index.php method=post>\n";
   echo "Alias: $alias<br>";
   echo "<input type=hidden name=alias value=\"".$alias."\">";
   echo "Rcpt: <textarea rows=6 cols=30 wrap=virtual name=rcpt>".$rcpt."</textarea><br>";
   if(isset($_GET["local"]))
   {
   echo "<input type=hidden name=local value=\"1\">";
   }
   echo "<input type=hidden name=domain value=\"".$domain."\">";
   echo "<input type=submit value=\"Save\">";
   echo "</form>";
   }

if($mode==33) // remove alias
   {
    $alias=mysql_escape_string($_GET["alias"]);
    $domain=mysql_escape_string($_GET["domain"]);
    $loc=mysql_escape_string($_GET["local"]);
    $link=mysql_char_connect($db_host,$db_user,$db_pass);
    mysql_select_db($db_name,$link);
    $res=mysql_query("delete from remote_aliases where alias='".$alias."'");
    mysql_close($link);
    if(!isset($_GET["local"]))
    {
    echo "Done. <a href=\"index.php?mode=11&domain=$domain\">Look</a>";
    }
    else
    {
    echo "Done. <a href=\"index.php?mode=3\">Look</a>";
    }
    }

?>
</td></tr></table>
<?
get_foot();
?>

[Источник: linux-ve.net]

[ опубликовано 13/06/2003 ]

Вячеслав Калошин (multik@multik.ru) - ISP-MAIL-HOWTO. v1.2.   Версия для печати