I have used perl for many years for current task. Here is my code, one of it's task is to check smtp time RCPT TO. There are 3 different functions - one to calculate quota from maildir file, another one to use - doveadm exec() logic - it's bad and slow. And final one - to use dovecot internal protocol to obtain current quota. Also I did not honour current message size - the logic - if we can't deliver message_size_limit - user is over quota. The function can be written for any storage engine and quota mechanism.
#!/usr/bin/perl -w use Sys::Syslog; use Data::Dumper; use Socket; use IO::Handle; use strict; my %conf = ( 'debug' => '1', 'debug_prefix' => 'perl', 'debug_delimeter' => ' -> ', 'quota_mail_path' => '/data/mail', 'quota_control' => 'data', 'quota_file' => 'maildirsize', 'quota_get_cmd' => '/usr/bin/doveadm -f tab quota get -u ', 'quota_socket' => '/var/run/dovecot/doveadm-server', 'doveadm_password' => 'AGRvdmVhZG0AbnVvSGVpSjk=', 'message_size_limit' => '1' ); sub set_conf { my $key = shift; my $value = shift; if (defined $conf{$key}) { $conf{$key} = $value; $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Set config: $key=$value\n"); return 0; } die "Unknown option:$key"; } sub check_maildir_quota{ my $local_part = shift; my $domain = shift; my $full_path="$conf{'quota_mail_path'}/$domain/$local_part/$conf{'quota_control'}/$conf{'quota_file'}"; $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Got: local_part=$local_part domain=$domain\n"); $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Quota file for $local_part\@$domain is $full_path"."\n"); #Check file with quota exists, if not - there can be first delivery - so trying to store message. If yes - begining to count. if (not stat "$full_path"){ Exim::log_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Can't find quota file for $local_part\@$domain --> $full_path"); return "yes"; }else{ open(QOUTA,'<',$full_path) or die "$!"; my $quota_limit = <QOUTA>; my $current_quota = 0; my $message_size_from_quota=0; ($quota_limit) = split(/\s+/,"$quota_limit"); $quota_limit =~ s/[A-Za-z]//; $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Quota limit for $local_part\@$domain is $quota_limit"."\n"); #If quota is unlimited. 0 - means this if ( $quota_limit == 0){ close(QOUTA); return "yes"; } #Count current quota while (<QOUTA>){ chomp; ($message_size_from_quota) = split(/\s+/); $current_quota += $message_size_from_quota; } $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Current quota size for $local_part\@$domain is $current_quota"."\n"); close(QOUTA); #Adding to quota max message size(message_size_limit) $current_quota += $conf{'message_size_limit'}; $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Current quota size for $local_part\@$domain with max message is $current_quota"."\n"); #Returning to exim our decision. if ($current_quota >= $quota_limit){ return "no"; }else{ return "yes"; } } } #Parsing #Quota name=Mailbox quota Type=STORAGE Value=608908 Limit=1048576 %=58 #Quota name=Mailbox quota Type=MESSAGE Value=17727 Limit=- %=0 sub check_doveadm_quota { my $local_part = shift; my $domain = shift; $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Got: local_part=$local_part domain=$domain\n"); $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Quota check command: $conf{'quota_get_cmd'}$local_part\@$domain\n"); my @quota_data=`$conf{'quota_get_cmd'} $local_part\@$domain`; #Everything is OK? if ($? != 0) { Exim::log_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Error running doveadm for user=$local_part\@$domain: $!\n"); #Not ok - but trying to deliver return "yes"; } $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Doveadm returns:\n @quota_data"); my (@current)=split(/\t/,"$quota_data[1]"); my $quota_limit = $current[3]; my $current_quota = $current[2]; #Quota is unlimited if ( $quota_limit == '-'){ $quota_limit='unlimited'; $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Current quota is $current_quota. Max is $quota_limit\n"); return "yes"; } #Cast to bytes $quota_limit*=1024; $current_quota*=1024; $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Current quota is $current_quota. Max is $quota_limit\n"); #Adding max message size(message_size_limit) $current_quota += $conf{'message_size_limit'}; $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Current quota size with max message is $current_quota"."\n"); #Returning to exim if ($current_quota >= $quota_limit){ return "no"; }else{ return "yes"; } } #Function uses internal dovecot protocol to obtain data from socker doveadm-a. #doveadm_password must be set for auth. #We use base64 coding value = user:"doveadm" pass:value from dovecot.conf sub check_socket_quota { my $local_part = shift; my $domain = shift; $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Got: local_part=$local_part domain=$domain \n"); $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Got: socket=$conf{'quota_socket'} pass=$conf{'doveadm_password'}\n"); socket(TSOCK, PF_UNIX, SOCK_STREAM,0); connect(TSOCK, sockaddr_un("$conf{quota_socket}")); if ($? != 0) { Exim::log_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Error connecting to $conf{'quota_socket'}: $@"); } #After connection to socker, dovecot returns "+" или "-". Simple check. if (defined(my $answer = <TSOCK>)) { $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Debug ANSWER:\n$answer"); print TSOCK "VERSION\tdoveadm-server\t1\t0\n"; print TSOCK "PLAIN\t$conf{'doveadm_password'}\n"; TSOCK->flush; #'+' here $answer=<TSOCK>; print TSOCK "\t$local_part\@$domain\tquota get\n"; TSOCK->flush; my $quota_data = <TSOCK>; $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Dovecot returned:\n$quota_data"); #"+" here $answer = <TSOCK>; close TSOCK; my (@current)=split(/\t/,"$quota_data"); my $quota_limit = $current[3]; my $current_quota = $current[2]; #Quota is unlimited if ( $quota_limit eq '-'){ $quota_limit='unlimited'; $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Current quota is $current_quota. Max is $quota_limit\n"); return "yes"; } #Cast to bytes $quota_limit*=1024; $current_quota*=1024; $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Current quota is $current_quota. Max is $quota_limit\n"); #Adding max message size(message_size_limit) $current_quota += $conf{'message_size_limit'}; $conf{'debug'} && Exim::debug_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "Current quota size with max message is $current_quota"."\n"); #Returning to exim if ($current_quota >= $quota_limit){ return "no"; }else{ return "yes"; } } else { Exim::log_write($conf{'debug_prefix'}.$conf{'debug_delimeter'}.(caller(0))[3].$conf{'debug_delimeter'}. "No data avaliable after connecting to $conf{'quota_socket'}. Check dovecot service"); #Default police - trying to deliver message return "yes"; } } In exim.conf you should set MESSAGE_SIZE_LIMIT=31457280 perl_startup = do 'EXIM_ROOT/perl/exim_helper.pl';\ set_conf('debug','0');\ set_conf('message_size_limit','MESSAGE_SIZE_LIMIT'); perl_at_start Router section: quota_check: driver = redirect domains = +local_domains allow_defer allow_fail #condition = ${if eq{1}{0}} condition = ${if \ eq{${perl{check_socket_quota}{$local_part}{$domain}}} {no}\ } data = :fail: Account is under quota I have used all 3 functions in the past - the last is speed champion. I used it now. My setup is not so big - about 800 active users and ~20k message deliveres per day(+15k rejects). Perl is working fine here. Also debug=1 is valuable for testing and troubleshuting. On Thu, Nov 29, 2012 at 5:47 AM, Todd Lyons <tly...@ivenue.com> wrote: > Tomorrow, I will be experimenting with jamming the following into a > perl function (I use the embedded perl a lot) and call it as a > condition for a verify_only router, and then defer with an appropriate > quota related message is customer is already over quota to stop > further incoming emails from clogging the queue. > > > use strict; > use warnings; > # Prints out 0 if over quota (means "not ok") > # Prints out 1 if not over quota > my $local_part = shift(); > my $domain = shift(); > print_and_exit(1) if (!defined $local_part || !defined $domain); > # We use hashed subdirectories for mailstores > my $home = "/netapp3/mail/maildirs"; > (my $tmp = $domain) =~ s/^(...).*/$1/; > foreach my $char (split(//,$tmp)) { > $home .= '/'; > $home .= $char =~ /\w/ ? $char : '_'; > } > > my $file="$home/$domain/$local_part/Maildir/maildirsize"; > open(my $fh, "<", $file) or print_and_exit(1); > my @lines = <$fh>; > close($fh); > print_and_exit(1) if (scalar @lines == 0); > > my ($quota,$qcount); # count is calculated, but ignored > my @quota = split(/,/,$lines[0]); > if (scalar @quota > 0) { > $quota = substr($quota[0],0,-1) > if (substr($quota[0],-1) eq 'S'); > $qcount = substr($quota[0],0,-1) > if (substr($quota[0],-1) eq 'C'); > } > print_and_exit(1) if (!defined $quota); > > my $line=my $size=my $msgs=0; > do { > $line++; > (my $msgsize, my $msgcount) = split(" ", $lines[$line]); > $size+=$msgsize; $msgs+=$msgcount; > } while ($line < $#lines); > > if ($size > $quota) { > print_and_exit(0); # Over quota! > } else { > print_and_exit(1); # Still has room > } > > sub print_and_exit { > my $rv = shift() || 0; > print $rv, "\n"; # Need the newline? > exit; > } > > > Expanded upon original work at > > http://www.kutukupret.com/2011/05/18/check-disk-quota-usage-by-parsing-maildirsize/ > > ...Todd > > > On Wed, Nov 28, 2012 at 6:22 PM, Robert Blayzor <rblayzor.b...@inoc.net> > wrote: > > On Nov 28, 2012, at 8:32 PM, Jeremy Harris <j...@wizmail.org> wrote: > >> The "quota" option on an appendfile transport is an expanded string; > >> you can basically do what you want with it given a bit of creativity. > > > > > > Well, yes, it is. But that string is expanded regardless if the user is > over quota or not. The idea is to trap the over quota condition and act on > it. > > > > Perhaps another string thats only expanded only if the over quota > condition happens. > > > > -- > > Robert Blayzor > > INOC, LLC > > rblay...@inoc.net > > http://www.inoc.net/~rblayzor/ > > > > > > > > > > -- > > ## List details at https://lists.exim.org/mailman/listinfo/exim-users > > ## Exim details at http://www.exim.org/ > > ## Please use the Wiki with this list - http://wiki.exim.org/ > > > > -- > The total budget at all receivers for solving senders' problems is $0. > If you want them to accept your mail and manage it the way you want, > send it the way the spec says to. --John Levine > > -- > ## List details at https://lists.exim.org/mailman/listinfo/exim-users > ## Exim details at http://www.exim.org/ > ## Please use the Wiki with this list - http://wiki.exim.org/ > -- ## List details at https://lists.exim.org/mailman/listinfo/exim-users ## Exim details at http://www.exim.org/ ## Please use the Wiki with this list - http://wiki.exim.org/