webadmin: make routes configurable: globally, per domain, per account

this simplifies some of the code that makes modifications to the config file. a
few protected functions can make changes to the dynamic config, which webadmin
can use. instead of having separate functions in mox-/admin.go for each type of
change.

this also exports the parsed full dynamic config to webadmin, so we need fewer
functions for specific config fields too.
This commit is contained in:
Mechiel Lukkien
2024-04-18 11:14:24 +02:00
parent baf4df55a6
commit a69887bfab
19 changed files with 1165 additions and 189 deletions

View File

@ -194,6 +194,11 @@ func xcheckf(ctx context.Context, err error, format string, args ...any) {
if err == nil {
return
}
// If caller tried saving a config that is invalid, cause a user error.
if errors.Is(err, mox.ErrConfig) {
xcheckuserf(ctx, err, format, args...)
}
msg := fmt.Sprintf(format, args...)
errmsg := fmt.Sprintf("%s: %s", msg, err)
pkglog.WithContext(ctx).Errorx(msg, err)
@ -1518,6 +1523,17 @@ func (Admin) ParseDomain(ctx context.Context, domain string) dns.Domain {
return d
}
// DomainConfig returns the configuration for a domain.
func (Admin) DomainConfig(ctx context.Context, domain string) config.Domain {
d, err := dns.ParseDomain(domain)
xcheckuserf(ctx, err, "parse domain")
conf, ok := mox.Conf.Domain(d)
if !ok {
xcheckuserf(ctx, errors.New("no such domain"), "looking up domain")
}
return conf
}
// DomainLocalparts returns the encoded localparts and accounts configured in domain.
func (Admin) DomainLocalparts(ctx context.Context, domain string) (localpartAccounts map[string]string) {
d, err := dns.ParseDomain(domain)
@ -1797,7 +1813,8 @@ func dnsblsStatus(ctx context.Context, log mlog.Log, resolver dns.Resolver) (res
// todo: check health before using dnsbl?
using = mox.Conf.Static.Listeners["public"].SMTP.DNSBLZones
zones := append([]dns.Domain{}, using...)
for _, zone := range mox.Conf.MonitorDNSBLs() {
conf := mox.Conf.DynamicConfig()
for _, zone := range conf.MonitorDNSBLZones {
if !slices.Contains(zones, zone) {
zones = append(zones, zone)
monitoring = append(monitoring, zone)
@ -1844,7 +1861,14 @@ func (Admin) MonitorDNSBLsSave(ctx context.Context, text string) {
}
zones = append(zones, d)
}
err := mox.MonitorDNSBLsSave(ctx, zones)
err := mox.ConfigSave(ctx, func(conf *config.Dynamic) {
conf.MonitorDNSBLs = make([]string, len(zones))
conf.MonitorDNSBLZones = nil
for i, z := range zones {
conf.MonitorDNSBLs[i] = z.Name()
}
})
xcheckf(ctx, err, "saving monitoring dnsbl zones")
}
@ -2172,7 +2196,10 @@ func (Admin) WebserverConfig(ctx context.Context) (conf WebserverConfig) {
}
func webserverConfig() WebserverConfig {
r, l := mox.Conf.WebServer()
conf := mox.Conf.DynamicConfig()
r := conf.WebDNSDomainRedirects
l := conf.WebHandlers
x := make([][2]dns.Domain, 0, len(r))
xs := make([][2]string, 0, len(r))
for k, v := range r {
@ -2218,7 +2245,10 @@ func (Admin) WebserverConfigSave(ctx context.Context, oldConf, newConf Webserver
domainRedirects[x[0]] = x[1]
}
err := mox.WebserverConfigSet(ctx, domainRedirects, newConf.WebHandlers)
err := mox.ConfigSave(ctx, func(conf *config.Dynamic) {
conf.WebDomainRedirects = domainRedirects
conf.WebHandlers = newConf.WebHandlers
})
xcheckf(ctx, err, "saving webserver config")
savedConf = webserverConfig()
@ -2384,3 +2414,32 @@ func (Admin) LookupCid(ctx context.Context, recvID string) (cid string) {
xcheckf(ctx, err, "received id to cid")
return fmt.Sprintf("%x", v)
}
// Config returns the dynamic config.
func (Admin) Config(ctx context.Context) config.Dynamic {
return mox.Conf.DynamicConfig()
}
// AccountRoutesSave saves routes for an account.
func (Admin) AccountRoutesSave(ctx context.Context, accountName string, routes []config.Route) {
err := mox.AccountSave(ctx, accountName, func(acc *config.Account) {
acc.Routes = routes
})
xcheckf(ctx, err, "saving account routes")
}
// DomainRoutesSave saves routes for a domain.
func (Admin) DomainRoutesSave(ctx context.Context, domainName string, routes []config.Route) {
err := mox.DomainSave(ctx, domainName, func(domain *config.Domain) {
domain.Routes = routes
})
xcheckf(ctx, err, "saving domain routes")
}
// RoutesSave saves global routes.
func (Admin) RoutesSave(ctx context.Context, routes []config.Route) {
err := mox.ConfigSave(ctx, func(config *config.Dynamic) {
config.Routes = routes
})
xcheckf(ctx, err, "saving global routes")
}

View File

@ -337,7 +337,7 @@ var api;
SPFResult["SPFTemperror"] = "temperror";
SPFResult["SPFPermerror"] = "permerror";
})(SPFResult = api.SPFResult || (api.SPFResult = {}));
api.structTypes = { "Account": true, "AuthResults": true, "AutoconfCheckResult": true, "AutodiscoverCheckResult": true, "AutodiscoverSRV": true, "AutomaticJunkFlags": true, "CheckResult": true, "ClientConfigs": true, "ClientConfigsEntry": true, "DANECheckResult": true, "DKIMAuthResult": true, "DKIMCheckResult": true, "DKIMRecord": true, "DMARCCheckResult": true, "DMARCRecord": true, "DMARCSummary": true, "DNSSECResult": true, "DateRange": true, "Destination": true, "Directive": true, "Domain": true, "DomainFeedback": true, "Evaluation": true, "EvaluationStat": true, "Extension": true, "FailureDetails": true, "Filter": true, "HoldRule": true, "Hook": true, "HookFilter": true, "HookResult": true, "HookRetired": true, "HookRetiredFilter": true, "HookRetiredSort": true, "HookSort": true, "IPDomain": true, "IPRevCheckResult": true, "Identifiers": true, "IncomingWebhook": true, "JunkFilter": true, "MTASTSCheckResult": true, "MTASTSRecord": true, "MX": true, "MXCheckResult": true, "Modifier": true, "Msg": true, "MsgResult": true, "MsgRetired": true, "OutgoingWebhook": true, "Pair": true, "Policy": true, "PolicyEvaluated": true, "PolicyOverrideReason": true, "PolicyPublished": true, "PolicyRecord": true, "Record": true, "Report": true, "ReportMetadata": true, "ReportRecord": true, "Result": true, "ResultPolicy": true, "RetiredFilter": true, "RetiredSort": true, "Reverse": true, "Route": true, "Row": true, "Ruleset": true, "SMTPAuth": true, "SPFAuthResult": true, "SPFCheckResult": true, "SPFRecord": true, "SRV": true, "SRVConfCheckResult": true, "STSMX": true, "Sort": true, "SubjectPass": true, "Summary": true, "SuppressAddress": true, "TLSCheckResult": true, "TLSRPTCheckResult": true, "TLSRPTDateRange": true, "TLSRPTRecord": true, "TLSRPTSummary": true, "TLSRPTSuppressAddress": true, "TLSReportRecord": true, "TLSResult": true, "Transport": true, "TransportDirect": true, "TransportSMTP": true, "TransportSocks": true, "URI": true, "WebForward": true, "WebHandler": true, "WebRedirect": true, "WebStatic": true, "WebserverConfig": true };
api.structTypes = { "Account": true, "AuthResults": true, "AutoconfCheckResult": true, "AutodiscoverCheckResult": true, "AutodiscoverSRV": true, "AutomaticJunkFlags": true, "Canonicalization": true, "CheckResult": true, "ClientConfigs": true, "ClientConfigsEntry": true, "ConfigDomain": true, "DANECheckResult": true, "DKIM": true, "DKIMAuthResult": true, "DKIMCheckResult": true, "DKIMRecord": true, "DMARC": true, "DMARCCheckResult": true, "DMARCRecord": true, "DMARCSummary": true, "DNSSECResult": true, "DateRange": true, "Destination": true, "Directive": true, "Domain": true, "DomainFeedback": true, "Dynamic": true, "Evaluation": true, "EvaluationStat": true, "Extension": true, "FailureDetails": true, "Filter": true, "HoldRule": true, "Hook": true, "HookFilter": true, "HookResult": true, "HookRetired": true, "HookRetiredFilter": true, "HookRetiredSort": true, "HookSort": true, "IPDomain": true, "IPRevCheckResult": true, "Identifiers": true, "IncomingWebhook": true, "JunkFilter": true, "MTASTS": true, "MTASTSCheckResult": true, "MTASTSRecord": true, "MX": true, "MXCheckResult": true, "Modifier": true, "Msg": true, "MsgResult": true, "MsgRetired": true, "OutgoingWebhook": true, "Pair": true, "Policy": true, "PolicyEvaluated": true, "PolicyOverrideReason": true, "PolicyPublished": true, "PolicyRecord": true, "Record": true, "Report": true, "ReportMetadata": true, "ReportRecord": true, "Result": true, "ResultPolicy": true, "RetiredFilter": true, "RetiredSort": true, "Reverse": true, "Route": true, "Row": true, "Ruleset": true, "SMTPAuth": true, "SPFAuthResult": true, "SPFCheckResult": true, "SPFRecord": true, "SRV": true, "SRVConfCheckResult": true, "STSMX": true, "Selector": true, "Sort": true, "SubjectPass": true, "Summary": true, "SuppressAddress": true, "TLSCheckResult": true, "TLSRPT": true, "TLSRPTCheckResult": true, "TLSRPTDateRange": true, "TLSRPTRecord": true, "TLSRPTSummary": true, "TLSRPTSuppressAddress": true, "TLSReportRecord": true, "TLSResult": true, "Transport": true, "TransportDirect": true, "TransportSMTP": true, "TransportSocks": true, "URI": true, "WebForward": true, "WebHandler": true, "WebRedirect": true, "WebStatic": true, "WebserverConfig": true };
api.stringsTypes = { "Align": true, "Alignment": true, "CSRFToken": true, "DKIMResult": true, "DMARCPolicy": true, "DMARCResult": true, "Disposition": true, "IP": true, "Localpart": true, "Mode": true, "PolicyOverride": true, "PolicyType": true, "RUA": true, "ResultType": true, "SPFDomainScope": true, "SPFResult": true };
api.intsTypes = {};
api.types = {
@ -372,6 +372,14 @@ var api;
"AutoconfCheckResult": { "Name": "AutoconfCheckResult", "Docs": "", "Fields": [{ "Name": "ClientSettingsDomainIPs", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "IPs", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Errors", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Warnings", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Instructions", "Docs": "", "Typewords": ["[]", "string"] }] },
"AutodiscoverCheckResult": { "Name": "AutodiscoverCheckResult", "Docs": "", "Fields": [{ "Name": "Records", "Docs": "", "Typewords": ["[]", "AutodiscoverSRV"] }, { "Name": "Errors", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Warnings", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Instructions", "Docs": "", "Typewords": ["[]", "string"] }] },
"AutodiscoverSRV": { "Name": "AutodiscoverSRV", "Docs": "", "Fields": [{ "Name": "Target", "Docs": "", "Typewords": ["string"] }, { "Name": "Port", "Docs": "", "Typewords": ["uint16"] }, { "Name": "Priority", "Docs": "", "Typewords": ["uint16"] }, { "Name": "Weight", "Docs": "", "Typewords": ["uint16"] }, { "Name": "IPs", "Docs": "", "Typewords": ["[]", "string"] }] },
"ConfigDomain": { "Name": "ConfigDomain", "Docs": "", "Fields": [{ "Name": "Description", "Docs": "", "Typewords": ["string"] }, { "Name": "ClientSettingsDomain", "Docs": "", "Typewords": ["string"] }, { "Name": "LocalpartCatchallSeparator", "Docs": "", "Typewords": ["string"] }, { "Name": "LocalpartCaseSensitive", "Docs": "", "Typewords": ["bool"] }, { "Name": "DKIM", "Docs": "", "Typewords": ["DKIM"] }, { "Name": "DMARC", "Docs": "", "Typewords": ["nullable", "DMARC"] }, { "Name": "MTASTS", "Docs": "", "Typewords": ["nullable", "MTASTS"] }, { "Name": "TLSRPT", "Docs": "", "Typewords": ["nullable", "TLSRPT"] }, { "Name": "Routes", "Docs": "", "Typewords": ["[]", "Route"] }] },
"DKIM": { "Name": "DKIM", "Docs": "", "Fields": [{ "Name": "Selectors", "Docs": "", "Typewords": ["{}", "Selector"] }, { "Name": "Sign", "Docs": "", "Typewords": ["[]", "string"] }] },
"Selector": { "Name": "Selector", "Docs": "", "Fields": [{ "Name": "Hash", "Docs": "", "Typewords": ["string"] }, { "Name": "HashEffective", "Docs": "", "Typewords": ["string"] }, { "Name": "Canonicalization", "Docs": "", "Typewords": ["Canonicalization"] }, { "Name": "Headers", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "HeadersEffective", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "DontSealHeaders", "Docs": "", "Typewords": ["bool"] }, { "Name": "Expiration", "Docs": "", "Typewords": ["string"] }, { "Name": "PrivateKeyFile", "Docs": "", "Typewords": ["string"] }] },
"Canonicalization": { "Name": "Canonicalization", "Docs": "", "Fields": [{ "Name": "HeaderRelaxed", "Docs": "", "Typewords": ["bool"] }, { "Name": "BodyRelaxed", "Docs": "", "Typewords": ["bool"] }] },
"DMARC": { "Name": "DMARC", "Docs": "", "Fields": [{ "Name": "Localpart", "Docs": "", "Typewords": ["string"] }, { "Name": "Domain", "Docs": "", "Typewords": ["string"] }, { "Name": "Account", "Docs": "", "Typewords": ["string"] }, { "Name": "Mailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "ParsedLocalpart", "Docs": "", "Typewords": ["Localpart"] }, { "Name": "DNSDomain", "Docs": "", "Typewords": ["Domain"] }] },
"MTASTS": { "Name": "MTASTS", "Docs": "", "Fields": [{ "Name": "PolicyID", "Docs": "", "Typewords": ["string"] }, { "Name": "Mode", "Docs": "", "Typewords": ["Mode"] }, { "Name": "MaxAge", "Docs": "", "Typewords": ["int64"] }, { "Name": "MX", "Docs": "", "Typewords": ["[]", "string"] }] },
"TLSRPT": { "Name": "TLSRPT", "Docs": "", "Fields": [{ "Name": "Localpart", "Docs": "", "Typewords": ["string"] }, { "Name": "Domain", "Docs": "", "Typewords": ["string"] }, { "Name": "Account", "Docs": "", "Typewords": ["string"] }, { "Name": "Mailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "ParsedLocalpart", "Docs": "", "Typewords": ["Localpart"] }, { "Name": "DNSDomain", "Docs": "", "Typewords": ["Domain"] }] },
"Route": { "Name": "Route", "Docs": "", "Fields": [{ "Name": "FromDomain", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "ToDomain", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "MinimumAttempts", "Docs": "", "Typewords": ["int32"] }, { "Name": "Transport", "Docs": "", "Typewords": ["string"] }, { "Name": "FromDomainASCII", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "ToDomainASCII", "Docs": "", "Typewords": ["[]", "string"] }] },
"Account": { "Name": "Account", "Docs": "", "Fields": [{ "Name": "OutgoingWebhook", "Docs": "", "Typewords": ["nullable", "OutgoingWebhook"] }, { "Name": "IncomingWebhook", "Docs": "", "Typewords": ["nullable", "IncomingWebhook"] }, { "Name": "FromIDLoginAddresses", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "KeepRetiredMessagePeriod", "Docs": "", "Typewords": ["int64"] }, { "Name": "KeepRetiredWebhookPeriod", "Docs": "", "Typewords": ["int64"] }, { "Name": "Domain", "Docs": "", "Typewords": ["string"] }, { "Name": "Description", "Docs": "", "Typewords": ["string"] }, { "Name": "FullName", "Docs": "", "Typewords": ["string"] }, { "Name": "Destinations", "Docs": "", "Typewords": ["{}", "Destination"] }, { "Name": "SubjectPass", "Docs": "", "Typewords": ["SubjectPass"] }, { "Name": "QuotaMessageSize", "Docs": "", "Typewords": ["int64"] }, { "Name": "RejectsMailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "KeepRejects", "Docs": "", "Typewords": ["bool"] }, { "Name": "AutomaticJunkFlags", "Docs": "", "Typewords": ["AutomaticJunkFlags"] }, { "Name": "JunkFilter", "Docs": "", "Typewords": ["nullable", "JunkFilter"] }, { "Name": "MaxOutgoingMessagesPerDay", "Docs": "", "Typewords": ["int32"] }, { "Name": "MaxFirstTimeRecipientsPerDay", "Docs": "", "Typewords": ["int32"] }, { "Name": "NoFirstTimeSenderDelay", "Docs": "", "Typewords": ["bool"] }, { "Name": "Routes", "Docs": "", "Typewords": ["[]", "Route"] }, { "Name": "DNSDomain", "Docs": "", "Typewords": ["Domain"] }] },
"OutgoingWebhook": { "Name": "OutgoingWebhook", "Docs": "", "Fields": [{ "Name": "URL", "Docs": "", "Typewords": ["string"] }, { "Name": "Authorization", "Docs": "", "Typewords": ["string"] }, { "Name": "Events", "Docs": "", "Typewords": ["[]", "string"] }] },
"IncomingWebhook": { "Name": "IncomingWebhook", "Docs": "", "Fields": [{ "Name": "URL", "Docs": "", "Typewords": ["string"] }, { "Name": "Authorization", "Docs": "", "Typewords": ["string"] }] },
@ -380,7 +388,6 @@ var api;
"SubjectPass": { "Name": "SubjectPass", "Docs": "", "Fields": [{ "Name": "Period", "Docs": "", "Typewords": ["int64"] }] },
"AutomaticJunkFlags": { "Name": "AutomaticJunkFlags", "Docs": "", "Fields": [{ "Name": "Enabled", "Docs": "", "Typewords": ["bool"] }, { "Name": "JunkMailboxRegexp", "Docs": "", "Typewords": ["string"] }, { "Name": "NeutralMailboxRegexp", "Docs": "", "Typewords": ["string"] }, { "Name": "NotJunkMailboxRegexp", "Docs": "", "Typewords": ["string"] }] },
"JunkFilter": { "Name": "JunkFilter", "Docs": "", "Fields": [{ "Name": "Threshold", "Docs": "", "Typewords": ["float64"] }, { "Name": "Onegrams", "Docs": "", "Typewords": ["bool"] }, { "Name": "Twograms", "Docs": "", "Typewords": ["bool"] }, { "Name": "Threegrams", "Docs": "", "Typewords": ["bool"] }, { "Name": "MaxPower", "Docs": "", "Typewords": ["float64"] }, { "Name": "TopWords", "Docs": "", "Typewords": ["int32"] }, { "Name": "IgnoreWords", "Docs": "", "Typewords": ["float64"] }, { "Name": "RareWords", "Docs": "", "Typewords": ["int32"] }] },
"Route": { "Name": "Route", "Docs": "", "Fields": [{ "Name": "FromDomain", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "ToDomain", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "MinimumAttempts", "Docs": "", "Typewords": ["int32"] }, { "Name": "Transport", "Docs": "", "Typewords": ["string"] }, { "Name": "FromDomainASCII", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "ToDomainASCII", "Docs": "", "Typewords": ["[]", "string"] }] },
"PolicyRecord": { "Name": "PolicyRecord", "Docs": "", "Fields": [{ "Name": "Domain", "Docs": "", "Typewords": ["string"] }, { "Name": "Inserted", "Docs": "", "Typewords": ["timestamp"] }, { "Name": "ValidEnd", "Docs": "", "Typewords": ["timestamp"] }, { "Name": "LastUpdate", "Docs": "", "Typewords": ["timestamp"] }, { "Name": "LastUse", "Docs": "", "Typewords": ["timestamp"] }, { "Name": "Backoff", "Docs": "", "Typewords": ["bool"] }, { "Name": "RecordID", "Docs": "", "Typewords": ["string"] }, { "Name": "Version", "Docs": "", "Typewords": ["string"] }, { "Name": "Mode", "Docs": "", "Typewords": ["Mode"] }, { "Name": "MX", "Docs": "", "Typewords": ["[]", "STSMX"] }, { "Name": "MaxAgeSeconds", "Docs": "", "Typewords": ["int32"] }, { "Name": "Extensions", "Docs": "", "Typewords": ["[]", "Pair"] }, { "Name": "PolicyText", "Docs": "", "Typewords": ["string"] }] },
"TLSReportRecord": { "Name": "TLSReportRecord", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Domain", "Docs": "", "Typewords": ["string"] }, { "Name": "FromDomain", "Docs": "", "Typewords": ["string"] }, { "Name": "MailFrom", "Docs": "", "Typewords": ["string"] }, { "Name": "HostReport", "Docs": "", "Typewords": ["bool"] }, { "Name": "Report", "Docs": "", "Typewords": ["Report"] }] },
"Report": { "Name": "Report", "Docs": "", "Fields": [{ "Name": "OrganizationName", "Docs": "", "Typewords": ["string"] }, { "Name": "DateRange", "Docs": "", "Typewords": ["TLSRPTDateRange"] }, { "Name": "ContactInfo", "Docs": "", "Typewords": ["string"] }, { "Name": "ReportID", "Docs": "", "Typewords": ["string"] }, { "Name": "Policies", "Docs": "", "Typewords": ["[]", "Result"] }] },
@ -437,11 +444,13 @@ var api;
"SuppressAddress": { "Name": "SuppressAddress", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Inserted", "Docs": "", "Typewords": ["timestamp"] }, { "Name": "ReportingAddress", "Docs": "", "Typewords": ["string"] }, { "Name": "Until", "Docs": "", "Typewords": ["timestamp"] }, { "Name": "Comment", "Docs": "", "Typewords": ["string"] }] },
"TLSResult": { "Name": "TLSResult", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "PolicyDomain", "Docs": "", "Typewords": ["string"] }, { "Name": "DayUTC", "Docs": "", "Typewords": ["string"] }, { "Name": "RecipientDomain", "Docs": "", "Typewords": ["string"] }, { "Name": "Created", "Docs": "", "Typewords": ["timestamp"] }, { "Name": "Updated", "Docs": "", "Typewords": ["timestamp"] }, { "Name": "IsHost", "Docs": "", "Typewords": ["bool"] }, { "Name": "SendReport", "Docs": "", "Typewords": ["bool"] }, { "Name": "SentToRecipientDomain", "Docs": "", "Typewords": ["bool"] }, { "Name": "RecipientDomainReportingAddresses", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "SentToPolicyDomain", "Docs": "", "Typewords": ["bool"] }, { "Name": "Results", "Docs": "", "Typewords": ["[]", "Result"] }] },
"TLSRPTSuppressAddress": { "Name": "TLSRPTSuppressAddress", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Inserted", "Docs": "", "Typewords": ["timestamp"] }, { "Name": "ReportingAddress", "Docs": "", "Typewords": ["string"] }, { "Name": "Until", "Docs": "", "Typewords": ["timestamp"] }, { "Name": "Comment", "Docs": "", "Typewords": ["string"] }] },
"Dynamic": { "Name": "Dynamic", "Docs": "", "Fields": [{ "Name": "Domains", "Docs": "", "Typewords": ["{}", "ConfigDomain"] }, { "Name": "Accounts", "Docs": "", "Typewords": ["{}", "Account"] }, { "Name": "WebDomainRedirects", "Docs": "", "Typewords": ["{}", "string"] }, { "Name": "WebHandlers", "Docs": "", "Typewords": ["[]", "WebHandler"] }, { "Name": "Routes", "Docs": "", "Typewords": ["[]", "Route"] }, { "Name": "MonitorDNSBLs", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "MonitorDNSBLZones", "Docs": "", "Typewords": ["[]", "Domain"] }] },
"CSRFToken": { "Name": "CSRFToken", "Docs": "", "Values": null },
"DMARCPolicy": { "Name": "DMARCPolicy", "Docs": "", "Values": [{ "Name": "PolicyEmpty", "Value": "", "Docs": "" }, { "Name": "PolicyNone", "Value": "none", "Docs": "" }, { "Name": "PolicyQuarantine", "Value": "quarantine", "Docs": "" }, { "Name": "PolicyReject", "Value": "reject", "Docs": "" }] },
"Align": { "Name": "Align", "Docs": "", "Values": [{ "Name": "AlignStrict", "Value": "s", "Docs": "" }, { "Name": "AlignRelaxed", "Value": "r", "Docs": "" }] },
"RUA": { "Name": "RUA", "Docs": "", "Values": null },
"Mode": { "Name": "Mode", "Docs": "", "Values": [{ "Name": "ModeEnforce", "Value": "enforce", "Docs": "" }, { "Name": "ModeTesting", "Value": "testing", "Docs": "" }, { "Name": "ModeNone", "Value": "none", "Docs": "" }] },
"Localpart": { "Name": "Localpart", "Docs": "", "Values": null },
"PolicyType": { "Name": "PolicyType", "Docs": "", "Values": [{ "Name": "TLSA", "Value": "tlsa", "Docs": "" }, { "Name": "STS", "Value": "sts", "Docs": "" }, { "Name": "NoPolicyFound", "Value": "no-policy-found", "Docs": "" }] },
"ResultType": { "Name": "ResultType", "Docs": "", "Values": [{ "Name": "ResultSTARTTLSNotSupported", "Value": "starttls-not-supported", "Docs": "" }, { "Name": "ResultCertificateHostMismatch", "Value": "certificate-host-mismatch", "Docs": "" }, { "Name": "ResultCertificateExpired", "Value": "certificate-expired", "Docs": "" }, { "Name": "ResultTLSAInvalid", "Value": "tlsa-invalid", "Docs": "" }, { "Name": "ResultDNSSECInvalid", "Value": "dnssec-invalid", "Docs": "" }, { "Name": "ResultDANERequired", "Value": "dane-required", "Docs": "" }, { "Name": "ResultCertificateNotTrusted", "Value": "certificate-not-trusted", "Docs": "" }, { "Name": "ResultSTSPolicyInvalid", "Value": "sts-policy-invalid", "Docs": "" }, { "Name": "ResultSTSWebPKIInvalid", "Value": "sts-webpki-invalid", "Docs": "" }, { "Name": "ResultValidationFailure", "Value": "validation-failure", "Docs": "" }, { "Name": "ResultSTSPolicyFetch", "Value": "sts-policy-fetch-error", "Docs": "" }] },
"Alignment": { "Name": "Alignment", "Docs": "", "Values": [{ "Name": "AlignmentAbsent", "Value": "", "Docs": "" }, { "Name": "AlignmentRelaxed", "Value": "r", "Docs": "" }, { "Name": "AlignmentStrict", "Value": "s", "Docs": "" }] },
@ -451,7 +460,6 @@ var api;
"DKIMResult": { "Name": "DKIMResult", "Docs": "", "Values": [{ "Name": "DKIMAbsent", "Value": "", "Docs": "" }, { "Name": "DKIMNone", "Value": "none", "Docs": "" }, { "Name": "DKIMPass", "Value": "pass", "Docs": "" }, { "Name": "DKIMFail", "Value": "fail", "Docs": "" }, { "Name": "DKIMPolicy", "Value": "policy", "Docs": "" }, { "Name": "DKIMNeutral", "Value": "neutral", "Docs": "" }, { "Name": "DKIMTemperror", "Value": "temperror", "Docs": "" }, { "Name": "DKIMPermerror", "Value": "permerror", "Docs": "" }] },
"SPFDomainScope": { "Name": "SPFDomainScope", "Docs": "", "Values": [{ "Name": "SPFDomainScopeAbsent", "Value": "", "Docs": "" }, { "Name": "SPFDomainScopeHelo", "Value": "helo", "Docs": "" }, { "Name": "SPFDomainScopeMailFrom", "Value": "mfrom", "Docs": "" }] },
"SPFResult": { "Name": "SPFResult", "Docs": "", "Values": [{ "Name": "SPFAbsent", "Value": "", "Docs": "" }, { "Name": "SPFNone", "Value": "none", "Docs": "" }, { "Name": "SPFNeutral", "Value": "neutral", "Docs": "" }, { "Name": "SPFPass", "Value": "pass", "Docs": "" }, { "Name": "SPFFail", "Value": "fail", "Docs": "" }, { "Name": "SPFSoftfail", "Value": "softfail", "Docs": "" }, { "Name": "SPFTemperror", "Value": "temperror", "Docs": "" }, { "Name": "SPFPermerror", "Value": "permerror", "Docs": "" }] },
"Localpart": { "Name": "Localpart", "Docs": "", "Values": null },
"IP": { "Name": "IP", "Docs": "", "Values": [] },
};
api.parser = {
@ -486,6 +494,14 @@ var api;
AutoconfCheckResult: (v) => api.parse("AutoconfCheckResult", v),
AutodiscoverCheckResult: (v) => api.parse("AutodiscoverCheckResult", v),
AutodiscoverSRV: (v) => api.parse("AutodiscoverSRV", v),
ConfigDomain: (v) => api.parse("ConfigDomain", v),
DKIM: (v) => api.parse("DKIM", v),
Selector: (v) => api.parse("Selector", v),
Canonicalization: (v) => api.parse("Canonicalization", v),
DMARC: (v) => api.parse("DMARC", v),
MTASTS: (v) => api.parse("MTASTS", v),
TLSRPT: (v) => api.parse("TLSRPT", v),
Route: (v) => api.parse("Route", v),
Account: (v) => api.parse("Account", v),
OutgoingWebhook: (v) => api.parse("OutgoingWebhook", v),
IncomingWebhook: (v) => api.parse("IncomingWebhook", v),
@ -494,7 +510,6 @@ var api;
SubjectPass: (v) => api.parse("SubjectPass", v),
AutomaticJunkFlags: (v) => api.parse("AutomaticJunkFlags", v),
JunkFilter: (v) => api.parse("JunkFilter", v),
Route: (v) => api.parse("Route", v),
PolicyRecord: (v) => api.parse("PolicyRecord", v),
TLSReportRecord: (v) => api.parse("TLSReportRecord", v),
Report: (v) => api.parse("Report", v),
@ -551,11 +566,13 @@ var api;
SuppressAddress: (v) => api.parse("SuppressAddress", v),
TLSResult: (v) => api.parse("TLSResult", v),
TLSRPTSuppressAddress: (v) => api.parse("TLSRPTSuppressAddress", v),
Dynamic: (v) => api.parse("Dynamic", v),
CSRFToken: (v) => api.parse("CSRFToken", v),
DMARCPolicy: (v) => api.parse("DMARCPolicy", v),
Align: (v) => api.parse("Align", v),
RUA: (v) => api.parse("RUA", v),
Mode: (v) => api.parse("Mode", v),
Localpart: (v) => api.parse("Localpart", v),
PolicyType: (v) => api.parse("PolicyType", v),
ResultType: (v) => api.parse("ResultType", v),
Alignment: (v) => api.parse("Alignment", v),
@ -565,7 +582,6 @@ var api;
DKIMResult: (v) => api.parse("DKIMResult", v),
SPFDomainScope: (v) => api.parse("SPFDomainScope", v),
SPFResult: (v) => api.parse("SPFResult", v),
Localpart: (v) => api.parse("Localpart", v),
IP: (v) => api.parse("IP", v),
};
// Admin exports web API functions for the admin web interface. All its methods are
@ -652,6 +668,14 @@ var api;
const params = [domain];
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
}
// DomainConfig returns the configuration for a domain.
async DomainConfig(domain) {
const fn = "DomainConfig";
const paramTypes = [["string"]];
const returnTypes = [["ConfigDomain"]];
const params = [domain];
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
}
// DomainLocalparts returns the encoded localparts and accounts configured in domain.
async DomainLocalparts(domain) {
const fn = "DomainLocalparts";
@ -1214,6 +1238,38 @@ var api;
const params = [recvID];
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
}
// Config returns the dynamic config.
async Config() {
const fn = "Config";
const paramTypes = [];
const returnTypes = [["Dynamic"]];
const params = [];
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
}
// AccountRoutesSave saves routes for an account.
async AccountRoutesSave(accountName, routes) {
const fn = "AccountRoutesSave";
const paramTypes = [["string"], ["[]", "Route"]];
const returnTypes = [];
const params = [accountName, routes];
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
}
// DomainRoutesSave saves routes for a domain.
async DomainRoutesSave(domainName, routes) {
const fn = "DomainRoutesSave";
const paramTypes = [["string"], ["[]", "Route"]];
const returnTypes = [];
const params = [domainName, routes];
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
}
// RoutesSave saves global routes.
async RoutesSave(routes) {
const fn = "RoutesSave";
const paramTypes = [["[]", "Route"]];
const returnTypes = [];
const params = [routes];
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
}
}
api.Client = Client;
api.defaultBaseURL = (function () {
@ -1855,7 +1911,14 @@ const index = async () => {
await check(recvIDFieldset, client.LookupCid(recvID.value));
}, recvIDFieldset = dom.fieldset(dom.label('Received ID', attr.title('The ID in the Received header that was added during incoming delivery.')), ' ', recvID = dom.input(attr.required('')), ' ', dom.submitbutton('Lookup cid', attr.title('Logging about an incoming message includes an attribute "cid", a counter identifying the transaction related to delivery of the message. The ID in the received header is an encrypted cid, which this form decrypts, after which you can look it up in the logging.')), ' ', cidElem = dom.span()))),
// todo: routing, globally, per domain and per account
dom.br(), dom.h2('Configuration'), dom.div(dom.a('Webserver', attr.href('#webserver'))), dom.div(dom.a('Files', attr.href('#config'))), dom.div(dom.a('Log levels', attr.href('#loglevels'))), footer);
dom.br(), dom.h2('Configuration'), dom.div(dom.a('Routes', attr.href('#routes'))), dom.div(dom.a('Webserver', attr.href('#webserver'))), dom.div(dom.a('Files', attr.href('#config'))), dom.div(dom.a('Log levels', attr.href('#loglevels'))), footer);
};
const globalRoutes = async () => {
const [transports, config] = await Promise.all([
client.Transports(),
client.Config(),
]);
dom._kids(page, crumbs(crumblink('Mox Admin', '#'), 'Routes'), RoutesEditor('global', transports, config.Routes || [], async (routes) => await client.RoutesSave(routes)));
};
const config = async () => {
const [staticPath, dynamicPath, staticText, dynamicText] = await client.ConfigFiles();
@ -1945,10 +2008,69 @@ const formatQuotaSize = (v) => {
}
return '' + v;
};
const RoutesEditor = (kind, transports, routes, save) => {
const transportNames = Object.keys(transports || {});
transportNames.sort();
const hdr = dom.h2('Routes', attr.title('Messages submitted to the queue for outgoing delivery are delivered directly to the MX records of the recipient domain by default. However, other "transports" can be configured, such as SMTP submission/relay or connecting through a SOCKS proxy. Routes with matching rules and a transport can be configured for accounts, domains and globally. Routes are evaluated in that order, the first match is applied.'));
let routesElem;
const render = () => {
if (transportNames.length === 0) {
return [hdr, dom.p('No transports configured.', attr.title('To configure routes, first configure transports via the mox.conf config file.'))];
}
let routesFieldset;
let routeRows = [];
let elem = dom.form(async function submit(e) {
e.stopPropagation();
e.preventDefault();
await check(routesFieldset, save(routeRows.map(rr => rr.gather())));
}, routesFieldset = dom.fieldset(dom.table(dom.thead(dom.tr(dom.th('From domain'), dom.th('To domain'), dom.th('Minimum attempts'), dom.th('Transport'), dom.th(dom.clickbutton('Add', function click() {
routes = routeRows.map(rr => rr.gather());
routes.push({ FromDomain: [], ToDomain: [], MinimumAttempts: 0, Transport: transportNames[0] });
render();
})))), dom.tbody((routes || []).length === 0 ? dom.tr(dom.td(attr.colspan('5'), 'No routes.')) : [], routeRows = (routes || []).map((r, index) => {
let fromDomain = dom.input(attr.value((r.FromDomain || []).join(',')));
let toDomain = dom.input(attr.value((r.ToDomain || []).join(',')));
let minimumAttempts = dom.input(attr.value('' + r.MinimumAttempts));
let transport = dom.select(attr.required(''), transportNames.map(s => dom.option(s, s === r.Transport ? attr.selected('') : [])));
const tr = dom.tr(dom.td(fromDomain), dom.td(toDomain), dom.td(minimumAttempts), dom.td(transport), dom.td(dom.clickbutton('Remove', function click() {
routeRows.splice(index, 1);
routes = routeRows.map(rr => rr.gather());
render();
})));
return {
root: tr,
gather: () => {
return {
FromDomain: fromDomain.value ? fromDomain.value.split(',') : [],
ToDomain: toDomain.value ? toDomain.value.split(',') : [],
MinimumAttempts: parseInt(minimumAttempts.value) || 0,
Transport: transport.value,
};
},
};
}))), dom.div(dom.submitbutton('Save'))));
if (!routesElem && (routes || []).length === 0) {
// Keep it short.
elem = dom.div('No ' + kind + ' routes configured. ', dom.clickbutton('Add', function click() {
routes = routeRows.map(rr => rr.gather());
routes.push({ FromDomain: [], ToDomain: [], MinimumAttempts: 0, Transport: transportNames[0] });
render();
}));
}
elem = dom.div(hdr, elem);
if (routesElem) {
routesElem.replaceWith(elem);
}
routesElem = elem;
return elem;
};
return render();
};
const account = async (name) => {
const [[config, diskUsage], domains] = await Promise.all([
const [[config, diskUsage], domains, transports] = await Promise.all([
client.Account(name),
client.Domains(),
client.Transports(),
]);
// todo: show suppression list, and buttons to add/remove entries.
let form;
@ -2044,7 +2166,7 @@ const account = async (name) => {
await check(fieldsetPassword, client.SetPassword(name, password.value));
window.alert('Password has been changed.');
formPassword.reset();
}), dom.br(), dom.h2('Danger'), dom.clickbutton('Remove account', async function click(e) {
}), dom.br(), RoutesEditor('account-specific', transports, config.Routes || [], async (routes) => await client.AccountRoutesSave(name, routes)), dom.br(), dom.h2('Danger'), dom.clickbutton('Remove account', async function click(e) {
e.preventDefault();
if (!window.confirm('Are you sure you want to remove this account?')) {
return;
@ -2056,13 +2178,15 @@ const account = async (name) => {
const domain = async (d) => {
const end = new Date();
const start = new Date(new Date().getTime() - 30 * 24 * 3600 * 1000);
const [dmarcSummaries, tlsrptSummaries, localpartAccounts, dnsdomain, clientConfigs, accounts] = await Promise.all([
const [dmarcSummaries, tlsrptSummaries, localpartAccounts, dnsdomain, clientConfigs, accounts, domainConfig, transports] = await Promise.all([
client.DMARCSummaries(start, end, d),
client.TLSRPTSummaries(start, end, d),
client.DomainLocalparts(d),
client.Domain(d),
client.ParseDomain(d),
client.ClientConfigsDomain(d),
client.Accounts(),
client.DomainConfig(d),
client.Transports(),
]);
let form;
let fieldset;
@ -2081,7 +2205,7 @@ const domain = async (d) => {
await check(fieldset, client.AddressAdd(localpart.value + '@' + d, account.value));
form.reset();
window.location.reload(); // todo: only reload the addresses
}, fieldset = dom.fieldset(dom.label(style({ display: 'inline-block' }), dom.span('Localpart', attr.title('The localpart is the part before the "@"-sign of an address. An empty localpart is the catchall destination/address for the domain.')), dom.br(), localpart = dom.input()), '@', domainName(dnsdomain), ' ', dom.label(style({ display: 'inline-block' }), dom.span('Account', attr.title('Account to assign the address to.')), dom.br(), account = dom.select(attr.required(''), (accounts || []).map(a => dom.option(a)))), ' ', dom.submitbutton('Add address', attr.title('Address will be added and the config reloaded.')))), dom.br(), dom.h2('External checks'), dom.ul(dom.li(link('https://internet.nl/mail/' + dnsdomain.ASCII + '/', 'Check configuration at internet.nl'))), dom.br(), dom.h2('Danger'), dom.clickbutton('Remove domain', async function click(e) {
}, fieldset = dom.fieldset(dom.label(style({ display: 'inline-block' }), dom.span('Localpart', attr.title('The localpart is the part before the "@"-sign of an address. An empty localpart is the catchall destination/address for the domain.')), dom.br(), localpart = dom.input()), '@', domainName(dnsdomain), ' ', dom.label(style({ display: 'inline-block' }), dom.span('Account', attr.title('Account to assign the address to.')), dom.br(), account = dom.select(attr.required(''), (accounts || []).map(a => dom.option(a)))), ' ', dom.submitbutton('Add address', attr.title('Address will be added and the config reloaded.')))), dom.br(), RoutesEditor('domain-specific', transports, domainConfig.Routes || [], async (routes) => await client.DomainRoutesSave(d, routes)), dom.br(), dom.h2('External checks'), dom.ul(dom.li(link('https://internet.nl/mail/' + dnsdomain.ASCII + '/', 'Check configuration at internet.nl'))), dom.br(), dom.h2('Danger'), dom.clickbutton('Remove domain', async function click(e) {
e.preventDefault();
if (!window.confirm('Are you sure you want to remove this domain?')) {
return;
@ -2093,14 +2217,14 @@ const domain = async (d) => {
const domainDNSRecords = async (d) => {
const [records, dnsdomain] = await Promise.all([
client.DomainRecords(d),
client.Domain(d),
client.ParseDomain(d),
]);
dom._kids(page, crumbs(crumblink('Mox Admin', '#'), crumblink('Domain ' + domainString(dnsdomain), '#domains/' + d), 'DNS Records'), dom.h1('Required DNS records'), dom.pre('pre', dom._class('literal'), (records || []).join('\n')), dom.br());
};
const domainDNSCheck = async (d) => {
const [checks, dnsdomain] = await Promise.all([
client.CheckDomain(d),
client.Domain(d),
client.ParseDomain(d),
]);
const resultSection = (title, r, details) => {
let success = [];
@ -3590,6 +3714,9 @@ const init = async () => {
else if (h === 'dnsbl') {
await dnsbl();
}
else if (h === 'routes') {
await globalRoutes();
}
else if (h === 'webserver') {
await webserver();
}

View File

@ -421,6 +421,7 @@ const index = async () => {
// todo: routing, globally, per domain and per account
dom.br(),
dom.h2('Configuration'),
dom.div(dom.a('Routes', attr.href('#routes'))),
dom.div(dom.a('Webserver', attr.href('#webserver'))),
dom.div(dom.a('Files', attr.href('#config'))),
dom.div(dom.a('Log levels', attr.href('#loglevels'))),
@ -428,6 +429,21 @@ const index = async () => {
)
}
const globalRoutes = async () => {
const [transports, config] = await Promise.all([
client.Transports(),
client.Config(),
])
dom._kids(page,
crumbs(
crumblink('Mox Admin', '#'),
'Routes',
),
RoutesEditor('global', transports, config.Routes || [], async (routes: api.Route[]) => await client.RoutesSave(routes)),
)
}
const config = async () => {
const [staticPath, dynamicPath, staticText, dynamicText] = await client.ConfigFiles()
@ -635,10 +651,112 @@ const formatQuotaSize = (v: number) => {
return ''+v
}
const RoutesEditor = (kind: string, transports: { [key: string]: api.Transport }, routes: api.Route[], save: (routes: api.Route[]) => Promise<void>) => {
const transportNames = Object.keys(transports || {})
transportNames.sort()
const hdr = dom.h2('Routes', attr.title('Messages submitted to the queue for outgoing delivery are delivered directly to the MX records of the recipient domain by default. However, other "transports" can be configured, such as SMTP submission/relay or connecting through a SOCKS proxy. Routes with matching rules and a transport can be configured for accounts, domains and globally. Routes are evaluated in that order, the first match is applied.'))
let routesElem: HTMLElement
const render = () => {
if (transportNames.length === 0) {
return [hdr, dom.p('No transports configured.', attr.title('To configure routes, first configure transports via the mox.conf config file.'))]
}
let routesFieldset: HTMLFieldSetElement
interface RouteRow {
root: HTMLElement
gather: () => api.Route
}
let routeRows: RouteRow[] = []
let elem: HTMLElement = dom.form(
async function submit(e: SubmitEvent) {
e.stopPropagation()
e.preventDefault()
await check(routesFieldset, save(routeRows.map(rr => rr.gather())))
},
routesFieldset=dom.fieldset(
dom.table(
dom.thead(
dom.tr(
dom.th('From domain'),
dom.th('To domain'),
dom.th('Minimum attempts'),
dom.th('Transport'),
dom.th(
dom.clickbutton('Add', function click() {
routes = routeRows.map(rr => rr.gather())
routes.push({FromDomain: [], ToDomain: [], MinimumAttempts: 0, Transport: transportNames[0]})
render()
}),
),
),
),
dom.tbody(
(routes || []).length === 0 ? dom.tr(dom.td(attr.colspan('5'), 'No routes.')) : [],
routeRows=(routes || []).map((r, index) => {
let fromDomain = dom.input(attr.value((r.FromDomain || []).join(',')))
let toDomain = dom.input(attr.value((r.ToDomain || []).join(',')))
let minimumAttempts = dom.input(attr.value(''+r.MinimumAttempts))
let transport = dom.select(attr.required(''), transportNames.map(s => dom.option(s, s === r.Transport ? attr.selected('') : [])))
const tr = dom.tr(
dom.td(fromDomain),
dom.td(toDomain),
dom.td(minimumAttempts),
dom.td(transport),
dom.td(
dom.clickbutton('Remove', function click() {
routeRows.splice(index, 1)
routes = routeRows.map(rr => rr.gather())
render()
}),
),
)
return {
root: tr,
gather: (): api.Route => {
return {
FromDomain: fromDomain.value ? fromDomain.value.split(',') : [],
ToDomain: toDomain.value ? toDomain.value.split(',') : [],
MinimumAttempts: parseInt(minimumAttempts.value) || 0,
Transport: transport.value,
}
},
}
}),
),
),
dom.div(dom.submitbutton('Save')),
),
)
if (!routesElem && (routes || []).length === 0) {
// Keep it short.
elem = dom.div(
'No '+kind+' routes configured. ',
dom.clickbutton('Add', function click() {
routes = routeRows.map(rr => rr.gather())
routes.push({FromDomain: [], ToDomain: [], MinimumAttempts: 0, Transport: transportNames[0]})
render()
}),
)
}
elem = dom.div(hdr, elem)
if (routesElem) {
routesElem.replaceWith(elem)
}
routesElem = elem
return elem
}
return render()
}
const account = async (name: string) => {
const [[config, diskUsage], domains] = await Promise.all([
const [[config, diskUsage], domains, transports] = await Promise.all([
client.Account(name),
client.Domains(),
client.Transports(),
])
// todo: show suppression list, and buttons to add/remove entries.
@ -839,6 +957,9 @@ const account = async (name: string) => {
},
),
dom.br(),
RoutesEditor('account-specific', transports, config.Routes || [], async (routes: api.Route[]) => await client.AccountRoutesSave(name, routes)),
dom.br(),
dom.h2('Danger'),
dom.clickbutton('Remove account', async function click(e: MouseEvent) {
e.preventDefault()
@ -854,13 +975,15 @@ const account = async (name: string) => {
const domain = async (d: string) => {
const end = new Date()
const start = new Date(new Date().getTime() - 30*24*3600*1000)
const [dmarcSummaries, tlsrptSummaries, localpartAccounts, dnsdomain, clientConfigs, accounts] = await Promise.all([
const [dmarcSummaries, tlsrptSummaries, localpartAccounts, dnsdomain, clientConfigs, accounts, domainConfig, transports] = await Promise.all([
client.DMARCSummaries(start, end, d),
client.TLSRPTSummaries(start, end, d),
client.DomainLocalparts(d),
client.Domain(d),
client.ParseDomain(d),
client.ClientConfigsDomain(d),
client.Accounts(),
client.DomainConfig(d),
client.Transports(),
])
let form: HTMLFormElement
@ -961,6 +1084,8 @@ const domain = async (d: string) => {
),
),
dom.br(),
RoutesEditor('domain-specific', transports, domainConfig.Routes || [], async (routes: api.Route[]) => await client.DomainRoutesSave(d, routes)),
dom.br(),
dom.h2('External checks'),
dom.ul(
dom.li(link('https://internet.nl/mail/'+dnsdomain.ASCII+'/', 'Check configuration at internet.nl')),
@ -981,7 +1106,7 @@ const domain = async (d: string) => {
const domainDNSRecords = async (d: string) => {
const [records, dnsdomain] = await Promise.all([
client.DomainRecords(d),
client.Domain(d),
client.ParseDomain(d),
])
dom._kids(page,
@ -999,7 +1124,7 @@ const domainDNSRecords = async (d: string) => {
const domainDNSCheck = async (d: string) => {
const [checks, dnsdomain] = await Promise.all([
client.CheckDomain(d),
client.Domain(d),
client.ParseDomain(d),
])
interface Result {
@ -4184,6 +4309,8 @@ const init = async () => {
await mtasts()
} else if (h === 'dnsbl') {
await dnsbl()
} else if (h === 'routes') {
await globalRoutes()
} else if (h === 'webserver') {
await webserver()
} else {

View File

@ -209,10 +209,18 @@ func TestAdminAuth(t *testing.T) {
api.Logout(ctx)
tneedErrorCode(t, "server:error", func() { api.Logout(ctx) })
}
err = queue.Init()
func TestAdmin(t *testing.T) {
os.RemoveAll("../testdata/webadmin/data")
mox.ConfigStaticPath = filepath.FromSlash("../testdata/webadmin/mox.conf")
mox.ConfigDynamicPath = filepath.Join(filepath.Dir(mox.ConfigStaticPath), "domains.conf")
mox.MustLoadConfig(true, false)
err := queue.Init()
tcheck(t, err, "queue init")
api := Admin{}
mrl := api.RetiredList(ctxbg, queue.RetiredFilter{}, queue.RetiredSort{})
tcompare(t, len(mrl), 0)
@ -233,6 +241,22 @@ func TestAdminAuth(t *testing.T) {
n = api.HookCancel(ctxbg, queue.HookFilter{})
tcompare(t, n, 0)
api.Config(ctxbg)
api.DomainConfig(ctxbg, "mox.example")
tneedErrorCode(t, "user:error", func() { api.DomainConfig(ctxbg, "bogus.example") })
api.AccountRoutesSave(ctxbg, "mjl", []config.Route{{Transport: "direct"}})
tneedErrorCode(t, "user:error", func() { api.AccountRoutesSave(ctxbg, "mjl", []config.Route{{Transport: "bogus"}}) })
api.AccountRoutesSave(ctxbg, "mjl", nil)
api.DomainRoutesSave(ctxbg, "mox.example", []config.Route{{Transport: "direct"}})
tneedErrorCode(t, "user:error", func() { api.DomainRoutesSave(ctxbg, "mox.example", []config.Route{{Transport: "bogus"}}) })
api.DomainRoutesSave(ctxbg, "mox.example", nil)
api.RoutesSave(ctxbg, []config.Route{{Transport: "direct"}})
tneedErrorCode(t, "user:error", func() { api.RoutesSave(ctxbg, []config.Route{{Transport: "bogus"}}) })
api.RoutesSave(ctxbg, nil)
}
func TestCheckDomain(t *testing.T) {

View File

@ -121,6 +121,26 @@
}
]
},
{
"Name": "DomainConfig",
"Docs": "DomainConfig returns the configuration for a domain.",
"Params": [
{
"Name": "domain",
"Typewords": [
"string"
]
}
],
"Returns": [
{
"Name": "r0",
"Typewords": [
"ConfigDomain"
]
}
]
},
{
"Name": "DomainLocalparts",
"Docs": "DomainLocalparts returns the encoded localparts and accounts configured in domain.",
@ -1528,6 +1548,73 @@
]
}
]
},
{
"Name": "Config",
"Docs": "Config returns the dynamic config.",
"Params": [],
"Returns": [
{
"Name": "r0",
"Typewords": [
"Dynamic"
]
}
]
},
{
"Name": "AccountRoutesSave",
"Docs": "AccountRoutesSave saves routes for an account.",
"Params": [
{
"Name": "accountName",
"Typewords": [
"string"
]
},
{
"Name": "routes",
"Typewords": [
"[]",
"Route"
]
}
],
"Returns": []
},
{
"Name": "DomainRoutesSave",
"Docs": "DomainRoutesSave saves routes for a domain.",
"Params": [
{
"Name": "domainName",
"Typewords": [
"string"
]
},
{
"Name": "routes",
"Typewords": [
"[]",
"Route"
]
}
],
"Returns": []
},
{
"Name": "RoutesSave",
"Docs": "RoutesSave saves global routes.",
"Params": [
{
"Name": "routes",
"Typewords": [
"[]",
"Route"
]
}
],
"Returns": []
}
],
"Sections": [],
@ -2794,6 +2881,368 @@
}
]
},
{
"Name": "ConfigDomain",
"Docs": "",
"Fields": [
{
"Name": "Description",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "ClientSettingsDomain",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "LocalpartCatchallSeparator",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "LocalpartCaseSensitive",
"Docs": "",
"Typewords": [
"bool"
]
},
{
"Name": "DKIM",
"Docs": "",
"Typewords": [
"DKIM"
]
},
{
"Name": "DMARC",
"Docs": "",
"Typewords": [
"nullable",
"DMARC"
]
},
{
"Name": "MTASTS",
"Docs": "",
"Typewords": [
"nullable",
"MTASTS"
]
},
{
"Name": "TLSRPT",
"Docs": "",
"Typewords": [
"nullable",
"TLSRPT"
]
},
{
"Name": "Routes",
"Docs": "",
"Typewords": [
"[]",
"Route"
]
}
]
},
{
"Name": "DKIM",
"Docs": "",
"Fields": [
{
"Name": "Selectors",
"Docs": "",
"Typewords": [
"{}",
"Selector"
]
},
{
"Name": "Sign",
"Docs": "",
"Typewords": [
"[]",
"string"
]
}
]
},
{
"Name": "Selector",
"Docs": "",
"Fields": [
{
"Name": "Hash",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "HashEffective",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "Canonicalization",
"Docs": "",
"Typewords": [
"Canonicalization"
]
},
{
"Name": "Headers",
"Docs": "",
"Typewords": [
"[]",
"string"
]
},
{
"Name": "HeadersEffective",
"Docs": "Used when signing. Based on Headers from config, or the reasonable default.",
"Typewords": [
"[]",
"string"
]
},
{
"Name": "DontSealHeaders",
"Docs": "",
"Typewords": [
"bool"
]
},
{
"Name": "Expiration",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "PrivateKeyFile",
"Docs": "",
"Typewords": [
"string"
]
}
]
},
{
"Name": "Canonicalization",
"Docs": "",
"Fields": [
{
"Name": "HeaderRelaxed",
"Docs": "",
"Typewords": [
"bool"
]
},
{
"Name": "BodyRelaxed",
"Docs": "",
"Typewords": [
"bool"
]
}
]
},
{
"Name": "DMARC",
"Docs": "",
"Fields": [
{
"Name": "Localpart",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "Domain",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "Account",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "Mailbox",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "ParsedLocalpart",
"Docs": "",
"Typewords": [
"Localpart"
]
},
{
"Name": "DNSDomain",
"Docs": "Effective domain, always set based on Domain field or Domain where this is configured.",
"Typewords": [
"Domain"
]
}
]
},
{
"Name": "MTASTS",
"Docs": "",
"Fields": [
{
"Name": "PolicyID",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "Mode",
"Docs": "",
"Typewords": [
"Mode"
]
},
{
"Name": "MaxAge",
"Docs": "",
"Typewords": [
"int64"
]
},
{
"Name": "MX",
"Docs": "",
"Typewords": [
"[]",
"string"
]
}
]
},
{
"Name": "TLSRPT",
"Docs": "",
"Fields": [
{
"Name": "Localpart",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "Domain",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "Account",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "Mailbox",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "ParsedLocalpart",
"Docs": "",
"Typewords": [
"Localpart"
]
},
{
"Name": "DNSDomain",
"Docs": "Effective domain, always set based on Domain field or Domain where this is configured.",
"Typewords": [
"Domain"
]
}
]
},
{
"Name": "Route",
"Docs": "",
"Fields": [
{
"Name": "FromDomain",
"Docs": "",
"Typewords": [
"[]",
"string"
]
},
{
"Name": "ToDomain",
"Docs": "",
"Typewords": [
"[]",
"string"
]
},
{
"Name": "MinimumAttempts",
"Docs": "",
"Typewords": [
"int32"
]
},
{
"Name": "Transport",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "FromDomainASCII",
"Docs": "",
"Typewords": [
"[]",
"string"
]
},
{
"Name": "ToDomainASCII",
"Docs": "",
"Typewords": [
"[]",
"string"
]
}
]
},
{
"Name": "Account",
"Docs": "",
@ -3201,58 +3650,6 @@
}
]
},
{
"Name": "Route",
"Docs": "",
"Fields": [
{
"Name": "FromDomain",
"Docs": "",
"Typewords": [
"[]",
"string"
]
},
{
"Name": "ToDomain",
"Docs": "",
"Typewords": [
"[]",
"string"
]
},
{
"Name": "MinimumAttempts",
"Docs": "",
"Typewords": [
"int32"
]
},
{
"Name": "Transport",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "FromDomainASCII",
"Docs": "",
"Typewords": [
"[]",
"string"
]
},
{
"Name": "ToDomainASCII",
"Docs": "",
"Typewords": [
"[]",
"string"
]
}
]
},
{
"Name": "PolicyRecord",
"Docs": "PolicyRecord is a cached policy or absence of a policy.",
@ -6153,6 +6550,68 @@
]
}
]
},
{
"Name": "Dynamic",
"Docs": "Dynamic is the parsed form of domains.conf, and is automatically reloaded when changed.",
"Fields": [
{
"Name": "Domains",
"Docs": "",
"Typewords": [
"{}",
"ConfigDomain"
]
},
{
"Name": "Accounts",
"Docs": "",
"Typewords": [
"{}",
"Account"
]
},
{
"Name": "WebDomainRedirects",
"Docs": "",
"Typewords": [
"{}",
"string"
]
},
{
"Name": "WebHandlers",
"Docs": "",
"Typewords": [
"[]",
"WebHandler"
]
},
{
"Name": "Routes",
"Docs": "",
"Typewords": [
"[]",
"Route"
]
},
{
"Name": "MonitorDNSBLs",
"Docs": "",
"Typewords": [
"[]",
"string"
]
},
{
"Name": "MonitorDNSBLZones",
"Docs": "",
"Typewords": [
"[]",
"Domain"
]
}
]
}
],
"Ints": [],
@ -6230,6 +6689,11 @@
}
]
},
{
"Name": "Localpart",
"Docs": "Localpart is a decoded local part of an email address, before the \"@\".\nFor quoted strings, values do not hold the double quote or escaping backslashes.\nAn empty string can be a valid localpart.\nLocalparts are in Unicode NFC.",
"Values": null
},
{
"Name": "PolicyType",
"Docs": "PolicyType indicates the policy success/failure results are for.",
@ -6534,11 +6998,6 @@
}
]
},
{
"Name": "Localpart",
"Docs": "Localpart is a decoded local part of an email address, before the \"@\".\nFor quoted strings, values do not hold the double quote or escaping backslashes.\nAn empty string can be a valid localpart.\nLocalparts are in Unicode NFC.",
"Values": null
},
{
"Name": "IP",
"Docs": "An IP is a single IP address, a slice of bytes.\nFunctions in this package accept either 4-byte (IPv4)\nor 16-byte (IPv6) slices as input.\n\nNote that in this documentation, referring to an\nIP address as an IPv4 address or an IPv6 address\nis a semantic property of the address, not just the\nlength of the byte slice: a 16-byte slice can still\nbe an IPv4 address.",

View File

@ -266,6 +266,73 @@ export interface AutodiscoverSRV {
IPs?: string[] | null
}
export interface ConfigDomain {
Description: string
ClientSettingsDomain: string
LocalpartCatchallSeparator: string
LocalpartCaseSensitive: boolean
DKIM: DKIM
DMARC?: DMARC | null
MTASTS?: MTASTS | null
TLSRPT?: TLSRPT | null
Routes?: Route[] | null
}
export interface DKIM {
Selectors?: { [key: string]: Selector }
Sign?: string[] | null
}
export interface Selector {
Hash: string
HashEffective: string
Canonicalization: Canonicalization
Headers?: string[] | null
HeadersEffective?: string[] | null // Used when signing. Based on Headers from config, or the reasonable default.
DontSealHeaders: boolean
Expiration: string
PrivateKeyFile: string
}
export interface Canonicalization {
HeaderRelaxed: boolean
BodyRelaxed: boolean
}
export interface DMARC {
Localpart: string
Domain: string
Account: string
Mailbox: string
ParsedLocalpart: Localpart
DNSDomain: Domain // Effective domain, always set based on Domain field or Domain where this is configured.
}
export interface MTASTS {
PolicyID: string
Mode: Mode
MaxAge: number
MX?: string[] | null
}
export interface TLSRPT {
Localpart: string
Domain: string
Account: string
Mailbox: string
ParsedLocalpart: Localpart
DNSDomain: Domain // Effective domain, always set based on Domain field or Domain where this is configured.
}
export interface Route {
FromDomain?: string[] | null
ToDomain?: string[] | null
MinimumAttempts: number
Transport: string
FromDomainASCII?: string[] | null
ToDomainASCII?: string[] | null
}
export interface Account {
OutgoingWebhook?: OutgoingWebhook | null
IncomingWebhook?: IncomingWebhook | null
@ -340,15 +407,6 @@ export interface JunkFilter {
RareWords: number
}
export interface Route {
FromDomain?: string[] | null
ToDomain?: string[] | null
MinimumAttempts: number
Transport: string
FromDomainASCII?: string[] | null
ToDomainASCII?: string[] | null
}
// PolicyRecord is a cached policy or absence of a policy.
export interface PolicyRecord {
Domain: string // Domain name, with unicode characters.
@ -946,6 +1004,17 @@ export interface TLSRPTSuppressAddress {
Comment: string
}
// Dynamic is the parsed form of domains.conf, and is automatically reloaded when changed.
export interface Dynamic {
Domains?: { [key: string]: ConfigDomain }
Accounts?: { [key: string]: Account }
WebDomainRedirects?: { [key: string]: string }
WebHandlers?: WebHandler[] | null
Routes?: Route[] | null
MonitorDNSBLs?: string[] | null
MonitorDNSBLZones?: Domain[] | null
}
export type CSRFToken = string
// Policy as used in DMARC DNS record for "p=" or "sp=".
@ -973,6 +1042,12 @@ export enum Mode {
ModeNone = "none", // In case MTA-STS is not or no longer implemented.
}
// Localpart is a decoded local part of an email address, before the "@".
// For quoted strings, values do not hold the double quote or escaping backslashes.
// An empty string can be a valid localpart.
// Localparts are in Unicode NFC.
export type Localpart = string
// PolicyType indicates the policy success/failure results are for.
export enum PolicyType {
TLSA = "tlsa", // For DANE, against a mail host (not recipient domain).
@ -1060,12 +1135,6 @@ export enum SPFResult {
SPFPermerror = "permerror",
}
// Localpart is a decoded local part of an email address, before the "@".
// For quoted strings, values do not hold the double quote or escaping backslashes.
// An empty string can be a valid localpart.
// Localparts are in Unicode NFC.
export type Localpart = string
// An IP is a single IP address, a slice of bytes.
// Functions in this package accept either 4-byte (IPv4)
// or 16-byte (IPv6) slices as input.
@ -1077,7 +1146,7 @@ export type Localpart = string
// be an IPv4 address.
export type IP = string
export const structTypes: {[typename: string]: boolean} = {"Account":true,"AuthResults":true,"AutoconfCheckResult":true,"AutodiscoverCheckResult":true,"AutodiscoverSRV":true,"AutomaticJunkFlags":true,"CheckResult":true,"ClientConfigs":true,"ClientConfigsEntry":true,"DANECheckResult":true,"DKIMAuthResult":true,"DKIMCheckResult":true,"DKIMRecord":true,"DMARCCheckResult":true,"DMARCRecord":true,"DMARCSummary":true,"DNSSECResult":true,"DateRange":true,"Destination":true,"Directive":true,"Domain":true,"DomainFeedback":true,"Evaluation":true,"EvaluationStat":true,"Extension":true,"FailureDetails":true,"Filter":true,"HoldRule":true,"Hook":true,"HookFilter":true,"HookResult":true,"HookRetired":true,"HookRetiredFilter":true,"HookRetiredSort":true,"HookSort":true,"IPDomain":true,"IPRevCheckResult":true,"Identifiers":true,"IncomingWebhook":true,"JunkFilter":true,"MTASTSCheckResult":true,"MTASTSRecord":true,"MX":true,"MXCheckResult":true,"Modifier":true,"Msg":true,"MsgResult":true,"MsgRetired":true,"OutgoingWebhook":true,"Pair":true,"Policy":true,"PolicyEvaluated":true,"PolicyOverrideReason":true,"PolicyPublished":true,"PolicyRecord":true,"Record":true,"Report":true,"ReportMetadata":true,"ReportRecord":true,"Result":true,"ResultPolicy":true,"RetiredFilter":true,"RetiredSort":true,"Reverse":true,"Route":true,"Row":true,"Ruleset":true,"SMTPAuth":true,"SPFAuthResult":true,"SPFCheckResult":true,"SPFRecord":true,"SRV":true,"SRVConfCheckResult":true,"STSMX":true,"Sort":true,"SubjectPass":true,"Summary":true,"SuppressAddress":true,"TLSCheckResult":true,"TLSRPTCheckResult":true,"TLSRPTDateRange":true,"TLSRPTRecord":true,"TLSRPTSummary":true,"TLSRPTSuppressAddress":true,"TLSReportRecord":true,"TLSResult":true,"Transport":true,"TransportDirect":true,"TransportSMTP":true,"TransportSocks":true,"URI":true,"WebForward":true,"WebHandler":true,"WebRedirect":true,"WebStatic":true,"WebserverConfig":true}
export const structTypes: {[typename: string]: boolean} = {"Account":true,"AuthResults":true,"AutoconfCheckResult":true,"AutodiscoverCheckResult":true,"AutodiscoverSRV":true,"AutomaticJunkFlags":true,"Canonicalization":true,"CheckResult":true,"ClientConfigs":true,"ClientConfigsEntry":true,"ConfigDomain":true,"DANECheckResult":true,"DKIM":true,"DKIMAuthResult":true,"DKIMCheckResult":true,"DKIMRecord":true,"DMARC":true,"DMARCCheckResult":true,"DMARCRecord":true,"DMARCSummary":true,"DNSSECResult":true,"DateRange":true,"Destination":true,"Directive":true,"Domain":true,"DomainFeedback":true,"Dynamic":true,"Evaluation":true,"EvaluationStat":true,"Extension":true,"FailureDetails":true,"Filter":true,"HoldRule":true,"Hook":true,"HookFilter":true,"HookResult":true,"HookRetired":true,"HookRetiredFilter":true,"HookRetiredSort":true,"HookSort":true,"IPDomain":true,"IPRevCheckResult":true,"Identifiers":true,"IncomingWebhook":true,"JunkFilter":true,"MTASTS":true,"MTASTSCheckResult":true,"MTASTSRecord":true,"MX":true,"MXCheckResult":true,"Modifier":true,"Msg":true,"MsgResult":true,"MsgRetired":true,"OutgoingWebhook":true,"Pair":true,"Policy":true,"PolicyEvaluated":true,"PolicyOverrideReason":true,"PolicyPublished":true,"PolicyRecord":true,"Record":true,"Report":true,"ReportMetadata":true,"ReportRecord":true,"Result":true,"ResultPolicy":true,"RetiredFilter":true,"RetiredSort":true,"Reverse":true,"Route":true,"Row":true,"Ruleset":true,"SMTPAuth":true,"SPFAuthResult":true,"SPFCheckResult":true,"SPFRecord":true,"SRV":true,"SRVConfCheckResult":true,"STSMX":true,"Selector":true,"Sort":true,"SubjectPass":true,"Summary":true,"SuppressAddress":true,"TLSCheckResult":true,"TLSRPT":true,"TLSRPTCheckResult":true,"TLSRPTDateRange":true,"TLSRPTRecord":true,"TLSRPTSummary":true,"TLSRPTSuppressAddress":true,"TLSReportRecord":true,"TLSResult":true,"Transport":true,"TransportDirect":true,"TransportSMTP":true,"TransportSocks":true,"URI":true,"WebForward":true,"WebHandler":true,"WebRedirect":true,"WebStatic":true,"WebserverConfig":true}
export const stringsTypes: {[typename: string]: boolean} = {"Align":true,"Alignment":true,"CSRFToken":true,"DKIMResult":true,"DMARCPolicy":true,"DMARCResult":true,"Disposition":true,"IP":true,"Localpart":true,"Mode":true,"PolicyOverride":true,"PolicyType":true,"RUA":true,"ResultType":true,"SPFDomainScope":true,"SPFResult":true}
export const intsTypes: {[typename: string]: boolean} = {}
export const types: TypenameMap = {
@ -1112,6 +1181,14 @@ export const types: TypenameMap = {
"AutoconfCheckResult": {"Name":"AutoconfCheckResult","Docs":"","Fields":[{"Name":"ClientSettingsDomainIPs","Docs":"","Typewords":["[]","string"]},{"Name":"IPs","Docs":"","Typewords":["[]","string"]},{"Name":"Errors","Docs":"","Typewords":["[]","string"]},{"Name":"Warnings","Docs":"","Typewords":["[]","string"]},{"Name":"Instructions","Docs":"","Typewords":["[]","string"]}]},
"AutodiscoverCheckResult": {"Name":"AutodiscoverCheckResult","Docs":"","Fields":[{"Name":"Records","Docs":"","Typewords":["[]","AutodiscoverSRV"]},{"Name":"Errors","Docs":"","Typewords":["[]","string"]},{"Name":"Warnings","Docs":"","Typewords":["[]","string"]},{"Name":"Instructions","Docs":"","Typewords":["[]","string"]}]},
"AutodiscoverSRV": {"Name":"AutodiscoverSRV","Docs":"","Fields":[{"Name":"Target","Docs":"","Typewords":["string"]},{"Name":"Port","Docs":"","Typewords":["uint16"]},{"Name":"Priority","Docs":"","Typewords":["uint16"]},{"Name":"Weight","Docs":"","Typewords":["uint16"]},{"Name":"IPs","Docs":"","Typewords":["[]","string"]}]},
"ConfigDomain": {"Name":"ConfigDomain","Docs":"","Fields":[{"Name":"Description","Docs":"","Typewords":["string"]},{"Name":"ClientSettingsDomain","Docs":"","Typewords":["string"]},{"Name":"LocalpartCatchallSeparator","Docs":"","Typewords":["string"]},{"Name":"LocalpartCaseSensitive","Docs":"","Typewords":["bool"]},{"Name":"DKIM","Docs":"","Typewords":["DKIM"]},{"Name":"DMARC","Docs":"","Typewords":["nullable","DMARC"]},{"Name":"MTASTS","Docs":"","Typewords":["nullable","MTASTS"]},{"Name":"TLSRPT","Docs":"","Typewords":["nullable","TLSRPT"]},{"Name":"Routes","Docs":"","Typewords":["[]","Route"]}]},
"DKIM": {"Name":"DKIM","Docs":"","Fields":[{"Name":"Selectors","Docs":"","Typewords":["{}","Selector"]},{"Name":"Sign","Docs":"","Typewords":["[]","string"]}]},
"Selector": {"Name":"Selector","Docs":"","Fields":[{"Name":"Hash","Docs":"","Typewords":["string"]},{"Name":"HashEffective","Docs":"","Typewords":["string"]},{"Name":"Canonicalization","Docs":"","Typewords":["Canonicalization"]},{"Name":"Headers","Docs":"","Typewords":["[]","string"]},{"Name":"HeadersEffective","Docs":"","Typewords":["[]","string"]},{"Name":"DontSealHeaders","Docs":"","Typewords":["bool"]},{"Name":"Expiration","Docs":"","Typewords":["string"]},{"Name":"PrivateKeyFile","Docs":"","Typewords":["string"]}]},
"Canonicalization": {"Name":"Canonicalization","Docs":"","Fields":[{"Name":"HeaderRelaxed","Docs":"","Typewords":["bool"]},{"Name":"BodyRelaxed","Docs":"","Typewords":["bool"]}]},
"DMARC": {"Name":"DMARC","Docs":"","Fields":[{"Name":"Localpart","Docs":"","Typewords":["string"]},{"Name":"Domain","Docs":"","Typewords":["string"]},{"Name":"Account","Docs":"","Typewords":["string"]},{"Name":"Mailbox","Docs":"","Typewords":["string"]},{"Name":"ParsedLocalpart","Docs":"","Typewords":["Localpart"]},{"Name":"DNSDomain","Docs":"","Typewords":["Domain"]}]},
"MTASTS": {"Name":"MTASTS","Docs":"","Fields":[{"Name":"PolicyID","Docs":"","Typewords":["string"]},{"Name":"Mode","Docs":"","Typewords":["Mode"]},{"Name":"MaxAge","Docs":"","Typewords":["int64"]},{"Name":"MX","Docs":"","Typewords":["[]","string"]}]},
"TLSRPT": {"Name":"TLSRPT","Docs":"","Fields":[{"Name":"Localpart","Docs":"","Typewords":["string"]},{"Name":"Domain","Docs":"","Typewords":["string"]},{"Name":"Account","Docs":"","Typewords":["string"]},{"Name":"Mailbox","Docs":"","Typewords":["string"]},{"Name":"ParsedLocalpart","Docs":"","Typewords":["Localpart"]},{"Name":"DNSDomain","Docs":"","Typewords":["Domain"]}]},
"Route": {"Name":"Route","Docs":"","Fields":[{"Name":"FromDomain","Docs":"","Typewords":["[]","string"]},{"Name":"ToDomain","Docs":"","Typewords":["[]","string"]},{"Name":"MinimumAttempts","Docs":"","Typewords":["int32"]},{"Name":"Transport","Docs":"","Typewords":["string"]},{"Name":"FromDomainASCII","Docs":"","Typewords":["[]","string"]},{"Name":"ToDomainASCII","Docs":"","Typewords":["[]","string"]}]},
"Account": {"Name":"Account","Docs":"","Fields":[{"Name":"OutgoingWebhook","Docs":"","Typewords":["nullable","OutgoingWebhook"]},{"Name":"IncomingWebhook","Docs":"","Typewords":["nullable","IncomingWebhook"]},{"Name":"FromIDLoginAddresses","Docs":"","Typewords":["[]","string"]},{"Name":"KeepRetiredMessagePeriod","Docs":"","Typewords":["int64"]},{"Name":"KeepRetiredWebhookPeriod","Docs":"","Typewords":["int64"]},{"Name":"Domain","Docs":"","Typewords":["string"]},{"Name":"Description","Docs":"","Typewords":["string"]},{"Name":"FullName","Docs":"","Typewords":["string"]},{"Name":"Destinations","Docs":"","Typewords":["{}","Destination"]},{"Name":"SubjectPass","Docs":"","Typewords":["SubjectPass"]},{"Name":"QuotaMessageSize","Docs":"","Typewords":["int64"]},{"Name":"RejectsMailbox","Docs":"","Typewords":["string"]},{"Name":"KeepRejects","Docs":"","Typewords":["bool"]},{"Name":"AutomaticJunkFlags","Docs":"","Typewords":["AutomaticJunkFlags"]},{"Name":"JunkFilter","Docs":"","Typewords":["nullable","JunkFilter"]},{"Name":"MaxOutgoingMessagesPerDay","Docs":"","Typewords":["int32"]},{"Name":"MaxFirstTimeRecipientsPerDay","Docs":"","Typewords":["int32"]},{"Name":"NoFirstTimeSenderDelay","Docs":"","Typewords":["bool"]},{"Name":"Routes","Docs":"","Typewords":["[]","Route"]},{"Name":"DNSDomain","Docs":"","Typewords":["Domain"]}]},
"OutgoingWebhook": {"Name":"OutgoingWebhook","Docs":"","Fields":[{"Name":"URL","Docs":"","Typewords":["string"]},{"Name":"Authorization","Docs":"","Typewords":["string"]},{"Name":"Events","Docs":"","Typewords":["[]","string"]}]},
"IncomingWebhook": {"Name":"IncomingWebhook","Docs":"","Fields":[{"Name":"URL","Docs":"","Typewords":["string"]},{"Name":"Authorization","Docs":"","Typewords":["string"]}]},
@ -1120,7 +1197,6 @@ export const types: TypenameMap = {
"SubjectPass": {"Name":"SubjectPass","Docs":"","Fields":[{"Name":"Period","Docs":"","Typewords":["int64"]}]},
"AutomaticJunkFlags": {"Name":"AutomaticJunkFlags","Docs":"","Fields":[{"Name":"Enabled","Docs":"","Typewords":["bool"]},{"Name":"JunkMailboxRegexp","Docs":"","Typewords":["string"]},{"Name":"NeutralMailboxRegexp","Docs":"","Typewords":["string"]},{"Name":"NotJunkMailboxRegexp","Docs":"","Typewords":["string"]}]},
"JunkFilter": {"Name":"JunkFilter","Docs":"","Fields":[{"Name":"Threshold","Docs":"","Typewords":["float64"]},{"Name":"Onegrams","Docs":"","Typewords":["bool"]},{"Name":"Twograms","Docs":"","Typewords":["bool"]},{"Name":"Threegrams","Docs":"","Typewords":["bool"]},{"Name":"MaxPower","Docs":"","Typewords":["float64"]},{"Name":"TopWords","Docs":"","Typewords":["int32"]},{"Name":"IgnoreWords","Docs":"","Typewords":["float64"]},{"Name":"RareWords","Docs":"","Typewords":["int32"]}]},
"Route": {"Name":"Route","Docs":"","Fields":[{"Name":"FromDomain","Docs":"","Typewords":["[]","string"]},{"Name":"ToDomain","Docs":"","Typewords":["[]","string"]},{"Name":"MinimumAttempts","Docs":"","Typewords":["int32"]},{"Name":"Transport","Docs":"","Typewords":["string"]},{"Name":"FromDomainASCII","Docs":"","Typewords":["[]","string"]},{"Name":"ToDomainASCII","Docs":"","Typewords":["[]","string"]}]},
"PolicyRecord": {"Name":"PolicyRecord","Docs":"","Fields":[{"Name":"Domain","Docs":"","Typewords":["string"]},{"Name":"Inserted","Docs":"","Typewords":["timestamp"]},{"Name":"ValidEnd","Docs":"","Typewords":["timestamp"]},{"Name":"LastUpdate","Docs":"","Typewords":["timestamp"]},{"Name":"LastUse","Docs":"","Typewords":["timestamp"]},{"Name":"Backoff","Docs":"","Typewords":["bool"]},{"Name":"RecordID","Docs":"","Typewords":["string"]},{"Name":"Version","Docs":"","Typewords":["string"]},{"Name":"Mode","Docs":"","Typewords":["Mode"]},{"Name":"MX","Docs":"","Typewords":["[]","STSMX"]},{"Name":"MaxAgeSeconds","Docs":"","Typewords":["int32"]},{"Name":"Extensions","Docs":"","Typewords":["[]","Pair"]},{"Name":"PolicyText","Docs":"","Typewords":["string"]}]},
"TLSReportRecord": {"Name":"TLSReportRecord","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"Domain","Docs":"","Typewords":["string"]},{"Name":"FromDomain","Docs":"","Typewords":["string"]},{"Name":"MailFrom","Docs":"","Typewords":["string"]},{"Name":"HostReport","Docs":"","Typewords":["bool"]},{"Name":"Report","Docs":"","Typewords":["Report"]}]},
"Report": {"Name":"Report","Docs":"","Fields":[{"Name":"OrganizationName","Docs":"","Typewords":["string"]},{"Name":"DateRange","Docs":"","Typewords":["TLSRPTDateRange"]},{"Name":"ContactInfo","Docs":"","Typewords":["string"]},{"Name":"ReportID","Docs":"","Typewords":["string"]},{"Name":"Policies","Docs":"","Typewords":["[]","Result"]}]},
@ -1177,11 +1253,13 @@ export const types: TypenameMap = {
"SuppressAddress": {"Name":"SuppressAddress","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"Inserted","Docs":"","Typewords":["timestamp"]},{"Name":"ReportingAddress","Docs":"","Typewords":["string"]},{"Name":"Until","Docs":"","Typewords":["timestamp"]},{"Name":"Comment","Docs":"","Typewords":["string"]}]},
"TLSResult": {"Name":"TLSResult","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"PolicyDomain","Docs":"","Typewords":["string"]},{"Name":"DayUTC","Docs":"","Typewords":["string"]},{"Name":"RecipientDomain","Docs":"","Typewords":["string"]},{"Name":"Created","Docs":"","Typewords":["timestamp"]},{"Name":"Updated","Docs":"","Typewords":["timestamp"]},{"Name":"IsHost","Docs":"","Typewords":["bool"]},{"Name":"SendReport","Docs":"","Typewords":["bool"]},{"Name":"SentToRecipientDomain","Docs":"","Typewords":["bool"]},{"Name":"RecipientDomainReportingAddresses","Docs":"","Typewords":["[]","string"]},{"Name":"SentToPolicyDomain","Docs":"","Typewords":["bool"]},{"Name":"Results","Docs":"","Typewords":["[]","Result"]}]},
"TLSRPTSuppressAddress": {"Name":"TLSRPTSuppressAddress","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"Inserted","Docs":"","Typewords":["timestamp"]},{"Name":"ReportingAddress","Docs":"","Typewords":["string"]},{"Name":"Until","Docs":"","Typewords":["timestamp"]},{"Name":"Comment","Docs":"","Typewords":["string"]}]},
"Dynamic": {"Name":"Dynamic","Docs":"","Fields":[{"Name":"Domains","Docs":"","Typewords":["{}","ConfigDomain"]},{"Name":"Accounts","Docs":"","Typewords":["{}","Account"]},{"Name":"WebDomainRedirects","Docs":"","Typewords":["{}","string"]},{"Name":"WebHandlers","Docs":"","Typewords":["[]","WebHandler"]},{"Name":"Routes","Docs":"","Typewords":["[]","Route"]},{"Name":"MonitorDNSBLs","Docs":"","Typewords":["[]","string"]},{"Name":"MonitorDNSBLZones","Docs":"","Typewords":["[]","Domain"]}]},
"CSRFToken": {"Name":"CSRFToken","Docs":"","Values":null},
"DMARCPolicy": {"Name":"DMARCPolicy","Docs":"","Values":[{"Name":"PolicyEmpty","Value":"","Docs":""},{"Name":"PolicyNone","Value":"none","Docs":""},{"Name":"PolicyQuarantine","Value":"quarantine","Docs":""},{"Name":"PolicyReject","Value":"reject","Docs":""}]},
"Align": {"Name":"Align","Docs":"","Values":[{"Name":"AlignStrict","Value":"s","Docs":""},{"Name":"AlignRelaxed","Value":"r","Docs":""}]},
"RUA": {"Name":"RUA","Docs":"","Values":null},
"Mode": {"Name":"Mode","Docs":"","Values":[{"Name":"ModeEnforce","Value":"enforce","Docs":""},{"Name":"ModeTesting","Value":"testing","Docs":""},{"Name":"ModeNone","Value":"none","Docs":""}]},
"Localpart": {"Name":"Localpart","Docs":"","Values":null},
"PolicyType": {"Name":"PolicyType","Docs":"","Values":[{"Name":"TLSA","Value":"tlsa","Docs":""},{"Name":"STS","Value":"sts","Docs":""},{"Name":"NoPolicyFound","Value":"no-policy-found","Docs":""}]},
"ResultType": {"Name":"ResultType","Docs":"","Values":[{"Name":"ResultSTARTTLSNotSupported","Value":"starttls-not-supported","Docs":""},{"Name":"ResultCertificateHostMismatch","Value":"certificate-host-mismatch","Docs":""},{"Name":"ResultCertificateExpired","Value":"certificate-expired","Docs":""},{"Name":"ResultTLSAInvalid","Value":"tlsa-invalid","Docs":""},{"Name":"ResultDNSSECInvalid","Value":"dnssec-invalid","Docs":""},{"Name":"ResultDANERequired","Value":"dane-required","Docs":""},{"Name":"ResultCertificateNotTrusted","Value":"certificate-not-trusted","Docs":""},{"Name":"ResultSTSPolicyInvalid","Value":"sts-policy-invalid","Docs":""},{"Name":"ResultSTSWebPKIInvalid","Value":"sts-webpki-invalid","Docs":""},{"Name":"ResultValidationFailure","Value":"validation-failure","Docs":""},{"Name":"ResultSTSPolicyFetch","Value":"sts-policy-fetch-error","Docs":""}]},
"Alignment": {"Name":"Alignment","Docs":"","Values":[{"Name":"AlignmentAbsent","Value":"","Docs":""},{"Name":"AlignmentRelaxed","Value":"r","Docs":""},{"Name":"AlignmentStrict","Value":"s","Docs":""}]},
@ -1191,7 +1269,6 @@ export const types: TypenameMap = {
"DKIMResult": {"Name":"DKIMResult","Docs":"","Values":[{"Name":"DKIMAbsent","Value":"","Docs":""},{"Name":"DKIMNone","Value":"none","Docs":""},{"Name":"DKIMPass","Value":"pass","Docs":""},{"Name":"DKIMFail","Value":"fail","Docs":""},{"Name":"DKIMPolicy","Value":"policy","Docs":""},{"Name":"DKIMNeutral","Value":"neutral","Docs":""},{"Name":"DKIMTemperror","Value":"temperror","Docs":""},{"Name":"DKIMPermerror","Value":"permerror","Docs":""}]},
"SPFDomainScope": {"Name":"SPFDomainScope","Docs":"","Values":[{"Name":"SPFDomainScopeAbsent","Value":"","Docs":""},{"Name":"SPFDomainScopeHelo","Value":"helo","Docs":""},{"Name":"SPFDomainScopeMailFrom","Value":"mfrom","Docs":""}]},
"SPFResult": {"Name":"SPFResult","Docs":"","Values":[{"Name":"SPFAbsent","Value":"","Docs":""},{"Name":"SPFNone","Value":"none","Docs":""},{"Name":"SPFNeutral","Value":"neutral","Docs":""},{"Name":"SPFPass","Value":"pass","Docs":""},{"Name":"SPFFail","Value":"fail","Docs":""},{"Name":"SPFSoftfail","Value":"softfail","Docs":""},{"Name":"SPFTemperror","Value":"temperror","Docs":""},{"Name":"SPFPermerror","Value":"permerror","Docs":""}]},
"Localpart": {"Name":"Localpart","Docs":"","Values":null},
"IP": {"Name":"IP","Docs":"","Values":[]},
}
@ -1227,6 +1304,14 @@ export const parser = {
AutoconfCheckResult: (v: any) => parse("AutoconfCheckResult", v) as AutoconfCheckResult,
AutodiscoverCheckResult: (v: any) => parse("AutodiscoverCheckResult", v) as AutodiscoverCheckResult,
AutodiscoverSRV: (v: any) => parse("AutodiscoverSRV", v) as AutodiscoverSRV,
ConfigDomain: (v: any) => parse("ConfigDomain", v) as ConfigDomain,
DKIM: (v: any) => parse("DKIM", v) as DKIM,
Selector: (v: any) => parse("Selector", v) as Selector,
Canonicalization: (v: any) => parse("Canonicalization", v) as Canonicalization,
DMARC: (v: any) => parse("DMARC", v) as DMARC,
MTASTS: (v: any) => parse("MTASTS", v) as MTASTS,
TLSRPT: (v: any) => parse("TLSRPT", v) as TLSRPT,
Route: (v: any) => parse("Route", v) as Route,
Account: (v: any) => parse("Account", v) as Account,
OutgoingWebhook: (v: any) => parse("OutgoingWebhook", v) as OutgoingWebhook,
IncomingWebhook: (v: any) => parse("IncomingWebhook", v) as IncomingWebhook,
@ -1235,7 +1320,6 @@ export const parser = {
SubjectPass: (v: any) => parse("SubjectPass", v) as SubjectPass,
AutomaticJunkFlags: (v: any) => parse("AutomaticJunkFlags", v) as AutomaticJunkFlags,
JunkFilter: (v: any) => parse("JunkFilter", v) as JunkFilter,
Route: (v: any) => parse("Route", v) as Route,
PolicyRecord: (v: any) => parse("PolicyRecord", v) as PolicyRecord,
TLSReportRecord: (v: any) => parse("TLSReportRecord", v) as TLSReportRecord,
Report: (v: any) => parse("Report", v) as Report,
@ -1292,11 +1376,13 @@ export const parser = {
SuppressAddress: (v: any) => parse("SuppressAddress", v) as SuppressAddress,
TLSResult: (v: any) => parse("TLSResult", v) as TLSResult,
TLSRPTSuppressAddress: (v: any) => parse("TLSRPTSuppressAddress", v) as TLSRPTSuppressAddress,
Dynamic: (v: any) => parse("Dynamic", v) as Dynamic,
CSRFToken: (v: any) => parse("CSRFToken", v) as CSRFToken,
DMARCPolicy: (v: any) => parse("DMARCPolicy", v) as DMARCPolicy,
Align: (v: any) => parse("Align", v) as Align,
RUA: (v: any) => parse("RUA", v) as RUA,
Mode: (v: any) => parse("Mode", v) as Mode,
Localpart: (v: any) => parse("Localpart", v) as Localpart,
PolicyType: (v: any) => parse("PolicyType", v) as PolicyType,
ResultType: (v: any) => parse("ResultType", v) as ResultType,
Alignment: (v: any) => parse("Alignment", v) as Alignment,
@ -1306,7 +1392,6 @@ export const parser = {
DKIMResult: (v: any) => parse("DKIMResult", v) as DKIMResult,
SPFDomainScope: (v: any) => parse("SPFDomainScope", v) as SPFDomainScope,
SPFResult: (v: any) => parse("SPFResult", v) as SPFResult,
Localpart: (v: any) => parse("Localpart", v) as Localpart,
IP: (v: any) => parse("IP", v) as IP,
}
@ -1406,6 +1491,15 @@ export class Client {
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as Domain
}
// DomainConfig returns the configuration for a domain.
async DomainConfig(domain: string): Promise<ConfigDomain> {
const fn: string = "DomainConfig"
const paramTypes: string[][] = [["string"]]
const returnTypes: string[][] = [["ConfigDomain"]]
const params: any[] = [domain]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as ConfigDomain
}
// DomainLocalparts returns the encoded localparts and accounts configured in domain.
async DomainLocalparts(domain: string): Promise<{ [key: string]: string }> {
const fn: string = "DomainLocalparts"
@ -2033,6 +2127,42 @@ export class Client {
const params: any[] = [recvID]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as string
}
// Config returns the dynamic config.
async Config(): Promise<Dynamic> {
const fn: string = "Config"
const paramTypes: string[][] = []
const returnTypes: string[][] = [["Dynamic"]]
const params: any[] = []
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as Dynamic
}
// AccountRoutesSave saves routes for an account.
async AccountRoutesSave(accountName: string, routes: Route[] | null): Promise<void> {
const fn: string = "AccountRoutesSave"
const paramTypes: string[][] = [["string"],["[]","Route"]]
const returnTypes: string[][] = []
const params: any[] = [accountName, routes]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// DomainRoutesSave saves routes for a domain.
async DomainRoutesSave(domainName: string, routes: Route[] | null): Promise<void> {
const fn: string = "DomainRoutesSave"
const paramTypes: string[][] = [["string"],["[]","Route"]]
const returnTypes: string[][] = []
const params: any[] = [domainName, routes]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// RoutesSave saves global routes.
async RoutesSave(routes: Route[] | null): Promise<void> {
const fn: string = "RoutesSave"
const paramTypes: string[][] = [["[]","Route"]]
const returnTypes: string[][] = []
const params: any[] = [routes]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
}
export const defaultBaseURL = (function() {