diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ecf66f8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +!Build/ +.last_cover_stats +/META.yml +/META.json +/MYMETA.* +*.o +*.pm.tdy +*.bs + +# Devel::Cover +cover_db/ + +# Devel::NYTProf +nytprof.out + +# Dizt::Zilla +/.build/ + +# Module::Build +_build/ +Build +Build.bat + +# Module::Install +inc/ + +# ExtUtils::MakeMaker +/blib/ +/_eumm/ +/*.gz +/Makefile +/Makefile.old +/MANIFEST.bak +/pm_to_blib +/*.zip diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/dmarc.cf b/dmarc.cf new file mode 100644 index 0000000..c7c1692 --- /dev/null +++ b/dmarc.cf @@ -0,0 +1,14 @@ +header GB_DMARC_REJECT eval:check_dmarc_reject() +describe GB_DMARC_REJECT Dmarc reject policy +score GB_DMARC_REJECT 10 +priority GB_DMARC_REJECT 1000 + +header GB_DMARC_QUAR eval:check_dmarc_quar() +describe GB_DMARC_NONE Dmarc quarantine policy +score GB_DMARC_QUAR 3 +priority GB_DMARC_QUAR 1000 + +header GB_DMARC_NONE eval:check_dmarc_none() +describe GB_DMARC_NONE Dmarc none policy +score GB_DMARC_NONE 1 +priority GB_DMARC_NONE 1000 diff --git a/dmarc.pm b/dmarc.pm new file mode 100644 index 0000000..7333cf1 --- /dev/null +++ b/dmarc.pm @@ -0,0 +1,193 @@ +# +# Author: Giovanni Bechis +# Copyright 2020 Giovanni Bechis +# +# <@LICENSE> +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to you under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# + +=head1 NAME + +Mail::SpamAssassin::Plugin::Dmarc - check Dmarc policy + +=head1 SYNOPSIS + + loadplugin Mail::SpamAssassin::Plugin::Dmarc + + ifplugin Mail::SpamAssassin::Plugin::Dmarc + header DMARC_REJECT eval:check_dmarc_reject() + describe DMARC_REJECT Dmarc reject policy + endif + +=head1 DESCRIPTION + +This plugin checks if emails matches Dmarc policy. + +=cut + +package Mail::SpamAssassin::Plugin::Dmarc; + +use strict; +use warnings; +use re 'taint'; + +my $VERSION = 0.1; + +use Mail::SpamAssassin; +use Mail::SpamAssassin::Plugin; + +our @ISA = qw(Mail::SpamAssassin::Plugin); + +use constant HAS_DMARC => eval { require Mail::DMARC::PurePerl; }; + +BEGIN +{ + eval{ + import Mail::DMARC::PurePerl + }; +} + +sub dbg { Mail::SpamAssassin::Plugin::dbg ("Dmarc: @_"); } + +# XXX copied from "FromNameSpoof" plugin, put into util ? +sub uri_to_domain { + my ($self, $domain) = @_; + + return unless defined $domain; + + if ($Mail::SpamAssassin::VERSION <= 3.004000) { + Mail::SpamAssassin::Util::uri_to_domain($domain); + } else { + $self->{main}->{registryboundaries}->uri_to_domain($domain); + } +} + +sub new { + my ($class, $mailsa) = @_; + + $class = ref($class) || $class; + my $self = $class->SUPER::new($mailsa); + bless ($self, $class); + + $self->set_config($mailsa->{conf}); + $self->register_eval_rule("check_dmarc_reject"); + $self->register_eval_rule("check_dmarc_quarantine"); + $self->register_eval_rule("check_dmarc_none"); + + return $self; +} + +sub set_config { +} + +sub check_dmarc_reject { + my ($self,$pms,$name) = @_; + + my @tags = ('DKIMCHECKDONE'); + + $pms->action_depends_on_tags(\@tags, + sub { my($pms, @args) = @_; + $self->_check_dmarc(@_); + if(($self->{dmarc_result} eq 'fail') and ($self->{dmarc_policy} eq 'reject')) { + $pms->got_hit($pms->get_current_eval_rule_name(), ""); + return 1; + } + } + ); + return 0; +} + +sub check_dmarc_quarantine { + my ($self,$pms,$name) = @_; + + my @tags = ('DKIMCHECKDONE'); + + $pms->action_depends_on_tags(\@tags, + sub { my($pms, @args) = @_; + $self->_check_dmarc(@_); + if(($self->{dmarc_result} eq 'fail') and ($self->{dmarc_policy} eq 'quarantine')) { + $pms->got_hit($pms->get_current_eval_rule_name(), ""); + return 1; + } + } + ); + return 0; +} + +sub check_dmarc_none { + my ($self,$pms,$name) = @_; + + my @tags = ('DKIMCHECKDONE'); + + $pms->action_depends_on_tags(\@tags, + sub { my($pms, @args) = @_; + $self->_check_dmarc(@_); + if(($self->{dmarc_result} eq 'fail') and ($self->{dmarc_policy} eq 'none')) { + $pms->got_hit($pms->get_current_eval_rule_name(), ""); + return 1; + } + } + ); + return 0; +} + +sub _check_dmarc { + my ($self,$pms,$name) = @_; + my $spf_status = 'none'; + my $spf_helo_status = 'none'; + my ($dmarc, $lasthop, $result); + + if($self->{dmarc_checked} eq 1) { + return; + } + $dmarc = Mail::DMARC::PurePerl->new(); + $lasthop = $pms->{relays_external}->[0]; + return if (not defined $lasthop->{ip}); + + # XXX handle all spf result codes + $spf_status = 'pass' if ($pms->{spf_pass} eq 1); + $spf_status = 'fail' if ($pms->{spf_fail} eq 1); + $spf_helo_status = 'pass' if ($pms->{spf_helo_pass} eq 1); + $spf_helo_status = 'fail' if ($pms->{spf_helo_fail} eq 1); + + $dmarc->source_ip($lasthop->{ip}); + $dmarc->envelope_to($self->uri_to_domain($pms->get('To:addr'))); + $dmarc->envelope_from($self->uri_to_domain($lasthop->{envfrom})); + $dmarc->header_from($self->uri_to_domain($pms->get('From:addr'))); + $dmarc->dkim($pms->{dkim_verifier}); + $dmarc->spf([ + { + scope => 'mfrom', + domain => "$self->uri_to_domain($lasthop->{envfrom})", + result => "$spf_status", + }, + { + scope => 'helo', + domain => "$lasthop->{lc_helo}", + result => "$spf_helo_status", + }, + ]); + $result = $dmarc->validate(); + + # use Data::Dumper + # dbg("Result: " . Dumper $result); + $self->{dmarc_result} = $result->result; + $self->{dmarc_policy} = $result->published->p; + $self->{dmarc_checked} = 1; +} + +1; diff --git a/dmarc.pre b/dmarc.pre new file mode 100644 index 0000000..743404e --- /dev/null +++ b/dmarc.pre @@ -0,0 +1 @@ +loadplugin Mail::SpamAssassin::Plugin::Dmarc dmarc.pm diff --git a/man/man3p/Mail::SpamAssassin::Plugin::Dmarc.3p b/man/man3p/Mail::SpamAssassin::Plugin::Dmarc.3p new file mode 100644 index 0000000..244abfa --- /dev/null +++ b/man/man3p/Mail::SpamAssassin::Plugin::Dmarc.3p @@ -0,0 +1,155 @@ +.\" Automatically generated by Pod::Man 4.12 (Pod::Simple 3.39) +.\" +.\" Standard preamble: +.\" ======================================================================== +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. +.de Ve \" End verbatim text +.ft R +.fi +.. +.\" Set up some character translations and predefined strings. \*(-- will +.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left +.\" double quote, and \*(R" will give a right double quote. \*(C+ will +.\" give a nicer C++. Capital omega is used to do unbreakable dashes and +.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, +.\" nothing in troff, for use with C<>. +.tr \(*W- +.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' +.ie n \{\ +. ds -- \(*W- +. ds PI pi +. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch +. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch +. ds L" "" +. ds R" "" +. ds C` "" +. ds C' "" +'br\} +.el\{\ +. ds -- \|\(em\| +. ds PI \(*p +. ds L" `` +. ds R" '' +. ds C` +. ds C' +'br\} +.\" +.\" Escape single quotes in literal strings from groff's Unicode transform. +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" +.\" If the F register is >0, we'll generate index entries on stderr for +.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index +.\" entries marked with X<> in POD. Of course, you'll have to process the +.\" output yourself in some meaningful fashion. +.\" +.\" Avoid warning from groff about undefined register 'F'. +.de IX +.. +.nr rF 0 +.if \n(.g .if rF .nr rF 1 +.if (\n(rF:(\n(.g==0)) \{\ +. if \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" +.. +. if !\nF==2 \{\ +. nr % 0 +. nr F 2 +. \} +. \} +.\} +.rr rF +.\" +.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). +.\" Fear. Run. Save yourself. No user-serviceable parts. +. \" fudge factors for nroff and troff +.if n \{\ +. ds #H 0 +. ds #V .8m +. ds #F .3m +. ds #[ \f1 +. ds #] \fP +.\} +.if t \{\ +. ds #H ((1u-(\\\\n(.fu%2u))*.13m) +. ds #V .6m +. ds #F 0 +. ds #[ \& +. ds #] \& +.\} +. \" simple accents for nroff and troff +.if n \{\ +. ds ' \& +. ds ` \& +. ds ^ \& +. ds , \& +. ds ~ ~ +. ds / +.\} +.if t \{\ +. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" +. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' +. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' +. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' +. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' +. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' +.\} +. \" troff and (daisy-wheel) nroff accents +.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' +.ds 8 \h'\*(#H'\(*b\h'-\*(#H' +.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] +.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' +.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' +.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] +.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] +.ds ae a\h'-(\w'a'u*4/10)'e +.ds Ae A\h'-(\w'A'u*4/10)'E +. \" corrections for vroff +.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' +.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' +. \" for low resolution devices (crt and lpr) +.if \n(.H>23 .if \n(.V>19 \ +\{\ +. ds : e +. ds 8 ss +. ds o a +. ds d- d\h'-1'\(ga +. ds D- D\h'-1'\(hy +. ds th \o'bp' +. ds Th \o'LP' +. ds ae ae +. ds Ae AE +.\} +.rm #[ #] #H #V #F C +.\" ======================================================================== +.\" +.IX Title "dmarc 3" +.TH dmarc 3 "2020-04-18" "perl v5.30.2" "User Contributed Perl Documentation" +.\" For nroff, turn off justification. Always turn off hyphenation; it makes +.\" way too many mistakes in technical documents. +.if n .ad l +.nh +.SH "NAME" +Mail::SpamAssassin::Plugin::Dmarc \- check Dmarc policy +.SH "SYNOPSIS" +.IX Header "SYNOPSIS" +.Vb 1 +\& loadplugin Mail::SpamAssassin::Plugin::Dmarc +\& +\& ifplugin Mail::SpamAssassin::Plugin::Dmarc +\& header DMARC_REJECT eval:check_dmarc_reject() +\& describe DMARC_REJECT Dmarc reject policy +\& endif +.Ve +.SH "DESCRIPTION" +.IX Header "DESCRIPTION" +This plugin checks if emails matches Dmarc policy.