Индивидуальное квотирование виртуальных почтовых ящиков для Postfix



Оригинал: http://vitaliy.tdauto.ru

Postfix 2. xx индивидуальное квотирование виртуальных почтовых ящиков
Патч Postfix 2.xx + virtual + maildir + MySQL/PostgreSQL/..
(проверен postfix 2.0.11 - 2.0.16)
© 2003 by -=VD= http://vitaliy.tdauto.ru

Копию патча postfix2.0pvq.gz можно скачать здесь:
ftp://ftp.opennet.ru/pub/net/mail/postfix_quota/


Задача:
необходимо выставлять виртуальным пользователям Postfix "живущим", к
примеру, в MySQL/PostgreSQL, индивидуальную квоту. Те патчи что я
нашел на http://www.postfix.org все же меня не совсем устроили, т.к.
захотелось решать все это на "лету" не принимая сообщения(сэкономим
трафик), но при этом и отправитель и получатель должны уведомляться об
этом "скорбном" факте.


Для реализации задачи использовались:
VMware + FreeBSD 4.8 + KDE + среда разработки KDevelop + оболочка
отладчика KDbg + gdb 5.22 (gdb не меньше 5.22 т.к. все что младше, с
KDbg у меня не работало).

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

- изменения котрым подверглись исходники в конце документа
- сам patch postfix2.0pvq.gz к сожалению утерян, сайт автора недоступен
(проверен Postfix 2.0.11 - Postfix 2.0.16)

BUGFIXES:

19.11.03 -> Устранена ошибка virtual_quota_user_message = пустое
значение -> вызывало "fatal: bad string length (0 < 1):"
19.11.03 -> Устранена проблема при совместной установке с патчем
ssl/tls -> они не любили друг друга(помирил)
19.11.03 -> Устранена ошибка вызывавшая возможное не срабатывание
процедуры проверки квоты -> пользователь с локальным именем мог не
квотироваться.

Обсуждение всех возникавших проблем здесь:
http://www.opennet.ru/openforum/vsluhforumID3/2827.html
Отдельное спасибо HFSC помогшему устранить некоторые ошибки в логике
кода.

Что в результате получилось:
После патча в main.cf, примерно как ниже, можно добавить 4 новых
параметра:

virtual_quota_user_message_limit = 2048
virtual_quota_user_bounce = yes
virtual_quota_user_message = /../../../ваш _ файл
virtual_mailbox_limit_maps = /../../../ваш _ файл

Из них взаимосвязаны только между собой:

virtual_quota_user_bounce = yes ( если = no 2 нижних просто неактуальны )
virtual_quota_user_message = /../../../ваш _ файл
virtual_quota_user_message_limit = 2048
virtual_mailbox_limit_maps = /../../../ваш _ файл включает сам механизм
квотирования, однако, при этом должны быть
определены еще два postfix параметра:
virtual_mailbox_maps = /../../../ваш_файл
virtual_mailbox_base = /../../../ваш_файл

ВНИМАНИЕ!!! Патч, на сегодняшний день, проверяет объем директори
пользователя от имени дочернего процесса postfix(master) -> smtpd,
поэтому права на maildir проверяемого пользователя должны совпадать с
правами запуска smtpd. Упрощенно, если владельца virtual_mailbox_base
определить отличного от mail_owner = ваше_значение, для патча не
будет возможности выяснить текущий объем maildir пользователя и
создать ему соответствующее уведомление. На работоспособности postfix
в целом, это конечно вообще никак не отразится, однако квота
отрабатывать не будет.


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

Ниже, моя врезка в main.cf:

virtual_mailbox_base = /var/mail/virtual
virtual_mailbox_maps = mysql:/usr/local/etc/postfix/sql/users.cf
# Virtual quota
virtual_mailbox_limit_maps =
mysql:/usr/local/etc/postfix/sql/mail_limit.cf
virtual_quota_user_bounce = yes
virtual_quota_user_message = /usr/local/etc/postfix/sql/message.cf
virtual_quota_user_message_limit = 2048


- virtual_mailbox_limit_maps по умолчанию = ""
Путь к стандартному, вида postfix, файлу ..._maps. Опделяет
дополнительное поле квоты.

- virtual_quota_user_bounce по умолчанию = no
Отсылать/нет служебное сообщение пользователю о превышении квоты.
Изначально воспроизводится из шаблона, после чего в него добавляются
строчки вида:
10.10.03 | mail from: aaa@bbb.cc

- virtual_quota_user_message по умолчанию = ""
Путь к файлу шаблона служебного сообщения.

- virtual_quota_user_message_limit по умолчанию = 1024
Максимальный размер(байт) файла служебного сообщения.

Реакция postfix на индивидуальную квоту, для отправителя/пользователья:

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

"4 хх /5 хх Sorry, the user <aaa@mydomain.com> has overdrawn diskspace
quota, please try again later."

(Захотите, переопределите это сообщение в исходниках.)

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

В самом пиковом варианте, после превышения квоты, пользователь
максимально сможет получить в свою maildir объем: (его квота - 1 байт)
+ (максимальный объем одного принимаемого сообщения), это связано с
тем, что объем входящей почты в начале сеанса НЕ ВСЕГДА число
определенное.


Установка патча и настройка дополнительных параметров на примере
postfix 2.0.16 + MySQL + Ваши_модули:

Установку и настройку postfix для работы с виртуальными пользователями
я здесь не описываю, т.к. этому уже посвящено несколько довольно
подробных описаний, вот ссылки на некотрые из них:
http://raven.elk.ru/unix/how-to/postfix2+cyrus-sasl2+kav+spamassassin+
courier-imap+tls+mysql+FreeBSD4/postfix2+cyrus-sasl2+kav+spamassassin+
courier-imap+tls+mysql+FreeBSD4.html
http://www.opennet.ru/base/net/postgresql_postfix.txt.html

Одним словом, я предполагаю что postfix у Вас уже установлен и
настроен и Вы так же имеете представление зачем Вам этот патч :)
Скачиваем отсюда патч postfix2.0pvq.gz распаковываем,
cp /куда_вы_распаковали/postfix2.0pvq /там_где_исходник/postfix 2.0.16
cd /там_где_исходник/postfix 2.0.16
patch < postfix2.0pvq

Патч может отработать в зависимости от версии postfix, c offset в 7
позиций (это конечно если патч наложен на "чистый" исходник).
Пересоберем postfix с Вашими параметрами типа SASL MySQL и т.д.
Собрался, значит, все готово.

Дальше можно даже и не устанавливать его с помощью make install, т.е.
если Вы пересобрали с параметрами что и в Вашем рабочем postfix, то
изменилось всего 3 бинарника:
./src/smtpd/smtpd
./src/postconf/postconf
./src/cleanup/cleanup

Ну а в прочем, make install хорошо сам во всем разберется (лишнего не
удалит).

В общем случае, осталось только отредактировать main.cf на предмет
нововведений и все, однако, прежде чем его отредактировать,
разбираемся на предмет текущего способа проверки пользователей и
maildir.

Тут дело в том, что патч отработает только при способе проверки(не
путать с транспортом) определенном в postfix как virtual, плюс при
условии, что у пользователей maildir, а не mailbox. На все другие
варианты конфигурации и способы доставки этот патч влияния не
оказывает.

Разбираемся как обстоят дела с текущим способом проверки:

telnet dew.tdauto.ru 25
220 dew.tdauto.ru ESMTP Postfix (2.0.16)
HELO vitaliy.tdauto.ru
250 dew.tdauto.ru
MAIL FROM: <any@aol.com>
250 Ok
RCPT TO: <nomail@tdauto.ru>
550 <nomail@tdauto.ru>: User unknown in virtual mailbox table

Я взял заведомо несуществующего у меня пользователя и ключевой момент
тут как раз "... in virtual mailbox table " т.е. " virtual mailbox
table " , это то что нам нужно, соответственно этот патч отработает.
Другими словами, в качестве карты maildir пользователей, должен быть
определен:

virtual_mailbox_maps = /.../ваш _ файл

А вот иные варианты ответов, в которых патч не сработает:

550 <nomail@tdauto.ru>: User unknown in local recipient table
550 <nomail@tdauto.ru>: User unknown in relay recipient table

А вообще, по "старшинству", адрес postfix проверяет так:
Сперва смотрит просто на наличие как такового в:

1. recipient_canonical_maps
2. canonical_maps
3. virtual_alias_maps

Если адрес среди них есть, то postfix его принимает с 250 ОК.
Если в тех что выше, ничего не найдено, проверяются с "пристрастием" и
в жесткой зависимости от типа проверки:

4. local_recipient_maps
5. virtual_mailbox_maps
6. relay_recipient_maps

Тут все наоборот, если способ проверки определен, а адреса нет,
postfix ничего не принимает и дает отбой 4хх/5хх....

На сегодня этот патч отрабатывает virtual_alias_maps и virtual_mailbox_maps,
остальные maps нет и смысла их включать в квотирование пока не вижу.

Идем дальше - maildir:

Для postfix, признак того, что у пользователей maildir - '/' в конце
пути пользовательской почтовой директории.

Кроме maildir, в самом конфиге, еще конечно должен быть:
virtual_mailbox_base = /вашему/пути/к/maildir

В общем случае:
Квота сработает если:

1. virtual_mailbox_maps = /.../ваш _ файл
2. virtual_mailbox_base = /вашему/пути/к/maildir
3. virtual_mailbox_limit_maps = /../../../ваш _ файл
4. У пользователей путь к почте ../../vasa/
5. Поле квоты в таблице не пустое.

Не сработает если:

1. local_recipient_maps = /.../ваш _ файл
2. Или у пользователей путь к почте ../../vasa
3. Или virtual_mailbox_limit_maps закомментарено
4. Или поле квоты в таблице пустое

Или этот адрес есть в:

5. recipient_canonical_maps = /.../ваш _ файл
6 . canonical_maps = /.../ваш _ файл

Редакируем main.cf
Добавляем туда примерно следующее(с Вашими путями конечно):

# Virtual quota
virtual_mailbox_limit_maps =
mysql:/usr/local/etc/postfix/sql/mail_limit.cf
virtual_quota_user_bounce = yes
virtual_quota_user_message = /usr/local/etc/postfix/sql/message.cf
virtual_quota_user_message_limit = 2048

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

Заносим в size максимальный объем maildir юзера (чило в байтах).
Соответственно создаем новый конфиг файл, для нашего нового поля size
(в скобках мои значения)
/usr/local/etc/postfix/sql/mail_limit.cf

user = ваш (***)
password = ваш (***)
dbname = ваша (mail)
table = ваша (users)
select_field = ваш (size)
where_field = ваш (login)
additional_conditions = ваши (and expired = '0')
hosts = ваш (localhost)

Настраиваем шаблон сообщения message.cf для пользователя.
Берем мой здесь и правим как Вам нужно, или создаем файл из шаблона
что ниже:
/usr/local/etc/postfix/sql/message.cf

----------------------------------------------
#
# Шаблон сообщения для пользователя превысившего дисковую квоту.
# Все строчки кроме тех что начинаются с # - значимые.
#
Return-Path: <postmaster@tdauto.ru>
X-Original-To: _RCPT_TO_
Delivered-To: _RCPT_TO_
From: <postmaster@tdauto.ru>
To: <_RCPT_TO_>
Date: _MSG_TIME_
Content-Type: text/plain; charset=koi8-r
Content-Transfer-Encoding: 8bit
Subject: ВНИМАНИЕ ! Прием Вашей почты временно приостановлен.
На данный момент, выделенное вам дисковое пространство
полностью израсходовано Вашей входящей почтой.
Прием на Ваш адрес временно прекращен и будет автоматически
возобновлен, после того как Вы заберете накопившуюся почту.

Допустимый объем Вашей директории: _USER_QUOTA_ байт
Текущий объем Вашей директории: _UDIR_SIZE_ байт

За это время Вам отсылали сообщения и были уведомлены
о временной недоступности Вашего адреса:

_MAIL_FROM_

С уважением:
postmaster@tdauto.ru
----------------------------------------------

В результате отработки шаблона пользователь получит на свой ящик
следующее:
----------------------------------------------
Return-Path: <postmaster@tdauto.ru>
X-Original-To: vitaliy@tdauto.ru
Delivered-To: vitaliy@tdauto.ru
From: <postmaster@tdauto.ru>
To: <vitaliy@tdauto.ru>
Date: Mon, 10 Nov 2003 13:25:43 +0300 (MSK)
Content-Type: text/plain; charset=koi8-r
Content-Transfer-Encoding: 8bit
Subject: ВНИМАНИЕ ! Прием Вашей почты временно приостановлен.

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

Допустимый объем Вашей директории: 2096000 байт
Текущий объем Вашей директории: 2099124 байт

За это время Вам отсылали сообщения и были уведомлены
о временной недоступности Вашего адреса:

07.10.03 13:25:57 (MSK) | mail from: any@yahoo.com
08.10.03 13:30:04 (MSK) | mail from: any@yahoo.com
09.10.03 15:00:45 (MSK) | mail from: any@mail.ru
10.11.03 15:21:37 (MSK) | mail from: any@any.ru
10.11.03 22:26:41 (MSK) | mail from: sssde@ttt.df

С уважением:
postmaster@tdauto.ru
----------------------------------------------

Конфиг готов, проверяем что вышло, предварительно режем у адреса квоту
до 1 байта и больше 1 байта ему в maildir положим:

telnet dew.tdauto.ru 25
220 dew.tdauto.ru ESMTP Postfix (2.0.16)
HELO vitaliy.tdauto.ru
250 dew.tdauto.ru
MAIL FROM: <any@aol.com>
250 Ok
RCPT TO: <vitaliy@tdauto.ru>
550 Sorry, the user <vitaliy@tdauto.ru> has overdrawn diskspace quota,
please try again later.
RCPT TO: <vitaliy@tdauto.ru>
550 Sorry, the user <vitaliy@tdauto.ru> has overdrawn diskspace quota,
please try again later.
RCPT TO: <vitaliy@tdauto.ru>
550 Sorry, the user <vitaliy@tdauto.ru> has overdrawn diskspace quota,
please try again later.

Получили "550 Sorry..." следовательно отбой по квоте
работает(намеренно отбой 3 раза, дабы проверить шаблон),
проверяем ящик vitaliy@tdauto.ru - там должно быть уведомление из
нашего шаблона с соответствующими цифрами, локальным временем и тремя:

xx.xx.xx xx:xx:xx ( ХХХ ) | mail from: any@aol.com

Как отработает квота по алиасам:

Квота работает только с виртуальными алиасами т.е. с теми что
находятся в virtual_alias_maps = /../ваш_файл
К примеру в virtual_alias_maps есть адрес www@tdauto.ru и на нем,
после всех алиасных переопределений, реально найдется 3 пользователя:
user1@tdauto.ru
user2@tdauto.ru
user3@tdauto.ru
Если все 3 пользователя существуют в virtual_mailbox_maps, то их
проверят всех.

Если кого-то там нет, следовательно, квота на него не распространяется
(почта для него принимается).

Если у кого нибудь из тех, кто есть в virtual_mailbox_maps квота
превышена, то он почту не получит, а получит только служебное
сообщение (все остальные получат почту).

Если все 3 пользователя есть в virtual_mailbox_maps и все разом
превысили квоту, отсылавший получит отбой:
"550 Sorry, the user <www@tdauto.ru> has overdrawn diskspace quota,
please try again later." и все 3 пользователя получат служебное
сообщение.

Как срабатывает ограничение на размер служебного сообщения:

virtual_quota_user_message_limit = 2048
virtual_quota_user_bounce = yes

Файл пишется непосредственно пользователю в maildir/new/ c уникальным
именем и пока он там существует, в него будут добавляться строчки
вида:

xx.xx.xx xx:xx:xx ( ХХХ ) | mail from: < ххх @ хххх . хх >

Я сделал так, дабы не плодить кучу мелких сообщений, по одному на
каждый отбой.

Для контроля позиции последней записи и текущего имени служебного
файла, в корне maildir/ пользователя, создается технический файл
maildir/postfix.user.quota (вроде это имя ни с кем другим не
пересекается, по крайней мере мне не известно).

Так вот, как только размер служебного сообшения в maildir/new/
становится = virtual_quota_user_message_limit, строчки в него
перестают добавляться.

Если же из maildir/new/ служебное сообщение перекочевало в
maildir/cur/ например, пользователь просмотрел всю новую почту, но так
ничего не забрав оставил ее на сервере, то на следующий отбой
связанный с превышением квоты, в maildir/new/ создастся новое
служебное сообщение, с новым уникальным именем, старое при этом
соответственно останется в maildir/cur/

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

На этом с настройками квоты все. Думаю что особых затруднений все это
вызвать не должно.
В любом случае: vitaliy at tdauto.ru
ПРИНИМАЕТСЯ ЛЮБАЯ КОНСТРУКТИВНАЯ КРИТИКА.

--------------------------------------------------------------------------------

-----------------------------------------------------------------------------------------------------------

ИЗМЕНЕНИЯ В ИСХОДНИКАХ

./src/global/mail_parms.h
------- Врезка ./src/global/mail_parms.h -------------------------------------------------------------------

/*
* Virtual maildir quota.
*/
#define VAR_VIRT_MAILBOX_LIMIT_MAPS "virtual_mailbox_limit_maps" /* название в main.cf */
#define DEF_VIRT_MAILBOX_LIMIT_MAPS "" /* по умолчанию */
extern char *var_virt_mailbox_limit_maps; /* сама глобальная переменная */

#define VAR_VIRT_QUOTA_USER_BOUNCE "virtual_quota_user_bounce"
#define DEF_VIRT_QUOTA_USER_BOUNCE 0
extern bool var_virt_quota_user_bounce;

#define VAR_VIRT_QUOTA_USER_MSG "virtual_quota_user_message"
#define DEF_VIRT_QUOTA_USER_MSG ""
extern char *var_virt_quota_user_message;

#define VAR_VIRT_QUOTA_USER_MSG_LIMIT "virtual_quota_user_message_limit"
#define DEF_VIRT_QUOTA_USER_MSG_LIMIT "1024"
extern char *var_virt_quota_user_message_limit;


------- Конец врезки./src/global/mail_parms.h ---------------------------------------------------------------

------- Врезка ./src/smtpd/smtpd.c -------------------------------------------------------------------------

/*
* Virtual Quota.
*/
char *var_virt_mailbox_limit_maps;
bool var_virt_quota_user_bounce;
char *var_virt_quota_user_message;
char *var_virt_quota_user_message_limit;
char *var_virt_mailbox_base;

.
.
/*
/* смотрим main(....) находим там инициализацию *char и BOOL
/* и добавляем туда новые переменные
*/
int main(int argc, char **argv)
{
.
.
.

static CONFIG_BOOL_TABLE bool_table[] = {
.
.
.

VAR_VIRT_QUOTA_USER_BOUNCE, DEF_VIRT_QUOTA_USER_BOUNCE, &var_virt_quota_user_bounce,
.
.
0,
};

static CONFIG_STR_TABLE str_table[] = {
.
.
.
VAR_VIRT_MAILBOX_LIMIT_MAPS, DEF_VIRT_MAILBOX_LIMIT_MAPS, &var_virt_mailbox_limit_maps, 0, 0,
VAR_VIRT_QUOTA_USER_MSG, DEF_VIRT_QUOTA_USER_MSG, &var_virt_quota_user_message, 0, 0,
VAR_VIRT_QUOTA_USER_MSG_LIMIT, DEF_VIRT_QUOTA_USER_MSG_LIMIT, &var_virt_quota_user_message_limit, 1, 0,
VAR_VIRT_MAILBOX_BASE, DEF_VIRT_MAILBOX_BASE, &var_virt_mailbox_base, 0, 0,
0,
};

}
------- Конец врезки ./src/smtpd/smtpd.c ---------------------------------------------------------------


------- Врезка ./src/smtpd/smtpd_check.c ---------------------------------------------------------------

/* Необходимые дополнительные заголовочные файлы */

#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <mail_date.h>
#include <been_here.h>
#include <quote_822_local.h>
#include <get_hostname.h>


/*
/* Новые функции и MAPS *virt_mailbox_limit_maps;
*/
static MAPS *virt_mailbox_limit_maps;
ARGV *smtpd_resolve_virt_alias(const char *addr, MAPS *maps, int propagate);
int smtpd_check_virt_user_quota(SMTPD_STATE *state, const char *addr);
long check_dir_size(char *dirname);
void smtpd_rw_quota_msg(ARGV *user_mess);
void smtpd_rw_templ_msg(ARGV *user_mess, VSTREAM *fpr, VSTREAM *fpw);
void smtpd_rw_user_msg(ARGV *user_mess, VSTREAM *fpw);
VSTRING *smtpd_cp_key_msg(VSTRING *buffer, char *cut, char *paste);
VSTRING *smtpd_quota_mail_date(time_t when);


/* void smtpd_check_init(void) Добавляем инициализацию *virt_mailbox_limit_maps */
void smtpd_check_init(void)
{
.
.
.
virt_mailbox_limit_maps = virtual8_maps_create(VAR_VIRT_MAILBOX_LIMIT_MAPS, var_virt_mailbox_limit_maps,
DICT_FLAG_LOCK );
}


/* static int check_rcpt_maps(SMTPD_STATE *state, const char *recipient) */
/* Добавляем дополнительные вызовы проверки квоты. */
static int check_rcpt_maps(SMTPD_STATE *state, const char *recipient)
{

ARGV *argv = 0;
VSTRING *temp1;
int cpp = 0;
int stat_quota = 0;
.
.
.

/* Определяем схожий с Postfix макрос. */
#define MATCHV8(map, rcpt)
checkv8_maps_find(state, recipient, map, rcpt)
.
.
.
/* Проверяем квоты по виртуальным алиасам. Тут немного добавилось
но при этом функциональность прежнего кода полностью сохранена */

if (MATCH(rcpt_canon_maps, CONST_STR(reply->recipient))
|| MATCH(canonical_maps, CONST_STR(reply->recipient)))
return (0);

if (MATCH(virt_alias_maps, CONST_STR(reply->recipient))
&& (reply->flags & RESOLVE_CLASS_VIRTUAL) /* проверка на virtual ресольв */
&& *var_virt_mailbox_maps
&& *var_virt_mailbox_base /* проверки всяческих катр */
&& *var_virt_mailbox_limit_maps) {
argv = smtpd_resolve_virt_alias(STR(reply->recipient), /* вызов ресольва альясов */
virt_alias_maps , 0); /* функция ресольва взята из postfix cleanup */
for (cpp = 0; cpp < argv->argc; cpp++) { /* в argv попадают реальные адреса */
if ((smtpd_check_virt_user_quota(state, argv->argv[cpp]))==0) /* вызов своей функции проверки квоты */
stat_quota = 1;
}
if (argv)
argv_free(argv);
if (stat_quota)
return (0); /* 250 Ок если хоть один адрес не превысил квоту */
return(smtpd_check_reject(state, MAIL_ERROR_BOUNCE, /* отбой если все адреса превысили квоту */
"%d Sorry, the user <%s> has overdrawn diskspace quota,
please try again later.", var_virt_mailbox_code,
CONST_STR(reply->recipient)));
}

/*
/* Дабы не портить общую картину, если наша проверка "промахнулась", оставляю
/* старый вариант проверки virt_alias_maps, он сработает если
/* reply->flags не попадает на RESOLVE_CLASS_VIRTUAL и т.д.
*/
if (MATCH(virt_alias_maps, CONST_STR(reply->recipient)))
return (0);
.
.
.
/* Сразу после postfix проверки virtual юзеров, добавляем свою проверку квот */
if ((reply->flags & RESOLVE_CLASS_VIRTUAL)
&& *var_virt_mailbox_maps
&& *var_virt_mailbox_base
&& *var_virt_mailbox_limit_maps) {
temp1 = vstring_alloc(100);
quote_822_local(temp1, CONST_STR(reply->recipient));
if (smtpd_check_virt_user_quota(state, CONST_STR(temp1))) { /* вызов своей функции проверки квоты */
vstring_free(temp1);
return(smtpd_check_reject(state, MAIL_ERROR_BOUNCE, /* отбой если адрес превысил квоту */
"%d Sorry, the user <%s> has overdrawn diskspace quota,
please try again later.",
var_virt_mailbox_code, CONST_STR(reply->recipient)));
}
vstring_free(temp1);
}
.
.
}

////////////////////////////////////////
//////* Далее Новые функции *//////////
///////////////////////////////////////

/* Ресольв альясов взят практически без изменений из cleanup, возвращает реальные адреса*/
ARGV *smtpd_resolve_virt_alias(const char *addr,
MAPS *maps, int propagate)
{
ARGV *argv;
ARGV *lookup;
VSTRING *temp1 = vstring_alloc(100);
int count;
int i;
int arg;
BH_TABLE *been_here;
char *saved_lhs;
char *myname = "smtpd_resolve_virt_alias";
/*
* Initialize.
*/
argv = argv_alloc(1);
argv_add(argv, addr, ARGV_END);
argv_terminate(argv);
been_here = been_here_init(0, BH_FLAG_FOLD);

/*
* Rewrite the address vector in place. With each map lookup result,
* split it into separate addresses, then rewrite and flatten each
* address, and repeat the process. Beware: argv is being changed, so we
* must index the array explicitly, instead of running along it with a
* pointer.
*/
#define UPDATE(ptr,new) { myfree(ptr); ptr = mystrdup(new); }
#define MAX_RECURSION 1000
#define MAX_EXPANSION 1000
#define STR vstring_str
#define RETURN(x) { been_here_free(been_here); return (x); }

for (arg = 0; arg < argv->argc; arg++) {
if (argv->argc > MAX_EXPANSION) {
msg_warn("%s: unreasonable %s map expansion size for %s",
myname, maps->title, addr);
break;
}
for (count = 0; /* void */ ; count++) {

/*
* Don't expand an address that already expanded into itself.
*/
if (been_here_check_fixed(been_here, argv->argv[arg]) != 0)
break;
if (count >= MAX_RECURSION) {
msg_warn("%s: unreasonable %s map nesting for %s",
myname, maps->title, addr);
break;
}
quote_822_local(temp1, argv->argv[arg]);
if ((lookup = mail_addr_map(maps, STR(temp1), propagate)) != 0) {
saved_lhs = mystrdup(argv->argv[arg]);
for (i = 0; i < lookup->argc; i++) {
unquote_822_local(temp1, lookup->argv[i]);
if (i == 0) {
UPDATE(argv->argv[arg], STR(temp1));
} else {
argv_add(argv, STR(temp1), ARGV_END);
argv_terminate(argv);
}

/*
* Allow an address to expand into itself once.
*/
if (strcasecmp(saved_lhs, STR(temp1)) == 0)
been_here_fixed(been_here, saved_lhs);
}
myfree(saved_lhs);
argv_free(lookup);
} else if (dict_errno != 0) {
msg_warn("%s: %s map lookup problem for %s",
myname, maps->title, addr);
vstring_free (temp1);
RETURN(argv);
} else {
break;
}
}
}
vstring_free (temp1);
RETURN(argv);
}


/* void smtpd_rw_quota_msg(ARGV *user_mess) */
/* открывает шаблон служебной мессаги или если она уже есть то открывает существующую*/
void smtpd_rw_quota_msg(ARGV *user_mess)
{
VSTREAM *fpr = 0;
VSTREAM *fpw = 0;
VSTREAM *fpt;
VSTRING *buf = vstring_alloc(100);
char *ftmp_quota;
long msg_send_max = atol(var_virt_quota_user_message_limit);
long msg_send_cur = 0;
time_t starttime = time((time_t *) 0);
struct stat st;

/* user_mess->argv[0] from
/* user_mess->argv[1] to
/* user_mess->argv[2] user quota
/* user_mess->argv[3] maidir size
/* user_mess->argv[4] maildir
*/

ftmp_quota = concatenate(user_mess->argv[4], /* пробуем открыть технический файл */
"postfix.user.quota", (char *)0); /* и прочесть информацию о последней записи в служебном сообщении */
if ((fpt = vstream_fopen(ftmp_quota, O_RDONLY | O_EXCL, 0600)) != 0) {
vstring_get_nonl(buf, fpt); /* вышло - читаем путь к сообщению */
vstream_fclose(fpt);
fpw = vstream_fopen(STR(buf), O_RDWR | O_EXCL, 0600); /* закрываем ибо пока не нужен */
}

myfree(ftmp_quota);

if (fpw ==0) { /* технического файла нет, открываем шаблон */
if ((fpr = vstream_fopen(var_virt_quota_user_message,
O_RDONLY, 0)) == 0) {
msg_warn("cannot open file %s:", var_virt_quota_user_message);
vstring_free(buf);
return;
}

vstring_sprintf(buf, "%snew/%lu.VQUOTA%lxI%lx.%s", user_mess->argv[4],
(unsigned long) starttime, (unsigned long) st.st_dev,
(unsigned long) st.st_ino, get_hostname()); /* создаем уникальное имя файла */ /* из текущего времени + st_dev + st.st_ino + хост*/
if ((fpw = vstream_fopen(STR(buf),
O_CREAT | O_RDWR | O_EXCL, 0600)) == 0) { /* создаем служебное сообщение*/
msg_warn("cannot create file %s:", STR(buf)); /* в юзер maildir/new/ */
vstring_free(buf);
vstream_fclose(fpr);
return;
}
smtpd_rw_templ_msg(user_mess, fpr, fpw); /* читаем шаблон - пишем сообщение */
vstring_free(buf);
vstream_fclose(fpr);
vstream_fclose(fpw);
return;
}

if ( fpw && ((msg_send_cur = vstream_fseek(fpw, /* проверка объема служебного сообщения */
0, SEEK_END)) < msg_send_max)) {
vstream_fseek(fpw, 0, SEEK_SET);
smtpd_rw_user_msg(user_mess, fpw); /* читаем сообщение, добавляем туда строчку*/
vstream_fclose(fpw);
}
vstring_free(buf);
return;
}


/*VSTRING *smtpd_cp_key_msg(VSTRING *buffer, char *cut, char *paste) */
/* вырезает из строки то что нам нужно и вставляет то что хотим и возвращает строку*/
VSTRING *smtpd_cp_key_msg(VSTRING *buffer, char *cut, char *paste)
{
char *save;
char *cp;

while ((cp = strstr(vstring_str(buffer), cut))!=0) {
save = mystrdup(cp + strlen(cut));
vstring_truncate (buffer, (VSTRING_LEN(buffer) - strlen(cp)));
VSTRING_TERMINATE(buffer);
vstring_sprintf_append(buffer, "%s%s", paste, save);
myfree(save);
}
return (buffer);
}

/*void smtpd_rw_templ_msg(ARGV *user_mess, VSTREAM *fpr, VSTREAM *fpw)*/
/* читает шаблон, пишет в юзер maildir/new/ служебное сообщение*/
void smtpd_rw_templ_msg(ARGV *user_mess, VSTREAM *fpr, VSTREAM *fpw)
{
int cp;
int cnt = 0;
VSTRING *buffer;
VSTRING *lt;
VSTREAM *fpt;
char *save;
char *ftmp_quota;
char *key;
char *kmf = "_MAIL_FROM_"; /* ключевые поля подстановки*/
char *krt = "_RCPT_TO_";
char *kuq = "_USER_QUOTA_";
char *kus = "_UDIR_SIZE_";
char *ktm = "_MSG_TIME_";

/* user_mess->argv[0] from
/* user_mess->argv[1] to
/* user_mess->argv[2] user quota
/* user_mess->argv[3] maidir size
/* user_mess->argv[4] maildir
*/

buffer = vstring_alloc(100);
do {
cp = vstring_get(buffer, fpr);
if (buffer->vbuf.data[0] == '#') /* режем комментарии*/
continue;

if ((key = strstr(vstring_str(buffer), krt))!=0) /*ищем ключи и заменяем их на реальные значения*/
smtpd_cp_key_msg(buffer, krt, user_mess->argv[1]);
if ((key = strstr(vstring_str(buffer), kuq))!=0)
smtpd_cp_key_msg(buffer, kuq, user_mess->argv[2]);
if ((key = strstr(vstring_str(buffer), kus))!=0)
smtpd_cp_key_msg(buffer, kus, user_mess->argv[3]);
if ((key = strstr(vstring_str(buffer), ktm))!=0)
smtpd_cp_key_msg(buffer, ktm, (char *)mail_date(time((time_t *) 0)));
if ((key = strstr(vstring_str(buffer), kmf))!=0) {
ftmp_quota = concatenate(user_mess->argv[4],
"postfix.user.quota", (char *)0); /*создаем технический файл postfix.user.quota*/
if ((fpt = vstream_fopen(ftmp_quota,
O_CREAT | O_WRONLY | O_TRUNC, 0600)) == 0) {
vstring_free(buffer);
msg_warn("cannot create file %s:", ftmp_quota);
myfree(ftmp_quota);
return;
}
lt = smtpd_quota_mail_date(time((time_t *) 0));
save = concatenate(vstring_str(lt), " | mail from: ",
user_mess->argv[0], (char *)0);
smtpd_cp_key_msg(buffer, kmf, save);
vstream_fprintf(fpt, "%s %d %d %d ", VSTREAM_PATH(fpw), cnt, /* вписываем в postfix.user.quota*/
key - vstring_str(buffer), /* путь к служебному сообщению*/
(key - vstring_str(buffer)) + strlen(save)); /* номер строки с последним _MAIL_FROM_*/
myfree(ftmp_quota); /* позицию _MAIL_FROM_ в строке */
myfree(save);
vstring_free(lt);
vstream_fclose(fpt);
}
vstream_fputs(vstring_str(buffer), fpw);
cnt++;
} while(cp != VSTREAM_EOF);
vstring_free(buffer);
return;
}



/*VSTRING *smtpd_quota_mail_date(time_t when)*/
/* возвращает сформатированное локальное время и зону*/
VSTRING *smtpd_quota_mail_date(time_t when)
{
VSTRING *vp;
struct tm *lt;

vp = vstring_alloc(100);

#define STRFTIME_FMT "%d.%m.%y %H:%M:%S"

lt = localtime(&when);
while (strftime(vstring_end(vp), vstring_avail(vp), STRFTIME_FMT, lt) == 0)
VSTRING_SPACE(vp, 100);
VSTRING_SKIP(vp);

while (strftime(vstring_end(vp), vstring_avail(vp), " (%Z)", lt) == 0)
VSTRING_SPACE(vp, 100);
VSTRING_SKIP(vp);
return (vp);
}

/*void smtpd_rw_user_msg(ARGV *user_mess, VSTREAM *fpw)*/
/*читает служебное сообщение если оно уже есть и добавляет туда новую строчку*/
void smtpd_rw_user_msg(ARGV *user_mess, VSTREAM *fpw)
{
int line, pos_s, pos_e = 0;
int cnt = 0;
int cp;
int ch;
long offset;
VSTRING *buffer;
VSTRING *lt;
VSTREAM *fpt;
char *ftmp_quota;
char *patch;
char *save;
char *new;

ftmp_quota = concatenate(user_mess->argv[4], /* открываем postfix.user.quota */
"postfix.user.quota", (char *)0);
if ((fpt = vstream_fopen(ftmp_quota, O_RDWR | O_EXCL, 0600)) == 0) {
myfree(ftmp_quota);
return;
}

buffer = vstring_alloc(100);

vstring_get_nonl(buffer, fpt);
patch = mystrdup(STR(buffer)); /* путь к служебному сообщению*/
vstring_get_nonl(buffer, fpt);
line = atoi(vstring_str(buffer)); /*номер строки*/
vstring_get_nonl(buffer, fpt);
pos_s = atoi(vstring_str(buffer)); /*позиция начала последней добавки*/
vstring_get_nonl(buffer, fpt);
pos_e = atoi(vstring_str(buffer)); /*позиция конца последней добавки*/

do { /* читаем до нужной строчки, сохраняем все что идет после нее и добавляем новую строку*/
cp = vstring_get(buffer, fpw);
if (line == cnt) {
lt = smtpd_quota_mail_date(time((time_t *) 0));
new = concatenate(vstring_str(lt), " | mail from: ", /*новая строка*/
user_mess->argv[0], (char *)0);
vstring_free(lt);
save = mystrndup(vstring_str(buffer)+pos_s, pos_e - pos_s);
smtpd_cp_key_msg(buffer, save, new);
vstream_fseek(fpt, 0, SEEK_SET);
vstream_fflush(fpt);
vstream_fprintf(fpt, "%s %d %d %d ", patch, cnt+1, /* обновляем postfix.user.quota*/
pos_s, pos_s + strlen(new));
myfree(patch);
myfree(save);
myfree(new);
offset = vstream_ftell(fpw);
VSTRING_SKIP(buffer);
while ((cp = VSTREAM_GETC(fpw))!= VSTREAM_EOF)
VSTRING_ADDCH(buffer, cp);
VSTRING_TERMINATE(buffer);
vstream_fseek(fpw, offset, SEEK_SET);
save = buffer->vbuf.data;
while ((ch = *buffer->vbuf.data++) != 0)
VSTREAM_PUTC(ch, fpw);
buffer->vbuf.data = save;
break;
}
cnt++;
} while(cp != VSTREAM_EOF);
vstring_free(buffer);
myfree(ftmp_quota);
vstream_fclose(fpt);
return;
}

/* long check_dir_size(char *dirname) */
/* Взято из EXIM практически без изменений*/
/* возвращает объем директории в байтах*/
long check_dir_size(char *dirname)
{
DIR *dir;
long sum = 0;
struct dirent *ent;
struct stat statbuf;

dir = opendir(dirname);
if (dir == NULL) {
msg_warn("check_dir_size: cannot open directory : %s", dirname);
return 0;
}

while ((ent = readdir(dir)) != NULL) {
char *name = ent->d_name;
VSTRING *buffer;

if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;

buffer = vstring_alloc(1024);

vstring_sprintf(buffer,"%s/%s",dirname,name);
if (stat(vstring_str(buffer), &statbuf) < 0) {
vstring_free(buffer);
continue;
}
if ((statbuf.st_mode & S_IFREG) != 0)
sum += statbuf.st_size;
else if ((statbuf.st_mode & S_IFDIR) != 0)
sum += check_dir_size(vstring_str(buffer));
vstring_free(buffer);

}
closedir(dir);
if (msg_verbose)
msg_info("check_dir_size: dir=%s sum=%ld", dirname, sum);
return sum;
}


/* int smtpd_check_virt_user_quota(SMTPD_STATE *state, const char *addr) */
/* проверяет квоту юзера в virtual_mailbox_maps */
/* возвращает 0 если все в порядке и 1 если квота превышена*/
int smtpd_check_virt_user_quota(SMTPD_STATE *state, const char *addr)
{
ARGV *argv;
char *user_quota;
char *maildir;
char *myname = "smtpd_check_virt_user_quota";
VSTRING *vb;
int dir_size = 0;
int size_quota = 0;

#define GETV8(map, rcpt)
checkv8_maps_find(state, rcpt, map, rcpt)

#define LAST_CHAR(s) (s[strlen(s) - 1])

/* Sanity check. */

if (*var_virt_mailbox_base != '/') /* проверка virt_mailbox_base на корректность*/
msg_fatal("do not specify relative pathname: %s = %s",
VAR_VIRT_MAILBOX_BASE, var_virt_mailbox_base);

if ((maildir = GETV8(virt_mailbox_maps, addr)) !=0 )
maildir = concatenate(var_virt_mailbox_base, "/",
maildir, (char *) 0);
else
return (0);

/* Check maildir. */

if (LAST_CHAR(maildir) != '/') { /* проверка юзера на maildir*/
msg_info("%s do not specify maildir pathname: %s",
myname, maildir);
myfree(maildir);
return (0);
}

if ((user_quota = mystrdup(GETV8(virt_mailbox_limit_maps, addr))) /*смотрим квоту*/
&& ((size_quota = atoi(user_quota)) > 0)) {
dir_size = check_dir_size(maildir);
if ((dir_size >= 0) && (dir_size < size_quota)) {
myfree(maildir);
myfree (user_quota);
return (0);
} else {
if (var_virt_quota_user_bounce { /* если надо, шлем служебное сообщение */
&& *var_virt_quota_user_message)
vb = vstring_alloc(100);
argv = argv_alloc(1);
vstring_sprintf(vb, "%d", dir_size);
argv_add(argv, state->sender, ARGV_END);
argv_add(argv, addr, ARGV_END);
argv_add(argv, user_quota, ARGV_END);
argv_add(argv, vstring_str(vb), ARGV_END);
argv_add(argv, maildir, ARGV_END);
argv_terminate(argv);
smtpd_rw_quota_msg(argv);
argv_free(argv);
vstring_free(vb);
}
myfree(maildir);
myfree (user_quota);
return (1);
}
}
return (0);
}

------ Конец врезки ./src/smtpd/smtpd_check.c -------------------------------------------------------

------ Врезка ./src/cleanup/cleanup.h ---------------------------------------------------------------

/*
* Virtual maildir quota.
*/
extern MAPS *cleanup_virt_mailbox_limit_maps;
extern MAPS *cleanup_virt_mailbox_maps;

------ Конец врезки ./src/cleanup/cleanup.h ----------------------------------------------------------

В cleanup postfix сам ресольвит альясы и тут я просто не пропускаю превысившие квоту адреса

------ Врезка ./src/cleanup/cleanup_init.c -----------------------------------------------------------

/* Virtual maildir quota. */
#include <virtual8_maps.h>

/*
* Virtual maildir quota.
*/
char *var_virt_mailbox_limit_maps;
char *var_virt_mailbox_maps;
char *var_virt_mailbox_base;
MAPS *cleanup_virt_mailbox_limit_maps;
MAPS *cleanup_virt_mailbox_maps;

CONFIG_STR_TABLE cleanup_str_table[] = { /* инициализация новых переменных */
.
.
.
VAR_VIRT_MAILBOX_LIMIT_MAPS, DEF_VIRT_MAILBOX_LIMIT_MAPS, &var_virt_mailbox_limit_maps, 0, 0,
VAR_VIRT_MAILBOX_BASE, DEF_VIRT_MAILBOX_BASE, &var_virt_mailbox_base, 0, 0,
VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0,
0,
};

void cleanup_pre_jail(char *unused_name, char **unused_argv)
{

.
.
.

if (*var_virt_mailbox_limit_maps)
cleanup_virt_mailbox_limit_maps =
virtual8_maps_create(VAR_VIRT_MAILBOX_LIMIT_MAPS,
var_virt_mailbox_limit_maps,
DICT_FLAG_LOCK );
if (*var_virt_mailbox_maps)
cleanup_virt_mailbox_maps =
virtual8_maps_create(VAR_VIRT_MAILBOX_MAPS, var_virt_mailbox_maps,
DICT_FLAG_LOCK);
}

------ Конец врезки ./src/cleanup/cleanup_init.c ---------------------------------------------------------------

------ Врезка ./src/cleanup/cleanup_out_recipient.c ---------------------------------------------------------------

/* Virtual maildir quota. */

#include <sys/types.h> /* opendir(3), stat(2) */
#include <sys/stat.h> /* stat(2) */
#include <dirent.h> /* opendir(3) */
#include <mail_addr_find.h> /*mail_addr_find*/
#include <stdlib.h> /*atoi*/
#include <stringops.h> /*concatenate*/
#include <msg.h> /*msg_info*/


/* Virtual maildir quota. */
int cleanup_check_virt_user_quota(char *recip);
long check_dir_size(char *dirname);


/* Здесь postfix ресольвит альясы */
void cleanup_out_recipient(CLEANUP_STATE *state, const char *orcpt,
const char *recip)
{

.
.
.

if (cleanup_virt_alias_maps == 0) {
if (been_here(state->dups, "%s %s", orcpt, recip) == 0) {
cleanup_out_string(state, REC_TYPE_ORCP, orcpt);
cleanup_out_string(state, REC_TYPE_RCPT, recip);
state->rcpt_count++;
}
} else {
argv = cleanup_map1n_internal(state, recip, cleanup_virt_alias_maps,
cleanup_ext_prop_mask & EXT_PROP_VIRTUAL);
for (cpp = argv->argv; *cpp; cpp++) {
if (been_here(state->dups, "%s %s", orcpt, *cpp) == 0) {

/////////* вклинился здесь с вот этим*///////////////
if (*var_virt_mailbox_maps
&& *var_virt_mailbox_limit_maps
&& *var_virt_mailbox_base
&& (cleanup_check_virt_user_quota(*cpp)))
continue;
/////////////////////////////////////////////////////

cleanup_out_string(state, REC_TYPE_ORCP, orcpt);
cleanup_out_string(state, REC_TYPE_RCPT, *cpp);
state->rcpt_count++;
}
}
argv_free(argv);
}
}

/* long check_dir_size(char *dirname)*/
/* Подсчет директории, тут все без изменений как и в smtpd_check.c*/
long check_dir_size(char *dirname)
{
DIR *dir;
long sum = 0;
struct dirent *ent;
struct stat statbuf;

dir = opendir(dirname);
if (dir == NULL)
return 0;

while ((ent = readdir(dir)) != NULL)
{
char *name = ent->d_name;
VSTRING *buffer;

if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;

buffer = vstring_alloc(1024);

vstring_sprintf(buffer,"%s/%s",dirname,name);
if (stat(vstring_str(buffer), &statbuf) < 0)
{
vstring_free(buffer);
continue;
}
if ((statbuf.st_mode & S_IFREG) != 0)
sum += statbuf.st_size;
else if ((statbuf.st_mode & S_IFDIR) != 0)
sum += check_dir_size(vstring_str(buffer));
vstring_free(buffer);

}
closedir(dir);
return sum;
}

/* int cleanup_check_virt_user_quota(char *recip) */
/* все как и в smtpd_check.c за исключением того что служебное сообщение теперь не шлется*/
int cleanup_check_virt_user_quota(char *recip)
{
char *maildir;
char *user_quota;
char *myname = "cleanup_check_virt_user_quota";
int dir_size = 0;
int size_quota = 0;

#define GETV8(map, rcpt)
(char *)mail_addr_find(map, rcpt, (char **) 0)

#define LAST_CHAR(s) (s[strlen(s) - 1])

/* Sanity check. */

if (*var_virt_mailbox_base != '/')
msg_fatal("do not specify relative pathname: %s = %s",
VAR_VIRT_MAILBOX_BASE, var_virt_mailbox_base);

if ((maildir = GETV8(cleanup_virt_mailbox_maps, recip)) !=0 )
maildir = concatenate(var_virt_mailbox_base, "/",
maildir, (char *) 0);
else
return (0);

/* Check maildir. */

if (LAST_CHAR(maildir) != '/') {
msg_info("%s do not specify maildir pathname: %s",
myname, maildir);
myfree(maildir);
return (0);
}

if ((user_quota = GETV8(cleanup_virt_mailbox_limit_maps, recip))
&& ((size_quota = atoi(user_quota)) > 0)) {
dir_size = check_dir_size(maildir);
if ((dir_size >= 0) && (dir_size < size_quota)) {
myfree(maildir);
return (0);
} else {
myfree(maildir);
return (1);
}
}
return (0);
}

------ Конец врезки ./src/cleanup/cleanup_out_recipient.c ---------------------------------------------------------------

Обновлено: 13.03.2015