Skip to main content

Check Maildir usage versus quota

June 30, 2020 by davidc

This script allows you to check virtual users' Maildir sizes compared with the quota, and produces a simple list of addresses that are overquota, which can be used for example by mail delivery agents to reject incoming messages when the user is overquota.

Mail delivery agents may not know during the incoming SMTP conversation whether a user is overquota, and so they may accept a message that they immediately have to bounce because the user is overquota. This is undesirable as the original message may not have been from a valid sender, causing a backscatter of bounces that themselves may be treated as spam and influence the reputation of the mail server.

This is a particular problem with Exim, where the SMTP ACLs and routers do not have a way to "call forward" to the transport that will ultimately deliver the message, so during the incoming SMTP conversation you cannot reject a message if the user is over quota.

The solution here is to produce and frequently update a list of users who are overquota, and put this in a file that Exim can lookup.

You pass a parameter for the root of the virtual mail, and it expects the first subdirectory to be the domain name, the next subdirectory to be the localpart, and then the third to be a Maildir. For example if pass -d /var/vmail then a user's mailbox may be /var/vmail/mydomain.com/david/Maildir.

You should run this frequently in cron. You can run it as the user who owns the virtual maildirs, but you should ensure that it can write to the directory of the output file (note it creates a temporary file to ensure atomicity, so it needs write access to the directory not just the file).

MAILTO=root
*/5 * * * * /etc/exim4/overquotacheck -d /var/vmail -o /var/vmail/overquota

Here's an example of an Exim router that checks the overquota condition:

reject_overquota:
  driver       = redirect
  retry_use_local_part = true
  condition    = ${lookup{$local_part@$domain}lsearch{/var/vmail/overquota}{yes}{no}}
  local_part_suffix = LOCALPART_SUFFIX
  local_part_suffix_optional
  allow_fail
  data         = :fail: User is over quota.
  debug_print  = R: reject_overquota for $local_part@$domain

Note that in Exim you'll need "quota_is_inclusive = false" to allow the user to go (slightly) overquota - otherwise they'll never be overquota and this mechanism won't work.

This work is based on the document and code posted by Tim Jackson, but rewritten in Python and updated to handle virtual domains. It also atomically moves the new file into place after creation, regardless of what filesystem the target is on, so there cannot be a situation where another process is accessing an incomplete file.

AttachmentSize
overquotacheck4.17 KB