///|
pub fn parse_tlsrpt(text : String) -> TlsRptRecord {
let raw = clean(text)
let version = tag_value(raw, "v")
let rua = tag_value(raw, "rua")
let rua_count = csv_count(rua)
let mailto_count = count_uri_prefix(rua, "mailto:")
let https_count = count_uri_prefix(rua, "https://")
let valid_version = lower(version) == "tlsrptv1"
let findings = tlsrpt_findings(
raw, valid_version, version, rua, rua_count, mailto_count, https_count,
)
{
raw,
valid_version,
version,
rua_count,
mailto_count,
https_count,
findings,
}
}
///|
fn count_uri_prefix(text : String, prefix : String) -> Int {
let wanted = lower(prefix)
let mut count = 0
for part in text.split(",") {
if lower(part.to_owned().trim().to_owned()).has_prefix(wanted) {
count += 1
}
}
count
}
///|
fn tlsrpt_findings(
raw : String,
valid_version : Bool,
version : String,
rua : String,
rua_count : Int,
mailto_count : Int,
https_count : Int,
) -> Array[PolicyFinding] {
let findings = Array::new(capacity=8)
if raw.is_empty() {
findings.push(
warn(
"tlsrpt.record-missing", "TLS-RPT record is missing", "TLS reporting helps observe MTA-STS and DANE delivery failures.",
"", "Publish _smtp._tls TXT with v=TLSRPTv1 and rua destinations.",
),
)
return findings
}
if valid_version {
findings.push(pass("tlsrpt.version", "TLS-RPT version is present", version))
} else {
findings.push(
fail(
"tlsrpt.version-invalid", "TLS-RPT version is invalid", "The v tag must be TLSRPTv1.",
version, "Use v=TLSRPTv1.",
),
)
}
if rua_count == 0 {
findings.push(
fail(
"tlsrpt.rua-missing", "TLS-RPT reporting destination is missing", "Reports cannot be delivered without rua.",
"", "Add rua=mailto:tlsrpt@example.com or an HTTPS endpoint.",
),
)
} else {
findings.push(
pass(
"tlsrpt.rua",
"TLS-RPT reporting destinations are configured",
rua_count.to_string(),
),
)
}
if rua_count > 0 && mailto_count == 0 && https_count == 0 {
findings.push(
fail(
"tlsrpt.rua-invalid", "TLS-RPT destinations have unsupported URI schemes",
"MailShield recognizes mailto and HTTPS TLS-RPT destinations.", rua, "Use mailto: or https:// destinations.",
),
)
} else if https_count > 0 {
findings.push(
pass(
"tlsrpt.https",
"TLS-RPT includes HTTPS reporting",
https_count.to_string(),
),
)
} else if mailto_count > 0 {
findings.push(
pass(
"tlsrpt.mailto",
"TLS-RPT includes mail reporting",
mailto_count.to_string(),
),
)
}
if rua_count > 5 {
findings.push(
warn(
"tlsrpt.rua-many",
"TLS-RPT has many reporting destinations",
"Too many destinations can make report handling noisy.",
rua_count.to_string(),
"Keep only destinations that are actively monitored.",
penalty=2,
),
)
}
findings
}
///|
pub fn TlsRptRecord::is_configured(self : TlsRptRecord) -> Bool {
!self.raw.is_empty() && self.valid_version && self.rua_count > 0
}
///|
pub fn TlsRptRecord::uses_https(self : TlsRptRecord) -> Bool {
self.https_count > 0
}
///|
pub fn TlsRptRecord::uses_mailto(self : TlsRptRecord) -> Bool {
self.mailto_count > 0
}