| 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; |