user/dev discussion of public-inbox itself
 help / color / mirror / code / Atom feed
From: Eric Wong <e@80x24.org>
To: meta@public-inbox.org
Subject: [PATCH 1/6] implement ListMirror SpamAssassin plugin
Date: Fri, 24 Jun 2016 20:47:13 +0000	[thread overview]
Message-ID: <20160624204718.27540-1-e@80x24.org> (raw)

When running mailing list mirrors, one needs to be careful
spammers do not try to sidestep the list server we want to
mirror from and inject email into our mail directly by setting
the appropriate list headers (e.g. "X-Mailing-List" or
"List-Id").  We trust the top-most Received: header is
the one our own mail server got the mail from.

Bcc:-ing a public mailing list is a very likely indicator of
spam in my experience, so throw in an extra rule mark it.
While public-inbox-mda rejects Bcc: entirely, public-inbox-watch
needs to mirror lists which allow Bcc.

==> list_mirror.cf <==
loadplugin PublicInbox::SaPlugin::ListMirror

ifplugin PublicInbox::SaPlugin::ListMirror
  header LIST_MIRROR_RECEIVED eval:check_list_mirror_received()
  describe LIST_MIRROR_RECEIVED Received does not match trusted list server
  score LIST_MIRROR_RECEIVED 10

  header LIST_MIRROR_BCC eval:check_list_mirror_bcc()
  describe LIST_MIRROR_BCC Mailing list was Bcc-ed
  score LIST_MIRROR_BCC 1
endif

==> ~/.spamassassin/user_prefs <==
ifplugin PublicInbox::SaPlugin::ListMirror
  list_mirror X-Mailing-List git@vger.kernel.org *.kernel.org git@vger.kernel.org
endif
---
 MANIFEST                               |   1 +
 lib/PublicInbox/SaPlugin/ListMirror.pm | 107 +++++++++++++++++++++++++++++++++
 2 files changed, 108 insertions(+)
 create mode 100644 lib/PublicInbox/SaPlugin/ListMirror.pm

diff --git a/MANIFEST b/MANIFEST
index 17a2a31..bc7d54c 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -64,6 +64,7 @@ lib/PublicInbox/NewsWWW.pm
 lib/PublicInbox/ParentPipe.pm
 lib/PublicInbox/ProcessPipe.pm
 lib/PublicInbox/Qspawn.pm
+lib/PublicInbox/SaPlugin/ListMirror.pm
 lib/PublicInbox/Search.pm
 lib/PublicInbox/SearchIdx.pm
 lib/PublicInbox/SearchMsg.pm
diff --git a/lib/PublicInbox/SaPlugin/ListMirror.pm b/lib/PublicInbox/SaPlugin/ListMirror.pm
new file mode 100644
index 0000000..1010188
--- /dev/null
+++ b/lib/PublicInbox/SaPlugin/ListMirror.pm
@@ -0,0 +1,107 @@
+# Copyright (C) 2016 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+
+# Rules useful for running a mailing list mirror.  We want to:
+# * ensure Received: headers are really from the list mail server
+#   users expect.  This is to prevent malicious users from
+#   injecting spam into mirrors without going through the expected
+#   server
+# * flag messages where the mailing list is Bcc:-ed since it is
+#   common for spam to have wrong or non-existent To:/Cc: headers.
+
+package PublicInbox::SaPlugin::ListMirror;
+use strict;
+use warnings;
+use base qw(Mail::SpamAssassin::Plugin);
+
+# constructor: register the eval rules
+sub new {
+	my ($class, $mail) = @_;
+
+	# some boilerplate...
+	$class = ref($class) || $class;
+	my $self = $class->SUPER::new($mail);
+	bless $self, $class;
+	$mail->{conf}->{list_mirror_check} = [];
+	$self->register_eval_rule('check_list_mirror_received');
+	$self->register_eval_rule('check_list_mirror_bcc');
+	$self->set_config($mail->{conf});
+	$self;
+}
+
+sub check_list_mirror_received {
+	my ($self, $pms) = @_;
+	my $recvd = $pms->get('Received') || '';
+	$recvd =~ s/\n.*\z//s;
+
+	foreach my $cfg (@{$pms->{conf}->{list_mirror_check}}) {
+		my ($hdr, $hval, $host_re, $addr_re) = @$cfg;
+		my $v = $pms->get($hdr) or next;
+		chomp $v;
+		next if $v ne $hval;
+		return 1 if $recvd !~ $host_re;
+	}
+
+	0;
+}
+
+sub check_list_mirror_bcc {
+	my ($self, $pms) = @_;
+	my $tocc = $pms->get('ToCc');
+
+	foreach my $cfg (@{$pms->{conf}->{list_mirror_check}}) {
+		my ($hdr, $hval, $host_re, $addr_re) = @$cfg;
+		defined $addr_re or next;
+		my $v = $pms->get($hdr) or next;
+		chomp $v;
+		next if $v ne $hval;
+		return 1 if !$tocc || $tocc !~ $addr_re;
+	}
+
+	0;
+}
+
+# list_mirror HEADER HEADER_VALUE HOSTNAME_GLOB [LIST_ADDRESS]
+# list_mirror X-Mailing-List git@vger.kernel.org *.kernel.org
+# list_mirror List-Id <foo.example.org> *.example.org foo@example.org
+sub config_list_mirror {
+	my ($self, $key, $value, $line) = @_;
+
+	defined $value or
+		return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
+
+	my ($hdr, $hval, $host_glob, @extra) = split(/\s+/, $value);
+	my $addr = shift @extra;
+
+	if (defined $addr) {
+		$addr !~ /\@/ and
+			return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+		$addr = join('|', map { quotemeta } split(/,/, $addr));
+		$addr = qr/\b$addr\b/i;
+	}
+
+	@extra and return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+
+	defined $host_glob or
+		return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
+
+	my %patmap = ('*' => '\S+', '?' => '.', '[' => '[', ']' => ']');
+	$host_glob =~ s!(.)!$patmap{$1} || "\Q$1"!ge;
+	my $host_re = qr/\A\s*from\s+$host_glob(?:\s|$)/si;
+
+	push @{$self->{list_mirror_check}}, [ $hdr, $hval, $host_re, $addr ];
+}
+
+sub set_config {
+	my ($self, $conf) = @_;
+	my @cmds;
+	push @cmds, {
+		setting => 'list_mirror',
+		default => '',
+		type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
+		code => *config_list_mirror,
+	};
+	$conf->{parser}->register_commands(\@cmds);
+}
+
+1;

             reply	other threads:[~2016-06-24 20:47 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-06-24 20:47 Eric Wong [this message]
2016-06-24 20:47 ` [PATCH 2/6] split out spamcheck/spamc to its own module Eric Wong
2016-06-24 20:47 ` [PATCH 3/6] document Filesys::Notify::Simple dependency Eric Wong
2016-06-24 20:47 ` [PATCH 4/6] watch_maildir: rename _check_spam => _remove_spam Eric Wong
2016-06-24 20:47 ` [PATCH 5/6] watch_maildir: implement optional spam checking Eric Wong
2016-06-24 20:47 ` [PATCH 6/6] watch_maildir: ignore Trash and Drafts, support Dovecot Eric Wong

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://public-inbox.org/README

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20160624204718.27540-1-e@80x24.org \
    --to=e@80x24.org \
    --cc=meta@public-inbox.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://80x24.org/public-inbox.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).