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