diff --git a/opendmarc/opendmarc-config.h b/opendmarc/opendmarc-config.h index 7ba394b..28f605e 100644 --- a/opendmarc/opendmarc-config.h +++ b/opendmarc/opendmarc-config.h @@ -36,6 +36,7 @@ struct configdef dmarcf_config[] = { "IgnoreHosts", CONFIG_TYPE_STRING, FALSE }, { "IgnoreMailFrom", CONFIG_TYPE_STRING, FALSE }, { "MilterDebug", CONFIG_TYPE_INTEGER, FALSE }, + { "OverrideMLM", CONFIG_TYPE_STRING, FALSE }, { "PidFile", CONFIG_TYPE_STRING, FALSE }, { "PublicSuffixList", CONFIG_TYPE_STRING, FALSE }, { "RecordAllMessages", CONFIG_TYPE_BOOLEAN, FALSE }, diff --git a/opendmarc/opendmarc.c b/opendmarc/opendmarc.c index ba04312..07e089d 100644 --- a/opendmarc/opendmarc.c +++ b/opendmarc/opendmarc.c @@ -168,6 +168,7 @@ struct dmarcf_config char * conf_ignorelist; char ** conf_trustedauthservids; char ** conf_ignoredomains; + struct list * conf_overridemlm; }; /* LIST -- basic linked list of strings */ @@ -1221,6 +1222,18 @@ dmarcf_config_load(struct config *data, struct dmarcf_config *conf, if (str != NULL) dmarcf_mkarray(str, &conf->conf_ignoredomains); + str = NULL; + (void) config_get(data, "OverrideMLM", &str, sizeof str); + if (str != NULL) + { + if (!dmarcf_loadlist(str, &conf->conf_overridemlm)) + { + fprintf(stderr, + "%s: can't load override MLM list from %s: %s\n", + progname, str, strerror(errno)); + } + } + (void) config_get(data, "AuthservIDWithJobID", &conf->conf_authservidwithjobid, sizeof conf->conf_authservidwithjobid); @@ -2982,30 +2995,45 @@ mlfi_eom(SMFICTX *ctx) case DMARC_POLICY_REJECT: /* Explicit reject */ aresult = "fail"; - if (conf->conf_rejectfail && random() % 100 < pct) + if (conf->conf_overridemlm != NULL && + (dmarcf_checkhost(cc->cctx_host, conf->conf_overridemlm) || + (dmarcf_checkip((struct sockaddr *)&cc->cctx_ip, conf->conf_overridemlm)))) { - snprintf(replybuf, sizeof replybuf, - "rejected by DMARC policy for %s", pdomain); - - status = dmarcf_setreply(ctx, DMARC_REJECT_SMTP, - DMARC_REJECT_ESC, replybuf); - if (status != MI_SUCCESS && conf->conf_dolog) + if (conf->conf_dolog) { - syslog(LOG_ERR, "%s: smfi_setreply() failed", - dfc->mctx_jobid); + syslog(LOG_INFO, "%s: overriding policy for mail from %s: MLM", + dfc->mctx_jobid, dfc->mctx_fromdomain); } - - ret = SMFIS_REJECT; - result = DMARC_RESULT_REJECT; + ret = SMFIS_ACCEPT; + result = DMARC_RESULT_OVRD_MAILING_LIST; } - - if (conf->conf_copyfailsto != NULL) + else { - status = dmarcf_addrcpt(ctx, conf->conf_copyfailsto); - if (status != MI_SUCCESS && conf->conf_dolog) + if (conf->conf_rejectfail && random() % 100 < pct) + { + snprintf(replybuf, sizeof replybuf, + "rejected by DMARC policy for %s", pdomain); + + status = dmarcf_setreply(ctx, DMARC_REJECT_SMTP, + DMARC_REJECT_ESC, replybuf); + if (status != MI_SUCCESS && conf->conf_dolog) + { + syslog(LOG_ERR, "%s: smfi_setreply() failed", + dfc->mctx_jobid); + } + + ret = SMFIS_REJECT; + result = DMARC_RESULT_REJECT; + } + + if (conf->conf_copyfailsto != NULL) { - syslog(LOG_ERR, "%s: smfi_addrcpt() failed", - dfc->mctx_jobid); + status = dmarcf_addrcpt(ctx, conf->conf_copyfailsto); + if (status != MI_SUCCESS && conf->conf_dolog) + { + syslog(LOG_ERR, "%s: smfi_addrcpt() failed", + dfc->mctx_jobid); + } } } @@ -3014,30 +3042,45 @@ mlfi_eom(SMFICTX *ctx) case DMARC_POLICY_QUARANTINE: /* Explicit quarantine */ aresult = "fail"; - if (conf->conf_rejectfail && random() % 100 < pct) + if (conf->conf_overridemlm != NULL && + (dmarcf_checkhost(cc->cctx_host, conf->conf_overridemlm) || + (dmarcf_checkip((struct sockaddr *)&cc->cctx_ip, conf->conf_overridemlm)))) { - snprintf(replybuf, sizeof replybuf, - "quarantined by DMARC policy for %s", - pdomain); - - status = smfi_quarantine(ctx, replybuf); - if (status != MI_SUCCESS && conf->conf_dolog) + if (conf->conf_dolog) { - syslog(LOG_ERR, "%s: smfi_quarantine() failed", - dfc->mctx_jobid); + syslog(LOG_INFO, "%s: overriding policy for mail from %s: MLM", + dfc->mctx_jobid, dfc->mctx_fromdomain); } - ret = SMFIS_ACCEPT; - result = DMARC_RESULT_QUARANTINE; + result = DMARC_RESULT_OVRD_MAILING_LIST; } - - if (conf->conf_copyfailsto != NULL) + else { - status = dmarcf_addrcpt(ctx, conf->conf_copyfailsto); - if (status != MI_SUCCESS && conf->conf_dolog) + if (conf->conf_rejectfail && random() % 100 < pct) + { + snprintf(replybuf, sizeof replybuf, + "quarantined by DMARC policy for %s", + pdomain); + + status = smfi_quarantine(ctx, replybuf); + if (status != MI_SUCCESS && conf->conf_dolog) + { + syslog(LOG_ERR, "%s: smfi_quarantine() failed", + dfc->mctx_jobid); + } + + ret = SMFIS_ACCEPT; + result = DMARC_RESULT_QUARANTINE; + } + + if (conf->conf_copyfailsto != NULL) { - syslog(LOG_ERR, "%s: smfi_addrcpt() failed", - dfc->mctx_jobid); + status = dmarcf_addrcpt(ctx, conf->conf_copyfailsto); + if (status != MI_SUCCESS && conf->conf_dolog) + { + syslog(LOG_ERR, "%s: smfi_addrcpt() failed", + dfc->mctx_jobid); + } } } diff --git a/opendmarc/opendmarc.conf.5.in b/opendmarc/opendmarc.conf.5.in index bdf2550..9ee16ae 100644 --- a/opendmarc/opendmarc.conf.5.in +++ b/opendmarc/opendmarc.conf.5.in @@ -190,6 +190,14 @@ Sets the debug level to be requested from the milter library. The default is 0. .TP +.I OverrideMLM (string) +Specifies the path to a file that contains a list of hostnames, IP +addresses, and/or CIDR expressions identifying hosts that run +mailing lists. Mails from these systems will be accepted even if +all DMARC tests fail. Such cases will be reported as "override/ +reason: MLM" + +.TP .I PidFile (string) Specifies the path to a file that should be created at process start containing the process ID. diff --git a/opendmarc/opendmarc.conf.sample b/opendmarc/opendmarc.conf.sample index 97b210f..fbfa49d 100644 --- a/opendmarc/opendmarc.conf.sample +++ b/opendmarc/opendmarc.conf.sample @@ -212,6 +212,17 @@ # # MilterDebug 0 +## OverrideMLM (path) +## default (none) +## +## Specifies the path to a file that contains a list of hostnames, IP +## addresses, and/or CIDR expressions identifying hosts that run +## mailing lists. Mails from these systems will be accepted even if +## all DMARC tests fail. Such cases will be reported as "override/ +## reason: MLM" +# +# OverrideMLM /usr/local/etc/opendmarc/overrideMLM.conf + ## PidFile path ## default (none) ## diff --git a/opendmarc/opendmarc.h b/opendmarc/opendmarc.h index c1d6593..f9b1e0b 100644 --- a/opendmarc/opendmarc.h +++ b/opendmarc/opendmarc.h @@ -52,6 +52,12 @@ #define DMARC_RESULT_ACCEPT 2 #define DMARC_RESULT_TEMPFAIL 3 #define DMARC_RESULT_QUARANTINE 4 +#define DMARC_RESULT_OVRD_FORWARDED 5 +#define DMARC_RESULT_OVRD_SAMPLED_OUT 6 +#define DMARC_RESULT_OVRD_TRUSTED_FORWARDER 7 +#define DMARC_RESULT_OVRD_MAILING_LIST 8 +#define DMARC_RESULT_OVRD_LOCAL_POLICY 9 +#define DMARC_RESULT_OVRD_OTHER 10 /* prototypes, etc., exported for test.c */ extern char *progname; diff --git a/reports/opendmarc-reports.in b/reports/opendmarc-reports.in index 2da1c31..a489c95 100755 --- a/reports/opendmarc-reports.in +++ b/reports/opendmarc-reports.in @@ -91,6 +91,8 @@ my $ipaddr; my $fromdomain; my $envdomain; my $dkimdomain; +my $reason; +my $comment; my $repdest; @@ -609,6 +611,8 @@ foreach (@$domainset) while ($dbi_a = $dbi_s->fetchrow_arrayref()) { undef $msgid; + undef $reason; + undef $comment; if (defined($dbi_a->[0])) { @@ -656,6 +660,12 @@ foreach (@$domainset) case 1 { $dispstr = "reject"; } case 2 { $dispstr = "none"; } case 4 { $dispstr = "quarantine"; } + case 5 { $dispstr = "none"; $reason = "forwarded"; } + case 6 { $dispstr = "none"; $reason = "sampled_out"; } + case 7 { $dispstr = "none"; $reason = "trusted_forwarder"; } + case 8 { $dispstr = "none"; $reason = "mailing_list"; } + case 9 { $dispstr = "none"; $reason = "local_policy"; $comment = ""; } + case 10 { $dispstr = "none"; $reason = "other"; $comment = ""; } else { $dispstr = "unknown"; } } @@ -697,6 +707,16 @@ foreach (@$domainset) print $tmpout " $dispstr\n"; print $tmpout " $align_dkimstr\n"; print $tmpout " $align_spfstr\n"; + if (defined($reason)) + { + print $tmpout " \n"; + print $tmpout " $reason\n"; + if (defined($comment)) + { + print $tmpout " $comment\n"; + } + print $tmpout " \n"; + } print $tmpout " \n"; print $tmpout " \n"; print $tmpout " \n";