Skip to content
This repository
Browse code

resolvable_fromhost: refactored, added: POD, tests, reject, reject_type

  • Loading branch information...
commit 2f49cafcd6a12dba381e472d76bfc18d974abf06 1 parent 9e239fd
Matt Simerson authored May 23, 2012
406  plugins/require_resolvable_fromhost
... ...
@@ -1,150 +1,318 @@
1 1
 #!perl -w
  2
+
  3
+=head1 NAME
  4
+
  5
+resolvable_fromhost
  6
+
  7
+=head1 SYNOPSIS
  8
+
  9
+Determine if the from host resolves to a valid MX or host.
  10
+
  11
+=head1 DESCRIPTION
  12
+
  13
+The fromhost is the part of the email address after the @ symbol, provided by
  14
+the sending server during the SMTP conversation. This is usually, but not
  15
+always, the same as the hostname in the From: header.
  16
+
  17
+B<resolvable_fromhost> tests to see if the fromhost resolves. It saves the 
  18
+results in the transaction note I<resolvable_fromhost> where other plugins can
  19
+use that information. Typical results are:
  20
+
  21
+ a  - fromhost resolved as an A record
  22
+ mx - fromhost has valid MX record(s)
  23
+ ip - fromhost was an IP
  24
+ whitelist - skipped checks due to whitelisting
  25
+ null - null sender
  26
+ config - fromhost not resolvable, but I<reject 0> was set.
  27
+
  28
+Any other result is an error message with details of the failure.
  29
+
  30
+If B<resolvable_fromhost> is enabled, the from hostname is also stored in
  31
+I<resolvable_fromhost_host>, making it accessible when $sender is not.
  32
+
  33
+=head1 CONFIGURATION
  34
+
  35
+=head2 reject <bool>
  36
+
  37
+If I<reject 1> is set, the old require_resolvable_fromhost plugin behavior of
  38
+temporary rejection is the default.
  39
+
  40
+ resolvable_fromhost reject [ 0 | 1 ]
  41
+
  42
+Default: 1
  43
+
  44
+=head2 reject_type
  45
+
  46
+ reject_type [ perm | temp ]
  47
+
  48
+Set I<reject_type perm> to reject mail instead of deferring it.
  49
+
  50
+Default: temp (temporary, aka soft, aka 4xx).
  51
+
  52
+=head1 EXAMPLE LOG ENTRIES
  53
+
  54
+ 80072 (mail) resolvable_fromhost: googlegroups.com has valid MX at gmr-smtp-in.l.google.com
  55
+ 80108 (mail) resolvable_fromhost: zerobarriers.net has valid MX at zerobarriers.net
  56
+ 80148 (mail) resolvable_fromhost: uhin.com has valid MX at filter.itsafemail.com
  57
+ 86627 (mail) resolvable_fromhost: no MX records for palmalar.com
  58
+ 86627 (mail) resolvable_fromhost: fail: palmalar.com (SERVFAIL)
  59
+
  60
+=head1 AUTHORS
  61
+
  62
+2012 - Matt Simerson - refactored, added: POD, tests, reject, reject_type
  63
+
  64
+2002 - Ask Bjørn Hansen - intial plugin
  65
+
  66
+=cut
  67
+
  68
+
  69
+use strict;
  70
+use warnings;
  71
+
  72
+use Qpsmtpd::Constants;
2 73
 use Qpsmtpd::DSN;
3  
-use Net::DNS qw(mx);
  74
+use Qpsmtpd::TcpServer;
  75
+
4 76
 use Socket;
  77
+use Net::DNS qw(mx);
5 78
 use Net::IP qw(:PROC);
6  
-use Qpsmtpd::TcpServer;
7 79
 
8  
-my %invalid = ();
  80
+my %invalid  = ();
9 81
 my $has_ipv6 = Qpsmtpd::TcpServer::has_ipv6();
10 82
 
  83
+sub register {
  84
+    my ($self, $qp, %args) = @_;
  85
+
  86
+    foreach (keys %args) {
  87
+        $self->{_args}->{$_} = $args{$_};
  88
+    }
  89
+    if ( ! defined $self->{_args}{reject} ) {
  90
+        $self->{_args}{reject} = 1;
  91
+    };
  92
+    $self->{_args}{reject_type} ||= 'soft';
  93
+}
  94
+
11 95
 sub hook_mail {
12  
-  my ($self, $transaction, $sender, %param) = @_;
  96
+    my ($self, $transaction, $sender, %param) = @_;
13 97
 
14  
-  return DECLINED
15  
-        if ($self->qp->connection->notes('whitelisthost'));
  98
+    $self->populate_invalid_networks();
16 99
 
17  
-  foreach my $i ($self->qp->config("invalid_resolvable_fromhost")) {
18  
-    $i =~ s/^\s*//;
19  
-    $i =~ s/\s*$//;
20  
-    if ($i =~ m#^((\d{1,3}\.){3}\d{1,3})/(\d\d?)#) {
21  
-      $invalid{$1} = $3;
22  
-    }
23  
-  }
24  
-
25  
-  if ($sender ne "<>" 
26  
-      and $self->qp->config("require_resolvable_fromhost")
27  
-      and !$self->check_dns($sender->host)) {
28  
-    if ($sender->host) {
29  
-	  $transaction->notes('temp_resolver_failed', $sender->host);
30  
-    } 
31  
-    else {
32  
-      # default of addr_bad_from_system is DENY, we use DENYSOFT here to
33  
-      # get the same behaviour as without Qpsmtpd::DSN...
34  
-      return Qpsmtpd::DSN->addr_bad_from_system(DENYSOFT, 
35  
-                               "FQDN required in the envelope sender");
36  
-    }
37  
-  }
38  
-  return DECLINED;
  100
+    # check first, so results are noted for other plugins
  101
+    my $resolved = $self->check_dns($sender->host, $transaction);
  102
+
  103
+    return DECLINED if $resolved;  # success, no need to continue
  104
+    return DECLINED if $self->is_immune( $sender, $transaction );
  105
+    return DECLINED if ! $self->{_args}{reject};
39 106
 
  107
+    return DECLINED if $sender->host;  # reject later
  108
+
  109
+    $self->log(LOGWARN, "FQDN required in envelope sender");
  110
+    return Qpsmtpd::DSN->addr_bad_from_system( $self->get_reject_type(),
  111
+                            "FQDN required in the envelope sender");
40 112
 }
41 113
 
42 114
 sub hook_rcpt {
43  
-	my ($self, $transaction, $recipient, %args) = @_;
  115
+    my ($self, $transaction, $recipient, %args) = @_;
44 116
 
45  
-	if (my $host = $transaction->notes('temp_resolver_failed')) {
46  
-		# default of temp_resolver_failed is DENYSOFT
47  
-		return Qpsmtpd::DSN->temp_resolver_failed("Could not resolve " . $host);
48  
-	}
  117
+    my $result = $transaction->notes('resolvable_fromhost');
  118
+    return DECLINED if ! $self->{_args}{reject};         # no reject policy
  119
+    return DECLINED if $result =~ /^(a|ip|mx)$/;         # success
  120
+    return DECLINED if $result =~ /^(whitelist|null|config)$/; # immunity
49 121
 
50  
-  return DECLINED;
  122
+    $self->log(LOGINFO, $result ); # log error
  123
+    return Qpsmtpd::DSN->temp_resolver_failed( $self->get_reject_type(), $result );
51 124
 }
52 125
 
53 126
 sub check_dns {
54  
-  my ($self, $host) = @_;
55  
-  my @host_answers;
56  
-
57  
-  # for stuff where we can't even parse a hostname out of the address
58  
-  return 0 unless $host;
59  
-
60  
-  return 1 if $host =~ m/^\[(\d{1,3}\.){3}\d{1,3}\]$/;
61  
-
62  
-  my $res = new Net::DNS::Resolver(dnsrch => 0);
63  
-  $res->tcp_timeout(30);
64  
-  $res->udp_timeout(30);
65  
-  my @mx = mx($res, $host);
66  
-  foreach my $mx (@mx) {
67  
-    # if any MX is valid, then we consider the domain
68  
-    # resolvable
69  
-    return 1 if mx_valid($self, $mx->exchange, $host);
70  
-  }
71  
-  # if there are MX records, and we got here,
72  
-  # then none of them are valid
73  
-  return 0 if (@mx > 0);
74  
-
75  
-  my $query = $res->search($host);
76  
-  if ($query) {
77  
-    foreach my $rrA ($query->answer) {
78  
-      push(@host_answers, $rrA);
79  
-    }
80  
-  }
81  
-  if ($has_ipv6) {
82  
-    my $query = $res->search($host, 'AAAA');
83  
-    if ($query) {
84  
-      foreach my $rrAAAA ($query->answer) {
85  
-        push(@host_answers, $rrAAAA);
86  
-      }
87  
-    }
88  
-  } 
89  
-  if (@host_answers) {
  127
+    my ($self, $host, $transaction) = @_;
  128
+
  129
+    # we can't even parse a hostname out of the address
  130
+    if ( ! $host ) {
  131
+        $transaction->notes('resolvable_fromhost', 'unparsable host');
  132
+        return;
  133
+    };
  134
+
  135
+    $transaction->notes('resolvable_fromhost_host', $host);
  136
+
  137
+    if ( $host =~ m/^\[(\d{1,3}\.){3}\d{1,3}\]$/ ) {
  138
+        $self->log(LOGINFO, "skip: $host is an IP");
  139
+        $transaction->notes('resolvable_fromhost', 'ip');
  140
+        return 1;
  141
+    };
  142
+
  143
+    my $res = new Net::DNS::Resolver(dnsrch => 0);
  144
+    $res->tcp_timeout(30);
  145
+    $res->udp_timeout(30);
  146
+
  147
+    my $has_mx = $self->get_and_validate_mx( $res, $host, $transaction );
  148
+    return 1 if $has_mx == 1;  # success!
  149
+    return if $has_mx == -1;   # has invalid MX records
  150
+
  151
+    my @host_answers = $self->get_host_records( $res, $host, $transaction );
90 152
     foreach my $rr (@host_answers) {
91  
-      return is_valid($rr->address) if $rr->type eq "A" or $rr->type eq "AAAA";
92  
-      return mx_valid($self, $rr->exchange, $host) if $rr->type eq "MX";
  153
+        if ( $rr->type eq 'A' || $rr->type eq 'AAAA' ) {
  154
+            $self->log(LOGINFO, "pass: found valid A for $host");
  155
+            $transaction->notes('resolvable_fromhost', 'a');
  156
+            return $self->ip_is_valid($rr->address);
  157
+        };
  158
+        if ( $rr->type eq 'MX' ) {
  159
+            $self->log(LOGINFO, "pass: found valid MX for $host");
  160
+            $transaction->notes('resolvable_fromhost', 'mx');
  161
+            return $self->mx_address_resolves($rr->exchange, $host);
  162
+        };
93 163
     }
94  
-  }
95  
-  else {
96  
-    $self->log(LOGWARN, "$$ query for $host failed: ", $res->errorstring)
97  
-      unless $res->errorstring eq "NXDOMAIN";
98  
-  }
99  
-  return 0;
  164
+    return;
100 165
 }
101 166
 
102  
-sub is_valid {
103  
-  my $ip = shift;
104  
-  my ($net,$mask);
105  
-  ### while (($net,$mask) = each %invalid) {
106  
-  ###         ... does NOT reset to beginning, will start on
107  
-  ###         2nd invocation after where it denied the first time..., so
108  
-  ###         2nd time the same "MAIL FROM" would be accepted!
109  
-  foreach $net (keys %invalid) {
110  
-    $mask = $invalid{$net};
111  
-    $mask = pack "B32", "1"x($mask)."0"x(32-$mask);
112  
-    return 0 
113  
-      if join(".", unpack("C4", inet_aton($ip) & $mask)) eq $net;
114  
-  }
115  
-  return 1; 
  167
+sub ip_is_valid {
  168
+    my ($self, $ip) = @_;
  169
+    my ($net, $mask);
  170
+    ### while (($net,$mask) = each %invalid) {
  171
+    ###      ... does NOT reset to beginning, will start on
  172
+    ###      2nd invocation after where it denied the first time..., so
  173
+    ###      2nd time the same "MAIL FROM" would be accepted!
  174
+    foreach $net (keys %invalid) {
  175
+        $mask = $invalid{$net};
  176
+        $mask = pack "B32", "1" x ($mask) . "0" x (32 - $mask);
  177
+        return if $net eq join('.', unpack("C4", inet_aton($ip) & $mask));
  178
+    }
  179
+    return 1;
116 180
 }
117 181
 
118  
-sub mx_valid {
119  
-  my ($self, $name, $host) = @_;
120  
-  my $res   = new Net::DNS::Resolver(dnsrch => 0);
121  
-  # IP in MX
122  
-  return is_valid($name) if ip_is_ipv4($name) or ip_is_ipv6($name);
123  
-
124  
-  my @mx_answers;
125  
-  my $query = $res->search($name, 'A');
126  
-  if ($query) {
127  
-    foreach my $rrA ($query->answer) {
128  
-      push(@mx_answers, $rrA);
  182
+sub get_and_validate_mx {
  183
+    my ($self, $res, $host, $transaction ) = @_;
  184
+
  185
+    my @mx = mx($res, $host);
  186
+    if ( ! scalar @mx ) {    # no mx records
  187
+        $self->log(LOGINFO, "no MX records for $host");
  188
+        return 0;
  189
+    };
  190
+
  191
+    foreach my $mx (@mx) {
  192
+        # if any MX is valid, then we consider the domain resolvable
  193
+        if ( $self->mx_address_resolves($mx->exchange, $host) ) {
  194
+            $self->log(LOGINFO, "pass: $host has valid MX at " . $mx->exchange);
  195
+            $transaction->notes('resolvable_fromhost', 'mx');
  196
+            return 1;
  197
+        };
  198
+    }
  199
+
  200
+    # if there are MX records, and we got here, none are valid
  201
+    $self->log(LOGINFO, "fail: invalid MX for $host");
  202
+    $transaction->notes('resolvable_fromhost', "invalid MX for $host");
  203
+    return -1;
  204
+};
  205
+
  206
+sub get_host_records {
  207
+    my ($self, $res, $host, $transaction ) = @_;
  208
+
  209
+    my @answers;
  210
+    my $query = $res->search($host);
  211
+
  212
+    if ($query) {
  213
+        foreach my $rrA ($query->answer) {
  214
+            push(@answers, $rrA);
  215
+        }
  216
+    }
  217
+
  218
+    if ($has_ipv6) {
  219
+        $query = $res->search($host, 'AAAA');
  220
+        if ($query) {
  221
+            foreach my $rrAAAA ($query->answer) {
  222
+                push(@answers, $rrAAAA);
  223
+            }
  224
+        }
129 225
     }
130  
-  }
131  
-  if ($has_ipv6) {
132  
-    my $query = $res->search($name, 'AAAA');
  226
+
  227
+    if ( ! scalar @answers) {
  228
+        if ( $res->errorstring ne 'NXDOMAIN' ) {
  229
+            $self->log(LOGWARN, "$$ query for $host failed: ", $res->errorstring);
  230
+        };
  231
+        return;
  232
+    };
  233
+
  234
+    return @answers;
  235
+};
  236
+
  237
+sub mx_address_resolves {
  238
+    my ($self, $name, $fromhost) = @_;
  239
+
  240
+    # IP in MX
  241
+    return $self->ip_is_valid($name) if ip_is_ipv4($name) || ip_is_ipv6($name);
  242
+
  243
+    my $res = new Net::DNS::Resolver(dnsrch => 0);
  244
+    my @mx_answers;
  245
+    my $query = $res->search($name, 'A');
133 246
     if ($query) {
134  
-      foreach my $rrAAAA ($query->answer) {
135  
-        push(@mx_answers, $rrAAAA);
136  
-      }
  247
+        foreach my $rrA ($query->answer) {
  248
+            push(@mx_answers, $rrA);
  249
+        }
  250
+    }
  251
+    if ($has_ipv6) {
  252
+        my $query = $res->search($name, 'AAAA');
  253
+        if ($query) {
  254
+            foreach my $rrAAAA ($query->answer) {
  255
+                push(@mx_answers, $rrAAAA);
  256
+            }
  257
+        }
  258
+    }
  259
+    if (! @mx_answers) {
  260
+        $self->log(LOGWARN, "query for $fromhost failed: ", $res->errorstring)
  261
+            unless $res->errorstring eq "NXDOMAIN";
  262
+        return;
137 263
     }
138  
-  }
139  
-  if (@mx_answers) {
  264
+
140 265
     foreach my $rr (@mx_answers) {
141  
-      next unless $rr->type eq "A" or $rr->type eq "AAAA";
142  
-      return is_valid($rr->address);
  266
+        next if ( $rr->type ne 'A' && $rr->type ne 'AAAA' );
  267
+        return $self->ip_is_valid($rr->address);
143 268
     }
144  
-  }
145  
-  else {
146  
-    $self->log(LOGWARN, "$$ query for $host failed: ", $res->errorstring)
147  
-      unless $res->errorstring eq "NXDOMAIN";
148  
-  }
149  
-  return 0;
  269
+
  270
+    return;
150 271
 }
  272
+
  273
+sub populate_invalid_networks {
  274
+    my $self = shift;
  275
+
  276
+    foreach my $i ($self->qp->config("invalid_resolvable_fromhost")) {
  277
+        $i =~ s/^\s*//;  # trim leading spaces
  278
+        $i =~ s/\s*$//;  # trim trailing spaces
  279
+        if ($i =~ m#^((\d{1,3}\.){3}\d{1,3})/(\d\d?)#) {
  280
+            $invalid{$1} = $3;
  281
+        }
  282
+    }
  283
+};
  284
+
  285
+sub is_immune {
  286
+    my ($self, $sender, $transaction) = @_;
  287
+
  288
+    if ( $self->qp->connection->notes('whitelisthost') ) {
  289
+        $transaction->notes('resolvable_fromhost', 'whitelist');
  290
+        $self->log(LOGINFO, "pass: whitelisted");
  291
+        return 1;
  292
+    };
  293
+
  294
+    if ( $sender eq '<>' ) {
  295
+        $transaction->notes('resolvable_fromhost', 'null');
  296
+        $self->log(LOGINFO, "pass: null sender");
  297
+        return 1;
  298
+    };
  299
+
  300
+    if ( ! $self->{_args}{reject} ) {
  301
+        $transaction->notes('resolvable_fromhost', 'config');
  302
+        $self->log(LOGINFO, "skip: reject not enabled in config.");
  303
+        return;
  304
+    };
  305
+
  306
+    return;
  307
+};
  308
+
  309
+sub get_reject_type {
  310
+    my $self = shift;
  311
+    my $default = shift || DENYSOFT;
  312
+    my $deny = $self->{_args}{reject_type} or return $default;
  313
+
  314
+    return $deny =~ /^(temp|soft)$/i ? DENYSOFT
  315
+         : $deny =~ /^(perm|hard)$/i ? DENY
  316
+         : $deny eq 'disconnect' ? DENY_DISCONNECT
  317
+         : $default;
  318
+};
165  t/plugin_tests/require_resolvable_fromhost
... ...
@@ -0,0 +1,165 @@
  1
+#!perl -w
  2
+
  3
+use strict;
  4
+use warnings;
  5
+
  6
+use Data::Dumper;
  7
+use Net::DNS;
  8
+use Qpsmtpd::Address;
  9
+use Qpsmtpd::Constants;
  10
+
  11
+my $res = new Net::DNS::Resolver(dnsrch => 0);
  12
+my $test_email = 'user@example.com';
  13
+
  14
+sub register_tests {
  15
+    my $self = shift;
  16
+
  17
+    my %args = ( );
  18
+    $self->register( $self->qp, reject => 0 );
  19
+
  20
+    $self->register_test('test_is_immune', 3);
  21
+    $self->register_test('test_populate_invalid_networks', 2);
  22
+    $self->register_test('test_mx_address_resolves', 2);
  23
+    $self->register_test('test_get_host_records', 2);
  24
+    $self->register_test('test_get_and_validate_mx', 2);
  25
+    $self->register_test('test_check_dns', 2);
  26
+    $self->register_test('test_hook_rcpt', 10);
  27
+    $self->register_test('test_hook_mail', 4);
  28
+}
  29
+
  30
+sub test_hook_mail {
  31
+    my $self = shift;
  32
+
  33
+    my $transaction = $self->qp->transaction;
  34
+    my $address = Qpsmtpd::Address->new('remote@example.com');
  35
+    $transaction->sender($address);
  36
+
  37
+    my $sender = $transaction->sender;
  38
+    $sender->host('perl.com');
  39
+
  40
+    ok( $self->hook_mail( $transaction, $sender ) );
  41
+    ok( $self->hook_mail( $transaction, $sender ) );
  42
+
  43
+    $sender->host('');
  44
+    $self->{_args}{reject} = 1;
  45
+    $self->{_args}{reject_type} = 'soft';
  46
+    my ($r) = $self->hook_mail( $transaction, $sender );
  47
+    ok( $r == DENYSOFT, "($r)");
  48
+
  49
+    $self->{_args}{reject_type} = 'hard';
  50
+    ($r) = $self->hook_mail( $transaction, $sender );
  51
+    ok( $r == DENY, "($r)");
  52
+};
  53
+
  54
+sub test_hook_rcpt {
  55
+    my $self = shift;
  56
+
  57
+    my $transaction = $self->qp->transaction;
  58
+    my $recipient = 'foo@example.com';
  59
+
  60
+    $transaction->notes('resolvable_fromhost', 'a');
  61
+    ok( DECLINED == $self->hook_rcpt( $transaction, $recipient ) );
  62
+
  63
+    $transaction->notes('resolvable_fromhost', 'mx');
  64
+    ok( DECLINED == $self->hook_rcpt( $transaction, $recipient ) );
  65
+
  66
+    $transaction->notes('resolvable_fromhost', 'ip');
  67
+    ok( DECLINED == $self->hook_rcpt( $transaction, $recipient ) );
  68
+
  69
+    $transaction->notes('resolvable_fromhost', 'whitelist');
  70
+    ok( DECLINED == $self->hook_rcpt( $transaction, $recipient ) );
  71
+
  72
+    $transaction->notes('resolvable_fromhost', 'null');
  73
+    ok( DECLINED == $self->hook_rcpt( $transaction, $recipient ) );
  74
+
  75
+    $transaction->notes('resolvable_fromhost', 'config');
  76
+    ok( DECLINED == $self->hook_rcpt( $transaction, $recipient ) );
  77
+
  78
+    $transaction->notes('resolvable_fromhost', 'oops!');
  79
+    ok( DECLINED == $self->hook_rcpt( $transaction, $recipient ) );
  80
+
  81
+    $transaction->notes('resolvable_fromhost', 'oops!');
  82
+    ok( DECLINED == $self->hook_rcpt( $transaction, $recipient ) );
  83
+
  84
+    $transaction->notes('resolvable_fromhost', 'oops!');
  85
+    $self->{_args}{reject} = 1;
  86
+    $self->{_args}{reject_type} = 'soft';
  87
+    my ($r) = $self->hook_rcpt( $transaction, $recipient );
  88
+    ok( DENYSOFT == $r, "($r)");
  89
+
  90
+    $transaction->notes('resolvable_fromhost', 'failed again');
  91
+    $self->{_args}{reject_type} = 'hard';
  92
+    ($r) = $self->hook_rcpt( $transaction, $recipient );
  93
+    ok( DENY == $r, "($r)");
  94
+};
  95
+
  96
+sub test_check_dns {
  97
+    my $self = shift;
  98
+
  99
+    my $transaction = $self->qp->transaction;
  100
+    ok( ! $self->check_dns( '', $transaction ) );
  101
+    ok( $self->check_dns( 'perl.com', $transaction ) );
  102
+}
  103
+
  104
+sub test_get_and_validate_mx {
  105
+    my $self = shift;
  106
+    my $transaction = $self->qp->transaction;
  107
+
  108
+    ok( scalar $self->get_and_validate_mx( $res, 'perl.com', $transaction ) );
  109
+
  110
+    ok( ! scalar $self->get_host_records( $res, 'fake-domain-name-for-test.com', $transaction ) );
  111
+};
  112
+
  113
+sub test_get_host_records {
  114
+    my $self = shift;
  115
+    my $transaction = $self->qp->transaction;
  116
+
  117
+    ok( scalar $self->get_host_records( $res, 'perl.com', $transaction ) );
  118
+    ok( ! scalar $self->get_host_records( $res, 'fake-domain-name-for-test.com', $transaction ) );
  119
+};
  120
+
  121
+sub test_mx_address_resolves {
  122
+    my $self = shift;
  123
+
  124
+    my $fromhost = 'perl.com';
  125
+
  126
+    ok( $self->mx_address_resolves('mail.perl.com', $fromhost) );
  127
+    ok( ! $self->mx_address_resolves('no-such-mx.perl.com', $fromhost) );
  128
+};
  129
+
  130
+sub test_populate_invalid_networks {
  131
+    my $self = shift;
  132
+
  133
+    my $ip = '10.9.8.7';
  134
+    ok( $self->ip_is_valid($ip) );
  135
+
  136
+    $self->qp->config('invalid_resolvable_fromhost', $ip);
  137
+    $self->populate_invalid_networks();
  138
+    ok( ! $self->ip_is_valid($ip) );
  139
+
  140
+    # clean up afterwards
  141
+    $self->qp->config('invalid_resolvable_fromhost', undef );
  142
+    $self->{invalid} = ();
  143
+};
  144
+
  145
+sub test_is_immune {
  146
+    my $self = shift;
  147
+
  148
+    my $transaction = $self->qp->transaction;
  149
+
  150
+    # null sender should be immune
  151
+    $transaction->sender('<>');
  152
+    ok( $self->is_immune( $transaction->sender, $transaction ) );
  153
+
  154
+    # whitelisted host should be immune
  155
+    my $connection = $self->qp->connection->notes('whitelisthost', 1);
  156
+    ok( $self->is_immune( $transaction->sender, $transaction ) );
  157
+    $self->qp->connection->notes('whitelisthost', undef);
  158
+
  159
+    # reject is not defined, so email should not be immune
  160
+    my $address = Qpsmtpd::Address->new( "<$test_email>" );
  161
+    $transaction->sender($address);
  162
+    ok( ! $self->is_immune( $transaction->sender, $transaction ) );
  163
+};
  164
+
  165
+

0 notes on commit 2f49caf

Please sign in to comment.
Something went wrong with that request. Please try again.