Refactor names, separate SPF validator from others
This commit is contained in:
parent
82c4579c58
commit
80ad069793
@ -1,22 +1,22 @@
|
||||
import { DmarcTool } from "./tools/DmarcTool.js";
|
||||
import { DmarcRecord } from "./records/DmarcRecord.js";
|
||||
|
||||
const tools = {
|
||||
"/dmarc-creator": DmarcTool,
|
||||
const records = {
|
||||
"/dmarc-creator": DmarcRecord,
|
||||
};
|
||||
|
||||
const Tool = tools[location.pathname];
|
||||
const Record = records[location.pathname];
|
||||
|
||||
addFields(document.getElementById("form"), Tool.fields.filter(field => !field.categoryName));
|
||||
addFields(document.getElementById("form"), Record.fields.filter(field => !field.categoryName));
|
||||
|
||||
const categories = Tool.fields.map(field => field.categoryName).filter(isUnique).filter(val => val);
|
||||
const categories = Record.fields.map(field => field.categoryName).filter(isUnique).filter(val => val);
|
||||
|
||||
for (const category of categories) {
|
||||
const details = document.createElement("details");
|
||||
details.innerHTML = `<summary>${Tool.categories[category]}</summary>`;
|
||||
details.innerHTML = `<summary>${Record.categories[category]}</summary>`;
|
||||
|
||||
document.getElementById("form").appendChild(details);
|
||||
|
||||
addFields(details, Tool.fields.filter(field => field.categoryName === category));
|
||||
addFields(details, Record.fields.filter(field => field.categoryName === category));
|
||||
}
|
||||
|
||||
function addFields(elem, fields) {
|
||||
@ -36,9 +36,9 @@ document.getElementById("form").onchange = () => generate();
|
||||
generate();
|
||||
|
||||
function generate() {
|
||||
const tool = new Tool();
|
||||
const record = new Record();
|
||||
|
||||
document.getElementById("record").value = tool.fieldsToString();
|
||||
document.getElementById("record").value = record.fieldsToString();
|
||||
}
|
||||
|
||||
document.getElementById("record").onclick = (e) => {
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
import { Field } from "./Field.js";
|
||||
import { ValidationError } from "../ValidationError.js";
|
||||
|
||||
export class DomainField extends Field {
|
||||
constructor(key, separator) {
|
||||
super(key);
|
||||
this.separator = separator;
|
||||
}
|
||||
|
||||
validate(value) {
|
||||
if (!value.match(/\w+(\.\w+)+/)) {
|
||||
throw new ValidationError(`Field ${this.key} is not a valid domain`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -1,61 +1,58 @@
|
||||
import { ConstantField } from "../fields/ConstantField.js";
|
||||
import { EnumField } from "../fields/EnumField.js";
|
||||
import { IntField } from "../fields/IntField.js";
|
||||
import { Field } from "../fields/Field.js";
|
||||
import { DmarcUriListField } from "../fields/DmarcUriListField.js";
|
||||
import { DnsTool } from "./DnsTool.js";
|
||||
import { ValidationError } from "../ValidationError.js";
|
||||
|
||||
export class DmarcTool extends DnsTool {
|
||||
static allowWhitespaceAroundSeparator = true;
|
||||
import { ConstantTag } from "../tags/ConstantTag.js";
|
||||
import { EnumTag } from "../tags/EnumTag.js";
|
||||
import { IntTag } from "../tags/IntTag.js";
|
||||
import { Tag } from "../tags/Tag.js";
|
||||
import { DmarcUriListTag } from "../tags/DmarcUriListTag.js";
|
||||
import { TagListRecord } from "./TagListRecord.js";
|
||||
|
||||
export class DmarcRecord extends TagListRecord {
|
||||
static fields = [
|
||||
new ConstantField("v", "DMARC1")
|
||||
new ConstantTag("v", "DMARC1")
|
||||
.required()
|
||||
.pos(0),
|
||||
|
||||
new EnumField("p", ["none", "quarantine", "reject"])
|
||||
new EnumTag("p", ["none", "quarantine", "reject"])
|
||||
.label("Mail Receiver policy")
|
||||
.desc("How to handle failed validations. The email may be quarantined (usually means sent to spam) or rejected")
|
||||
.options(["None", "Quarantine", "Reject"])
|
||||
.required()
|
||||
.pos(1),
|
||||
|
||||
new EnumField("adkim", ["r", "s"])
|
||||
new EnumTag("adkim", ["r", "s"])
|
||||
.label("DKIM")
|
||||
.desc("How strictly to handle DKIM validation")
|
||||
.options(["Relaxed", "Strict"])
|
||||
.default("r")
|
||||
.pos(2),
|
||||
|
||||
new EnumField("aspf", ["r", "s"])
|
||||
new EnumTag("aspf", ["r", "s"])
|
||||
.label("SPF")
|
||||
.desc("How strictly to handle SPF validation")
|
||||
.options(["Relaxed", "Strict"])
|
||||
.default("r")
|
||||
.pos(2),
|
||||
|
||||
new EnumField("sp", ["none", "quarantine", "reject"])
|
||||
new EnumTag("sp", ["none", "quarantine", "reject"])
|
||||
.label("Mail Receiver policy (for subdomains)")
|
||||
.desc("Same as Mail Receiver policy, but applies only to subdomains. If not set, Mail Receiver policy applies to both top-level domain and subdomains")
|
||||
.category("advanced")
|
||||
.options(["None", "Quarantine", "Reject"])
|
||||
.pos(2),
|
||||
|
||||
new IntField("pct", 0, 100)
|
||||
new IntTag("pct", 0, 100)
|
||||
.label("Percentage")
|
||||
.desc("Percentage of emails to apply DMARC validation on. Useful for split-testing and continuous rollout")
|
||||
.category("advanced")
|
||||
.default(100)
|
||||
.pos(2),
|
||||
|
||||
new DmarcUriListField("ruf")
|
||||
new DmarcUriListTag("ruf")
|
||||
.label("Send failure reports to")
|
||||
.desc("When DMARC validation fails, reports are sent to this email")
|
||||
.category("failure-reporting")
|
||||
.pos(2),
|
||||
|
||||
new EnumField("fo", ["0", "1", "d", "s"])
|
||||
new EnumTag("fo", ["0", "1", "d", "s"])
|
||||
.label("Failure reporting options")
|
||||
.desc("Define how reports will be generated")
|
||||
.category("failure-reporting")
|
||||
@ -68,19 +65,19 @@ export class DmarcTool extends DnsTool {
|
||||
.default("0")
|
||||
.pos(2),
|
||||
|
||||
new DmarcUriListField("rua")
|
||||
new DmarcUriListTag("rua")
|
||||
.label("Send aggregate feedback to")
|
||||
.desc("Aggregate reports will be sent to this email, if defined")
|
||||
.category("failure-reporting"),
|
||||
|
||||
new IntField("ri", 0, 2 ** 32)
|
||||
new IntTag("ri", 0, 2 ** 32)
|
||||
.label("Aggregate report interval")
|
||||
.desc("Interval (in seconds) between aggregate reports")
|
||||
.category("failure-reporting")
|
||||
.default(86400)
|
||||
.pos(2),
|
||||
|
||||
new Field("rf").default("afrf"), // Other values not supported
|
||||
new Tag("rf").default("afrf"), // Other values not supported
|
||||
];
|
||||
|
||||
static categories = {
|
||||
167
assets/scripts/records/SpfRecord.js
Normal file
167
assets/scripts/records/SpfRecord.js
Normal file
@ -0,0 +1,167 @@
|
||||
import { Mechanism } from "../spf/Mechanism.js";
|
||||
import { ValidationError } from "../ValidationError.js";
|
||||
import { Modifier } from "../spf/Modifier.js";
|
||||
import { ValueRequirement } from "../spf/ValueRequirement.js";
|
||||
|
||||
export class SpfRecord {
|
||||
static fields = [
|
||||
new Modifier("v")
|
||||
.required()
|
||||
.validate((key, val) => {
|
||||
if (val !== "spf1") throw new ValidationError(`Version must be "spf1"`);
|
||||
return true;
|
||||
})
|
||||
.pos(0),
|
||||
|
||||
new Mechanism("include")
|
||||
.validate(this.validateDomain)
|
||||
.multiple()
|
||||
.pos(1),
|
||||
|
||||
new Mechanism("a")
|
||||
.value(ValueRequirement.OPTIONAL)
|
||||
.validate(this.validateDomain)
|
||||
.multiple()
|
||||
.pos(1),
|
||||
|
||||
new Mechanism("mx")
|
||||
.value(ValueRequirement.OPTIONAL)
|
||||
.validate(this.validateDomain)
|
||||
.multiple()
|
||||
.pos(2),
|
||||
|
||||
new Mechanism("ptr")
|
||||
.value(ValueRequirement.OPTIONAL)
|
||||
.validate(() => { throw new ValidationError(`"ptr" mechanism should not be used`) })
|
||||
.multiple()
|
||||
.pos(2),
|
||||
|
||||
new Mechanism("ipv4")
|
||||
.validate(this.validateIPv4)
|
||||
.multiple()
|
||||
.pos(2),
|
||||
|
||||
new Mechanism("ipv6")
|
||||
.validate(this.validateIPv6)
|
||||
.multiple()
|
||||
.pos(2),
|
||||
|
||||
new Mechanism("exists")
|
||||
.validate(this.validateDomain)
|
||||
.multiple()
|
||||
.pos(2),
|
||||
|
||||
new Mechanism("all")
|
||||
.value(ValueRequirement.PROHIBITED)
|
||||
.pos(3),
|
||||
|
||||
new Modifier("redirect")
|
||||
.validate(this.validateDomain)
|
||||
.pos(4),
|
||||
|
||||
new Modifier("exp")
|
||||
.validate(this.validateDomain)
|
||||
.pos(4),
|
||||
];
|
||||
|
||||
constructor(text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
tokenize() {
|
||||
return this.text.trim().split(/\s+/);
|
||||
}
|
||||
|
||||
getKeyValues() {
|
||||
const result = [];
|
||||
|
||||
for (const token of this.tokenize()) {
|
||||
const name = token.match(/^[-+?~]?(\w*)/)[1];
|
||||
|
||||
const term = this.constructor.fields.find(f => f.key === name);
|
||||
if (!term) {
|
||||
throw new ValidationError(`Unknown term: ${name}`);
|
||||
}
|
||||
|
||||
const [directive, value] = token.split(term.separator);
|
||||
const [, qualifier, key] = directive.match(/^([-+?~]?)(\w*)/);
|
||||
|
||||
result.push({ qualifier, key, value });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
validate() {
|
||||
const values = this.getKeyValues();
|
||||
|
||||
for (const term of this.constructor.fields) {
|
||||
if (term.isRequired && !values.some(v => v.key === term.key)) {
|
||||
throw new ValidationError(`Term "${term.key}" is required`);
|
||||
}
|
||||
}
|
||||
|
||||
let lastPos = 0;
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
const input = values[i];
|
||||
const term = this.constructor.fields.find(d => d.key === input.key);
|
||||
|
||||
if (!term) {
|
||||
throw new ValidationError(`Unknown term: ${input.key}`);
|
||||
}
|
||||
|
||||
if (term.position < lastPos) {
|
||||
const lastDirective = this.constructor.fields.find(d => d.key === values[i-1].key);
|
||||
|
||||
throw new ValidationError(`Term "${lastDirective.key}" must come after "${term.key}"`);
|
||||
}
|
||||
|
||||
if (term instanceof Modifier && input.qualifier) {
|
||||
throw new ValidationError(`Modifier "${term.key}" must not have a qualifier`)
|
||||
}
|
||||
|
||||
if (!input.value && term.valueRequirement === ValueRequirement.REQUIRED) {
|
||||
throw new ValidationError(`Term "${term.key}" is missing a value`);
|
||||
}
|
||||
|
||||
if (input.value && term.valueRequirement === ValueRequirement.PROHIBITED) {
|
||||
throw new ValidationError(`Term "${term.key}" must not have a value`);
|
||||
}
|
||||
|
||||
if (input.value) term.validationFunction(term.key, input.value);
|
||||
|
||||
lastPos = term.position;
|
||||
}
|
||||
}
|
||||
|
||||
static validateDomain(key, value) {
|
||||
// https://www.rfc-editor.org/rfc/rfc7208#section-7.1
|
||||
const valid = value
|
||||
.split(".")
|
||||
.every(segment => segment.match(/^([^%]|%_|%%|%-|%\{[slodiphcrtv]\d*r?[-.+,\/_=]*})+$/));
|
||||
|
||||
if (!valid) throw new ValidationError(`Value for "${key}" is not a valid domain name`);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static validateIPv4(key, value) {
|
||||
const segments = value.split(".");
|
||||
|
||||
const valid = segments.every(segment => {
|
||||
const number = parseInt(segment);
|
||||
|
||||
return !isNaN(number) && number >= 0 && number <= 255;
|
||||
});
|
||||
|
||||
if (segments.length !== 4 || !valid) {
|
||||
throw new ValidationError(`Value for ${key} is not a valid IPv4 address`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static validateIPv6(key, value) {
|
||||
return true; // TODO validate properly
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import { ValidationError } from "../ValidationError.js";
|
||||
|
||||
export class DnsTool {
|
||||
static allowWhitespaceAroundSeparator;
|
||||
/** Common class for DMARC/DKIM which both use semicolon-separated tag-value lists */
|
||||
export class TagListRecord {
|
||||
static fields = [];
|
||||
|
||||
constructor(text) {
|
||||
@ -16,20 +16,10 @@ export class DnsTool {
|
||||
const result = [];
|
||||
|
||||
for (const token of this.tokenize()) {
|
||||
const key = token.match(/^\w*/)[0];
|
||||
|
||||
const field = this.constructor.fields.find(f => f.key === key);
|
||||
if (!field) {
|
||||
throw new ValidationError(`Unknown field: ${key}`);
|
||||
}
|
||||
|
||||
const wsp = this.constructor.allowWhitespaceAroundSeparator ? "\\s*" : "";
|
||||
const separator = new RegExp(wsp + field.separator + wsp);
|
||||
|
||||
const value = token.split(separator)[1];
|
||||
const [key, value] = token.split(/\s*=\s*/);
|
||||
|
||||
if (!value) {
|
||||
throw new ValidationError(`Field "${key}" is missing a value`);
|
||||
throw new ValidationError(`Tag "${key}" is missing a value`);
|
||||
}
|
||||
|
||||
result.push({ key, value });
|
||||
@ -43,7 +33,7 @@ export class DnsTool {
|
||||
|
||||
for (const field of this.constructor.fields) {
|
||||
if (field.isRequired && !values.some(v => v.key === field.key)) {
|
||||
throw new ValidationError(`Field "${field.key}" is required`);
|
||||
throw new ValidationError(`Tag "${field.key}" is required`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,13 +43,13 @@ export class DnsTool {
|
||||
const field = this.constructor.fields.find(f => f.key === input.key);
|
||||
|
||||
if (!field) {
|
||||
throw new ValidationError(`Unknown field: ${input.key}`);
|
||||
throw new ValidationError(`Unknown tag: ${input.key}`);
|
||||
}
|
||||
|
||||
if (field.position < lastPos) {
|
||||
const lastField = this.constructor.fields.find(f => f.key === values[i-1].key);
|
||||
|
||||
throw new ValidationError(`Field "${lastField.key}" must come after "${field.key}"`);
|
||||
throw new ValidationError(`Tag "${lastField.key}" must come after "${field.key}"`);
|
||||
}
|
||||
|
||||
field.validate(input.value);
|
||||
9
assets/scripts/spf/Mechanism.js
Normal file
9
assets/scripts/spf/Mechanism.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { Term } from "./Term.js";
|
||||
|
||||
export class Mechanism extends Term {
|
||||
separator = ":";
|
||||
|
||||
constructor(key) {
|
||||
super(key);
|
||||
}
|
||||
}
|
||||
9
assets/scripts/spf/Modifier.js
Normal file
9
assets/scripts/spf/Modifier.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { Term } from "./Term.js";
|
||||
|
||||
export class Modifier extends Term {
|
||||
separator = "=";
|
||||
|
||||
constructor(key) {
|
||||
super(key);
|
||||
}
|
||||
}
|
||||
41
assets/scripts/spf/Term.js
Normal file
41
assets/scripts/spf/Term.js
Normal file
@ -0,0 +1,41 @@
|
||||
import { ValueRequirement } from "./ValueRequirement.js";
|
||||
|
||||
export class Term {
|
||||
separator = null;
|
||||
isRequired = false;
|
||||
position = null;
|
||||
allowMultiple = false;
|
||||
valueRequirement = ValueRequirement.REQUIRED;
|
||||
validationFunction = null;
|
||||
|
||||
constructor(key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
// Builder methods
|
||||
|
||||
required() {
|
||||
this.isRequired = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
pos(i) {
|
||||
this.position = i;
|
||||
return this;
|
||||
}
|
||||
|
||||
multiple() {
|
||||
this.allowMultiple = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
value(requirement) {
|
||||
this.valueRequirement = requirement;
|
||||
return this;
|
||||
}
|
||||
|
||||
validate(func) {
|
||||
this.validationFunction = func;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
5
assets/scripts/spf/ValueRequirement.js
Normal file
5
assets/scripts/spf/ValueRequirement.js
Normal file
@ -0,0 +1,5 @@
|
||||
export const ValueRequirement = {
|
||||
REQUIRED: "required",
|
||||
OPTIONAL: "optional",
|
||||
PROHIBITED: "prohibited",
|
||||
};
|
||||
@ -1,9 +1,7 @@
|
||||
import { Field } from "./Field.js";
|
||||
import { Tag } from "./Tag.js";
|
||||
import { ValidationError } from "../ValidationError.js";
|
||||
|
||||
export class ConstantField extends Field {
|
||||
separator = "=";
|
||||
|
||||
export class ConstantTag extends Tag {
|
||||
constructor(key, value) {
|
||||
super(key);
|
||||
this.value = value;
|
||||
@ -1,9 +1,7 @@
|
||||
import { Field } from "./Field.js";
|
||||
import { Tag } from "./Tag.js";
|
||||
import { ValidationError } from "../ValidationError.js";
|
||||
|
||||
export class DmarcUriListField extends Field {
|
||||
separator = "=";
|
||||
|
||||
export class DmarcUriListTag extends Tag {
|
||||
constructor(key) {
|
||||
super(key);
|
||||
}
|
||||
@ -17,7 +15,7 @@ export class DmarcUriListField extends Field {
|
||||
try {
|
||||
new URL(uri);
|
||||
} catch(e) {
|
||||
throw new ValidationError(`Invalid URI for field "${this.key}": ${uri}`);
|
||||
throw new ValidationError(`Invalid URI for tag "${this.key}": ${uri}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import { Field } from "./Field.js";
|
||||
import { Tag } from "./Tag.js";
|
||||
import { ValidationError } from "../ValidationError.js";
|
||||
|
||||
export class EnumField extends Field {
|
||||
separator = "=";
|
||||
|
||||
export class EnumTag extends Tag {
|
||||
constructor(key, values) {
|
||||
super(key);
|
||||
this.values = values;
|
||||
@ -14,7 +12,7 @@ export class EnumField extends Field {
|
||||
if (this.values.includes(value))
|
||||
return true;
|
||||
|
||||
throw new ValidationError(`Invalid value for field "${this.key}" - must be one of: ${this.values.join(", ")}`);
|
||||
throw new ValidationError(`Invalid value for tag "${this.key}" - must be one of: ${this.values.join(", ")}`);
|
||||
}
|
||||
|
||||
getInputHtml() {
|
||||
@ -1,9 +1,7 @@
|
||||
import { Field } from "./Field.js";
|
||||
import { Tag } from "./Tag.js";
|
||||
import { ValidationError } from "../ValidationError.js";
|
||||
|
||||
export class IntField extends Field {
|
||||
separator = "=";
|
||||
|
||||
export class IntTag extends Tag {
|
||||
constructor(key, min, max) {
|
||||
super(key);
|
||||
this.min = min;
|
||||
@ -1,7 +1,5 @@
|
||||
export class Field {
|
||||
separator = null;
|
||||
export class Tag {
|
||||
isRequired = false;
|
||||
allowMultiple = false;
|
||||
defaultValue = null;
|
||||
displayName = null;
|
||||
description = null;
|
||||
@ -10,7 +8,7 @@ export class Field {
|
||||
|
||||
constructor(key) {
|
||||
this.key = key;
|
||||
this.id = "field-" + key;
|
||||
this.id = "tag-" + key;
|
||||
}
|
||||
|
||||
// Virtual methods
|
||||
@ -34,11 +32,6 @@ export class Field {
|
||||
return this;
|
||||
}
|
||||
|
||||
multiple() {
|
||||
this.allowMultiple = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
default(value) {
|
||||
this.defaultValue = value;
|
||||
return this;
|
||||
@ -1,57 +0,0 @@
|
||||
import { ConstantField } from "../fields/ConstantField.js";
|
||||
import { DomainField } from "../fields/DomainField.js";
|
||||
import { DnsTool } from "./DnsTool.js";
|
||||
|
||||
export class SpfTool extends DnsTool {
|
||||
static allowWhitespaceAroundSeparator = false;
|
||||
|
||||
static fields = [
|
||||
new ConstantField("v", "spf1")
|
||||
.required()
|
||||
.pos(0),
|
||||
|
||||
new DomainField("include", ":")
|
||||
.multiple()
|
||||
.pos(1),
|
||||
|
||||
new DomainField("a", ":")
|
||||
.multiple()
|
||||
.pos(1),
|
||||
|
||||
new DomainField("mx", ":")
|
||||
.multiple()
|
||||
.pos(2),
|
||||
|
||||
new DomainField("ptr", ":")
|
||||
.multiple()
|
||||
.pos(2),
|
||||
|
||||
new DomainField("ipv4", ":")
|
||||
.multiple()
|
||||
.pos(2),
|
||||
|
||||
new DomainField("ipv6", ":")
|
||||
.multiple()
|
||||
.pos(2),
|
||||
|
||||
new DomainField("exists", ":")
|
||||
.multiple()
|
||||
.pos(2),
|
||||
|
||||
new DomainField("redirect", "=")
|
||||
.pos(3),
|
||||
|
||||
new DomainField("exp", "=")
|
||||
.pos(3),
|
||||
|
||||
// TODO all
|
||||
];
|
||||
|
||||
constructor(text) {
|
||||
super(text);
|
||||
}
|
||||
|
||||
tokenize() {
|
||||
return this.text.split(/\s+/);
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
import { DmarcTool } from "./tools/DmarcTool.js";
|
||||
import { SpfTool } from "./tools/SpfTool.js";
|
||||
import { DmarcRecord } from "./records/DmarcRecord.js";
|
||||
import { SpfRecord } from "./records/SpfRecord.js";
|
||||
|
||||
const tools = {
|
||||
"/dmarc-validator": DmarcTool,
|
||||
"/spf-validator": SpfTool,
|
||||
const records = {
|
||||
"/dmarc-validator": DmarcRecord,
|
||||
"/spf-validator": SpfRecord,
|
||||
};
|
||||
|
||||
const Tool = tools[location.pathname];
|
||||
const Record = records[location.pathname];
|
||||
|
||||
document.getElementById("record").oninput = event => validate(event.target.value);
|
||||
|
||||
@ -26,10 +26,10 @@ function validate(value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tool = new Tool(value);
|
||||
const record = new Record(value);
|
||||
|
||||
try {
|
||||
tool.validate();
|
||||
record.validate();
|
||||
|
||||
document.getElementById("record").classList.add("valid");
|
||||
document.getElementById("success").style.display = "flex";
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
<p id="result-placeholder" class="validation-result"></p>
|
||||
|
||||
<p>
|
||||
DMARC is a standard for web servers to tell how to handle validation errors in SPF and DKIM.
|
||||
DMARC is a standard for email servers to tell how to handle validation errors in SPF and DKIM.
|
||||
It is a DNS TXT record with different values, defining rules on when to reject the email, how to report
|
||||
failures etc.
|
||||
</p>
|
||||
|
||||
@ -27,7 +27,40 @@
|
||||
|
||||
<p id="result-placeholder" class="validation-result"></p>
|
||||
|
||||
<p>Insert SPF description</p>
|
||||
<p>
|
||||
SPF is an email standard for authorizing hosts to send emails from a specific domain. Most email
|
||||
servers, such as Gmail or Outlook, require SPF for security reasons, since without it, it is easy to
|
||||
spoof email addresses.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
SPF is defined with a TXT record on the email domain. It must start with "v=spf1" (to differentiate it
|
||||
from other TXT records), and after that contains a set of mechanisms and modifiers (together called
|
||||
terms), separated by space.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Mechanisms are colon-separated key-value pairs, whose primary purpose is to define which IPs are allowed
|
||||
to send emails from the domain. The most used mechanisms are:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li><b>include</b>: Includes the SPF definition from the specified domain</li>
|
||||
<li><b>a</b>: Allows emails from the specified domain</li>
|
||||
<li><b>mx</b>: Allows emails from the IP addresses specified in the MX records</li>
|
||||
<li><b>ipv6</b> / <b>ipv6</b>: Allows emails from the specified IP address</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
By default, mechanisms allow the specified IPs to send emails. You can add a qualifier, such as <b>~</b>
|
||||
or <b>-</b> to prohibit them instead.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Often, you would include some IP addresses (using e.g. the <b>include</b>, <b>a</b> and <b>mx</b>
|
||||
mechanisms), and end with <b>-all</b> or <b>~all</b> to reject emails from everywhere else. If the
|
||||
<b>all</b> mechanism is used, it must come after the other mechanisms.
|
||||
</p>
|
||||
|
||||
<center>
|
||||
<h3>More tools:</h3>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user