~ubuntu-branches/ubuntu/utopic/spamassassin/utopic-updates

« back to all changes in this revision

Viewing changes to lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm

  • Committer: Package Import Robot
  • Author(s): Noah Meyerhans
  • Date: 2014-02-14 22:45:15 UTC
  • mfrom: (0.8.1) (0.6.2) (5.1.22 sid)
  • Revision ID: package-import@ubuntu.com-20140214224515-z1es2twos8xh7n2y
Tags: 3.4.0-1
* New upstream version! (Closes: 738963, 738872, 738867)
* Scrub the environment when switching to the debian-spamd user in
  postinst and cron.daily. (Closes: 738951)
* Enhancements to postinst to better manage ownership of
  /var/lib/spamassassin, via Iain Lane <iain.lane@canonical.com>
  (Closes: 738974)

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
 
27
27
=head1 DESCRIPTION
28
28
 
29
 
This works by analysing message text and HTML for URLs, extracting the
30
 
domain names from those, querying their NS records in DNS, resolving
31
 
the hostnames used therein, and querying various DNS blocklists for
32
 
those IP addresses.  This is quite effective.
 
29
This works by analysing message text and HTML for URLs, extracting host
 
30
names from those, then querying various DNS blocklists for either:
 
31
IP addresses of these hosts (uridnsbl,a) or their nameservers (uridnsbl,ns),
 
32
or domain names of these hosts (urirhsbl), or domain names of their
 
33
nameservers (urinsrhsbl, urifullnsrhsbl).
33
34
 
34
35
=head1 USER SETTINGS
35
36
 
83
84
is the type of lookup (B<TXT> or B<A>).   Note that you must also
84
85
define a body-eval rule calling C<check_uridnsbl()> to use this.
85
86
 
 
87
This works by collecting domain names from URLs and querying DNS
 
88
blocklists with an IP address of host names found in URLs or with
 
89
IP addresses of their name servers, according to tflags as follows.
 
90
 
 
91
If the corresponding body rule has a tflag 'a', the DNS blocklist will
 
92
be queried with an IP address of a host found in URLs.
 
93
 
 
94
If the corresponding body rule has a tflag 'ns', DNS will be queried
 
95
for name servers (NS records) of a domain name found in URLs, then
 
96
these name server names will be resolved to their IP addresses, which
 
97
in turn will be sent to DNS blocklist.
 
98
 
 
99
Tflags directive may specify either 'a' or 'ns' or both flags. In absence
 
100
of any of these two flags, a default is a 'ns', which is compatible with
 
101
pre-3.4 versions of SpamAssassin.
 
102
 
 
103
The choice of tflags must correspond to the policy and expected use of
 
104
each DNS blocklist and is normally not a local decision. As an example,
 
105
a blocklist expecting queries resulting from an 'a' tflag is a
 
106
"black_a.txt" ( http://www.uribl.com/datasets.shtml ).
 
107
 
86
108
Example:
87
109
 
88
110
 uridnsbl        URIBL_SBLXBL    sbl-xbl.spamhaus.org.   TXT
89
111
 body            URIBL_SBLXBL    eval:check_uridnsbl('URIBL_SBLXBL')
90
112
 describe        URIBL_SBLXBL    Contains a URL listed in the SBL/XBL blocklist
 
113
 tflags          URIBL_SBLXBL    net ns
91
114
 
92
115
=item uridnssub NAME_OF_RULE dnsbl_zone lookuptype subtest
93
116
 
95
118
name of the rule to be used, C<dnsbl_zone> is the zone to look up IPs in,
96
119
and C<lookuptype> is the type of lookup (B<TXT> or B<A>).
97
120
 
 
121
Tflags 'ns' and 'a' on a corresponding body rule are recognized and have
 
122
the same meaning as in the uridnsbl directive.
 
123
 
98
124
C<subtest> is a sub-test to run against the returned data.  The sub-test may
99
125
be in one of the following forms: m, n1-n2, or n/m, where n,n1,n2,m can be
100
126
any of: decimal digits, 0x followed by up to 8 hexadecimal digits, or an IPv4
104
130
for a range n1-n2 the following must be true: (r >= n1 && r <= n2);
105
131
for a n/m form the following must be true: (r & m) == (n & m);
106
132
for a single value in quad-dot form the following must be true: r == n;
107
 
for a single decimal or hex form the following must be true: (r & n) != 0.
 
133
for a single decimal or hex form the following must be true:
 
134
  ((r & n) != 0) && ((r & 0xff000000) == 0x7f000000), i.e. within 127.0.0.0/8
108
135
 
109
136
Some typical examples of a sub-test are: 127.0.1.2, 127.0.1.20-127.0.1.39,
110
137
127.0.1.0/255.255.255.0, 0.0.0.16/0.0.0.16, 0x10/0x10, 16, 0x10 .
153
180
for a range n1-n2 the following must be true: (r >= n1 && r <= n2);
154
181
for a n/m form the following must be true: (r & m) == (n & m);
155
182
for a single value in quad-dot form the following must be true: r == n;
156
 
for a single decimal or hex form the following must be true: (r & n) != 0.
 
183
for a single decimal or hex form the following must be true:
 
184
  ((r & n) != 0) && ((r & 0xff000000) == 0x7f000000), i.e. within 127.0.0.0/8
157
185
 
158
186
Some typical examples of a sub-test are: 127.0.1.2, 127.0.1.20-127.0.1.39,
159
187
127.2.3.0/255.255.255.0, 0.0.0.16/0.0.0.16, 0x10/0x10, 16, 0x10 .
229
257
Only URIs containing a non-IP-address "host" component will be matched against
230
258
the named "urirhsbl"/"urirhssub" rule.
231
259
 
 
260
=item tflags NAME_OF_RULE ns
 
261
 
 
262
The 'ns' flag may be applied to rules corresponding to uridnsbl and uridnssub
 
263
directives. Host names from URLs will be mapped to their name server IP
 
264
addresses (a NS lookup followed by an A lookup), which in turn will be sent
 
265
to blocklists. This is a default when neither 'a' nor 'ns' flags are specified.
 
266
 
 
267
=item tflags NAME_OF_RULE a
 
268
 
 
269
The 'a' flag may be applied to rules corresponding to uridnsbl and uridnssub
 
270
directives. Host names from URLs will be mapped to their IP addresses, which
 
271
will be sent to blocklists. When both 'ns' and 'a' flags are specified,
 
272
both queries will be performed.
 
273
 
232
274
=back
233
275
 
234
276
=head1 ADMINISTRATOR SETTINGS
296
338
# the lookups here!
297
339
sub parsed_metadata {
298
340
  my ($self, $opts) = @_;
299
 
  my $scanner = $opts->{permsgstatus};
300
 
 
301
 
  return 0  if $scanner->{main}->{conf}->{skip_uribl_checks};
302
 
 
303
 
  if (!$scanner->is_dns_available()) {
 
341
  my $pms = $opts->{permsgstatus};
 
342
  my $conf = $pms->{conf};
 
343
 
 
344
  return 0  if $conf->{skip_uribl_checks};
 
345
 
 
346
  if (!$pms->is_dns_available()) {
304
347
    $self->{dns_not_available} = 1;
305
348
    return 0;
306
349
  } else {
309
352
    $self->{dns_not_available} = 0;
310
353
  }
311
354
 
312
 
  $scanner->{'uridnsbl_activerules'} = { };
313
 
  $scanner->{'uridnsbl_hits'} = { };
314
 
  $scanner->{'uridnsbl_seen_domain'} = { };
 
355
  $pms->{'uridnsbl_activerules'} = { };
 
356
  $pms->{'uridnsbl_hits'} = { };
 
357
  $pms->{'uridnsbl_seen_lookups'} = { };
315
358
 
316
359
  # only hit DNSBLs for active rules (defined and score != 0)
317
 
  $scanner->{'uridnsbl_active_rules_rhsbl'} = { };
318
 
  $scanner->{'uridnsbl_active_rules_rhsbl_ipsonly'} = { };
319
 
  $scanner->{'uridnsbl_active_rules_rhsbl_domsonly'} = { };
320
 
  $scanner->{'uridnsbl_active_rules_nsrhsbl'} = { };
321
 
  $scanner->{'uridnsbl_active_rules_fullnsrhsbl'} = { };
322
 
  $scanner->{'uridnsbl_active_rules_revipbl'} = { };
323
 
 
324
 
  foreach my $rulename (keys %{$scanner->{conf}->{uridnsbls}}) {
325
 
    next unless ($scanner->{conf}->is_rule_active('body_evals',$rulename));
326
 
 
327
 
    my $rulecf = $scanner->{conf}->{uridnsbls}->{$rulename};
328
 
    my $tflags = $scanner->{conf}->{tflags}->{$rulename};
 
360
  $pms->{'uridnsbl_active_rules_rhsbl'} = { };
 
361
  $pms->{'uridnsbl_active_rules_rhsbl_ipsonly'} = { };
 
362
  $pms->{'uridnsbl_active_rules_rhsbl_domsonly'} = { };
 
363
  $pms->{'uridnsbl_active_rules_nsrhsbl'} = { };
 
364
  $pms->{'uridnsbl_active_rules_fullnsrhsbl'} = { };
 
365
  $pms->{'uridnsbl_active_rules_nsrevipbl'} = { };
 
366
  $pms->{'uridnsbl_active_rules_arevipbl'} = { };
 
367
 
 
368
  foreach my $rulename (keys %{$conf->{uridnsbls}}) {
 
369
    next unless ($conf->is_rule_active('body_evals',$rulename));
 
370
 
 
371
    my $rulecf = $conf->{uridnsbls}->{$rulename};
 
372
    my $tflags = $conf->{tflags}->{$rulename};
329
373
    $tflags = ''  if !defined $tflags;
 
374
    my %tfl = map { ($_,1) } split(' ',$tflags);
330
375
 
331
 
    if ($rulecf->{is_rhsbl} && $tflags =~ /\b ips_only \b/x) {
332
 
      $scanner->{uridnsbl_active_rules_rhsbl_ipsonly}->{$rulename} = 1;
333
 
    } elsif ($rulecf->{is_rhsbl} && $tflags =~ /\b domains_only \b/x) {
334
 
      $scanner->{uridnsbl_active_rules_rhsbl_domsonly}->{$rulename} = 1;
335
 
    } elsif ($rulecf->{is_rhsbl}) {
336
 
      $scanner->{uridnsbl_active_rules_rhsbl}->{$rulename} = 1;
 
376
    my $is_rhsbl = $rulecf->{is_rhsbl};
 
377
    if (     $is_rhsbl && $tfl{'ips_only'}) {
 
378
      $pms->{uridnsbl_active_rules_rhsbl_ipsonly}->{$rulename} = 1;
 
379
    } elsif ($is_rhsbl && $tfl{'domains_only'}) {
 
380
      $pms->{uridnsbl_active_rules_rhsbl_domsonly}->{$rulename} = 1;
 
381
    } elsif ($is_rhsbl) {
 
382
      $pms->{uridnsbl_active_rules_rhsbl}->{$rulename} = 1;
337
383
    } elsif ($rulecf->{is_fullnsrhsbl}) {
338
 
      $scanner->{uridnsbl_active_rules_fullnsrhsbl}->{$rulename} = 1;
 
384
      $pms->{uridnsbl_active_rules_fullnsrhsbl}->{$rulename} = 1;
339
385
    } elsif ($rulecf->{is_nsrhsbl}) {
340
 
      $scanner->{uridnsbl_active_rules_nsrhsbl}->{$rulename} = 1;
341
 
    } else {
342
 
      $scanner->{uridnsbl_active_rules_revipbl}->{$rulename} = 1;
 
386
      $pms->{uridnsbl_active_rules_nsrhsbl}->{$rulename} = 1;
 
387
    } else {  # just a plain dnsbl rule (IP based), not a RHS rule (name-based)
 
388
      if ($tfl{'a'}) {  # tflag 'a' explicitly
 
389
        $pms->{uridnsbl_active_rules_arevipbl}->{$rulename} = 1;
 
390
      }
 
391
      if ($tfl{'ns'} || !$tfl{'a'}) {  # tflag 'ns' explicitly, or default
 
392
        $pms->{uridnsbl_active_rules_nsrevipbl}->{$rulename} = 1;
 
393
      }
343
394
    }
344
395
  }
345
396
 
346
397
  # get all domains in message
347
398
 
348
399
  # don't keep dereferencing this
349
 
  my $skip_domains = $scanner->{main}->{conf}->{uridnsbl_skip_domains};
 
400
  my $skip_domains = $conf->{uridnsbl_skip_domains};
350
401
  $skip_domains = {}  if !$skip_domains;
351
402
 
352
403
  # list of hashes to use in order
353
404
  my @uri_ordered;
354
405
 
355
406
  # Generate the full list of html-parsed domains.
356
 
  my $uris = $scanner->get_uri_detail_list();
 
407
  my $uris = $pms->get_uri_detail_list();
357
408
 
358
409
  # go from uri => info to uri_ordered
359
410
  # 0: a
364
415
  # 5: a_empty
365
416
  while (my($uri, $info) = each %{$uris}) {
366
417
    # we want to skip mailto: uris
367
 
    next if ($uri =~ /^mailto:/);
 
418
    next if ($uri =~ /^mailto:/i);
368
419
 
369
 
    # no domains were found via this uri, so skip
370
 
    next unless ($info->{domains});
 
420
    # no hosts/domains were found via this uri, so skip
 
421
    next unless ($info->{hosts});
371
422
 
372
423
    my $entry = 3;
373
424
 
392
443
      $entry = 4;
393
444
    }
394
445
 
395
 
    # take the usable domains and add to the ordered list
396
 
    foreach ( keys %{ $info->{domains} } ) {
397
 
      if (exists $skip_domains->{$_}) {
398
 
        dbg("uridnsbl: domain $_ in skip list");
399
 
        next;
 
446
    # take the usable domains and add them to the ordered list
 
447
    while (my($host,$domain) = each( %{$info->{hosts}} )) {
 
448
      if ($skip_domains->{$domain}) {
 
449
        dbg("uridnsbl: domain $domain in skip list, host $host");
 
450
      } else {
 
451
        # use hostname as a key, and drag along the stripped domain name part
 
452
        $uri_ordered[$entry]->{$host} = $domain;
400
453
      }
401
 
      $uri_ordered[$entry]->{$_} = 1;
402
454
    }
403
455
  }
404
456
 
405
 
  # at this point, @uri_ordered is an ordered array of uri hashes
406
 
 
407
 
  my %domlist;
408
 
  my $umd = $scanner->{main}->{conf}->{uridnsbl_max_domains};
409
 
  while (keys %domlist < $umd && @uri_ordered) {
 
457
  # at this point, @uri_ordered is an ordered array of hostname hashes
 
458
 
 
459
  my %hostlist;  # keys are host names, values are their domain parts
 
460
 
 
461
  my $umd = $conf->{uridnsbl_max_domains};
 
462
  while (keys %hostlist < $umd && @uri_ordered) {
410
463
    my $array = shift @uri_ordered;
411
464
    next unless $array;
412
465
 
413
466
    # run through and find the new domains in this grouping
414
 
    my @domains = grep(!$domlist{$_}, keys %{$array});
415
 
    next unless @domains;
 
467
    my @hosts = grep(!$hostlist{$_}, keys %{$array});
 
468
    next unless @hosts;
416
469
 
417
 
    # the new domains are all useful, just add them in
418
 
    if (keys(%domlist) + @domains <= $umd) {
419
 
      foreach (@domains) {
420
 
        $domlist{$_} = 1;
 
470
    # the new hosts are all useful, just add them in
 
471
    if (keys(%hostlist) + @hosts <= $umd) {
 
472
      foreach my $host (@hosts) {
 
473
        $hostlist{$host} = $array->{$host};
421
474
      }
422
475
    }
423
476
    else {
 
477
      dbg("uridnsbl: more than $umd URIs, picking a subset");
424
478
      # trim down to a limited number - pick randomly
425
 
      while (@domains && keys %domlist < $umd) {
426
 
        my $r = int rand (scalar @domains);
427
 
        $domlist{splice (@domains, $r, 1)} = 1;
 
479
      while (@hosts && keys %hostlist < $umd) {
 
480
        my $r = int rand(scalar @hosts);
 
481
        my $picked_host = splice(@hosts, $r, 1);
 
482
        $hostlist{$picked_host} = $array->{$picked_host};
428
483
      }
429
484
    }
430
485
  }
431
486
 
 
487
  my @hnames = keys %hostlist;
 
488
  $pms->set_tag('URIHOSTS',
 
489
                @hnames == 1 ? $hnames[0] : \@hnames)  if @hnames;
 
490
  my @dnames = values %hostlist;
 
491
  $pms->set_tag('URIDOMAINS',
 
492
                @dnames == 1 ? $dnames[0] : \@dnames)  if @dnames;
 
493
 
432
494
  # and query
433
 
  dbg("uridnsbl: domains to query: ".join(' ',keys %domlist));
434
 
  foreach my $dom (keys %domlist) {
435
 
    $self->query_domain ($scanner, $dom);
436
 
  }
 
495
  $self->query_hosts_or_domains($pms, \%hostlist);
437
496
 
438
497
  return 1;
439
498
}
472
531
        $_ = Mail::SpamAssassin::Util::my_inet_aton($_);  # quad-dot -> number
473
532
        $any_quad_dot = 1;
474
533
      } else {
475
 
        return undef;
 
534
        return;
476
535
      }
477
536
    }
478
537
    $digested_subtest = defined $n2 ? $n1.$delim.$n2
503
562
    is_priv => 1,
504
563
    code => sub {
505
564
      my ($self, $key, $value, $line) = @_;
 
565
      local($1,$2,$3);
506
566
      if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)$/) {
507
567
        my $rulename = $1;
508
568
        my $zone = $2;
509
569
        my $type = $3;
 
570
        $zone =~ s/\.\z//;  # strip a redundant trailing dot
510
571
        $self->{uridnsbls}->{$rulename} = {
511
572
          zone => $zone, type => $type,
512
573
          is_rhsbl => 0
532
593
        my $zone = $2;
533
594
        my $type = $3;
534
595
        my $subrule = $4;
 
596
        $zone =~ s/\.\z//;  # strip a redundant trailing dot
 
597
        $subrule = parse_and_canonicalize_subtest($subrule);
 
598
        defined $subrule or return $Mail::SpamAssassin::Conf::INVALID_VALUE;
535
599
        $self->{uridnsbls}->{$rulename} = {
536
600
         zone => $zone, type => $type,
537
 
          is_rhsbl => 0, is_subrule => 1
 
601
          is_rhsbl => 0, subtest => $subrule,
538
602
        };
539
 
        $self->{uridnsbl_subs}->{$zone} ||= { };
540
 
        $subrule = parse_and_canonicalize_subtest($subrule);
541
 
        defined $subrule or return $Mail::SpamAssassin::Conf::INVALID_VALUE;
542
 
        push(@{$self->{uridnsbl_subs}->{$zone}->{$subrule}->{rulenames}},
543
 
             $rulename);
544
603
      }
545
604
      elsif ($value =~ /^$/) {
546
605
        return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
556
615
    is_priv => 1,
557
616
    code => sub {
558
617
      my ($self, $key, $value, $line) = @_;
 
618
      local($1,$2,$3);
559
619
      if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)$/) {
560
620
        my $rulename = $1;
561
621
        my $zone = $2;
562
622
        my $type = $3;
 
623
        $zone =~ s/\.\z//;  # strip a redundant trailing dot
563
624
        $self->{uridnsbls}->{$rulename} = {
564
625
          zone => $zone, type => $type,
565
626
          is_rhsbl => 1
585
646
        my $zone = $2;
586
647
        my $type = $3;
587
648
        my $subrule = $4;
 
649
        $zone =~ s/\.\z//;  # strip a redundant trailing dot
 
650
        $subrule = parse_and_canonicalize_subtest($subrule);
 
651
        defined $subrule or return $Mail::SpamAssassin::Conf::INVALID_VALUE;
588
652
        $self->{uridnsbls}->{$rulename} = {
589
653
          zone => $zone, type => $type,
590
 
          is_rhsbl => 1, is_subrule => 1
 
654
          is_rhsbl => 1, subtest => $subrule,
591
655
        };
592
 
        $self->{uridnsbl_subs}->{$zone} ||= { };
593
 
        $subrule = parse_and_canonicalize_subtest($subrule);
594
 
        defined $subrule or return $Mail::SpamAssassin::Conf::INVALID_VALUE;
595
 
        push(@{$self->{uridnsbl_subs}->{$zone}->{$subrule}->{rulenames}},
596
 
             $rulename);
597
656
      }
598
657
      elsif ($value =~ /^$/) {
599
658
        return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
609
668
    is_priv => 1,
610
669
    code => sub {
611
670
      my ($self, $key, $value, $line) = @_;
 
671
      local($1,$2,$3);
612
672
      if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)$/) {
613
673
        my $rulename = $1;
614
674
        my $zone = $2;
615
675
        my $type = $3;
 
676
        $zone =~ s/\.\z//;  # strip a redundant trailing dot
616
677
        $self->{uridnsbls}->{$rulename} = {
617
678
          zone => $zone, type => $type,
618
679
          is_nsrhsbl => 1
638
699
        my $zone = $2;
639
700
        my $type = $3;
640
701
        my $subrule = $4;
 
702
        $zone =~ s/\.\z//;  # strip a redundant trailing dot
 
703
        $subrule = parse_and_canonicalize_subtest($subrule);
 
704
        defined $subrule or return $Mail::SpamAssassin::Conf::INVALID_VALUE;
641
705
        $self->{uridnsbls}->{$rulename} = {
642
706
          zone => $zone, type => $type,
643
 
          is_nsrhsbl => 1, is_subrule => 1
 
707
          is_nsrhsbl => 1, subtest => $subrule,
644
708
        };
645
 
        $self->{uridnsbl_subs}->{$zone} ||= { };
646
 
        $subrule = parse_and_canonicalize_subtest($subrule);
647
 
        defined $subrule or return $Mail::SpamAssassin::Conf::INVALID_VALUE;
648
 
        push(@{$self->{uridnsbl_subs}->{$zone}->{$subrule}->{rulenames}},
649
 
             $rulename);
650
709
      }
651
710
      elsif ($value =~ /^$/) {
652
711
        return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
662
721
    is_priv => 1,
663
722
    code => sub {
664
723
      my ($self, $key, $value, $line) = @_;
 
724
      local($1,$2,$3);
665
725
      if ($value =~ /^(\S+)\s+(\S+)\s+(\S+)$/) {
666
726
        my $rulename = $1;
667
727
        my $zone = $2;
668
728
        my $type = $3;
 
729
        $zone =~ s/\.\z//;  # strip a redundant trailing dot
669
730
        $self->{uridnsbls}->{$rulename} = {
670
731
          zone => $zone, type => $type,
671
732
          is_fullnsrhsbl => 1
691
752
        my $zone = $2;
692
753
        my $type = $3;
693
754
        my $subrule = $4;
 
755
        $zone =~ s/\.\z//;  # strip a redundant trailing dot
 
756
        $subrule = parse_and_canonicalize_subtest($subrule);
 
757
        defined $subrule or return $Mail::SpamAssassin::Conf::INVALID_VALUE;
694
758
        $self->{uridnsbls}->{$rulename} = {
695
759
          zone => $zone, type => $type,
696
 
          is_fullnsrhsbl => 1, is_subrule => 1
 
760
          is_fullnsrhsbl => 1, subtest => $subrule,
697
761
        };
698
 
        $self->{uridnsbl_subs}->{$zone} ||= { };
699
 
        $subrule = parse_and_canonicalize_subtest($subrule);
700
 
        defined $subrule or return $Mail::SpamAssassin::Conf::INVALID_VALUE;
701
 
        push(@{$self->{uridnsbl_subs}->{$zone}->{$subrule}->{rulenames}},
702
 
             $rulename);
703
762
      }
704
763
      elsif ($value =~ /^$/) {
705
764
        return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
757
816
 
758
817
# ---------------------------------------------------------------------------
759
818
 
760
 
sub query_domain {
761
 
  my ($self, $scanner, $dom) = @_;
762
 
 
763
 
  #warn "uridnsbl: domain $dom\n";
764
 
  #return;
765
 
 
766
 
  $dom = lc $dom;
767
 
  return if $scanner->{uridnsbl_seen_domain}->{$dom};
768
 
  $scanner->{uridnsbl_seen_domain}->{$dom} = 1;
769
 
  $self->log_dns_result("querying domain $dom");
770
 
 
771
 
  my $obj = { dom => $dom };
772
 
 
773
 
  my $tflags = $scanner->{conf}->{tflags};
774
 
  my $cf = $scanner->{uridnsbl_active_rules_revipbl};
775
 
 
776
 
  my ($is_ip, $single_dnsbl);
777
 
  if ($dom =~ /^\d+\.\d+\.\d+\.\d+$/) {
778
 
    my $IPV4_ADDRESS = IPV4_ADDRESS;
779
 
    my $IP_PRIVATE = IP_PRIVATE;
780
 
    # only look up the IP if it is public and valid
781
 
    if ($dom =~ /^$IPV4_ADDRESS$/ && $dom !~ /^$IP_PRIVATE$/) {
782
 
      $self->lookup_dnsbl_for_ip($scanner, $obj, $dom);
783
 
      # and check the IP in RHSBLs too
784
 
      local($1,$2,$3,$4);
785
 
      if ($dom =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
786
 
        $dom = "$4.$3.$2.$1";
787
 
        $single_dnsbl = 1;
788
 
        $is_ip = 1;
789
 
      }
790
 
    }
791
 
  }
792
 
  else {
793
 
    $single_dnsbl = 1;
794
 
  }
795
 
 
796
 
  my $rhsblrules = $scanner->{uridnsbl_active_rules_rhsbl};
797
 
  my $rhsbliprules = $scanner->{uridnsbl_active_rules_rhsbl_ipsonly};
798
 
  my $rhsbldomrules = $scanner->{uridnsbl_active_rules_rhsbl_domsonly};
799
 
  my $nsrhsblrules = $scanner->{uridnsbl_active_rules_nsrhsbl};
800
 
  my $fullnsrhsblrules = $scanner->{uridnsbl_active_rules_fullnsrhsbl};
801
 
  my $reviprules = $scanner->{uridnsbl_active_rules_revipbl};
802
 
 
803
 
  if ($single_dnsbl) {
804
 
    # look up the domain in the basic RHSBL subset
805
 
    my @rhsbldoms = keys %{$rhsblrules};
806
 
 
807
 
    # and add the "domains_only" and "ips_only" subsets as appropriate
808
 
    if ($is_ip) {
809
 
      push @rhsbldoms, keys %{$rhsbliprules};
810
 
    } else {
811
 
      push @rhsbldoms, keys %{$rhsbldomrules};
812
 
    }
813
 
 
814
 
    foreach my $rulename (@rhsbldoms) {
815
 
      my $rulecf = $scanner->{conf}->{uridnsbls}->{$rulename};
816
 
      $self->lookup_single_dnsbl($scanner, $obj, $rulename,
817
 
                                 $dom, $rulecf->{zone}, $rulecf->{type});
818
 
 
819
 
      # see comment below
820
 
      $scanner->register_async_rule_start($rulename);
821
 
    }
822
 
 
823
 
    # perform NS, A lookups to look up the domain in the non-RHSBL subset,
824
 
    # but only if there are active reverse-IP-URIBL rules
825
 
    if ($dom !~ /^\d+\.\d+\.\d+\.\d+$/ && 
826
 
                (scalar keys %{$reviprules} ||
827
 
                  scalar keys %{$nsrhsblrules} ||
828
 
                  scalar keys %{$fullnsrhsblrules}))
829
 
    {
830
 
      $self->lookup_domain_ns($scanner, $obj, $dom);
831
 
    }
832
 
  }
833
 
 
834
 
  # note that these rules are now underway.   important: unless the
835
 
  # rule hits, in the current design, these will not be considered
836
 
  # "finished" until harvest_dnsbl_queries() completes
837
 
  foreach my $rulename (keys %{$reviprules}) {
838
 
    $scanner->register_async_rule_start($rulename);
 
819
sub query_hosts_or_domains {
 
820
  my ($self, $pms, $hosthash_ref) = @_;
 
821
  my $conf = $pms->{conf};
 
822
  my $seen_lookups = $pms->{'uridnsbl_seen_lookups'};
 
823
 
 
824
  my $rhsblrules = $pms->{uridnsbl_active_rules_rhsbl};
 
825
  my $rhsbliprules = $pms->{uridnsbl_active_rules_rhsbl_ipsonly};
 
826
  my $rhsbldomrules = $pms->{uridnsbl_active_rules_rhsbl_domsonly};
 
827
  my $nsrhsblrules = $pms->{uridnsbl_active_rules_nsrhsbl};
 
828
  my $fullnsrhsblrules = $pms->{uridnsbl_active_rules_fullnsrhsbl};
 
829
  my $nsreviprules = $pms->{uridnsbl_active_rules_nsrevipbl};
 
830
  my $areviprules = $pms->{uridnsbl_active_rules_arevipbl};
 
831
 
 
832
  while (my($host,$domain) = each(%$hosthash_ref)) {
 
833
    $domain = lc $domain;  # just in case
 
834
    $host = lc $host;
 
835
    dbg("uridnsbl: considering host=$host, domain=$domain");
 
836
    my $obj = { dom => $domain };
 
837
 
 
838
    my ($is_ip, $single_dnsbl);
 
839
    if ($host =~ /^\d+\.\d+\.\d+\.\d+$/) {
 
840
      my $IPV4_ADDRESS = IPV4_ADDRESS;
 
841
      my $IP_PRIVATE = IP_PRIVATE;
 
842
      # only look up the IP if it is public and valid
 
843
      if ($host =~ /^$IPV4_ADDRESS$/o && $host !~ /^$IP_PRIVATE$/o) {
 
844
        my $obj = { dom => $host };
 
845
        $self->lookup_dnsbl_for_ip($pms, $obj, $host);
 
846
        # and check the IP in RHSBLs too
 
847
        local($1,$2,$3,$4);
 
848
        if ($host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
 
849
          $domain = "$4.$3.$2.$1";
 
850
          $single_dnsbl = 1;
 
851
          $is_ip = 1;
 
852
        }
 
853
      }
 
854
    }
 
855
    else {
 
856
      $single_dnsbl = 1;
 
857
    }
 
858
 
 
859
    if ($single_dnsbl) {
 
860
      # rule names which look up a domain in the basic RHSBL subset
 
861
      my @rhsblrules = keys %{$rhsblrules};
 
862
 
 
863
      # and add the "domains_only" and "ips_only" subsets as appropriate
 
864
      if ($is_ip) {
 
865
        push @rhsblrules, keys %{$rhsbliprules};
 
866
      } else {
 
867
        push @rhsblrules, keys %{$rhsbldomrules};
 
868
      }
 
869
 
 
870
      foreach my $rulename (@rhsblrules) {
 
871
        my $rulecf = $conf->{uridnsbls}->{$rulename};
 
872
        $self->lookup_single_dnsbl($pms, $obj, $rulename,
 
873
                                   $domain, $rulecf->{zone}, $rulecf->{type});
 
874
 
 
875
        # note that these rules are now underway.   important: unless the
 
876
        # rule hits, in the current design, these will not be considered
 
877
        # "finished" until harvest_dnsbl_queries() completes
 
878
        $pms->register_async_rule_start($rulename);
 
879
      }
 
880
 
 
881
      # perform NS+A or A queries to look up the domain in the non-RHSBL subset,
 
882
      # but only if there are active reverse-IP-URIBL rules
 
883
      if ($host !~ /^\d+\.\d+\.\d+\.\d+$/) {
 
884
        if ( !$seen_lookups->{'NS:'.$domain} &&
 
885
             (%$nsreviprules || %$nsrhsblrules || %$fullnsrhsblrules) ) {
 
886
          $seen_lookups->{'NS:'.$domain} = 1;
 
887
          $self->lookup_domain_ns($pms, $obj, $domain);
 
888
        }
 
889
        if (%$areviprules && !$seen_lookups->{'A:'.$host}) {
 
890
          $seen_lookups->{'A:'.$host} = 1;
 
891
          my $obj = { dom => $host };
 
892
          $self->lookup_a_record($pms, $obj, $host);
 
893
          $pms->register_async_rule_start($_)  for keys %$areviprules;
 
894
        }
 
895
      }
 
896
    }
839
897
  }
840
898
}
841
899
 
842
900
# ---------------------------------------------------------------------------
843
901
 
844
902
sub lookup_domain_ns {
845
 
  my ($self, $scanner, $obj, $dom) = @_;
846
 
 
847
 
  my $key = "NS:".$dom;
848
 
  return if $scanner->{async}->get_lookup($key);
849
 
 
 
903
  my ($self, $pms, $obj, $dom, $rulename) = @_;
 
904
 
 
905
  my $key = "NS:" . $dom;
 
906
  my $ent = {
 
907
    key => $key, zone => $dom, obj => $obj, type => "URI-NS",
 
908
    rulename => $rulename,
 
909
  };
850
910
  # dig $dom ns
851
 
  my $ent = $self->start_lookup($scanner, $dom, 'NS',
852
 
                                $self->res_bgsend($scanner, $dom, 'NS', $key),
853
 
                                $key);
854
 
  $ent->{obj} = $obj;
 
911
  $ent = $pms->{async}->bgsend_and_start_lookup(
 
912
    $dom, 'NS', undef, $ent,
 
913
    sub { my ($ent2,$pkt) = @_;
 
914
          $self->complete_ns_lookup($pms, $ent2, $pkt, $dom) },
 
915
    master_deadline => $pms->{master_deadline} );
 
916
 
 
917
  return $ent;
855
918
}
856
919
 
857
920
sub complete_ns_lookup {
858
 
  my ($self, $scanner, $ent, $dom) = @_;
859
 
 
860
 
  my $packet = $ent->{response_packet};
861
 
  my @answer = !defined $packet ? () : $packet->answer;
 
921
  my ($self, $pms, $ent, $pkt, $dom) = @_;
 
922
 
 
923
  if (!$pkt) {
 
924
    # $pkt will be undef if the DNS query was aborted (e.g. timed out)
 
925
    dbg("uridnsbl: complete_ns_lookup aborted %s", $ent->{key});
 
926
    return;
 
927
  }
 
928
 
 
929
  dbg("uridnsbl: complete_ns_lookup %s", $ent->{key});
 
930
  my $conf = $pms->{conf};
 
931
  my @answer = $pkt->answer;
862
932
 
863
933
  my $IPV4_ADDRESS = IPV4_ADDRESS;
864
934
  my $IP_PRIVATE = IP_PRIVATE;
865
 
  my $nsrhsblrules = $scanner->{uridnsbl_active_rules_nsrhsbl};
866
 
  my $fullnsrhsblrules = $scanner->{uridnsbl_active_rules_fullnsrhsbl};
 
935
  my $nsrhsblrules = $pms->{uridnsbl_active_rules_nsrhsbl};
 
936
  my $fullnsrhsblrules = $pms->{uridnsbl_active_rules_fullnsrhsbl};
 
937
  my $seen_lookups = $pms->{'uridnsbl_seen_lookups'};
867
938
 
 
939
  my $j = 0;
868
940
  foreach my $rr (@answer) {
 
941
    $j++;
869
942
    my $str = $rr->string;
870
943
    next unless (defined($str) && defined($dom));
871
 
    $self->log_dns_result ("NSs for $dom: $str");
 
944
    dbg("uridnsbl: got($j) NS for $dom: $str");
872
945
 
873
946
    if ($str =~ /IN\s+NS\s+(\S+)/) {
874
 
      my $nsmatch = $1;
 
947
      my $nsmatch = lc $1;
 
948
      $nsmatch =~ s/\.$//;
875
949
      my $nsrhblstr = $nsmatch;
876
950
      my $fullnsrhblstr = $nsmatch;
877
 
      $fullnsrhblstr =~ s/\.$//;
878
951
 
879
 
      if ($nsmatch =~ /^\d+\.\d+\.\d+\.\d+\.?$/) {
880
 
        $nsmatch =~ s/\.$//;
 
952
      if ($nsmatch =~ /^\d+\.\d+\.\d+\.\d+$/) {
881
953
        # only look up the IP if it is public and valid
882
 
        if ($nsmatch =~ /^$IPV4_ADDRESS$/ && $nsmatch !~ /^$IP_PRIVATE$/) {
883
 
          $self->lookup_dnsbl_for_ip($scanner, $ent->{obj}, $nsmatch);
 
954
        if ($nsmatch =~ /^$IPV4_ADDRESS$/o && $nsmatch !~ /^$IP_PRIVATE$/o) {
 
955
          $self->lookup_dnsbl_for_ip($pms, $ent->{obj}, $nsmatch);
884
956
        }
885
957
        $nsrhblstr = $nsmatch;
886
958
      }
887
959
      else {
888
 
        $self->lookup_a_record($scanner, $ent->{obj}, $nsmatch);
 
960
        if (!$seen_lookups->{'A:'.$nsmatch}) {
 
961
          $seen_lookups->{'A:'.$nsmatch} = 1;
 
962
          $self->lookup_a_record($pms, $ent->{obj}, $nsmatch);
 
963
        }
889
964
        $nsrhblstr = Mail::SpamAssassin::Util::RegistrarBoundaries::trim_domain($nsmatch);
890
965
      }
891
966
 
892
967
      foreach my $rulename (keys %{$nsrhsblrules}) {
893
 
        my $rulecf = $scanner->{conf}->{uridnsbls}->{$rulename};
894
 
        $self->lookup_single_dnsbl($scanner, $ent->{obj}, $rulename,
 
968
        my $rulecf = $conf->{uridnsbls}->{$rulename};
 
969
        $self->lookup_single_dnsbl($pms, $ent->{obj}, $rulename,
895
970
                                  $nsrhblstr, $rulecf->{zone}, $rulecf->{type});
896
971
 
897
 
        $scanner->register_async_rule_start($rulename);
 
972
        $pms->register_async_rule_start($rulename);
898
973
      }
899
974
 
900
975
      foreach my $rulename (keys %{$fullnsrhsblrules}) {
901
 
        my $rulecf = $scanner->{conf}->{uridnsbls}->{$rulename};
902
 
        $self->lookup_single_dnsbl($scanner, $ent->{obj}, $rulename,
 
976
        my $rulecf = $conf->{uridnsbls}->{$rulename};
 
977
        $self->lookup_single_dnsbl($pms, $ent->{obj}, $rulename,
903
978
                                  $fullnsrhblstr, $rulecf->{zone}, $rulecf->{type});
904
979
 
905
 
        $scanner->register_async_rule_start($rulename);
 
980
        $pms->register_async_rule_start($rulename);
906
981
      }
907
982
    }
908
983
  }
911
986
# ---------------------------------------------------------------------------
912
987
 
913
988
sub lookup_a_record {
914
 
  my ($self, $scanner, $obj, $hname) = @_;
915
 
 
916
 
  my $key = "A:".$hname;
917
 
  return if $scanner->{async}->get_lookup($key);
918
 
 
 
989
  my ($self, $pms, $obj, $hname, $rulename) = @_;
 
990
 
 
991
  my $key = "A:" . $hname;
 
992
  my $ent = {
 
993
    key => $key, zone => $hname, obj => $obj, type => "URI-A",
 
994
    rulename => $rulename,
 
995
  };
919
996
  # dig $hname a
920
 
  my $ent = $self->start_lookup($scanner, $hname, 'A',
921
 
                                $self->res_bgsend($scanner, $hname, 'A', $key),
922
 
                                $key);
923
 
  $ent->{obj} = $obj;
 
997
  $ent = $pms->{async}->bgsend_and_start_lookup(
 
998
    $hname, 'A', undef, $ent,
 
999
    sub { my ($ent2,$pkt) = @_;
 
1000
          $self->complete_a_lookup($pms, $ent2, $pkt, $hname) },
 
1001
    master_deadline => $pms->{master_deadline} );
 
1002
 
 
1003
  return $ent;
924
1004
}
925
1005
 
926
1006
sub complete_a_lookup {
927
 
  my ($self, $scanner, $ent, $hname) = @_;
928
 
 
929
 
  my $packet = $ent->{response_packet};
930
 
  my @answer = !defined $packet ? () : $packet->answer;
 
1007
  my ($self, $pms, $ent, $pkt, $hname) = @_;
 
1008
 
 
1009
  if (!$pkt) {
 
1010
    # $pkt will be undef if the DNS query was aborted (e.g. timed out)
 
1011
    dbg("uridnsbl: complete_a_lookup aborted %s", $ent->{key});
 
1012
    return;
 
1013
  }
 
1014
 
 
1015
  dbg("uridnsbl: complete_a_lookup %s", $ent->{key});
 
1016
  my @answer = $pkt->answer;
 
1017
  my $j = 0;
931
1018
  foreach my $rr (@answer) {
 
1019
    $j++;
932
1020
    my $str = $rr->string;
933
 
    $self->log_dns_result ("A for NS $hname: $str");
 
1021
    if (!defined $hname) {
 
1022
      warn "complete_a_lookup-1: $j, (hname is undef), $str";
 
1023
    } elsif (!defined $str) {
 
1024
      warn "complete_a_lookup-2: $j, $hname, (str is undef)";
 
1025
      next;
 
1026
    }
 
1027
    dbg("uridnsbl: complete_a_lookup got(%d) A for %s: %s", $j,$hname,$str);
934
1028
 
 
1029
    local $1;
935
1030
    if ($str =~ /IN\s+A\s+(\S+)/) {
936
 
      $self->lookup_dnsbl_for_ip($scanner, $ent->{obj}, $1);
 
1031
      $self->lookup_dnsbl_for_ip($pms, $ent->{obj}, $1);
937
1032
    }
938
1033
  }
939
1034
}
941
1036
# ---------------------------------------------------------------------------
942
1037
 
943
1038
sub lookup_dnsbl_for_ip {
944
 
  my ($self, $scanner, $obj, $ip) = @_;
 
1039
  my ($self, $pms, $obj, $ip) = @_;
945
1040
 
946
1041
  local($1,$2,$3,$4);
947
1042
  $ip =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
948
1043
  my $revip = "$4.$3.$2.$1";
949
1044
 
950
 
  my $tflags = $scanner->{conf}->{tflags};
951
 
  my $cf = $scanner->{uridnsbl_active_rules_revipbl};
952
 
  foreach my $rulename (keys %{$cf}) {
953
 
    my $rulecf = $scanner->{conf}->{uridnsbls}->{$rulename};
 
1045
  my $conf = $pms->{conf};
 
1046
  my $tflags = $conf->{tflags};
 
1047
  my $cfns = $pms->{uridnsbl_active_rules_nsrevipbl};
 
1048
  my $cfa  = $pms->{uridnsbl_active_rules_arevipbl};
 
1049
  foreach my $rulename (keys %$cfa, keys %$cfns) {
 
1050
    my $rulecf = $conf->{uridnsbls}->{$rulename};
954
1051
 
955
1052
    # ips_only/domains_only lookups should not act on this kind of BL
956
 
    next if ($tflags->{$rulename} =~ /\b(?:ips_only|domains_only)\b/);
957
 
    
958
 
    $self->lookup_single_dnsbl($scanner, $obj, $rulename,
 
1053
    next  if defined $tflags->{$rulename} &&
 
1054
             $tflags->{$rulename} =~ /\b(?:ips_only|domains_only)\b/;
 
1055
 
 
1056
    $self->lookup_single_dnsbl($pms, $obj, $rulename,
959
1057
                               $revip, $rulecf->{zone}, $rulecf->{type});
960
1058
  }
961
1059
}
962
1060
 
963
1061
sub lookup_single_dnsbl {
964
 
  my ($self, $scanner, $obj, $rulename, $lookupstr, $dnsbl, $qtype) = @_;
965
 
 
966
 
  my $key = "DNSBL:".$dnsbl.":".$lookupstr;
967
 
  return if $scanner->{async}->get_lookup($key);
968
 
  my $item = $lookupstr.".".$dnsbl;
969
 
 
970
 
  # dig $ip txt
971
 
  my $ent = $self->start_lookup($scanner, $item, 'DNSBL',
972
 
                              $self->res_bgsend($scanner, $item, $qtype, $key),
973
 
                              $key);
974
 
  $ent->{obj} = $obj;
975
 
  $ent->{rulename} = $rulename;
976
 
  $ent->{zone} = $dnsbl;
 
1062
  my ($self, $pms, $obj, $rulename, $lookupstr, $dnsbl, $qtype) = @_;
 
1063
 
 
1064
  my $key = "DNSBL:" . $lookupstr . ':' . $dnsbl;
 
1065
  my $ent = {
 
1066
    key => $key, zone => $dnsbl, obj => $obj, type => 'URI-DNSBL',
 
1067
    rulename => $rulename,
 
1068
  };
 
1069
  $ent = $pms->{async}->bgsend_and_start_lookup(
 
1070
    $lookupstr.".".$dnsbl, $qtype, undef, $ent,
 
1071
    sub { my ($ent2,$pkt) = @_;
 
1072
          $self->complete_dnsbl_lookup($pms, $ent2, $pkt) },
 
1073
    master_deadline => $pms->{master_deadline} );
 
1074
 
 
1075
  return $ent;
977
1076
}
978
1077
 
979
1078
sub complete_dnsbl_lookup {
980
 
  my ($self, $scanner, $ent, $dnsblip) = @_;
981
 
 
982
 
  my $conf = $scanner->{conf};
983
 
  my @subtests;
 
1079
  my ($self, $pms, $ent, $pkt) = @_;
 
1080
 
 
1081
  if (!$pkt) {
 
1082
    # $pkt will be undef if the DNS query was aborted (e.g. timed out)
 
1083
    dbg("uridnsbl: complete_dnsbl_lookup aborted %s %s",
 
1084
        $ent->{rulename}, $ent->{key});
 
1085
    return;
 
1086
  }
 
1087
 
 
1088
  dbg("uridnsbl: complete_dnsbl_lookup %s %s", $ent->{rulename}, $ent->{key});
 
1089
  my $conf = $pms->{conf};
 
1090
 
 
1091
  my $zone = $ent->{zone};
 
1092
  my $dom = $ent->{obj}->{dom};
984
1093
  my $rulename = $ent->{rulename};
985
1094
  my $rulecf = $conf->{uridnsbls}->{$rulename};
986
1095
 
987
 
  my $packet = $ent->{response_packet};
988
 
  my @answer = !defined $packet ? () : $packet->answer;
989
 
 
990
 
  my $uridnsbl_subs = $conf->{uridnsbl_subs}->{$ent->{zone}};
 
1096
  my @subtests;
 
1097
  my @answer = $pkt->answer;
991
1098
  foreach my $rr (@answer)
992
1099
  {
993
 
    next if ($rr->type ne 'A' && $rr->type ne 'TXT');
 
1100
    my($rdatastr,$rdatanum);
 
1101
    my $rr_type = $rr->type;
994
1102
 
995
 
    my $dom = $ent->{obj}->{dom};
996
 
    my $rdatastr = $rr->rdatastr;
997
 
    my $rdatanum;
998
 
    if ($rdatastr =~ m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
999
 
      $rdatanum = Mail::SpamAssassin::Util::my_inet_aton($rdatastr);
 
1103
    if ($rr_type eq 'A') {
 
1104
      $rdatastr = $rr->rdatastr;
 
1105
      if ($rdatastr =~ m/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
 
1106
        $rdatanum = Mail::SpamAssassin::Util::my_inet_aton($rdatastr);
 
1107
      }
 
1108
    } elsif ($rr_type eq 'TXT') {
 
1109
      # txtdata returns a non- zone-file-format encoded result, unlike rdatastr;
 
1110
      # avoid space-separated RDATA <character-string> fields if possible;
 
1111
      # txtdata provides a list of strings in list context since Net::DNS 0.69
 
1112
      $rdatastr = join('',$rr->txtdata);
 
1113
    } else {
 
1114
      next;
1000
1115
    }
1001
1116
 
1002
 
    if (!$rulecf->{is_subrule}) {
 
1117
    my $subtest = $rulecf->{subtest};
 
1118
 
 
1119
    dbg("uridnsbl: %s . %s -> %s, %s%s",
 
1120
        $dom, $zone, $rdatastr, $rulename,
 
1121
        !defined $subtest ? '' : ', subtest:'.$subtest);
 
1122
 
 
1123
    my $match;
 
1124
    if (!defined $subtest) {
1003
1125
      # this zone is a simple rule, not a set of subrules
1004
1126
      # skip any A record that isn't on 127/8
1005
 
      if ($rr->type eq 'A' && $rr->rdatastr !~ /^127\./) {
 
1127
      if ($rr_type eq 'A' && $rdatastr !~ /^127\./) {
1006
1128
        warn("uridnsbl: bogus rr for domain=$dom, rule=$rulename, id=" .
1007
 
            $packet->header->id." rr=".$rr->string);
 
1129
            $pkt->header->id." rr=".$rr->string);
1008
1130
        next;
1009
1131
      }
1010
 
      $self->got_dnsbl_hit($scanner, $ent, $rdatastr, $dom, $rulename);
1011
 
    }
1012
 
    else {
1013
 
      local($1,$2,$3);
1014
 
      foreach my $subtest (keys (%{$uridnsbl_subs})) {
1015
 
        my $match;
1016
 
        if ($subtest eq $rdatastr) {
1017
 
          $match = 1;
1018
 
        } elsif ($subtest =~ m{^ (\d+) (?: ([/-]) (\d+) )? \z}x) {
1019
 
          my($n1,$delim,$n2) = ($1,$2,$3);
1020
 
          $match =
1021
 
            !defined $n2  ? $rdatanum & $n1                       # mask only
1022
 
          : $delim eq '-' ? $rdatanum >= $n1 && $rdatanum <= $n2  # range
1023
 
          : $delim eq '/' ? ($rdatanum & $n2) == ($n1 & $n2)      # value/mask
1024
 
          : 0;  
1025
 
        # dbg("uridnsbl: %s %s/%s/%s, %s, %s", $match?'Y':'N', $dom, $rulename,
1026
 
        #     join('.',@{$uridnsbl_subs->{$subtest}->{rulenames}}),
1027
 
        #     $rdatanum, !defined $n2 ? $n1 : "$n1 $delim $n2");
1028
 
        }
1029
 
        if ($match) {
1030
 
          foreach my $subrulename (@{$uridnsbl_subs->{$subtest}->{rulenames}}) {
1031
 
            $self->got_dnsbl_hit($scanner, $ent, $rdatastr, $dom, $subrulename);
1032
 
          }
1033
 
        }
1034
 
      }
1035
 
    }
 
1132
      $match = 1;
 
1133
    } elsif ($subtest eq $rdatastr) {
 
1134
      $match = 1;
 
1135
    } elsif ($subtest =~ m{^ (\d+) (?: ([/-]) (\d+) )? \z}x) {
 
1136
      my($n1,$delim,$n2) = ($1,$2,$3);
 
1137
      $match =
 
1138
        !defined $n2  ? ($rdatanum & $n1) &&                  # mask only
 
1139
                          (($rdatanum & 0xff000000) == 0x7f000000)  # 127/8
 
1140
      : $delim eq '-' ? $rdatanum >= $n1 && $rdatanum <= $n2  # range
 
1141
      : $delim eq '/' ? ($rdatanum & $n2) == ($n1 & $n2)      # value/mask
 
1142
      : 0;  
 
1143
 
 
1144
      dbg("uridnsbl: %s . %s -> %s, %s, %08x %s %s",
 
1145
          $dom, $zone, $rdatastr, $rulename, $rdatanum,
 
1146
          !defined $n2 ? sprintf('& %08x', $n1)
 
1147
          : $n1 == $n2 ? sprintf('== %08x', $n1)
 
1148
          :              sprintf('%08x%s%08x', $n1,$delim,$n2),
 
1149
          $match ? 'match' : 'no');
 
1150
    }
 
1151
    $self->got_dnsbl_hit($pms, $ent, $rdatastr, $dom, $rulename) if $match;
1036
1152
  }
1037
1153
}
1038
1154
 
1039
1155
sub got_dnsbl_hit {
1040
 
  my ($self, $scanner, $ent, $str, $dom, $rulename) = @_;
 
1156
  my ($self, $pms, $ent, $str, $dom, $rulename) = @_;
1041
1157
 
1042
1158
  $str =~ s/\s+/  /gs;  # long whitespace => short
1043
1159
  dbg("uridnsbl: domain \"$dom\" listed ($rulename): $str");
1044
1160
 
1045
 
  if (!defined $scanner->{uridnsbl_hits}->{$rulename}) {
1046
 
    $scanner->{uridnsbl_hits}->{$rulename} = { };
 
1161
  if (!defined $pms->{uridnsbl_hits}->{$rulename}) {
 
1162
    $pms->{uridnsbl_hits}->{$rulename} = { };
1047
1163
  };
1048
 
  $scanner->{uridnsbl_hits}->{$rulename}->{$dom} = 1;
 
1164
  $pms->{uridnsbl_hits}->{$rulename}->{$dom} = 1;
1049
1165
 
1050
 
  if ($scanner->{uridnsbl_active_rules_revipbl}->{$rulename}
1051
 
    || $scanner->{uridnsbl_active_rules_nsrhsbl}->{$rulename}
1052
 
    || $scanner->{uridnsbl_active_rules_fullnsrhsbl}->{$rulename}
1053
 
    || $scanner->{uridnsbl_active_rules_rhsbl}->{$rulename}
1054
 
    || $scanner->{uridnsbl_active_rules_rhsbl_ipsonly}->{$rulename}
1055
 
    || $scanner->{uridnsbl_active_rules_rhsbl_domsonly}->{$rulename})
 
1166
  if ( $pms->{uridnsbl_active_rules_nsrevipbl}->{$rulename}
 
1167
    || $pms->{uridnsbl_active_rules_arevipbl}->{$rulename}
 
1168
    || $pms->{uridnsbl_active_rules_nsrhsbl}->{$rulename}
 
1169
    || $pms->{uridnsbl_active_rules_fullnsrhsbl}->{$rulename}
 
1170
    || $pms->{uridnsbl_active_rules_rhsbl}->{$rulename}
 
1171
    || $pms->{uridnsbl_active_rules_rhsbl_ipsonly}->{$rulename}
 
1172
    || $pms->{uridnsbl_active_rules_rhsbl_domsonly}->{$rulename})
1056
1173
  {
1057
1174
    # TODO: this needs to handle multiple domain hits per rule
1058
 
    $scanner->clear_test_state();
1059
 
    my $uris = join (' ', keys %{$scanner->{uridnsbl_hits}->{$rulename}});
1060
 
    $scanner->test_log ("URIs: $uris");
1061
 
    $scanner->got_hit ($rulename, "");
 
1175
    $pms->clear_test_state();
 
1176
    my $uris = join (' ', keys %{$pms->{uridnsbl_hits}->{$rulename}});
 
1177
    $pms->test_log ("URIs: $uris");
 
1178
    $pms->got_hit ($rulename, "");
1062
1179
 
1063
1180
    # note that this rule has completed (since it got at least 1 hit)
1064
 
    $scanner->register_async_rule_finish($rulename);
1065
 
  }
1066
 
}
1067
 
 
1068
 
# ---------------------------------------------------------------------------
1069
 
 
1070
 
sub start_lookup {
1071
 
  my ($self, $scanner, $zone, $type, $id, $key) = @_;
1072
 
 
1073
 
  my $ent = {
1074
 
    key => $key,
1075
 
    zone => $zone,  # serves to fetch other per-zone settings
1076
 
    type => "URI-".$type,
1077
 
    id => $id,
1078
 
    completed_callback => sub {
1079
 
      my $ent = shift;
1080
 
      if (defined $ent->{response_packet}) {  # not aborted or empty
1081
 
        $self->completed_lookup_callback ($scanner, $ent);
1082
 
      }
1083
 
    }
1084
 
  };
1085
 
  $scanner->{async}->start_lookup($ent, $scanner->{master_deadline});
1086
 
  return $ent;
1087
 
}
1088
 
 
1089
 
sub completed_lookup_callback {
1090
 
  my ($self, $scanner, $ent) = @_;
1091
 
  my $type = $ent->{type};
1092
 
  my $key = $ent->{key};
1093
 
  $key =~ /:(\S+?)$/; my $val = $1;
1094
 
 
1095
 
  if ($type eq 'URI-NS') {
1096
 
    $self->complete_ns_lookup ($scanner, $ent, $val);
1097
 
  }
1098
 
  elsif ($type eq 'URI-A') {
1099
 
    $self->complete_a_lookup ($scanner, $ent, $val);
1100
 
  }
1101
 
  elsif ($type eq 'URI-DNSBL') {
1102
 
    $self->complete_dnsbl_lookup ($scanner, $ent, $val);
1103
 
  }
1104
 
}
1105
 
 
1106
 
# ---------------------------------------------------------------------------
1107
 
 
1108
 
sub res_bgsend {
1109
 
  my ($self, $scanner, $host, $type, $key) = @_;
1110
 
 
1111
 
  return $self->{main}->{resolver}->bgsend($host, $type, undef, sub {
1112
 
        my ($pkt, $id, $timestamp) = @_;
1113
 
        $scanner->{async}->set_response_packet($id, $pkt, $key, $timestamp);
1114
 
      });
1115
 
}
1116
 
 
1117
 
sub log_dns_result {
1118
 
  #my $self = shift;
1119
 
  #Mail::SpamAssassin::dbg("uridnsbl: ".join (' ', @_));
 
1181
    $pms->register_async_rule_finish($rulename);
 
1182
  }
1120
1183
}
1121
1184
 
1122
1185
# ---------------------------------------------------------------------------
1125
1188
#
1126
1189
sub has_tflags_domains_only { 1 }
1127
1190
sub has_subtest_for_ranges { 1 }
 
1191
sub has_uridnsbl_for_a { 1 }  # uridnsbl rules recognize tflags 'a' and 'ns'
1128
1192
 
1129
1193
1;