You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
150 lines
5.6 KiB
150 lines
5.6 KiB
#!@l_prefix@/bin/perl |
|
## |
|
## milter-header -- Header Insertion MILTER Program |
|
## Copyright (c) 2007-2009 Ralf S. Engelschall <rse@engelschall.com> |
|
## |
|
|
|
# requirements |
|
require 5.008; |
|
use warnings; |
|
use strict; |
|
use IO::All; |
|
use IO::File; |
|
use OSSP::cfg; |
|
use Date::Format; |
|
use Data::Dumper; |
|
use Sendmail::PMilter qw(:all); |
|
|
|
# command line handling |
|
my $cfgfile = $ARGV[0] || "@l_prefix@/etc/milter-header/milter-header.cfg"; |
|
|
|
# parse configuration file |
|
my $txt < io($cfgfile); |
|
my $cfg = new OSSP::cfg::simple; |
|
$cfg->parse($txt); |
|
my $tree = $cfg->unpack(); |
|
undef $cfg; |
|
|
|
# determine milter parameters |
|
my ($socket) = map { $_->[1] } grep { ref($_) eq 'ARRAY' and $_->[0] eq 'socket' } |
|
map { @{$_->[1]} } grep { ref($_) eq 'ARRAY' and $_->[0] eq 'milter' } @{$tree}; |
|
my ($logfile) = map { $_->[1] } grep { ref($_) eq 'ARRAY' and $_->[0] eq 'logfile' } |
|
map { @{$_->[1]} } grep { ref($_) eq 'ARRAY' and $_->[0] eq 'milter' } @{$tree}; |
|
my ($max_backlog) = map { $_->[1] } grep { ref($_) eq 'ARRAY' and $_->[0] eq 'max_backlog' } |
|
map { @{$_->[1]} } grep { ref($_) eq 'ARRAY' and $_->[0] eq 'milter' } @{$tree}; |
|
my ($max_childs) = map { $_->[1] } grep { ref($_) eq 'ARRAY' and $_->[0] eq 'max_childs' } |
|
map { @{$_->[1]} } grep { ref($_) eq 'ARRAY' and $_->[0] eq 'milter' } @{$tree}; |
|
my ($max_requests) = map { $_->[1] } grep { ref($_) eq 'ARRAY' and $_->[0] eq 'max_requests' } |
|
map { @{$_->[1]} } grep { ref($_) eq 'ARRAY' and $_->[0] eq 'milter' } @{$tree}; |
|
|
|
# determine header actions |
|
my @action = (); |
|
foreach my $action (map { $_->[1] } grep { ref($_) eq 'ARRAY' and $_->[0] eq 'action' } @{$tree}) { |
|
my @require = map { my $value = $_->[2]; $value =~ s/^\s+//s; $value =~ s/\s+$//; my $x = { -name => $_->[1], -value => $value }; $x } |
|
grep { ref($_) eq 'ARRAY' and $_->[0] eq 'require' } @{$action}; |
|
my @insert = map { my $value = $_->[2]; $value =~ s/^\s+//s; $value =~ s/\s+$//; my $x = { -name => $_->[1], -value => $value }; $x } |
|
grep { ref($_) eq 'ARRAY' and $_->[0] eq 'insert' } @{$action}; |
|
push(@action, { -require => [ @require ], -insert => [ @insert ] }); |
|
} |
|
|
|
# helper function for logging |
|
sub logbook ($;@) { |
|
my ($fmt, @args) = @_; |
|
my $log = new IO::File ">>$logfile"; |
|
if (defined $log) { |
|
$log->printf("%s %05d %s\n", time2str("%Y-%m-%d %H:%M:%S", time()), $$, sprintf($fmt, @args)); |
|
$log->close(); |
|
} |
|
} |
|
|
|
# catch run-time warnings from Sendmail::PMilter |
|
$SIG{__WARN__} = sub { |
|
my ($err) = @_; |
|
$err =~ s/\r?\n$//s; |
|
logbook("MILTER Warning: %s", $err); |
|
}; |
|
|
|
# create milter |
|
umask(002); |
|
unlink($socket); |
|
my $milter = new Sendmail::PMilter; |
|
my $dispatcher = Sendmail::PMilter::prefork_dispatcher( |
|
max_children => $max_childs, |
|
max_requests_per_child => $max_requests, |
|
); |
|
$milter->set_dispatcher($dispatcher); |
|
$milter->set_listen($max_backlog); |
|
$milter->setconn("local:$socket"); |
|
$milter->register( |
|
"milter-header", { |
|
'connect' => \&milter_cb_connect, |
|
'header' => \&milter_cb_header, |
|
'eom' => \&milter_cb_eom, |
|
}, |
|
(SMFI_V2_ACTS|SMFIF_ADDHDRS) |
|
); |
|
logbook("started"); |
|
$milter->main(); |
|
logbook("terminated"); |
|
exit(0); |
|
|
|
# milter hook: connect |
|
sub milter_cb_connect { |
|
my ($ctx, $hostname, $hostaddr) = @_; |
|
my $status = { -header => [] }; |
|
$ctx->setpriv($status); |
|
return SMFIS_CONTINUE; |
|
} |
|
|
|
# milter hook: header |
|
sub milter_cb_header { |
|
my ($ctx, $name, $value) = @_; |
|
my $status = $ctx->getpriv(); |
|
push(@{$status->{-header}}, { -name => $name, -value => $value }); |
|
return SMFIS_CONTINUE; |
|
}; |
|
|
|
# milter hook: end of message |
|
sub milter_cb_eom ($;@) { |
|
my ($ctx) = @_; |
|
my $status = $ctx->getpriv(); |
|
|
|
# loop over all header actions |
|
foreach my $action (@action) { |
|
# check number of fulfilled requirements |
|
my $fulfilled = 0; |
|
foreach my $require (@{$action->{-require}}) { |
|
#logbook("check requirement: header \"%s\" regex \"%s\"", $require->{-name}, $require->{-value}); |
|
foreach my $header (@{$status->{-header}}) { |
|
#logbook("compare with header \"%s\" value \"%s\"", $header->{-name}, $header->{-value}); |
|
my $regex = $require->{-value}; |
|
if ( uc($header->{-name}) eq uc($require->{-name}) |
|
and $header->{-value} =~ m/$regex/) { |
|
#logbook("requirement fulfilled by header \"%s\" value \"%s\"", $header->{-name}, $header->{-value}); |
|
$fulfilled++; |
|
} |
|
} |
|
} |
|
if ($fulfilled == scalar(@{$action->{-require}})) { |
|
# if _all_ requirements are fulfilled |
|
foreach my $insert (@{$action->{-insert}}) { |
|
my $value = $insert->{-value}; |
|
$value =~ s/\s*\n+$//s; |
|
$value =~ s/^\s+//s; |
|
$value =~ s/\n\s+/\n\t/sg; |
|
if (grep { $_->{-name} eq $insert->{-name} } @{$status->{-header}}) { |
|
#logbook("change existing header \"%s\" value \"%s\"", $insert->{-name}, $insert->{-value}); |
|
logbook("change existing header \"%s\"", $insert->{-name}); |
|
$ctx->chgheader($insert->{-name}, 0, $value); |
|
} |
|
else { |
|
#logbook("add new header \"%s\" value \"%s\"", $insert->{-name}, $insert->{-value}); |
|
logbook("add new header \"%s\"", $insert->{-name}); |
|
$ctx->addheader($insert->{-name}, $value); |
|
} |
|
} |
|
} |
|
} |
|
return SMFIS_CONTINUE; |
|
} |
|
|
|
|