~ubuntu-branches/ubuntu/utopic/spamassassin/utopic-proposed

« back to all changes in this revision

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

  • Committer: Bazaar Package Importer
  • Author(s): Noah Meyerhans
  • Date: 2010-01-26 22:53:12 UTC
  • mfrom: (1.1.13 upstream) (5.1.7 sid)
  • Revision ID: james.westby@ubuntu.com-20100126225312-wkftb10idc1kz2aq
Tags: 3.3.0-1
* New upstream version.
* Switch to dpkg-source 3.0 (quilt) format

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
=head1 NAME
19
 
 
20
 
Mail::SpamAssassin::Plugin::DomainKeys - perform DomainKeys verification tests
21
 
 
22
 
=head1 SYNOPSIS
23
 
 
24
 
 loadplugin Mail::SpamAssassin::Plugin::DomainKeys [/path/to/DomainKeys.pm]
25
 
 
26
 
Signature:
27
 
 header DK_SIGNED                eval:check_domainkeys_signed()
28
 
 header DK_VERIFIED              eval:check_domainkeys_verified()
29
 
 
30
 
Policy:
31
 
   Note that DK policy record is only fetched if DK_VERIFIED is
32
 
   false to save a signing domain from unnecessary DNS queries,
33
 
   as recommended (SHOULD) by draft-delany-domainkeys-base.
34
 
   Rules DK_POLICY_* should preferably not be relied upon when
35
 
   DK_VERIFIED is true, although they will return false in current
36
 
   implementation when a policy record is not fetched, except for
37
 
   DK_POLICY_TESTING, which is true if t=y appears in a public key
38
 
   record OR in a policy record (when available).
39
 
 header DK_POLICY_TESTING        eval:check_domainkeys_testing()
40
 
 header DK_POLICY_SIGNSOME       eval:check_domainkeys_signsome()
41
 
 header DK_POLICY_SIGNALL        eval:check_domainkeys_signall()
42
 
 
43
 
Whitelisting based on verified signature:
44
 
 header USER_IN_DK_WHITELIST     eval:check_for_dk_whitelist_from()
45
 
 header USER_IN_DEF_DK_WL        eval:check_for_def_dk_whitelist_from()
46
 
 
47
 
=head1 DESCRIPTION
48
 
 
49
 
This is the DomainKeys plugin and it needs lots more documentation.
50
 
 
51
 
Note that if the C<Mail::SpamAssassin::Plugin::DKIM> plugin is installed with
52
 
C<Mail::DKIM> version 0.20 or later, that plugin will also perform Domain Key
53
 
lookups on DomainKey-Signature headers, in which case this plugin is redundant.
54
 
 
55
 
 
56
 
Here is author's note from module C<Mail::DomainKeys> version 1.0:
57
 
 
58
 
  THIS MODULE IS OFFICIALLY UNSUPPORTED.
59
 
 
60
 
  Please move on to DKIM like a responsible Internet user.  I have.
61
 
 
62
 
  I will leave this module here on CPAN for a while, just in case someone
63
 
  has grown to depend on it.  It is apparent that DK will not be the way
64
 
  of the future. Thus, it is time to put this module to ground before it
65
 
  causes any further harm.
66
 
 
67
 
  Thanks for your support,
68
 
  Anthony
69
 
 
70
 
=cut
71
 
 
72
 
package Mail::SpamAssassin::Plugin::DomainKeys;
73
 
 
74
 
use Mail::SpamAssassin::Plugin;
75
 
use Mail::SpamAssassin::Logger;
76
 
use Mail::SpamAssassin::Timeout;
77
 
 
78
 
use strict;
79
 
use warnings;
80
 
use bytes;
81
 
 
82
 
# Have to do this so that RPM doesn't find these as required perl modules
83
 
BEGIN { require Mail::DomainKeys::Message; require Mail::DomainKeys::Policy; }
84
 
 
85
 
use vars qw(@ISA);
86
 
@ISA = qw(Mail::SpamAssassin::Plugin);
87
 
 
88
 
# constructor: register the eval rule
89
 
sub new {
90
 
  my $class = shift;
91
 
  my $mailsaobject = shift;
92
 
 
93
 
  $class = ref($class) || $class;
94
 
  my $self = $class->SUPER::new($mailsaobject);
95
 
  bless ($self, $class);
96
 
 
97
 
  $self->register_eval_rule ("check_domainkeys_signed");
98
 
  $self->register_eval_rule ("check_domainkeys_verified");
99
 
  $self->register_eval_rule ("check_domainkeys_signsome");
100
 
  $self->register_eval_rule ("check_domainkeys_testing");
101
 
  $self->register_eval_rule ("check_domainkeys_signall");
102
 
  $self->register_eval_rule ("check_for_dk_whitelist_from");
103
 
  $self->register_eval_rule ("check_for_def_dk_whitelist_from");
104
 
 
105
 
  $self->set_config($mailsaobject->{conf});
106
 
 
107
 
  return $self;
108
 
}
109
 
 
110
 
###########################################################################
111
 
 
112
 
sub set_config {
113
 
  my($self, $conf) = @_;
114
 
  my @cmds = ();
115
 
 
116
 
=head1 USER SETTINGS
117
 
 
118
 
=over 4
119
 
 
120
 
=item whitelist_from_dk add@ress.com [signing domain name]
121
 
 
122
 
Use this to supplement the whitelist_from addresses with a check to make sure
123
 
the message has been signed by a DomainKeys signature that can be verified
124
 
against the From: domain's DomainKeys public key.
125
 
 
126
 
In order to support signing domain names that differ from the address domain
127
 
name, only one whitelist entry is allowed per line, exactly like
128
 
C<whitelist_from_rcvd>.  Multiple C<whitelist_from_dk> lines are allowed.  
129
 
File-glob style meta characters are allowed for the From: address, just like
130
 
with C<whitelist_from_rcvd>.  The optional signing domain name parameter must
131
 
match from the right-most side, also like in C<whitelist_from_rcvd>.
132
 
 
133
 
If no signing domain name parameter is specified the domain of the address
134
 
parameter specified will be used instead.
135
 
 
136
 
The From: address is obtained from a signed part of the message (ie. the
137
 
"From:" header), not from envelope data that is possible to forge.
138
 
 
139
 
Since this whitelist requires a DomainKeys check to be made, network tests must
140
 
be enabled.
141
 
 
142
 
Examples:
143
 
 
144
 
  whitelist_from_dk joe@example.com
145
 
  whitelist_from_dk *@corp.example.com
146
 
 
147
 
  whitelist_from_dk bob@it.example.net  example.net
148
 
  whitelist_from_dk *@eng.example.net   example.net
149
 
 
150
 
=item def_whitelist_from_dk add@ress.com [signing domain name]
151
 
 
152
 
Same as C<whitelist_from_dk>, but used for the default whitelist entries
153
 
in the SpamAssassin distribution.  The whitelist score is lower, because
154
 
these are often targets for spammer spoofing.
155
 
 
156
 
=cut
157
 
 
158
 
  push (@cmds, {
159
 
    setting => 'whitelist_from_dk',
160
 
    code => sub {
161
 
      my ($self, $key, $value, $line) = @_;
162
 
      unless (defined $value && $value !~ /^$/) {
163
 
        return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
164
 
      }
165
 
      unless ($value =~ /^(\S+)(?:\s+(\S+))?$/) {
166
 
        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
167
 
      }
168
 
      my $address = $1;
169
 
      my $signer = (defined $2 ? $2 : $1);
170
 
 
171
 
      unless (defined $2) {
172
 
        $signer =~ s/^.*@(.*)$/$1/;
173
 
      }
174
 
      $self->{parser}->add_to_addrlist_rcvd ('whitelist_from_dk',
175
 
                                                $address, $signer);
176
 
    }
177
 
  });
178
 
 
179
 
  push (@cmds, {
180
 
    setting => 'def_whitelist_from_dk',
181
 
    code => sub {
182
 
      my ($self, $key, $value, $line) = @_;
183
 
      unless (defined $value && $value !~ /^$/) {
184
 
        return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
185
 
      }
186
 
      unless ($value =~ /^(\S+)(?:\s+(\S+))?$/) {
187
 
        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
188
 
      }
189
 
      my $address = $1;
190
 
      my $signer = (defined $2 ? $2 : $1);
191
 
 
192
 
      unless (defined $2) {
193
 
        $signer =~ s/^.*@(.*)$/$1/;
194
 
      }
195
 
      $self->{parser}->add_to_addrlist_rcvd ('def_whitelist_from_dk',
196
 
                                                $address, $signer);
197
 
    }
198
 
  });
199
 
 
200
 
=back
201
 
 
202
 
=head1 ADMINISTRATOR SETTINGS
203
 
 
204
 
=over 4
205
 
 
206
 
=item domainkeys_timeout n             (default: 5)
207
 
 
208
 
How many seconds to wait for a DomainKeys query to complete, before
209
 
scanning continues without the DomainKeys result.
210
 
 
211
 
=cut
212
 
 
213
 
  push (@cmds, {
214
 
    setting => 'domainkeys_timeout',
215
 
    is_admin => 1,
216
 
    default => 5,
217
 
    type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
218
 
  });
219
 
 
220
 
  $conf->{parser}->register_commands(\@cmds);
221
 
}
222
 
 
223
 
 
224
 
sub check_domainkeys_signed {
225
 
  my ($self, $scan) = @_;
226
 
 
227
 
  $self->_check_domainkeys($scan) unless $scan->{domainkeys_checked};
228
 
  
229
 
  return $scan->{domainkeys_signed};
230
 
}
231
 
 
232
 
sub check_domainkeys_verified {
233
 
  my ($self, $scan) = @_;
234
 
 
235
 
  $self->_check_domainkeys($scan) unless $scan->{domainkeys_checked};
236
 
  
237
 
  return $scan->{domainkeys_verified};
238
 
}
239
 
 
240
 
sub check_domainkeys_signsome {
241
 
  my ($self, $scan) = @_;
242
 
 
243
 
  $self->_check_domainkeys($scan) unless $scan->{domainkeys_checked};
244
 
  return $scan->{domainkeys_signsome};
245
 
}
246
 
 
247
 
sub check_domainkeys_testing {
248
 
  my ($self, $scan) = @_;
249
 
 
250
 
  $self->_check_domainkeys($scan) unless $scan->{domainkeys_checked};
251
 
  
252
 
  return $scan->{domainkeys_testing};
253
 
}
254
 
 
255
 
sub check_domainkeys_signall {
256
 
  my ($self, $scan) = @_;
257
 
 
258
 
  $self->_check_domainkeys($scan) unless $scan->{domainkeys_checked};
259
 
  
260
 
  return $scan->{domainkeys_signall};
261
 
}
262
 
 
263
 
sub check_for_dk_whitelist_from {
264
 
  my ($self, $scan) = @_;
265
 
  $self->_check_dk_whitelist($scan, 0) unless $scan->{dk_whitelist_from_checked};
266
 
  $scan->{dk_whitelist_from};
267
 
}
268
 
 
269
 
sub check_for_def_dk_whitelist_from {
270
 
  my ($self, $scan) = @_;
271
 
  $self->_check_dk_whitelist($scan, 1) unless $scan->{def_dk_whitelist_from_checked};
272
 
  $scan->{def_dk_whitelist_from};
273
 
}
274
 
 
275
 
# ---------------------------------------------------------------------------
276
 
 
277
 
sub _check_domainkeys {
278
 
  my ($self, $scan) = @_;
279
 
 
280
 
  $scan->{domainkeys_checked} = 0;
281
 
  $scan->{domainkeys_signed} = 0;
282
 
  $scan->{domainkeys_verified} = 0;
283
 
  $scan->{domainkeys_signsome} = 0;
284
 
  $scan->{domainkeys_testing} = 0;
285
 
  $scan->{domainkeys_signall} = 0;
286
 
 
287
 
  my $header = $scan->{msg}->get_pristine_header();
288
 
  my $body = $scan->{msg}->get_body();
289
 
  my $dksighdr = $scan->{msg}->get_header("DomainKey-Signature");
290
 
  dbg("dk: signature: $dksighdr")  if defined $dksighdr;
291
 
 
292
 
  $self->sanitize_header_for_dk(\$header)
293
 
    if defined $dksighdr && $dksighdr !~ /(?:^|;)[ \t]*h=/;  # case sensitive
294
 
 
295
 
  my $message = Mail::DomainKeys::Message->load(HeadString => $header,
296
 
                                                 BodyReference => $body);
297
 
 
298
 
  if (!$message) {
299
 
    dbg("dk: cannot load message using Mail::DomainKeys::Message");
300
 
    return;
301
 
  }
302
 
 
303
 
  $scan->{domainkeys_checked} = 1;
304
 
 
305
 
  # does a sender domain header exist?
306
 
  my $domain = $message->senderdomain();
307
 
  if (!$domain) {
308
 
    dbg("dk: no sender domain");
309
 
    return;
310
 
  }
311
 
 
312
 
  # get the sender address for whitelist checks
313
 
  if (defined $message->sender()) {
314
 
    $scan->{dk_address} = @{$message->sender()}[1];
315
 
    dbg("dk: sender: $scan->{dk_address}");
316
 
  } elsif (defined $message->from()) {
317
 
    $scan->{dk_address} ||= @{$message->from()}[1];
318
 
    dbg("dk: from: $scan->{dk_address}");
319
 
  } else {
320
 
    dbg("dk: could not determine sender: or from: identity");
321
 
  }
322
 
 
323
 
  # get the signing domain name for whitelist checks
324
 
  $scan->{dk_signing_domain} = $self->_dkmsg_signing_domain($scan, $message);
325
 
  dbg("dk: signing domain name: ".
326
 
    ($scan->{dk_signing_domain} ? $scan->{dk_signing_domain} : "not found"));
327
 
 
328
 
  my $timeout = $scan->{conf}->{domainkeys_timeout};
329
 
 
330
 
  my $timer = Mail::SpamAssassin::Timeout->new({ secs => $timeout });
331
 
  my $err = $timer->run_and_catch(sub {
332
 
 
333
 
    $self->_dk_lookup_trapped($scan, $message, $domain);
334
 
 
335
 
  });
336
 
 
337
 
  if ($timer->timed_out()) {
338
 
    dbg("dk: lookup timed out after $timeout seconds");
339
 
    return 0;
340
 
  }
341
 
 
342
 
  if ($err) {
343
 
    chomp $err;
344
 
    warn("dk: lookup failed: $err\n");
345
 
    return 0;
346
 
  }
347
 
 
348
 
  my $comment = $self->_dkmsg_hdr($message);
349
 
  $comment ||= '';
350
 
  $comment =~ s/\s+/ /gs;       # no newlines please
351
 
 
352
 
  $scan->{dk_comment} = "DomainKeys status: $comment";
353
 
}
354
 
 
355
 
# perform DK lookups.  This method is trapped within a timeout alarm() scope
356
 
sub _dk_lookup_trapped {
357
 
  my ($self, $scan, $message, $domain) = @_;
358
 
 
359
 
  # verified
360
 
  if ($message->signed()) {
361
 
    $scan->{domainkeys_signed} = 1;
362
 
    if ($message->verify()) {
363
 
      $scan->{domainkeys_verified} = 1;
364
 
    }
365
 
  }
366
 
  # testing flag in signature
367
 
  if ($message->testing()) {
368
 
    $scan->{domainkeys_testing} = 1;
369
 
  }
370
 
  my $policy;
371
 
  if (!$scan->{domainkeys_verified}) {
372
 
    # Recipient systems SHOULD not retrieve a policy TXT record
373
 
    # for email that successfully verifies.
374
 
    $policy = Mail::DomainKeys::Policy->fetch(Protocol => 'dns',
375
 
                                              Domain => $domain);
376
 
    my($fetched_policy) = $policy ? $policy->as_string : 'NONE';
377
 
    $fetched_policy = ''  if !defined $fetched_policy;
378
 
    dbg ("dk: fetched policy for domain $domain: $fetched_policy");
379
 
  }
380
 
  return unless $policy;
381
 
 
382
 
  # not signed and domain doesn't sign all
383
 
  if ($policy->signsome()) {
384
 
    $scan->{domainkeys_signsome} = 1;
385
 
  }
386
 
 
387
 
  # testing flag in policy
388
 
  if ($policy->testing()) {
389
 
    $scan->{domainkeys_testing} = 1;
390
 
  }
391
 
 
392
 
  # does policy require all mail to be signed
393
 
  if ($policy->signall()) {
394
 
    $scan->{domainkeys_signall} = 1;
395
 
  }
396
 
 
397
 
  my $comment = $self->_dkmsg_hdr($message);
398
 
  dbg("dk: comment is '$comment'");
399
 
}
400
 
 
401
 
# get the DK status "header" from the Mail::DomainKeys::Message object
402
 
sub _dkmsg_hdr {
403
 
  my ($self, $message) = @_;
404
 
  # try to use the signature() API if it exists (post-0.80)
405
 
  if ($message->can("signature")) {
406
 
    my($sts,$msg);
407
 
    if (!$message->signed) {
408
 
      $sts = "no signature";
409
 
    } else {
410
 
      $sts = $message->signature->status;
411
 
      $msg = $message->signature->errorstr;
412
 
    }
413
 
    dbg("dk: $sts" . (defined $msg ? " ($msg)" : ''));
414
 
    return $sts;
415
 
  } else {
416
 
    return $message->header->value;
417
 
  }
418
 
}
419
 
 
420
 
# get the DK signing domain name from the Mail::DomainKeys::Message object
421
 
sub _dkmsg_signing_domain {
422
 
  my ($self, $scan, $message) = @_;
423
 
  # try to use the signature() API if it exists (post-0.80)
424
 
  if ($message->can("signature")) {
425
 
    if (!$message->signed) {
426
 
      return undef;
427
 
    }
428
 
    return $message->signature->domain;
429
 
  } else {
430
 
    # otherwise parse it ourself
431
 
    if ($scan->{msg}->get_header("DomainKey-Signature") =~
432
 
        /(?: ^|; ) [ \t]* d= [ \t]* ([^;]*?) [ \t]* (?: ;|$ )/x) {
433
 
      return $1;
434
 
    }
435
 
    return undef;
436
 
  }
437
 
}
438
 
 
439
 
sub sanitize_header_for_dk {
440
 
  my ($self, $ref) = @_;
441
 
 
442
 
  dbg("dk: sanitizing header, no \"h\" tag in signature");
443
 
  # remove folding, in a HTML-escape data-preserving style, so we can
444
 
  # strip headers easily
445
 
  $$ref =~ s/!/!ex;/gs;
446
 
  $$ref =~ s/\n([ \t])/!nl;$1/gs;
447
 
  my @hdrs = split(/^/m, $$ref);
448
 
 
449
 
  while (scalar @hdrs > 0) {
450
 
    my $last = pop @hdrs;
451
 
    next if ($last =~ /^\r?$/);
452
 
 
453
 
    # List all the known appended headers that may break a DK signature. Things
454
 
    # to note:
455
 
    # 
456
 
    # 1. only *appended* headers should be listed; prepended additions are fine.
457
 
    # 2. some virus-scanner headers may be better left out, since there are ISPs
458
 
    # who scan for viruses before the message leaves their SMTP relay; this is
459
 
    # not quite decided.
460
 
    #
461
 
    # TODO: there's probably loads more, and this should be user-configurable
462
 
 
463
 
    if ($last =~ /^ (?:
464
 
            # SpamAssassin additions, remove these so that mass-check works
465
 
            X-Spam-\S+
466
 
 
467
 
            # other spam filters
468
 
            |X-MailScanner(?:-SpamCheck)?
469
 
            |X-Pyzor |X-DCC-\S{2,25}-Metrics
470
 
            |X-Bogosity
471
 
 
472
 
            # post-delivery MUA additions
473
 
            |X-Evolution
474
 
            |X-MH-Thread-Markup
475
 
 
476
 
            # IMAP or POP additions
477
 
            |X-Keywords
478
 
            |(?:X-)?Status |X-Flags |Replied |Forwarded
479
 
            |Lines |Content-Length
480
 
            |X-UIDL? |X-IMAPbase
481
 
 
482
 
            # MTA delivery control headers
483
 
            |X-MDaemon-Deliver-To
484
 
 
485
 
            # other MUAs: VM and Gnus
486
 
            |X-VM-(?:Bookmark|(?:POP|IMAP)-Retrieved|Labels|Last-Modified
487
 
            |Summary-Format|VHeader|v\d-Data|Message-Order)
488
 
            |X-Gnus-Mail-Source
489
 
            |Xref
490
 
          ):/ix)
491
 
    {
492
 
      $last =~ /^([^:]+):/; dbg("dk: ignoring header '$1'");
493
 
      next;
494
 
    }
495
 
 
496
 
    push (@hdrs, $last); last;
497
 
  }
498
 
 
499
 
  $$ref = join("", @hdrs);
500
 
 
501
 
  # and return the remaining headers to pristine condition
502
 
  # $$ref =~ s/^\n//gs; $$ref =~ s/\n$//gs;
503
 
  $$ref =~ s/!nl;/\n/gs;
504
 
  $$ref =~ s/!ex;/!/gs;
505
 
}
506
 
 
507
 
sub _check_dk_whitelist {
508
 
  my ($self, $scan, $default) = @_;
509
 
 
510
 
  return unless $scan->is_dns_available();
511
 
 
512
 
  # trigger a DK check so we can get address/signer info
513
 
  # if verification failed only continue if we want the debug info
514
 
  unless ($self->check_domainkeys_verified($scan)) {
515
 
    unless (would_log("dbg", "dk")) {
516
 
      return;
517
 
    }
518
 
  }
519
 
 
520
 
  unless ($scan->{dk_address}) {
521
 
    dbg("dk: ". ($default ? "def_" : "") ."whitelist_from_dk: could not find sender or from address");
522
 
    return;
523
 
  }
524
 
  unless ($scan->{dk_signing_domain}) {
525
 
    dbg("dk: ". ($default ? "def_" : "") ."whitelist_from_dk: could not find signing domain name");
526
 
    return;
527
 
  }
528
 
 
529
 
  if ($default) {
530
 
    $scan->{def_dk_whitelist_from_checked} = 1;
531
 
    $scan->{def_dk_whitelist_from} =
532
 
                    $self->_wlcheck_domain($scan,'def_whitelist_from_dk');
533
 
 
534
 
    if (!$scan->{def_dk_whitelist_from}) {
535
 
      $scan->{def_dk_whitelist_from} =
536
 
                    $self->_wlcheck_no_domain($scan,'def_whitelist_auth');
537
 
    }
538
 
  } else {
539
 
    $scan->{dk_whitelist_from_checked} = 1;
540
 
    $scan->{dk_whitelist_from} =
541
 
                    $self->_wlcheck_domain($scan,'whitelist_from_dk');
542
 
    
543
 
    if (!$scan->{dk_whitelist_from}) {
544
 
      $scan->{dk_whitelist_from} =
545
 
                    $self->_wlcheck_no_domain($scan,'whitelist_auth');
546
 
    }
547
 
  }
548
 
 
549
 
  # if the message doesn't pass DK validation, it can't pass a DK whitelist
550
 
  if ($default) {
551
 
    if ($scan->{def_dk_whitelist_from}) {
552
 
      if ($self->check_domainkeys_verified($scan)) {
553
 
        dbg("dk: address: $scan->{dk_address} signing domain name: ".
554
 
          "$scan->{dk_signing_domain} is in user's DEF_WHITELIST_FROM_DK and ".
555
 
          "passed DK verification");
556
 
      } else {
557
 
        dbg("dk: address: $scan->{dk_address} signing domain name: ".
558
 
          "$scan->{dk_signing_domain} is in user's DEF_WHITELIST_FROM_DK but ".
559
 
          "failed DK verification");
560
 
        $scan->{def_dk_whitelist_from} = 0;
561
 
      }
562
 
    } else {
563
 
      dbg("dk: address: $scan->{dk_address} signing domain name: ".
564
 
          "$scan->{dk_signing_domain} is not in user's DEF_WHITELIST_FROM_DK");
565
 
    }
566
 
  } else {
567
 
    if ($scan->{dk_whitelist_from}) {
568
 
      if ($self->check_domainkeys_verified($scan)) {
569
 
        dbg("dk: address: $scan->{dk_address} signing domain name: ".
570
 
          "$scan->{dk_signing_domain} is in user's WHITELIST_FROM_DK and ".
571
 
          "passed DK verification");
572
 
      } else {
573
 
        dbg("dk: address: $scan->{dk_address} signing domain name: ".
574
 
          "$scan->{dk_signing_domain} is in user's WHITELIST_FROM_DK but ".
575
 
          "failed DK verification");
576
 
        $scan->{dk_whitelist_from} = 0;
577
 
      }
578
 
    } else {
579
 
      dbg("dk: address: $scan->{dk_address} signing domain name: ".
580
 
          "$scan->{dk_signing_domain} is not in user's WHITELIST_FROM_DK");
581
 
    }
582
 
  }
583
 
}
584
 
 
585
 
sub _wlcheck_domain {
586
 
  my ($self, $scan, $wl) = @_;
587
 
 
588
 
  foreach my $white_addr (keys %{$scan->{conf}->{$wl}}) {
589
 
    my $re = qr/$scan->{conf}->{$wl}->{$white_addr}{re}/i;
590
 
    foreach my $domain (@{$scan->{conf}->{$wl}->{$white_addr}{domain}}) {
591
 
      $self->_wlcheck_one_dom($scan, $wl, $white_addr, $domain, $re) and return 1;
592
 
    }
593
 
  }
594
 
  return 0;
595
 
}
596
 
 
597
 
sub _wlcheck_one_dom {
598
 
  my ($self, $scan, $wl, $white_addr, $domain, $re) = @_;
599
 
 
600
 
  if ($scan->{dk_address} =~ $re) {
601
 
    if ($scan->{dk_signing_domain} =~ /(?:^|\.)\Q${domain}\E$/i) {
602
 
      dbg("dk: address: $scan->{dk_address} matches $wl $re $domain");
603
 
      return 1;
604
 
    }
605
 
  }
606
 
  return 0;
607
 
}
608
 
 
609
 
 
610
 
# use a traditional whitelist_from-style addrlist, and infer the
611
 
# domain from each address on the fly.  Note: don't pre-parse and
612
 
# store the domains; that's inefficient memory-wise and only saves 1 m//
613
 
sub _wlcheck_no_domain {
614
 
  my ($self, $scan, $wl) = @_;
615
 
 
616
 
  foreach my $white_addr (keys %{$scan->{conf}->{$wl}}) {
617
 
    my $domain = ($white_addr =~ /\@(.*?)$/) ? $1 : $white_addr;
618
 
    my $re = $scan->{conf}->{$wl}->{$white_addr};
619
 
    $self->_wlcheck_one_dom($scan, $wl, $white_addr, $domain, $re) and return 1;
620
 
  }
621
 
  return 0;
622
 
}
623
 
 
624
 
1;