You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

126 lines
3.4 KiB

validate_mailto_list <- function(x) {
all(stri_detect_regex(unlist(strsplit(x[1], ",")), "mailto:[^@]+@[[:print:]]+$"))
}
validate_fo <- function(x) {
x <- x[1]
stri_detect_regex(x, "[01ds]{1}") |
(all(stri_detect_regex(unlist(strsplit(x, ":")), "[01ds]{1}")))
}
#' Validate a DMARC Record
#'
#' @param record The DMARC TXT record value
#' @param fqdn The FQDN used (e.g. `_dmarc.example.com`)
#' @param nameserver character vector of nameservers
#' @export
validate_dmarc_record <- function(record, fqdn, nameservers = checkdmarc_default_nameservers) {
fqdn <- fqdn[1]
notes <- c()
if ((!length(record)) || (is.na(record[1]))) {
notes <- c(notes, "No DMARC record found.")
data.frame(
fqdn = fqdn,
key = NA_character_,
value = NA_character_,
valid = NA_character_,
stringsAsFactors = FALSE
) -> out
attr(out, "valid") <- FALSE
} else {
record <- stri_trim_both(record)
if (stri_detect_regex(record, "^v=DMARC1")) {
record <- stri_replace_first_regex(record, "^v=DMARC1;[[:space:]]*", "")
split_rec <- stri_replace_last_regex(record, ";$", "")
split_rec <- unlist(stri_split_regex(split_rec, ";[[:space:]]*"))
do.call(
rbind.data.frame,
lapply(split_rec, function(field) {
vals <- stri_trim_both(unlist(stri_split_fixed(field, "=", 2)))
if (length(vals) != 2) {
notes <<- c(notes, sprintf("Malformed key-value pair found in DMARC record: %s.", paste0(vals, "||")))
data.frame(
key = character(0),
value = character(0),
stringsAsFactors = FALSE
)
} else {
tdf <- set_names(as.list(vals), c("key", "value"))
as.data.frame(tdf, stringsAsFactors = FALSE)
}
})
) -> out
out$fqdn <- fqdn
out$value <- out[["value"]]
if (!all(out[["key"]] %in% c("p", "sp", "rua", "ruf", "adkim", "aspf", "ri", "fo", "rf", "pct"))) {
notes <- c(notes, "DMARC record contains one or more non-standard keys.")
}
if (any(table(out[["key"]]) > 1)) {
notes <- c(notes, "DMARC record has duplicate keys.")
}
out$valid <- sapply(out[["key"]], function(key) {
val <- out[out$key == key, "value", drop=TRUE]
switch(
key,
p = (val %in% c("none", "quarantine", "reject")),
sp = (val %in% c("none", "quarantine", "reject")),
rua = validate_mailto_list(val),
ruf = validate_mailto_list(val),
adkim = (val %in% c("r", "s", "strict", "relaxed")),
aspf = (val %in% c("r", "s", "strict", "relaxed")),
ri = stri_detect_regex(val, "^[[:digit:]]+$"),
fo = validate_fo(val),
rf = (val == "afrf"),
pct = stri_detect_regex(val, "^[[:digit:]]+$"),
FALSE
)
})
attr(out, "valid") <- ((sum(out$valid) == nrow(out))) && (length(notes) == 0)
} else { # invalid
notes <- c(notes, "Non-DMARC TXT record found in DMARC record.")
data.frame(
fqdn = fqdn,
key = NA_character_,
value = NA_character_,
valid = NA_character_,
stringsAsFactors = FALSE
) -> out
attr(out, "valid") <- FALSE
}
}
attr(out, "notes") <- notes
class(out) <- c("tbl_df", "tbl", "data.frame")
out[,c("fqdn", "key", "value", "valid")]
}