~rgrjr/rgrjr-scripts

84711da54a099302e9bdebbbd035e904a91bb3bb — Bob Rogers 2 years ago 76b2015
Add --list-host to email/forged-local-address.pl

Plus testing and qmail-deliver.pl support.
* email/forged-local-address.pl:
   + Add a --list-host option, as a (possibly better) alternative to
     --sender-re for what look like forgeries from email lists.
   + (add_list_host):  Command line parsing helper.
   + (list_host_p):  Check whether a "Received:" header matches.
   + Use same to override a determination of nonlocal.
   + Add documentation for --list-host.
   + Also add a SYNOPSIS section, and drop the complaint about
     --network-prefix from BUGS.
* email/qmail-deliver.pl:
   + Add a --list-host keyword, and pass it to forged-local-address.pl.
* email/test-qmail-deliver.pl:
   + Add a --list-host test using mailing-list-1.text.
* makefile:
   + (test-nonforged-addresses):  Add a test case for --list-host.
* email/mailing-list-1.text (added):
   + Test data.
M email/forged-local-address.pl => email/forged-local-address.pl +80 -6
@@ 27,12 27,14 @@ my $not_p = 0;		# to reverse the sense of the test.
my ($local_domain_file, %match_domains, @suffix_domains);
my @local_networks;
my %relay_p;		# authorized relays.
my %list_host_p;	# authorized mailing list host names.
my @sender_regexps;
my $dotted_quad = '\d+.\d+.\d+.\d+';	# constant regexp.

GetOptions('verbose+' => \$verbose_p,
	   'not!' => \$not_p,
	   'sender-re=s' => \@sender_regexps,
	   'list-host=s' => \&add_list_host,
	   'add-local=s' => \&add_local_domain,
	   'relay-ip=s' => \&add_relay,
	   'network-prefix=s' => \&add_local_net,


@@ 103,6 105,15 @@ sub add_local_net {
    push(@local_networks, $block);
}

sub add_list_host {
    # Still more option parsing.
    my ($option, $host_name) = @_;

    die "$0:  Mailing list host name must be a FQDN.\n"
	unless $host_name =~ /\D[.]\D/;
    $list_host_p{lc($host_name)}++;
}

sub ensure_nonlocal_host {
    # Exits with $spam_exit code if it matches any local domain.
    my ($host, $description) = @_;


@@ 207,6 218,41 @@ sub local_header_p {
    }
}

sub list_host_p {
    # The sole argument is expected to be a "Received:\s*" header string
    # generated by our server, possibly multiline, but without the part
    # matching this RE.  This is expected to be of the form:
    #
    #    "from anna.opensuse.org (foo.opensuse.org [195.135.8.3]) by ..."
    #
    # where "anna.opensuse.org" is what the corresponding server told us when
    # it connected to our server, "195.135.8.3" is the IP address we got from
    # the connection (and therefore trustworthy), and "foo.opensuse.org" is
    # what reverse DNS told us about the IP address (and therefore somewhat
    # less trustworthy).  Return undef if we have no header, 'list' if the rDNS
    # name or a suffix is an email list host in %list_host_p, and 0 otherwise.
    my ($hdr) = @_;

    return
	# Can't make a determination.
	unless $hdr;

    # Collapse linear whitespace for ease of parsing.
    $hdr =~ s/[ \t\n]+/ /;
    if ($hdr =~ /from \S+ \((\S+) \[\S+\]\)/) {
	my $host_name = $1;
	# Chop off prefixes to see if something matches.
	while ($host_name) {
	    return 'list'
		if $list_host_p{$host_name};
	    $host_name =~ s/^[^.]+[.]//
		or last;
	}
    }
    # No match.
    return 0;
}

### Main code.

# Check headers for local vs. remote.


@@ 227,6 273,8 @@ do {
    # for higher indices if no such header exists.  -- rgr, 17-Mar-09.]
    my $header = $message->get('Received', $rcvd_idx);
    $local_p = local_header_p($header);
    $local_p = list_host_p($header)
	if defined($local_p) && ! $local_p;
    warn("header $rcvd_idx:  ", $header || "\n",
	 "header $rcvd_idx:  result is ",
	 (defined($local_p) ? "'$local_p'" : '(undef)'), ".\n")


@@ 325,9 373,19 @@ exit($legit_exit);

__END__

=head1 DESCRIPTION
=head1 NAME

Detect forged email addresses by examining "Received:" headers.
forged-local-address.pl - detect forged emails by examining "Received:" headers

=head1 SYNOPSIS

    forged-local-address.pl [ --verbose ... ] [ --not ]
		[ --sender-re=<regexp> ... ] [ --list-host=<host-name ... ]
		[ --add-local=<domain-name> ... ] [ --relay-ip=<dotted-quad> ]
		[ --network-prefix=<netblock> ... ]
		[ --locals=<local-domain-file> ]

=head1 DESCRIPTION

Each mail transport agent (MTA) adds at least one "Received:" header
to the front of the pile, so the first one was added by your MTA


@@ 356,6 414,12 @@ came from the relay and we trust the relay.

=item 3.

If it's a designated mailing list server, identified by the
L</--list-host> option, then we assume we're seeing our post coming
back to us, and consider it "local" even though it's not.

=item 4.

Otherwise it's from the wild, wild West, and we disallow any of our
local domain names in the envelope sender and the "Sender:", "From:",
and "Reply-To:" headers, including parenthetical comments and text


@@ 387,11 451,17 @@ output.
Any external systems that are authorized to relay mail from the
Internet at large, identified by C<--relay-ip>.

=item 4.

Any external mailing list servers that are authorized by name to send
copies of posts that appear to original from local email addresses,
identified by C<--list-host>.

=back

The defaults for these values are usually sufficient; the only thing
that C<forged-local-address.pl> can't figure out on its own is the
existence of any authorized relays.
existence of authorized relays and mailing list servers.

Currently, the only supported MTAs are Qmail and Postfix.  Since
"Received:" headers are supposed to be fairly standard, it's possible


@@ 432,6 502,13 @@ Specifies a single domain name to add to the "local" set.  If the name
starts with a ".", it is a wildcard; otherwise, the whole domain name
must match.

=item B<--list-host>

Specifies a single domain name to add to the set of mailing list
servers.  Any host that has an rDNS name with this as a suffix is
considered valid as a mailing list host, so emails using one of our
addresses coming from such a relay are not considered forgeries.

=item B<--locals>

Specifies a file of domain names.  Each line in this file is treated


@@ 569,9 646,6 @@ you must mention both explicitly.

=head1 BUGS

C<--network-prefix> shouldn't be biased towards class C networks that
start with "192.168...".

There is no error if the C<--locals> file does not exist, even if it
was specified explicitly.


A email/mailing-list-1.text => email/mailing-list-1.text +74 -0
@@ 0,0 1,74 @@
X-Delivered-By: /usr/local/bin/qmail-deliver.pl (30965)
Return-Path: <users-bounces@opensuse.org>
X-Original-To: rogers-suse@rgrjr.homedns.org
Delivered-To: rogers-suse@rgrjr.homedns.org
Received: from rgrjr.com (li126-47.members.linode.com [69.164.211.47])
	by scorpio.rgrjr.com (Postfix on openSUSE GNU/Linux) with ESMTP id 4690A5FE8A
	for <rogers-suse@rgrjr.homedns.org>; Sun, 28 Nov 2021 00:23:09 -0500 (EST)
Received: from anna.opensuse.org (proxy-nue1.opensuse.org [195.135.221.145])
	by rgrjr.com (Postfix on openSUSE) with ESMTP id 120A71D6BB2
	for <rogers-suse@rgrjr.homedns.org>; Sun, 28 Nov 2021 05:23:23 +0000 (UTC)
Received: from mailman3.infra.opensuse.org (mailman3.infra.opensuse.org [192.168.47.80])
	by anna.opensuse.org (Postfix) with ESMTP id 91F9C21DEC;
	Sun, 28 Nov 2021 05:23:00 +0000 (UTC)
Received: from mailman3.infra.opensuse.org (localhost [127.0.0.1])
	by mailman3.infra.opensuse.org (Postfix) with ESMTP id 5F5476728;
	Sun, 28 Nov 2021 05:23:00 +0000 (UTC)
Received: from mx2.opensuse.org (mx2.infra.opensuse.org [192.168.47.96])
	by mailman3.infra.opensuse.org (Postfix) with ESMTP id 05BE079D
	for <users@lists.opensuse.org>; Sun, 28 Nov 2021 05:22:47 +0000 (UTC)
Received: from mx2.opensuse.org (localhost [127.0.0.1])
	by mx2.opensuse.org (Postfix) with ESMTP id A6CADC9B
	for <users@lists.opensuse.org>; Sun, 28 Nov 2021 05:22:44 +0000 (UTC)
X-Spam-Checker-Version: SpamAssassin 3.4.5 (2021-03-20) on
	mx2.infra.opensuse.org
X-Spam-Level: 
X-Spam-Status: No, score=-0.5 required=5.0 tests=KHOP_HELO_FCRDNS,NICE_REPLY_A
	autolearn=disabled version=3.4.5
X-Spam-Virus: No
Received: from rgrjr.com (li126-47.members.linode.com [69.164.211.47])
	by mx2.opensuse.org (Postfix) with ESMTP
	for <users@lists.opensuse.org>; Sun, 28 Nov 2021 05:22:44 +0000 (UTC)
Received: from rgrjr.com (c-73-16-206-7.hsd1.ma.comcast.net [73.16.206.7])
	by rgrjr.com (Postfix on openSUSE) with ESMTP id A25701D6BB2
	for <users@lists.opensuse.org>; Sun, 28 Nov 2021 05:22:53 +0000 (UTC)
Received: from orion.rgrjr.com (orion.rgrjr.com [192.168.0.3])
	by scorpio.rgrjr.com (Postfix on openSUSE GNU/Linux) with ESMTP id 3EF146017A;
	Sun, 28 Nov 2021 00:22:39 -0500 (EST)
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Message-ID: <24995.4636.242641.263134@orion.rgrjr.com>
Date: Sun, 28 Nov 2021 00:22:36 -0500
From: Bob Rogers <rogers-suse@rgrjr.homedns.org>
To: oS-EN <users@lists.opensuse.org>
Subject: Re: [oS-en] eth0 trouble
In-Reply-To: <c26c1212-b74b-88a3-c120-21113a0ac64e@telefonica.net>
References: <04256326-0fd4-5d3f-bbbc-c147e0c02158@telefonica.net>
	<b77888a7-7565-3a2e-0968-ceaa32a63a43@telefonica.net>
	<sntqfs$io8$3@saturn.local.net>
	<b45af0fd-103e-8c7c-b0bd-544135a6636e@telefonica.net>
	<ee930951-f7a0-68f4-a15e-c28d7d087534@jknott.net>
	<20211127203838.64817e9b@acer-suse.lan>
	<fef8630c-5f98-cc82-379a-9cec1186a749@telefonica.net>
	<459027a6-6533-cfc6-7b77-634d5341bd20@jknott.net>
	<b45f29d4-5697-0a2c-f539-b2aa5e66a35d@telefonica.net>
	<32075fee-2520-443a-feec-eb7ae6b851bd@jknott.net>
	<c26c1212-b74b-88a3-c120-21113a0ac64e@telefonica.net>
X-Mailer: VM 7.19 under Emacs 28.0.60
Message-ID-Hash: QZVX4QZOBVA5DMNPRJOUVJVUNR6CCCXS
X-Message-ID-Hash: QZVX4QZOBVA5DMNPRJOUVJVUNR6CCCXS
X-MailFrom: SRS0=f9Mk=QP=rgrjr.homedns.org=rogers-suse@opensuse.org
X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-config-1; header-match-config-2; header-match-config-3; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header
X-Mailman-Version: 3.3.4
Precedence: list
List-Id: openSUSE Users <users.lists.opensuse.org>
Archived-At: <https://lists.opensuse.org/archives/list/users@lists.opensuse.org/message/QZVX4QZOBVA5DMNPRJOUVJVUNR6CCCXS/>
List-Archive: <https://lists.opensuse.org/archives/list/users@lists.opensuse.org/>
List-Help: <mailto:users-request@lists.opensuse.org?subject=help>
List-Owner: <mailto:users-owner@lists.opensuse.org>
List-Post: <mailto:users@lists.opensuse.org>
List-Subscribe: <mailto:users-join@lists.opensuse.org>
List-Unsubscribe: <mailto:users-leave@lists.opensuse.org>

[body omitted]

M email/qmail-deliver.pl => email/qmail-deliver.pl +13 -3
@@ 41,7 41,7 @@ GetOptions('help' => \$help, 'man' => \$man, 'usage' => \$usage,
	   'whitelist=s' => \@whitelists,
	   'blacklist=s' => \@blacklists,
	   make_forged_local_pushers
	       (qw(network-prefix=s add-local=s relay-ip=s)))
	       (qw(network-prefix=s list-host=s add-local=s relay-ip=s)))
    or pod2usage(2);
pod2usage(2) if $usage;
pod2usage(1) if $help;


@@ 452,7 452,7 @@ sub deliver_message {
    if (-r '.qmail-spam') {
	my $file;
	if (address_forged_p($header)) {
	    # Found spam; redirect it.
	    # Found spam with a forged address; redirect it.
	    $qmail_file = -r '.qmail-forged' ? '.qmail-forged' : '.qmail-spam';
	}
	elsif (@whitelists || @blacklists || @host_deadlists || @deadlists


@@ 506,6 506,7 @@ qmail-deliver.pl - deliver mail like qmail-local, with whitelists/blacklists
    qmail-deliver.pl [ --verbose ... ] [ --[no]test ] [ --redeliver ]
    		     [ --add-local=<name> ... ] [ --[no-]use-delivered-to ]
		     [ --network-prefix=<IP> ... ] [ relay-ip=<IP> ... ]
                     [ --list-host=<host-name> ... ]
		     [ --whitelist=<file> ... ] [ --blacklist=<file> ... ]
		     [ --deadlist=<file> ... ] [ --host-deadlist=<file> ... ]



@@ 535,7 536,8 @@ F<.qmail-dead> file as the destination for emails with matching addresses,
though they may just contain the line
F</dev/null> in order to discard matching emails.

The L</--add-local>, L</--network-prefix>, and L</--relay-ip> options
The L</--add-local>, L</--network-prefix>, L</--list-host>,
and L</--relay-ip> options
are for the C<forged-local-address.pl> script; if any of these three
is supplied (and all may be repeated), then C<forged-local-address.pl>
is used to detect whether the sender has spoofed a local address


@@ 643,6 645,14 @@ list, then the message is sent to F<.qmail-dead> if that exists, else
to F<.qmail-spam>.
See L</Message processing> for details.

=item B<--list-host>

Specifies a single domain name to add to the set of mailing list
servers.  Any host that has an rDNS name with this as a suffix is
considered valid as a mailing list host, so emails using one of our
addresses coming from such a relay are not considered forgeries.
This is passed verbatim to C<forged-local-address.pl>.

=item B<--man>

Prints the full documentation in the Unix `manpage' style.

M email/test-qmail-deliver.pl => email/test-qmail-deliver.pl +19 -1
@@ 8,7 8,7 @@
use strict;
use warnings;

use Test::More tests => 72;
use Test::More tests => 77;

### Subroutines.



@@ 45,12 45,18 @@ my %boolean_option_p
       verbose_p => " --verbose");
my %keyword_option_p
    = (network_prefix => '--network-prefix',
       list_host => '--list-host',
       blacklist => '--blacklist',
       whitelist => '--whitelist',
       deadlist => '--deadlist',
       host_deadlist => '--host-deadlist');

sub deliver_one {
    # After running ./qmail-deliver.pl with @options, which may include an
    # expected exit_code, we expect to have $expected_messages count of
    # messages in $maildir.  There are two "ok" calls, one for the exit code
    # (and we warn if that fails) and one for the message count (and we die if
    # that fails, because misdelivery will screw up subsequent tests).
    my ($message_file, $maildir, $expected_messages, @options) = @_;
    my %options = @options;
    my $exit_code = ($options{exit_code} || 0) << 8;


@@ 240,6 246,18 @@ deliver_one('relay-test.text', 'Maildir', 8,
	    whitelist => 'list.tmp',
	    network_prefix => '209.85.128.0/17');

## Test the --list-host option.
deliver_one('mailing-list-1.text', 'Maildir', 9,
	    sender => 'users-bounces@opensuse.org',
	    list_host => 'opensuse.org',
	    network_prefix => '209.85.128.0/17');
deliver_one('viagra-inc.text', 'spam', 9,
	    sender => 'rogerryals@hcsmail.com',
	    list_host => 'opensuse.org',
	    network_prefix => '209.85.128.0/17');
ok(9 == count_messages(),
   "--list-host makes no difference if not delivered from that system.");

## Tidy up.
clean_up();


M makefile => makefile +3 -0
@@ 175,6 175,9 @@ test-new-forged-address:
	SENDER=whoever@wherever.com email/forged-local-address.pl \
		${rgrjr-config-options} < email/spam-7.text
test-nonforged-addresses:
	SENDER=users-bounces@opensuse.org \
	    email/forged-local-address.pl --list-host opensuse.org --not \
		${rgrjr-config-options} < email/mailing-list-1.text
	SENDER=perl6-internals-return-48162-etc@perl.org \
	    email/forged-local-address.pl --sender-re='@perl.org$$' --not \
		${rgrjr-config-options} < email/perl6-non-spam.text