On Tue, 08 Nov 2011 10:30:47 +0100, Egoitz Aurrekoetxea Aurre <ego...@ramattack.net> wrote:
You could too take a look to Postfix Quota Reject. http://postfixquotareject.ramattack.net. It's a postfix policy daemon which allow mail to be rejected at smtp dialogue when mailbox are overquota... can work from the own mailbox machine or from the own mailscanning machine farm asking to a daemon in mailbox machines.
For what it's worth, here's a similar policy daemon that I've been using for a few years. - it only checks the quota if MAIL FROM has a SIZE parameter (which is not always the case) - it executes 'postmap' on each request to find the proper maildir so will not scale very well - been using it on an ancient Postfix installation together with the VDA patch and Courier - daemon must be run by a user with access to all maildirs (in my case it's 'vmail') Might not be a good idea to paste this in a message since whitespace is significant in Python, but here it is: #!/usr/bin/env python ## ## Maildir++ quota checking policy server for Postfix - http://www.postfix.org/ ## ## 1. Uses 'postconf' and 'postmap' to lookup the recipient's maildir ## 2. Reads the quota file (maildirsize) to determine available storage ## 3. Checks the message size against available storage and returns ## - REJECT if there's not enough storage ## - DUNNO if no message size was specified in 'MAIL FROM' ## - DUNNO in case of a lookup error. Perhaps this ought to be DEFER instead, not sure. ## - WARN if the recipient does not exist (if postmap returns nothing) ## ## ## ## Logging ## ------- ## No logging is done unless the --debug parameter is passed in. With --debug, all policy results will be sent to syslog. ## facility = mail ## priority = debug ## ident = postfix/<script-name> (Just like Postfix's other daemons) ## ## ## ## Example usage ## ------------- ## ## [master.cf] ## ... ## check-quota unix - n n - - spawn user=vmail ## argv=/usr/local/libexec/check-quota ## --debug ## ... ## ## [main.cf] ## ... ## smtpd_recipient_restrictions = ## ... ## check_policy_service unix:private/check-quota ## ... ## ## ## ## Notes ## ----- ## 1. Proxymap is never used since it's a private service and only the postfix user may access it. Any 'proxy:' prefix for maps is removed. ## 2. The user running this policy server must be able to read 'maildirsize' in each maildir. ## 3. This policy server only checks storage quotas, not message quotas ## import optparse import os.path import os import sys import select import syslog class InvalidRecipientError(Exception): pass class QuotaChecker: def __init__(self, timeout): self.timeout = timeout self.virtual_mailbox_base = os.popen("/usr/sbin/postconf -h virtual_mailbox_base").read().strip() self.virtual_mailbox_maps = os.popen("/usr/sbin/postconf -h virtual_mailbox_maps | sed s/proxy://g").read().strip() def find_maildir(self, recipient): # Pass recipient to postmap postmap = "/usr/sbin/postmap -q '%s' '%s'" % (recipient, self.virtual_mailbox_maps) input = os.popen(postmap) # Wait for postmap to finish rfds, wfds, efds = select.select( [input], [], [], self.timeout) if not rfds: input.close() raise IOError, "Lookup timeout for %s" % recipient # Get maildir maildir = input.read().strip() exit_code = input.close() if exit_code == 256: # Recipient does not exist return None if exit_code or not maildir: raise IOError, "Lookup error for %s" % recipient return maildir def get_available_storage(self, recipient): # Lookup maildir maildir = self.find_maildir(recipient) if not maildir: raise InvalidRecipientError, "No such recipient: %s" % recipient # Find quota file filename = os.path.join(self.virtual_mailbox_base, maildir, "maildirsize") if not os.path.exists(filename): # No quota set return None # Read quota file file = open(filename) try: max_storage = 0L used_storage = 0L for line in file: line = line.strip() try: (storage, messages) = line.split() except ValueError: storage = line if storage.endswith("S"): max_storage = long(storage.strip("S")) else: used_storage += long(storage) return max_storage - used_storage finally: file.close() class PostfixQuotaPolicy: def __init__(self, quota_checker): self.quota_checker = quota_checker def listen(self): attributes = {} while True: line = sys.stdin.readline() if not line: break line = line.strip() if line: (attribute, value) = line.split("=", 1) attributes[attribute] = value else: if "request" in attributes: result = self.check_policy(attributes) syslog.syslog(syslog.LOG_DEBUG, result) sys.stdout.write("action=%s\n\n" % result) sys.stdout.flush() attributes = {} def check_policy(self, attributes): recipient = attributes["recipient"] size = long(attributes["size"]) # Size specified? if not size: return "DUNNO Not checking quota for %s - no message size specified" % recipient # Check available storage try: available = self.quota_checker.get_available_storage(recipient) except InvalidRecipientError: return "WARN Invalid recipient: %s" % recipient except: return "WARN Quota lookup failure for %s: %s: %s" % (recipient, sys.exc_type, sys.exc_value) # No storage limit? if available == None: return "DUNNO No quota set for %s" % recipient # Not enough storage? if size > available: syslog.syslog(syslog.LOG_DEBUG, "Not enough storage for %s - message size: %s, available storage: %s" % (recipient, size, available)) return "REJECT Sorry, %s does not have enough storage for a %s byte message" % (recipient, size) # Enough storage return "DUNNO Quota ok for %s - message size: %s, available storage: %s" % (recipient, size, available) # Get arguments parser = optparse.OptionParser() parser.add_option("--timeout", action = "store", type = "string", dest = "timeout", help = "Timeout in seconds before cancelling postmap query [default: %default]") parser.add_option("--debug", action = "store_true", dest = "debug", help = "Enable debug logging") parser.set_defaults( timeout = 10, debug = False, ) (options, args) = parser.parse_args() # Open syslog ident = os.path.basename(sys.argv[0]) syslog.openlog("postfix/%s" % ident, syslog.LOG_PID, syslog.LOG_MAIL) if not options.debug: syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_INFO)) # Start policy server quota_checker = QuotaChecker(options.timeout) policy = PostfixQuotaPolicy(quota_checker) policy.listen() syslog.closelog()