Несколько лет назад в статье "Двухуровневое квотирование в Dovecot 2" автором была описана система устройства квот пользователь / домен в популярном IMAP4 / POP3 сервере Dovecot. Вкратце, напомню, что она позволяет ограничивать размер занимаемый как конкретным почтовым ящиком, так и всеми принадлежащими данному домену учётным записям. Это решение нашло применение на многих инсталляциях почтовых систем по всему миру.
Однако, в ходе реальной эксплуатации рядом пользователей были замечены некоторые технические проблемы такого решения. Рассмотрим в чём они состоят и как решаются.
Как уже упоминалось в статье "Двухуровневое квотирование в Dovecot 2", в случае развёртывания данной схемы квотирования для уже существующих почтовых ящиков потребуется заполнение базы данных реальными значениями объёма занимаемого места и количества сообщений. Для этого используется стандартная утилита doveadm с параметрами пересчёта квот quota recalc. Однако, одна не поддерживает механизм доменных квот. Поэтому попытка её использования с параметром -u и указанием в качестве имени пользователя имени домена или пересчёта квот всех почтовых ящиков -A завершается неудачей.
root@beta:~ # doveadm quota recalc -u my.domain
doveadm(my.domain): Error: User doesn't exist
root@beta:~ # doveadm quota recalc -A
Error: User listing returned failure
doveadm: Error: Failed to iterate through some users
root@beta:~ # grep dovecot /var/log/maillog | grep sql
Nov 27 17:57:36 beta dovecot: auth-worker(18300): Warning: mysql: Query failed, retrying: Table 'exim.users' doesn't exist
Nov 27 17:57:36 beta dovecot: auth-worker(18300): Error: sql: Iterate query failed: Table 'exim.users' doesn't exist (using built-in default iterate_query: SELECT username, domain FROM users)
При этом, конечно же, с базой данных словарей квот всё в порядке.
root@beta:~ # mysql -uroot -p -D exim
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 47862
Server version: 5.7.20-log Source distribution
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
root@localhost [exim]> SELECT * FROM quota2 WHERE username LIKE '%my.domain';
+-------------------+-------------+----------+
| username | bytes | messages |
+-------------------+-------------+----------+
| foo@my.domain | 8039401321 | 32474 |
| my.domain | 12039421270 | 45581 |
| john@my.domain | 3455382803 | 11142 |
| mary@my.domain | 544637146 | 1965 |
+-------------------+-------------+----------+
4 rows in set (0.00 sec)
...
Поэтому для пересчёта мы должны будем вручную пройтись по списку пользователей и проверим результат через параметр quota get.
root@beta:~ # doveadm quota recalc -u foo@my.domain
root@beta:~ # doveadm quota get -u foo@my.domain
Quota name Type Value Limit %
user_quota STORAGE 7850978 - 0
user_quota MESSAGE 32474 - 0
domain_quota STORAGE 7850978 - 0
domain_quota MESSAGE 32474 - 0
Результат вывода запрошенной квоты обескураживает. Он не имеет ничего общего с её реальным размером. Удостоверимся в этом непосредственно в базе данных.
...
root@localhost [exim]> SELECT * FROM quota2 WHERE username LIKE '%my.domain';
+-------------------+-------------+----------+
| username | bytes | messages |
+-------------------+-------------+----------+
| foo@my.domain | 8039401321 | 32474 |
| my.domain | 8039401321 | 32474 |
| john@my.domain | 3455382803 | 11142 |
| mary@my.domain | 544637146 | 1965 |
+-------------------+-------------+----------+
4 rows in set (0.00 sec)
...
Действительно, для пользователя foo@my.domain были расчитаны верные значения, однако, при этом ровно те же значения были записаны и в строку с квотой для домена, а квоты для других почтовых ящиков данного домена проигнорированы. Безусловно, это не нормальная ситуация.
Принимая во внимание вышеизложенное, в качестве средства для решения проблемы был написан shell-скрипт dovequota.sh для корректного расчёта значений, который избавит нас как от необходимости ручного перебора всех почтовых ящиков для пересчёта занимаемых ими объёмов, так и позволит получить верные значения доменных квот.
root@beta:~ # cd /usr/local/etc/dovecot/
root@beta:/usr/local/etc/dovecot # touch dovequota.sh
root@beta:/usr/local/etc/dovecot # chmod +x dovequota.sh
root@beta:/usr/local/etc/dovecot # cat dovequota.sh
#!/bin/sh
# Get or recalculate Dovecot 2 with 2-level quotas for all users or domain
# see https://kostikov.co/dvuhurovnevoe-kvotirovanie-v-dovecot-2
#
# v.20171127 (c)2017 by Max Kostikov http://kostikov.co e-mail: max@kostikov.co
#
# Usage: dovequota.sh get|calc [user|domain]
#
# Requires MySQL and Postfixadmin installed
#
## Settings
# db credentials
DBUSER="exim"
DBPASS="exim"
DBNAME="exim"
SQL="`which mysql` -u $DBUSER -p$DBPASS -D $DBNAME"
# path to doveadm
DOVEADM=`which doveadm`
# Check arguments
if [ $# -lt 1 ]
then
echo "Provide 'get' or 'calc' as argument with optional user or domain name (empty means all)"
exit 1
fi
# domain quota recalc
domrecalc()
{
$SQL -s --disable-column-names -e "UPDATE quota2 t1, (SELECT SUM(bytes) bytes, SUM(messages) messages FROM quota2 WHERE username LIKE CONCAT('%@','$1')) t2 SET t1.bytes = t2.bytes, t1.messages = t2.messages WHERE username = '$1'" 2>/dev/null
}
case $1 in
get) $SQL -e "SELECT username, bytes, messages FROM quota2 WHERE username LIKE '%$2'" 2>/dev/null
;;
calc) if [ $# -eq 1 ]
then
# recalc for all users by domains
DOM=`$SQL -s --disable-column-names -e "SELECT username FROM quota2 WHERE username NOT LIKE '%@%'" 2>/dev/null`
for i in $DOM
do
for j in `$SQL -s --disable-column-names -e "SELECT username FROM quota2 WHERE username LIKE '%@$i'" 2>/dev/null`
do
$DOVEADM quota recalc -u $j
done
domrecalc $i
done
else
if [ `echo "$2" | grep "@"` ]
then
# recalc for user
MASK="$2"; DOM=`echo $MASK | cut -d '@' -f 2`
else
# recalc for domain
MASK="%@$2"; DOM="$2"
fi
# get users list
MBOX=`$SQL -s --disable-column-names -e "SELECT username FROM quota2 WHERE username LIKE '$MASK'" 2>/dev/null`
if [ -z "$MBOX" ]
then
echo "No users found for recalc"
exit 1
fi
for i in $MBOX
do
$DOVEADM quota recalc -u $i
done
domrecalc $DOM
fi
;;
*) echo "Wrong usage argument value '$1'!"
exit 1
;;
esac
Данный скипт имеет два основных режима.
root@beta:/usr/local/etc/dovecot # ./dovequota.sh calc foo@my.domain
root@beta:/usr/local/etc/dovecot # ./dovequota.sh get my.domain
+-------------------+-------------+----------+
| username | bytes | messages |
+-------------------+-------------+----------+
| foo@my.domain | 8039401321 | 32474 |
| my.domain | 12039781150 | 45582 |
| john@my.domain | 3455382803 | 11142 |
| mary@my.domain | 544997026 | 1966 |
+-------------------+-------------+----------+
И, конечно же, при любых пересчётах квот корректно ведутся и квоты для соответствующего домена.
Аналогично предыдущей утилите, интерфейс для управления почтовыми аккаунтами и квотами, который предоставляет Postfixadmin, несмотря на то, что также позволяет организовывать двухуровневое квотирование для уровней пользователь / домен, при удалении почтового ящика не корректирует доменную квоту.
В этой связи, разумным подходом в данном случае будет воспользововаться механизмом триггеров на уровне баз данных, который предоставляет MySQL. Добавив соответствующую процедуру на событие DELETE мы сможем исправить эту досадную ошибку.
DELIMITER |
CREATE TRIGGER domain_recalc AFTER DELETE ON mailbox FOR EACH ROW
BEGIN
SET @dom = SUBSTRING_INDEX(OLD.`username`,'@',-1);
UPDATE `quota2` t1, (SELECT SUM(`bytes`) bytes, SUM(`messages`) messages FROM `quota2` WHERE `username` LIKE CONCAT ('%@',@dom)) t2 SET t1.bytes = t2.bytes, t1.messages = t2.messages WHERE `username` = @dom;
END;
|
DELIMITER ;
Проверка после добавления.
...
root@localhost [exim]> SHOW TRIGGERS\G
*************************** 1. row ***************************
Trigger: domain_recalc
Event: DELETE
Table: mailbox
Statement: BEGIN
SET @dom = SUBSTRING_INDEX(OLD.`username`,'@',-1);
UPDATE `quota2` t1, (SELECT SUM(`bytes`) bytes, SUM(`messages`) messages FROM `quota2` WHERE `username` LIKE CONCAT('%@',@dom)) t2 SET t1.bytes = t2.bytes, t1.messages = t2.messages WHERE `username` = @dom;
END
Timing: AFTER
Created: 2017-11-27 21:32:07.80
sql_mode: NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
Definer: root@localhost
character_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8_general_ci
1 row in set (0.04 sec)
root@localhost [exim]> QUIT
Bye
Триггер с именем domain_recalc создан. Теперь при удалении почтового ящика из таблицы пользователей 'mailbox' будет производится пересчёт квоты для домена, к которому он относился.
Статья была полезной? Тогда прошу не стесняться и поддерживать деньгами через PayPal или Яндекс.Деньги.