On Thu, Nov 29, 2012 at 2:12 PM, Phil Pennock <pdp@???> wrote:
>> It would appear that unless I remove root squash for my mail
>> servers, my only option is to have a cheap daemon running as that
>> unprivileged user which will simply listen on a port for a command, do
>> the calculation, and return the answer.
> Honestly, the daemon approach is what I'd use, because you can then do
I ended up not doing all the caching, I just did one simple function.
Prep work on our CentOS 5.x boxen:
# Comes from RPMForge (i.e. Dag) repo
yum install perl-Log-Handler perl-Proc-Daemon
mkdir /var/log/helper /var/run/helper
chown vmail: /var/log/helper /var/run/helper
exim.conf macros:
OVERQUOTA_CHECK = ${readsocket{inet:localhost:8049}{CHECK_QUOTA
$local_part $domain}}
OVERQUOTA_DATA = ${readsocket{inet:localhost:8049}{SHOW_QUOTA
$local_part $domain}}
exim.conf rcpt acl somewhere before first accept statement:
defer domains = +local_domains
condition = ${if eq{OVERQUOTA_CHECK}{1} {yes}{no}}
message = The mailbox for $local_part@$domain is
full, deferring.
log_message = Recipient mailbox is full, deferring.
[root@ivwm53 ~]# cat /usr/local/bin/exim-policyd
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket;
use Log::Handler;
use Proc::Daemon;
#use Cache::Memcached;
use Getopt::Long;
use Data::Dumper;
my ($client,$log,%opts);
GetOptions( \%opts,
'debug',
'pidfile:s'
);
$0 = "exim-policyd";
$SIG{CHLD} = "IGNORE";
Proc::Daemon::Init();
$log = Log::Handler->new(
file => {
filename => "/var/log/helper/exim.log",
maxlevel => 'debug',
minlevel => 'emergency',
timeformat => "%Y/%m/%d %H:%M:%S",
message_layout => "%T [%L] %m"
} );
$opts{'pidfile'} ||= "/var/run/helper/exim-policyd.pid";
open(my $pidfh, '>', $opts{'pidfile'}) or do {
$log->critical("Unable to write '".$opts{'pidfile'}."': $!");
exit(1);
};
print $pidfh $$;
close $pidfh;
$log->info("Started, PID=$$");
# Future caching
#my $servers = [ 'mem51:11211', 'mem52:11211' ];
#my $m = Cache::Memcached->new( {
# 'servers' => $servers,
# 'namespace' => 'policyd',
# 'debug' => 0,
# 'compress_threshold' => 10_000
# } );
my $port = 8049;
my $socket = IO::Socket::INET->new(
Proto => "tcp",
LocalPort => $port,
Reuse => 1,
Listen => 1) or $log->error("ERROR: $!");
while($client=$socket->accept()) {
next if my $pid=fork;
$log->error("Cannot fork $!") unless defined $pid;
my $host = $client->peerhost();
$log->debug("Connection received from ".$host) if $opts{'debug'};
while (defined(my $buf=<$client>)) {
$log->debug("Received: $buf") if $opts{'debug'};
my ($func,@VALS) = parse_input($buf);
if ($func eq "CHECK_QUOTA") {
my $rv = is_quota_met(@VALS);
$client->send("$rv");
last;
}
elsif ($func eq "SHOW_QUOTA") {
my $rv = show_quota(@VALS);
$client->send("$rv\n");
}
elsif ($func eq "QUIT") {
$client->send("Bye!\n");
last;
}
}
$client->shutdown(2);
exit;
}
sub parse_input {
my @DATA = split(/\s/,shift());
$DATA[0] = uc($DATA[0]) if $DATA[0];
return(@DATA);
}
sub show_quota {
my ($size,$quota) = _calculate_quota(@_);
return("using=$size limit=$quota available=".($quota-$size));
}
sub is_quota_met {
my ($size,$quota) = _calculate_quota(@_);
# 1 => over quota, 0 => still has space left
return (($size > $quota) ? 1 : 0);
}
sub _calculate_quota {
# Prints out 0 if not over quota
# Prints out 1 if over quota (means "not ok")
my $local_part = shift();
my $domain = shift();
return(0,0) if (!defined $local_part || !defined $domain);
$log->debug("Asked for quota for $local_part\@$domain")
if $opts{'debug'};
### Construct your mailstore paths here ###
# 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";
### Finished constructing mailstore path ###
-f "$file" or return(0,0);
open(my $fh, "<", $file) or return(0,0);
my @lines = <$fh>;
close($fh);
return(0,0) if (scalar @lines == 0);
$log->debug("Read ".scalar @lines." lines from $file")
if $opts{'debug'};
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');
}
return(0,0) 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);
$log->info("Usage/Quota for $local_part\@$domain is $size / $quota");
return($size,$quota);
}
[root@ivwm53 ~]# cat /etc/init.d/exim-policyd
#!/bin/bash
#
# exim-policyd This shell script takes care of starting and stopping
exim-policyd
#
# chkconfig: 2345 80 30
# description: exim-policyd is a utility script
# processname: exim-policyd
# config: none
# pidfile: /var/run/helper/exim-policyd.pid
progdir="/usr/local/bin"
prog="exim-policyd"
vmail_user="vmail"
# Source function library.
. /etc/rc.d/init.d/functions
RETVAL=0
[ -x ${progdir}/${prog} ] || exit 0
start() {
# Start daemons.
echo -n "Starting $prog: "
daemon --user ${vmail_user} ${progdir}/${prog}
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/${prog}
return $RETVAL
}
stop() {
# Stop daemons.
echo -n "Shutting down $prog: "
killproc ${prog}
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/${prog}
return $RETVAL
}
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
restart|reload)
stop
start
RETVAL=$?
;;
condrestart)
if [ -f /var/lock/subsys/${prog} ]; then
stop
start
RETVAL=$?
fi
;;
status)
status ${prog}
RETVAL=$?
;;
*)
echo "Usage: $0 {start|stop|restart|condrestart|status}"
exit 1
esac
exit $RETVAL
[root@ivwm53 ~]# cat /etc/logrotate.d/exim-policyd
/var/log/helper/exim*log {
missingok
notifempty
sharedscripts
postrotate
/etc/init.d/exim-policyd restart > /dev/null 2>/dev/null || true
endscript
}
I hope this ends up being useful for someone.
...Todd
--
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