qpsmtpd/plugins/queue/smtp-forward
Matt Simerson f9d84d94c7 Add Postfix XCLIENT support to smtp-forward plugin
manually merged in PR #2 from cventers

XCLIENT support allows Qpsmtpd to forward client information, such as
the IP address and HELO information, to Postfix such that it can use
that information in access control decisions and logging.

XCLIENT is documented here: http://www.postfix.org/XCLIENT_README.html

This patch adds a "xclient" argument to smtp-forward which enables the
use of the XCLIENT verb if it is advertised by the server smtp-forward
is delivering mail to.
2014-02-13 12:53:33 -08:00

169 lines
4.4 KiB
Perl

#!perl -w
=head1 NAME
smtp-forward
=head1 DESCRIPTION
This plugin forwards the mail via SMTP to a specified server, rather than
delivering the email locally.
It also supports the Postfix XCLIENT extension.
=head1 CONFIG
It takes one required parameter, the IP address or hostname to forward to.
queue/smtp-forward 10.2.2.2
Optionally you can also add a port:
queue/smtp-forward 10.2.2.2 9025
And a flag:
queue/smtp-forward 10.2.2.2 9025 xclient
=cut
use Net::SMTP;
use Net::Cmd qw//;
sub init {
my ($self, $qp, @args) = @_;
if (@args <= 0) {
die "No SMTP server specified in smtp-forward config";
};
if ($args[0] =~ /^([\.\w_-]+)$/) {
$self->{_smtp_server} = $1;
}
else {
die "Bad data in smtp server: $args[0]";
}
$self->{_smtp_port} = 25;
if (@args > 1 and $args[1] =~ /^(\d+)$/) {
$self->{_smtp_port} = $1;
}
for (my $i = 2; $i < @args; $i++) {
if ($args[$i] !~ /^(\w+)$/) {
$self->log(LOGWARN, "WARNING: Rejecting invalid flag");
next;
}
my $flag = lc($1);
$self->log(LOGWARN, "WARNING: Unknown flag $flag") unless $flag eq 'xclient';
$self->{_flags}{$flag} = 1;
}
}
sub hook_queue {
my ($self, $transaction) = @_;
$self->log(LOGINFO,
"forwarding to $self->{_smtp_server}:$self->{_smtp_port}");
my $smtp = Net::SMTP->new(
$self->{_smtp_server},
Port => $self->{_smtp_port},
Timeout => 60,
Hello => $self->qp->config("me"),
) || die $!;
my $xcret = $self->xclient($smtp);
return(DECLINED, $xcret) if defined $xcret;
$smtp->mail($transaction->sender->address || "")
or return (DECLINED, "Unable to queue message ($!)");
for ($transaction->recipients) {
$smtp->to($_->address)
or return (DECLINED, "Unable to queue message ($!)");
}
$smtp->data() or return (DECLINED, "Unable to queue message ($!)");
$smtp->datasend($transaction->header->as_string)
or return (DECLINED, "Unable to queue message ($!)");
$transaction->body_resetpos;
while (my $line = $transaction->body_getline) {
$smtp->datasend($line)
or return (DECLINED, "Unable to queue message ($!)");
}
$smtp->dataend() or return (DECLINED, "Unable to queue message ($!)");
my $qid = $smtp->message();
my @list = split(' ', $qid);
$qid = pop(@list);
$smtp->quit() or return (DECLINED, "Unable to queue message ($!)");
$self->log(LOGINFO, "finished queueing");
return (OK, "queued as $qid");
}
sub xclient {
my ($self, $smtp) = @_;
return unless $self->{_flags}{xclient};
my $parts = $smtp->supports('XCLIENT');
if (!defined($parts)) { # what parts do they want?
return "Unable to queue message (Server does not advertise XCLIENT support)";
};
my %haveparts;
for my $part (split(/\s+/, $parts)) {
next unless $part =~ /^(\w+)$/;
$haveparts{uc($part)} = 1;
}
my $conn = $self->qp->connection;
my @rparts;
if ($haveparts{NAME}) {
my $name = $conn->remote_host || '[UNAVAILABLE]';
$name = '[UNAVAILABLE]' if ($name eq 'Unknown');
push(@rparts, "NAME=$name");
}
if ($haveparts{ADDR}) {
my $ip = $conn->remote_ip;
push(@rparts, "ADDR=$ip");
}
if ($haveparts{PORT}) {
my $port = $conn->remote_port;
push(@rparts, "PORT=$port");
}
my $hello_name = $self->connection->hello_host;
$hello_name ||= '[UNAVAILABLE]';
if ($haveparts{HELO}) {
push(@rparts, "HELO=$hello_name");
}
my $hello = $conn->hello;
if ($haveparts{PROTO} && defined($hello)) {
my $proto = (uc($hello) eq 'EHLO') ? 'ESMTP' : 'SMTP';
push(@rparts, "PROTO=$proto");
}
while (scalar(@rparts)) {
my @items;
my $cursz = 0;
while (defined(my $item = $rparts[0])) {
my $len = length($item);
last if ($cursz + $len > 500);
$cursz += $len;
push(@items, shift @rparts);
}
last unless @items;
if ($smtp->command('XCLIENT', @items)->response() != Net::Cmd::CMD_OK) {
return "Unable to queue message (XCLIENT failed)";
}
}
$smtp->hello($hello_name) or return "Unable to queue message (HELLO after XCLIENT failed)";
return;
}