Non puoi selezionare più di 25 argomenti
Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
268 righe
7.2 KiB
268 righe
7.2 KiB
#!/usr/bin/perl |
|
## |
|
## pb4sd -- POP-before-SMTP Daemon |
|
## Copyright (c) 2001 Ralf S. Engelschall <rse@engelschall.com> |
|
## |
|
## This program is derived from Bennett Todd <bet@rahul.net>'s |
|
## pop-before-smtp 1.28 (http://people.oven.com/bet/pop-before-smtp/) |
|
## |
|
|
|
use File::Tail; |
|
use DB_File; |
|
use Net::Netmask; |
|
use Date::Parse; |
|
use Getopt::Long; |
|
use Fcntl ':flock'; |
|
use POSIX qw(getpid setsid); |
|
use IO; |
|
|
|
# logfile parsing patters |
|
my $pattern = { |
|
# QPopper 4.0.x (OpenPKG) |
|
'qpopper' => |
|
'^(... .. ..:..:..) (?:<\S+>|\S+) (?:/\S+?)?q?popper\S*\[\d+\]: ' . |
|
'\([^)]*\) POP login by user "[^"]+" at \([^)]+\) (\d+.\d+.\d+.\d+)$', |
|
# Qpopper 3.x |
|
'popper3' => |
|
'^(\w{3} \w{3} \d{2} \d{2}:\d{2}:\d{2} \d{4}) \[\d+\] ' . |
|
' Stats:\s+\w+ \d \d \d \d [\w\.]+ (\d+\.\d+\.\d+\.\d+)', |
|
# UW ipop3d/imapd |
|
'ipop3d' => |
|
'^(... .. ..:..:..) \S+ (?:ipop3d|imapd)\[\d+\]: ' . |
|
'(?:Login|Authenticated|Auth) user=\S+ host=(?:\S+)?\[(\d+\.\d+\.\d+\.\d+)\](?: nmsgs=\d+/\d+)?$', |
|
# GNU pop3d |
|
'popd3d' => |
|
'^(... .. ..:..:..) \S+ gnu-pop3d\[\d+\]: ' . |
|
'User .* logged in with mailbox .* from (\d+\.\d+\.\d+\.\d+)$', |
|
# Cyrus |
|
'cyrus' => |
|
'^(... .. ..:..:..) \S+ (?:pop3d|imapd)\[\d+\]: ' . |
|
'login: \S*\[(\d+\.\d+\.\d+\.\d+)\] \S+ \S+', |
|
# Courier-IMAP |
|
'courier' => |
|
'^(... .. ..:..:..) \S+ imaplogin: ' . |
|
'LOGIN, user=\S+, ip=\[(\d+\.\d+\.\d+\.\d+)\]$', |
|
# Qmail pop3d |
|
'pop3d' => |
|
'^(... .. ..:..:..) \S+ vpopmail\[\d+\]: ' . |
|
'vchkpw: login \[\S+\] from (\d+\.\d+\.\d+\.\d+)$', |
|
# cucipop |
|
'cucipop' => |
|
'^(... .. ..:..:..) \S+ cucipop\[\d+\]: \S+ ' . |
|
'(\d+\.\d+\.\d+\.\d+) \d+, \d+ \(\d+\), \d+ \(\d+\)', |
|
# popa3d |
|
'popa3d' => |
|
'^(... .. ..:..:..) \S+ popa3d\[\d+\]: Authentication passed for \S+ -- \[(\d+.\d+.\d+.\d+)\]$', |
|
}; |
|
|
|
# parameters and their defaults |
|
my $daemon = 0; |
|
my $infile = 'qpopper.log'; |
|
my $dbfile = 'qpopper'; |
|
my $popserver = 'qpopper'; |
|
my @exclude = (); |
|
my $grace = 1800; |
|
my $logfile = 'pb4s.log'; |
|
my $pidfile = 'pb4s.pid'; |
|
|
|
# option parsing |
|
GetOptions( |
|
"daemon!" => \$daemon, |
|
"infile=s" => \$infile, |
|
"dbfile=s" => \$dbfile, |
|
"popserver=s" => \$popserver, |
|
"exclude=s@" => \@exclude, |
|
"grace=i" => \$grace, |
|
"logfile=s" => \$logfile, |
|
"pidfile=s" => \$pidfile, |
|
) or die "Usage: p4bs [--daemon]\n" . |
|
" [--infile=filename]\n" . |
|
" [--dbfile=filename]\n" . |
|
" [--popserver=type]\n" . |
|
" [--exclude=a.b.c.d/x]\n" . |
|
" [--grace=seconds]\n" . |
|
" [--logfile=filename]\n" . |
|
" [--pidfile=filename]\n"; |
|
|
|
# make sure filenames are specified as absolute paths |
|
die "--infile requires absolute filename" if ($infile !~ m|^/|); |
|
|
|
# make sure it is a known pop server |
|
die "unknown pop server '$popserver'" if (not defined($pattern->{$popserver})); |
|
|
|
# make sure input logfile exists |
|
die "logfile '$infile' not found" if (not -f $infile); |
|
|
|
# create tail object |
|
my $lf = File::Tail->new( |
|
name => $infile, |
|
maxinterval => 2, |
|
interval => 1, |
|
adjustafter => 3, |
|
resetafter => 30 |
|
) || die "unable to create tail object for '$infile'"; |
|
|
|
# create network block |
|
my $nt = {}; |
|
foreach my $exclude (@exclude) { |
|
my $nb = new Net::Netmask ($exclude) || die; |
|
$nb->storeNetblock($nt); |
|
} |
|
|
|
# create DB hash file |
|
my %db; |
|
my $dbh = tie %db, 'DB_File', $dbfile, O_CREAT|O_RDWR, 0666, $DB_HASH |
|
|| die "cannot open DB file '$dbfile': $!\n"; |
|
|
|
# create DB hash file descriptor |
|
my $fd = $dbh->fd; |
|
open(DB_FH, "+<&=$fd") || die "cannot open '$dbfile' filehandle: $!\n"; |
|
|
|
# delete database |
|
flock(DB_FH, LOCK_EX) || die "(exclusive) lock failed: $!\n"; |
|
foreach $k (keys(%db)) { |
|
delete $db{$k}; |
|
} |
|
flock(DB_FH, LOCK_UN) or die "unlock failed: $!\n"; |
|
|
|
# open logfile |
|
my $log = new IO::File ">>$logfile" || die; |
|
$log->autoflush(1); |
|
|
|
# establish signal handlers |
|
$SIG{__DIE__} = sub { |
|
$log->print("[".localtime(time())."] DIE error=".join(" ", @_)."\n"); |
|
die @_; |
|
}; |
|
|
|
# start/stop logging |
|
$log->print("[".localtime(time())."] STARTUP\n"); |
|
$SIG{'QUIT'} = $SIG{'INT'} = $SIG{'TERM'} = sub { |
|
$log->print("[".localtime(time())."] SHUTDOWN\n"); |
|
exit(0); |
|
}; |
|
|
|
# optionally daemonize |
|
if ($daemon) { |
|
my ($pid, $sess_id, $i); |
|
|
|
# fork and exit parent |
|
if ($pid = fork()) { |
|
exit(0); |
|
} |
|
|
|
# detach from the terminal |
|
$sess_id = POSIX::setsid(); |
|
|
|
# prevent possibility of acquiring a controling terminal |
|
$SIG{'HUP'} = 'IGNORE'; |
|
if ($pid = fork()) { |
|
exit(0); |
|
} |
|
|
|
# create pidfile |
|
open(PID, ">$pidfile") || die; |
|
printf(PID "%d\n", POSIX::getpid()); |
|
close(PID); |
|
|
|
# change working directory |
|
chdir("/"); |
|
|
|
# clear file creation mask |
|
umask(0); |
|
|
|
# close stdio file descriptors |
|
close(STDIN); |
|
close(STDOUT); |
|
close(STDERR); |
|
|
|
# re-open stdio file descriptors to /dev/null |
|
open(STDIN, "+>/dev/null"); |
|
open(STDOUT, "+>&STDIN"); |
|
open(STDERR, "+>&STDIN"); |
|
} |
|
|
|
my $t = {}; # ip to expire table |
|
my $q = []; # ip/expire stack |
|
while (1) { |
|
my $line = $lf->read(); |
|
my $now = time(); |
|
if ($line =~ m/$pattern->{$popserver}/o) { |
|
my ($timestamp, $ipaddr) = ($1, $2); |
|
|
|
# log recognition of entry |
|
$log->print("[".localtime($now)."] SEE client=".$ipaddr. |
|
" login=".localtime(str2time($timestamp))."\n"); |
|
|
|
# calculate expire time |
|
my $expire = str2time($timestamp) || next; |
|
$expire += $grace; |
|
|
|
# skip if grace period is already expired or ip is excluded |
|
next if ($expire < $now); |
|
next if (findNetblock($ipaddr, $nt)); |
|
|
|
# push ip/expire onto stack |
|
push @{$q}, [$ipaddr, $expire]; |
|
|
|
# remember ip |
|
my $already_enabled = exists($t->{$ipaddr}); |
|
$t->{$ipaddr} = $expire; |
|
|
|
# skip if ip was already enabled |
|
next if $already_enabled; |
|
|
|
# lock database |
|
flock(DB_FH, LOCK_EX); |
|
|
|
# add entry to database |
|
$db{$ipaddr} = "OK"; |
|
$log->print("[".localtime($now)."] ADD client=".$ipaddr." logout=".localtime($expire)."\n"); |
|
|
|
# purge expired database entries |
|
while ($q->[0][1] < $now) { |
|
if ($q->[0][1] == $t->{$q->[0][0]}) { |
|
$log->print("[".localtime($now)."] DEL client=".$q->[0][0]." logout=".localtime($q->[0][1])."\n"); |
|
delete $t->{$q->[0][0]}; |
|
delete $db{$q->[0][0]}; |
|
} |
|
shift @q; |
|
} |
|
|
|
# synchronize database |
|
$dbh->sync(); |
|
|
|
# unlock database |
|
flock(DB_FH, LOCK_UN); |
|
} |
|
} |
|
|
|
__DATA__ |
|
|
|
=pod |
|
|
|
=head1 NAME |
|
|
|
pb4sd -- POP-before-SMTP Daemon |
|
|
|
=head1 SYNOPSIS |
|
|
|
B<p4bsd> |
|
[--daemon] |
|
[--infile=filename] |
|
[--dbfile=filename] |
|
[--popserver=type] |
|
[--exclude=a.b.c.d/x] |
|
[--grace=seconds] |
|
[--logfile=filename] |
|
[--pidfile=filename] |
|
|
|
=head1 DESCRIPTION |
|
|
|
B<pb4sd> is a little daemon program which watches a POP/IMAP server's |
|
logfile for successful client authentications and writes the |
|
corresponding IP addresses into a Berkeley-DB hash file. This hash file |
|
then can be used by the MTA to allow relaying access. |
|
|
|
=cut |
|
|
|
|