File Coverage

blib/lib/Mail/SpamAssassin/Plugin/FreeMail.pm
Criterion Covered Total %
statement 51 293 17.4
branch 4 152 2.6
condition 4 29 13.7
subroutine 10 19 52.6
pod 4 9 44.4
total 73 502 14.5


line stmt bran cond sub pod time code
1             # <@LICENSE>
2             # Licensed to the Apache Software Foundation (ASF) under one or more
3             # contributor license agreements. See the NOTICE file distributed with
4             # this work for additional information regarding copyright ownership.
5             # The ASF licenses this file to you under the Apache License, Version 2.0
6             # (the "License"); you may not use this file except in compliance with
7             # the License. You may obtain a copy of the License at:
8             #
9             # http://www.apache.org/licenses/LICENSE-2.0
10             #
11             # Unless required by applicable law or agreed to in writing, software
12             # distributed under the License is distributed on an "AS IS" BASIS,
13             # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14             # See the License for the specific language governing permissions and
15             # limitations under the License.
16             # </@LICENSE>
17              
18             package Mail::SpamAssassin::Plugin::FreeMail;
19 21     21   158 use strict;
  21         44  
  21         667  
20 21     21   126 use warnings;
  21         45  
  21         652  
21 21     21   143 use re 'taint';
  21         53  
  21         1228  
22              
23             my $VERSION = 2.003;
24              
25             =head1 NAME
26              
27             FreeMail - check message headers/body for freemail-domains
28              
29             =head1 SYNOPSIS
30              
31             If for example From-address is freemail, and Reply-To or address found in mail body is
32             different freemail address, return success. Good sign of Nigerian scams
33             etc. Test idea from Marc Perkel.
34              
35             Also separate functions to check various portions of message for freemails.
36              
37             =head1 CONFIGURATION
38              
39             freemail_domains domain ...
40              
41             List of domains to be used in checks.
42              
43             Regexp is not supported, but following wildcards work:
44              
45             ? for single character (does not match a dot)
46             * for multiple characters (does not match a dot)
47              
48             For example:
49             freemail_domains hotmail.com hotmail.co.?? yahoo.* yahoo.*.*
50              
51             freemail_whitelist email/domain ...
52              
53             Emails or domains listed here are ignored (pretend they aren't
54             freemail). No wildcards!
55              
56             freemail_import_whitelist_auth 1/0
57              
58             Entries in whitelist_auth will also be used to whitelist emails
59             or domains from being freemail. Default is 0.
60              
61             freemail_import_def_whitelist_auth 1/0
62              
63             Entries in def_whitelist_auth will also be used to whitelist emails
64             or domains from being freemail. Default is 0.
65              
66             header FREEMAIL_REPLYTO eval:check_freemail_replyto(['option'])
67              
68             Checks/compares freemail addresses found from headers and body.
69              
70             Possible options:
71              
72             replyto From: or body address is different than Reply-To
73             (this is the default)
74             reply as above, but if no Reply-To header is found,
75             compares From: and body
76              
77             header FREEMAIL_FROM eval:check_freemail_from(['regex'])
78              
79             Checks all possible "from" headers to see if sender is freemail.
80             Uses SA all_from_addrs() function (includes 'Resent-From', 'From',
81             'EnvelopeFrom' etc).
82              
83             Add optional regex to match the found email address(es). For example,
84             to see if user ends in digit: check_freemail_from('\d@')
85              
86             If you use multiple check_freemail_from rules with regexes, remember
87             that they might hit different emails from different heades. To match
88             a certain header only, use check_freemail_header.
89              
90             header FREEMAIL_HDRX eval:check_freemail_header('header' [, 'regex'])
91              
92             Searches defined header for freemail address. Optional regex to match
93             the found address (like in check_freemail_from).
94              
95             header FREEMAIL_BODY eval:check_freemail_body(['regex'])
96              
97             Searches body for freemail address. With optional regex to match.
98              
99             =head1 CHANGELOG
100              
101             1.996 - fix freemail_skip_bulk_envfrom
102             1.997 - set freemail_skip_when_over_max to 1 by default
103             1.998 - don't warn about missing freemail_domains when linting
104             1.999 - default whitelist undisclosed-recipient@yahoo.com etc
105             2.000 - some cleaning up
106             2.001 - fix freemail_whitelist
107             2.002 - _add_desc -> _got_hit, fix description email append bug
108             2.003 - freemail_import_(def_)whitelist_auth
109              
110             =cut
111              
112 21     21   143 use Mail::SpamAssassin::Plugin;
  21         59  
  21         637  
113 21     21   129 use Mail::SpamAssassin::PerMsgStatus;
  21         43  
  21         656  
114 21     21   131 use Mail::SpamAssassin::Util qw(compile_regexp);
  21         59  
  21         80967  
115              
116             our @ISA = qw(Mail::SpamAssassin::Plugin);
117              
118             # default email whitelist
119             our $email_whitelist = qr/
120             ^(?:
121             abuse|support|sales|info|helpdesk|contact|kontakt
122             | (?:post|host|domain)master
123             | undisclosed.* # yahoo.com etc(?)
124             | request-[a-f0-9]{16} # live.com
125             | bounced?- # yahoo.com etc
126             | [a-f0-9]{8}(?:\.[a-f0-9]{8}|-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}) # gmail msgids?
127             | .+=.+=.+ # gmail forward
128             )\@
129             /xi;
130              
131             # skip replyto check when envelope sender is
132             # allow <> for now
133             # no re "strict"; # since perl 5.21.8: Ranges of ASCII printables...
134             our $skip_replyto_envfrom = qr/
135             (?:
136             ^(?:post|host|domain)master
137             | ^double-bounce
138             | ^(?:sentto|owner|return|(?:gr)?bounced?)-.+
139             | -(?:request|bounces?|admin|owner)
140             | \b(?:do[._-t]?)?no[._-t]?repl(?:y|ies)
141             | .+=.+
142             )\@
143             /xi;
144              
145 62     62 1 441 sub dbg { Mail::SpamAssassin::Plugin::dbg ("FreeMail: @_"); }
146              
147             sub new {
148 62     62 1 281 my ($class, $mailsa) = @_;
149              
150 62   33     479 $class = ref($class) || $class;
151 62         381 my $self = $class->SUPER::new($mailsa);
152 62         175 bless ($self, $class);
153              
154 62         344 $self->{freemail_available} = 1;
155 62         401 $self->set_config($mailsa->{conf});
156 62         440 $self->register_eval_rule("check_freemail_replyto");
157 62         235 $self->register_eval_rule("check_freemail_from");
158 62         228 $self->register_eval_rule("check_freemail_header");
159 62         223 $self->register_eval_rule("check_freemail_body");
160              
161 62         592 return $self;
162             }
163              
164             sub _init_email_regex {
165 0     0   0 my ($self) = @_;
166              
167 0         0 dbg("initializing email regex");
168              
169             # Some regexp tips courtesy of http://www.regular-expressions.info/email.html
170             # full email regex v0.02
171 0         0 $self->{email_regex} = qr/
172             (?=.{0,64}\@) # limit userpart to 64 chars (and speed up searching?)
173             (?<![a-z0-9!#\$%&'*+\/=?^_`{|}~-]) # start boundary
174             ( # capture email
175             [a-z0-9!#\$%&'*+\/=?^_`{|}~-]+ # no dot in beginning
176             (?:\.[a-z0-9!#\$%&'*+\/=?^_`{|}~-]+)* # no consecutive dots, no ending dot
177             \@
178             (?:[a-z0-9](?:[a-z0-9-]{0,59}[a-z0-9])?\.){1,4} # max 4x61 char parts (should be enough?)
179             $self->{main}->{registryboundaries}->{valid_tlds_re} # ends with valid tld
180             )
181             /xi;
182             }
183              
184             sub set_config {
185 62     62 0 200 my ($self, $conf) = @_;
186 62         186 my @cmds;
187 62         349 push(@cmds, {
188             setting => 'freemail_max_body_emails',
189             default => 5,
190             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
191             }
192             );
193 62         318 push(@cmds, {
194             setting => 'freemail_max_body_freemails',
195             default => 3,
196             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
197             }
198             );
199 62         269 push(@cmds, {
200             setting => 'freemail_skip_when_over_max',
201             default => 1,
202             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
203             }
204             );
205 62         279 push(@cmds, {
206             setting => 'freemail_skip_bulk_envfrom',
207             default => 1,
208             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
209             }
210             );
211 62         330 push(@cmds, {
212             setting => 'freemail_add_describe_email',
213             default => 1,
214             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
215             }
216             );
217 62         261 push(@cmds, {
218             setting => 'freemail_import_whitelist_auth',
219             default => 0,
220             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
221             }
222             );
223 62         263 push(@cmds, {
224             setting => 'freemail_import_def_whitelist_auth',
225             default => 0,
226             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
227             }
228             );
229 62         320 $conf->{parser}->register_commands(\@cmds);
230             }
231              
232             sub parse_config {
233 0     0 1 0 my ($self, $opts) = @_;
234              
235 0 0       0 if ($opts->{key} eq "freemail_domains") {
236 0         0 foreach my $temp (split(/\s+/, $opts->{value})) {
237 0 0       0 if ($temp =~ /^[a-z0-9.*?-]+$/i) {
238 0         0 my $value = lc($temp);
239 0 0       0 if ($value =~ /[*?]/) { # separate wildcard list
240 0         0 $self->{freemail_temp_wc}{$value} = 1;
241             }
242             else {
243 0         0 $self->{freemail_domains}{$value} = 1;
244             }
245             }
246             else {
247 0         0 warn("invalid freemail_domains: $temp");
248             }
249             }
250 0         0 $self->inhibit_further_callbacks();
251 0         0 return 1;
252             }
253              
254 0 0       0 if ($opts->{key} eq "freemail_whitelist") {
255 0         0 foreach my $temp (split(/\s+/, $opts->{value})) {
256 0         0 my $value = lc($temp);
257 0 0       0 if ($value =~ /\w[.@]\w/) {
258 0         0 $self->{freemail_whitelist}{$value} = 1;
259             }
260             else {
261 0         0 warn("invalid freemail_whitelist: $temp");
262             }
263             }
264 0         0 $self->inhibit_further_callbacks();
265 0         0 return 1;
266             }
267              
268 0         0 return 0;
269             }
270              
271             sub finish_parsing_end {
272 62     62 1 209 my ($self, $opts) = @_;
273              
274 62         152 my $wcount = 0;
275 62 50       295 if (defined $self->{freemail_temp_wc}) {
276 0         0 my @domains;
277 0         0 foreach my $value (keys %{$self->{freemail_temp_wc}}) {
  0         0  
278 0         0 $value =~ s/\./\\./g;
279 0         0 $value =~ s/\?/./g;
280 0         0 $value =~ s/\*/[^.]*/g;
281 0         0 push(@domains, $value);
282             }
283 0         0 my $doms = join('|', @domains);
284 0         0 $self->{freemail_domains_re} = qr/\@(?:${doms})$/;
285 0         0 $wcount = scalar @domains;
286 0         0 undef $self->{freemail_temp_wc};
287 0         0 delete $self->{freemail_temp_wc};
288             }
289              
290 62         134 my $count = scalar keys %{$self->{freemail_domains}};
  62         327  
291 62 50       268 if ($count + $wcount) {
292 0         0 dbg("loaded freemail_domains entries: $count normal, $wcount wildcard");
293             }
294             else {
295 62 50 100     446 if ($self->{main}->{lint_rules} ||1) {
296 62         277 dbg("no freemail_domains entries defined, disabling plugin");
297             }
298             else {
299 0         0 warn("no freemail_domains entries defined, disabling plugin");
300             }
301 62         233 $self->{freemail_available} = 0;
302             }
303              
304             # valid_tlds_re will be available at finish_parsing_end, compile it now,
305             # we only need to do it once and before possible forking
306 62 50 33     277 if ($self->{freemail_available} && !$self->{email_regex}) {
307 0         0 $self->_init_email_regex();
308             }
309              
310 62         188 return 0;
311             }
312              
313             sub _is_freemail {
314 0     0     my ($self, $email, $pms) = @_;
315              
316 0 0         return 0 if $email eq '';
317              
318 0 0         if (defined $self->{freemail_whitelist}{$email}) {
319 0           dbg("whitelisted email: $email");
320 0           return 0;
321             }
322              
323 0           my $domain = $email;
324 0           $domain =~ s/.*\@//;
325              
326 0 0         if (defined $self->{freemail_whitelist}{$domain}) {
327 0           dbg("whitelisted domain: $domain");
328 0           return 0;
329             }
330              
331 0 0         if ($email =~ $email_whitelist) {
332 0           dbg("whitelisted email, default: $email");
333 0           return 0;
334             }
335              
336 0           foreach my $list ('whitelist_auth','def_whitelist_auth') {
337 0 0         if ($pms->{conf}->{"freemail_import_$list"}) {
338 0           foreach my $regexp (values %{$pms->{conf}->{$list}}) {
  0            
339 0 0         if ($email =~ /$regexp/o) {
340 0           dbg("whitelisted email, $list: $email");
341 0           return 0;
342             }
343             }
344             }
345             }
346            
347 0 0 0       if (defined $self->{freemail_domains}{$domain}
      0        
348             or ( defined $self->{freemail_domains_re}
349             and $email =~ $self->{freemail_domains_re} )) {
350 0           return 1;
351             }
352              
353 0           return 0;
354             }
355              
356             sub _parse_body {
357 0     0     my ($self, $pms) = @_;
358              
359             # Parse body
360 0 0         if (not defined $pms->{freemail_cache}{body}) {
361 0           %{$pms->{freemail_cache}{body}} = ();
  0            
362 0           my %seen;
363             my @body_emails;
364             # get all <a href="mailto:", since they don't show up on stripped_body
365 0           my $parsed = $pms->get_uri_detail_list();
366 0           while (my($uri, $info) = each %{$parsed}) {
  0            
367 0 0 0       if (defined $info->{types}->{a} and not defined $info->{types}->{parsed}) {
368 0 0         if ($uri =~ /^(?:(?i)mailto):$self->{email_regex}/o) {
369 0           my $email = lc($1);
370 0 0         push(@body_emails, $email) unless defined $seen{$email};
371 0           $seen{$email} = 1;
372 0 0         last if scalar @body_emails >= 20; # sanity
373             }
374             }
375             }
376             # scan stripped normalized body
377             # have to do this way since get_uri_detail_list doesn't know what mails are inside <>
378 0           my $body = $pms->get_decoded_stripped_body_text_array();
379 0           BODY: foreach (@$body) {
380             # strip urls with possible emails inside
381 0           s{<?https?://\S{0,255}(?:\@|%40)\S{0,255}}{ }gi;
382             # strip emails contained in <>, not mailto:
383             # also strip ones followed by quote-like "wrote:" (but not fax: and tel: etc)
384 0           s{<?(?<!mailto:)$self->{email_regex}(?:>|\s{1,10}(?!(?:fa(?:x|csi)|tel|phone|e?-?mail))[a-z]{2,11}:)}{ }gi;
385 0           while (/$self->{email_regex}/g) {
386 0           my $email = lc($1);
387 0 0         utf8::encode($email) if utf8::is_utf8($email); # chars to UTF-8
388 0 0         push(@body_emails, $email) unless $seen{$email};
389 0           $seen{$email} = 1;
390 0 0         last BODY if @body_emails >= 40; # sanity
391             }
392             }
393 0           my $count_all = 0;
394 0           my $count_fm = 0;
395 0           foreach my $email (@body_emails) { # as UTF-8 octets
396 0 0         if (++$count_all == $pms->{main}->{conf}->{freemail_max_body_emails}) {
397 0 0         if ($pms->{main}->{conf}->{freemail_skip_when_over_max}) {
398 0           $pms->{freemail_skip_body} = 1;
399 0           dbg("too many unique emails found from body");
400 0           return 0;
401             }
402             }
403 0 0         next unless $self->_is_freemail($email, $pms);
404 0 0         if (++$count_fm == $pms->{main}->{conf}->{freemail_max_body_freemails}) {
405 0 0         if ($pms->{main}->{conf}->{freemail_skip_when_over_max}) {
406 0           $pms->{freemail_skip_body} = 1;
407 0           dbg("too many unique freemails found from body");
408 0           return 0;
409             }
410             }
411 0           $pms->{freemail_cache}{body}{$email} = 1;
412             }
413 0           dbg("all body freemails: ".join(', ', keys %{$pms->{freemail_cache}{body}}))
414 0 0         if scalar keys %{$pms->{freemail_cache}{body}};
  0            
415             }
416              
417 0 0         if (defined $pms->{freemail_skip_body}) {
418 0           dbg("[cached] body email limit exceeded, skipping");
419 0           return 0;
420             }
421              
422 0           return 1;
423             }
424              
425             sub _got_hit {
426 0     0     my ($self, $pms, $email, $desc) = @_;
427              
428 0           my $rulename = $pms->get_current_eval_rule_name();
429              
430 0 0         if (defined $pms->{conf}->{descriptions}->{$rulename}) {
431 0           $desc = $pms->{conf}->{descriptions}->{$rulename};
432             }
433              
434 0 0         if ($pms->{main}->{conf}->{freemail_add_describe_email}) {
435 0           $email =~ s/\@/[at]/g;
436 0           $pms->test_log($email);
437             }
438              
439 0           $pms->got_hit($rulename, "", description => $desc, ruletype => 'eval');
440             }
441              
442             sub check_freemail_header {
443 0     0 0   my ($self, $pms, $header, $regex) = @_;
444              
445 0 0         return 0 unless $self->{freemail_available};
446              
447 0           my $rulename = $pms->get_current_eval_rule_name();
448 0 0         dbg("RULE ($rulename) check_freemail_header".(defined $regex ? " regex:$regex" : ""));
449              
450 0 0         unless (defined $header) {
451 0           warn("check_freemail_header needs argument");
452 0           return 0;
453             }
454              
455 0           my $re;
456 0 0         if (defined $regex) {
457 0           my ($rec, $err) = compile_regexp($regex, 0);
458 0 0         if (!$rec) {
459 0           warn "freemail: invalid regexp for $rulename '$regex': $err\n";
460 0           return 0;
461             }
462 0           $re = $rec;
463             }
464              
465 0           my @emails = map (lc, $pms->{main}->find_all_addrs_in_line ($pms->get($header)));
466              
467 0 0         if (!scalar (@emails)) {
468 0           dbg("header $header not found from mail");
469 0           return 0;
470             }
471 0           dbg("addresses from header $header: ".join(';',@emails));
472              
473 0           foreach my $email (@emails) {
474 0 0         if ($self->_is_freemail($email, $pms)) {
475 0 0         if (defined $re) {
476 0 0         next unless $email =~ /$re/o;
477 0           dbg("HIT! $email is freemail and matches regex");
478             }
479             else {
480 0           dbg("HIT! $email is freemail");
481             }
482 0           $self->_got_hit($pms, $email, "Header $header is freemail");
483 0           return 1;
484             }
485             }
486              
487 0           return 0;
488             }
489              
490             sub check_freemail_body {
491 0     0 0   my ($self, $pms, $regex) = @_;
492              
493 0 0         return 0 unless $self->{freemail_available};
494              
495 0           my $rulename = $pms->get_current_eval_rule_name();
496 0 0         dbg("RULE ($rulename) check_freemail_body".(defined $regex ? " regex:$regex" : ""));
497              
498 0 0         return 0 unless $self->_parse_body($pms);
499              
500 0           my $re;
501 0 0         if (defined $regex) {
502 0           my ($rec, $err) = compile_regexp($regex, 0);
503 0 0         if (!$rec) {
504 0           warn "freemail: invalid regexp for $rulename '$regex': $err\n";
505 0           return 0;
506             }
507 0           $re = $rec;
508             }
509              
510 0 0         if (defined $re) {
    0          
511 0           foreach my $email (keys %{$pms->{freemail_cache}{body}}) {
  0            
512 0 0         if ($email =~ /$re/o) {
513 0           dbg("HIT! email from body is freemail and matches regex: $email");
514 0           $self->_got_hit($pms, $email, "Email from body is freemail");
515 0           return 0;
516             }
517             }
518             }
519 0           elsif (scalar keys %{$pms->{freemail_cache}{body}}) {
520 0           my $emails = join(', ', keys %{$pms->{freemail_cache}{body}});
  0            
521 0           dbg("HIT! body has freemails: $emails");
522 0           $self->_got_hit($pms, $emails, "Body contains freemails");
523 0           return 0;
524             }
525              
526 0           return 0;
527             }
528              
529             sub check_freemail_from {
530 0     0 0   my ($self, $pms, $regex) = @_;
531              
532 0 0         return 0 unless $self->{freemail_available};
533              
534 0           my $rulename = $pms->get_current_eval_rule_name();
535 0 0         dbg("RULE ($rulename) check_freemail_from".(defined $regex ? " regex:$regex" : ""));
536              
537 0           my $re;
538 0 0         if (defined $regex) {
539 0           my ($rec, $err) = compile_regexp($regex, 0);
540 0 0         if (!$rec) {
541 0           warn "freemail: invalid regexp for $rulename '$regex': $err\n";
542 0           return 0;
543             }
544 0           $re = $rec;
545             }
546              
547 0           my %from_addrs = map { lc($_) => 1 } ($pms->all_from_addrs());
  0            
548 0           delete $from_addrs{''}; # no empty ones thx
549              
550 0 0         unless (scalar keys %from_addrs) {
551 0           dbg("no from-addresses found to check");
552 0           return 0;
553             }
554              
555 0           dbg("all from-addresses: ".join(', ', keys %from_addrs));
556              
557 0           foreach my $email (keys %from_addrs) {
558 0 0         next unless $self->_is_freemail($email, $pms);
559 0 0         if (defined $re) {
560 0 0         next unless $email =~ /$re/o;
561 0           dbg("HIT! $email is freemail and matches regex");
562             }
563             else {
564 0           dbg("HIT! $email is freemail");
565             }
566 0           $self->_got_hit($pms, $email, "Sender address is freemail");
567 0           return 0;
568             }
569              
570 0           return 0;
571             }
572              
573             sub check_freemail_replyto {
574 0     0 0   my ($self, $pms, $what) = @_;
575              
576 0 0         return 0 unless $self->{freemail_available};
577              
578 0           my $rulename = $pms->get_current_eval_rule_name();
579 0           dbg("RULE ($rulename) check_freemail_replyto");
580              
581 0 0         if (defined $what) {
582 0 0 0       if ($what ne 'replyto' and $what ne 'reply') {
583 0           warn("invalid check_freemail_replyto option: $what");
584 0           return 0;
585             }
586             }
587             else {
588 0           $what = 'replyto';
589             }
590              
591             # Skip mailing-list etc looking requests, mostly FPs from them
592 0 0         if ($pms->{main}->{conf}->{freemail_skip_bulk_envfrom}) {
593 0           my $envfrom = lc($pms->get("EnvelopeFrom"));
594 0 0         if ($envfrom =~ $skip_replyto_envfrom) {
595 0           dbg("envelope sender looks bulk, skipping check: $envfrom");
596 0           return 0;
597             }
598             }
599              
600 0           my $from = lc($pms->get("From:addr"));
601 0           my $replyto = lc($pms->get("Reply-To:addr"));
602 0           my $from_is_fm = $self->_is_freemail($from, $pms);
603 0           my $replyto_is_fm = $self->_is_freemail($replyto, $pms);
604              
605 0 0         dbg("From address: $from") if $from ne '';
606 0 0         dbg("Reply-To address: $replyto") if $replyto ne '';
607              
608 0 0 0       if ($from_is_fm and $replyto_is_fm and ($from ne $replyto)) {
      0        
609 0           dbg("HIT! From and Reply-To are different freemails");
610 0           $self->_got_hit($pms, "$from, $replyto", "From and Reply-To are different freemails");
611 0           return 0;
612             }
613              
614 0 0         if ($what eq 'replyto') {
    0          
615 0 0         if (!$replyto_is_fm) {
616 0           dbg("Reply-To is not freemail, skipping check");
617 0           return 0;
618             }
619             }
620             elsif ($what eq 'reply') {
621 0 0 0       if ($replyto ne '' and !$replyto_is_fm) {
    0          
622 0           dbg("Reply-To defined and is not freemail, skipping check");
623 0           return 0;
624             }
625             elsif (!$from_is_fm) {
626 0           dbg("No Reply-To and From is not freemail, skipping check");
627 0           return 0;
628             }
629             }
630 0 0         my $reply = $replyto_is_fm ? $replyto : $from;
631              
632 0 0         return 0 unless $self->_parse_body($pms);
633            
634             # Compare body to headers
635 0 0         if (scalar keys %{$pms->{freemail_cache}{body}}) {
  0            
636 0 0         my $check = $what eq 'replyto' ? $replyto : $reply;
637 0           dbg("comparing $check to body freemails");
638 0           foreach my $email (keys %{$pms->{freemail_cache}{body}}) {
  0            
639 0 0         if ($email ne $check) {
640 0           dbg("HIT! $check and $email are different freemails");
641 0           $self->_got_hit($pms, "$check, $email", "Different freemails in reply header and body");
642 0           return 0;
643             }
644             }
645             }
646              
647 0           return 0;
648             }
649              
650             1;