На одном из почтовых серверов появилась необходимость отшибать письма (до приема содержимого письма) для пользователей, у которых исчерпан отведенный им лимит дискового пространства. В качестве MTA на сервере используется Exim. Как известно стандартными средствами данного MTA такое не сделать, но в силу гибкости Exim,а можно выйти из положения написав скрипт, который будет проверять лимиты пользователей и посредством сокетов взаимодействовать с ним (см. в доках readsocket). Что и было реализовано в скрипте, приведенном немного ниже. Скрипт запускается в фоне и обслуживает запросы Exim,а на проверку превышения квоты пользователем. На входе скрипт имеет полный почтовый адрес пользователя, а на выход выдает одну из заранее определенных констант:
Данный скрипт написан для почтовой системы, описанной в данной статье. Думаю, переделать под какую-либо другую конфигурацию не составит особого труда. Для автоматического запуска во время старта системы можно добавить его в автозагрузку, например, создать файл в /usr/local/etc/rc.d с таким содержимым:
Правило для Exim,а будет выглядить так:
Поскольку скрипт не может узнать размер принимаемого сообщения, то в MTA или MDA необходимо отключить проверку квот, чтобы допустить переполнение почтового ящика. В остальном скрипт довольно прост для понимания, поэтому я не буду подробно расписывать каждую строчку. Если есть вопросы по работе скрипта, то задавайте их в комменты или в форум.
- QUOTA_OK — квота не исчерпана;
- QUOTA_EXCEEDED — квота исчерпана;
- QUOTA_UNKNOWN — по какой-либо причине не удалось установить состояние квоты;
- QUOTA_ERROR — произошла ошибка на стороне скрипта.
- #!/usr/bin/env perl
- #
- # Для работы требуется модуль DBD::Pg
- #
- use strict;
- use warnings;
- use Socket;
- use POSIX;
- use DBI;
- my $work_dir = "/path/to/workdir";
- my $sock_name = "/tmp/ckmq.sock";
- my $log_file = "/path/to/log/ckmq_daemon.log";
- my $pid_file = "/var/run/ckmq.pid";
- my $SOCK_FD = -1;
- my $srv_status = 1;
- my $sock_addr = sockaddr_un($sock_name);
- my $max_forks = 15;
- my $fork_count = 0;
- my $pid = -1;
- my $dbh = -1;
- my $db_host = '127.0.0.1';
- my $db_user = 'dbuser';
- my $db_pass = 'dbpass';
- my $db_name = 'dbname';
- #--------------------------------------------------------------------
- # Переключаемся в режим демона
- $SIG{INT} = \&sig_int;
- $SIG{TERM} = \&sig_int;
- $SIG{CHLD} = \&sig_chld;
- ¬ice_log("check quota daemon started (pid: $$)");
- while($srv_status){
- my $CLIENT_FD = -1;
- if ($fork_count >= $max_forks){
- ¬ice_log('fork_max exceeded');
- next;
- }
- $fork_count++;
- $SIG{INT} = 'IGNORE';
- $SIG{TERM} = 'DEFAULT';
- $SIG{CHLD} = 'IGNORE';
- &client_work($CLIENT_FD);
- }
- }
- if ($SOCK_FD != -1){
- }
- ¬ice_log('check quota daemon stopped');
- #--------------------------------------------------------------------
- sub error_log {
- if ($SOCK_FD != -1){
- }
- }
- sub notice_log {
- }
- sub sig_int {
- $srv_status = 0;
- $SOCK_FD = -1;
- }
- sub sig_chld {
- my $pid = -1;
- $fork_count--;
- }
- }
- sub client_work {
- my $buffer = '';
- my $user_name = '';
- my @email = ();
- my ($quota_size, $mail_root) = (0,0);
- &db_connect();
- #print("Name: ", $email[0], "\nDomain: ", $email[1], "\n");
- }
- ($quota_size, $mail_root) = &db_get_user_quota($email[0], $email[1]);
- $quota_size = $quota_size * 1024;
- $buffer = "$mail_root/$email[1]/$email[0]/Maildir/maildirsize";
- my $total_size = 0;
- $buffer = <MDSF>;
- while (<MDSF>){
- $total_size += $tmp;
- }
- #¬ice_log("Quota size: $quota_size");
- #¬ice_log("Total maildir size: $total_size");
- send(CLIENT_FD, ($quota_size == 0) || ($total_size < $quota_size) ? 'QUOTA_OK' : 'QUOTA_EXCEEDED', 0);
- } else {
- }
- &db_disconnect();
- }
- #--------------------------------------------------------------------
- sub db_connect {
- }
- sub db_disconnect {
- $dbh->disconnect();
- }
- # Функция возвращает массив из двух элементов: путь до почтового ящика и размер квоты
- sub db_get_user_data {
- my ($uname, $dname) = @_;
- my $stm = -1;
- my @row = ();
- @row = $dbh->selectrow_array('SELECT "users_tb"."quota", "users_tb"."homedir" FROM "users_tb"
- INNER JOIN "domains_tb" ON ("users_tb"."domain_id" = "domains_tb"."id")
- WHERE "username" = $$' . $uname . '$$ AND
- "domains_tb"."domainname" = $$' . $dname . '$$ LIMIT 1;');
- }
- # Функция преобразовывает алиас в реальное имя пользователя
- sub db_get_user_from_alias {
- my ($uname, $dname) = @_;
- my $stm = -1;
- my @row = ();
- @row = $dbh->selectrow_array('SELECT "aliases_tb"."mailaddr" FROM "aliases_tb"
- INNER JOIN "domains_tb" ON ("aliases_tb"."domain_id" = "domains_tb"."id")
- WHERE "aliases_tb"."aliasname" = $$'.$uname.'$$ AND
- "domains_tb"."domainname" = $$'.$dname.'$$ AND
- "aliases_tb"."active" = $$true$$ AND "domains_tb"."active" = $$true$$');
- }
- #!/bin/sh
- # PROVIDE: ckmq
- # BEFORE: exim
- # KEYWORD: shutdown
- #
- # Add the following lines to /etc/rc.conf to enable Check Mail Quota Daemon:
- # ckmq_enable (bool): Set to "NO" by default.
- # Set it to "YES" to enable ckmq.
- #
- . /etc/rc.subr
- name="ckmq"
- rcvar=ckmq_enable
- load_rc_config ${name}
- : ${ckmq_enable="NO"}
- pidfile="/var/run/${name}.pid"
- command="/path/to/script"
- run_rc_command "$1"
- deny message = Quota size exceeded
- domains = +local_domains
- condition = ${if eq{QUOTA_EXCEEDED}{${readsocket{/tmp/ckmq.sock}{$local_part@$domain}{3s}{}{false}}}{yes}{no}}