| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # <@LICENSE> | 
| 2 |  |  |  |  |  |  | # Licensed to the Apache Software Foundation (ASF) under one or more | 
| 3 |  |  |  |  |  |  | # contributor license agreements.  See the NOTICE file distributed with | 
| 4 |  |  |  |  |  |  | # this work for additional information regarding copyright ownership. | 
| 5 |  |  |  |  |  |  | # The ASF licenses this file to you under the Apache License, Version 2.0 | 
| 6 |  |  |  |  |  |  | # (the "License"); you may not use this file except in compliance with | 
| 7 |  |  |  |  |  |  | # the License.  You may obtain a copy of the License at: | 
| 8 |  |  |  |  |  |  | # | 
| 9 |  |  |  |  |  |  | #     http://www.apache.org/licenses/LICENSE-2.0 | 
| 10 |  |  |  |  |  |  | # | 
| 11 |  |  |  |  |  |  | # Unless required by applicable law or agreed to in writing, software | 
| 12 |  |  |  |  |  |  | # distributed under the License is distributed on an "AS IS" BASIS, | 
| 13 |  |  |  |  |  |  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
| 14 |  |  |  |  |  |  | # See the License for the specific language governing permissions and | 
| 15 |  |  |  |  |  |  | # limitations under the License. | 
| 16 |  |  |  |  |  |  | # </@LICENSE> | 
| 17 |  |  |  |  |  |  |  | 
| 18 | 41 |  |  | 41 |  | 236 | use strict;  # make Test::Perl::Critic happy | 
|  | 41 |  |  |  |  | 65 |  | 
|  | 41 |  |  |  |  | 1724 |  | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | use strict; | 
| 22 |  |  |  |  |  |  | use warnings; | 
| 23 | 41 |  |  | 41 |  | 230 | # use bytes; | 
|  | 41 |  |  |  |  | 68 |  | 
|  | 41 |  |  |  |  | 837 |  | 
| 24 | 41 |  |  | 41 |  | 181 | use re 'taint'; | 
|  | 41 |  |  |  |  | 70 |  | 
|  | 41 |  |  |  |  | 1262 |  | 
| 25 |  |  |  |  |  |  |  | 
| 26 | 41 |  |  | 41 |  | 226 | use Mail::SpamAssassin::Conf; | 
|  | 41 |  |  |  |  | 72 |  | 
|  | 41 |  |  |  |  | 1274 |  | 
| 27 |  |  |  |  |  |  | use Mail::SpamAssassin::PerMsgStatus; | 
| 28 | 41 |  |  | 41 |  | 220 | use Mail::SpamAssassin::AsyncLoop; | 
|  | 41 |  |  |  |  | 93 |  | 
|  | 41 |  |  |  |  | 949 |  | 
| 29 | 41 |  |  | 41 |  | 209 | use Mail::SpamAssassin::Constants qw(:ip); | 
|  | 41 |  |  |  |  | 68 |  | 
|  | 41 |  |  |  |  | 807 |  | 
| 30 | 41 |  |  | 41 |  | 196 | use Mail::SpamAssassin::Util qw(untaint_var am_running_on_windows); | 
|  | 41 |  |  |  |  | 65 |  | 
|  | 41 |  |  |  |  | 995 |  | 
| 31 | 41 |  |  | 41 |  | 191 |  | 
|  | 41 |  |  |  |  | 72 |  | 
|  | 41 |  |  |  |  | 4515 |  | 
| 32 | 41 |  |  | 41 |  | 275 | use File::Spec; | 
|  | 41 |  |  |  |  | 75 |  | 
|  | 41 |  |  |  |  | 1825 |  | 
| 33 |  |  |  |  |  |  | use IO::Socket; | 
| 34 | 41 |  |  | 41 |  | 242 | use POSIX ":sys_wait_h"; | 
|  | 41 |  |  |  |  | 73 |  | 
|  | 41 |  |  |  |  | 991 |  | 
| 35 | 41 |  |  | 41 |  | 2741 |  | 
|  | 41 |  |  |  |  | 47180 |  | 
|  | 41 |  |  |  |  | 459 |  | 
| 36 | 41 |  |  | 41 |  | 22773 |  | 
|  | 41 |  |  |  |  | 94 |  | 
|  | 41 |  |  |  |  | 352 |  | 
| 37 |  |  |  |  |  |  | our $KNOWN_BAD_DIALUP_RANGES; # Nothing uses this var??? | 
| 38 |  |  |  |  |  |  | our $LAST_DNS_CHECK; | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | # use very well-connected domains (fast DNS response, many DNS servers, | 
| 41 |  |  |  |  |  |  | # geographical distribution is a plus, TTL of at least 3600s) | 
| 42 |  |  |  |  |  |  | our @EXISTING_DOMAINS = qw{ | 
| 43 |  |  |  |  |  |  | adelphia.net | 
| 44 |  |  |  |  |  |  | akamai.com | 
| 45 |  |  |  |  |  |  | apache.org | 
| 46 |  |  |  |  |  |  | cingular.com | 
| 47 |  |  |  |  |  |  | colorado.edu | 
| 48 |  |  |  |  |  |  | comcast.net | 
| 49 |  |  |  |  |  |  | doubleclick.com | 
| 50 |  |  |  |  |  |  | ebay.com | 
| 51 |  |  |  |  |  |  | gmx.net | 
| 52 |  |  |  |  |  |  | google.com | 
| 53 |  |  |  |  |  |  | intel.com | 
| 54 |  |  |  |  |  |  | kernel.org | 
| 55 |  |  |  |  |  |  | linux.org | 
| 56 |  |  |  |  |  |  | mit.edu | 
| 57 |  |  |  |  |  |  | motorola.com | 
| 58 |  |  |  |  |  |  | msn.com | 
| 59 |  |  |  |  |  |  | sourceforge.net | 
| 60 |  |  |  |  |  |  | sun.com | 
| 61 |  |  |  |  |  |  | w3.org | 
| 62 |  |  |  |  |  |  | yahoo.com | 
| 63 |  |  |  |  |  |  | }; | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  | our $IS_DNS_AVAILABLE = undef; | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | #Removed $VERSION per BUG 6422 | 
| 68 |  |  |  |  |  |  | #$VERSION = 'bogus';     # avoid CPAN.pm picking up razor ver | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | ########################################################################### | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | BEGIN { | 
| 73 |  |  |  |  |  |  | # some trickery. Load these modules right here, if possible; that way, if | 
| 74 |  |  |  |  |  |  | # the module exists, we'll get it loaded now.  Very useful to avoid attempted | 
| 75 |  |  |  |  |  |  | # loads later (which will happen).  If we do a fork(), we could wind up | 
| 76 |  |  |  |  |  |  | # attempting to load these modules in *every* subprocess. | 
| 77 |  |  |  |  |  |  | # | 
| 78 |  |  |  |  |  |  | # # We turn off strict and warnings, because Net::DNS and Razor both contain | 
| 79 |  |  |  |  |  |  | # # crud that -w complains about (perl 5.6.0).  Not that this seems to work, | 
| 80 |  |  |  |  |  |  | # # mind ;) | 
| 81 |  |  |  |  |  |  | # no strict; | 
| 82 |  |  |  |  |  |  | # local ($^W) = 0; | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | no warnings; | 
| 85 |  |  |  |  |  |  | eval { | 
| 86 | 41 |  |  | 41 |  | 8632 | require Net::DNS; | 
|  | 41 |  |  |  |  | 68 |  | 
|  | 41 |  |  |  |  | 3089 |  | 
| 87 | 41 |  |  | 41 |  | 113 | require Net::DNS::Resolver; | 
| 88 | 41 |  |  |  |  | 17173 | }; | 
| 89 | 41 |  |  |  |  | 2000159 | eval { | 
| 90 |  |  |  |  |  |  | require MIME::Base64; | 
| 91 | 41 |  |  |  |  | 354 | }; | 
| 92 | 41 |  |  |  |  | 383 | eval { | 
| 93 |  |  |  |  |  |  | require IO::Socket::UNIX; | 
| 94 | 41 |  |  |  |  | 317 | }; | 
| 95 | 41 |  |  |  |  | 176702 | }; | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  | ########################################################################### | 
| 98 |  |  |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | my ($self, $rule, $set, $type, $host, $subtest) = @_; | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | $host =~ s/\.\z//s;  # strip a redundant trailing dot | 
| 102 | 0 |  |  | 0 | 0 | 0 | my $key = "dns:$type:$host"; | 
| 103 |  |  |  |  |  |  | my $existing_ent = $self->{async}->get_lookup($key); | 
| 104 | 0 |  |  |  |  | 0 |  | 
| 105 | 0 |  |  |  |  | 0 | # only make a specific query once | 
| 106 | 0 |  |  |  |  | 0 | if (!$existing_ent) { | 
| 107 |  |  |  |  |  |  | my $ent = { | 
| 108 |  |  |  |  |  |  | key => $key, | 
| 109 | 0 | 0 |  |  |  | 0 | zone => $host,  # serves to fetch other per-zone settings | 
| 110 | 0 |  |  |  |  | 0 | type => "DNSBL-".$type, | 
| 111 |  |  |  |  |  |  | sets => [ ],  # filled in below | 
| 112 |  |  |  |  |  |  | rules => [ ], # filled in below | 
| 113 |  |  |  |  |  |  | # id is filled in after we send the query below | 
| 114 |  |  |  |  |  |  | }; | 
| 115 |  |  |  |  |  |  | $existing_ent = $self->{async}->bgsend_and_start_lookup( | 
| 116 |  |  |  |  |  |  | $host, $type, undef, $ent, | 
| 117 |  |  |  |  |  |  | sub { my($ent, $pkt) = @_; $self->process_dnsbl_result($ent, $pkt) }, | 
| 118 |  |  |  |  |  |  | master_deadline => $self->{master_deadline} ); | 
| 119 |  |  |  |  |  |  | } | 
| 120 | 0 |  |  | 0 |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 121 | 0 |  |  |  |  | 0 | if ($existing_ent) { | 
| 122 |  |  |  |  |  |  | # always add set | 
| 123 |  |  |  |  |  |  | push @{$existing_ent->{sets}}, $set; | 
| 124 | 0 | 0 |  |  |  | 0 |  | 
| 125 |  |  |  |  |  |  | # sometimes match or always match | 
| 126 | 0 |  |  |  |  | 0 | if (defined $subtest) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 127 |  |  |  |  |  |  | $self->{dnspost}->{$set}->{$subtest} = $rule; | 
| 128 |  |  |  |  |  |  | } else { | 
| 129 | 0 | 0 |  |  |  | 0 | push @{$existing_ent->{rules}}, $rule; | 
| 130 | 0 |  |  |  |  | 0 | } | 
| 131 |  |  |  |  |  |  |  | 
| 132 | 0 |  |  |  |  | 0 | $self->{rule_to_rblkey}->{$rule} = $key; | 
|  | 0 |  |  |  |  | 0 |  | 
| 133 |  |  |  |  |  |  | } | 
| 134 |  |  |  |  |  |  | } | 
| 135 | 0 |  |  |  |  | 0 |  | 
| 136 |  |  |  |  |  |  | # TODO: these are constant so they should only be added once at startup | 
| 137 |  |  |  |  |  |  | my ($self, $rule, $set, $subtest) = @_; | 
| 138 |  |  |  |  |  |  |  | 
| 139 |  |  |  |  |  |  | if ($subtest =~ /^sb:/) { | 
| 140 |  |  |  |  |  |  | warn("dns: ignored $rule, SenderBase rules are deprecated\n"); | 
| 141 | 0 |  |  | 0 | 0 | 0 | return 0; | 
| 142 |  |  |  |  |  |  | } | 
| 143 | 0 | 0 |  |  |  | 0 |  | 
| 144 | 0 |  |  |  |  | 0 | $self->{dnspost}->{$set}->{$subtest} = $rule; | 
| 145 | 0 |  |  |  |  | 0 | } | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | my ($self, $rule, $type, $host) = @_; | 
| 148 | 0 |  |  |  |  | 0 |  | 
| 149 |  |  |  |  |  |  | $host =~ s/\.\z//s;  # strip a redundant trailing dot | 
| 150 |  |  |  |  |  |  | my $key = "dns:$type:$host"; | 
| 151 |  |  |  |  |  |  |  | 
| 152 | 0 |  |  | 0 | 0 | 0 | my $ent = { | 
| 153 |  |  |  |  |  |  | key => $key, | 
| 154 | 0 |  |  |  |  | 0 | zone => $host,  # serves to fetch other per-zone settings | 
| 155 | 0 |  |  |  |  | 0 | type => "DNSBL-".$type, | 
| 156 |  |  |  |  |  |  | rules => [ $rule ], | 
| 157 | 0 |  |  |  |  | 0 | # id is filled in after we send the query below | 
| 158 |  |  |  |  |  |  | }; | 
| 159 |  |  |  |  |  |  | $ent = $self->{async}->bgsend_and_start_lookup( | 
| 160 |  |  |  |  |  |  | $host, $type, undef, $ent, | 
| 161 |  |  |  |  |  |  | sub { my($ent, $pkt) = @_; $self->process_dnsbl_result($ent, $pkt) }, | 
| 162 |  |  |  |  |  |  | master_deadline => $self->{master_deadline} ); | 
| 163 |  |  |  |  |  |  | $ent; | 
| 164 |  |  |  |  |  |  | } | 
| 165 |  |  |  |  |  |  |  | 
| 166 | 0 |  |  | 0 |  | 0 | ########################################################################### | 
|  | 0 |  |  |  |  | 0 |  | 
| 167 | 0 |  |  |  |  | 0 |  | 
| 168 | 0 |  |  |  |  | 0 | my ($self, $rule, $question, $answer) = @_; | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | my $log = ""; | 
| 171 |  |  |  |  |  |  | if (substr($rule, 0, 2) eq "__") { | 
| 172 |  |  |  |  |  |  | # don't bother with meta rules | 
| 173 |  |  |  |  |  |  | } elsif ($answer->type eq 'TXT') { | 
| 174 | 0 |  |  | 0 | 0 | 0 | # txtdata returns a non- zone-file-format encoded result, unlike rdstring; | 
| 175 |  |  |  |  |  |  | # avoid space-separated RDATA <character-string> fields if possible, | 
| 176 | 0 |  |  |  |  | 0 | # txtdata provides a list of strings in a list context since Net::DNS 0.69 | 
| 177 | 0 | 0 |  |  |  | 0 | $log = join('',$answer->txtdata); | 
|  |  | 0 |  |  |  |  |  | 
| 178 |  |  |  |  |  |  | local $1; | 
| 179 |  |  |  |  |  |  | $log =~ s{ (?<! [<(\[] ) (https? : // \S+)}{<$1>}xgi; | 
| 180 |  |  |  |  |  |  | } else {  # assuming $answer->type eq 'A' | 
| 181 |  |  |  |  |  |  | local($1,$2,$3,$4,$5); | 
| 182 |  |  |  |  |  |  | if ($question->string =~ m/^((?:[0-9a-fA-F]\.){32})(\S+\w)/) { | 
| 183 | 0 |  |  |  |  | 0 | $log = ' listed in ' . lc($2); | 
| 184 | 0 |  |  |  |  | 0 | my $ipv6addr = join('', reverse split(/\./, lc $1)); | 
| 185 | 0 |  |  |  |  | 0 | $ipv6addr =~ s/\G(....)/$1:/g;  chop $ipv6addr; | 
| 186 |  |  |  |  |  |  | $ipv6addr =~ s/:0{1,3}/:/g; | 
| 187 | 0 |  |  |  |  | 0 | $log = $ipv6addr . $log; | 
| 188 | 0 | 0 |  |  |  | 0 | } elsif ($question->string =~ m/^(\d+)\.(\d+)\.(\d+)\.(\d+)\.(\S+\w)/) { | 
|  |  | 0 |  |  |  |  |  | 
| 189 | 0 |  |  |  |  | 0 | $log = "$4.$3.$2.$1 listed in " . lc($5); | 
| 190 | 0 |  |  |  |  | 0 | } else { | 
| 191 | 0 |  |  |  |  | 0 | $log = 'listed in ' . $question->string; | 
|  | 0 |  |  |  |  | 0 |  | 
| 192 | 0 |  |  |  |  | 0 | } | 
| 193 | 0 |  |  |  |  | 0 | } | 
| 194 |  |  |  |  |  |  |  | 
| 195 | 0 |  |  |  |  | 0 | # TODO: this may result in some log messages appearing under the | 
| 196 |  |  |  |  |  |  | # wrong rules, since we could see this sequence: { test one hits, | 
| 197 | 0 |  |  |  |  | 0 | # test one's message is logged, test two hits, test one fires again | 
| 198 |  |  |  |  |  |  | # on another IP, test one's message is logged for that other IP -- | 
| 199 |  |  |  |  |  |  | # but under test two's heading }.   Right now though it's better | 
| 200 |  |  |  |  |  |  | # than just not logging at all. | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | $self->{already_logged} ||= { }; | 
| 203 |  |  |  |  |  |  | if ($log && !$self->{already_logged}->{$log}) { | 
| 204 |  |  |  |  |  |  | $self->test_log($log); | 
| 205 |  |  |  |  |  |  | $self->{already_logged}->{$log} = 1; | 
| 206 |  |  |  |  |  |  | } | 
| 207 |  |  |  |  |  |  |  | 
| 208 | 0 |  | 0 |  |  | 0 | if (!$self->{tests_already_hit}->{$rule}) { | 
| 209 | 0 | 0 | 0 |  |  | 0 | $self->got_hit($rule, "RBL: ", ruletype => "dnsbl"); | 
| 210 | 0 |  |  |  |  | 0 | } | 
| 211 | 0 |  |  |  |  | 0 | } | 
| 212 |  |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  | my ($self, $question, $answer) = @_; | 
| 214 | 0 | 0 |  |  |  | 0 |  | 
| 215 | 0 |  |  |  |  | 0 | my $qname = $question->qname; | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | # txtdata returns a non- zone-file-format encoded result, unlike rdstring; | 
| 218 |  |  |  |  |  |  | # avoid space-separated RDATA <character-string> fields if possible, | 
| 219 |  |  |  |  |  |  | # txtdata provides a list of strings in a list context since Net::DNS 0.69 | 
| 220 | 0 |  |  | 0 | 0 | 0 | # | 
| 221 |  |  |  |  |  |  | # rdatastr() is historical/undocumented, use rdstring() since Net::DNS 0.69 | 
| 222 | 0 |  |  |  |  | 0 | my $rdatastr = $answer->UNIVERSAL::can('txtdata') ? join('',$answer->txtdata) | 
| 223 |  |  |  |  |  |  | : $answer->UNIVERSAL::can('rdstring') ? $answer->rdstring | 
| 224 |  |  |  |  |  |  | : $answer->rdatastr; | 
| 225 |  |  |  |  |  |  | if (defined $qname && defined $rdatastr) { | 
| 226 |  |  |  |  |  |  | my $qclass = $question->qclass; | 
| 227 |  |  |  |  |  |  | my $qtype = $question->qtype; | 
| 228 |  |  |  |  |  |  | my @vals; | 
| 229 | 0 | 0 |  |  |  | 0 | push(@vals, "class=$qclass") if $qclass ne "IN"; | 
|  |  | 0 |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | push(@vals, "type=$qtype") if $qtype ne "A"; | 
| 231 |  |  |  |  |  |  | my $uri = "dns:$qname" . (@vals ? "?" . join(";", @vals) : ""); | 
| 232 | 0 | 0 | 0 |  |  | 0 | push @{ $self->{dnsuri}->{$uri} }, $rdatastr; | 
| 233 | 0 |  |  |  |  | 0 |  | 
| 234 | 0 |  |  |  |  | 0 | dbg("dns: hit <$uri> $rdatastr"); | 
| 235 | 0 |  |  |  |  | 0 | } | 
| 236 | 0 | 0 |  |  |  | 0 | } | 
| 237 | 0 | 0 |  |  |  | 0 |  | 
| 238 | 0 | 0 |  |  |  | 0 | # called as a completion routine to bgsend by DnsResolver::poll_responses; | 
| 239 | 0 |  |  |  |  | 0 | # returns 1 on successful packet processing | 
|  | 0 |  |  |  |  | 0 |  | 
| 240 |  |  |  |  |  |  | my ($self, $ent, $pkt) = @_; | 
| 241 | 0 |  |  |  |  | 0 |  | 
| 242 |  |  |  |  |  |  | return if !$pkt; | 
| 243 |  |  |  |  |  |  | my $question = ($pkt->question)[0]; | 
| 244 |  |  |  |  |  |  | return if !$question; | 
| 245 |  |  |  |  |  |  |  | 
| 246 |  |  |  |  |  |  | my $sets = $ent->{sets} || []; | 
| 247 |  |  |  |  |  |  | my $rules = $ent->{rules}; | 
| 248 | 0 |  |  | 0 | 0 | 0 |  | 
| 249 |  |  |  |  |  |  | # NO_DNS_FOR_FROM | 
| 250 | 0 | 0 |  |  |  | 0 | if ($self->{sender_host} && | 
| 251 | 0 |  |  |  |  | 0 | # fishy, qname should have been "RFC 1035 zone format" -decoded first | 
| 252 | 0 | 0 |  |  |  | 0 | lc($question->qname) eq lc($self->{sender_host}) && | 
| 253 |  |  |  |  |  |  | $question->qtype =~ /^(?:A|MX)$/ && | 
| 254 | 0 |  | 0 |  |  | 0 | $pkt->header->rcode =~ /^(?:NXDOMAIN|SERVFAIL)$/ && | 
| 255 | 0 |  |  |  |  | 0 | ++$self->{sender_host_fail} == 2) | 
| 256 |  |  |  |  |  |  | { | 
| 257 |  |  |  |  |  |  | for my $rule (@{$rules}) { | 
| 258 | 0 | 0 | 0 |  |  | 0 | $self->got_hit($rule, "DNS: ", ruletype => "dns"); | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 259 |  |  |  |  |  |  | } | 
| 260 |  |  |  |  |  |  | } | 
| 261 |  |  |  |  |  |  |  | 
| 262 |  |  |  |  |  |  | # DNSBL tests are here | 
| 263 |  |  |  |  |  |  | foreach my $answer ($pkt->answer) { | 
| 264 |  |  |  |  |  |  | next if !$answer; | 
| 265 | 0 |  |  |  |  | 0 | # track all responses | 
|  | 0 |  |  |  |  | 0 |  | 
| 266 | 0 |  |  |  |  | 0 | $self->dnsbl_uri($question, $answer); | 
| 267 |  |  |  |  |  |  | my $answ_type = $answer->type; | 
| 268 |  |  |  |  |  |  | # TODO: there are some CNAME returns that might be useful | 
| 269 |  |  |  |  |  |  | next if ($answ_type ne 'A' && $answ_type ne 'TXT'); | 
| 270 |  |  |  |  |  |  | if ($answ_type eq 'A') { | 
| 271 | 0 |  |  |  |  | 0 | # Net::DNS::RR::A::address() is available since Net::DNS 0.69 | 
| 272 | 0 | 0 |  |  |  | 0 | my $ip_address = $answer->UNIVERSAL::can('address') ? $answer->address | 
| 273 |  |  |  |  |  |  | : $answer->rdatastr; | 
| 274 | 0 |  |  |  |  | 0 | # skip any A record that isn't on 127.0.0.0/8 | 
| 275 | 0 |  |  |  |  | 0 | next if $ip_address !~ /^127\./; | 
| 276 |  |  |  |  |  |  | } | 
| 277 | 0 | 0 | 0 |  |  | 0 | for my $rule (@{$rules}) { | 
| 278 | 0 | 0 |  |  |  | 0 | $self->dnsbl_hit($rule, $question, $answer); | 
| 279 |  |  |  |  |  |  | } | 
| 280 | 0 | 0 |  |  |  | 0 | for my $set (@{$sets}) { | 
| 281 |  |  |  |  |  |  | if ($self->{dnspost}->{$set}) { | 
| 282 |  |  |  |  |  |  | $self->process_dnsbl_set($set, $question, $answer); | 
| 283 | 0 | 0 |  |  |  | 0 | } | 
| 284 |  |  |  |  |  |  | } | 
| 285 | 0 |  |  |  |  | 0 | } | 
|  | 0 |  |  |  |  | 0 |  | 
| 286 | 0 |  |  |  |  | 0 | return 1; | 
| 287 |  |  |  |  |  |  | } | 
| 288 | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 289 | 0 | 0 |  |  |  | 0 | my ($self, $set, $question, $answer) = @_; | 
| 290 | 0 |  |  |  |  | 0 |  | 
| 291 |  |  |  |  |  |  | # txtdata returns a non- zone-file-format encoded result, unlike rdstring; | 
| 292 |  |  |  |  |  |  | # avoid space-separated RDATA <character-string> fields if possible, | 
| 293 |  |  |  |  |  |  | # txtdata provides a list of strings in a list context since Net::DNS 0.69 | 
| 294 | 0 |  |  |  |  | 0 | # | 
| 295 |  |  |  |  |  |  | # rdatastr() is historical/undocumented, use rdstring() since Net::DNS 0.69 | 
| 296 |  |  |  |  |  |  | my $rdatastr = $answer->UNIVERSAL::can('txtdata')  ? join('',$answer->txtdata) | 
| 297 |  |  |  |  |  |  | : $answer->UNIVERSAL::can('rdstring') ? $answer->rdstring | 
| 298 | 0 |  |  | 0 | 0 | 0 | : $answer->rdatastr; | 
| 299 |  |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  | while (my ($subtest, $rule) = each %{ $self->{dnspost}->{$set} }) { | 
| 301 |  |  |  |  |  |  | next if $self->{tests_already_hit}->{$rule}; | 
| 302 |  |  |  |  |  |  |  | 
| 303 |  |  |  |  |  |  | if ($subtest =~ /^\d+\.\d+\.\d+\.\d+$/) { | 
| 304 |  |  |  |  |  |  | # test for exact equality, not a regexp (an IPv4 address) | 
| 305 | 0 | 0 |  |  |  | 0 | $self->dnsbl_hit($rule, $question, $answer)  if $subtest eq $rdatastr; | 
|  |  | 0 |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | } | 
| 307 |  |  |  |  |  |  | # bitmask | 
| 308 |  |  |  |  |  |  | elsif ($subtest =~ /^\d+$/) { | 
| 309 | 0 |  |  |  |  | 0 | # Bug 6803: response should be within 127.0.0.0/8, ignore otherwise | 
|  | 0 |  |  |  |  | 0 |  | 
| 310 | 0 | 0 |  |  |  | 0 | if ($rdatastr =~ m/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ && | 
| 311 |  |  |  |  |  |  | Mail::SpamAssassin::Util::my_inet_aton($rdatastr) & $subtest) | 
| 312 | 0 | 0 |  |  |  | 0 | { | 
|  |  | 0 |  |  |  |  |  | 
| 313 |  |  |  |  |  |  | $self->dnsbl_hit($rule, $question, $answer); | 
| 314 | 0 | 0 |  |  |  | 0 | } | 
| 315 |  |  |  |  |  |  | } | 
| 316 |  |  |  |  |  |  | # regular expression | 
| 317 |  |  |  |  |  |  | else { | 
| 318 |  |  |  |  |  |  | my $test = qr/$subtest/; | 
| 319 | 0 | 0 | 0 |  |  | 0 | if ($rdatastr =~ /$test/) { | 
| 320 |  |  |  |  |  |  | $self->dnsbl_hit($rule, $question, $answer); | 
| 321 |  |  |  |  |  |  | } | 
| 322 | 0 |  |  |  |  | 0 | } | 
| 323 |  |  |  |  |  |  | } | 
| 324 |  |  |  |  |  |  | } | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  | my ($self, $rule) = @_; | 
| 327 | 0 |  |  |  |  | 0 |  | 
| 328 | 0 | 0 |  |  |  | 0 | dbg("dns: harvest_until_rule_completes"); | 
| 329 | 0 |  |  |  |  | 0 | my $result = 0; | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | for (my $first=1;  ; $first=0) { | 
| 332 |  |  |  |  |  |  | # complete_lookups() may call completed_callback(), which may | 
| 333 |  |  |  |  |  |  | # call start_lookup() again (like in Plugin::URIDNSBL) | 
| 334 |  |  |  |  |  |  | my ($alldone,$anydone) = | 
| 335 |  |  |  |  |  |  | $self->{async}->complete_lookups($first ? 0 : 1.0,  1); | 
| 336 | 0 |  |  | 0 | 0 | 0 |  | 
| 337 |  |  |  |  |  |  | $result = 1  if $self->is_rule_complete($rule); | 
| 338 | 0 |  |  |  |  | 0 | last  if $result || $alldone; | 
| 339 | 0 |  |  |  |  | 0 |  | 
| 340 |  |  |  |  |  |  | dbg("dns: harvest_until_rule_completes - check_tick"); | 
| 341 | 0 |  |  |  |  | 0 | $self->{main}->call_plugins ("check_tick", { permsgstatus => $self }); | 
| 342 |  |  |  |  |  |  | } | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | return $result; | 
| 345 | 0 | 0 |  |  |  | 0 | } | 
| 346 |  |  |  |  |  |  |  | 
| 347 | 0 | 0 |  |  |  | 0 | my ($self) = @_; | 
| 348 | 0 | 0 | 0 |  |  | 0 |  | 
| 349 |  |  |  |  |  |  | dbg("dns: harvest_dnsbl_queries"); | 
| 350 | 0 |  |  |  |  | 0 |  | 
| 351 | 0 |  |  |  |  | 0 | for (my $first=1;  ; $first=0) { | 
| 352 |  |  |  |  |  |  | # complete_lookups() may call completed_callback(), which may | 
| 353 |  |  |  |  |  |  | # call start_lookup() again (like in Plugin::URIDNSBL) | 
| 354 | 0 |  |  |  |  | 0 |  | 
| 355 |  |  |  |  |  |  | # the first time around we specify a 0 timeout, which gives | 
| 356 |  |  |  |  |  |  | # complete_lookups a chance to ripe any available results and | 
| 357 |  |  |  |  |  |  | # abort overdue requests, without needlessly waiting for more | 
| 358 | 96 |  |  | 96 | 0 | 218 |  | 
| 359 |  |  |  |  |  |  | my ($alldone,$anydone) = | 
| 360 | 96 |  |  |  |  | 266 | $self->{async}->complete_lookups($first ? 0 : 1.0,  1); | 
| 361 |  |  |  |  |  |  |  | 
| 362 | 96 |  |  |  |  | 189 | last  if $alldone; | 
| 363 |  |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  | dbg("dns: harvest_dnsbl_queries - check_tick"); | 
| 365 |  |  |  |  |  |  | $self->{main}->call_plugins ("check_tick", { permsgstatus => $self }); | 
| 366 |  |  |  |  |  |  | } | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | # explicitly abort anything left | 
| 369 |  |  |  |  |  |  | $self->{async}->abort_remaining_lookups(); | 
| 370 |  |  |  |  |  |  | $self->{async}->log_lookups_timing(); | 
| 371 | 96 | 50 |  |  |  | 414 | $self->mark_all_async_rules_complete(); | 
| 372 |  |  |  |  |  |  | 1; | 
| 373 | 96 | 50 |  |  |  | 346 | } | 
| 374 |  |  |  |  |  |  |  | 
| 375 | 0 |  |  |  |  | 0 | # collect and process whatever DNS responses have already arrived, | 
| 376 | 0 |  |  |  |  | 0 | # don't waste time waiting for more, don't poll too often. | 
| 377 |  |  |  |  |  |  | # don't abort any queries even if overdue, | 
| 378 |  |  |  |  |  |  | my ($self) = @_; | 
| 379 |  |  |  |  |  |  |  | 
| 380 | 96 |  |  |  |  | 471 | # don't bother collecting responses too often | 
| 381 | 96 |  |  |  |  | 388 | my $last_poll_time = $self->{async}->last_poll_responses_time(); | 
| 382 | 96 |  |  |  |  | 427 | return if defined $last_poll_time && time - $last_poll_time < 0.1; | 
| 383 | 96 |  |  |  |  | 230 |  | 
| 384 |  |  |  |  |  |  | my ($alldone,$anydone) = $self->{async}->complete_lookups(0, 0); | 
| 385 |  |  |  |  |  |  | if ($anydone) { | 
| 386 |  |  |  |  |  |  | dbg("dns: harvested completed queries"); | 
| 387 |  |  |  |  |  |  | #   $self->{main}->call_plugins ("check_tick", { permsgstatus => $self }); | 
| 388 |  |  |  |  |  |  | } | 
| 389 |  |  |  |  |  |  | } | 
| 390 | 3096 |  |  | 3096 | 0 | 4323 |  | 
| 391 |  |  |  |  |  |  | my ($self) = @_; | 
| 392 |  |  |  |  |  |  |  | 
| 393 | 3096 |  |  |  |  | 8058 | # DNS URIs | 
| 394 | 3096 | 100 | 66 |  |  | 5826 | my $rbl_tag = $self->{tag_data}->{RBL};  # just in case, should be empty | 
| 395 |  |  |  |  |  |  | $rbl_tag = ''  if !defined $rbl_tag; | 
| 396 | 3063 |  |  |  |  | 6292 | while (my ($dnsuri, $answers) = each %{ $self->{dnsuri} }) { | 
| 397 | 3063 | 100 |  |  |  | 7228 | # when parsing, look for elements of \".*?\" or \S+ with ", " as separator | 
| 398 | 3 |  |  |  |  | 8 | $rbl_tag .= "<$dnsuri>" . " [" . join(", ", @{ $answers }) . "]\n"; | 
| 399 |  |  |  |  |  |  | } | 
| 400 |  |  |  |  |  |  | if (defined $rbl_tag && $rbl_tag ne '') { | 
| 401 |  |  |  |  |  |  | chomp $rbl_tag; | 
| 402 |  |  |  |  |  |  | $self->set_tag('RBL', $rbl_tag); | 
| 403 |  |  |  |  |  |  | } | 
| 404 | 96 |  |  | 96 | 0 | 185 | } | 
| 405 |  |  |  |  |  |  |  | 
| 406 |  |  |  |  |  |  | ########################################################################### | 
| 407 | 96 |  |  |  |  | 238 |  | 
| 408 | 96 | 50 |  |  |  | 252 | my ($self) = @_; | 
| 409 | 96 |  |  |  |  | 186 |  | 
|  | 96 |  |  |  |  | 572 |  | 
| 410 |  |  |  |  |  |  | $self->set_rbl_tag_data(); | 
| 411 | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 412 |  |  |  |  |  |  | delete $self->{dnspost}; | 
| 413 | 96 | 50 | 33 |  |  | 553 | delete $self->{dnsuri}; | 
| 414 | 0 |  |  |  |  | 0 | } | 
| 415 | 0 |  |  |  |  | 0 |  | 
| 416 |  |  |  |  |  |  | ########################################################################### | 
| 417 |  |  |  |  |  |  |  | 
| 418 |  |  |  |  |  |  | my ($self) = @_; | 
| 419 |  |  |  |  |  |  | $self->{resolver} = $self->{main}->{resolver}; | 
| 420 |  |  |  |  |  |  | return $self->{resolver}->load_resolver(); | 
| 421 |  |  |  |  |  |  | } | 
| 422 | 96 |  |  | 96 | 0 | 192 |  | 
| 423 |  |  |  |  |  |  | my ($self) = @_; | 
| 424 | 96 |  |  |  |  | 303 | dbg("dns: clear_resolver"); | 
| 425 |  |  |  |  |  |  | $self->{main}->{resolver}->{res} = undef; | 
| 426 | 96 |  |  |  |  | 182 | return 0; | 
| 427 | 96 |  |  |  |  | 252 | } | 
| 428 |  |  |  |  |  |  |  | 
| 429 |  |  |  |  |  |  | my ($self, $dom) = @_; | 
| 430 |  |  |  |  |  |  |  | 
| 431 |  |  |  |  |  |  | return unless $self->load_resolver(); | 
| 432 |  |  |  |  |  |  | return if ($self->server_failed_to_respond_for_domain ($dom)); | 
| 433 | 1 |  |  | 1 | 0 | 2 |  | 
| 434 | 1 |  |  |  |  | 2 | my $nsrecords; | 
| 435 | 1 |  |  |  |  | 12 | dbg("dns: looking up NS for '$dom'"); | 
| 436 |  |  |  |  |  |  |  | 
| 437 |  |  |  |  |  |  | eval { | 
| 438 |  |  |  |  |  |  | my $query = $self->{resolver}->send($dom, 'NS'); | 
| 439 | 1 |  |  | 1 | 0 | 2 | my @nses; | 
| 440 | 1 |  |  |  |  | 4 | if ($query) { | 
| 441 | 1 |  |  |  |  | 13 | foreach my $rr ($query->answer) { | 
| 442 | 1 |  |  |  |  | 2 | if ($rr->type eq "NS") { push (@nses, $rr->nsdname); } | 
| 443 |  |  |  |  |  |  | } | 
| 444 |  |  |  |  |  |  | } | 
| 445 |  |  |  |  |  |  | $nsrecords = [ @nses ]; | 
| 446 | 0 |  |  | 0 | 0 | 0 | 1; | 
| 447 |  |  |  |  |  |  | } or do { | 
| 448 | 0 | 0 |  |  |  | 0 | my $eval_stat = $@ ne '' ? $@ : "errno=$!";  chomp $eval_stat; | 
| 449 | 0 | 0 |  |  |  | 0 | dbg("dns: NS lookup failed horribly, perhaps bad resolv.conf setting? (%s)", $eval_stat); | 
| 450 |  |  |  |  |  |  | return; | 
| 451 | 0 |  |  |  |  | 0 | }; | 
| 452 | 0 |  |  |  |  | 0 |  | 
| 453 |  |  |  |  |  |  | $nsrecords; | 
| 454 |  |  |  |  |  |  | } | 
| 455 | 0 |  |  |  |  | 0 |  | 
| 456 | 0 |  |  |  |  | 0 | my ($self) = @_; | 
| 457 | 0 | 0 |  |  |  | 0 | my $dnsopt = $self->{conf}->{dns_available}; | 
| 458 | 0 |  |  |  |  | 0 | my $dnsint = $self->{conf}->{dns_test_interval} || 600; | 
| 459 | 0 | 0 |  |  |  | 0 | my @domains; | 
|  | 0 |  |  |  |  | 0 |  | 
| 460 |  |  |  |  |  |  |  | 
| 461 |  |  |  |  |  |  | $LAST_DNS_CHECK ||= 0; | 
| 462 | 0 |  |  |  |  | 0 | my $diff = time() - $LAST_DNS_CHECK; | 
| 463 | 0 |  |  |  |  | 0 |  | 
| 464 | 0 | 0 |  |  |  | 0 | # undef $IS_DNS_AVAILABLE if we should be testing for | 
| 465 | 0 | 0 |  |  |  | 0 | # working DNS and our check interval time has passed | 
|  | 0 |  |  |  |  | 0 |  | 
| 466 | 0 |  |  |  |  | 0 | if ($dnsopt eq "test" && $diff > $dnsint) { | 
| 467 | 0 |  |  |  |  | 0 | $IS_DNS_AVAILABLE = undef; | 
| 468 |  |  |  |  |  |  | dbg("dns: is_dns_available() last checked %.1f seconds ago; re-checking", | 
| 469 |  |  |  |  |  |  | $diff); | 
| 470 | 0 |  |  |  |  | 0 | } | 
| 471 |  |  |  |  |  |  |  | 
| 472 |  |  |  |  |  |  | return $IS_DNS_AVAILABLE if (defined $IS_DNS_AVAILABLE); | 
| 473 |  |  |  |  |  |  | $LAST_DNS_CHECK = time(); | 
| 474 | 336 |  |  | 336 | 0 | 574 |  | 
| 475 | 336 |  |  |  |  | 608 | $IS_DNS_AVAILABLE = 0; | 
| 476 | 336 |  | 50 |  |  | 783 | if ($dnsopt eq "no") { | 
| 477 | 336 |  |  |  |  | 410 | dbg("dns: dns_available set to no in config file, skipping test"); | 
| 478 |  |  |  |  |  |  | return $IS_DNS_AVAILABLE; | 
| 479 | 336 |  | 100 |  |  | 683 | } | 
| 480 | 336 |  |  |  |  | 787 |  | 
| 481 |  |  |  |  |  |  | # Even if "dns_available" is explicitly set to "yes", we want to ignore | 
| 482 |  |  |  |  |  |  | # DNS if we're only supposed to be looking at local tests. | 
| 483 |  |  |  |  |  |  | goto done if ($self->{main}->{local_tests_only}); | 
| 484 | 336 | 50 | 33 |  |  | 715 |  | 
| 485 | 0 |  |  |  |  | 0 | # Check version numbers - runtime check only | 
| 486 | 0 |  |  |  |  | 0 | if (defined $Net::DNS::VERSION) { | 
| 487 |  |  |  |  |  |  | if (am_running_on_windows()) { | 
| 488 |  |  |  |  |  |  | if ($Net::DNS::VERSION < 0.46) { | 
| 489 |  |  |  |  |  |  | warn("dns: Net::DNS version is $Net::DNS::VERSION, but need 0.46 for Win32"); | 
| 490 | 336 | 100 |  |  |  | 1594 | return $IS_DNS_AVAILABLE; | 
| 491 | 8 |  |  |  |  | 25 | } | 
| 492 |  |  |  |  |  |  | } | 
| 493 | 8 |  |  |  |  | 16 | else { | 
| 494 | 8 | 50 |  |  |  | 26 | if ($Net::DNS::VERSION < 0.34) { | 
| 495 | 0 |  |  |  |  | 0 | warn("dns: Net::DNS version is $Net::DNS::VERSION, but need 0.34"); | 
| 496 | 0 |  |  |  |  | 0 | return $IS_DNS_AVAILABLE; | 
| 497 |  |  |  |  |  |  | } | 
| 498 |  |  |  |  |  |  | } | 
| 499 |  |  |  |  |  |  | } | 
| 500 |  |  |  |  |  |  |  | 
| 501 | 8 | 100 |  |  |  | 52 | $self->clear_resolver(); | 
| 502 |  |  |  |  |  |  | goto done unless $self->load_resolver(); | 
| 503 |  |  |  |  |  |  |  | 
| 504 | 1 | 50 |  |  |  | 3 | if ($dnsopt eq "yes") { | 
| 505 | 1 | 50 |  |  |  | 5 | # optionally shuffle the list of nameservers to distribute the load | 
| 506 | 0 | 0 |  |  |  | 0 | if ($self->{conf}->{dns_options}->{rotate}) { | 
| 507 | 0 |  |  |  |  | 0 | my @nameservers = $self->{resolver}->available_nameservers(); | 
| 508 | 0 |  |  |  |  | 0 | Mail::SpamAssassin::Util::fisher_yates_shuffle(\@nameservers); | 
| 509 |  |  |  |  |  |  | dbg("dns: shuffled NS list: " . join(", ", @nameservers)); | 
| 510 |  |  |  |  |  |  | $self->{resolver}->available_nameservers(@nameservers); | 
| 511 |  |  |  |  |  |  | } | 
| 512 | 1 | 50 |  |  |  | 4 | $IS_DNS_AVAILABLE = 1; | 
| 513 | 0 |  |  |  |  | 0 | dbg("dns: dns_available set to yes in config file, skipping test"); | 
| 514 | 0 |  |  |  |  | 0 | return $IS_DNS_AVAILABLE; | 
| 515 |  |  |  |  |  |  | } | 
| 516 |  |  |  |  |  |  |  | 
| 517 |  |  |  |  |  |  | if ($dnsopt =~ /^test:\s*(\S.*)$/) { | 
| 518 |  |  |  |  |  |  | @domains = split (/\s+/, $1); | 
| 519 | 1 |  |  |  |  | 4 | dbg("dns: looking up NS records for user specified domains: %s", | 
| 520 | 1 | 50 |  |  |  | 3 | join(", ", @domains)); | 
| 521 |  |  |  |  |  |  | } else { | 
| 522 | 1 | 50 |  |  |  | 4 | @domains = @EXISTING_DOMAINS; | 
| 523 |  |  |  |  |  |  | dbg("dns: looking up NS records for built-in domains"); | 
| 524 | 1 | 50 |  |  |  | 3 | } | 
| 525 | 0 |  |  |  |  | 0 |  | 
| 526 | 0 |  |  |  |  | 0 | # do the test with a full set of configured nameservers | 
| 527 | 0 |  |  |  |  | 0 | my @nameservers = $self->{resolver}->configured_nameservers(); | 
| 528 | 0 |  |  |  |  | 0 |  | 
| 529 |  |  |  |  |  |  | # optionally shuffle the list of nameservers to distribute the load | 
| 530 | 1 |  |  |  |  | 3 | if ($self->{conf}->{dns_options}->{rotate}) { | 
| 531 | 1 |  |  |  |  | 3 | Mail::SpamAssassin::Util::fisher_yates_shuffle(\@nameservers); | 
| 532 | 1 |  |  |  |  | 13 | dbg("dns: shuffled NS list, testing: " . join(", ", @nameservers)); | 
| 533 |  |  |  |  |  |  | } else { | 
| 534 |  |  |  |  |  |  | dbg("dns: testing resolver nameservers: " . join(", ", @nameservers)); | 
| 535 | 0 | 0 |  |  |  | 0 | } | 
| 536 | 0 |  |  |  |  | 0 |  | 
| 537 | 0 |  |  |  |  | 0 | # Try the different nameservers here and collect a list of working servers | 
| 538 |  |  |  |  |  |  | my @good_nameservers; | 
| 539 |  |  |  |  |  |  | foreach my $ns (@nameservers) { | 
| 540 | 0 |  |  |  |  | 0 | $self->{resolver}->available_nameservers($ns);  # try just this one | 
| 541 | 0 |  |  |  |  | 0 | for (my $retry = 3; $retry > 0 && @domains; $retry--) { | 
| 542 |  |  |  |  |  |  | my $domain = splice(@domains, rand(@domains), 1); | 
| 543 |  |  |  |  |  |  | dbg("dns: trying ($retry) $domain, server $ns ..."); | 
| 544 |  |  |  |  |  |  | my $result = $self->lookup_ns($domain); | 
| 545 | 0 |  |  |  |  | 0 | $self->{resolver}->finish_socket(); | 
| 546 |  |  |  |  |  |  | if (!$result) { | 
| 547 |  |  |  |  |  |  | dbg("dns: NS lookup of $domain using $ns failed horribly, ". | 
| 548 | 0 | 0 |  |  |  | 0 | "may not be a valid nameserver"); | 
| 549 | 0 |  |  |  |  | 0 | last; | 
| 550 | 0 |  |  |  |  | 0 | } elsif (!@$result) { | 
| 551 |  |  |  |  |  |  | dbg("dns: NS lookup of $domain using $ns failed, no results found"); | 
| 552 | 0 |  |  |  |  | 0 | } else { | 
| 553 |  |  |  |  |  |  | dbg("dns: NS lookup of $domain using $ns succeeded => DNS available". | 
| 554 |  |  |  |  |  |  | " (set dns_available to override)"); | 
| 555 |  |  |  |  |  |  | push(@good_nameservers, $ns); | 
| 556 | 0 |  |  |  |  | 0 | last; | 
| 557 | 0 |  |  |  |  | 0 | } | 
| 558 | 0 |  |  |  |  | 0 | } | 
| 559 | 0 |  | 0 |  |  | 0 | } | 
| 560 | 0 |  |  |  |  | 0 |  | 
| 561 | 0 |  |  |  |  | 0 | if (!@good_nameservers) { | 
| 562 | 0 |  |  |  |  | 0 | dbg("dns: all NS queries failed => DNS unavailable ". | 
| 563 | 0 |  |  |  |  | 0 | "(set dns_available to override)"); | 
| 564 | 0 | 0 |  |  |  | 0 | } else { | 
|  |  | 0 |  |  |  |  |  | 
| 565 | 0 |  |  |  |  | 0 | $IS_DNS_AVAILABLE = 1; | 
| 566 |  |  |  |  |  |  | dbg("dns: NS list: ".join(", ", @good_nameservers)); | 
| 567 | 0 |  |  |  |  | 0 | $self->{resolver}->available_nameservers(@good_nameservers); | 
| 568 |  |  |  |  |  |  | } | 
| 569 | 0 |  |  |  |  | 0 |  | 
| 570 |  |  |  |  |  |  | done: | 
| 571 | 0 |  |  |  |  | 0 | # jm: leaving this in! | 
| 572 |  |  |  |  |  |  | dbg("dns: is DNS available? " . $IS_DNS_AVAILABLE); | 
| 573 | 0 |  |  |  |  | 0 | return $IS_DNS_AVAILABLE; | 
| 574 | 0 |  |  |  |  | 0 | } | 
| 575 |  |  |  |  |  |  |  | 
| 576 |  |  |  |  |  |  | ########################################################################### | 
| 577 |  |  |  |  |  |  |  | 
| 578 |  |  |  |  |  |  | my ($self, $dom) = @_; | 
| 579 | 0 | 0 |  |  |  | 0 | if ($self->{dns_server_too_slow}->{$dom}) { | 
| 580 | 0 |  |  |  |  | 0 | dbg("dns: server for '$dom' failed to reply previously, not asking again"); | 
| 581 |  |  |  |  |  |  | return 1; | 
| 582 |  |  |  |  |  |  | } | 
| 583 | 0 |  |  |  |  | 0 | return 0; | 
| 584 | 0 |  |  |  |  | 0 | } | 
| 585 | 0 |  |  |  |  | 0 |  | 
| 586 |  |  |  |  |  |  | my ($self, $dom) = @_; | 
| 587 |  |  |  |  |  |  | dbg("dns: server for '$dom' failed to reply, marking as bad"); | 
| 588 | 7 |  |  |  |  | 38 | $self->{dns_server_too_slow}->{$dom} = 1; | 
| 589 |  |  |  |  |  |  | } | 
| 590 |  |  |  |  |  |  |  | 
| 591 | 7 |  |  |  |  | 43 | ########################################################################### | 
| 592 |  |  |  |  |  |  |  | 
| 593 |  |  |  |  |  |  | my ($self) = @_; | 
| 594 |  |  |  |  |  |  |  | 
| 595 |  |  |  |  |  |  | dbg("dns: entering helper-app run mode"); | 
| 596 |  |  |  |  |  |  | $self->{old_slash} = $/;              # Razor pollutes this | 
| 597 | 0 |  |  | 0 | 0 | 0 | %{$self->{old_env}} = (); | 
| 598 | 0 | 0 |  |  |  | 0 | if ( %ENV ) { | 
| 599 | 0 |  |  |  |  | 0 | # undefined values in %ENV can result due to autovivification elsewhere, | 
| 600 | 0 |  |  |  |  | 0 | # this prevents later possible warnings when we restore %ENV | 
| 601 |  |  |  |  |  |  | while (my ($key, $value) = each %ENV) { | 
| 602 | 0 |  |  |  |  | 0 | $self->{old_env}->{$key} = $value if defined $value; | 
| 603 |  |  |  |  |  |  | } | 
| 604 |  |  |  |  |  |  | } | 
| 605 |  |  |  |  |  |  |  | 
| 606 | 0 |  |  | 0 | 0 | 0 | Mail::SpamAssassin::Util::clean_path_in_taint_mode(); | 
| 607 | 0 |  |  |  |  | 0 |  | 
| 608 | 0 |  |  |  |  | 0 | my $newhome; | 
| 609 |  |  |  |  |  |  | if ($self->{main}->{home_dir_for_helpers}) { | 
| 610 |  |  |  |  |  |  | $newhome = $self->{main}->{home_dir_for_helpers}; | 
| 611 |  |  |  |  |  |  | } else { | 
| 612 |  |  |  |  |  |  | # use spamd -u user's home dir | 
| 613 |  |  |  |  |  |  | $newhome = (Mail::SpamAssassin::Util::portable_getpwuid ($>))[7]; | 
| 614 | 4 |  |  | 4 | 0 | 7 | } | 
| 615 |  |  |  |  |  |  |  | 
| 616 | 4 |  |  |  |  | 9 | if ($newhome) { | 
| 617 | 4 |  |  |  |  | 16 | $ENV{'HOME'} = Mail::SpamAssassin::Util::untaint_file_path ($newhome); | 
| 618 | 4 |  |  |  |  | 5 | } | 
|  | 4 |  |  |  |  | 11 |  | 
| 619 | 4 | 50 |  |  |  | 12 |  | 
| 620 |  |  |  |  |  |  | # enforce SIGCHLD as DEFAULT; IGNORE causes spurious kernel warnings | 
| 621 |  |  |  |  |  |  | # on Red Hat NPTL kernels (bug 1536), and some users of the | 
| 622 | 4 |  |  |  |  | 23 | # Mail::SpamAssassin modules set SIGCHLD to be a fatal signal | 
| 623 | 136 | 50 |  |  |  | 554 | # for some reason! (bug 3507) | 
| 624 |  |  |  |  |  |  | $self->{old_sigchld_handler} = $SIG{CHLD}; | 
| 625 |  |  |  |  |  |  | $SIG{CHLD} = 'DEFAULT'; | 
| 626 |  |  |  |  |  |  | } | 
| 627 | 4 |  |  |  |  | 15 |  | 
| 628 |  |  |  |  |  |  | my ($self) = @_; | 
| 629 | 4 |  |  |  |  | 5 |  | 
| 630 | 4 | 50 |  |  |  | 11 | dbg("dns: leaving helper-app run mode"); | 
| 631 | 0 |  |  |  |  | 0 | $/ = $self->{old_slash}; | 
| 632 |  |  |  |  |  |  | %ENV = %{$self->{old_env}}; | 
| 633 |  |  |  |  |  |  |  | 
| 634 | 4 |  |  |  |  | 14 | if (defined $self->{old_sigchld_handler}) { | 
| 635 |  |  |  |  |  |  | $SIG{CHLD} = $self->{old_sigchld_handler}; | 
| 636 |  |  |  |  |  |  | } else { | 
| 637 | 4 | 50 |  |  |  | 24 | # if SIGCHLD has never been explicitly set, it's returned as undef. | 
| 638 | 4 |  |  |  |  | 13 | # however, when *setting* SIGCHLD, using undef(%) or assigning to an | 
| 639 |  |  |  |  |  |  | # undef value produces annoying 'Use of uninitialized value in scalar | 
| 640 |  |  |  |  |  |  | # assignment' warnings.  That's silly.  workaround: | 
| 641 |  |  |  |  |  |  | $SIG{CHLD} = 'DEFAULT'; | 
| 642 |  |  |  |  |  |  | } | 
| 643 |  |  |  |  |  |  | } | 
| 644 |  |  |  |  |  |  |  | 
| 645 | 4 |  |  |  |  | 20 | # note: this must be called before leave_helper_run_mode() is called, | 
| 646 | 4 |  |  |  |  | 60 | # as the SIGCHLD signal must be set to DEFAULT for it to work. | 
| 647 |  |  |  |  |  |  | my ($self, $pid) = @_; | 
| 648 |  |  |  |  |  |  |  | 
| 649 |  |  |  |  |  |  | if ($SIG{CHLD} && $SIG{CHLD} ne 'IGNORE') {	# running from spamd | 
| 650 | 4 |  |  | 4 | 0 | 9 | waitpid ($pid, 0); | 
| 651 |  |  |  |  |  |  | } | 
| 652 | 4 |  |  |  |  | 13 | } | 
| 653 | 4 |  |  |  |  | 13 |  | 
| 654 | 4 |  |  |  |  | 6 | ########################################################################### | 
|  | 4 |  |  |  |  | 330 |  | 
| 655 |  |  |  |  |  |  |  | 
| 656 | 4 | 100 |  |  |  | 24 | my ($self, $rule) = @_; | 
| 657 | 3 |  |  |  |  | 40 | dbg("dns: $rule lookup start"); | 
| 658 |  |  |  |  |  |  | $self->{rule_to_rblkey}->{$rule} = '*ASYNC_START'; | 
| 659 |  |  |  |  |  |  | } | 
| 660 |  |  |  |  |  |  |  | 
| 661 |  |  |  |  |  |  | my ($self, $rule) = @_; | 
| 662 |  |  |  |  |  |  | dbg("dns: $rule lookup finished"); | 
| 663 | 1 |  |  |  |  | 13 | delete $self->{rule_to_rblkey}->{$rule}; | 
| 664 |  |  |  |  |  |  | } | 
| 665 |  |  |  |  |  |  |  | 
| 666 |  |  |  |  |  |  | my ($self) = @_; | 
| 667 |  |  |  |  |  |  | $self->{rule_to_rblkey} = { }; | 
| 668 |  |  |  |  |  |  | } | 
| 669 |  |  |  |  |  |  |  | 
| 670 | 0 |  |  | 0 | 0 | 0 | my ($self, $rule) = @_; | 
| 671 |  |  |  |  |  |  |  | 
| 672 | 0 | 0 | 0 |  |  | 0 | my $key = $self->{rule_to_rblkey}->{$rule}; | 
| 673 | 0 |  |  |  |  | 0 | if (!defined $key) { | 
| 674 |  |  |  |  |  |  | # dbg("dns: $rule lookup complete, not in list"); | 
| 675 |  |  |  |  |  |  | return 1; | 
| 676 |  |  |  |  |  |  | } | 
| 677 |  |  |  |  |  |  |  | 
| 678 |  |  |  |  |  |  | if ($key eq '*ASYNC_START') { | 
| 679 |  |  |  |  |  |  | dbg("dns: $rule lookup not yet complete"); | 
| 680 | 135 |  |  | 135 | 0 | 202 | return 0;       # not yet complete | 
| 681 | 135 |  |  |  |  | 334 | } | 
| 682 | 135 |  |  |  |  | 363 |  | 
| 683 |  |  |  |  |  |  | my $ent = $self->{async}->get_lookup($key); | 
| 684 |  |  |  |  |  |  | if (!defined $ent) { | 
| 685 |  |  |  |  |  |  | dbg("dns: $rule lookup complete, $key no longer pending"); | 
| 686 | 42 |  |  | 42 | 0 | 64 | return 1; | 
| 687 | 42 |  |  |  |  | 121 | } | 
| 688 | 42 |  |  |  |  | 260 |  | 
| 689 |  |  |  |  |  |  | dbg("dns: $rule lookup not yet complete"); | 
| 690 |  |  |  |  |  |  | return 0;         # not yet complete | 
| 691 |  |  |  |  |  |  | } | 
| 692 | 96 |  |  | 96 | 0 | 203 |  | 
| 693 | 96 |  |  |  |  | 320 | ########################################################################### | 
| 694 |  |  |  |  |  |  |  | 
| 695 |  |  |  |  |  |  | # interface called by SPF plugin | 
| 696 |  |  |  |  |  |  | my ($self, $pms) = @_; | 
| 697 | 0 |  |  | 0 | 0 | 0 | if (defined $pms->{sender_host_fail}) { | 
| 698 |  |  |  |  |  |  | return ($pms->{sender_host_fail} == 2); # both MX and A need to fail | 
| 699 | 0 |  |  |  |  | 0 | } | 
| 700 | 0 | 0 |  |  |  | 0 | } | 
| 701 |  |  |  |  |  |  |  | 
| 702 | 0 |  |  |  |  | 0 | 1; |