qpsmtpd/plugins/rcpt_mysql
2024-01-02 23:36:02 +01:00

269 lines
5.4 KiB
Plaintext

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";
$self->{port} = "3306";
$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();
$self->tryToConnect();
}
sub createDSN
{
my $self = shift;
#my $dsn = "DBI:mysql:database=$database;host=$hostname;port=$port";
my $dsn = "DBI:mariadb:database=" . $self->{database} . ";host=" . $self->{host} . ";port=" . $self->{port};
$self->{dsn} = $dsn;
# 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}="";
}
$self->log(LOGDEBUG, "created DSN " . $self->{dsn});
}
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;
$self->log(LOGDEBUG, "created query " . $self->{sqlquery});
}
sub tryToConnect
{
my $self = shift;
my $dbh = DBI::connect($self->{dsn}, $self->{user}, $self->{pass});
if ($dbh->err())
{
$self->log(LOGERROR, "error connecting to DB: " . $dbh->errstr());
return DECLINED;
}
$dbh->disconnect;
}
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)
{
$self->log(LOGDEBUG, "config line " . $line);
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;
if (length($self->{dsn}) == 0)
{
$self->log(LOGERROR, "DSN not valid not checking recipient in database");
return DECLINED;
}
my $dsn = $self->{dsn};
my $dbh = DBI::connect($dsn, $self->{user}, $self->{pass});
if ($dbh->err())
{
$self->log(LOGERROR, "error connecting to DB: " . $dbh->errstr());
return DECLINED;
}
my $sth = $dbh->prepare($self->{sqlquery});
if ($sth->err())
{
$self->log(LOGERROR, "error preparing query: " . $sth->errstr());
return DECLINED ;
}
$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) = @_;
$self->register_hook("rcpt", "rcpt_to");
}