use warnings;
use warnings FATAL => 'utf8';
no warnings 'uninitialized';
use strict;
use re 'taint';
use IO::Socket;
use Time::HiRes ();
use vars qw($VERSION); $VERSION = 1.500;
use vars qw($log_level $socketname);
$log_level = 1;
$socketname = '/var/amavis/amavisd.sock';
sub sanitize_str {
my($str, $keep_eol) = @_;
my(%map) = ("\r" => '\\r', "\n" => '\\n', "\f" => '\\f', "\t" => '\\t',
"\b" => '\\b', "\e" => '\\e', "\\" => '\\\\');
if ($keep_eol) {
$str =~ s/([^\012\040-\133\135-\176])/ exists($map{$1}) ? $map{$1} :
sprintf(ord($1)>255 ? '\\x{%04x}' : '\\%03o', ord($1))/eg;
} else {
$str =~ s/([^\040-\133\135-\176])/ exists($map{$1}) ? $map{$1} :
sprintf(ord($1)>255 ? '\\x{%04x}' : '\\%03o', ord($1))/eg;
}
$str;
}
sub do_log($$) {
my($level, $errmsg) = @_;
print STDERR sanitize_str($errmsg),"\n" if $level <= $log_level;
}
sub proto_decode($) {
my($str) = @_;
$str =~ s/%([0-9a-fA-F]{2})/pack("C",hex($1))/eg;
$str;
}
sub proto_encode($@) {
my($attribute_name,@strings) = @_; local($1);
$attribute_name =~ s/([^0-9a-zA-Z_-])/sprintf("%%%02x",ord($1))/eg;
for (@strings) { s/([^\041-\044\046-\176])/sprintf("%%%02x",ord($1))/eg;
}
$attribute_name . '=' . join(' ',@strings);
}
sub ask_amavisd($$) {
my($sock,$query_ref) = @_;
my(@encoded_query) =
map { /^([^=]+)=(.*)\z/s; proto_encode($1,$2) } @$query_ref;
do_log(2,'> '.$_) for @encoded_query;
$sock->print( map { $_."\015\012" } (@encoded_query,'') )
or die "Can't write response to socket: $!";
$sock->flush or die "Can't flush on socket: $!";
my(%attr);
local($/) = "\015\012"; do_log(2,"waiting for response");
while(<$sock>) {
last if /^\015\012\z/; if (/^ ([^=\000\012]*?) (=|:[ \t]*) ([^\012]*?) \015\012 \z/xsi) {
my($attr_name) = proto_decode($1);
my($attr_val) = proto_decode($3);
if (!exists $attr{$attr_name}) { $attr{$attr_name} = [] }
push(@{$attr{$attr_name}}, $attr_val);
}
}
if (!defined($_) && $! != 0) { die "read from socket failed: $!" }
\%attr;
}
sub release_file($$$@) {
my($sock,$mail_file,$secret_id,@alt_recips) = @_;
my($fn_path,$fn_prefix,$mail_id,$fn_suffix,$part_tag); local($1,$2,$3,$4);
$part_tag = $1 if $mail_file =~ s/ \[ ( [^\]]* ) \] \z//xs;
if ($mail_file =~ m{^ ([^/].*/)? ([A-Z0-9][A-Z0-9._-]*[_-])?
([A-Z0-9][A-Z0-9_+-]{10}[A-Z0-9]) (\.gz)? \z}xsi) {
($fn_path,$fn_prefix,$mail_id,$fn_suffix) = ($1,$2,$3,$4);
} elsif ($mail_file =~ m{^ ([^/].*/)? () ([A-Za-z0-9$._=+-]+?) (\.gz)?\z}xs){
($fn_path,$fn_prefix,$mail_id,$fn_suffix) = ($1,$2,$3,$4); } else {
usage("Invalid quarantine ID: $mail_file");
}
my($quar_type) =
$fn_suffix eq '.gz' ? 'Z'
: $fn_path eq '' && $mail_id eq $mail_file ? 'Q' : 'F';
my($request_type) = $0 =~ /\breport\z/i ? 'report'
: $0 =~ /\brequeue\z/i ? 'requeue' : 'release';
my(@query) = (
"request=$request_type",
"quar_type=$quar_type",
"mail_id=$mail_id",
);
push(@query, "secret_id=$secret_id") if $secret_id ne '';
push(@query, "mail_file=$mail_file") if $quar_type =~ /^[FZB]\z/;
push(@query, "partition_tag=$part_tag") if $part_tag ne '';
if (@alt_recips) { push(@query, map {"recipient=$_"} @alt_recips);
}
my($attr_ref) = ask_amavisd($sock,\@query);
$attr_ref && %$attr_ref or die "Invalid response received";
for my $attr_name (keys %$attr_ref) {
for my $attr_val (@{$attr_ref->{$attr_name}})
{ do_log(2,"< $attr_name=$attr_val") }
}
do_log(0,$_) for (@{$attr_ref->{'setreply'}});
}
sub usage(;$) {
my($msg) = @_;
print STDERR $msg,"\n\n" if $msg ne '';
my($prog) = $0; $prog =~ s{^.*/(?=[^/]+\z)}{};
print STDERR "$prog version $VERSION\n";
die "Usage: \$ $prog mail_file [secret_id [alt_recip1 alt_recip2 ...]]\n".
" or to read request lines from stdin: \$ $prog -\n";
}
my($sock,$mail_file,$secret_id);
@ARGV >= 1 or usage("Not enough arguments");
$mail_file = shift(@ARGV); if ($mail_file eq '-') { @ARGV==0 or usage("Extra arguments after '-'") }
my($is_inet) = $socketname=~m{^/} ? 0 : 1; if ($is_inet) { $sock = IO::Socket::INET->new($socketname)
or die "Can't connect to INET socket $socketname: $!";
} else { $sock = IO::Socket::UNIX->new(Type => SOCK_STREAM)
or die "Can't create UNIX socket: $!";
$sock->connect( pack_sockaddr_un($socketname) )
or die "Can't connect to UNIX socket $socketname: $!";
}
if ($mail_file eq '-') {
undef $!;
while (<>) { chomp;
next if /^[ \t]*( my($mail_file, $secret_id, @alt_recips) = split(' ');
release_file($sock,$mail_file,$secret_id,@alt_recips);
}
$!==0 or die "Error reading from STDIN: $!";
} else {
$secret_id = $ARGV[0]=~/\@/ ? '' : shift(@ARGV);
release_file($sock,$mail_file,$secret_id,@ARGV);
}
$sock->close or die "Error closing socket: $!";
close(STDIN) or die "Error closing STDIN: $!";