Dovecotには、「クォータ」と呼ばれる、ユーザ毎に「容量制限」を設けることができます。ここでは、Postfix Adminから設定した「容量制限」をDovecotに反映させる設定を行いたいと思います。クォータの管理はDovecotが行い、そのバックエンドとしては最新の(そしてDovecotのマニュアルでは推奨している)「count」を用います。

FreeBSD 14.1で動作確認していますが、設定ファイルのあるディレクトリを読み替えれば、Linuxでも同様に設定できると思います。

Postfix Adminのクォータ設定

Postfix Adminで、クォータ設定を表示させるには「/usr/local/www/postfixadmin/config.local.php」に次の設定を追記します。

$CONF['quota'] = 'YES';
$CONF['domain_quota'] = 'NO';
$CONF['quota_multiplier'] = '1048576';

クォータを有効にします。が、今回はドメインクォータは有効化しません。ドメインクォータを有効にする場合は、「$CONF['domain_quota'] = 'NO';」を削除してください。クォータはメガバイト単位で設定しますが、デフォルトでは「1024000バイト」がメガバイトの単位になっています。これを「1024 × 1024 = 1048576バイト」にしています(これは好みの問題なのですが、私の愛用のメーラーであるThunderbirdは、この設定を行うとキリのいい表示をしてくれます)。この設定を行うと、ユーザの設定画面でクォータを設定することができます。

Dovecotのクォータ設定

Postfixからユーザのメールボックスへの配送は、DovecotのLMTPに任せているので、クォータ設定はDovecotに集約されます。

/usr/local/etc/dovecot/dovecot-sql.con

Postfix Adminで設定されたクォータ情報をSQLで検索できるように、変更を行います。次のパッチを当ててください。

*** dovecot-sql.conf.ext.orig	Mon Nov  4 21:03:16 2024
--- dovecot-sql.conf.ext	Mon Nov  4 21:03:22 2024
***************
*** 129,135 ****
  #
  user_query = \
    SELECT '/var/spool/vmail/' || maildir AS home, \
!   'vmail' AS uid, 'vmail' AS gid \
    FROM mailbox WHERE username = '%u' AND active = true
  
  # If you wish to avoid two SQL lookups (passdb + userdb), you can use
--- 129,136 ----
  #
  user_query = \
    SELECT '/var/spool/vmail/' || maildir AS home, \
!   'vmail' AS uid, 'vmail' AS gid, \
!   '*:bytes=' || quota AS quota_rule \
    FROM mailbox WHERE username = '%u' AND active = true
  
  # If you wish to avoid two SQL lookups (passdb + userdb), you can use

これで、DovecotはPostfix Adminで設定されたクォータ情報を読み取れるようになりました。

/usr/local/etc/dovecot/conf.d/90-quota.conf

クォータ設定の肝となるファイル、「90-quota.conf」です。これに、次のパッチファイルを当ててください。

*** ../example-config/conf.d/90-quota.conf	Sat Oct 12 17:44:22 2024
--- 90-quota.conf	Tue Nov  5 00:23:09 2024
***************
*** 39,59 ****
  # Note that % needs to be escaped as %%, otherwise "% " expands to empty.
  
  plugin {
!   #quota_warning = storage=95%% quota-warning 95 %u
!   #quota_warning2 = storage=80%% quota-warning 80 %u
  }
  
  # Example quota-warning service. The unix listener's permissions should be
  # set in a way that mail processes can connect to it. Below example assumes
  # that mail processes run as vmail user. If you use mode=0666, all system users
  # can generate quota warnings to anyone.
! #service quota-warning {
! #  executable = script /usr/local/bin/quota-warning.sh
! #  user = dovecot
! #  unix_listener quota-warning {
! #    user = vmail
! #  }
! #}
  
  ##
  ## Quota backends
--- 39,61 ----
  # Note that % needs to be escaped as %%, otherwise "% " expands to empty.
  
  plugin {
!   quota_warning = storage=95%% quota-warning 95 %u %d
!   quota_warning2 = storage=80%% quota-warning 80 %u %d
!   quota_warning3 = -storage=50%% quota-warning below %u %d
  }
  
  # Example quota-warning service. The unix listener's permissions should be
  # set in a way that mail processes can connect to it. Below example assumes
  # that mail processes run as vmail user. If you use mode=0666, all system users
  # can generate quota warnings to anyone.
! service quota-warning {
!   executable = script /usr/local/bin/quota-warning.sh
!   user = vmail
!   unix_listener quota-warning {
!     mode = 0600
!     user = vmail
!   }
! }
  
  ##
  ## Quota backends
***************
*** 80,83 ****
--- 82,108 ----
    #quota2 = dict:domain:%d:proxy::quota_domain
    #quota_rule = *:storage=102400
    #quota2_rule = *:storage=1048576
+ }
+ 
+ # Use count as quota backend
+ plugin {
+   quota_vsizes = yes
+   quota = count:User quota
+   quota_rule2 = Trash:storage=+100M
+   quota_rule3 = Trash.*:storage=+100M
+   quota_rule4 = Junk:ignore
+   quota_status_success = DUNNO
+   quota_status_nouser = DUNNO
+   quota_status_overquota = "552 5.2.2 Mailbox is full"
+ }
+ 
+ ##
+ ##  Quota status service for postfix
+ ##
+ service quota-status {
+   executable = quota-status -p postfix
+   inet_listener {
+     port = 12340
+   }
+   client_limit = 1
  }

変更点の説明をします。

plugin {quota_warning ...}では

  • 95%、80%を超えたユーザに警告メールを送信、また、使用容量が50%を切ったユーザには「通常状態に戻った」旨を通知
  • マルチドメインを念頭に、ドメイン情報を追加するように「%d」を追記

の設定をしています。「service quota_warning {...}」が実際にメールを出す設定になります。今回は「/usr/local/bin/quota-warning.sh」を呼び出す設定にしています。

「# Use count as quota backend」以下に追記しているのが実際のクォータの設定です。今回は

  • クォータの計算方法(バックエンド)にDovecotが推奨する「count」を採用(そのために必要な「quota_vsizes = yes」も追記)
  • ゴミ箱(とそのサブフォルダ)にはクォータの計算時に100MBを追加して、メールの削除時にクォータ制限に引っかかってメールが消せない事態を起こさないようにする
  • 迷惑メールが届くJunkフォルダはクォータの計算から除く。ただし、このフォルダが「無制限に使えるフォルダ」として使われないように、サブフォルダは除かない

という設定を行っています。クォータの設定は、

  • 最初にデフォルトの設定として「quota_rule」を適応する
  • 次に、その例外として「quota_rule2」「quota_rule3」…を適用する

となるのですが、デフォルトの「quota_rule」は「dovecot-sql.conf.ext」で設定しているようにPostfix Adminから読み込んでいるので、このファイルでは設定していません。もし設定しても、SQLから引っ張ってきた「quota_rule」のほうが優先します。

「service quota-status {...}」では、Postfixがメール受信時に「この大きさのメールを受信しようとしているが、クォータの制限は大丈夫かどうか?」を問い合わせるための設定をしています。UNIXソケットではなくTCP/12340を開くので、このポートに対してルータの設定を適切に行う必要があります(外部にセカンダリMXマシンが無いなら、このポートをルータで開く必要はありません)。

/usr/local/bin/quota-warning.sh

クォータの制限に引っかかりそうなユーザに「警告メール」を出すスクリプトを設置します。次の内容のスクリプトを「/usr/local/bin/quota-warning.sh」として作成してください。

#!/bin/sh

PERCENT=$1
USER=$2
DOMAIN=$3
MSG=""

if [ $PERCENT = "below" ]; then
  MSG="Your mailbox is now back to normal."
else
  MSG="Your mailbox on the server is now more then ${PERCENT}% full."
fi

cat << EOF | /usr/local/libexec/dovecot/dovecot-lda -f postmaster@$DOMAIN -d $USER -o "plugin/quota=count:User quota:noenforcing"
From: Postmaster <postmaster@$DOMAIN>
To: $USER
Subject: Quota Warning

$MSG

Regards,

Postmaster
EOF

いろいろなサイトで紹介されているスクリプトを参考にしていますが、マルチドメインであるということで引数として「ドメイン名」を受け取り、そのドメイン名の「postmaster」から送信しています。「/usr/local/libexec/dovecot/dovecot-lda」を用いてメールを送信していますが、このコマンドに「-o "plugin/quota=count:User quota:noenforcing"」を指定し、クォータがいっぱいであっても強制的にメールをメールボックスに入れる、という設定を行います。この設定が無いと、メールボックス満杯の場合、このメールがクォータ制限で受け入れられなくなり、キューに溜まってしまいます。

/usr/local/etc/dovecot/conf.d/10-mail.conf

次のパッチを当てて、クォータを有効にしてください。

*** 10-mail.conf.orig	Mon Nov  4 20:56:57 2024
--- 10-mail.conf	Mon Nov  4 21:11:40 2024
***************
*** 215,221 ****
  
  # Space separated list of plugins to load for all services. Plugins specific to
  # IMAP, LDA, etc. are added to this list in their own .conf files.
! #mail_plugins = 
  
  ##
  ## Mailbox handling optimizations
--- 215,221 ----
  
  # Space separated list of plugins to load for all services. Plugins specific to
  # IMAP, LDA, etc. are added to this list in their own .conf files.
! mail_plugins = quota
  
  ##
  ## Mailbox handling optimizations

/usr/local/etc/dovecot/conf.d/20-imap.conf

「20-imap.conf」ファイルへのパッチです。このパッチを当てると、サポートしているIMAPクライアントでは、クォータの使用状況を見ることができます。

*** ../example-config/conf.d/20-imap.conf	Sat Oct 12 17:44:22 2024
--- 20-imap.conf	Mon Nov  4 21:14:24 2024
***************
*** 91,97 ****
  
  protocol imap {
    # Space separated list of plugins to load (default is global mail_plugins).
!   mail_plugins = $mail_plugins imap_zlib
  
    # Maximum number of IMAP connections allowed for a user from each IP address.
    # NOTE: The username is compared case-sensitively.
--- 91,97 ----
  
  protocol imap {
    # Space separated list of plugins to load (default is global mail_plugins).
!   mail_plugins = $mail_plugins imap_zlib imap_quota
  
    # Maximum number of IMAP connections allowed for a user from each IP address.
    # NOTE: The username is compared case-sensitively.

/usr/local/etc/dovecot/conf.d/20-lmtp.conf

「20-lmtp.conf」へのパッチです。メールを受信した際に、クォータをチェックする設定を行います。クォータの容量制限に引っかかる場合は、受信拒否します。

*** 20-lmtp.conf.orig	Sat Oct 12 17:44:22 2024
--- 20-lmtp.conf	Mon Nov  4 21:15:02 2024
***************
*** 11,17 ****
  #lmtp_save_to_detail_mailbox = no
  
  # Verify quota before replying to RCPT TO. This adds a small overhead.
! #lmtp_rcpt_check_quota = no
  
  # Add "Received:" header to mails delivered.
  #lmtp_add_received_header = yes
--- 11,17 ----
  #lmtp_save_to_detail_mailbox = no
  
  # Verify quota before replying to RCPT TO. This adds a small overhead.
! lmtp_rcpt_check_quota = yes
  
  # Add "Received:" header to mails delivered.
  #lmtp_add_received_header = yes

/usr/local/etc/dovecot/conf.d/10-master.conf

クォータの警告メールを送信する際に、ワーニングが発生しないようにします。次のパッチを当ててください。

*** 10-master.conf.orig	Mon Nov  4 23:15:43 2024
--- 10-master.conf	Mon Nov  4 23:16:26 2024
***************
*** 135,137 ****
--- 135,148 ----
      #group = 
    }
  }
+ 
+ service stats {
+   unix_listener stats-reader {
+     mode = 0600
+     user = vmail
+   }
+   unix_listener stats-writer {
+     mode = 0600
+     user = vmail
+   }
+ }

これで必要なファイルは揃いました。次のコマンドでDovecotに設定ファイルを再読み込みさせましょう。

service dovecot reload

次に、バックエンドに「count」を用いているのですが、これはDovecotが管理しているインデックスファイルにメールの大きさを書き込むもので、最初は書き込まれていないのでIMAPクライアントが接続した際にDovecotがメールをスキャンして書き込むのですが、メール数が多い場合にはスキャンに時間がかかり、IMAPクライアントとの接続に時間がかかってしまいます。そのため、あらかじめスキャンして書き込んでしまいます。次のコマンドを実行します。

doveadm mailbox status -A vsize '*'

次に、Postfix Adminでクォータを設定します(詳しい手順は割愛します)。設定したら、次のコマンドを実行してみます。

doveadm quota get -A

このコマンドで、次のような表示を得られるはずです。

Username            Quota name   Type    Value    Limit             %
test@example.com    User quota   STORAGE 31264    35840            87
test@example.com    User quota   MESSAGE    61        -             0

すべてのユーザのクォータを得られるはずです。これでDovecot側の設定は完了です。

Postfixの設定

Postfixでは、「/usr/local/etc/postfix/main.cf」に次のパッチを当てます。

*** main.cf.orig	Wed Nov  6 07:00:40 2024
--- main.cf	Wed Nov  6 07:01:43 2024
***************
*** 718,723 ****
--- 718,725 ----
  	permit_mynetworks,
  	reject_invalid_hostname,
  	permit
+ smtpd_recipient_restrictions =
+ 	check_policy_service inet:localhost:12340
  smtpd_sender_restrictions =
  	hash:/usr/local/etc/postfix/reject_sender,
  	reject_unknown_sender_domain,

これで、メールを受信する際に、Dovecotに対して「クォータの容量は大丈夫かどうか?」を問い合わせるようになります。ただし、ローカル送信の場合は一度送信されたうえでMAILER-DAEMONから「クォータがいっぱいでした」と返事が来ます(外部からメールを受信する際には、メール受信の前にクォータチェックを行い、クォータがいっぱいの場合はメールを拒否します)。次のコマンドを実行して、設定を読み込ませましょう。

service postfix reload

バックアップMXサーバを設ける場合

いろいろな考え方があると思いますが、PostfixでバックアップMXサーバを設ける場合、Dovecotが動いているマシンで「TCP/12340」を開放の上、バックアップMXサーバで動いているPostfixに

smtpd_recipient_restrictions =
...
	check_policy_service {
		inet:mailserver.example.com:12340,
		default_action=DUNNO
	}
...

の設定を行えば、メールを受信する前にクォータの状態を確認し、クォータの容量制限に引っかかるような大きなメールの場合は受信拒否できます。ただし、Dovecotが動いていないと問い合わせが失敗するので、「check_policy_service」のオプションで「default_action=DUNNO」を指定し、とりあえずは受け入れる設定をしています。DovecotがプライマリMXサー上にある今回の設定例では、Dovecotが動いていない間メールの受け入れを拒否すれば、プライマリMXサーバが動いていない場合に代行して受信するというバックアップMXサーバの意味がなくなってしまいます。

バックアップMXサーバは、「プライマリのMXサーバが停止している間」のみならず、プライマリMXサーバが動いている間も少ないながらもメールを受信します。その時のための設定となります。

長期間プライマリMXサーバが停止している間は、プライマリMXサーバのクォータを超えていてもバックアップMXサーバが受信し続けるので、「この設定に意味はあるのか?」と自問自答しつつこの設定を施しています。

本質的な解決方法は送信サーバと受信サーバを分ける等の方法ですが、セキュリティ的に「クォータの情報を外に出して大丈夫なのか?」というのも、問題になってくると思います。メールを送ってみれば、送信者にはクォータがいっぱいであるかどうかがわかるのですが…。