[PATCH] imported check_qmail_deliverable

[email protected] (Matt Simerson)
Newsgroups perl.qpsmtpd
Message-ID <[email protected]>
requires Qmail::Deliverable, which is a modern replacement for the
apparently popular but no longer available check_deliver plugin.
---
STATUS                          |    5 -
plugins/check_qmail_deliverable |  202 +++++++++++++++++++++++++++++++++++++++
2 files changed, 202 insertions(+), 5 deletions(-)
create mode 100755 plugins/check_qmail_deliverable

diff --git a/STATUS b/STATUS
index 81cf0df..78ef005 100644
--- a/STATUS
+++ b/STATUS
@@ -18,11 +18,6 @@ Roadmap
 - Add user configuration plugin infrastructure
   - Add plugin API for checking if a local email address is valid

-
- - Include the popular check_delivery[1] functionality via the user API
-   [1] until then get it from 
-      http://www.openminddev.net/files/qpsmtpd/plugins/check_delivery/
-
 - Add API to reject individual recipients after the RCPT has been
   accepted and generate individual bounce messages.

diff --git a/plugins/check_qmail_deliverable b/plugins/check_qmail_deliverable
new file mode 100755
index 0000000..6230e66
--- /dev/null
+++ b/plugins/check_qmail_deliverable
@@ -0,0 +1,202 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+check_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:
+
+    check_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.
+
+=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
+
+#################################
+#################################
+
+BEGIN {
+    use FindBin qw($Bin $Script);
+    if (not $INC{'Qpsmtpd.pm'}) {
+        my $dir = '$PLUGINS_DIRECTORY';
+        -d and $dir = $_ for qw(
+            /home/qpsmtpd/plugins
+            /home/smtp/qpsmtpd/plugins
+            /usr/local/qpsmtpd/plugins
+            /usr/local/share/qpsmtpd/plugins
+            /usr/share/qpsmtpd/plugins
+        );
+
+        my $file = "the 'plugins' configuration file";
+        -f and $file = $_ for qw(
+            /home/qpsmtpd/config/plugins
+            /home/smtp/qpsmtpd/config/plugins
+            /usr/local/qpsmtpd/config/plugins
+            /usr/local/etc/qpsmtpd/plugins
+            /etc/qpsmtpd/plugins
+        );
+
+        # "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;
+    }
+}
+
+#################################
+#################################
+
+use Qmail::Deliverable::Client qw(deliverable);
+use strict;
+
+my %smtproutes;
+my $shared_domain;  # global variable to be closed over by the SERVER callback
+
+sub register {
+    my ($self, $qp, @args) = @_;
+    if (@args % 2) {
+        $self->log(LOGWARN, "Odd number of arguments, using default config");
+    } else {
+        my %args = @args;
+        if ($args{server} =~ /^smtproutes:/) {
+
+            my ($fallback, $port) = $args{server} =~ /:(?:(.*?):?)(\d+)/;
+
+            open my $fh, "/var/qmail/control/smtproutes"
+                or warn "Could not read smtproutes";
+            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;
+            };
+
+        } elsif ($args{server}) {
+            $Qmail::Deliverable::Client::SERVER = $args{server};
+        }
+    }
+    $self->register_hook("rcpt", "rcpt_handler");
+}
+
+sub rcpt_handler {
+    my ($self, $transaction, $rcpt) = @_;
+
+    my $address = $rcpt->address;
+    $self->log(LOGINFO, "Checking deliverability for recipient '$address'");
+
+    $shared_domain = $rcpt->host;
+
+    my $rv = deliverable $address;
+
+    if (not defined $rv or not length $rv) {
+        $self->log(LOGWARN, "Unknown error.");
+        return DECLINED;
+    }
+
+    my $k = 0;  # known status code
+    $self->log(LOGINFO, "Permission failure"),              $k++ if $rv == 0x11;
+    $self->log(LOGINFO, "qmail-command in dot-qmail"),      $k++ if $rv == 0x12;
+    $self->log(LOGINFO, "bouncesaying with program"),       $k++ if $rv == 0x13;
+    $self->log(LOGINFO, "Temporarily undeliverable: group/world writable"), $k++
+                                                                 if $rv == 0x21;
+    $self->log(LOGINFO, "Temporarily undeliverable: sticky home directory"),$k++
+                                                                 if $rv == 0x22;
+    $self->log(LOGINFO, "Error: $Qmail::Deliverable::Client::ERROR"), $k++
+                                                                 if $rv == 0x2f;
+    $self->log(LOGINFO, "Normal delivery"),                 $k++ if $rv == 0xf1;
+    $self->log(LOGINFO, "Deliverable through vpopmail"),    $k++ if $rv == 0xf2;
+    $self->log(LOGINFO, "SHOULD NOT HAPPEN"),               $k++ if $rv == 0xfe;
+    $self->log(LOGINFO, "Address is not local"),            $k++ if $rv == 0xff;
+
+    $self->log(LOGINFO, sprintf("Unknown: 0x%02x", $rv)) if $rv and not $k;
+
+    return DECLINED if $rv;
+    return DENY, "Sorry, no mailbox here by that name. qd (#5.1.1)";
+}
+
+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{""};
+}
+
-- 
1.7.9.4
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.