Exim has no facility for checking the quotas of a mailbox at RCPT TO: time. I asked around for what the the best approach would be to implement.

Philip H recommended a socket daemon approach, as did others. Some recommended a periodic scan of all mailboxes, which writes to a file to be used to :fail: particular mailboxes that are full. This is the approach suggested and implemented by Tim Jackson.

I decided to implement the daemon approach, having had good results doing what Philip Hazel suggests. The finished product is written in perl. The script is basically a socket daemon that expects a maildir path on its socket input, and it writes a ‘yes’ if the mailbox is over quota and a ‘no’ if not back to exim.

It has several deficiencies:

  • It is not portable in its current state. It uses /proc to determine if its already running. If you can suggest a better way to check this that is portable, let me know. I didn’t spend much time on it.

  • It maintains a cache of all maildirs its ever checked. This cache is not managed; if it grows too large, the daemon will slow down or consume all available memory. Use at your own peril.

  • It is not multithreaded/multiprocess; it can only deal with one request at a time. This is probably a feature. (Actually, I don’t think it is.)

Features:

  • Only recalculates the quota if it absolutely has to; it does a stat() on the maildirsize file to determine if its been modified and only then will it recalculate.

  • It will not recalculate if it has already done so within a configurable time limit (set to 60 seconds by default). This is in response to Tim’s suggestion that a large spam attack might cause problems with the daemon. I’m not convinced that this is a feature, actually, because it makes the daemon ’sloppy’. If a message comes in while the mailbox is over quota but the mailquotad hasn’t realised it yet, the message will be queued locally. I think I prefer it to check quota for every delivery, which is easy to fix by setting $maxkeep to -1. I’m not sure what effect this would have on a busy mailhost though.

  • If the maildirsize file doesn’t exist, it will return that the mailbox is not over quota, assuming that a message delivery will create the maildirsize file.

Usage:

To use it, you will be required to have a variable that you can feed to the ${readsocket call that contains the location of the maildir. I use LDAP, and I only do a single LDAP lookup for each recipient usually, and fill a variable with the result of that lookup, like:

GET_ADDRESS_DATA = ${lookup ldap {\
        ldap:///BASEDN??sub?(&(uid=${quote_ldap:$local_part}@${quote_ldap:$domain}))\
        }\
}

lookup:
  driver = redirect
  address_data = GET_ADDRESS_DATA
  # data is intentionally left blank so that the router will decline
  # we just want this router to do a lookup so the results are availble
  # for the other routers.
  data =

When this lookup is run, $address_data is filled with the LDAP entry information, including the maildir location.

So, we run the lookup by having a ‘require’ clause in the ACL, and then a defer if the mailbox is full. Or a deny, if you wish.

  require domains       = +local_domains
          verify        = recipient
  defer   domains       = +local_domains
          condition     = ${if eq\
                                {${readsocket{/var/run/mailquotad.sock}\
                                   {${extract{mailMessageStore}{$address_data}}/Maildir}\
                                   {2s}\
                                 }}\
                                 {yes}\
                                 {1}{0}\
                           }
          message       = mailbox full

Which brings me to a question, is it better to deny or to defer on a full mailbox?

the mailquotad code is shared in the hope that it might be useful. I’d love to hear any suggestions that people might have or any constructive criticisms. It should be understood that I consider this in a prototype state, and not suitable for a production mail system. I do intend to rewrite it more thoroughly (with memory management and better error handling) in C at some point soon.

Download

mailquotad (5.4k)

Warning

This code should be considered experimental. I have tried running this in a production environment, and it fails miserably. There is a lot of work to be done to take this idea further.

I am working on a C based daemon that can do the job much more efficiently than a perl script, but its not quite ready for release just yet.

Questions to this page.