2023-12-26 15:34:34 +01:00
|
|
|
use Qpsmtpd::Constants;
|
|
|
|
use DBI;
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# called when initializing the plugin
|
|
|
|
#
|
|
|
|
sub init {
|
|
|
|
my ($self, $qp, %args) = @_;
|
|
|
|
|
|
|
|
# some default values
|
|
|
|
$self->{database} = "mail";
|
|
|
|
$self->{host} = "localhost";
|
2023-12-26 16:36:12 +01:00
|
|
|
$self->{port} = "3306";
|
2023-12-26 15:34:34 +01:00
|
|
|
$self->{user} = undef;
|
|
|
|
$self->{pass} = undef;
|
|
|
|
$self->{rawquery}= undef;
|
|
|
|
|
|
|
|
# the cache timeout in seconds
|
|
|
|
$self->{cacheTimeout} = 500; #seconds
|
|
|
|
|
|
|
|
# a map for caching database repsonses
|
|
|
|
# done on a per email basis
|
|
|
|
$self->{cache} = {};
|
|
|
|
|
|
|
|
# parse the configuration file
|
|
|
|
$self->parseConfig();
|
|
|
|
|
|
|
|
$self->createQuery();
|
|
|
|
|
|
|
|
$self->createDSN();
|
|
|
|
}
|
|
|
|
|
|
|
|
sub createDSN
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
|
2023-12-27 11:19:58 +01:00
|
|
|
my $dsn = "dbi:MariaDB(RaiseError=>0):database=" . $self->{database} . ";host=" . $self->{host} . ";port=" . $self->{port};
|
2023-12-26 15:34:34 +01:00
|
|
|
$self->{dsn} = $dsn;
|
2023-12-26 16:55:41 +01:00
|
|
|
|
2023-12-26 16:58:52 +01:00
|
|
|
$self->log(LOGDEBUG, "created DSN " . $self->{dsn});
|
2023-12-27 11:29:48 +01:00
|
|
|
|
|
|
|
# try to parse the dsn to ensure it is valid
|
|
|
|
my @data = DBI->parse_dsn($self->{dsn});
|
|
|
|
|
|
|
|
if (@data == 0)
|
|
|
|
{
|
|
|
|
$self->log(LOGERROR, "DSN " . $self->{dsn} . " not valid");
|
|
|
|
$self->{dsn}="";
|
|
|
|
}
|
|
|
|
|
2023-12-26 15:34:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
sub createQuery
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
|
|
|
|
#
|
|
|
|
# !u the user part of the rcpt
|
|
|
|
# !d the domain part of the rcpt
|
|
|
|
|
|
|
|
my @params;
|
|
|
|
my @variables = split /!/,$self->{rawquery};
|
|
|
|
|
|
|
|
for my $v (@variables)
|
|
|
|
{
|
|
|
|
if ($v eq "u")
|
|
|
|
{
|
|
|
|
push(@params,"user");
|
|
|
|
}
|
|
|
|
elsif ($v eq "d")
|
|
|
|
{
|
|
|
|
push(@params,"domain")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$self->{sqlparams}=\@params;
|
|
|
|
|
|
|
|
my $query = $self->{rawquery};
|
|
|
|
$query =~s/!u/?/g;
|
|
|
|
$query =~s/!d/?/g;
|
|
|
|
|
|
|
|
$self->{sqlquery}=$query;
|
|
|
|
|
2023-12-26 16:55:41 +01:00
|
|
|
$self->log(LOGDEBUG, "created query " . $self->{sqlquery});
|
2023-12-26 15:34:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
sub prepareParams
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
my $recipient = shift;
|
|
|
|
|
|
|
|
my @params;
|
|
|
|
|
|
|
|
for my $p (@params)
|
|
|
|
{
|
|
|
|
if ($p eq "user")
|
|
|
|
{
|
|
|
|
push(@params,$recipient->user);
|
|
|
|
}
|
|
|
|
elsif($p eq "domain")
|
|
|
|
{
|
|
|
|
push(@params,$recipient->user);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return @params;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub parseConfig
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
|
|
|
|
# parse the configuration file of this plugin line by line
|
|
|
|
my @config = $self->qp->config('rcpt_mysql');
|
|
|
|
|
|
|
|
for my $line (@config)
|
|
|
|
{
|
2023-12-26 16:55:41 +01:00
|
|
|
$self->log(LOGDEBUG, "config line " . $line);
|
2023-12-26 15:34:34 +01:00
|
|
|
my @value = split /:=/, $line;
|
|
|
|
|
|
|
|
my $key = lc($value[0]);
|
|
|
|
|
|
|
|
if ( $key eq "database")
|
|
|
|
{
|
|
|
|
$self->{database} = $value[1];
|
|
|
|
}
|
|
|
|
elsif ( $key eq "host")
|
|
|
|
{
|
|
|
|
$self->{host} = $value[1];
|
|
|
|
}
|
|
|
|
elsif ( $key eq "user")
|
|
|
|
{
|
|
|
|
$self->{user} = $value[1];
|
|
|
|
}
|
|
|
|
elsif ( $key eq "pass")
|
|
|
|
{
|
|
|
|
$self->{pass} = $value[1];
|
|
|
|
}
|
|
|
|
elsif ( $key eq "port")
|
|
|
|
{
|
|
|
|
$self->{port} = $value[1];
|
|
|
|
}
|
|
|
|
elsif ( $key eq "cachetimeout")
|
|
|
|
{
|
|
|
|
$self->{cacheTimeout} = $value[1];
|
|
|
|
}
|
|
|
|
elsif ( $key eq "pass")
|
|
|
|
{
|
|
|
|
$self->{pass} = $value[1];
|
|
|
|
}
|
|
|
|
elsif ( $key eq "query")
|
|
|
|
{
|
|
|
|
$self->{rawquery} = $value[1];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
warn("Key \"" . $key . "\" is an unknown configuration option");
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
sub askCache
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
my $recipient = shift;
|
|
|
|
|
|
|
|
# combine to a normal recipient
|
|
|
|
my $rcpt = lc $recipient->user . '@' . lc $recipient->host;
|
|
|
|
|
|
|
|
# check if there is something in the cache for this recipient
|
|
|
|
if ($self->{cache}->{$rcpt} && $self->{cache}->{$rcpt}->{time}-time() <= $self->{cacheTimeout}) {
|
|
|
|
if ($self->{cache}->{$rcpt}->{hit} == 0 )
|
|
|
|
{
|
|
|
|
return DECLINED;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub askDatabase
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
my $recipient = shift;
|
2023-12-27 11:19:58 +01:00
|
|
|
|
2023-12-27 11:29:48 +01:00
|
|
|
if (length($self->{dsn}) == 0)
|
|
|
|
{
|
|
|
|
$self->log(LOGERROR, "DSN not valid not checking recipient in database");
|
|
|
|
return DECLINED;
|
|
|
|
}
|
|
|
|
|
2023-12-29 23:04:21 +01:00
|
|
|
my $dsn = $self->{dsn};
|
|
|
|
|
|
|
|
my $dbh = DBI::connect($dsn, $self->{user}, $self->{pass},{ RaiseError => 1, PrintError => 0 });
|
2023-12-26 15:34:34 +01:00
|
|
|
|
|
|
|
if ($dbh->err())
|
|
|
|
{
|
2023-12-27 11:29:48 +01:00
|
|
|
$self->log(LOGERROR, "error connecting to DB: " . $dbh->errstr());
|
|
|
|
return DECLINED;
|
2023-12-26 15:34:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
my $sth = $dbh->prepare($self->{sqlquery});
|
|
|
|
if ($sth->err())
|
|
|
|
{
|
2023-12-27 11:29:48 +01:00
|
|
|
$self->log(LOGERROR, "error preparing query: " . $sth->errstr());
|
2023-12-29 23:04:21 +01:00
|
|
|
return DECLINED ;
|
2023-12-26 15:34:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$sth->execute($self->prepareParams($recipient));
|
|
|
|
|
|
|
|
my $ret = DECLINED;
|
|
|
|
|
|
|
|
if ($sth->rows > 0)
|
|
|
|
{
|
|
|
|
$ret = OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
#
|
|
|
|
# plugin hook called if rcpt_to is issued within the smtp session
|
|
|
|
#
|
|
|
|
sub rcpt_to {
|
|
|
|
my ($self, $transaction, $recipient) = @_;
|
|
|
|
|
|
|
|
# some basic validations
|
|
|
|
return DECLINED unless $recipient->host && $recipient->user;
|
|
|
|
|
|
|
|
# ask the cache for results
|
|
|
|
my $cacheResult = $self->askCache($recipient);
|
|
|
|
return $cacheResult if defined($cacheResult);
|
|
|
|
|
|
|
|
# ask the database for results
|
|
|
|
return $self->askDatabase($recipient);
|
|
|
|
}
|
|
|
|
|
|
|
|
#
|
|
|
|
# called for registering all the hooks
|
|
|
|
#
|
|
|
|
sub register {
|
|
|
|
my ($self, $qp, %args) = @_;
|
|
|
|
|
|
|
|
|
2023-12-26 16:36:12 +01:00
|
|
|
$self->register_hook("rcpt", "rcpt_to");
|
2023-12-26 15:34:34 +01:00
|
|
|
|
|
|
|
}
|