File Coverage

blib/lib/Mail/SpamAssassin/Plugin/HeaderEval.pm
Criterion Covered Total %
statement 156 566 27.5
branch 25 294 8.5
condition 9 162 5.5
subroutine 22 52 42.3
pod 2 32 6.2
total 214 1106 19.3


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              
19             use strict;
20 22     22   162 use warnings;
  22         48  
  22         11054  
21 22     22   127 # use bytes;
  22         43  
  22         6498  
22             use re 'taint';
23 22     22   132 use Errno qw(EBADF);
  22         45  
  22         742  
24 22     22   154  
  22         55  
  22         1162  
25             use Mail::SpamAssassin::Plugin;
26 22     22   131 use Mail::SpamAssassin::Locales;
  22         43  
  22         508  
27 22     22   121 use Mail::SpamAssassin::Util qw(get_my_locales parse_rfc822_date);
  22         38  
  22         600  
28 22     22   166 use Mail::SpamAssassin::Logger;
  22         53  
  22         1372  
29 22     22   169 use Mail::SpamAssassin::Constants qw(:sa :ip);
  22         55  
  22         1239  
30 22     22   169  
  22         51  
  22         81263  
31             our @ISA = qw(Mail::SpamAssassin::Plugin);
32              
33             # constructor: register the eval rule
34             my $class = shift;
35             my $mailsaobject = shift;
36 63     63 1 226  
37 63         143 # some boilerplate...
38             $class = ref($class) || $class;
39             my $self = $class->SUPER::new($mailsaobject);
40 63   33     5838 bless ($self, $class);
41 63         351  
42 63         156 # the important bit!
43             $self->register_eval_rule("check_for_fake_aol_relay_in_rcvd");
44             $self->register_eval_rule("check_for_faraway_charset_in_headers");
45 63         6111 $self->register_eval_rule("check_for_unique_subject_id");
46 63         217 $self->register_eval_rule("check_illegal_chars");
47 63         182 $self->register_eval_rule("check_for_forged_hotmail_received_headers");
48 63         202 $self->register_eval_rule("check_for_no_hotmail_received_headers");
49 63         216 $self->register_eval_rule("check_for_msn_groups_headers");
50 63         192 $self->register_eval_rule("check_for_forged_eudoramail_received_headers");
51 63         214 $self->register_eval_rule("check_for_forged_yahoo_received_headers");
52 63         187 $self->register_eval_rule("check_for_forged_juno_received_headers");
53 63         182 $self->register_eval_rule("check_for_forged_gmail_received_headers");
54 63         242 $self->register_eval_rule("check_for_matching_env_and_hdr_from");
55 63         167 $self->register_eval_rule("sorted_recipients");
56 63         215 $self->register_eval_rule("similar_recipients");
57 63         210 $self->register_eval_rule("check_for_missing_to_header");
58 63         207 $self->register_eval_rule("check_for_forged_gw05_received_headers");
59 63         5311 $self->register_eval_rule("check_for_shifted_date");
60 63         168 $self->register_eval_rule("subject_is_all_caps");
61 63         169 $self->register_eval_rule("check_for_to_in_subject");
62 63         197 $self->register_eval_rule("check_outlook_message_id");
63 63         207 $self->register_eval_rule("check_messageid_not_usable");
64 63         208 $self->register_eval_rule("check_header_count_range");
65 63         180 $self->register_eval_rule("check_unresolved_template");
66 63         5140 $self->register_eval_rule("check_ratware_name_id");
67 63         202 $self->register_eval_rule("check_ratware_envelope_from");
68 63         158 $self->register_eval_rule("gated_through_received_hdr_remover");
69 63         184 $self->register_eval_rule("received_within_months");
70 63         173 $self->register_eval_rule("check_equal_from_domains");
71 63         180  
72 63         193 return $self;
73             }
74 63         566  
75             # load triplets.txt into memory
76             my ($self) = @_;
77              
78             $self->word_is_in_dictionary("aba");
79 2     2 1 7 }
80              
81 2         8 my ($self, $pms) = @_;
82             local ($_);
83              
84             $_ = $pms->get('Received');
85 0     0 0 0 s/\s/ /gs;
86 0         0  
87             # this is the hostname format used by AOL for their relays. Spammers love
88 0         0 # forging it. Don't make it more specific to match aol.com only, though --
89 0         0 # there's another set of spammers who generate fake hostnames to go with
90             # it!
91             if (/ rly-[a-z][a-z]\d\d\./i) {
92             return 0 if /\/AOL-\d+\.\d+\.\d+\)/; # via AOL mail relay
93             return 0 if /ESMTP id (?:RELAY|MAILRELAY|MAILIN)/; # AOLish
94             return 1;
95 0 0       0 }
96 0 0       0  
97 0 0       0 # spam: Received: from unknown (HELO mta05bw.bigpond.com) (80.71.176.130) by
98 0         0 # rly-xw01.mx.aol.com with QMQP; Sat, 15 Jun 2002 23:37:16 -0000
99              
100             # non: Received: from rly-xj02.mx.aol.com (rly-xj02.mail.aol.com [172.20.116.39]) by
101             # omr-r05.mx.aol.com (v83.35) with ESMTP id RELAYIN7-0501132011; Wed, 01
102             # May 2002 13:20:11 -0400
103              
104             # non: Received: from logs-tr.proxy.aol.com (logs-tr.proxy.aol.com [152.163.201.132])
105             # by rly-ip01.mx.aol.com (8.8.8/8.8.8/AOL-5.0.0)
106             # with ESMTP id NAA08955 for <sapient-alumni@yahoogroups.com>;
107             # Thu, 4 Apr 2002 13:11:20 -0500 (EST)
108              
109             return 0;
110             }
111              
112             my ($self, $pms) = @_;
113 0         0 my $hdr;
114              
115             my @locales = get_my_locales($self->{main}->{conf}->{ok_locales});
116              
117 0     0 0 0 return 0 if grep { $_ eq "all" } @locales;
118 0         0  
119             for my $h (qw(From Subject)) {
120 0         0 my @hdrs = $pms->get("$h:raw"); # ??? get() returns a scalar ???
121             if ($#hdrs >= 0) {
122 0 0       0 $hdr = join(" ", @hdrs);
  0         0  
123             } else {
124 0         0 $hdr = '';
125 0         0 }
126 0 0       0 while ($hdr =~ /=\?(.+?)\?.\?.*?\?=/g) {
127 0         0 Mail::SpamAssassin::Locales::is_charset_ok_for_locales($1, @locales)
128             or return 1;
129 0         0 }
130             }
131 0         0 0;
132 0 0       0 }
133              
134             my ($self, $pms) = @_;
135             local ($_);
136 0         0 $_ = lc $pms->get('Subject');
137             study; # study is a no-op since perl 5.16.0, eliminating related bugs
138              
139             my $id = 0;
140 0     0 0 0 if (/[-_\.\s]{7,}([-a-z0-9]{4,})$/
141 0         0 || /\s{10,}(?:\S\s)?(\S+)$/
142 0         0 || /\s{3,}[-:\#\(\[]+([-a-z0-9]{4,})[\]\)]+$/
143 0         0 || /\s{3,}[:\#\(\[]*([a-f0-9]{4,})[\]\)]*$/
144             || /\s{3,}[-:\#]([a-z0-9]{5,})$/
145 0         0 || /[\s._]{3,}([^0\s._]\d{3,})$/
146 0 0 0     0 || /[\s._]{3,}\[(\S+)\]$/
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
147              
148             # (7217vPhZ0-478TLdy5829qicU9-0@26) and similar
149             || /\(([-\w]{7,}\@\d+)\)$/
150              
151             # Seven or more digits at the end of a subject is almost certainly a id
152             || /\b(\d{7,})\s*$/
153              
154             # stuff at end of line after "!" or "?" is usually an id
155             || /[!\?]\s*(\d{4,}|\w+(-\w+)+)\s*$/
156              
157             # 9095IPZK7-095wsvp8715rJgY8-286-28 and similar
158             # excluding 'Re:', etc and the first word
159             || /(?:\w{2,3}:\s)?\w+\s+(\w{7,}-\w{7,}(-\w+)*)\s*$/
160              
161             # #30D7 and similar
162             || /\s#\s*([a-f0-9]{4,})\s*$/
163             )
164             {
165             $id = $1;
166             # exempt online purchases
167             if ($id =~ /\d{5,}/
168             && /(?:item|invoice|order|number|confirmation).{1,6}\Q$id\E\s*$/)
169             {
170             $id = 0;
171 0         0 }
172              
173 0 0 0     0 # for the "foo-bar-baz" case, otherwise it won't
174             # be found in the dict:
175             $id =~ s/-//;
176 0         0 }
177              
178             return ($id && !$self->word_is_in_dictionary($id));
179             }
180              
181 0         0 # word_is_in_dictionary()
182             #
183             # See if the word looks like an English word, by checking if each triplet
184 0   0     0 # of letters it contains is one that can be found in the English language.
185             # Does not include triplets only found in proper names, or in the Latin
186             # and Greek terms that might be found in a larger dictionary
187              
188             my %triplets;
189             my $triplets_loaded = 0;
190              
191             my ($self, $word) = @_;
192             local ($_);
193             local $/ = "\n"; # Ensure $/ is set appropriately
194              
195             # $word =~ tr/A-Z/a-z/; # already done by this stage
196             $word =~ s/^\s+//;
197             $word =~ s/\s+$//;
198 2     2 0 6  
199 2         4 # If it contains a digit, dash, etc, it's not a valid word.
200 2         7 # Don't reject words like "can't" and "I'll"
201             return 0 if ($word =~ /[^a-z\']/);
202              
203 2         8 # handle a few common "blah blah blah (comment)" styles
204 2         6 return 1 if ($word eq "ot"); # off-topic
205             return 1 if ($word =~ /(?:linux|nix|bsd)/); # not in most dicts
206             return 1 if ($word =~ /(?:whew|phew|attn|tha?nx)/); # not in most dicts
207              
208 2 50       7 my $word_len = length($word);
209              
210             # Unique IDs probably aren't going to be only one or two letters long
211 2 50       7 return 1 if ($word_len < 3);
212 2 50       17  
213 2 50       8 if (!$triplets_loaded) {
214             # take a copy to avoid modifying the real one
215 2         5 my @default_triplets_path = @Mail::SpamAssassin::default_rules_path;
216             s{$}{/triplets.txt} for @default_triplets_path;
217             my $filename = $self->{main}->first_existing_path (@default_triplets_path);
218 2 50       6  
219             if (!defined $filename) {
220 2 50       5 dbg("eval: failed to locate the triplets.txt file");
221             return 1;
222 2         9 }
223 2         20  
224 2         14 local *TRIPLETS;
225             if (!open (TRIPLETS, "<$filename")) {
226 2 50       7 dbg("eval: failed to open '$filename', cannot check dictionary: $!");
227 2         7 return 1;
228 2         12 }
229             for($!=0; <TRIPLETS>; $!=0) {
230             chomp;
231 0         0 $triplets{$_} = 1;
232 0 0       0 }
233 0         0 defined $_ || $!==0 or
234 0         0 $!==EBADF ? dbg("eval: error reading from $filename: $!")
235             : die "error reading from $filename: $!";
236 0         0 close(TRIPLETS) or die "error closing $filename: $!";
237 0         0  
238 0         0 $triplets_loaded = 1;
239             } # if (!$triplets_loaded)
240 0 0 0     0  
    0          
241              
242             my $i;
243 0 0       0  
244             for ($i = 0; $i < ($word_len - 2); $i++) {
245 0         0 my $triplet = substr($word, $i, 3);
246             if (!$triplets{$triplet}) {
247             dbg("eval: unique ID: letter triplet '$triplet' from word '$word' not valid");
248             return 0;
249 0         0 }
250             } # for ($i = 0; $i < ($word_len - 2); $i++)
251 0         0  
252 0         0 # All letter triplets in word were found to be valid
253 0 0       0 return 1;
254 0         0 }
255 0         0  
256             # look for 8-bit and other illegal characters that should be MIME
257             # encoded, these might want to exempt languages that do not use
258             # Latin-based alphabets, but only if the user wants it that way
259             my ($self, $pms, $header, $ratio, $count) = @_;
260 0         0  
261             $header .= ":raw" unless $header =~ /:raw$/;
262             my $str = $pms->get($header);
263             return 0 if !defined $str || $str eq '';
264              
265             # count illegal substrings (RFC 2045)
266             # (non-ASCII + C0 controls except TAB, NL, CR)
267 0     0 0 0 my $illegal = $str =~ tr/\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff//;
268              
269 0 0       0 # minor exemptions for Subject
270 0         0 if ($illegal > 0 && lc $header eq "subject:raw") {
271 0 0 0     0 # only exempt a single cent sign, pound sign, or registered sign
272             my $exempt = $str =~ tr/\xa2\xa3\xae//;
273             $illegal-- if $exempt == 1;
274             }
275 0         0  
276             return 0 if $str eq '';
277             return (($illegal / length($str)) >= $ratio && $illegal >= $count);
278 0 0 0     0 }
279              
280 0         0 # ezmlm has a very bad habit of removing Received: headers! bad ezmlm.
281 0 0       0 #
282             my ($self, $pms) = @_;
283              
284 0 0       0 my $txt = $pms->get("Mailing-List",undef);
285 0   0     0 if (defined $txt && $txt =~ /^contact \S+\@\S+\; run by ezmlm$/) {
286             my $dlto = $pms->get("Delivered-To");
287             my $rcvd = $pms->get("Received");
288              
289             # ensure we have other indicative headers too
290             if ($dlto =~ /^mailing list \S+\@\S+/ &&
291 0     0 0 0 $rcvd =~ /qmail \d+ invoked (?:from network|by .{3,20})\); \d+ ... \d+/)
292             {
293 0         0 return 1;
294 0 0 0     0 }
295 0         0 }
296 0         0  
297             my $rcvd = $pms->get("Received",undef);
298             if (!defined $rcvd) {
299 0 0 0     0 # we have no Received headers! These tests cannot run in that case
300             return 1;
301             }
302 0         0  
303             # MSN groups removes Received lines. thanks MSN
304             if ($rcvd =~ /from groups\.msn\.com \(\S+\.msn\.com /) {
305             return 1;
306 0         0 }
307 0 0       0  
308             return 0;
309 0         0 }
310              
311             # FORGED_HOTMAIL_RCVD
312             my ($self, $pms) = @_;
313 0 0       0  
314 0         0 if (defined $pms->{hotmail_addr_but_no_hotmail_received}) { return; }
315              
316             $pms->{hotmail_addr_with_forged_hotmail_received} = 0;
317 0         0 $pms->{hotmail_addr_but_no_hotmail_received} = 0;
318              
319             my $rcvd = $pms->get('Received');
320             $rcvd =~ s/\s+/ /gs; # just spaces, simplify the regexp
321              
322 0     0   0 return if ($rcvd =~
323             /from mail pickup service by hotmail\.com with Microsoft SMTPSVC;/);
324 0 0       0  
  0         0  
325             # Microsoft passes Hotmail mail directly to MSN Group servers.
326 0         0 return if $self->check_for_msn_groups_headers($pms);
327 0         0  
328             my $ip = $pms->get('X-Originating-Ip',undef);
329 0         0 my $IP_ADDRESS = IP_ADDRESS;
330 0         0 my $orig = $pms->get('X-OriginatorOrg',undef);
331             my $ORIGINATOR = 'hotmail.com';
332 0 0       0  
333             if (defined $ip && $ip =~ /$IP_ADDRESS/) { $ip = 1; } else { $ip = 0; }
334             if (defined $orig && $orig =~ /$ORIGINATOR/) { $orig = 1; } else { $orig = 0; }
335              
336 0 0       0 # Hotmail formats its received headers like this:
337             # Received: from hotmail.com (f135.law8.hotmail.com [216.33.241.135])
338 0         0 # or like
339 0         0 # Received: from EUR01-VE1-obe.outbound.protection.outlook.com (mail-oln040092066056.outbound.protection.outlook.com [40.92.66.56])
340 0         0 # spammers do not ;)
341 0         0  
342             if ($self->gated_through_received_hdr_remover($pms)) { return; }
343 0 0 0     0  
  0         0  
  0         0  
344 0 0 0     0 if ($rcvd =~ /from (?:\S*\.)?hotmail.com \(\S+\.hotmail(?:\.msn)?\.com[ \)]/ && $ip)
  0         0  
  0         0  
345             { return; }
346             if ($rcvd =~ /from \S*\.outbound\.protection\.outlook\.com \(\S+\.outbound\.protection\.outlook\.com[ \)]/ && $orig)
347             { return; }
348             if ($rcvd =~ /from \S*\.hotmail.com \(\[$IP_ADDRESS\][ \):]/ && $ip)
349             { return; }
350             if ($rcvd =~ /from \S+ by \S+\.hotmail(?:\.msn)?\.com with HTTP\;/ && $ip)
351             { return; }
352 0 0       0 if ($rcvd =~ /from \[66\.218.\S+\] by \S+\.yahoo\.com/ && $ip)
  0         0  
353             { return; }
354 0 0 0     0  
355 0         0 if ($rcvd =~ /(?:from |HELO |helo=)\S*hotmail\.com\b/) {
356 0 0 0     0 # HELO'd as hotmail.com, despite not being hotmail
357 0         0 $pms->{hotmail_addr_with_forged_hotmail_received} = 1;
358 0 0 0     0 } else {
359 0         0 # check to see if From claimed to be @hotmail.com
360 0 0 0     0 my $from = $pms->get('From:addr');
361 0         0 if ($from !~ /\bhotmail\.com$/i) { return; }
362 0 0 0     0 $pms->{hotmail_addr_but_no_hotmail_received} = 1;
363 0         0 }
364             }
365 0 0       0  
366             # FORGED_HOTMAIL_RCVD
367 0         0 my ($self, $pms) = @_;
368             $self->_check_for_forged_hotmail_received_headers($pms);
369             return $pms->{hotmail_addr_with_forged_hotmail_received};
370 0         0 }
371 0 0       0  
  0         0  
372 0         0 # SEMIFORGED_HOTMAIL_RCVD
373             my ($self, $pms) = @_;
374             $self->_check_for_forged_hotmail_received_headers($pms);
375             return $pms->{hotmail_addr_but_no_hotmail_received};
376             }
377              
378 0     0 0 0 # MSN_GROUPS
379 0         0 my ($self, $pms) = @_;
380 0         0  
381             my $to = $pms->get('To');
382             return 0 unless $to =~ /<(\S+)\@groups\.msn\.com>/i;
383             my $listname = $1;
384              
385 0     0 0 0 # from Theo Van Dinter, see bug 591
386 0         0 # Updated by DOS, based on messages from Bob Menschel, bug 4301
387 0         0  
388             return 0 unless $pms->get('Received') =~
389             /from mail pickup service by ((?:p\d\d\.)groups\.msn\.com)\b/;
390             my $server = $1;
391              
392 0     0 0 0 if ($listname =~ /^notifications$/) {
393             return 0 unless $pms->get('Message-Id') =~ /^<\S+\@$server>/;
394 0         0 } else {
395 0 0       0 return 0 unless $pms->get('Message-Id') =~ /^<$listname-\S+\@groups\.msn\.com>/;
396 0         0 return 0 unless $pms->get('EnvelopeFrom:addr') =~ /$listname-bounce\@groups\.msn\.com/;
397             }
398             return 1;
399              
400             # MSN Groups
401 0 0       0 # Return-path: <ListName-bounce@groups.msn.com>
402             # Received: from groups.msn.com (tk2dcpuba02.msn.com [65.54.195.210]) by
403 0         0 # dogma.slashnull.org (8.11.6/8.11.6) with ESMTP id g72K35v10457 for
404             # <zzzzzzzzzzzz@jmason.org>; Fri, 2 Aug 2002 21:03:05 +0100
405 0 0       0 # Received: from mail pickup service by groups.msn.com with Microsoft
406 0 0       0 # SMTPSVC; Fri, 2 Aug 2002 13:01:30 -0700
407             # Message-id: <ListName-1392@groups.msn.com>
408 0 0       0 # X-loop: notifications@groups.msn.com
409 0 0       0 # Reply-to: "List Full Name" <ListName@groups.msn.com>
410             # To: "List Full Name" <ListName@groups.msn.com>
411 0         0  
412             # Return-path: <ListName-bounce@groups.msn.com>
413             # Received: from p04.groups.msn.com ([65.54.195.216]) etc...
414             # Received: from mail pickup service by p04.groups.msn.com with Microsoft SMTPSVC;
415             # Thu, 5 May 2005 20:30:37 -0700
416             # X-Originating-Ip: 207.68.170.30
417             # From: =?iso-8859-1?B?IqSj4/D9pEbzeN9s9vLw6qQiIA==?=<zzzzzzzz@hotmail.com>
418             # To: "Managers of List Name" <notifications@groups.msn.com>
419             # Subject: =?iso-8859-1?Q?APPROVAL_NEEDED:_=A4=A3=E3=F0=FD=A4F=F3x=DFl?=
420             # =?iso-8859-1?Q?=F6=F2=F0=EA=A4_applied_to_join_List_Name=2C?=
421             # =?iso-8859-1?Q?_an_MSN_Group?=
422             # Date: Thu, 5 May 2005 20:30:37 -0700
423             # MIME-Version: 1.0
424             # Content-Type: multipart/alternative;
425             # boundary="----=_NextPart_000_333944_01C551B1.4BBA02B0"
426             # X-MimeOLE: Produced By Microsoft MimeOLE V5.50.4927.1200
427             # Message-ID: <TK2DCPUBA042cv0aGlt00020aa3@p04.groups.msn.com>
428              
429             # Return-path: <ListName-bounce@groups.msn.com>
430             # Received: from [65.54.208.83] (helo=p05.groups.msn.com) etc...
431             # Received: from mail pickup service by p05.groups.msn.com with Microsoft SMTPSVC;
432             # Fri, 6 May 2005 14:59:25 -0700
433             # X-Originating-Ip: 207.68.170.30
434             # Message-Id: <ListName-101@groups.msn.com>
435             # Reply-To: "List Name" <ListName@groups.msn.com>
436             # From: "whoever" <zzzzzzzzzz@hotmail.com>
437             # To: "List Name" <ListName@groups.msn.com>
438             # Subject: whatever
439             # Date: Fri, 6 May 2005 14:59:25 -0700
440              
441             }
442              
443             ###########################################################################
444              
445             my ($self, $pms) = @_;
446              
447             my $from = $pms->get('From:addr');
448             if ($from !~ /\beudoramail\.com$/i) { return 0; }
449              
450             my $rcvd = $pms->get('Received');
451             $rcvd =~ s/\s+/ /gs; # just spaces, simplify the regexp
452              
453             my $ip = $pms->get('X-Sender-Ip',undef);
454             my $IP_ADDRESS = IP_ADDRESS;
455             if (defined $ip && $ip =~ /$IP_ADDRESS/) { $ip = 1; } else { $ip = 0; }
456              
457             # Eudoramail formats its received headers like this:
458             # Received: from Unknown/Local ([?.?.?.?]) by shared1-mail.whowhere.com;
459 0     0 0 0 # Thu Nov 29 13:44:25 2001
460             # Message-Id: <JGDHDEHPPJECDAAA@shared1-mail.whowhere.com>
461 0         0 # Organization: QUALCOMM Eudora Web-Mail (http://www.eudoramail.com:80)
462 0 0       0 # X-Sender-Ip: 192.175.21.146
  0         0  
463             # X-Mailer: MailCity Service
464 0         0  
465 0         0 if ($self->gated_through_received_hdr_remover($pms)) { return 0; }
466              
467 0         0 if ($rcvd =~ /by \S*whowhere.com\;/ && $ip) { return 0; }
468 0         0
469 0 0 0     0 return 1;
  0         0  
  0         0  
470             }
471              
472             ###########################################################################
473              
474             my ($self, $pms) = @_;
475              
476             my $from = $pms->get('From:addr');
477             if ($from !~ /\byahoo\.com$/i) { return 0; }
478              
479 0 0       0 my $rcvd = $pms->get('Received');
  0         0  
480            
481 0 0 0     0 if ($pms->get("Resent-From") ne '' && $pms->get("Resent-To") ne '') {
  0         0  
482             my $xrcvd = $pms->get("X-Received");
483 0         0 $rcvd = $xrcvd if $xrcvd ne '';
484             }
485             $rcvd =~ s/\s+/ /gs; # just spaces, simplify the regexp
486              
487             # not sure about this
488             #if ($rcvd !~ /from \S*yahoo\.com/) { return 0; }
489 81     81 0 188  
490             if ($self->gated_through_received_hdr_remover($pms)) { return 0; }
491 81         278  
492 81 50       345 # bug 3740: ignore bounces from Yahoo!. only honoured if the
  81         1220  
493             # correct rDNS shows up in the trusted relay list, or first untrusted relay
494 0         0 #
495             # bug 4528: [ ip=68.142.202.54 rdns=mta122.mail.mud.yahoo.com
496 0 0 0     0 # helo=mta122.mail.mud.yahoo.com by=eclectic.kluge.net ident=
497 0         0 # envfrom= intl=0 id=49F2EAF13B auth= ]
498 0 0       0 #
499             if ($pms->{relays_trusted_str} =~ / rdns=\S+\.yahoo\.com /
500 0         0 || $pms->{relays_untrusted_str} =~ /^[^\]]+ rdns=\S+\.yahoo\.com /)
501             { return 0; }
502              
503             if ($rcvd =~ /by web\S+\.mail\S*\.yahoo\.com via HTTP/) { return 0; }
504             if ($rcvd =~ /by sonic\S+\.consmr\.mail\S*\.yahoo\.com with HTTP/) { return 0; }
505 0 0       0 if ($rcvd =~ /by smtp\S+\.yahoo\.com with SMTP/) { return 0; }
  0         0  
506             my $IP_ADDRESS = IP_ADDRESS;
507             if ($rcvd =~
508             /from \[$IP_ADDRESS\] by \S+\.(?:groups|scd|dcn)\.yahoo\.com with NNFMP/) {
509             return 0;
510             }
511              
512             # used in "forward this news item to a friend" links. There's no better
513             # received hdrs to match on, unfortunately. I'm not sure if the next test is
514 0 0 0     0 # still useful, as a result.
515             #
516 0         0 # search for msgid <20020929140301.451A92940A9@xent.com>, subject "Yahoo!
517             # News Story - Top Stories", date Sep 29 2002 on
518 0 0       0 # <http://xent.com/pipermail/fork/> for an example.
  0         0  
519 0 0       0 #
  0         0  
520 0 0       0 if ($rcvd =~ /\bmailer\d+\.bulk\.scd\.yahoo\.com\b/
  0         0  
521 0         0 && $from =~ /\@reply\.yahoo\.com$/i) { return 0; }
522 0 0       0  
523             if ($rcvd =~ /by \w+\.\w+\.yahoo\.com \(\d+\.\d+\.\d+\/\d+\.\d+\.\d+\)(?: with ESMTP)? id \w+/) {
524 0         0 # possibly sent from "mail this story to a friend"
525             return 0;
526             }
527              
528             return 1;
529             }
530              
531             my ($self, $pms) = @_;
532              
533             my $from = $pms->get('From:addr');
534             if ($from !~ /\bjuno\.com$/i) { return 0; }
535 0 0 0     0  
536 0         0 if ($self->gated_through_received_hdr_remover($pms)) { return 0; }
537              
538 0 0       0 my $xorig = $pms->get('X-Originating-IP');
539             my $xmailer = $pms->get('X-Mailer');
540 0         0 my $rcvd = $pms->get('Received');
541             my $IP_ADDRESS = IP_ADDRESS;
542              
543 0         0 if ($xorig ne '') {
544             # New style Juno has no X-Originating-IP header, and other changes
545             if($rcvd !~ /from.*\b(?:juno|untd)\.com.*[\[\(]$IP_ADDRESS[\]\)].*by/
546             && $rcvd !~ / cookie\.(?:juno|untd)\.com /) { return 1; }
547 0     0 0 0 if($xmailer !~ /Juno /) { return 1; }
548             } else {
549 0         0 if($rcvd =~ /from.*\bmail\.com.*\[$IP_ADDRESS\].*by/) {
550 0 0       0 if($xmailer !~ /\bmail\.com/) { return 1; }
  0         0  
551             } elsif($rcvd =~ /from (webmail\S+\.untd\.com) \(\1 \[$IP_ADDRESS\]\) by/) {
552 0 0       0 if($xmailer !~ /^Webmail Version \d/) { return 1; }
  0         0  
553             } else {
554 0         0 return 1;
555 0         0 }
556 0         0 if($xorig !~ /$IP_ADDRESS/) { return 1; }
557 0         0 }
558              
559 0 0       0 return 0;
560             }
561 0 0 0     0  
562 0         0 my ($self, $pms) = @_;
563 0 0       0 use constant GOOGLE_MESSAGE_STATE_LENGTH_MIN => 60;
  0         0  
564             use constant GOOGLE_SMTP_SOURCE_LENGTH_MIN => 60;
565 0 0       0  
    0          
566 0 0       0 my $from = $pms->get('From:addr');
  0         0  
567             if ($from !~ /\bgmail\.com$/i) { return 0; }
568 0 0       0  
  0         0  
569             my $xgms = $pms->get('X-Gm-Message-State');
570 0         0 my $xss = $pms->get('X-Google-Smtp-Source');
571             my $xreceived = $pms->get('X-Received');
572 0 0       0 my $received = $pms->get('Received');
  0         0  
573              
574             if ($xreceived =~ /by 10\.\S+ with SMTP id \S+/) { return 0; }
575 0         0 if ($xreceived =~ /by 2002\:a\d\d\:\w+\:\S+ with SMTP id \S+/) { return 0; }
576             if ($received =~ /by smtp\.googlemail\.com with ESMTPSA id \S+/) {
577             return 0;
578             }
579 0     0 0 0 if ( (length($xgms) >= GOOGLE_MESSAGE_STATE_LENGTH_MIN) &&
580 22     22   246 (length($xss) >= GOOGLE_SMTP_SOURCE_LENGTH_MIN)) {
  22         79  
  22         7345  
581 22     22   159 return 0;
  22         54  
  22         8375  
582             }
583 0         0  
584 0 0       0 return 1;
  0         0  
585             }
586 0         0  
587 0         0 my ($self, $pms) =@_;
588 0         0 # two blank headers match so don't bother checking
589 0         0 return (lc $pms->get('EnvelopeFrom:addr') eq lc $pms->get('From:addr'));
590             }
591 0 0       0  
  0         0  
592 0 0       0 my ($self, $pms) = @_;
  0         0  
593 0 0       0  
594 0         0 if (!exists $pms->{tocc_sorted}) {
595             $self->_check_recipients($pms);
596 0 0 0     0 }
597             return $pms->{tocc_sorted};
598 0         0 }
599              
600             my ($self, $pms, $min, $max) = @_;
601 0         0  
602             if (!exists $pms->{tocc_similar}) {
603             $self->_check_recipients($pms);
604             }
605 0     0 0 0 return (($min eq 'undef' || $pms->{tocc_similar} >= $min) &&
606             ($max eq 'undef' || $pms->{tocc_similar} < $max));
607 0         0 }
608              
609             # best experimentally derived values
610             use constant TOCC_SORTED_COUNT => 7;
611 81     81 0 196 use constant TOCC_SIMILAR_COUNT => 5;
612             use constant TOCC_SIMILAR_LENGTH => 2;
613 81 100       248  
614 41         153 my ($self, $pms) = @_;
615              
616 81         1351 my @inputs;
617              
618             # ToCc: pseudo-header works best, but sometimes Bcc: is better
619             for ('ToCc', 'Bcc') {
620 81     81 0 381 my $to = $pms->get($_); # get recipients
621             $to =~ s/\(.*?\)//g; # strip out the (comments)
622 81 100       285 push(@inputs, ($to =~ m/([\w.=-]+\@\w+(?:[\w.-]+\.)+\w+)/g));
623 40         156 last if scalar(@inputs) >= TOCC_SIMILAR_COUNT;
624             }
625              
626 81   33     2031 # remove duplicate addresses only when they appear next to each other
627             my @address;
628             my $previous = '';
629             while (my $current = shift @inputs) {
630 22     22   198 push(@address, ($previous = $current)) if lc($current) ne lc($previous);
  22         45  
  22         1194  
631 22     22   135 last if @address == 256;
  22         48  
  22         1040  
632 22     22   161 }
  22         59  
  22         68449  
633              
634             # ideas that had both poor S/O ratios and poor hit rates:
635 81     81   222 # - testing for reverse sorted recipient lists
636             # - testing To: and Cc: headers separately
637 81         131 $pms->{tocc_sorted} = (scalar(@address) >= TOCC_SORTED_COUNT &&
638             join(',', @address) eq (join(',', sort @address)));
639              
640 81         179 # a good S/O ratio and hit rate is achieved by comparing 2-byte
641 161         367 # substrings and requiring 5 or more addresses
642 161         302 $pms->{tocc_similar} = 0;
643 161         442 if (scalar (@address) >= TOCC_SIMILAR_COUNT) {
644 161 100       493 my @user = map { substr($_,0,TOCC_SIMILAR_LENGTH) } @address;
645             my @fqhn = map { m/\@(.*)/ } @address;
646             my @host = map { substr($_,0,TOCC_SIMILAR_LENGTH) } @fqhn;
647             my $hits = 0;
648 81         133 my $combinations = 0;
649 81         152 for (my $i = 0; $i <= $#address; $i++) {
650 81         259 for (my $j = $i+1; $j <= $#address; $j++) {
651 55 50       163 $hits++ if $user[$i] eq $user[$j];
652 55 50       177 $hits++ if $host[$i] eq $host[$j] && $fqhn[$i] ne $fqhn[$j];
653             $combinations++;
654             }
655             }
656             $pms->{tocc_similar} = $hits / $combinations;
657             }
658 81   66     371 }
659              
660             my ($self, $pms) = @_;
661              
662             my $hdr = $pms->get('To');
663 81         207 $hdr = $pms->get('Apparently-To') if $hdr eq '';
664 81 100       265 return 1 if $hdr eq '';
665 1         4  
  48         111  
666 1         9 return 0;
  48         155  
667 1         21 }
  48         101  
668 1         7  
669 1         3 my ($self, $pms) = @_;
670 1         5 local ($_);
671 48         87  
672 1128 100       1737 my $rcv = $pms->get('Received');
673 1128 100 100     2780  
674 1128         2035 # e.g.
675             # Received: from mail3.icytundra.com by gw05 with ESMTP; Thu, 21 Jun 2001 02:28:32 -0400
676             my ($h1, $h2) = ($rcv =~
677 1         22 m/\nfrom\s(\S+)\sby\s(\S+)\swith\sESMTP\;\s+\S\S\S,\s+\d+\s+\S\S\S\s+
678             \d{4}\s+\d\d:\d\d:\d\d\s+[-+]*\d{4}\n$/xs);
679              
680             if (defined ($h1) && defined ($h2) && $h2 !~ /\./) {
681             return 1;
682 0     0 0 0 }
683              
684 0         0 0;
685 0 0       0 }
686 0 0       0  
687             ###########################################################################
688 0         0  
689             my ($self, $pms, $min, $max) = @_;
690              
691             if (!exists $pms->{date_diff}) {
692 0     0 0 0 $self->_check_date_diff($pms);
693 0         0 }
694             return (($min eq 'undef' || $pms->{date_diff} >= (3600 * $min)) &&
695 0         0 ($max eq 'undef' || $pms->{date_diff} < (3600 * $max)));
696             }
697              
698             # filters out some false positives in old corpus mail - Allen
699 0         0 my ($self,$pms,$min,$max) = @_;
700              
701             if (!exists($pms->{date_received})) {
702             $self->_check_date_received($pms);
703 0 0 0     0 }
      0        
704 0         0 my $diff = time() - $pms->{date_received};
705              
706             # 365.2425 * 24 * 60 * 60 = 31556952 = seconds in year (including leap)
707 0         0  
708             if (((! defined($min)) || ($min eq 'undef') ||
709             ($diff >= (31556952 * ($min/12)))) &&
710             ((! defined($max)) || ($max eq 'undef') ||
711             ($diff < (31556952 * ($max/12))))) {
712             return 1;
713 0     0 0 0 } else {
714             return 0;
715 0 0       0 }
716 0         0 }
717              
718             my ($self, $pms) = @_;
719 0   0     0  
720             my $time;
721             # a Resent-Date: header takes precedence over any Date: header
722             DATE: for my $header ('Resent-Date', 'Date') {
723             my @dates = $pms->{msg}->get_header($header);
724 0     0 0 0 for my $date (@dates) {
725             if (defined($date) && length($date)) {
726 0 0       0 chomp($date);
727 0         0 $time = parse_rfc822_date($date);
728             }
729 0         0 last DATE if defined($time);
730             }
731             }
732             if (defined($time)) {
733 0 0 0     0 $pms->{date_header_time} = $time;
      0        
      0        
734             }
735             else {
736             $pms->{date_header_time} = undef;
737 0         0 }
738             }
739 0         0  
740             my ($self, $pms) = @_;
741              
742             $pms->{received_header_times} = [ () ];
743             $pms->{received_fetchmail_time} = undef;
744 0     0   0  
745             my (@received);
746 0         0 my $received = $pms->get('Received');
747             if ($received ne '') {
748 0         0 @received = grep {$_ =~ m/\S/} (split(/\n/,$received));
749 0         0 }
750 0         0 # if we have no Received: headers, chances are we're archived mail
751 0 0 0     0 # with a limited set of headers
752 0         0 if (!scalar(@received)) {
753 0         0 return;
754             }
755 0 0       0  
756             # handle fetchmail headers
757             my (@local);
758 0 0       0 if (($received[0] =~
759 0         0 m/\bfrom (?:localhost\s|(?:\S+ ){1,2}\S*\b127\.0\.0\.1\b)/) ||
760             ($received[0] =~ m/qmail \d+ invoked by uid \d+/)) {
761             push @local, (shift @received);
762 0         0 }
763             if (scalar(@received) &&
764             ($received[0] =~ m/\bby localhost with \w+ \(fetchmail-[\d.]+/)) {
765             push @local, (shift @received);
766             }
767 0     0   0 elsif (scalar(@local)) {
768             unshift @received, (shift @local);
769 0         0 }
770 0         0  
771             if (scalar(@local)) {
772 0         0 my (@fetchmail_times);
773 0         0 foreach my $rcvd (@local) {
774 0 0       0 if ($rcvd =~ m/(\s.?\d+ \S\S\S \d+ \d+:\d+:\d+ \S+)/) {
775 0         0 my $date = $1;
  0         0  
776             dbg2("eval: trying Received fetchmail header date for real time: $date");
777             my $time = parse_rfc822_date($date);
778             if (defined($time) && (time() >= $time)) {
779 0 0       0 dbg2("eval: time_t from date=$time, rcvd=$date");
780 0         0 push @fetchmail_times, $time;
781             }
782             }
783             }
784 0         0 if (scalar(@fetchmail_times) > 1) {
785 0 0 0     0 $pms->{received_fetchmail_time} =
786             (sort {$b <=> $a} (@fetchmail_times))[0];
787             } elsif (scalar(@fetchmail_times)) {
788 0         0 $pms->{received_fetchmail_time} = $fetchmail_times[0];
789             }
790 0 0 0     0 }
    0          
791              
792 0         0 my (@header_times);
793             foreach my $rcvd (@received) {
794             if ($rcvd =~ m/(\s.?\d+ \S\S\S \d+ \d+:\d+:\d+ \S+)/) {
795 0         0 my $date = $1;
796             dbg2("eval: trying Received header date for real time: $date");
797             my $time = parse_rfc822_date($date);
798 0 0       0 if (defined($time)) {
799 0         0 dbg2("eval: time_t from date=$time, rcvd=$date");
800 0         0 push @header_times, $time;
801 0 0       0 }
802 0         0 }
803 0         0 }
804 0         0  
805 0 0 0     0 if (scalar(@header_times)) {
806 0         0 $pms->{received_header_times} = [ @header_times ];
807 0         0 } else {
808             dbg("eval: no dates found in Received headers");
809             }
810             }
811 0 0       0  
    0          
812             my ($self, $pms) = @_;
813 0         0  
  0         0  
814             my (@dates_poss);
815 0         0  
816             $pms->{date_received} = 0;
817              
818             if (!exists($pms->{date_header_time})) {
819 0         0 $self->_get_date_header_time($pms);
820 0         0 }
821 0 0       0  
822 0         0 if (defined($pms->{date_header_time})) {
823 0         0 push @dates_poss, $pms->{date_header_time};
824 0         0 }
825 0 0       0  
826 0         0 if (!exists($pms->{received_header_times})) {
827 0         0 $self->_get_received_header_times($pms);
828             }
829             my (@received_header_times) = @{ $pms->{received_header_times} };
830             if (scalar(@received_header_times)) {
831             push @dates_poss, $received_header_times[0];
832 0 0       0 }
833 0         0 if (defined($pms->{received_fetchmail_time})) {
834             push @dates_poss, $pms->{received_fetchmail_time};
835 0         0 }
836              
837             if (defined($pms->{date_header_time}) && scalar(@received_header_times)) {
838             if (!exists($pms->{date_diff})) {
839             $self->_check_date_diff($pms);
840 0     0   0 }
841             push @dates_poss, $pms->{date_header_time} - $pms->{date_diff};
842 0         0 }
843              
844 0         0 if (scalar(@dates_poss)) { # use median
845             $pms->{date_received} = (sort {$b <=> $a}
846 0 0       0 (@dates_poss))[int($#dates_poss/2)];
847 0         0 dbg("eval: date chosen from message: " .
848             scalar(localtime($pms->{date_received})));
849             } else {
850 0 0       0 dbg("eval: no dates found in message");
851 0         0 }
852             }
853              
854 0 0       0 my ($self, $pms) = @_;
855 0         0  
856             $pms->{date_diff} = 0;
857 0         0  
  0         0  
858 0 0       0 if (!exists($pms->{date_header_time})) {
859 0         0 $self->_get_date_header_time($pms);
860             }
861 0 0       0  
862 0         0 if (!defined($pms->{date_header_time})) {
863             return; # already have tests for this
864             }
865 0 0 0     0  
866 0 0       0 if (!exists($pms->{received_header_times})) {
867 0         0 $self->_get_received_header_times($pms);
868             }
869 0         0 my (@header_times) = @{ $pms->{received_header_times} };
870              
871             if (!scalar(@header_times)) {
872 0 0       0 return; # archived mail?
873 0         0 }
  0         0  
874              
875             my (@diffs) = map {$pms->{date_header_time} - $_} (@header_times);
876 0         0  
877             # if the last Received: header has no difference, then we choose to
878 0         0 # exclude it
879             if ($#diffs > 0 && $diffs[$#diffs] == 0) {
880             pop(@diffs);
881             }
882              
883 0     0   0 # use the date with the smallest absolute difference
884             # (experimentally, this results in the fewest false positives)
885 0         0 @diffs = sort { abs($a) <=> abs($b) } @diffs;
886             $pms->{date_diff} = $diffs[0];
887 0 0       0 }
888 0         0  
889              
890             my ($self, $pms) = @_;
891 0 0       0 my $subject = $pms->get('Subject');
892 0         0  
893             $subject =~ s/^\s+//;
894             $subject =~ s/\s+$//;
895 0 0       0 $subject =~ s/^(?:(?:Re|Fwd|Fw|Aw|Antwort|WG|SV|VB|VS|VL):\s*)+//i; # Bug 6805
896 0         0 return 0 if $subject !~ /\s/; # don't match one word subjects
897             return 0 if (length $subject < 10); # don't match short subjects
898 0         0 $subject =~ s/[^a-zA-Z]//g; # only look at letters
  0         0  
899              
900 0 0       0 # now, check to see if the subject is encoded using a non-ASCII charset.
901 0         0 # If so, punt on this test to avoid FPs. We just list the known charsets
902             # this test will FP on, here.
903             my $subjraw = $pms->get('Subject:raw');
904 0         0 my $CLTFAC = Mail::SpamAssassin::Constants::CHARSETS_LIKELY_TO_FP_AS_CAPS;
  0         0  
905             if ($subjraw =~ /=\?${CLTFAC}\?/i) {
906             return 0;
907             }
908 0 0 0     0  
909 0         0 return length($subject) && ($subject eq uc($subject));
910             }
911              
912             my ($self, $pms, $test) = @_;
913              
914 0         0 my $full_to = $pms->get('To:addr');
  0         0  
915 0         0 return 0 unless $full_to ne '';
916              
917             my $subject = $pms->get('Subject');
918              
919             if ($test eq "address") {
920 0     0 0 0 return $subject =~ /\b\Q$full_to\E\b/i; # "user@domain.com"
921 0         0 }
922             elsif ($test eq "user") {
923 0         0 my $to = $full_to;
924 0         0 $to =~ s/\@.*//;
925 0         0 my $subj = $subject;
926 0 0       0 $subj =~ s/^\s+//;
927 0 0       0 $subj =~ s/\s+$//;
928 0         0
929             return $subject =~ /^(?:
930             (?:re|fw):\s*(?:\w+\s+)?\Q$to\E$
931             |(?-i:\Q$to\E)\s*[,:;!?-](?:$|\s)
932             |\Q$to\E$
933 0         0 |,\s*\Q$to\E[,:;!?-]$
934 0         0 )/ix;
935 0 0       0 }
936 0         0 return 0;
937             }
938              
939 0   0     0 my ($self, $pms) = @_;
940             local ($_);
941              
942             my $id = $pms->get('MESSAGEID');
943 0     0 0 0 return 0 if $id !~ /^<[0-9a-f]{4}([0-9a-f]{8})\$[0-9a-f]{8}\$[0-9a-f]{8}\@/;
944              
945 0         0 my $timetoken = hex($1);
946 0 0       0 my $x = 0.0023283064365387;
947             my $y = 27111902.8329849;
948 0         0  
949             my $fudge = 250;
950 0 0       0  
    0          
951 0         0 $_ = $pms->get('Date');
952             $_ = parse_rfc822_date($_) || 0;
953             my $expected = int (($_ * $x) + $y);
954 0         0 my $diff = $timetoken - $expected;
955 0         0 return 0 if (abs($diff) < $fudge);
956 0         0  
957 0         0 $_ = $pms->get('Received');
958 0         0 /(\s.?\d+ \S\S\S \d+ \d+:\d+:\d+ \S+).*?$/;
959             $_ = parse_rfc822_date($_) || 0;
960 0         0 $expected = int(($_ * $x) + $y);
961             $diff = $timetoken - $expected;
962              
963             return (abs($diff) >= $fudge);
964             }
965              
966             my ($self, $pms) = @_;
967 0         0 local ($_);
968              
969             # Lyris eats message-ids. also some ezmlm, I think :(
970             $_ = $pms->get("List-Unsubscribe");
971 81     81 0 180 return 1 if (/<mailto:(?:leave-\S+|\S+-unsubscribe)\@\S+>$/i);
972 81         140  
973             # ezmlm again
974 81         220 if($self->gated_through_received_hdr_remover($pms)) { return 1; }
975 81 100       1415  
976             # Allen notes this as 'Wacky sendmail version?'
977 1         7 $_ = $pms->get("Received");
978 1         2 return 1 if /\/CWT\/DCE\)/;
979 1         2  
980             # Apr 2 2003 jm: iPlanet rewrites lots of stuff, including Message-IDs
981 1         3 return 1 if /iPlanet Messaging Server/;
982              
983 1         4 return 0;
984 1   50     16 }
985 1         5  
986 1         4 # Return true if the count of $hdr headers are within the given range
987 1 50       4 my ($self, $pms, $hdr, $min, $max) = @_;
988             my %uniq;
989 1         5 my @hdrs = grep(!$uniq{$_}++, $pms->{msg}->get_header ($hdr));
990 1         35 return (scalar @hdrs >= $min && scalar @hdrs <= $max);
991 1   50     4 }
992 1         6  
993 1         2 my ($self, $pms) = @_;
994              
995 1         23 my $all = $pms->get('ALL'); # cached access
996            
997             for my $header (split(/\n/, $all)) {
998             # slightly faster to test in this order
999 0     0 0   if ($header =~ /%[A-Z][A-Z_-]/ &&
1000 0           $header !~ /^(?:X-VMS-To|X-UIDL|X-Face|To|Cc|From|Subject|References|In-Reply-To|(?:X-|Resent-|X-Original-)?Message-Id):/i)
1001             {
1002             return 1;
1003 0           }
1004 0 0         }
1005             return 0;
1006             }
1007 0 0          
  0            
1008             my ($self, $pms) = @_;
1009              
1010 0           my $mid = $pms->get('MESSAGEID');
1011 0 0         my $from = $pms->get('From');
1012             if ($mid =~ m/<[A-Z]{28}\.([^>]+?)>/) {
1013             if ($from =~ m/\"[^\"]+\"\s*<\Q$1\E>/) {
1014 0 0         return 1;
1015             }
1016 0           }
1017             return 0;
1018             }
1019              
1020             my ($self, $pms) = @_;
1021 0     0 0    
1022 0           my $to = $pms->get('To:addr');
1023 0           my $from = $pms->get('EnvelopeFrom:addr');
1024 0   0        
1025             return 0 if $from eq '' || $to eq '';
1026             return 0 if $from =~ /^SRS\d[=+-]/i;
1027              
1028 0     0 0   if ($to =~ /^([^@]+)\@(.+)$/) {
1029             my($user,$dom) = ($1,$2);
1030 0           $dom = $self->{main}->{registryboundaries}->trim_domain($dom);
1031             return unless
1032 0           ($self->{main}->{registryboundaries}->is_domain_valid($dom));
1033              
1034 0 0 0       return 1 if ($from =~ /\b\Q$dom\E.\Q$user\E@/i);
1035             }
1036              
1037 0           return 0;
1038             }
1039              
1040 0           # ADDED FROM BUG 6487
1041             my ($self, $pms) = @_;
1042              
1043             my $from = $pms->get('From:addr');
1044 0     0 0   my $envfrom = $pms->get('EnvelopeFrom:addr');
1045              
1046 0           local $1;
1047 0           my $fromdomain = '';
1048 0 0         #Revised regexp from 6487 comment 3
1049 0 0         $fromdomain = $1 if $from =~ /\@([^@]*)\z/;
1050 0           $fromdomain =~ s/^.+\.([^\.]+\.[^\.]+)$/$1/;
1051             return 0 if $fromdomain eq '';
1052              
1053 0           my $envfromdomain = '';
1054             $envfromdomain = $1 if $envfrom =~ /\@([^@]*)\z/;
1055             $envfromdomain =~ s/^.+\.([^\.]+\.[^\.]+)$/$1/;
1056             return 0 if $envfromdomain eq '';
1057 0     0 0    
1058             dbg("eval: From 2nd level domain: $fromdomain, EnvelopeFrom 2nd level domain: $envfromdomain");
1059 0          
1060 0           return 1 if lc($fromdomain) ne lc($envfromdomain);
1061              
1062 0 0 0       return 0;
1063 0 0         }
1064              
1065 0 0          
1066 0           ###########################################################################
1067 0            
1068             # support eval-test verbose debugs using "-Deval"
1069 0 0         if (would_log('dbg', 'eval') == 2) {
1070             dbg(@_);
1071 0 0         }
1072             }
1073              
1074 0           1;