2012-06-25 09:24:43 +02:00
|
|
|
#!/usr/bin/perl
|
|
|
|
|
|
|
|
=head1 NAME
|
|
|
|
|
|
|
|
qmail_deliverable - Check that the recipient address is deliverable
|
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
|
|
|
|
See the description of Qmail::Deliverable.
|
|
|
|
|
|
|
|
This B<qpsmtpd plugin> uses the client/server interface and needs a running
|
|
|
|
qmail-deliverabled. If no connection can be made, deliverability is simply
|
|
|
|
assumed.
|
|
|
|
|
|
|
|
The modules LWP (libwww-perl) and HTTP::Daemon, available from CPAN, are
|
|
|
|
required for qmail-deliverabled and Qmail::Deliverable::Client.
|
|
|
|
|
|
|
|
=head1 CONFIGURATION
|
|
|
|
|
|
|
|
=over 4
|
|
|
|
|
|
|
|
=item server host:port
|
|
|
|
|
|
|
|
Hostname (or IP address), and port (both!) of the qmail-deliverabled server. If
|
|
|
|
none is specified, the default (127.0.0.1:8998) is used.
|
|
|
|
|
|
|
|
=item server smtproutes:host:port
|
|
|
|
|
|
|
|
If the specification is prepended by the literal text C<smtproutes:>, then for
|
|
|
|
recipient domains listed in your /var/qmail/control/smtproutes use their
|
|
|
|
respective hosts for the check. For other domains, the given host is used. The
|
|
|
|
port has to be the same across all servers.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
qmail_deliverable server smtproutes:127.0.0.1:8998
|
|
|
|
|
|
|
|
Use "smtproutes:8998" (no second colon) to simply skip the deliverability
|
|
|
|
check for domains not listed in smtproutes.
|
|
|
|
|
2012-07-20 17:59:54 +02:00
|
|
|
=item vpopmail_ext [ 0 | 1 ]
|
|
|
|
|
|
|
|
Is vpopmail configured with the qmail-ext feature enabled? If so, this config
|
|
|
|
option must be enabled in order for user-ext@example.org addresses to work.
|
|
|
|
|
|
|
|
Default: 0
|
|
|
|
|
2013-03-23 07:17:45 +01:00
|
|
|
=item reject
|
|
|
|
|
|
|
|
karma reject [ 0 | 1 | connect | naughty ]
|
|
|
|
|
|
|
|
I<0> will not reject any connections.
|
|
|
|
|
|
|
|
I<1> will reject naughty senders.
|
|
|
|
|
|
|
|
I<connect> is the most efficient setting.
|
|
|
|
|
|
|
|
To reject at any other connection hook, use the I<naughty> setting and the
|
|
|
|
B<naughty> plugin.
|
|
|
|
|
2012-06-25 09:24:43 +02:00
|
|
|
=back
|
|
|
|
|
|
|
|
=head1 CAVEATS
|
|
|
|
|
|
|
|
Given a null host in smtproutes, the normal MX lookup should be used. This
|
|
|
|
plugin does not do this, because we don't want to harrass arbitrary servers.
|
|
|
|
|
|
|
|
Connection failure is *faked* when there is no smtproute.
|
|
|
|
|
|
|
|
=head1 LEGAL
|
|
|
|
|
|
|
|
This software is released into the public domain, and does not come with
|
|
|
|
warranty or guarantee of any kind. Use it at your own risk.
|
|
|
|
|
|
|
|
=head1 AUTHOR
|
|
|
|
|
|
|
|
Juerd <#####@juerd.nl>
|
|
|
|
|
|
|
|
=head1 SEE ALSO
|
|
|
|
|
|
|
|
L<Qmail::Deliverable>, L<qmail-deliverabled>, L<Qmail::Deliverable::Client>
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
2012-07-20 17:59:54 +02:00
|
|
|
#################################
|
|
|
|
#################################
|
|
|
|
|
|
|
|
BEGIN {
|
|
|
|
use FindBin qw($Bin $Script);
|
|
|
|
if (not $INC{'Qpsmtpd.pm'}) {
|
|
|
|
my $dir = '$PLUGINS_DIRECTORY';
|
|
|
|
-d and $dir = $_ for qw(
|
2013-04-21 06:50:39 +02:00
|
|
|
/home/qpsmtpd/plugins
|
|
|
|
/home/smtp/qpsmtpd/plugins
|
|
|
|
/usr/local/qpsmtpd/plugins
|
|
|
|
/usr/local/share/qpsmtpd/plugins
|
|
|
|
/usr/share/qpsmtpd/plugins
|
|
|
|
);
|
2012-07-20 17:59:54 +02:00
|
|
|
|
|
|
|
my $file = "the 'plugins' configuration file";
|
|
|
|
-f and $file = $_ for qw(
|
2013-04-21 06:50:39 +02:00
|
|
|
/home/qpsmtpd/config/plugins
|
|
|
|
/home/smtp/qpsmtpd/config/plugins
|
|
|
|
/usr/local/qpsmtpd/config/plugins
|
|
|
|
/usr/local/etc/qpsmtpd/plugins
|
|
|
|
/etc/qpsmtpd/plugins
|
|
|
|
);
|
2012-07-20 17:59:54 +02:00
|
|
|
|
|
|
|
# "die" would print "BEGIN failed" garbage
|
|
|
|
print STDERR <<"END";
|
|
|
|
|
|
|
|
This is a plugin for qpsmtpd and should not be run manually.
|
|
|
|
|
|
|
|
To install the plugin:
|
|
|
|
|
|
|
|
ln -s $Bin/$Script $dir/
|
|
|
|
|
|
|
|
And add "$Script server 127.0.0.1:8998" to $file, before rcpt_ok.
|
|
|
|
For configuration instructions, read "man $Script"
|
|
|
|
|
|
|
|
(Paths may vary.)
|
|
|
|
|
|
|
|
END
|
|
|
|
exit 255;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#################################
|
|
|
|
#################################
|
|
|
|
|
2012-06-25 09:24:43 +02:00
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
use Qpsmtpd::Constants;
|
2012-07-20 17:59:54 +02:00
|
|
|
use Qmail::Deliverable::Client qw(deliverable);
|
2012-06-25 09:24:43 +02:00
|
|
|
|
|
|
|
my %smtproutes;
|
2013-04-21 06:50:39 +02:00
|
|
|
my $shared_domain; # global variable to be closed over by the SERVER callback
|
2012-06-25 09:24:43 +02:00
|
|
|
|
|
|
|
sub register {
|
|
|
|
my ($self, $qp, @args) = @_;
|
|
|
|
if (@args % 2) {
|
|
|
|
$self->log(LOGWARN, "Odd number of arguments, using default config");
|
2013-04-21 06:50:39 +02:00
|
|
|
}
|
|
|
|
else {
|
2012-06-25 09:24:43 +02:00
|
|
|
my %args = @args;
|
2012-06-28 02:17:00 +02:00
|
|
|
if ($args{server} && $args{server} =~ /^smtproutes:/) {
|
2012-06-25 09:24:43 +02:00
|
|
|
|
|
|
|
my ($fallback, $port) = $args{server} =~ /:(?:(.*?):?)(\d+)/;
|
|
|
|
|
|
|
|
open my $fh, "/var/qmail/control/smtproutes"
|
2013-04-21 06:50:39 +02:00
|
|
|
or warn "Could not read smtproutes";
|
2012-06-25 09:24:43 +02:00
|
|
|
for (readline $fh) {
|
|
|
|
my ($domain, $mx) = /^(.*?) : \[? ( [^\]:\s]* )/x;
|
|
|
|
$smtproutes{$domain} = $mx;
|
|
|
|
}
|
|
|
|
|
|
|
|
$Qmail::Deliverable::Client::SERVER = sub {
|
|
|
|
my $server = _smtproute($shared_domain);
|
|
|
|
return "$server:$port" if defined $server;
|
|
|
|
return "$fallback:$port" if defined $fallback;
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
}
|
|
|
|
elsif ($args{server}) {
|
2012-06-25 09:24:43 +02:00
|
|
|
$Qmail::Deliverable::Client::SERVER = $args{server};
|
|
|
|
}
|
2012-07-20 17:59:54 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
if ($args{vpopmail_ext}) {
|
2012-07-20 17:59:54 +02:00
|
|
|
$Qmail::Deliverable::VPOPMAIL_EXT = $args{vpopmail_ext};
|
2013-04-21 06:50:39 +02:00
|
|
|
}
|
|
|
|
if ($args{reject}) {
|
2013-03-23 07:17:45 +01:00
|
|
|
$self->{_args}{reject} = $args{reject};
|
2013-04-21 06:50:39 +02:00
|
|
|
}
|
2012-06-25 09:24:43 +02:00
|
|
|
}
|
2012-07-20 17:59:54 +02:00
|
|
|
$self->register_hook("rcpt", "rcpt_handler");
|
2012-06-25 09:24:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub rcpt_handler {
|
|
|
|
my ($self, $transaction, $rcpt) = @_;
|
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
return DECLINED if $self->is_immune(); # requires QP 0.90+
|
2012-06-25 09:24:43 +02:00
|
|
|
|
|
|
|
my $address = $rcpt->address;
|
|
|
|
$self->log(LOGDEBUG, "Checking deliverability for recipient '$address'");
|
|
|
|
|
|
|
|
$shared_domain = $rcpt->host;
|
|
|
|
|
|
|
|
my $rv = deliverable $address;
|
|
|
|
|
|
|
|
if (not defined $rv or not length $rv) {
|
2012-07-20 17:59:54 +02:00
|
|
|
$self->log(LOGWARN, "error (unknown) checking '$address'");
|
2012-06-25 09:24:43 +02:00
|
|
|
return DECLINED;
|
|
|
|
}
|
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
my $k = 0; # known status code
|
|
|
|
$self->log(LOGINFO, "error, permission failure"), $k++ if $rv == 0x11;
|
|
|
|
$self->log(LOGINFO, "pass, qmail-command in dot-qmail"), $k++
|
|
|
|
if $rv == 0x12;
|
2012-07-20 17:59:54 +02:00
|
|
|
$self->log(LOGINFO, "pass, bouncesaying with program"), $k++ if $rv == 0x13;
|
2013-04-21 06:50:39 +02:00
|
|
|
if ($rv == 0x14) {
|
2012-11-19 06:29:33 +01:00
|
|
|
my $s = $transaction->sender->address;
|
2013-04-22 08:29:29 +02:00
|
|
|
if (!$s || $s eq '<>') {
|
|
|
|
$self->adjust_karma(-1);
|
2014-09-18 03:28:51 +02:00
|
|
|
return DENY, "mailing lists do not accept null senders";
|
2013-04-22 08:29:29 +02:00
|
|
|
};
|
2013-04-21 06:50:39 +02:00
|
|
|
$self->log(LOGINFO, "pass, ezmlm list");
|
|
|
|
$k++;
|
|
|
|
}
|
2012-06-25 09:24:43 +02:00
|
|
|
$self->log(LOGINFO, "Temporarily undeliverable: group/world writable"), $k++
|
2013-04-21 06:50:39 +02:00
|
|
|
if $rv == 0x21;
|
|
|
|
$self->log(LOGINFO, "Temporarily undeliverable: sticky home directory"),
|
|
|
|
$k++
|
|
|
|
if $rv == 0x22;
|
2012-07-20 17:59:54 +02:00
|
|
|
$self->log(LOGINFO, "error, $Qmail::Deliverable::Client::ERROR"), $k++
|
2013-04-21 06:50:39 +02:00
|
|
|
if $rv == 0x2f;
|
|
|
|
$self->log(LOGINFO, "pass, normal delivery"), $k++ if $rv == 0xf1;
|
|
|
|
$self->log(LOGINFO, "pass, vpopmail dir"), $k++ if $rv == 0xf2;
|
|
|
|
$self->log(LOGINFO, "pass, vpopmail alias"), $k++ if $rv == 0xf3;
|
|
|
|
$self->log(LOGINFO, "pass, vpopmail catchall"), $k++ if $rv == 0xf4;
|
|
|
|
$self->log(LOGINFO, "pass, vpopmail vuser"), $k++ if $rv == 0xf5;
|
|
|
|
$self->log(LOGINFO, "pass, vpopmail qmail-ext"), $k++ if $rv == 0xf6;
|
|
|
|
$self->log(LOGINFO, "error, SHOULD NOT HAPPEN"), $k++ if $rv == 0xfe;
|
|
|
|
$self->log(LOGINFO, "fail, address not local"), $k++ if $rv == 0xff;
|
|
|
|
|
|
|
|
if ($rv) {
|
2012-07-20 17:59:54 +02:00
|
|
|
$self->log(LOGINFO, sprintf("error, unknown: 0x%02x", $rv)) if not $k;
|
|
|
|
return DECLINED;
|
2013-04-21 06:50:39 +02:00
|
|
|
}
|
2012-06-25 09:24:43 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
$self->adjust_karma(-1);
|
|
|
|
return $self->get_reject("Sorry, no mailbox by that name. qd (#5.1.1)");
|
2012-06-25 09:24:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub _smtproute {
|
|
|
|
my ($domain) = @_;
|
|
|
|
my @parts = split /\./, $domain;
|
|
|
|
if (exists $smtproutes{$domain}) {
|
|
|
|
return undef if $smtproutes{$domain} eq "";
|
|
|
|
return $smtproutes{$domain};
|
|
|
|
}
|
|
|
|
for (reverse 1 .. @parts) {
|
|
|
|
my $wildcard = join "", map ".$_", @parts[-$_ .. -1];
|
|
|
|
if (exists $smtproutes{$wildcard}) {
|
|
|
|
return undef if $smtproutes{$wildcard} eq "";
|
|
|
|
return $smtproutes{$wildcard};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return undef if not exists $smtproutes{""};
|
|
|
|
return undef if $smtproutes{""} eq "";
|
|
|
|
return $smtproutes{""};
|
|
|
|
}
|
|
|
|
|