mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 16:33:47 +03:00
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:
parent
baf4df55a6
commit
a69887bfab
19 changed files with 1165 additions and 189 deletions
2
Makefile
2
Makefile
|
@ -7,7 +7,7 @@ build0:
|
|||
CGO_ENABLED=0 go build
|
||||
CGO_ENABLED=0 go vet ./...
|
||||
./gendoc.sh
|
||||
(cd webadmin && CGO_ENABLED=0 go run ../vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/*.go -adjust-function-names none Admin) >webadmin/api.json
|
||||
(cd webadmin && CGO_ENABLED=0 go run ../vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/*.go -adjust-function-names none -rename 'config Domain ConfigDomain' Admin) >webadmin/api.json
|
||||
(cd webaccount && CGO_ENABLED=0 go run ../vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/*.go -adjust-function-names none Account) >webaccount/api.json
|
||||
(cd webmail && CGO_ENABLED=0 go run ../vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/*.go -adjust-function-names none Webmail) >webmail/api.json
|
||||
./gents.sh webadmin/api.json webadmin/api.ts
|
||||
|
|
|
@ -320,13 +320,15 @@ type TLSRPT struct {
|
|||
DNSDomain dns.Domain `sconf:"-"` // Effective domain, always set based on Domain field or Domain where this is configured.
|
||||
}
|
||||
|
||||
type Canonicalization struct {
|
||||
HeaderRelaxed bool `sconf-doc:"If set, some modifications to the headers (mostly whitespace) are allowed."`
|
||||
BodyRelaxed bool `sconf-doc:"If set, some whitespace modifications to the message body are allowed."`
|
||||
}
|
||||
|
||||
type Selector struct {
|
||||
Hash string `sconf:"optional" sconf-doc:"sha256 (default) or (older, not recommended) sha1"`
|
||||
HashEffective string `sconf:"-"`
|
||||
Canonicalization struct {
|
||||
HeaderRelaxed bool `sconf-doc:"If set, some modifications to the headers (mostly whitespace) are allowed."`
|
||||
BodyRelaxed bool `sconf-doc:"If set, some whitespace modifications to the message body are allowed."`
|
||||
} `sconf:"optional"`
|
||||
Canonicalization Canonicalization `sconf:"optional"`
|
||||
Headers []string `sconf:"optional" sconf-doc:"Headers to sign with DKIM. If empty, a reasonable default set of headers is selected."`
|
||||
HeadersEffective []string `sconf:"-"` // Used when signing. Based on Headers from config, or the reasonable default.
|
||||
DontSealHeaders bool `sconf:"optional" sconf-doc:"If set, don't prevent duplicate headers from being added. Not recommended."`
|
||||
|
|
2
go.mod
2
go.mod
|
@ -8,7 +8,7 @@ require (
|
|||
github.com/mjl-/bstore v0.0.5
|
||||
github.com/mjl-/sconf v0.0.6
|
||||
github.com/mjl-/sherpa v0.6.7
|
||||
github.com/mjl-/sherpadoc v0.0.13
|
||||
github.com/mjl-/sherpadoc v0.0.14
|
||||
github.com/mjl-/sherpaprom v0.0.2
|
||||
github.com/mjl-/sherpats v0.0.6
|
||||
github.com/prometheus/client_golang v1.18.0
|
||||
|
|
4
go.sum
4
go.sum
|
@ -35,8 +35,8 @@ github.com/mjl-/sconf v0.0.6/go.mod h1:uF8OdWtLT8La3i4ln176i1pB0ps9pXGCaABEU55Zk
|
|||
github.com/mjl-/sherpa v0.6.7 h1:C5F8XQdV5nCuS4fvB+ye/ziUQrajEhOoj/t2w5T14BY=
|
||||
github.com/mjl-/sherpa v0.6.7/go.mod h1:dSpAOdgpwdqQZ72O4n3EHo/tR68eKyan8tYYraUMPNc=
|
||||
github.com/mjl-/sherpadoc v0.0.0-20190505200843-c0a7f43f5f1d/go.mod h1:5khTKxoKKNXcB8bkVUO6GlzC7PFtMmkHq578lPbmnok=
|
||||
github.com/mjl-/sherpadoc v0.0.13 h1:Rem75lUlFsgX6TPTdtBxskY/Ze/yivaRkFw1H1MUmFw=
|
||||
github.com/mjl-/sherpadoc v0.0.13/go.mod h1:vh5zcsk3j/Tvm725EY+unTZb3EZcZcpiEQzrODSa6+I=
|
||||
github.com/mjl-/sherpadoc v0.0.14 h1:Xrdg8RhAmTDQXlEU+qDSlige4zfhMHr+VKBJNpPeWe4=
|
||||
github.com/mjl-/sherpadoc v0.0.14/go.mod h1:vh5zcsk3j/Tvm725EY+unTZb3EZcZcpiEQzrODSa6+I=
|
||||
github.com/mjl-/sherpaprom v0.0.2 h1:1dlbkScsNafM5jURI44uiWrZMSwfZtcOFEEq7vx2C1Y=
|
||||
github.com/mjl-/sherpaprom v0.0.2/go.mod h1:cl5nMNOvqhzMiQJ2FzccQ9ReivjHXe53JhOVkPfSvw4=
|
||||
github.com/mjl-/sherpats v0.0.6 h1:2lSoJbb+jkjLOdlvoMxItq0QQrrnkH+rnm3PMRfpbmA=
|
||||
|
|
|
@ -47,7 +47,9 @@ func recvid(r *http.Request) string {
|
|||
// If no handler matched, false is returned.
|
||||
// WebHandle sets w.Name to that of the matching handler.
|
||||
func WebHandle(w *loggingWriter, r *http.Request, host dns.Domain) (handled bool) {
|
||||
redirects, handlers := mox.Conf.WebServer()
|
||||
conf := mox.Conf.DynamicConfig()
|
||||
redirects := conf.WebDNSDomainRedirects
|
||||
handlers := conf.WebHandlers
|
||||
|
||||
for from, to := range redirects {
|
||||
if host != from {
|
||||
|
|
|
@ -430,28 +430,68 @@ func DomainRemove(ctx context.Context, domain dns.Domain) (rerr error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
func WebserverConfigSet(ctx context.Context, domainRedirects map[string]string, webhandlers []config.WebHandler) (rerr error) {
|
||||
// DomainSave calls xmodify with a shallow copy of the domain config. xmodify
|
||||
// can modify the config, but must clone all referencing data it changes.
|
||||
// xmodify may employ panic-based error handling. After xmodify returns, the
|
||||
// modified config is verified, saved and takes effect.
|
||||
func DomainSave(ctx context.Context, domainName string, xmodify func(config *config.Domain)) (rerr error) {
|
||||
log := pkglog.WithContext(ctx)
|
||||
defer func() {
|
||||
if rerr != nil {
|
||||
log.Errorx("saving webserver config", rerr)
|
||||
log.Errorx("saving domain config", rerr)
|
||||
}
|
||||
}()
|
||||
|
||||
Conf.dynamicMutex.Lock()
|
||||
defer Conf.dynamicMutex.Unlock()
|
||||
|
||||
// Compose new config without modifying existing data structures. If we fail, we
|
||||
// leave no trace.
|
||||
nc := Conf.Dynamic
|
||||
nc.WebDomainRedirects = domainRedirects
|
||||
nc.WebHandlers = webhandlers
|
||||
|
||||
if err := writeDynamic(ctx, log, nc); err != nil {
|
||||
return fmt.Errorf("writing domains.conf: %v", err)
|
||||
nc := Conf.Dynamic // Shallow copy.
|
||||
dom, ok := nc.Domains[domainName] // dom is a shallow copy.
|
||||
if !ok {
|
||||
return fmt.Errorf("domain not present")
|
||||
}
|
||||
|
||||
log.Info("webserver config saved")
|
||||
xmodify(&dom)
|
||||
|
||||
// Compose new config without modifying existing data structures. If we fail, we
|
||||
// leave no trace.
|
||||
nc.Domains = map[string]config.Domain{}
|
||||
for name, d := range Conf.Dynamic.Domains {
|
||||
nc.Domains[name] = d
|
||||
}
|
||||
nc.Domains[domainName] = dom
|
||||
|
||||
if err := writeDynamic(ctx, log, nc); err != nil {
|
||||
return fmt.Errorf("writing domains.conf: %w", err)
|
||||
}
|
||||
|
||||
log.Info("domain saved")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigSave calls xmodify with a shallow copy of the dynamic config. xmodify
|
||||
// can modify the config, but must clone all referencing data it changes.
|
||||
// xmodify may employ panic-based error handling. After xmodify returns, the
|
||||
// modified config is verified, saved and takes effect.
|
||||
func ConfigSave(ctx context.Context, xmodify func(config *config.Dynamic)) (rerr error) {
|
||||
log := pkglog.WithContext(ctx)
|
||||
defer func() {
|
||||
if rerr != nil {
|
||||
log.Errorx("saving config", rerr)
|
||||
}
|
||||
}()
|
||||
|
||||
Conf.dynamicMutex.Lock()
|
||||
defer Conf.dynamicMutex.Unlock()
|
||||
|
||||
nc := Conf.Dynamic // Shallow copy.
|
||||
xmodify(&nc)
|
||||
|
||||
if err := writeDynamic(ctx, log, nc); err != nil {
|
||||
return fmt.Errorf("writing domains.conf: %w", err)
|
||||
}
|
||||
|
||||
log.Info("config saved")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -774,7 +814,7 @@ func AccountAdd(ctx context.Context, account, address string) (rerr error) {
|
|||
nc.Accounts[account] = MakeAccountConfig(addr)
|
||||
|
||||
if err := writeDynamic(ctx, log, nc); err != nil {
|
||||
return fmt.Errorf("writing domains.conf: %v", err)
|
||||
return fmt.Errorf("writing domains.conf: %w", err)
|
||||
}
|
||||
log.Info("account added", slog.String("account", account), slog.Any("address", addr))
|
||||
return nil
|
||||
|
@ -808,7 +848,7 @@ func AccountRemove(ctx context.Context, account string) (rerr error) {
|
|||
}
|
||||
|
||||
if err := writeDynamic(ctx, log, nc); err != nil {
|
||||
return fmt.Errorf("writing domains.conf: %v", err)
|
||||
return fmt.Errorf("writing domains.conf: %w", err)
|
||||
}
|
||||
log.Info("account removed", slog.String("account", account))
|
||||
return nil
|
||||
|
@ -892,7 +932,7 @@ func AddressAdd(ctx context.Context, address, account string) (rerr error) {
|
|||
nc.Accounts[account] = a
|
||||
|
||||
if err := writeDynamic(ctx, log, nc); err != nil {
|
||||
return fmt.Errorf("writing domains.conf: %v", err)
|
||||
return fmt.Errorf("writing domains.conf: %w", err)
|
||||
}
|
||||
log.Info("address added", slog.String("address", address), slog.String("account", account))
|
||||
return nil
|
||||
|
@ -989,7 +1029,7 @@ func AddressRemove(ctx context.Context, address string) (rerr error) {
|
|||
nc.Accounts[ad.Account] = na
|
||||
|
||||
if err := writeDynamic(ctx, log, nc); err != nil {
|
||||
return fmt.Errorf("writing domains.conf: %v", err)
|
||||
return fmt.Errorf("writing domains.conf: %w", err)
|
||||
}
|
||||
log.Info("address removed", slog.String("address", address), slog.String("account", ad.Account))
|
||||
return nil
|
||||
|
@ -1035,35 +1075,6 @@ func AccountSave(ctx context.Context, account string, xmodify func(acc *config.A
|
|||
return nil
|
||||
}
|
||||
|
||||
func MonitorDNSBLsSave(ctx context.Context, zones []dns.Domain) (rerr error) {
|
||||
log := pkglog.WithContext(ctx)
|
||||
defer func() {
|
||||
if rerr != nil {
|
||||
log.Errorx("saving monitor dnsbl zones", rerr)
|
||||
}
|
||||
}()
|
||||
|
||||
Conf.dynamicMutex.Lock()
|
||||
defer Conf.dynamicMutex.Unlock()
|
||||
|
||||
c := Conf.Dynamic
|
||||
|
||||
// Compose new config without modifying existing data structures. If we fail, we
|
||||
// leave no trace.
|
||||
nc := c
|
||||
nc.MonitorDNSBLs = make([]string, len(zones))
|
||||
nc.MonitorDNSBLZones = nil
|
||||
for i, z := range zones {
|
||||
nc.MonitorDNSBLs[i] = z.Name()
|
||||
}
|
||||
|
||||
if err := writeDynamic(ctx, log, nc); err != nil {
|
||||
return fmt.Errorf("writing domains.conf: %v", err)
|
||||
}
|
||||
log.Info("monitor dnsbl zones saved")
|
||||
return nil
|
||||
}
|
||||
|
||||
type TLSMode uint8
|
||||
|
||||
const (
|
||||
|
|
|
@ -163,6 +163,14 @@ func (c *Config) loadDynamic() []error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// DynamicConfig returns a shallow copy of the dynamic config. Must not be modified.
|
||||
func (c *Config) DynamicConfig() (config config.Dynamic) {
|
||||
c.withDynamicLock(func() {
|
||||
config = c.Dynamic // Shallow copy.
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Config) Domains() (l []string) {
|
||||
c.withDynamicLock(func() {
|
||||
for name := range c.Dynamic.Domains {
|
||||
|
@ -224,14 +232,6 @@ func (c *Config) AccountDestination(addr string) (accDests AccountDestination, o
|
|||
return
|
||||
}
|
||||
|
||||
func (c *Config) WebServer() (r map[dns.Domain]dns.Domain, l []config.WebHandler) {
|
||||
c.withDynamicLock(func() {
|
||||
r = c.Dynamic.WebDNSDomainRedirects
|
||||
l = c.Dynamic.WebHandlers
|
||||
})
|
||||
return r, l
|
||||
}
|
||||
|
||||
func (c *Config) Routes(accountName string, domain dns.Domain) (accountRoutes, domainRoutes, globalRoutes []config.Route) {
|
||||
c.withDynamicLock(func() {
|
||||
acc := c.Dynamic.Accounts[accountName]
|
||||
|
@ -245,13 +245,6 @@ func (c *Config) Routes(accountName string, domain dns.Domain) (accountRoutes, d
|
|||
return
|
||||
}
|
||||
|
||||
func (c *Config) MonitorDNSBLs() (zones []dns.Domain) {
|
||||
c.withDynamicLock(func() {
|
||||
zones = c.Dynamic.MonitorDNSBLZones
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Config) allowACMEHosts(log mlog.Log, checkACMEHosts bool) {
|
||||
for _, l := range c.Static.Listeners {
|
||||
if l.TLS == nil || l.TLS.ACME == "" {
|
||||
|
|
|
@ -86,7 +86,8 @@ func monitorDNSBL(log mlog.Log) {
|
|||
|
||||
// Gather zones.
|
||||
zones := append([]dns.Domain{}, publicListener.SMTP.DNSBLZones...)
|
||||
for _, zone := range mox.Conf.MonitorDNSBLs() {
|
||||
conf := mox.Conf.DynamicConfig()
|
||||
for _, zone := range conf.MonitorDNSBLZones {
|
||||
if !slices.Contains(zones, zone) {
|
||||
zones = append(zones, zone)
|
||||
}
|
||||
|
|
5
testdata/webadmin/mox.conf
vendored
5
testdata/webadmin/mox.conf
vendored
|
@ -10,3 +10,8 @@ Listeners:
|
|||
Postmaster:
|
||||
Account: mjl
|
||||
Mailbox: postmaster
|
||||
|
||||
Transports:
|
||||
direct:
|
||||
Direct:
|
||||
DisableIPv6: true
|
||||
|
|
4
vendor/github.com/mjl-/sherpadoc/README.txt
generated
vendored
4
vendor/github.com/mjl-/sherpadoc/README.txt
generated
vendored
|
@ -17,8 +17,8 @@ MIT-licensed, see LICENSE.
|
|||
- major cleanup required. too much parsing is done that can probably be handled by the go/* packages.
|
||||
- check that all cases of embedding work (seems like we will include duplicates: when a struct has fields that override an embedded struct, we generate duplicate fields).
|
||||
- check that all cross-package referencing (ast.SelectorExpr) works
|
||||
- better cli syntax for replacements, and always replace based on fully qualified names. currently you need to specify both the fully qualified and unqualified type paths.
|
||||
- see if order of items in output depends on a map somewhere, i've seen diffs for generated jsons where a type was only moved, not modified.
|
||||
- better cli syntax for replacements and renames, and always replace based on fully qualified names. currently you need to specify both the fully qualified and unqualified type paths.
|
||||
- see if order of items in output depends on a map somewhere, i've seen diffs for generated jsons where a type was only moved, not modified. perhaps the type was discovered earlier/later because of other type changes. we may want to sort sections,methods,types in the output.
|
||||
- better error messages and error handling, stricter parsing
|
||||
- support type aliases
|
||||
- support plain iota enums? currently only simple literals are supported for enums.
|
||||
|
|
30
vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/main.go
generated
vendored
30
vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/main.go
generated
vendored
|
@ -56,6 +56,7 @@ import (
|
|||
var (
|
||||
packagePath = flag.String("package-path", ".", "of source code to parse")
|
||||
replace = flag.String("replace", "", "comma-separated list of type replacements, e.g. \"somepkg.SomeType string\"")
|
||||
rename = flag.String("rename", "", "comma-separated list of type renames as used with a package selector, e.g. \"somepkg SomeName OtherName\"")
|
||||
title = flag.String("title", "", "title of the API, default is the name of the type of the main API")
|
||||
adjustFunctionNames = flag.String("adjust-function-names", "", `by default, the first character of function names is turned into lower case; with "lowerWord" the first string of upper case characters is lower cased, with "none" the name is left as is`)
|
||||
)
|
||||
|
@ -140,6 +141,13 @@ func check(err error, action string) {
|
|||
}
|
||||
}
|
||||
|
||||
type renameSrc struct {
|
||||
Pkg string // Package selector, not full path at the moment.
|
||||
Name string
|
||||
}
|
||||
|
||||
var renames = map[renameSrc]string{}
|
||||
|
||||
func usage() {
|
||||
log.Println("usage: sherpadoc [flags] section")
|
||||
flag.PrintDefaults()
|
||||
|
@ -155,6 +163,28 @@ func main() {
|
|||
usage()
|
||||
}
|
||||
|
||||
if *rename != "" {
|
||||
to := map[string]bool{} // Track target names, for detecting duplicates.
|
||||
for _, elem := range strings.Split(*rename, ",") {
|
||||
l := strings.Split(elem, " ")
|
||||
if len(l) != 3 {
|
||||
log.Printf("invalid rename %q", elem)
|
||||
usage()
|
||||
}
|
||||
src := renameSrc{l[0], l[1]}
|
||||
if _, ok := renames[src]; ok {
|
||||
log.Printf("duplicate rename %q", elem)
|
||||
usage()
|
||||
}
|
||||
if to[l[2]] {
|
||||
log.Printf("duplicate rename type %q", l[2])
|
||||
usage()
|
||||
}
|
||||
to[l[2]] = true
|
||||
renames[src] = l[2]
|
||||
}
|
||||
}
|
||||
|
||||
// If vendor exists, we load packages from it.
|
||||
for dir, _ := os.Getwd(); dir != "" && dir != "/"; dir = filepath.Dir(dir) {
|
||||
p := filepath.Join(dir, "go.mod")
|
||||
|
|
26
vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/parse.go
generated
vendored
26
vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/parse.go
generated
vendored
|
@ -181,15 +181,21 @@ func parseSection(t *doc.Type, pp *parsedPackage) *section {
|
|||
}
|
||||
|
||||
// Ensure type "t" (used in a field or argument) defined in package pp is parsed
|
||||
// and added to the section.
|
||||
func ensureNamedType(t *doc.Type, sec *section, pp *parsedPackage) {
|
||||
typePath := pp.Path + "." + t.Name
|
||||
// and added to the section. The returned string is the name as used in the
|
||||
// sherpadoc, it may have been renamed.
|
||||
func ensureNamedType(t *doc.Type, sec *section, pp *parsedPackage) (name string) {
|
||||
if s, ok := renames[renameSrc{pp.Pkg.Name, t.Name}]; ok {
|
||||
name = s
|
||||
} else {
|
||||
name = t.Name
|
||||
}
|
||||
typePath := pp.Path + "." + name
|
||||
if _, have := sec.Typeset[typePath]; have {
|
||||
return
|
||||
}
|
||||
|
||||
tt := &namedType{
|
||||
Name: t.Name,
|
||||
Name: name,
|
||||
Text: strings.TrimSpace(t.Doc),
|
||||
}
|
||||
// add it early, so self-referencing types can't cause a loop
|
||||
|
@ -360,6 +366,7 @@ func ensureNamedType(t *doc.Type, sec *section, pp *parsedPackage) {
|
|||
default:
|
||||
logFatalLinef(pp, t.Decl.TokPos, "unsupported field/param/return type %T", ts.Type)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func hasOmitEmpty(tag *ast.BasicLit) bool {
|
||||
|
@ -407,8 +414,8 @@ func gatherFieldType(typeName string, f *field, e ast.Expr, fieldTag *ast.BasicL
|
|||
case *ast.Ident:
|
||||
tt := pp.lookupType(t.Name)
|
||||
if tt != nil {
|
||||
ensureNamedType(tt, sec, pp)
|
||||
return []string{t.Name}
|
||||
name := ensureNamedType(tt, sec, pp)
|
||||
return []string{name}
|
||||
}
|
||||
commaString := isCommaString(fieldTag)
|
||||
name := t.Name
|
||||
|
@ -465,8 +472,8 @@ func parseArgType(e ast.Expr, sec *section, pp *parsedPackage) typewords {
|
|||
case *ast.Ident:
|
||||
tt := pp.lookupType(t.Name)
|
||||
if tt != nil {
|
||||
ensureNamedType(tt, sec, pp)
|
||||
return []string{t.Name}
|
||||
name := ensureNamedType(tt, sec, pp)
|
||||
return []string{name}
|
||||
}
|
||||
name := t.Name
|
||||
switch name {
|
||||
|
@ -560,8 +567,7 @@ func parseSelector(t *ast.SelectorExpr, srcTypeName string, sec *section, pp *pa
|
|||
if tt == nil {
|
||||
logFatalLinef(pp, t.Pos(), "could not find type %q in package %q", typeName, importPath)
|
||||
}
|
||||
ensureNamedType(tt, sec, opp)
|
||||
return typeName
|
||||
return ensureNamedType(tt, sec, opp)
|
||||
}
|
||||
|
||||
type replacement struct {
|
||||
|
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
|
@ -25,7 +25,7 @@ github.com/mjl-/sconf
|
|||
# github.com/mjl-/sherpa v0.6.7
|
||||
## explicit; go 1.12
|
||||
github.com/mjl-/sherpa
|
||||
# github.com/mjl-/sherpadoc v0.0.13
|
||||
# github.com/mjl-/sherpadoc v0.0.14
|
||||
## explicit; go 1.16
|
||||
github.com/mjl-/sherpadoc
|
||||
github.com/mjl-/sherpadoc/cmd/sherpadoc
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.",
|
||||
|
|
170
webadmin/api.ts
170
webadmin/api.ts
|
@ -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() {
|
||||
|
|
Loading…
Reference in a new issue