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(); } 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_to"); }