From c4f5490abdd5ab7fcd6ed1dc24f29530663a9283 Mon Sep 17 00:00:00 2001 From: Dominik Meyer Date: Tue, 26 Dec 2023 15:34:34 +0100 Subject: [PATCH] ADD: initial version of rcpt_mysql plugin --- plugins/rcpt_mysql | 232 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 plugins/rcpt_mysql diff --git a/plugins/rcpt_mysql b/plugins/rcpt_mysql new file mode 100644 index 0000000..90d61a6 --- /dev/null +++ b/plugins/rcpt_mysql @@ -0,0 +1,232 @@ +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} = "3301"; + $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; + + my $dsn = "DBI:MariaDB:database=" . $self->{database} . ";host=" . $self->{host} . ";port=" . $self->{port}; + $self->{dsn} = $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; + +} + +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) + { + 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; + my $dbh = DBI::connect($self->{dsn}, $self->{user}, $self->{pass},{ RaiseError => 0, PrintError => 0 }); + + if ($dbh->err()) + { + warn("error connecting to DB: " . $dbh->errstr()); + return DENYSOFT; + } + + my $sth = $dbh->prepare($self->{sqlquery}); + if ($sth->err()) + { + warn("error preparing query: " . $sth->errstr()); + return DENYSOFT; + } + + $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_handler"); + +}