Postfix quota integration

Omni Flux omniflux+lists at omniflux.com
Mon May 7 20:55:01 EDT 2007


I was hoping the Postfix version of smmapd would replace this for me,
but it looks like that won't be happening soon.

I've been using a postfix policy daemon to integrate with the current
smmapd to perform quota checks. It has a few shortcomings: it does not
support virtual domains as is, but I think that would be a minor
modification, and IIRC, postfix does not pass the final username to the
policy daemon if alias expansion occurs, however, for my uses, this
catches nearly all cases of over quota mail.

I have not had any problems with this, but I would appreciate feedback
from anyone with more experience if you see any potential problems with
the implementation, improvements, or know of a better way to reject mail
for over quota accounts.

-- =

Omni Flux
-------------- next part --------------
#!/usr/bin/perl -w

# Postfix Policy Daemon for checking Cyrus Mailbox Quotas
# Version: 0.02
# Author: Omni Flux
# Most recent version is available at http://www.omniflux.com/devel/
#
# Policy Daemon code based on postfix-policyd-spf by Meng Weng Wong
#  available at http://www.openspf.org/

use strict;
use IO::Socket::UNIX;
use Sys::Syslog qw(:DEFAULT setlogsock);
use Text::Netstring qw(netstring_encode netstring_decode netstring_read net=
string_verify);

# ----------------------------------------------------------
#                      configuration
# ----------------------------------------------------------

my $mydomain =3D 'example.tld';
#
# Responses to postfix
#
my $default_response   =3D 'DUNNO';
my $overquota_response =3D '552 5.2.2 Over quota';

#
# Settings for conecting to the Cyrus smmap daemon
#
my $socket =3D "/var/run/cyrus/socket/smmap";

#
# Syslogging options for verbose mode and for fatal errors.
# NOTE: comment out the $syslog_socktype line if syslogging does not
# work on your system.
#
my $syslog_socktype =3D 'unix';
my $syslog_facility =3D 'mail';
my $syslog_options  =3D 'pid';
my $syslog_priority =3D 'info';
my $syslog_ident    =3D 'postfix/policy-cyrquota';

# ----------------------------------------------------------
#                   minimal documentation
# ----------------------------------------------------------

#
# Usage: cyrquota-pf_policy.pl [-v]
#
# This policy server checks if a Cyrus mail account is over quota.
#
# This method does create a race condition as it is possible
# for a message to pass this check while a message which will
# push the account over quota is being delivered to cyrus, but
# this should be correct in the majority of cases, and should
# never incorrectly reject mail.
#
# This method will not catch overquota accounts if postfix
# rewrites the address before performing local delivery
# (aliases, virtual domains).
#
# This documentation assumes you have read Postfix's
# README_FILES/SMTPD_POLICY_README
#
# Logging is sent to syslogd.
#
# To run this from /etc/postfix/master.cf:
#
#    cyrquota-policy	unix	-	n	n	-	-	spawn
#       user=3Dnobody argv=3D/usr/bin/perl /usr/local/sbin/cyrquota-pf_poli=
cy.pl
#
# To use this from Postfix SMTPD, use in /etc/postfix/main.cf:
#
#    smtpd_recipient_restrictions =3D
#    ...
#    reject_unlisted_recipient,
#    check_policy_service unix:private/cyrquota-policy,
#    permit_sasl_authenticated,
#    reject_unauth_destination
#    ...
#
# This policy should be included after reject_unlisted_recipient if used,
# but before any permit rules or maps which return OK.
#
# To test this script by hand, execute:
#
#   % perl cyrquota-pf_policy.pl
#
# Each query is a bunch of attributes. Order does not matter.
#
#    request=3Dsmtpd_access_policy
#    recipient=3Dbar at foo.tld
#    [empty line]
#
# The policy server script will answer in the same style, with an
# attribute list followed by a empty line:
#
#    action=3Ddunno
#    [empty line]

# ----------------------------------------------------------
#                      initialization
# ----------------------------------------------------------

#
# This process runs as a daemon, so it can't log to a terminal. Use
# syslog so that people can actually see our messages.
#
setlogsock ($syslog_socktype);
openlog ($syslog_ident, $syslog_options, $syslog_facility);

#
# Parse commandline.
#
my $verbose =3D 0;
while (my $option =3D shift (@ARGV)) {
	if ($option eq '-v') {
		$verbose =3D 1;
	} else {
		fatal_exit ("Invalid option: $option. Usage: $0 [-v]");
	}
}

#
# Unbuffer standard output.
#
select ((select (STDOUT), $| =3D 1)[0]);

# ----------------------------------------------------------
#                           main
# ----------------------------------------------------------

#
# Receive a bunch of attributes, evaluate the policy, send the result.
#

my $sock =3D IO::Socket::UNIX->new(Peer =3D> $socket) or fatal_exit ("unabl=
e to connect to smmap daemon");

my %attr;
while (<STDIN>) {
	chomp;
	if (/=3D/) {
		my ($k, $v) =3D split (/=3D/, $_, 2);
		$attr{$k} =3D $v;
		syslog (debug =3D> "Attribute: %s=3D%s", $k, $v) if $verbose;
		next
	} elsif (length) {
		syslog (warning =3D> sprintf ("warning: ignoring garbage: %.100s", $_));
		next;
	}

	if ($attr{'request'} ne 'smtpd_access_policy') {
		fatal_exit ("unrecognized request type: '$attr{'request'}'")
	}

	my $action =3D $default_response;

	if (lc(rhs ($attr{'recipient'})) eq lc($mydomain))
	{
		print $sock netstring_encode ("0 " . lhs ($attr{'recipient'}));
		my $result =3D netstring_read ($sock);
		if (!$result) {
			syslog (warning =3D> "query error");
		}
		elsif ($result eq "") {
			syslog (warning =3D> "lost connection to smmsp daemon");
		}
		else {
			if (netstring_verify($result)) {
				$result =3D netstring_decode($result);
				if ($result =3D~ /^(PERM|TEMP) Over quota$/) {
					$action =3D $overquota_response;
				}
			}
			else {
				syslog (warning =3D> "error decoding smmsp response");
			}
		}
	}
	else
	{
		syslog (debug =3D> "Skipping external domain: %s", rhs ($attr{'recipient'=
})) if $verbose;
	}

	print STDOUT "action=3D$action\n\n";
	%attr =3D ();
}

# ----------------------------------------------------------
#                       subroutines
# ----------------------------------------------------------

#
# Log an error and abort.
#
sub fatal_exit {
	syslog (err =3D> "fatal_exit: @_");
	die ("fatal: @_");
}

sub lhs {
	my $string =3D shift;
	for ($string) {
		s/\@.*$//;
	}
	return $string;
}

sub rhs {
	my $string =3D shift;
	for ($string) {
		s/.*\@//;
	}
	return $string;
}


More information about the Info-cyrus mailing list