public function check_record_content($content) { $prefix = "Record content is not valid. "; if (empty($content)) { return array("message" => $prefix . "Content may never be empty.", "code" => "RECORD_RHS_EMPTY"); } if (!isset($this->type) || empty($this->type)) { return $prefix . "Type may never be empty."; } if (strlen($content) > 4096) { return array("message" => $prefix . "Content is too long, must be less than 4096 characters.", "code" => "RECORD_RHS_TOO_LONG"); } switch ($this->type) { case "A": if (preg_match(VALID_IPV4, $content) === 0) { return array("message" => $prefix . "An A record requires a valid IPv4 address without trailing dot.", "code" => "RECORD_RHS_INVALID_IPV4"); } break; case "AAAA": if (preg_match(VALID_IPV6, $content) === 0) { return array("message" => $prefix . "An AAAA record requires a valid IPv6 address without trailing dot. IPv4 addresses in IPv6 notation are not supported.", "code" => "RECORD_RHS_INVALID_IPV6"); } break; case "MX": if (!isset($this->priority)) { return array("message" => $prefix . "A MX record must also specify a priority.", "code" => "RECORD_RHS_MISSING_PRIORITY"); } if (!isset($type)) { $type = "MX"; } case "NS": if (!isset($type)) { $type = "NS"; } case "PTR": if (!isset($type)) { $type = "PTR"; } case "CNAME": if (!isset($type)) { $type = "CNAME"; } if ($this->record_type === "TEMPLATE" && preg_match(VALID_TEMPLATE_DOMAIN, $content) === 0) { return array("message" => $prefix . "A {$type} template record must contain a valid FQDN without trailing dot. May also end with [ZONE].", "code" => "RECORD_RHS_INVALID_FQDN"); } else { if ($this->record_type !== "TEMPLATE" && preg_match(VALID_DOMAIN, $content) === 0) { return array("message" => $prefix . "A {$type} record must contain a valid FQDN without trailing dot.", "code" => "RECORD_RHS_INVALID_FQDN"); } } break; case "NAPTR": $parts = explode(" ", $content); if (count($parts) !== 6) { return array("message" => $prefix . "A NAPTR record must provide all 6 parts (note the quotes and trailing dot): <order> <preference> \"<flags>\" \"<service>\" \"<regexp>\" replacement.", "code" => "RECORD_RHS_NAPTR_PARTS_MISSING"); } $naptr_terminal = false; $naptr_regex = false; for ($i = 0; $i < count($parts); $i++) { switch ($i) { case 0: // Order // Order case 1: // Preference if (!ctype_digit($parts[$i])) { return array("message" => $prefix . sprintf("NAPTR record part %d must be a valid integer.", $i + 1), "code" => "RECORD_RHS_NAPTR_INVALID_PART_" . $i); } break; case 2: // Flags if (preg_match(VALID_QUOTED, $parts[$i], $p) === 0) { return array("message" => $prefix . sprintf("NAPTR record part %d must be a valid quoted string.", $i + 1), "code" => "RECORD_RHS_NAPTR_INVALID_PART_" . $i); } if (preg_match(NAPTR_FLAGS_VALID, $p[1]) === 0) { return array("message" => $prefix . sprintf("NAPTR record part %d contains invalid characters. May only contain alphanumeric characters.", $i + 1), "code" => "RECORD_RHS_NAPTR_INVALID_PART_" . $i); } if (preg_match_all(NAPTR_FLAGS_EXCLUSIVE, $p[1], $q) > 1) { return array("message" => $prefix . sprintf("NAPTR record part %d contains too many multiple exclusive FLAGS: S, A, U). Use only one at a time.", $i + 1), "code" => "RECORD_RHS_NAPTR_INVALID_PART_" . $i); } switch (strtolower($p[1])) { case "s": case "a": case "u": $naptr_terminal = true; break; default: $naptr_terminal = false; break; } unset($p); unset($q); break; case 3: // Service if (preg_match(VALID_QUOTED, $parts[$i], $p) === 0) { return array("message" => $prefix . sprintf("NAPTR record part %d must be a valid quoted string.", $i + 1), "code" => "RECORD_RHS_NAPTR_INVALID_PART_" . $i); } if ($naptr_terminal && empty($p[1])) { return array("message" => $prefix . sprintf("NAPTR record part %d is invalid. A SERVICE must be specified if the FLAGS include a terminal flag.", $i + 1), "code" => "RECORD_RHS_NAPTR_INVALID_PART_" . $i); } if (preg_match(NAPTR_SERVICE_VALID, $p[1]) === 0) { return array("message" => $prefix . sprintf("NAPTR record part %d is invalid.", $i + 1), "code" => "RECORD_RHS_NAPTR_INVALID_PART_" . $i); } unset($p); break; case 4: // Regexp if (preg_match(VALID_QUOTED, $parts[$i], $p) === 0) { return array("message" => $prefix . sprintf("NAPTR record part %d must be a valid quoted string.", $i + 1), "code" => "RECORD_RHS_NAPTR_INVALID_PART_" . $i); } if (!empty($p[1])) { $naptr_regex = true; $delimiter = substr($p[1], 0, 1); $reg = substr($p[1], 1); if (preg_match(NAPTR_REGEX_VALID_DELIMITER, $delimiter) === 0) { return array("message" => $prefix . sprintf("NAPTR record part %d contains an invalid POSIX replacement regexp. Delimiter may be any character except 'i', '\\' and may not be a digit. ", $i + 1), "code" => "RECORD_RHS_NAPTR_INVALID_PART_" . $i); } $partz = explode($delimiter, $reg); if (count($partz) !== 3) { return array("message" => $prefix . sprintf("NAPTR record %d contains an invalid POSIX replacement regexp. Not all parts were specified.", $i + 1), "code" => "RECORD_RHS_NAPTR_INVALID_PART_" . $i); } if (preg_match(NAPTR_REGEX_VALID_BACKREF, $partz[1]) === 0) { return array("message" => $prefix . sprintf("NAPTR record part %d contains an invalid POSIX replacement regexp. May only contain one backref in the form of '\\1'.", $i + 1), "code" => "RECORD_RHS_NAPTR_INVALID_PART_" . $i); } if (preg_match(NAPTR_REGEX_VALID_FLAG, $partz[2]) === 0) { return array("message" => $prefix . sprintf("NAPTR record part %d contains an invalid POSIX regexp flag. May optionally contain 'i', or nothing at all.", $i + 1), "code" => "RECORD_RHS_NAPTR_INVALID_PART_" . $i); } unset($delimiter); unset($partz); } unset($p); break; case 5: // Replacement if (preg_match(VALID_NOTEMPTY, $parts[$i], $p) === 0) { return array("message" => $prefix . sprintf("NAPTR record part %d must be a valid record pointer, or a single dot (.).", $i + 1), "code" => "RECORD_RHS_NAPTR_INVALID_PART_" . $i); } if (ValidatorConfig::BIND_COMPATABILITY === true) { $replacement = $p[1]; if ($naptr_regex && $replacement != ".") { return array("message" => $prefix . sprintf("NAPTR record part %d is invalid. REGEXP and REPLACEMENT should not be used at the same time.", $i + 1), "code" => "RECORD_RHS_NAPTR_INVALID_PART_" . $i); } } else { $replacement = HelperFunctions::str_replace_last(".", "", $p[1]); if ($naptr_regex && $replacement != "") { return array("message" => $prefix . sprintf("NAPTR record part %d is invalid. REGEXP and REPLACEMENT should not be used at the same time.", $i + 1), "code" => "RECORD_RHS_NAPTR_INVALID_PART_" . $i); } } if (!empty($replacement) && preg_match(VALID_DOMAIN, $replacement) === 0 && preg_match(VALID_EMPTY_DOMAIN, $replacement) === 0) { return array("message" => $prefix . sprintf("NAPTR record part %d is invalid. REPLACEMENT must be either '.' or a valid FQDN.", $i + 1), "code" => "RECORD_RHS_NAPTR_INVALID_PART_" . $i); } unset($replacement); unset($p); break; } } break; case "RP": $parts = explode(" ", $content); if (count($parts) !== 2) { return array("message" => $prefix . "A RP record must provide all 2 parts: <mailbox name> <more-info pointer>", "code" => "RECORD_RHS_RP_PARTS_MISSING"); } if ($this->record_type === "TEMPLATE") { if (preg_match(VALID_TEMPLATE_DOMAIN, $parts[0]) === 0) { return array("message" => $prefix . "A RP records mailbox name must be an email address with the at-sign replaced by a dot (.). May also end with [ZONE]", "code" => "RECORD_RHS_RP_INVALID_PART_0"); } if (preg_match(VALID_TEMPLATE_DOMAIN, $parts[1]) === 0) { return array("message" => $prefix . "A RP records more-info pointer must be a valid FQDN. May also end with [ZONE]", "code" => "RECORD_RHS_RP_INVALID_PART_1"); } } else { if (preg_match(VALID_DOMAIN, $parts[0]) === 0) { return array("message" => $prefix . "A RP records mailbox name must be an email address with the at-sign replaced by a dot (.).", "code" => "RECORD_RHS_RP_INVALID_PART_0"); } if (preg_match(VALID_DOMAIN, $parts[1]) === 0) { return array("message" => $prefix . "A RP records more-info pointer must be a valid FQDN.", "code" => "RECORD_RHS_RP_INVALID_PART_1"); } } break; case "SOA": $parts = explode(" ", $content); if (count($parts) !== 7) { return array("message" => $prefix . "A SOA record must provide all 7 parts: <primary> <hostmaster> <serial> <refresh> <retry> <expire> <default_ttl>", "code" => "RECORD_RHS_SOA_PARTS_MISSING"); } for ($i = 0; $i < count($parts); $i++) { switch ($i) { case 0: if (preg_match(VALID_DOMAIN, $parts[$i]) === 0) { return array("message" => $prefix . "A SOA record must provide a valid FQDN as primary hostname.", "code" => "RECORD_RHS_SOA_INVALID_PART_" . $i); } break; case 1: if (filter_var($parts[$i], FILTER_VALIDATE_EMAIL) === false && preg_match(VALID_DOMAIN, $parts[$i]) === 0) { return array("message" => $prefix . "A SOA record must provide a valid email address as hostmaster.", "code" => "RECORD_RHS_SOA_INVALID_PART_" . $i); } break; case 2: case 3: case 4: case 5: case 6: if (!ctype_digit($parts[$i])) { return array("message" => sprintf("SOA record part %d must be a valid integer.", $i + 1), "code" => "RECORD_RHS_SOA_INVALID_PART_" . $i); } break; } } break; case "SPF": if (!isset($type)) { $type = "SPF"; } case "TXT": if (!isset($type)) { $type = "TXT"; } if (preg_match(VALID_QUOTED, $content) === 0) { return array("message" => $prefix . "A {$type} record must provide a valid quoted string.", "code" => "RECORD_RHS_INVALID_QUOTED_STRING"); } break; case "SSHFP": $parts = explode(" ", $content); if (count($parts) !== 3) { return array("message" => $prefix . "A SSHFP record must provide all 3 parts: <algorithm> <fp-type> <fingeprint>", "code" => "RECORD_RHS_SSHFP_PARTS_MISSING"); } for ($i = 0; $i < count($parts); $i++) { switch ($i) { case 0: if ($parts[$i] != "1" && $parts[$i] != "2" && $parts[$i] != "3") { return array("message" => $prefix . "A SSHFP record must provide either 1 (RSA), 2 (DSA) or 3 (ECDSA) as algorithm.", "code" => "RECORD_RHS_SSHFP_INVALID_PART_0"); } break; case 1: if ($parts[$i] != "1" && $parts[$i] != "2") { return array("message" => $prefix . "A SSHFP record must provide 1 (SHA-1) or 2 (SHA-256) as fp-type.", "code" => "RECORD_RHS_SSHFP_INVALID_PART_1"); } break; case 2: if ($parts[1] == "1" && strlen($parts[$i]) !== 40) { return array("message" => $prefix . "A SSHFP record must provide a SHA-1 fingerprint as a 40 character ASCII hexadecimal string.", "code" => "RECORD_RHS_SSHFP_INVALID_PART_2"); } else { if ($parts[1] == "2" && strlen($parts[$i]) !== 64) { return array("message" => $prefix . "A SSHFP record must provide a SHA-256 fingerprint as a 64 character ASCII hexadecimal string.", "code" => "RECORD_RHS_SSHFP_INVALID_PART_2"); } } break; } } break; case "SRV": if (!isset($this->priority)) { return array("message" => $prefix . "A SRV record must also provide a priority.", "code" => "RECORD_RHS_MISSING_PRIORITY"); } $parts = explode(" ", $content); if (count($parts) !== 3) { return array("message" => $prefix . "A SRV record must provide all 3 parts: <weight> <port> <service>", "code" => "RECORD_RHS_SRV_PARTS_MISSING"); } for ($i = 0; $i < count($parts); $i++) { switch ($i) { case 0: case 1: if (!ctype_digit($parts[$i])) { return array("message" => $prefix . sprintf("SRV record part %d must be a valid integer.", $i + 1), "code" => "RECORD_RHS_SRV_INVALID_PART_" . $i); } break; case 2: if ($this->record_type === "TEMPLATE" && preg_match(VALID_TEMPLATE_DOMAIN, $parts[$i]) === 0) { return array("message" => $prefix . "A SRV record must provide a valid FQDN as service. May also end with [ZONE]", "code" => "RECORD_RHS_SRV_INVALID_PART_" . $i); } else { if ($this->record_type !== "TEMPLATE" && preg_match(VALID_DOMAIN, $parts[$i]) === 0) { return array("message" => $prefix . "A SRV record must provide a valid FQDN as service.", "code" => "RECORD_RHS_SRV_INVALID_PART_" . $i); } } break; } } break; default: break; } return true; }