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()

Reply via email to