From 98878214415112cfb9400151e537074ceb09a23e Mon Sep 17 00:00:00 2001 From: Dan Mahoney Date: Thu, 23 Jul 2026 01:18:54 -0700 Subject: [PATCH] feat: RFC 9990 generator/pass-disposition + RFC 9989 obsolete-tag cleanup - report_metadata now includes , identifying the report-generating software, matching the string already used for --version output and the X-Mailer header. - opendmarc-reports strips the obsolete "!NNNk" RUA size-suffix (obs-dmarc-report-size) from destination addresses but no longer enforces it as a per-destination size cap, per RFC 9989's ABNF ("reporters should ignore" it). - Removed rf=/ri= parsing, the now-dead DMARC_POLICY_T fields, and the orphaned (never publicly declared) opendmarc_policy_fetch_rf() -- both tags were removed from the DMARC record format by RFC 9989. - ActionDispositionType now emits "pass" (RFC 9990 S3.1.1.9) alongside none/quarantine/reject: new DMARC_RESULT_PASS is set when alignment passed under an enforcing (reject/quarantine) policy, keyed off the unadjusted published p/sp so t=y step-down doesn't affect it, per the existing rule that t= doesn't affect report generation. --- DMARCBIS-REMAINING-WORK.md | 64 +++++++---- libopendmarc/dmarc.h.in | 3 - libopendmarc/docs/Makefile.am | 1 - libopendmarc/docs/index.html | 4 - .../docs/opendmarc_policy_fetch_rf.html | 85 --------------- libopendmarc/opendmarc_internal.h | 2 - libopendmarc/opendmarc_policy.c | 103 ------------------ libopendmarc/tests/test_dmarc_parse.c | 6 +- opendmarc/opendmarc.c | 22 ++++ opendmarc/opendmarc.h | 1 + reports/opendmarc-reports.in | 30 ++--- 11 files changed, 74 insertions(+), 247 deletions(-) delete mode 100644 libopendmarc/docs/opendmarc_policy_fetch_rf.html diff --git a/DMARCBIS-REMAINING-WORK.md b/DMARCBIS-REMAINING-WORK.md index 36eb0897..8ad4e55d 100644 --- a/DMARCBIS-REMAINING-WORK.md +++ b/DMARCBIS-REMAINING-WORK.md @@ -20,9 +20,50 @@ specifically (all resolved) and are not duplicated here. - **RFC 9989 `t=`/`pct=`**: `t=` parsing, fetch accessor, and enforcement step-down (reject->quarantine->none). `pct=` deliberately kept for POLA, with `DMARCbisIgnorePct` for operators who want strict compliance. (#434) +- **`!NNNk` RUA size-suffix syntax**: RFC 9989's ABNF marks this obsolete + (`obs-dmarc-uri`/`obs-dmarc-report-size`) and says reporters MUST ignore + it. Unlike the `pct=` POLA call above, `opendmarc-reports` now drops the + enforcement PR #392 had restored -- the suffix is still stripped from + the destination address so delivery isn't broken, but it no longer + overrides `report_maxbytes`. - **RFC 9990 aggregate reporting**: `np`/`testing`/`discovery_method` in `policy_published`, namespace bumped to `dmarc-2.0`, `` removed (branch `feat/rfc9990-aggregate-reporting`). +- **RFC 9990 `generator` element** (S3.1.1.3): `report_metadata` now + includes `` identifying the report-generating software + (`$progname v$version`), matching the string already used for + `--version` output and the `X-Mailer` header. (Unrelated: the separate + `contrib/dmarc-report-totext.pl` *consumer* tool already parses this + field from other senders' reports — that's reading, not writing.) +- **`rf=`/`ri=` cleanup**: both tags were removed from the DMARC record + format by RFC 9989. Removed the parsing (`opendmarc_policy.c`), the + now-dead `DMARC_POLICY_T` fields (`rf`, `ri`), the orphaned + `opendmarc_policy_fetch_rf()` (never declared in the public header, so + unreachable by consumers anyway) and its stale doc page, and the + `RF=`/`RI=` lines from `opendmarc_policy_to_buf()`'s dump. Unrecognized + tags are already silently ignored by the parser per spec, so `rf=`/`ri=` + now fall through to that same path rather than being validated. + Verified via a real `./configure && make check` on quark (this sandbox + lacks milter headers) — all 10 `libopendmarc` tests pass, including + `test_dmarc_parse`'s negative cases updated to expect `DMARC_PARSE_OKAY` + now that these are unknown tags rather than validated ones. +- **`pass` disposition value** (S3.1.1.9): `ActionDispositionType` now + emits `pass` alongside `none`/`quarantine`/`reject`. New + `DMARC_RESULT_PASS` (`opendmarc.h`) is set in `opendmarc.c`'s + `enforce_policy` switch specifically when alignment passed *and* the + unadjusted published `p`/`sp` (whichever applies) was `reject` or + `quarantine` -- under `p=none`/`sp=none` the disposition stays `none`, + since RFC 9990 reserves `pass` for "passing DMARC w/enforcing policy". + Deliberately keyed off the unadjusted policy rather than the + `t=y`-downgraded `enforce_policy`, matching the existing rule that `t=` + doesn't affect report generation. Also added a `dis=pass` case to the + Authentication-Results `dis=` tag mapping for consistency with the + existing `dmarc=pass` `aresult`. Flows through `messages.disp` (a plain + unconstrained `TINYINT`, so no schema migration) into + `opendmarc-reports.in`'s disposition mapping. Verified with a clean + `./configure && make check` on quark: full project builds with no + warnings, `libopendmarc` suite still 10/10 (unaffected, but a good + regression signal since this touches the same enforcement path). - **RFC 9991 failure reporting**: `Identity-Alignment` and `DKIM-Domain`/`-Identity`/`-Selector` ARF headers, `ruf=` external destination verification + rate-limiting in `opendmarc-reports @@ -56,18 +97,10 @@ specifically (all resolved) and are not duplicated here. ### RFC 9990 aggregate reporting -- **`pass` disposition value** (S3.1.1.9): `ActionDispositionType` now - includes `pass` (message passed DMARC under an *enforcing* policy) - alongside `none`/`quarantine`/`reject`. Current code only ever emits the - original three. Not touched by the `np`/`testing`/`discovery_method` work. - **`policy_test_mode` reason type** (S3.1.6): a `` value a report record should carry when `t=y` caused a policy step-down. Directly adjacent to work already done — `t=` enforcement and `` in `policy_published` both shipped, but this per-record annotation didn't. -- **`generator` element** (S3.1.1.3): identifies the report-generating - software. Not implemented in `opendmarc-reports`. (Unrelated: the - separate `contrib/dmarc-report-totext.pl` *consumer* tool already parses - this field from other senders' reports — that's reading, not writing.) - **`error` element** (S3.1.1.3/S3.1.5): describes processing errors encountered while evaluating the DMARC Policy Record. Not implemented. - **DKIM signature priority + 100-signature cap** (S3.1.3): defines which @@ -77,10 +110,6 @@ specifically (all resolved) and are not duplicated here. - **Extension mechanism** (S3.2, S5): `` at file level, namespaced elements at record level. Low priority — only matters if extensions are actually adopted by report consumers. -- **`rf=`/`ri=` cleanup**: both tags were removed from the DMARC record - format by RFC 9989. OpenDMARC still parses them into unused - `DMARC_POLICY_T` fields (`rf`, `ri`). Minor; safe to remove. - ### RFC 9991 failure reporting Both RFC 6591 fields originally tracked here (`DKIM-Canonicalized-Header`/ @@ -135,14 +164,3 @@ in this section. startup instead of a compile error until someone actually parses a config file containing it, which apparently hadn't happened for three of these four since they were introduced. - -### Open decision, not just missing code - -- **`!NNNk` RUA size-suffix syntax**: RFC 9990 Appendix C says this syntax - is obsolete and receivers (i.e. OpenDMARC, acting as report generator) - MUST ignore it. The restored PR #392 code (`check_size_restriction`) - actively parses and enforces it, faithfully reproducing pre-regression - behavior rather than the RFC 9990 text. Same shape of question as the - `pct=` POLA decision: keep honoring it for senders who rely on it, or - drop it now that it's back in front of you. Needs an explicit call, not - a default. diff --git a/libopendmarc/dmarc.h.in b/libopendmarc/dmarc.h.in index 247bc339..f2922fe9 100644 --- a/libopendmarc/dmarc.h.in +++ b/libopendmarc/dmarc.h.in @@ -47,9 +47,6 @@ extern "C" { #define DMARC_RECORD_P_NONE ('n') /* p and sp */ #define DMARC_RECORD_P_QUARANTINE ('q') /* p and sp */ #define DMARC_RECORD_P_REJECT ('r') /* p and sp */ -#define DMARC_RECORD_RF_UNSPECIFIED (0x0) /* rf, a bitmap */ -#define DMARC_RECORD_RF_AFRF (0x1) /* rf, a bitmap */ -#define DMARC_RECORD_RF_IODEF (0x2) /* rf, a bitmap */ #define DMARC_RECORD_FO_UNSPECIFIED (0x0) /* fo, a bitmap */ #define DMARC_RECORD_FO_0 (0x1) /* fo, a bitmap */ #define DMARC_RECORD_FO_1 (0x2) /* fo, a bitmap */ diff --git a/libopendmarc/docs/Makefile.am b/libopendmarc/docs/Makefile.am index dca980ff..3d1aebd7 100644 --- a/libopendmarc/docs/Makefile.am +++ b/libopendmarc/docs/Makefile.am @@ -13,7 +13,6 @@ dist_doc_DATA = index.html \ opendmarc_policy_fetch_fo.html \ opendmarc_policy_fetch_p.html \ opendmarc_policy_fetch_pct.html \ - opendmarc_policy_fetch_rf.html \ opendmarc_policy_fetch_rua.html \ opendmarc_policy_fetch_ruf.html \ opendmarc_policy_fetch_sp.html \ diff --git a/libopendmarc/docs/index.html b/libopendmarc/docs/index.html index bb90f1fd..6bb5312b 100644 --- a/libopendmarc/docs/index.html +++ b/libopendmarc/docs/index.html @@ -160,10 +160,6 @@

Functions

opendmarc_policy_fetch_fo() Fetch a bitmap that shows how/when that RUF reports should be sent. - - opendmarc_policy_fetch_rf() - Fetch a bitmap that shows which format (arfrf versus iodef) should be used. - opendmarc_policy_fetch_alignment() Fetch the result of SPF and DKIM alignment checks. diff --git a/libopendmarc/docs/opendmarc_policy_fetch_rf.html b/libopendmarc/docs/opendmarc_policy_fetch_rf.html deleted file mode 100644 index 4dd67851..00000000 --- a/libopendmarc/docs/opendmarc_policy_fetch_rf.html +++ /dev/null @@ -1,85 +0,0 @@ - -opendmarc_policy_fetch_rf() - - -

opendmarc_policy_fetch_rf()

-

[back to index]

- - - - - - - - - - - - - - - - - - - -
SYNOPSIS -
-#include <dmarc.h>
-OPENDMARC_STATUS_T opendmarc_policy_fetch_rf(
-	DMARC_POLICY_T *pctx,
-	int *rf
-);
-
-Query the library to find the rf= value from the DMARC record. -
DESCRIPTION - - - - -
Called Whenopendmarc_policy_fetch_rf() - is called after the DMARC record has been fetched and parsed. -
- - -
ARGUMENTS - - - - - - -
ArgumentDescription
pctxThe address of a structure of type - DMARC_POLICY_T as returned from opendmarc_policy_connect_init(). -
rfThe address of an integer to receive the value following the rf=, a value (a bitmap) of: -
DMARC_RECORD_RF_UNSPECIFIED if the rf= was absent or if no ruf= was specified. -
DMARC_RECORD_RF_AFRF if rf=afrf. -
DMARC_RECORD_RF_IODEF if rf=iodef. -
-
RETURN VALUES -
    -
  • DMARC_PARSE_OKAY -- On success. -
  • DMARC_PARSE_ERROR_EMPTY -- The pctp pointer was NULL. -
  • DMARC_PARSE_ERROR_NULL_CTX -- If you passed in a rf pointer value that was NULL. -
-
NOTES - -
- -
- -Copyright (c) 2012, The Trusted Domain Project. All rights reserved. - -
-By using this file, you agree to the terms and conditions set -forth in the license. -
- - diff --git a/libopendmarc/opendmarc_internal.h b/libopendmarc/opendmarc_internal.h index dbb1f8e5..72c62ebc 100644 --- a/libopendmarc/opendmarc_internal.h +++ b/libopendmarc/opendmarc_internal.h @@ -174,8 +174,6 @@ typedef struct dmarc_policy_t { int psd; /* RFC 9989: DMARC_RECORD_PSD_* */ int t; /* RFC 9989: DMARC_RECORD_T_*, test mode */ int pct; - int rf; - uint32_t ri; int rua_cnt; u_char ** rua_list; int ruf_cnt; diff --git a/libopendmarc/opendmarc_policy.c b/libopendmarc/opendmarc_policy.c index f643f846..6e6d8d84 100644 --- a/libopendmarc/opendmarc_policy.c +++ b/libopendmarc/opendmarc_policy.c @@ -1336,7 +1336,6 @@ opendmarc_policy_parse_dmarc(DMARC_POLICY_T *pctx, u_char *domain, u_char *recor * Set the defaults to detect missing required items. */ pctx->pct = -1; - pctx->ri = -1; (void) memset((char *)copy, '\0', sizeof copy); (void) strlcpy((char *)copy, (char *)record, sizeof copy); @@ -1498,65 +1497,6 @@ opendmarc_policy_parse_dmarc(DMARC_POLICY_T *pctx, u_char *domain, u_char *recor return DMARC_PARSE_ERROR_BAD_VALUE; } } - else if (strcasecmp((char *)cp, "ri") == 0) - { - char *xp; - - for (xp = (char *)vp; *xp != '\0'; ++xp) - { - if (! isdigit((int)*xp)) - return DMARC_PARSE_ERROR_BAD_VALUE; - } - errno = 0; - pctx->ri = strtoul((char *)vp, NULL, 10); - if (errno == EINVAL || errno == ERANGE) - { - return DMARC_PARSE_ERROR_BAD_VALUE; - } - } - else if (strcasecmp((char *)cp, "rf") == 0) - { - char *xp, *yp; - - /* - * The list may be a comma delimilted list of choices. - */ - for (xp = (char *)vp; *xp != '\0'; ) - { - u_char xbuf[32]; - - yp = strchr(xp, ','); - if (yp != NULL) - *yp = '\0'; - - xp = (char *)opendmarc_util_cleanup((u_char *)xp, xbuf, sizeof xbuf); - if (xp != NULL && strlen((char *)xp) > 0) - { - /* - * Be generous. Accept, for example, "rf=a, aspf=afrf or any - * left match of "afrf". - */ - if (strncasecmp((char *)xp, "afrf", strlen((char *)xp)) == 0) - pctx->rf |= DMARC_RECORD_RF_AFRF; - else if (strncasecmp((char *)xp, "iodef", strlen((char *)xp)) == 0) - pctx->aspf |= DMARC_RECORD_RF_IODEF; - else - { - /* A totaly unknown value */ - return DMARC_PARSE_ERROR_BAD_VALUE; - } - } - else - { - return DMARC_PARSE_ERROR_BAD_VALUE; - } - - if (yp != NULL) - xp = yp+1; - else - break; - } - } else if (strcasecmp((char *)cp, "rua") == 0) { char *xp, *yp; @@ -1696,10 +1636,6 @@ opendmarc_policy_parse_dmarc(DMARC_POLICY_T *pctx, u_char *domain, u_char *recor pctx->aspf = DMARC_RECORD_A_RELAXED; if (pctx->pct < 0) pctx->pct = 100; - if (pctx->rf == DMARC_RECORD_RF_UNSPECIFIED) - pctx->rf = DMARC_RECORD_RF_AFRF; - if (pctx->ri == -1) - pctx->ri = 86400; if (pctx->fo == DMARC_RECORD_FO_UNSPECIFIED) pctx->fo = DMARC_RECORD_FO_0; @@ -1992,20 +1928,6 @@ opendmarc_policy_fetch_fo(DMARC_POLICY_T *pctx, int *fo) return DMARC_PARSE_OKAY; } -OPENDMARC_STATUS_T -opendmarc_policy_fetch_rf(DMARC_POLICY_T *pctx, int *rf) -{ - if (pctx == NULL) - return DMARC_PARSE_ERROR_NULL_CTX; - if (rf == NULL) - return DMARC_PARSE_ERROR_EMPTY; - if (pctx->ruf_list == NULL) - *rf = DMARC_RECORD_RF_UNSPECIFIED; - else - *rf = pctx->rf; - return DMARC_PARSE_OKAY; -} - /************************************************************************************************** ** OPENDMARC_POLICY_FETCH_UTILIZED_DOMAIN -- Return domain used to get the dmarc record ** Either the From: domain or the organizational domain @@ -2462,31 +2384,6 @@ opendmarc_policy_to_buf(DMARC_POLICY_T *pctx, char *buf, size_t buflen) if (strlcat(buf, nbuf, buflen) >= buflen) return E2BIG; if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG; - if (strlcat(buf, "RF=", buflen) >= buflen) return E2BIG; - if (pctx->rf == 0) - { - if (strlcat(buf, "UNSPECIFIED", buflen) >= buflen) return E2BIG; - } - if ((pctx->rf&DMARC_RECORD_RF_AFRF) != 0) - { - if (strlcat(buf, "AFRF", buflen) >= buflen) return E2BIG; - } - if ((pctx->rf&DMARC_RECORD_RF_IODEF) != 0 && - (pctx->rf&DMARC_RECORD_RF_AFRF) != 0) - { - if (strlcat(buf, ",", buflen) >= buflen) return E2BIG; - } - if ((pctx->rf&DMARC_RECORD_RF_IODEF) != 0) - { - if (strlcat(buf, "IODEF", buflen) >= buflen) return E2BIG; - } - if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG; - - if (strlcat(buf, "RI=", buflen) >= buflen) return E2BIG; - (void) snprintf(nbuf, sizeof nbuf, "%d", pctx->ri); - if (strlcat(buf, nbuf, buflen) >= buflen) return E2BIG; - if (strlcat(buf, "\n", buflen) >= buflen) return E2BIG; - if (strlcat(buf, "RUA=", buflen) >= buflen) return E2BIG; for (i = 0; i < pctx->rua_cnt; ++i) { diff --git a/libopendmarc/tests/test_dmarc_parse.c b/libopendmarc/tests/test_dmarc_parse.c index 425b498e..6bbbafcf 100644 --- a/libopendmarc/tests/test_dmarc_parse.c +++ b/libopendmarc/tests/test_dmarc_parse.c @@ -27,14 +27,14 @@ main(int argc, char **argv) /* 9 */ {"v=DMARC1; p=none; sp=bob;", DMARC_PARSE_ERROR_BAD_VALUE}, /* 10 */ {"v=DMARC1; p=none; adkim=bob;", DMARC_PARSE_ERROR_BAD_VALUE}, /* 11 */ {"v=DMARC1; p=none; aspf=bob;", DMARC_PARSE_ERROR_BAD_VALUE}, - /* 12 */ {"v=DMARC1; p=none; rf=bob;", DMARC_PARSE_ERROR_BAD_VALUE}, - /* 13 */ {"v=DMARC1; p=none; ri=bob;", DMARC_PARSE_ERROR_BAD_VALUE}, + /* 12 */ {"v=DMARC1; p=none; rf=bob;", DMARC_PARSE_OKAY}, /* rf= removed by RFC 9989; now an ignored unknown tag */ + /* 13 */ {"v=DMARC1; p=none; ri=bob;", DMARC_PARSE_OKAY}, /* ri= removed by RFC 9989; now an ignored unknown tag */ /* 14 */ {"v=DMARC1; p=none; pct=500;", DMARC_PARSE_ERROR_BAD_VALUE}, /* 15 */ {"v=DMARC1; pct=100;", DMARC_PARSE_ERROR_NO_REQUIRED_P}, /* 16 */ {"v=DMARC1; p=none; rua=ftp://abuse.com", DMARC_PARSE_OKAY}, /* 17 */ {"v=DMARC1; p=none; ruf=mailto://abuse.com", DMARC_PARSE_OKAY}, /* 18 */ {"v=DMARC1; p=none; ruf=mailto://abuse.com; foo=bar; buzz=happy;", DMARC_PARSE_OKAY}, - /* 19 */ {"v=DMARC1; p=none; rf=000000000000000000000000000000000", DMARC_PARSE_ERROR_BAD_VALUE}, + /* 19 */ {"v=DMARC1; p=none; rf=000000000000000000000000000000000", DMARC_PARSE_OKAY}, /* rf= removed by RFC 9989; now an ignored unknown tag */ /* 20 */ {"v=DMARC1; p=reject; t=y;", DMARC_PARSE_OKAY}, /* 21 */ {"v=DMARC1; p=reject; t=n;", DMARC_PARSE_OKAY}, /* 22 */ {"v=DMARC1; p=reject; t=bob;", DMARC_PARSE_ERROR_BAD_VALUE}, diff --git a/opendmarc/opendmarc.c b/opendmarc/opendmarc.c index 742ea68d..81572315 100644 --- a/opendmarc/opendmarc.c +++ b/opendmarc/opendmarc.c @@ -3716,6 +3716,24 @@ mlfi_eom(SMFICTX *ctx) case DMARC_POLICY_PASS: /* Explicit accept */ aresult = "pass"; + + /* + * RFC 9990 S3.1.1.9: the "pass" disposition is reserved for + * messages that aligned despite an enforcing (quarantine/reject) + * policy. Under p=none/sp=none the outcome is unenforced either + * way, so the report disposition stays "none". Deliberately uses + * the unadjusted published p/sp, not t=y-downgraded + * enforce_policy: t= "does not affect the generation of DMARC + * reports" per RFC 9989, same as the failure-report eligibility + * check below. + */ + switch (apused == DMARC_USED_POLICY_IS_SP ? sp : p) + { + case DMARC_RECORD_P_REJECT: + case DMARC_RECORD_P_QUARANTINE: + result = DMARC_RESULT_PASS; + break; + } break; case DMARC_POLICY_REJECT: /* Explicit reject */ @@ -4281,6 +4299,10 @@ mlfi_eom(SMFICTX *ctx) adisposition = "quarantine"; break; + case DMARC_RESULT_PASS: + adisposition = "pass"; + break; + default: adisposition = "none"; break; diff --git a/opendmarc/opendmarc.h b/opendmarc/opendmarc.h index 24ccbae7..e7074da2 100644 --- a/opendmarc/opendmarc.h +++ b/opendmarc/opendmarc.h @@ -54,6 +54,7 @@ #define DMARC_RESULT_ACCEPT 2 #define DMARC_RESULT_TEMPFAIL 3 #define DMARC_RESULT_QUARANTINE 4 +#define DMARC_RESULT_PASS 5 /* RFC 9990: aligned under an enforcing (reject/quarantine) policy */ #define DMARC_ARC_POLICY_RESULT_PASS 0 #define DMARC_ARC_POLICY_RESULT_UNUSED 1 diff --git a/reports/opendmarc-reports.in b/reports/opendmarc-reports.in index 4cc11891..df1a722f 100755 --- a/reports/opendmarc-reports.in +++ b/reports/opendmarc-reports.in @@ -1512,6 +1512,7 @@ foreach (@$domainset) print $tmpout " $repstart\n"; print $tmpout " $repend\n"; print $tmpout " \n"; + print $tmpout " $progname v$version\n"; print $tmpout " \n"; print $tmpout " \n"; @@ -1650,6 +1651,7 @@ foreach (@$domainset) elsif ($disp == 1) { $dispstr = "reject"; } elsif ($disp == 2) { $dispstr = "none"; } elsif ($disp == 4) { $dispstr = "quarantine"; } + elsif ($disp == 5) { $dispstr = "pass"; } else { $dispstr = "unknown"; } if ($spfresult == 0) { $spfresultstr = "pass"; } @@ -1881,31 +1883,13 @@ foreach (@$domainset) $repdest = $uri->opaque; my $report_maxbytes = $report_maxbytes_global; - # check for max report size - if ($repdest =~ m/^(\S+)!(\d{1,15})([kmgt])?$/i) + # Strip the obsolete "!NNNk" report-size suffix (obs-dmarc-uri / + # obs-dmarc-report-size in RFC 9989's ABNF) from the destination + # address. Per that grammar, reporters MUST ignore this value if + # present rather than honor it as a per-destination size cap. + if ($repdest =~ m/^(\S+)!\d{1,15}[kmgt]?$/i) { $repdest = $1; - $report_maxbytes = $2; - if ($3) - { - my $letter = lc($3); - if ($letter eq 'k') - { - $report_maxbytes = $report_maxbytes * 1024; - } - if ($letter eq 'm') - { - $report_maxbytes = $report_maxbytes * 1048576; - } - if ($letter eq 'g') - { - $report_maxbytes = $report_maxbytes * (2**30); - } - if ($letter eq 't') - { - $report_maxbytes = $report_maxbytes * (2**40); - } - } } # Skip if this destination should be suppressed for any reason