File Coverage

qpsmtpd-plugin/qmail_deliverable
Criterion Covered Total %
statement 55 86 63.9
branch 43 66 65.1
condition 6 15 40.0
subroutine 8 10 80.0
pod n/a
total 112 177 63.2


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             =head1 NAME
4              
5             qmail_deliverable - Check that the recipient address is deliverable
6              
7             =head1 DESCRIPTION
8              
9             See the description of Qmail::Deliverable.
10              
11             This B uses the client/server interface and needs a running
12             qmail-deliverabled. If no connection can be made, deliverability is simply
13             assumed.
14              
15             The modules LWP (libwww-perl) and HTTP::Daemon, available from CPAN, are
16             required for qmail-deliverabled and Qmail::Deliverable::Client.
17              
18             =head1 CONFIGURATION
19              
20             =over 4
21              
22             =item server host:port
23              
24             Hostname (or IP address), and port (both!) of the qmail-deliverabled server. If
25             none is specified, the default (127.0.0.1:8998) is used.
26              
27             =item server smtproutes:host:port
28              
29             If the specification is prepended by the literal text C, then for
30             recipient domains listed in your /var/qmail/control/smtproutes use their
31             respective hosts for the check. For other domains, the given host is used. The
32             port has to be the same across all servers.
33              
34             Example:
35              
36             qmail_deliverable server smtproutes:127.0.0.1:8998
37              
38             Use "smtproutes:8998" (no second colon) to simply skip the deliverability
39             check for domains not listed in smtproutes.
40              
41             =item vpopmail_ext [ 0 | 1 ]
42              
43             Is vpopmail configured with the qmail-ext feature enabled? If so, this config
44             option must be enabled in order for user-ext@example.org addresses to work.
45              
46             Default: 0
47              
48             =back
49              
50             =head1 CAVEATS
51              
52             Given a null host in smtproutes, the normal MX lookup should be used. This
53             plugin does not do this, because we don't want to harrass arbitrary servers.
54              
55             Connection failure is *faked* when there is no smtproute.
56              
57             =head1 LEGAL
58              
59             This software does not come with warranty or guarantee of any kind. Use it at
60             your own risk.
61              
62             This software may be redistributed under the terms of the GPL, LGPL, modified
63             BSD, or Artistic license, or any of the other OSI approved licenses listed at
64             http://www.opensource.org/licenses/alphabetical. Distribution is allowed under
65             all of these these licenses, or any smaller subset of multiple or just one of
66             these licenses.
67              
68             When using a packaged version, please refer to the package metadata to see
69             under which license terms it was distributed. Alternatively, a distributor may
70             choose to replace the LICENSE section of the documentation and/or include a
71             LICENSE file to reflect the license(s) they chose to redistribute under.
72              
73             =head1 AUTHORS
74              
75             =over 4
76              
77             =item *
78              
79             Juerd Waalboer <#####@juerd.nl> (original author)
80              
81             =item *
82              
83             Matt Simerson (current maintainer)
84              
85             =back
86              
87             =head1 CONTRIBUTORS
88              
89             =over 4
90              
91             =item *
92              
93             Martin Sluka
94              
95             =back
96              
97             =head1 SEE ALSO
98              
99             L, L, L
100              
101             =cut
102              
103             #################################
104             #################################
105              
106             BEGIN {
107 1     1   129352 use FindBin qw($Bin $Script);
  1         1533  
  1         314  
108 1 50   1   34 if ( not $INC{'Qpsmtpd.pm'} ) {
109 0         0 my $dir = '$PLUGINS_DIRECTORY';
110 0   0     0 -d and $dir = $_ for qw(
111             /home/qpsmtpd/plugins
112             /home/smtp/qpsmtpd/plugins
113             /usr/local/qpsmtpd/plugins
114             /usr/local/share/qpsmtpd/plugins
115             /usr/share/qpsmtpd/plugins
116             );
117              
118 0         0 my $file = "the 'plugins' configuration file";
119 0   0     0 -f and $file = $_ for qw(
120             /home/qpsmtpd/config/plugins
121             /home/smtp/qpsmtpd/config/plugins
122             /usr/local/qpsmtpd/config/plugins
123             /usr/local/etc/qpsmtpd/plugins
124             /etc/qpsmtpd/plugins
125             );
126              
127             # "die" would print "BEGIN failed" garbage
128 0         0 print STDERR <<"END";
129              
130             This is a plugin for qpsmtpd and should not be run manually.
131              
132             To install the plugin:
133              
134             ln -s $Bin/$Script $dir/
135              
136             And add "$Script server 127.0.0.1:8998" to $file, before rcpt_ok.
137             For configuration instructions, read "man $Script"
138              
139             (Paths may vary.)
140              
141             END
142 0         0 exit 255;
143             }
144             }
145              
146             #################################
147             #################################
148              
149 1     1   6 use strict;
  1         2  
  1         27  
150 1     1   14 use warnings;
  1         2  
  1         50  
151              
152 1     1   7 use Qpsmtpd::Constants;
  1         2  
  1         132  
153 1     1   537 use Qmail::Deliverable::Client qw(deliverable);
  1         2  
  1         816  
154              
155             my %smtproutes;
156             my $shared_domain; # global variable to be closed over by the SERVER callback
157              
158             sub register {
159 1     1   177689 my ( $self, $qp, @args ) = @_;
160 1 50       6 if ( @args % 2 ) {
161 0         0 $self->log( LOGWARN, "Odd number of arguments, using default config" );
162             }
163             else {
164 1         4 my %args = @args;
165 1 50 33     12 if ( $args{server} && $args{server} =~ /^smtproutes:/ ) {
    50          
166              
167 0         0 my ( $fallback, $port ) = $args{server} =~ /:(?:(.*?):?)(\d+)/;
168              
169 0 0       0 open my $fh, "/var/qmail/control/smtproutes"
170             or warn "Could not read smtproutes";
171 0         0 for ( readline $fh ) {
172 0         0 my ( $domain, $mx ) = /^(.*?) : \[? ( [^\]:\s]* )/x;
173 0         0 $smtproutes{$domain} = $mx;
174             }
175              
176             $Qmail::Deliverable::Client::SERVER = sub {
177 0     0   0 my $server = _smtproute($shared_domain);
178 0 0       0 return "$server:$port" if defined $server;
179 0 0       0 return "$fallback:$port" if defined $fallback;
180 0         0 return;
181 0         0 };
182              
183             }
184             elsif ( $args{server} ) {
185 1         3 $Qmail::Deliverable::Client::SERVER = $args{server};
186             }
187              
188 1 50       5 if ( $args{vpopmail_ext} ) {
189 0         0 $Qmail::Deliverable::VPOPMAIL_EXT = $args{vpopmail_ext};
190             }
191             }
192 1         6 $self->register_hook( "rcpt", "rcpt_handler" );
193             }
194              
195             sub rcpt_handler {
196 20     20   55466 my ( $self, $transaction, $rcpt ) = @_;
197              
198             #return DECLINED if $self->is_immune(); # requires QP 0.90+
199              
200 20         58 my $address = $rcpt->address;
201 20         153 $self->log( LOGDEBUG, "Checking deliverability for recipient '$address'" );
202              
203 20         239 $shared_domain = $rcpt->host;
204              
205 20         81 my $rv = deliverable $address;
206              
207 20 100 66     155 if ( not defined $rv or not length $rv ) {
208 1         4 $self->log( LOGWARN, "error (unknown) checking '$address'" );
209 1         9 return DECLINED;
210             }
211              
212 19         29 my $k = 0; # known status code
213 19 100       45 $self->log( LOGINFO, "error, permission failure" ), $k++ if $rv == 0x11;
214 19 100       55 $self->log( LOGINFO, "pass, qmail-command in dot-qmail" ), $k++ if $rv == 0x12;
215 19 100       55 $self->log( LOGINFO, "pass, bouncesaying with program" ), $k++ if $rv == 0x13;
216 19 100       46 if ( $rv == 0x14 ) {
217 3         12 my $s = $transaction->sender->address;
218 3 100 100     48 return ( DENY, "fail, mailing lists do not accept null senders" )
219             if ( !$s || $s eq '<>' );
220 1         4 $self->log( LOGINFO, "pass, ezmlm list" );
221 1         9 $k++;
222             }
223 17 100       38 $self->log( LOGINFO, "Temporarily undeliverable: group/world writable" ), $k++
224             if $rv == 0x21;
225 17 100       41 $self->log( LOGINFO, "Temporarily undeliverable: sticky home directory" ), $k++
226             if $rv == 0x22;
227 17 100       62 $self->log( LOGINFO, "error, $Qmail::Deliverable::Client::ERROR" ), $k++
228             if $rv == 0x2f;
229 17 100       40 $self->log( LOGINFO, "pass, normal delivery" ), $k++ if $rv == 0xf1;
230 17 100       63 $self->log( LOGINFO, "pass, vpopmail dir" ), $k++ if $rv == 0xf2;
231 17 100       42 $self->log( LOGINFO, "pass, vpopmail alias" ), $k++ if $rv == 0xf3;
232 17 100       43 $self->log( LOGINFO, "pass, vpopmail catchall" ), $k++ if $rv == 0xf4;
233 17 100       51 $self->log( LOGINFO, "pass, vpopmail vuser" ), $k++ if $rv == 0xf5;
234 17 100       41 $self->log( LOGINFO, "pass, vpopmail qmail-ext" ), $k++ if $rv == 0xf6;
235 17 100       40 $self->log( LOGINFO, "error, SHOULD NOT HAPPEN" ), $k++ if $rv == 0xfe;
236 17 100       49 $self->log( LOGINFO, "fail, address not local" ), $k++ if $rv == 0xff;
237              
238 17 100       42 if ($rv) {
239 16 100       39 $self->log( LOGINFO, sprintf( "error, unknown: 0x%02x", $rv ) ) if not $k;
240 16         138 return DECLINED;
241             }
242              
243 1         21 return ( DENY, "Sorry, no mailbox by that name. qd (#5.1.1)" );
244             }
245              
246             sub _smtproute {
247 0     0     my ($domain) = @_;
248 0           my @parts = split /\./, $domain;
249 0 0         if ( exists $smtproutes{$domain} ) {
250 0 0         return undef if $smtproutes{$domain} eq "";
251 0           return $smtproutes{$domain};
252             }
253 0           for ( reverse 1 .. @parts ) {
254 0           my $wildcard = join "", map ".$_", @parts[ -$_ .. -1 ];
255 0 0         if ( exists $smtproutes{$wildcard} ) {
256 0 0         return undef if $smtproutes{$wildcard} eq "";
257 0           return $smtproutes{$wildcard};
258             }
259             }
260 0 0         return undef if not exists $smtproutes{""};
261 0 0         return undef if $smtproutes{""} eq "";
262 0           return $smtproutes{""};
263             }
264