power-mailinabox/management/templates/smtp-relays.html
2022-02-20 20:45:14 +00:00

322 lines
10 KiB
HTML

<style>
</style>
<h2>SMTP Relays</h2>
<p>SMTP Relays are third-party services that can deliver email on your behalf. They
can be useful when, for example, port 25 is blocked, the cloud provider/ISP doesn't provide Reverse DNS, or the IP
address
has a low reputation, among other situations where deliverablity isn't great.</p>
<p>These services are governed by their own terms and as such limits can be imposed in the usage of those services.</p>
<p>Here, you can configure an authenticated SMTP relay and authorize it's associated servers to send mail for you.</p>
<div id="smtp_relay_config">
<form class="form-horizontal" role="form" onsubmit="set_smtp_relay_config(); return false;">
<h3>SMTP Relay Configuration</h3>
<div id="smtp-relays" class="col-12">
<div class="input-group mb-3">
<label for="use_relay" class="input-group-text"><b>Use Relay?</b></label>
<div class="input-group-text">
<div class="form-switch">
<input type="checkbox" role="switch" id="use_relay" class="form-check-input" value=false
onclick="checkfields();">
</div>
</div>
</div>
<div class="input-group mb-3">
<label class="input-group-text">Where to?</label>
<input type="text" class="form-control" id="relay_host" placeholder="relay.example.com">
<label class="input-group-text">:</label>
<div style="width: 20%">
<input type="number" class="form-control" id="relay_port" placeholder="Port (465)">
</div>
</div>
<div class="input-group mb-3">
<label class="input-group-text">Username</label>
<input type="text" class="form-control" id="relay_auth_user" placeholder="">
</div>
<div>
<div class="input-group">
<label class="input-group-text">Password/Key</label>
<input type="password" class="form-control" id="relay_auth_pass" placeholder="">
</div>
<p class="small">If you've already set up a relay before on this box, you can leave this field blank if
you don't want to change it's password.</p>
</div>
</div>
<h3>Authorized Servers</h3>
<p>The relay service should specify the servers where the email will be sent from, please add them below. These
will probably be published in the form of a SPF record. <b>Failure to do so will potentially have your email
sent to spam or even rejected altogether by recipients.</b>
</p>
<p>You can use the button below to attempt to localize the SPF record associated with the service you're using.
</p>
<button id="smtp_relay_autospf_btn" type="button" class="btn btn-secondary" onclick="autodetect_spf()">Detect
SPF
Records</button>
<div id="smtp_relay_autospf"></div>
<br>
<div class="form-group">
<h4>Add your SPF configuration/authorized servers here</h4>
<input type="text" class="form-control" id="relay_authorized_servers"
placeholder="mail1.example.net mail2.example.net">
<p class="small">You can separate multiple servers with commas or spaces. You can also add IP addresses or
subnets using <code>10.20.30.40</code> or <code>10.0.0.0/8</code>. You can "import" SPF records using
<code>spf:example.com</code>. If your provider gave you an SPF record to add to your DNS, you can also paste it here.
</p>
</div>
<h3>DKIM Configuration</h3>
<p>DKIM allows receivers to verify that the email was sent by the relay you configured (this is, somebody you
trust). <b>Not doing so will have your email sent to spam.</b></p>
<div class="col-lg-6 col-md-8 col-12">
<div class="input-group">
<input type="text" class="form-control" id="relay_dkim_selector" placeholder="selector"></td>
<label class="input-group-text" for="relay_dkim_selector">._domainkey.{{hostname}}</label>
</div>
</div>
<h4>Paste the DKIM key here:</h4>
<p><textarea id="relay_dkim_key" class="form-control" style="width: 100%; height: 8em"
placeholder="k=algo;p=K3y/C0N7ent5/dGhpcyBpcyBub3QgYSByZWFsIGtleSwgc28gYmV3YXJlIDrigb4"></textarea>
</p>
<div>
<button type="submit" class="btn btn-primary">Update</button>
</div>
<h3>After configuration</h3>
<p>By that time you should be good to go. If your relay provider provides their own custom DNS verification
methods (<b>including custom DMARC configurations</b>), feel free to publish them on DNS.</p>
</form>
</div>
<script>
const use_relay = document.getElementById("use_relay")
const relay_host = document.getElementById("relay_host")
const relay_port = document.getElementById("relay_port")
const relay_auth_user = document.getElementById("relay_auth_user")
const relay_auth_pass = document.getElementById("relay_auth_pass")
const relay_authorized_servers = document.getElementById("relay_authorized_servers")
const relay_spf_discover = document.getElementById("smtp_relay_autospf_btn")
const relay_spf_discover_results = document.getElementById("smtp_relay_autospf")
const relay_dkim_sel = document.getElementById("relay_dkim_selector")
const relay_dkim_key = document.getElementById("relay_dkim_key")
function checkfields() {
let relay_enabled = use_relay.checked
relay_host.disabled = !relay_enabled
relay_port.disabled = !relay_enabled
relay_auth_user.disabled = !relay_enabled
relay_auth_pass.disabled = !relay_enabled
relay_authorized_servers.disabled = !relay_enabled
relay_spf_discover.disabled = !relay_enabled
relay_spf_discover_results.innerHTML = ""
relay_dkim_sel.disabled = !relay_enabled
relay_dkim_key.disabled = !relay_enabled
}
function show_smtp_relays() {
api(
"/system/smtp/relay",
"GET",
{},
data => {
use_relay.checked = data.enabled
relay_host.value = data.host
relay_port.value = data.port
relay_auth_user.value = data.user
relay_auth_pass.value = ""
relay_authorized_servers.value = ""
if (data.spf_record) {
relay_authorized_servers.value = data.spf_record
}else if (data.authorized_servers) {
data.authorized_servers.forEach(element => {
relay_authorized_servers.value += `${element} `
});
}
if (data.dkim_selector) {
relay_dkim_sel.value = data.dkim_selector
relay_dkim_key.value = data.dkim_rr
}
checkfields()
}
)
}
function set_smtp_relay_config() {
let relay_configuration = {
enabled: use_relay.checked,
host: relay_host.value,
port: relay_port.value,
user: relay_auth_user.value,
key: relay_auth_pass.value,
dkim_selector: relay_dkim_sel.value,
dkim_rr: relay_dkim_key.value
}
if (relay_authorized_servers.value.substr(0, 7) === "v=spf1 ") {
relay_configuration.spf_record = relay_authorized_servers.value
} else {
relay_configuration.authorized_servers = relay_authorized_servers.value
}
api(
"/system/smtp/relay",
"POST",
relay_configuration,
() => {
show_modal_error("Done!", "The configuration has been updated and Postfix was restarted successfully. Please make sure everything is functioning as intended.", () => {
return false
})
},
(e) => {
show_modal_error("Error!", e, () => { return false })
}
)
}
function add_spf(domain) {
document.getElementById(`smtp_relay_spfinclude_${domain}`).disabled = true
let impl = false
relay_authorized_servers.value.split(/[\s,]+/).forEach(s => {
if (s === `spf:${domain}`) {
impl = true
}
});
if (!impl) {
relay_authorized_servers.value += ` spf:${domain}`
}
}
async function autodetect_spf() {
let btn = $("#smtp_relay_autospf_btn")
let results = $("#smtp_relay_autospf")
let host = relay_host.value
if (host.trim() == "") {
results.html("<b>Error:</b> No hostname specified.")
return
}
let hmatches = host.match(/([^\s.\\\/]+\.)+([^\s.\\\/]+)/)
if (!hmatches || hmatches[0] != host) {
results.html(`<b>Error: <code>${host}</code></b> is not a valid hostname.`)
return
}
btn.html("Working...")
btn.prop("disabled", true)
results.html("")
let base_host = hmatches[hmatches.length - 2] + hmatches[hmatches.length - 1]
function record_html(name, rec) {
if (rec.error) {
return `<b>${name}</b> - ${rec.error} Error (${rec.msg})`
} else {
return `<button id="smtp_relay_spfinclude_${name}" type="button" class="btn btn-secondary" onclick="add_spf('${name}')">Include</button> <b>${name}</b> <code>${rec.msg}</code>`
}
}
let records = []
await Promise.all([
query_spf(base_host).then(rr => { records[0] = record_html(base_host, rr) }),
query_spf(`spf.${base_host}`).then(rr => { records[1] = record_html(`spf.${base_host}`, rr) }),
query_spf(`_spf.${base_host}`).then(rr => { records[2] = record_html(`_spf.${base_host}`, rr) })
])
let txt = "<h4>Here's what I've found:</h4><ul>"
records.forEach((r) => {
txt += `<li>${r}</li>`
})
txt += "</ul><small>Only some common subdomains are tested here, so it's possible I've missed some. You <b>SHOULD NOT</b> include records you do not recognize, else there will be servers that can send mail as you, but that you will not use.</small>"
results.html(txt)
btn.html("Detect SPF Records")
btn.prop("disabled", false)
}
function query_spf(hostname) {
// We use Cloudflare's DNS servers for this (DNS over HTTPS)
return new Promise((resolve, _) => {
$.ajax({
url: "https://cloudflare-dns.com/dns-query",
headers: {
accept: "application/dns-json"
},
method: "GET",
data: {
name: hostname,
type: "TXT",
do: false,
cd: false
},
success: (data) => {
const RRTXT = 16
const status_description = [
// From https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6
// (last checked: 3rd June 2021)
"Success",
"Format Error",
"Server Failure",
"Non-Existent Domain",
"Not Implemented",
"Query Refused",
"Name exists when it should not",
"RR set exists when it should not",
"RR set that should exist does not",
"Server is not authoritative for zone",
"Not Authorized",
"Name not contained in zone",
"DSO-TYPE Not Implemented"
]
if (data.Status != 0) {
if (data.Status > 11) {
return resolve({ error: "DNS", msg: "Unknown Error" })
} else {
return resolve({ error: "DNS", msg: status_description[data.Status] })
}
}
if (data.Answer) {
data.Answer.forEach(ans => {
if (ans.type == 16 && ans.name == hostname && ans.data.substring(1, 7) == "v=spf1") {
return resolve({ msg: ans.data.substring(1, ans.data.length - 1) })
}
})
}
return resolve({ error: "DNS", msg: "No SPF record found" })
},
error: (_, __, err) => {
resolve({ error: "HTTP", msg: err })
}
})
})
}
</script>