2025-02-06 20:23:36 +01:00
use strict;
use warnings;
use Qpsmtpd::Constants;
use Qpsmtpd::DSN;
use DBI;
sub register {
my ($self, $qp) = (shift, shift);
# some default values
$self->{database} = $qp->config("fedhq_mysql_database") || "mail";
$self->{host} = $qp->config("fedhq_mysql_host") || "localhost";
$self->{port} = $qp->config("fedhq_mysql_port") || "3306";
$self->{user} = $qp->config("fedhq_mysql_user") || "qpsmtpd";
$self->{pass} = $qp->config("fedhq_mysql_password");
$self->createDSN();
}
sub createStatements
{
my $self = shift;
2025-02-07 11:10:18 +01:00
$self->{rcpt_sth} = $self->{dbh}->prepare("select username,dovecot_server from email_address where alias=? and domain=?");
2025-02-06 20:23:36 +01:00
2025-02-07 11:00:38 +01:00
$self->{fetch_all_sth} = $self->{dbh}->prepare("select username,dovecot_server from email_address where domain=? and isFetchAll=1");
2025-02-08 19:02:24 +01:00
$self->{fetch_dovecot_details_sth} = $self->{dbh}->prepare("select username, hostname, port from dovecot_server RIGHT JOIN email_address ON dovecot_server.name = email_address.dovecot_server where alias=? and domain=?");
2025-02-07 11:00:38 +01:00
2025-02-06 20:23:36 +01:00
}
sub connect
{
my $self = shift;
$self->{dbh} = DBI->connect($self->{dsn}, $self->{user}, $self->{pass}, { RaiseError => 0}) ||
$self->log(LOGERROR, "error connecting to DB " . $DBI::errstr);
if ($self->{dbh}->err())
{
$self->log(LOGERROR, "error connecting to DB: " . $self->{dbh}->errstr());
$self->{dbh}=undef;
}
}
2025-02-07 11:00:38 +01:00
sub disconnect
{
my $self = shift;
$self->{dbh}->disconnect() if ($self->{dbh});
}
2025-02-06 20:23:36 +01:00
sub createDSN
{
my $self = shift;
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});
}
2025-02-08 19:02:24 +01:00
sub updateTransactionWithSmtpInfo
2025-02-07 11:00:38 +01:00
{
2025-02-08 19:02:24 +01:00
my ($self, $transaction, $recipient) = @_;
2025-02-07 11:00:38 +01:00
2025-02-08 19:02:24 +01:00
my $queue = $transaction->notes("queue") || {};
my $rcpt = $recipient->user . "@" . $recipient->host;
$queue->{$rcpt}->{destination} = "relay";
$queue->{$rcpt}->{protocol} = "smtp";
$queue->{$rcpt}->{host} = $self->qp->config("relay_server");
$transaction->notes("queue", $queue);
2025-02-07 11:23:59 +01:00
2025-02-08 19:02:24 +01:00
return 0;
}
2025-02-07 11:00:38 +01:00
2025-02-08 19:02:24 +01:00
sub updateTransactionWithLmtpInfo
{
my ($self, $transaction, $recipient) = @_;
2025-02-07 11:31:22 +01:00
2025-02-08 19:02:24 +01:00
my $result = $self->{fetch_dovecot_details_sth}->execute($recipient->user, $recipient->host);
2025-02-07 11:00:38 +01:00
if (!$result)
{
2025-02-08 19:02:24 +01:00
$self->log(LOGERROR, "Failed to fetch dovecot information from the database"); return -1;
2025-02-07 11:00:38 +01:00
}
if ($self->{fetch_dovecot_details_sth}->rows == 0)
{
2025-02-08 19:02:24 +01:00
$self->log(LOGERROR, "no dovecot information in database found");
return -1;
}
elsif ($self->{fetch_dovecot_details_sth}->rows > 1)
{
$self->log(LOGERROR, "too many dovecot entries in the database");
return -1;
2025-02-07 11:00:38 +01:00
}
2025-02-08 19:02:24 +01:00
my $row = $self->{fetch_dovecot_details_sth}->fetchrow_hashref;
2025-02-07 11:00:38 +01:00
2025-02-08 19:02:24 +01:00
my $username = $row->{username}
2025-02-07 11:00:38 +01:00
my $hostname = $row->{hostname};
my $port = $row->{port};
2025-02-08 19:02:24 +01:00
my $queue = $transaction->notes("queue") || {};
my $rcpt = $recipient->user . "@" . $recipient->host;
$queue->{$rcpt}->{destination} = "local";
$queue->{$rcpt}->{protocol} = "lmtp";
$queue->{$rcpt}->{host} = $hostname;
$queue->{$rcpt}->{port} = $port;
$queue->{$rcpt}->{user} = $username;
$transaction->notes("queue", $queue);
2025-02-07 11:10:18 +01:00
2025-02-07 11:00:38 +01:00
$self->log(LOGNOTICE, "Setting LMTP server to dovecot on $hostname:$port for user: $username");
return 1;
}
2025-02-08 19:02:24 +01:00
sub CheckRecipient
{
my ($self, $recipient) = @_;
2025-02-06 20:23:36 +01:00
$self->connect();
$self->createStatements();
my $result = $self->{rcpt_sth}->execute($recipient->user, $recipient->host);
2025-02-07 11:00:38 +01:00
if (!$result)
{
2025-02-08 19:02:24 +01:00
$self->log(LOGERROR, "Failed to fetch recipient information from the database"); return -1;
2025-02-07 11:00:38 +01:00
}
if ($self->{rcpt_sth}->rows == 1)
2025-02-06 20:23:36 +01:00
{
$self->log(LOGDEBUG, " found recipient in database");
2025-02-07 11:00:38 +01:00
2025-02-08 19:02:24 +01:00
return 1;
2025-02-06 20:23:36 +01:00
}
2025-02-07 11:00:38 +01:00
elsif ($self->{rcpt_sth}->rows > 1)
{
$self->log(LOGERROR,"found multiple users for same recipient in database. Something wrong with database? (" . $recipient->user . '@' . $recipient->host . ")" );
2025-02-08 19:02:24 +01:00
return -2;
2025-02-07 11:00:38 +01:00
}
2025-02-06 20:23:36 +01:00
2025-02-08 19:02:24 +01:00
return 0;
}
sub hook_quit {
my ($self, $transaction) = @_;
$self->disconnect();
}
sub hook_rcpt {
my ($self, $transaction, $recipient) = @_;
$self->log(LOGNOTICE, "Recipient: " . $recipient->user . "@" . $recipient->host);
2025-02-06 20:23:36 +01:00
2025-02-08 19:02:24 +01:00
return DECLINED unless $recipient->host && $recipient->user;
my $rcptValid = $self->CheckRecipient($recipient);
if ($rcptValid == 1 )
2025-02-06 20:23:36 +01:00
{
2025-02-08 19:02:24 +01:00
$self->updateTransactionWithLmtpInfo($transaction, $recipient) || return DENYSOFT, "Temporary failure, try again later";
2025-02-06 20:23:36 +01:00
return OK;
}
2025-02-08 19:02:24 +01:00
elseif( $self->is_immune())
{
$self->updateTransactionWithSmtpInfo($transaction, $recipient) || return DENYSOFT, "Temporary failure, try again later";
return OK;
}
elseif ($rcptValid == -1)
{
return DENYSOFT, "Temporary failure, try again later";
}
2025-02-06 20:23:36 +01:00
2025-02-08 19:02:24 +01:00
return Qpsmtpd::DSN->relaying_denied();
2025-02-06 20:23:36 +01:00
}