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; $self->{rcpt_sth} = $self->{dbh}->prepare("select username,dovecot_server from email_address where alias=? and domain=?"); $self->{fetch_all_sth} = $self->{dbh}->prepare("select username,dovecot_server from email_address where domain=? and isFetchAll=1"); $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=?"); } 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; } } sub disconnect { my $self = shift; $self->{dbh}->disconnect() if ($self->{dbh}); } 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}); } sub updateTransactionWithSmtpInfo { my ($self, $transaction, $recipient) = @_; 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); return 0; } sub updateTransactionWithLmtpInfo { my ($self, $transaction, $recipient) = @_; my $result = $self->{fetch_dovecot_details_sth}->execute($recipient->user, $recipient->host); if (!$result) { $self->log(LOGERROR, "Failed to fetch dovecot information from the database"); return -1; } if ($self->{fetch_dovecot_details_sth}->rows == 0) { $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; } my $row = $self->{fetch_dovecot_details_sth}->fetchrow_hashref; my $username = $row->{username}; my $hostname = $row->{hostname}; my $port = $row->{port}; 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); $self->log(LOGNOTICE, "Setting LMTP server to dovecot on $hostname:$port for user: $username"); return 1; } sub CheckRecipient { my ($self, $recipient) = @_; $self->connect(); $self->createStatements(); my $result = $self->{rcpt_sth}->execute($recipient->user, $recipient->host); if (!$result) { $self->log(LOGERROR, "Failed to fetch recipient information from the database"); return -1; } if ($self->{rcpt_sth}->rows == 1) { $self->log(LOGDEBUG, " found recipient in database"); return 1; } 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 . ")" ); return -2; } 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); return DECLINED unless $recipient->host && $recipient->user; my $rcptValid = $self->CheckRecipient($recipient); if ($rcptValid == 1 ) { $self->updateTransactionWithLmtpInfo($transaction, $recipient) || return DENYSOFT, "Temporary failure, try again later"; return OK; } elsif( $self->is_immune()) { $self->updateTransactionWithSmtpInfo($transaction, $recipient) || return DENYSOFT, "Temporary failure, try again later"; return OK; } elsif ($rcptValid == -1) { return DENYSOFT, "Temporary failure, try again later"; } return Qpsmtpd::DSN->relaying_denied(); }