mirror of
https://github.com/mjl-/mox.git
synced 2025-06-27 22:28:16 +03:00
Allow multiple localpart catch all separators, e.g. both "+" and "-", for addresses you+anything@example.com and you-anything@example.com
The original config option stays, and we still use it for the common case where we have a single separator. The "+" is configured by default. It is optional, just like the new option "LocalpartCatchallSeparators" (plural). When parsing the config file, we combine LocalpartCatchallSeparator and LocalpartCatchallSeparators into a single list LocalpartCatchallSeparatorsEffective, which we use throughout the code. For issue #301 by janc13
This commit is contained in:
parent
d0b241499f
commit
9a8bb1134b
@ -737,7 +737,7 @@ func AccountRemove(ctx context.Context, account string) (rerr error) {
|
||||
}
|
||||
|
||||
// checkAddressAvailable checks that the address after canonicalization is not
|
||||
// already configured, and that its localpart does not contain the catchall
|
||||
// already configured, and that its localpart does not contain a catchall
|
||||
// localpart separator.
|
||||
//
|
||||
// Must be called with config lock held.
|
||||
@ -749,9 +749,13 @@ func checkAddressAvailable(addr smtp.Address) error {
|
||||
lp := mox.CanonicalLocalpart(addr.Localpart, dc)
|
||||
if _, ok := mox.Conf.AccountDestinationsLocked[smtp.NewAddress(lp, addr.Domain).String()]; ok {
|
||||
return fmt.Errorf("canonicalized address %s already configured", smtp.NewAddress(lp, addr.Domain))
|
||||
} else if dc.LocalpartCatchallSeparator != "" && strings.Contains(string(addr.Localpart), dc.LocalpartCatchallSeparator) {
|
||||
return fmt.Errorf("localpart cannot include domain catchall separator %s", dc.LocalpartCatchallSeparator)
|
||||
} else if _, ok := dc.Aliases[lp.String()]; ok {
|
||||
}
|
||||
for _, sep := range dc.LocalpartCatchallSeparatorsEffective {
|
||||
if strings.Contains(string(addr.Localpart), sep) {
|
||||
return fmt.Errorf("localpart cannot include domain catchall separator %s", sep)
|
||||
}
|
||||
}
|
||||
if _, ok := dc.Aliases[lp.String()]; ok {
|
||||
return fmt.Errorf("address in use as alias")
|
||||
}
|
||||
return nil
|
||||
|
@ -279,17 +279,18 @@ type TransportDirect struct {
|
||||
}
|
||||
|
||||
type Domain struct {
|
||||
Disabled bool `sconf:"optional" sconf-doc:"Disabled domains can be useful during/before migrations. Domains that are disabled can still be configured like normal, including adding addresses using the domain to accounts. However, disabled domains: 1. Do not try to fetch ACME certificates. TLS connections to host names involving the email domain will fail. A TLS certificate for the hostname (that wil be used as MX) itself will be requested. 2. Incoming deliveries over SMTP are rejected with a temporary error '450 4.2.1 recipient domain temporarily disabled'. 3. Submissions over SMTP using an (envelope) SMTP MAIL FROM address or message 'From' address of a disabled domain will be rejected with a temporary error '451 4.3.0 sender domain temporarily disabled'. Note that accounts with addresses at disabled domains can still log in and read email (unless the account itself is disabled)."`
|
||||
Description string `sconf:"optional" sconf-doc:"Free-form description of domain."`
|
||||
ClientSettingsDomain string `sconf:"optional" sconf-doc:"Hostname for client settings instead of the mail server hostname. E.g. mail.<domain>. For future migration to another mail operator without requiring all clients to update their settings, it is convenient to have client settings that reference a subdomain of the hosted domain instead of the hostname of the server where the mail is currently hosted. If empty, the hostname of the mail server is used for client configurations. Unicode name."`
|
||||
LocalpartCatchallSeparator string `sconf:"optional" sconf-doc:"If not empty, only the string before the separator is used to for email delivery decisions. For example, if set to \"+\", you+anything@example.com will be delivered to you@example.com."`
|
||||
LocalpartCaseSensitive bool `sconf:"optional" sconf-doc:"If set, upper/lower case is relevant for email delivery."`
|
||||
DKIM DKIM `sconf:"optional" sconf-doc:"With DKIM signing, a domain is taking responsibility for (content of) emails it sends, letting receiving mail servers build up a (hopefully positive) reputation of the domain, which can help with mail delivery."`
|
||||
DMARC *DMARC `sconf:"optional" sconf-doc:"With DMARC, a domain publishes, in DNS, a policy on how other mail servers should handle incoming messages with the From-header matching this domain and/or subdomain (depending on the configured alignment). Receiving mail servers use this to build up a reputation of this domain, which can help with mail delivery. A domain can also publish an email address to which reports about DMARC verification results can be sent by verifying mail servers, useful for monitoring. Incoming DMARC reports are automatically parsed, validated, added to metrics and stored in the reporting database for later display in the admin web pages."`
|
||||
MTASTS *MTASTS `sconf:"optional" sconf-doc:"MTA-STS is a mechanism that allows publishing a policy with requirements for WebPKI-verified SMTP STARTTLS connections for email delivered to a domain. Existence of a policy is announced in a DNS TXT record (often unprotected/unverified, MTA-STS's weak spot). If a policy exists, it is fetched with a WebPKI-verified HTTPS request. The policy can indicate that WebPKI-verified SMTP STARTTLS is required, and which MX hosts (optionally with a wildcard pattern) are allowd. MX hosts to deliver to are still taken from DNS (again, not necessarily protected/verified), but messages will only be delivered to domains matching the MX hosts from the published policy. Mail servers look up the MTA-STS policy when first delivering to a domain, then keep a cached copy, periodically checking the DNS record if a new policy is available, and fetching and caching it if so. To update a policy, first serve a new policy with an updated policy ID, then update the DNS record (not the other way around). To remove an enforced policy, publish an updated policy with mode \"none\" for a long enough period so all cached policies have been refreshed (taking DNS TTL and policy max age into account), then remove the policy from DNS, wait for TTL to expire, and stop serving the policy."`
|
||||
TLSRPT *TLSRPT `sconf:"optional" sconf-doc:"With TLSRPT a domain specifies in DNS where reports about encountered SMTP TLS behaviour should be sent. Useful for monitoring. Incoming TLS reports are automatically parsed, validated, added to metrics and stored in the reporting database for later display in the admin web pages."`
|
||||
Routes []Route `sconf:"optional" sconf-doc:"Routes for delivering outgoing messages through the queue. Each delivery attempt evaluates account routes, these domain routes and finally global routes. The transport of the first matching route is used in the delivery attempt. If no routes match, which is the default with no configured routes, messages are delivered directly from the queue."`
|
||||
Aliases map[string]Alias `sconf:"optional" sconf-doc:"Aliases that cause messages to be delivered to one or more locally configured addresses. Keys are localparts (encoded, as they appear in email addresses)."`
|
||||
Disabled bool `sconf:"optional" sconf-doc:"Disabled domains can be useful during/before migrations. Domains that are disabled can still be configured like normal, including adding addresses using the domain to accounts. However, disabled domains: 1. Do not try to fetch ACME certificates. TLS connections to host names involving the email domain will fail. A TLS certificate for the hostname (that wil be used as MX) itself will be requested. 2. Incoming deliveries over SMTP are rejected with a temporary error '450 4.2.1 recipient domain temporarily disabled'. 3. Submissions over SMTP using an (envelope) SMTP MAIL FROM address or message 'From' address of a disabled domain will be rejected with a temporary error '451 4.3.0 sender domain temporarily disabled'. Note that accounts with addresses at disabled domains can still log in and read email (unless the account itself is disabled)."`
|
||||
Description string `sconf:"optional" sconf-doc:"Free-form description of domain."`
|
||||
ClientSettingsDomain string `sconf:"optional" sconf-doc:"Hostname for client settings instead of the mail server hostname. E.g. mail.<domain>. For future migration to another mail operator without requiring all clients to update their settings, it is convenient to have client settings that reference a subdomain of the hosted domain instead of the hostname of the server where the mail is currently hosted. If empty, the hostname of the mail server is used for client configurations. Unicode name."`
|
||||
LocalpartCatchallSeparator string `sconf:"optional" sconf-doc:"If not empty, only the string before the separator is used to for email delivery decisions. For example, if set to \"+\", you+anything@example.com will be delivered to you@example.com."`
|
||||
LocalpartCatchallSeparators []string `sconf:"optional" sconf-doc:"Similar to LocalpartCatchallSeparator, but in case multiple are needed. For example both \"+\" and \"-\". Only of one LocalpartCatchallSeparator or LocalpartCatchallSeparators can be set. If set, the first separator is used to make unique addresses for outgoing SMTP connections with FromIDLoginAddresses."`
|
||||
LocalpartCaseSensitive bool `sconf:"optional" sconf-doc:"If set, upper/lower case is relevant for email delivery."`
|
||||
DKIM DKIM `sconf:"optional" sconf-doc:"With DKIM signing, a domain is taking responsibility for (content of) emails it sends, letting receiving mail servers build up a (hopefully positive) reputation of the domain, which can help with mail delivery."`
|
||||
DMARC *DMARC `sconf:"optional" sconf-doc:"With DMARC, a domain publishes, in DNS, a policy on how other mail servers should handle incoming messages with the From-header matching this domain and/or subdomain (depending on the configured alignment). Receiving mail servers use this to build up a reputation of this domain, which can help with mail delivery. A domain can also publish an email address to which reports about DMARC verification results can be sent by verifying mail servers, useful for monitoring. Incoming DMARC reports are automatically parsed, validated, added to metrics and stored in the reporting database for later display in the admin web pages."`
|
||||
MTASTS *MTASTS `sconf:"optional" sconf-doc:"MTA-STS is a mechanism that allows publishing a policy with requirements for WebPKI-verified SMTP STARTTLS connections for email delivered to a domain. Existence of a policy is announced in a DNS TXT record (often unprotected/unverified, MTA-STS's weak spot). If a policy exists, it is fetched with a WebPKI-verified HTTPS request. The policy can indicate that WebPKI-verified SMTP STARTTLS is required, and which MX hosts (optionally with a wildcard pattern) are allowd. MX hosts to deliver to are still taken from DNS (again, not necessarily protected/verified), but messages will only be delivered to domains matching the MX hosts from the published policy. Mail servers look up the MTA-STS policy when first delivering to a domain, then keep a cached copy, periodically checking the DNS record if a new policy is available, and fetching and caching it if so. To update a policy, first serve a new policy with an updated policy ID, then update the DNS record (not the other way around). To remove an enforced policy, publish an updated policy with mode \"none\" for a long enough period so all cached policies have been refreshed (taking DNS TTL and policy max age into account), then remove the policy from DNS, wait for TTL to expire, and stop serving the policy."`
|
||||
TLSRPT *TLSRPT `sconf:"optional" sconf-doc:"With TLSRPT a domain specifies in DNS where reports about encountered SMTP TLS behaviour should be sent. Useful for monitoring. Incoming TLS reports are automatically parsed, validated, added to metrics and stored in the reporting database for later display in the admin web pages."`
|
||||
Routes []Route `sconf:"optional" sconf-doc:"Routes for delivering outgoing messages through the queue. Each delivery attempt evaluates account routes, these domain routes and finally global routes. The transport of the first matching route is used in the delivery attempt. If no routes match, which is the default with no configured routes, messages are delivered directly from the queue."`
|
||||
Aliases map[string]Alias `sconf:"optional" sconf-doc:"Aliases that cause messages to be delivered to one or more locally configured addresses. Keys are localparts (encoded, as they appear in email addresses)."`
|
||||
|
||||
Domain dns.Domain `sconf:"-"`
|
||||
ClientSettingsDNSDomain dns.Domain `sconf:"-" json:"-"`
|
||||
@ -297,7 +298,8 @@ type Domain struct {
|
||||
// Set when DMARC and TLSRPT (when set) has an address with different domain (we're
|
||||
// hosting the reporting), and there are no destination addresses configured for
|
||||
// the domain. Disables some functionality related to hosting a domain.
|
||||
ReportsOnly bool `sconf:"-" json:"-"`
|
||||
ReportsOnly bool `sconf:"-" json:"-"`
|
||||
LocalpartCatchallSeparatorsEffective []string `sconf:"-"` // Either LocalpartCatchallSeparators, the value of LocalpartCatchallSeparator, or empty.
|
||||
}
|
||||
|
||||
// todo: allow external addresses as members of aliases. we would add messages for them to the queue for outgoing delivery. we should require an admin addresses to which delivery failures will be delivered (locally, and to use in smtp mail from, so dsns go there). also take care to evaluate smtputf8 (if external address requires utf8 and incoming transaction didn't).
|
||||
|
@ -789,6 +789,14 @@ See https://pkg.go.dev/github.com/mjl-/sconf for details.
|
||||
# delivered to you@example.com. (optional)
|
||||
LocalpartCatchallSeparator:
|
||||
|
||||
# Similar to LocalpartCatchallSeparator, but in case multiple are needed. For
|
||||
# example both "+" and "-". Only of one LocalpartCatchallSeparator or
|
||||
# LocalpartCatchallSeparators can be set. If set, the first separator is used to
|
||||
# make unique addresses for outgoing SMTP connections with FromIDLoginAddresses.
|
||||
# (optional)
|
||||
LocalpartCatchallSeparators:
|
||||
-
|
||||
|
||||
# If set, upper/lower case is relevant for email delivery. (optional)
|
||||
LocalpartCaseSensitive: false
|
||||
|
||||
|
@ -1238,6 +1238,21 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
|
||||
c.ClientSettingDomains[csd] = struct{}{}
|
||||
}
|
||||
|
||||
if domain.LocalpartCatchallSeparator != "" && len(domain.LocalpartCatchallSeparators) != 0 {
|
||||
addDomainErrorf("cannot have both LocalpartCatchallSeparator and LocalpartCatchallSeparators")
|
||||
}
|
||||
domain.LocalpartCatchallSeparatorsEffective = domain.LocalpartCatchallSeparators
|
||||
if domain.LocalpartCatchallSeparator != "" {
|
||||
domain.LocalpartCatchallSeparatorsEffective = append(domain.LocalpartCatchallSeparatorsEffective, domain.LocalpartCatchallSeparator)
|
||||
}
|
||||
sepSeen := map[string]bool{}
|
||||
for _, sep := range domain.LocalpartCatchallSeparatorsEffective {
|
||||
if sepSeen[sep] {
|
||||
addDomainErrorf("duplicate localpart catchall separator %q", sep)
|
||||
}
|
||||
sepSeen[sep] = true
|
||||
}
|
||||
|
||||
for _, sign := range domain.DKIM.Sign {
|
||||
if _, ok := domain.DKIM.Selectors[sign]; !ok {
|
||||
addDomainErrorf("unknown selector %s for signing", sign)
|
||||
@ -1434,7 +1449,7 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
|
||||
dom, ok := c.Domains[a.Domain.Name()]
|
||||
if !ok {
|
||||
addAccountErrorf("unknown domain in fromid login address %q", s)
|
||||
} else if dom.LocalpartCatchallSeparator == "" {
|
||||
} else if len(dom.LocalpartCatchallSeparatorsEffective) == 0 {
|
||||
addAccountErrorf("localpart catchall separator not configured for domain for fromid login address %q", s)
|
||||
}
|
||||
acc.ParsedFromIDLoginAddresses[i] = a
|
||||
@ -1660,9 +1675,14 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
|
||||
dc := c.Domains[address.Domain.Name()]
|
||||
domainHasAddress[address.Domain.Name()] = true
|
||||
lp := CanonicalLocalpart(address.Localpart, dc)
|
||||
if dc.LocalpartCatchallSeparator != "" && strings.Contains(string(address.Localpart), dc.LocalpartCatchallSeparator) {
|
||||
addDestErrorf("localpart of address %s includes domain catchall separator %s", address, dc.LocalpartCatchallSeparator)
|
||||
} else {
|
||||
var hasSep bool
|
||||
for _, sep := range dc.LocalpartCatchallSeparatorsEffective {
|
||||
if strings.Contains(string(address.Localpart), sep) {
|
||||
hasSep = true
|
||||
addDestErrorf("localpart of address %s includes domain catchall separator %s", address, sep)
|
||||
}
|
||||
}
|
||||
if !hasSep {
|
||||
address.Localpart = lp
|
||||
}
|
||||
addrFull := address.Pack(true)
|
||||
@ -1817,10 +1837,17 @@ func prepareDynamicConfig(ctx context.Context, log mlog.Log, dynamicPath string,
|
||||
if err != nil {
|
||||
addAliasErrorf("parsing alias: %v", err)
|
||||
continue
|
||||
} else if domain.LocalpartCatchallSeparator != "" && strings.Contains(string(lp), domain.LocalpartCatchallSeparator) {
|
||||
addAliasErrorf("alias contains localpart catchall separator")
|
||||
continue
|
||||
} else {
|
||||
var hasSep bool
|
||||
for _, sep := range domain.LocalpartCatchallSeparatorsEffective {
|
||||
if strings.Contains(string(lp), sep) {
|
||||
addAliasErrorf("alias contains localpart catchall separator")
|
||||
hasSep = true
|
||||
}
|
||||
}
|
||||
if hasSep {
|
||||
continue
|
||||
}
|
||||
clp = CanonicalLocalpart(lp, domain)
|
||||
}
|
||||
|
||||
|
@ -86,10 +86,10 @@ func LookupAddress(localpart smtp.Localpart, domain dns.Domain, allowPostmaster,
|
||||
}
|
||||
|
||||
// CanonicalLocalpart returns the canonical localpart, removing optional catchall
|
||||
// separator, and optionally lower-casing the string.
|
||||
// separators, and optionally lower-casing the string.
|
||||
func CanonicalLocalpart(localpart smtp.Localpart, d config.Domain) smtp.Localpart {
|
||||
if d.LocalpartCatchallSeparator != "" {
|
||||
t := strings.SplitN(string(localpart), d.LocalpartCatchallSeparator, 2)
|
||||
for _, sep := range d.LocalpartCatchallSeparatorsEffective {
|
||||
t := strings.SplitN(string(localpart), sep, 2)
|
||||
localpart = smtp.Localpart(t[0])
|
||||
}
|
||||
|
||||
|
@ -650,8 +650,8 @@ func Incoming(ctx context.Context, log mlog.Log, acc *store.Account, messageID s
|
||||
log.Debugx("parsing recipient domain in incoming message", err)
|
||||
} else {
|
||||
domconf, _ := mox.Conf.Domain(dom)
|
||||
if domconf.LocalpartCatchallSeparator != "" {
|
||||
t := strings.SplitN(string(m.RcptToLocalpart), domconf.LocalpartCatchallSeparator, 2)
|
||||
if len(domconf.LocalpartCatchallSeparatorsEffective) > 0 {
|
||||
t := strings.SplitN(string(m.RcptToLocalpart), domconf.LocalpartCatchallSeparatorsEffective[0], 2)
|
||||
if len(t) == 2 {
|
||||
fromID = t[1]
|
||||
}
|
||||
|
@ -2469,7 +2469,7 @@ func (c *conn) submit(ctx context.Context, recvHdrFor func(string) string, msgWr
|
||||
var genFromID bool
|
||||
if useFromID {
|
||||
// With submission, user can bring their own fromid.
|
||||
t := strings.SplitN(string(c.mailFrom.Localpart), confDom.LocalpartCatchallSeparator, 2)
|
||||
t := strings.SplitN(string(c.mailFrom.Localpart), confDom.LocalpartCatchallSeparatorsEffective[0], 2)
|
||||
localpartBase = t[0]
|
||||
if len(t) == 2 {
|
||||
fromID = t[1]
|
||||
@ -2500,7 +2500,7 @@ func (c *conn) submit(ctx context.Context, recvHdrFor func(string) string, msgWr
|
||||
if genFromID {
|
||||
fromID = xrandomID(16)
|
||||
}
|
||||
fp.Localpart = smtp.Localpart(localpartBase + confDom.LocalpartCatchallSeparator + fromID)
|
||||
fp.Localpart = smtp.Localpart(localpartBase + confDom.LocalpartCatchallSeparatorsEffective[0] + fromID)
|
||||
}
|
||||
|
||||
// For multiple recipients, we don't make each message prefix unique, leaving out
|
||||
|
@ -1494,12 +1494,13 @@ func TestCatchall(t *testing.T) {
|
||||
testDeliver("mjl@mox.example", nil) // Exact match.
|
||||
testDeliver("mjl+test@mox.example", nil) // Domain localpart catchall separator.
|
||||
testDeliver("MJL+TEST@mox.example", nil) // Again, and case insensitive.
|
||||
testDeliver("unknown@mox.example", nil) // Catchall address, to account catchall.
|
||||
|
||||
n, err := bstore.QueryDB[store.Message](ctxbg, ts.acc.DB).Count()
|
||||
tcheck(t, err, "checking delivered messages")
|
||||
tcompare(t, n, 3)
|
||||
|
||||
testDeliver("unknown@mox.example", nil) // Catchall address, to account catchall.
|
||||
|
||||
acc, err := store.OpenAccount(pkglog, "catchall", false)
|
||||
tcheck(t, err, "open account")
|
||||
defer func() {
|
||||
@ -1509,6 +1510,13 @@ func TestCatchall(t *testing.T) {
|
||||
n, err = bstore.QueryDB[store.Message](ctxbg, acc.DB).Count()
|
||||
tcheck(t, err, "checking delivered messages to catchall account")
|
||||
tcompare(t, n, 1)
|
||||
|
||||
testDeliver("mjl-test@mox2.example", nil) // Second catchall separator.
|
||||
testDeliver("mjl-test+test@mox2.example", nil) // Silly, both separators in address.
|
||||
testDeliver("mjl+test-test@mox2.example", nil)
|
||||
n, err = bstore.QueryDB[store.Message](ctxbg, ts.acc.DB).Count()
|
||||
tcheck(t, err, "checking delivered messages")
|
||||
tcompare(t, n, 6)
|
||||
}
|
||||
|
||||
// Test DKIM signing for outgoing messages.
|
||||
|
5
testdata/smtpservercatchall/domains.conf
vendored
5
testdata/smtpservercatchall/domains.conf
vendored
@ -1,11 +1,16 @@
|
||||
Domains:
|
||||
mox.example:
|
||||
LocalpartCatchallSeparator: +
|
||||
mox2.example:
|
||||
LocalpartCatchallSeparators:
|
||||
- +
|
||||
- -
|
||||
Accounts:
|
||||
mjl:
|
||||
Domain: mox.example
|
||||
Destinations:
|
||||
mjl@mox.example: nil
|
||||
mjl@mox2.example: nil
|
||||
catchall:
|
||||
Domain: mox.example
|
||||
Destinations:
|
||||
|
3
testdata/webmail/domains.conf
vendored
3
testdata/webmail/domains.conf
vendored
@ -2,6 +2,9 @@ Domains:
|
||||
disabled.example:
|
||||
Disabled: true
|
||||
mox.example:
|
||||
LocalpartCatchallSeparators:
|
||||
- +
|
||||
- -
|
||||
DKIM:
|
||||
Selectors:
|
||||
testsel:
|
||||
|
@ -2527,9 +2527,19 @@ func (Admin) DomainClientSettingsDomainSave(ctx context.Context, domainName, cli
|
||||
|
||||
// DomainLocalpartConfigSave saves the localpart catchall and case-sensitive
|
||||
// settings for a domain.
|
||||
func (Admin) DomainLocalpartConfigSave(ctx context.Context, domainName, localpartCatchallSeparator string, localpartCaseSensitive bool) {
|
||||
func (Admin) DomainLocalpartConfigSave(ctx context.Context, domainName string, localpartCatchallSeparators []string, localpartCaseSensitive bool) {
|
||||
err := admin.DomainSave(ctx, domainName, func(domain *config.Domain) error {
|
||||
domain.LocalpartCatchallSeparator = localpartCatchallSeparator
|
||||
domain.LocalpartCatchallSeparatorsEffective = localpartCatchallSeparators
|
||||
// If there is a single separator, we prefer the non-list form, it's easier to
|
||||
// read/edit and should suffice for most setups.
|
||||
domain.LocalpartCatchallSeparator = ""
|
||||
domain.LocalpartCatchallSeparators = nil
|
||||
if len(localpartCatchallSeparators) == 1 {
|
||||
domain.LocalpartCatchallSeparator = localpartCatchallSeparators[0]
|
||||
} else {
|
||||
domain.LocalpartCatchallSeparators = localpartCatchallSeparators
|
||||
}
|
||||
|
||||
domain.LocalpartCaseSensitive = localpartCaseSensitive
|
||||
return nil
|
||||
})
|
||||
|
@ -298,7 +298,7 @@ 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": "Disabled", "Docs": "", "Typewords": ["bool"] }, { "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": "Aliases", "Docs": "", "Typewords": ["{}", "Alias"] }, { "Name": "Domain", "Docs": "", "Typewords": ["Domain"] }] },
|
||||
"ConfigDomain": { "Name": "ConfigDomain", "Docs": "", "Fields": [{ "Name": "Disabled", "Docs": "", "Typewords": ["bool"] }, { "Name": "Description", "Docs": "", "Typewords": ["string"] }, { "Name": "ClientSettingsDomain", "Docs": "", "Typewords": ["string"] }, { "Name": "LocalpartCatchallSeparator", "Docs": "", "Typewords": ["string"] }, { "Name": "LocalpartCatchallSeparators", "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": "Aliases", "Docs": "", "Typewords": ["{}", "Alias"] }, { "Name": "Domain", "Docs": "", "Typewords": ["Domain"] }, { "Name": "LocalpartCatchallSeparatorsEffective", "Docs": "", "Typewords": ["[]", "string"] }] },
|
||||
"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"] }, { "Name": "Algorithm", "Docs": "", "Typewords": ["string"] }] },
|
||||
"Canonicalization": { "Name": "Canonicalization", "Docs": "", "Fields": [{ "Name": "HeaderRelaxed", "Docs": "", "Typewords": ["bool"] }, { "Name": "BodyRelaxed", "Docs": "", "Typewords": ["bool"] }] },
|
||||
@ -1220,11 +1220,11 @@ var api;
|
||||
}
|
||||
// DomainLocalpartConfigSave saves the localpart catchall and case-sensitive
|
||||
// settings for a domain.
|
||||
async DomainLocalpartConfigSave(domainName, localpartCatchallSeparator, localpartCaseSensitive) {
|
||||
async DomainLocalpartConfigSave(domainName, localpartCatchallSeparators, localpartCaseSensitive) {
|
||||
const fn = "DomainLocalpartConfigSave";
|
||||
const paramTypes = [["string"], ["string"], ["bool"]];
|
||||
const paramTypes = [["string"], ["[]", "string"], ["bool"]];
|
||||
const returnTypes = [];
|
||||
const params = [domainName, localpartCatchallSeparator, localpartCaseSensitive];
|
||||
const params = [domainName, localpartCatchallSeparators, localpartCaseSensitive];
|
||||
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
|
||||
}
|
||||
// DomainDMARCAddressSave saves the DMARC reporting address/processing
|
||||
@ -2372,7 +2372,6 @@ const domain = async (d) => {
|
||||
let clientSettingsDomainFieldset;
|
||||
let clientSettingsDomain;
|
||||
let localpartFieldset;
|
||||
let localpartCatchallSeparator;
|
||||
let localpartCaseSensitive;
|
||||
let dmarcFieldset;
|
||||
let dmarcLocalpart;
|
||||
@ -2469,11 +2468,39 @@ const domain = async (d) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
await check(clientSettingsDomainFieldset, client.DomainClientSettingsDomainSave(d, clientSettingsDomain.value));
|
||||
}, clientSettingsDomainFieldset = dom.fieldset(style({ display: 'flex', gap: '1em' }), dom.label(attr.title('Hostname for client settings instead of the mail server hostname. E.g. mail.<domain>. For future migration to another mail operator without requiring all clients to update their settings, it is convenient to have client settings that reference a subdomain of the hosted domain instead of the hostname of the server where the mail is currently hosted. If empty, the hostname of the mail server is used for client configurations. Unicode name.'), dom.div('Client settings domain'), clientSettingsDomain = dom.input(attr.value(domainConfig.ClientSettingsDomain), style({ width: '30em' }))), dom.div(dom.span('\u00a0'), dom.div(dom.submitbutton('Save'))))), dom.form(style({ marginTop: '1ex' }), async function submit(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
await check(localpartFieldset, client.DomainLocalpartConfigSave(d, localpartCatchallSeparator.value, localpartCaseSensitive.checked));
|
||||
}, localpartFieldset = dom.fieldset(style({ display: 'flex', gap: '1em' }), dom.label(attr.title('If set, upper/lower case is relevant for email delivery.'), dom.div('Localpart case sensitive'), localpartCaseSensitive = dom.input(attr.type('checkbox'), domainConfig.LocalpartCaseSensitive ? attr.checked('') : [])), dom.label(attr.title('If not empty, only the string before the separator is used to for email delivery decisions. For example, if set to \"+\", you+anything@example.com will be delivered to you@example.com.'), dom.div('Localpart catchall separator'), localpartCatchallSeparator = dom.input(attr.value(domainConfig.LocalpartCatchallSeparator))), dom.div(dom.span('\u00a0'), dom.div(dom.submitbutton('Save'))))), dom.br(), dom.h2('DMARC reporting address'), dom.form(style({ marginTop: '1ex' }), async function submit(e) {
|
||||
}, clientSettingsDomainFieldset = dom.fieldset(style({ display: 'flex', gap: '1em' }), dom.label(attr.title('Hostname for client settings instead of the mail server hostname. E.g. mail.<domain>. For future migration to another mail operator without requiring all clients to update their settings, it is convenient to have client settings that reference a subdomain of the hosted domain instead of the hostname of the server where the mail is currently hosted. If empty, the hostname of the mail server is used for client configurations. Unicode name.'), dom.div('Client settings domain'), clientSettingsDomain = dom.input(attr.value(domainConfig.ClientSettingsDomain), style({ width: '30em' }))), dom.div(dom.span('\u00a0'), dom.div(dom.submitbutton('Save'))))), (() => {
|
||||
let separatorViews = [];
|
||||
let separatorsBox;
|
||||
const addSeparatorView = (s) => {
|
||||
const separator = dom.input(attr.required(''), attr.value(s), style({ width: '2em' }));
|
||||
const v = {
|
||||
separator: separator,
|
||||
root: dom.div(separator, ' ', dom.clickbutton('Remove', function click() {
|
||||
separatorViews.splice(separatorViews.indexOf(v), 1);
|
||||
v.root.remove();
|
||||
if (separatorViews.length === 0) {
|
||||
separatorsBox.append(dom.div('(None)'));
|
||||
}
|
||||
})),
|
||||
};
|
||||
if (separatorViews.length === 0) {
|
||||
dom._kids(separatorsBox);
|
||||
}
|
||||
separatorViews.push(v);
|
||||
separatorsBox.appendChild(v.root);
|
||||
};
|
||||
const elem = dom.form(style({ marginTop: '1ex' }), async function submit(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
await check(localpartFieldset, client.DomainLocalpartConfigSave(d, separatorViews.map(v => v.separator.value), localpartCaseSensitive.checked));
|
||||
}, localpartFieldset = dom.fieldset(style({ display: 'flex', gap: '1em' }), dom.label(attr.title('If set, upper/lower case is relevant for email delivery.'), dom.div('Localpart case sensitive'), localpartCaseSensitive = dom.input(attr.type('checkbox'), domainConfig.LocalpartCaseSensitive ? attr.checked('') : [])), dom.div(dom.label(attr.title('If not empty, only the string before the separator is used for email delivery decisions. For example, if set to \"+\", you+anything@example.com will be delivered to you@example.com.'), 'Localpart catchall separators'), ' ', dom.clickbutton('Add', function click() {
|
||||
addSeparatorView('');
|
||||
}), separatorsBox = dom.div(style({ display: 'flex', flexDirection: 'column', gap: '.25em' }), dom.div('(None)'))), dom.div(dom.span('\u00a0'), dom.div(dom.submitbutton('Save')))));
|
||||
for (const sep of (domainConfig.LocalpartCatchallSeparatorsEffective || [])) {
|
||||
addSeparatorView(sep);
|
||||
}
|
||||
return elem;
|
||||
})(), dom.br(), dom.h2('DMARC reporting address'), dom.form(style({ marginTop: '1ex' }), async function submit(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!dmarcLocalpart.value) {
|
||||
|
@ -1243,7 +1243,6 @@ const domain = async (d: string) => {
|
||||
let clientSettingsDomain: HTMLInputElement
|
||||
|
||||
let localpartFieldset: HTMLFieldSetElement
|
||||
let localpartCatchallSeparator: HTMLInputElement
|
||||
let localpartCaseSensitive: HTMLInputElement
|
||||
|
||||
let dmarcFieldset: HTMLFieldSetElement
|
||||
@ -1588,28 +1587,68 @@ const domain = async (d: string) => {
|
||||
dom.div(dom.span('\u00a0'), dom.div(dom.submitbutton('Save'))),
|
||||
),
|
||||
),
|
||||
dom.form(
|
||||
style({marginTop: '1ex'}),
|
||||
async function submit(e: SubmitEvent) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
await check(localpartFieldset, client.DomainLocalpartConfigSave(d, localpartCatchallSeparator.value, localpartCaseSensitive.checked))
|
||||
},
|
||||
localpartFieldset=dom.fieldset(
|
||||
style({display: 'flex', gap: '1em'}),
|
||||
dom.label(
|
||||
attr.title('If set, upper/lower case is relevant for email delivery.'),
|
||||
dom.div('Localpart case sensitive'),
|
||||
localpartCaseSensitive=dom.input(attr.type('checkbox'), domainConfig.LocalpartCaseSensitive ? attr.checked('') : []),
|
||||
(() => {
|
||||
interface SeparatorView {
|
||||
root: HTMLElement
|
||||
separator: HTMLInputElement
|
||||
}
|
||||
let separatorViews: SeparatorView[] = []
|
||||
let separatorsBox: HTMLDivElement
|
||||
|
||||
const addSeparatorView = (s: string) => {
|
||||
const separator = dom.input(attr.required(''), attr.value(s), style({width: '2em'}))
|
||||
const v = {
|
||||
separator: separator,
|
||||
root: dom.div(
|
||||
separator, ' ',
|
||||
dom.clickbutton('Remove', function click() {
|
||||
separatorViews.splice(separatorViews.indexOf(v), 1)
|
||||
v.root.remove()
|
||||
if (separatorViews.length === 0) {
|
||||
separatorsBox.append(dom.div('(None)'))
|
||||
}
|
||||
}),
|
||||
),
|
||||
}
|
||||
if (separatorViews.length === 0) {
|
||||
dom._kids(separatorsBox)
|
||||
}
|
||||
separatorViews.push(v)
|
||||
separatorsBox.appendChild(v.root)
|
||||
}
|
||||
|
||||
const elem = dom.form(
|
||||
style({marginTop: '1ex'}),
|
||||
async function submit(e: SubmitEvent) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
await check(localpartFieldset, client.DomainLocalpartConfigSave(d, separatorViews.map(v => v.separator.value), localpartCaseSensitive.checked))
|
||||
},
|
||||
localpartFieldset=dom.fieldset(
|
||||
style({display: 'flex', gap: '1em'}),
|
||||
dom.label(
|
||||
attr.title('If set, upper/lower case is relevant for email delivery.'),
|
||||
dom.div('Localpart case sensitive'),
|
||||
localpartCaseSensitive=dom.input(attr.type('checkbox'), domainConfig.LocalpartCaseSensitive ? attr.checked('') : []),
|
||||
),
|
||||
dom.div(
|
||||
dom.label(
|
||||
attr.title('If not empty, only the string before the separator is used for email delivery decisions. For example, if set to \"+\", you+anything@example.com will be delivered to you@example.com.'),
|
||||
'Localpart catchall separators',
|
||||
), ' ',
|
||||
dom.clickbutton('Add', function click() {
|
||||
addSeparatorView('')
|
||||
}),
|
||||
separatorsBox=dom.div(style({display: 'flex', flexDirection: 'column', gap: '.25em'}), dom.div('(None)')),
|
||||
),
|
||||
dom.div(dom.span('\u00a0'), dom.div(dom.submitbutton('Save'))),
|
||||
),
|
||||
dom.label(
|
||||
attr.title('If not empty, only the string before the separator is used to for email delivery decisions. For example, if set to \"+\", you+anything@example.com will be delivered to you@example.com.'),
|
||||
dom.div('Localpart catchall separator'),
|
||||
localpartCatchallSeparator=dom.input(attr.value(domainConfig.LocalpartCatchallSeparator)),
|
||||
),
|
||||
dom.div(dom.span('\u00a0'), dom.div(dom.submitbutton('Save'))),
|
||||
),
|
||||
),
|
||||
)
|
||||
for (const sep of (domainConfig.LocalpartCatchallSeparatorsEffective || [])) {
|
||||
addSeparatorView(sep)
|
||||
}
|
||||
return elem
|
||||
})(),
|
||||
dom.br(),
|
||||
|
||||
dom.h2('DMARC reporting address'),
|
||||
|
@ -277,9 +277,9 @@ func TestAdmin(t *testing.T) {
|
||||
tneedErrorCode(t, "user:error", func() { api.DomainClientSettingsDomainSave(ctxbg, "bogus.example", "unknown.example") })
|
||||
api.DomainClientSettingsDomainSave(ctxbg, "mox.example", "") // Restore.
|
||||
|
||||
api.DomainLocalpartConfigSave(ctxbg, "mox.example", "-", true)
|
||||
tneedErrorCode(t, "user:error", func() { api.DomainLocalpartConfigSave(ctxbg, "bogus.example", "", false) })
|
||||
api.DomainLocalpartConfigSave(ctxbg, "mox.example", "", false) // Restore.
|
||||
api.DomainLocalpartConfigSave(ctxbg, "mox.example", []string{"-"}, true)
|
||||
tneedErrorCode(t, "user:error", func() { api.DomainLocalpartConfigSave(ctxbg, "bogus.example", nil, false) })
|
||||
api.DomainLocalpartConfigSave(ctxbg, "mox.example", nil, false) // Restore.
|
||||
|
||||
api.DomainDMARCAddressSave(ctxbg, "mox.example", "dmarc-reports", "", "mjl", "DMARC")
|
||||
tneedErrorCode(t, "user:error", func() { api.DomainDMARCAddressSave(ctxbg, "bogus.example", "dmarc-reports", "", "mjl", "DMARC") })
|
||||
|
@ -1710,8 +1710,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "localpartCatchallSeparator",
|
||||
"Name": "localpartCatchallSeparators",
|
||||
"Typewords": [
|
||||
"[]",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
@ -3441,6 +3442,14 @@
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "LocalpartCatchallSeparators",
|
||||
"Docs": "",
|
||||
"Typewords": [
|
||||
"[]",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "LocalpartCaseSensitive",
|
||||
"Docs": "",
|
||||
@ -3501,6 +3510,14 @@
|
||||
"Typewords": [
|
||||
"Domain"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "LocalpartCatchallSeparatorsEffective",
|
||||
"Docs": "Either LocalpartCatchallSeparators, the value of LocalpartCatchallSeparator, or empty.",
|
||||
"Typewords": [
|
||||
"[]",
|
||||
"string"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -270,6 +270,7 @@ export interface ConfigDomain {
|
||||
Description: string
|
||||
ClientSettingsDomain: string
|
||||
LocalpartCatchallSeparator: string
|
||||
LocalpartCatchallSeparators?: string[] | null
|
||||
LocalpartCaseSensitive: boolean
|
||||
DKIM: DKIM
|
||||
DMARC?: DMARC | null
|
||||
@ -278,6 +279,7 @@ export interface ConfigDomain {
|
||||
Routes?: Route[] | null
|
||||
Aliases?: { [key: string]: Alias }
|
||||
Domain: Domain
|
||||
LocalpartCatchallSeparatorsEffective?: string[] | null // Either LocalpartCatchallSeparators, the value of LocalpartCatchallSeparator, or empty.
|
||||
}
|
||||
|
||||
export interface DKIM {
|
||||
@ -1184,7 +1186,7 @@ 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":"Disabled","Docs":"","Typewords":["bool"]},{"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":"Aliases","Docs":"","Typewords":["{}","Alias"]},{"Name":"Domain","Docs":"","Typewords":["Domain"]}]},
|
||||
"ConfigDomain": {"Name":"ConfigDomain","Docs":"","Fields":[{"Name":"Disabled","Docs":"","Typewords":["bool"]},{"Name":"Description","Docs":"","Typewords":["string"]},{"Name":"ClientSettingsDomain","Docs":"","Typewords":["string"]},{"Name":"LocalpartCatchallSeparator","Docs":"","Typewords":["string"]},{"Name":"LocalpartCatchallSeparators","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":"Aliases","Docs":"","Typewords":["{}","Alias"]},{"Name":"Domain","Docs":"","Typewords":["Domain"]},{"Name":"LocalpartCatchallSeparatorsEffective","Docs":"","Typewords":["[]","string"]}]},
|
||||
"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"]},{"Name":"Algorithm","Docs":"","Typewords":["string"]}]},
|
||||
"Canonicalization": {"Name":"Canonicalization","Docs":"","Fields":[{"Name":"HeaderRelaxed","Docs":"","Typewords":["bool"]},{"Name":"BodyRelaxed","Docs":"","Typewords":["bool"]}]},
|
||||
@ -2194,11 +2196,11 @@ export class Client {
|
||||
|
||||
// DomainLocalpartConfigSave saves the localpart catchall and case-sensitive
|
||||
// settings for a domain.
|
||||
async DomainLocalpartConfigSave(domainName: string, localpartCatchallSeparator: string, localpartCaseSensitive: boolean): Promise<void> {
|
||||
async DomainLocalpartConfigSave(domainName: string, localpartCatchallSeparators: string[] | null, localpartCaseSensitive: boolean): Promise<void> {
|
||||
const fn: string = "DomainLocalpartConfigSave"
|
||||
const paramTypes: string[][] = [["string"],["string"],["bool"]]
|
||||
const paramTypes: string[][] = [["string"],["[]","string"],["bool"]]
|
||||
const returnTypes: string[][] = []
|
||||
const params: any[] = [domainName, localpartCatchallSeparator, localpartCaseSensitive]
|
||||
const params: any[] = [domainName, localpartCatchallSeparators, localpartCaseSensitive]
|
||||
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
|
||||
}
|
||||
|
||||
|
@ -1007,10 +1007,7 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S
|
||||
useFromID := slices.Contains(accConf.ParsedFromIDLoginAddresses, loginAddr)
|
||||
var localpartBase string
|
||||
if useFromID {
|
||||
if confDom.LocalpartCatchallSeparator == "" {
|
||||
xcheckuserf(errors.New(`localpart catchall separator must be configured for domain`), `composing unique "from" address`)
|
||||
}
|
||||
localpartBase = strings.SplitN(string(fromPath.Localpart), confDom.LocalpartCatchallSeparator, 2)[0]
|
||||
localpartBase = strings.SplitN(string(fromPath.Localpart), confDom.LocalpartCatchallSeparatorsEffective[0], 2)[0]
|
||||
}
|
||||
fromIDs := make([]string, len(recipients))
|
||||
qml := make([]queue.Msg, len(recipients))
|
||||
@ -1019,7 +1016,7 @@ func (s server) Send(ctx context.Context, req webapi.SendRequest) (resp webapi.S
|
||||
fp := fromPath
|
||||
if useFromID {
|
||||
fromIDs[i] = xrandomID(16)
|
||||
fp.Localpart = smtp.Localpart(localpartBase + confDom.LocalpartCatchallSeparator + fromIDs[i])
|
||||
fp.Localpart = smtp.Localpart(localpartBase + confDom.LocalpartCatchallSeparatorsEffective[0] + fromIDs[i])
|
||||
}
|
||||
|
||||
// Don't use per-recipient unique message prefix when multiple recipients are
|
||||
|
@ -956,7 +956,7 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
|
||||
fromPath := fromAddr.Address.Path()
|
||||
var localpartBase string
|
||||
if useFromID {
|
||||
localpartBase = strings.SplitN(string(fromPath.Localpart), confDom.LocalpartCatchallSeparator, 2)[0]
|
||||
localpartBase = strings.SplitN(string(fromPath.Localpart), confDom.LocalpartCatchallSeparatorsEffective[0], 2)[0]
|
||||
}
|
||||
qml := make([]queue.Msg, len(recipients))
|
||||
now := time.Now()
|
||||
@ -965,7 +965,7 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
|
||||
var fromID string
|
||||
if useFromID {
|
||||
fromID = xrandomID(ctx, 16)
|
||||
fp.Localpart = smtp.Localpart(localpartBase + confDom.LocalpartCatchallSeparator + fromID)
|
||||
fp.Localpart = smtp.Localpart(localpartBase + confDom.LocalpartCatchallSeparatorsEffective[0] + fromID)
|
||||
}
|
||||
|
||||
// Don't use per-recipient unique message prefix when multiple recipients are
|
||||
|
@ -2002,9 +2002,10 @@
|
||||
"Docs": "DomainAddressConfig has the address (localpart) configuration for a domain, so\nthe webmail client can decide if an address matches the addresses of the\naccount.",
|
||||
"Fields": [
|
||||
{
|
||||
"Name": "LocalpartCatchallSeparator",
|
||||
"Name": "LocalpartCatchallSeparators",
|
||||
"Docs": "Can be empty.",
|
||||
"Typewords": [
|
||||
"[]",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
|
@ -264,7 +264,7 @@ export interface EventStart {
|
||||
// the webmail client can decide if an address matches the addresses of the
|
||||
// account.
|
||||
export interface DomainAddressConfig {
|
||||
LocalpartCatchallSeparator: string // Can be empty.
|
||||
LocalpartCatchallSeparators?: string[] | null // Can be empty.
|
||||
LocalpartCaseSensitive: boolean
|
||||
}
|
||||
|
||||
@ -621,7 +621,7 @@ export const types: TypenameMap = {
|
||||
"Settings": {"Name":"Settings","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["uint8"]},{"Name":"Signature","Docs":"","Typewords":["string"]},{"Name":"Quoting","Docs":"","Typewords":["Quoting"]},{"Name":"ShowAddressSecurity","Docs":"","Typewords":["bool"]},{"Name":"ShowHTML","Docs":"","Typewords":["bool"]},{"Name":"NoShowShortcuts","Docs":"","Typewords":["bool"]},{"Name":"ShowHeaders","Docs":"","Typewords":["[]","string"]}]},
|
||||
"Ruleset": {"Name":"Ruleset","Docs":"","Fields":[{"Name":"SMTPMailFromRegexp","Docs":"","Typewords":["string"]},{"Name":"MsgFromRegexp","Docs":"","Typewords":["string"]},{"Name":"VerifiedDomain","Docs":"","Typewords":["string"]},{"Name":"HeadersRegexp","Docs":"","Typewords":["{}","string"]},{"Name":"IsForward","Docs":"","Typewords":["bool"]},{"Name":"ListAllowDomain","Docs":"","Typewords":["string"]},{"Name":"AcceptRejectsToMailbox","Docs":"","Typewords":["string"]},{"Name":"Mailbox","Docs":"","Typewords":["string"]},{"Name":"Comment","Docs":"","Typewords":["string"]},{"Name":"VerifiedDNSDomain","Docs":"","Typewords":["Domain"]},{"Name":"ListAllowDNSDomain","Docs":"","Typewords":["Domain"]}]},
|
||||
"EventStart": {"Name":"EventStart","Docs":"","Fields":[{"Name":"SSEID","Docs":"","Typewords":["int64"]},{"Name":"LoginAddress","Docs":"","Typewords":["MessageAddress"]},{"Name":"Addresses","Docs":"","Typewords":["[]","MessageAddress"]},{"Name":"DomainAddressConfigs","Docs":"","Typewords":["{}","DomainAddressConfig"]},{"Name":"MailboxName","Docs":"","Typewords":["string"]},{"Name":"Mailboxes","Docs":"","Typewords":["[]","Mailbox"]},{"Name":"RejectsMailbox","Docs":"","Typewords":["string"]},{"Name":"Settings","Docs":"","Typewords":["Settings"]},{"Name":"AccountPath","Docs":"","Typewords":["string"]},{"Name":"Version","Docs":"","Typewords":["string"]}]},
|
||||
"DomainAddressConfig": {"Name":"DomainAddressConfig","Docs":"","Fields":[{"Name":"LocalpartCatchallSeparator","Docs":"","Typewords":["string"]},{"Name":"LocalpartCaseSensitive","Docs":"","Typewords":["bool"]}]},
|
||||
"DomainAddressConfig": {"Name":"DomainAddressConfig","Docs":"","Fields":[{"Name":"LocalpartCatchallSeparators","Docs":"","Typewords":["[]","string"]},{"Name":"LocalpartCaseSensitive","Docs":"","Typewords":["bool"]}]},
|
||||
"EventViewErr": {"Name":"EventViewErr","Docs":"","Fields":[{"Name":"ViewID","Docs":"","Typewords":["int64"]},{"Name":"RequestID","Docs":"","Typewords":["int64"]},{"Name":"Err","Docs":"","Typewords":["string"]}]},
|
||||
"EventViewReset": {"Name":"EventViewReset","Docs":"","Fields":[{"Name":"ViewID","Docs":"","Typewords":["int64"]},{"Name":"RequestID","Docs":"","Typewords":["int64"]}]},
|
||||
"EventViewMsgs": {"Name":"EventViewMsgs","Docs":"","Fields":[{"Name":"ViewID","Docs":"","Typewords":["int64"]},{"Name":"RequestID","Docs":"","Typewords":["int64"]},{"Name":"MessageItems","Docs":"","Typewords":["[]","[]","MessageItem"]},{"Name":"ParsedMessage","Docs":"","Typewords":["nullable","ParsedMessage"]},{"Name":"ViewEnd","Docs":"","Typewords":["bool"]}]},
|
||||
|
@ -355,6 +355,18 @@ func TestAPI(t *testing.T) {
|
||||
})
|
||||
// todo: check delivery of 6 messages to inbox, 1 to sent
|
||||
|
||||
api.MessageSubmit(ctx, SubmitMessage{
|
||||
From: "mjl-altcatchall@mox.example",
|
||||
To: []string{"mjl-to@mox.example", "mjl to2 <mjl+to2@mox.example>"},
|
||||
Cc: []string{"mjl-cc@mox.example", "mjl cc2 <mjl+cc2@mox.example>"},
|
||||
Bcc: []string{"mjl-bcc@mox.example", "mjl bcc2 <mjl+bcc2@mox.example>"},
|
||||
Subject: "test email",
|
||||
TextBody: "this is the content\n\ncheers,\nmox",
|
||||
ReplyTo: "mjl replyto <mjl-replyto@mox.example>",
|
||||
UserAgent: "moxwebmail/dev",
|
||||
})
|
||||
// todo: check delivery of 6 messages to inbox, 1 to sent
|
||||
|
||||
// Reply with attachments.
|
||||
api.MessageSubmit(ctx, SubmitMessage{
|
||||
From: "mjl@mox.example",
|
||||
|
@ -314,7 +314,7 @@ var api;
|
||||
"Settings": { "Name": "Settings", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["uint8"] }, { "Name": "Signature", "Docs": "", "Typewords": ["string"] }, { "Name": "Quoting", "Docs": "", "Typewords": ["Quoting"] }, { "Name": "ShowAddressSecurity", "Docs": "", "Typewords": ["bool"] }, { "Name": "ShowHTML", "Docs": "", "Typewords": ["bool"] }, { "Name": "NoShowShortcuts", "Docs": "", "Typewords": ["bool"] }, { "Name": "ShowHeaders", "Docs": "", "Typewords": ["[]", "string"] }] },
|
||||
"Ruleset": { "Name": "Ruleset", "Docs": "", "Fields": [{ "Name": "SMTPMailFromRegexp", "Docs": "", "Typewords": ["string"] }, { "Name": "MsgFromRegexp", "Docs": "", "Typewords": ["string"] }, { "Name": "VerifiedDomain", "Docs": "", "Typewords": ["string"] }, { "Name": "HeadersRegexp", "Docs": "", "Typewords": ["{}", "string"] }, { "Name": "IsForward", "Docs": "", "Typewords": ["bool"] }, { "Name": "ListAllowDomain", "Docs": "", "Typewords": ["string"] }, { "Name": "AcceptRejectsToMailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "Mailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "Comment", "Docs": "", "Typewords": ["string"] }, { "Name": "VerifiedDNSDomain", "Docs": "", "Typewords": ["Domain"] }, { "Name": "ListAllowDNSDomain", "Docs": "", "Typewords": ["Domain"] }] },
|
||||
"EventStart": { "Name": "EventStart", "Docs": "", "Fields": [{ "Name": "SSEID", "Docs": "", "Typewords": ["int64"] }, { "Name": "LoginAddress", "Docs": "", "Typewords": ["MessageAddress"] }, { "Name": "Addresses", "Docs": "", "Typewords": ["[]", "MessageAddress"] }, { "Name": "DomainAddressConfigs", "Docs": "", "Typewords": ["{}", "DomainAddressConfig"] }, { "Name": "MailboxName", "Docs": "", "Typewords": ["string"] }, { "Name": "Mailboxes", "Docs": "", "Typewords": ["[]", "Mailbox"] }, { "Name": "RejectsMailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "Settings", "Docs": "", "Typewords": ["Settings"] }, { "Name": "AccountPath", "Docs": "", "Typewords": ["string"] }, { "Name": "Version", "Docs": "", "Typewords": ["string"] }] },
|
||||
"DomainAddressConfig": { "Name": "DomainAddressConfig", "Docs": "", "Fields": [{ "Name": "LocalpartCatchallSeparator", "Docs": "", "Typewords": ["string"] }, { "Name": "LocalpartCaseSensitive", "Docs": "", "Typewords": ["bool"] }] },
|
||||
"DomainAddressConfig": { "Name": "DomainAddressConfig", "Docs": "", "Fields": [{ "Name": "LocalpartCatchallSeparators", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "LocalpartCaseSensitive", "Docs": "", "Typewords": ["bool"] }] },
|
||||
"EventViewErr": { "Name": "EventViewErr", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Err", "Docs": "", "Typewords": ["string"] }] },
|
||||
"EventViewReset": { "Name": "EventViewReset", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }] },
|
||||
"EventViewMsgs": { "Name": "EventViewMsgs", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }, { "Name": "MessageItems", "Docs": "", "Typewords": ["[]", "[]", "MessageItem"] }, { "Name": "ParsedMessage", "Docs": "", "Typewords": ["nullable", "ParsedMessage"] }, { "Name": "ViewEnd", "Docs": "", "Typewords": ["bool"] }] },
|
||||
|
@ -314,7 +314,7 @@ var api;
|
||||
"Settings": { "Name": "Settings", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["uint8"] }, { "Name": "Signature", "Docs": "", "Typewords": ["string"] }, { "Name": "Quoting", "Docs": "", "Typewords": ["Quoting"] }, { "Name": "ShowAddressSecurity", "Docs": "", "Typewords": ["bool"] }, { "Name": "ShowHTML", "Docs": "", "Typewords": ["bool"] }, { "Name": "NoShowShortcuts", "Docs": "", "Typewords": ["bool"] }, { "Name": "ShowHeaders", "Docs": "", "Typewords": ["[]", "string"] }] },
|
||||
"Ruleset": { "Name": "Ruleset", "Docs": "", "Fields": [{ "Name": "SMTPMailFromRegexp", "Docs": "", "Typewords": ["string"] }, { "Name": "MsgFromRegexp", "Docs": "", "Typewords": ["string"] }, { "Name": "VerifiedDomain", "Docs": "", "Typewords": ["string"] }, { "Name": "HeadersRegexp", "Docs": "", "Typewords": ["{}", "string"] }, { "Name": "IsForward", "Docs": "", "Typewords": ["bool"] }, { "Name": "ListAllowDomain", "Docs": "", "Typewords": ["string"] }, { "Name": "AcceptRejectsToMailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "Mailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "Comment", "Docs": "", "Typewords": ["string"] }, { "Name": "VerifiedDNSDomain", "Docs": "", "Typewords": ["Domain"] }, { "Name": "ListAllowDNSDomain", "Docs": "", "Typewords": ["Domain"] }] },
|
||||
"EventStart": { "Name": "EventStart", "Docs": "", "Fields": [{ "Name": "SSEID", "Docs": "", "Typewords": ["int64"] }, { "Name": "LoginAddress", "Docs": "", "Typewords": ["MessageAddress"] }, { "Name": "Addresses", "Docs": "", "Typewords": ["[]", "MessageAddress"] }, { "Name": "DomainAddressConfigs", "Docs": "", "Typewords": ["{}", "DomainAddressConfig"] }, { "Name": "MailboxName", "Docs": "", "Typewords": ["string"] }, { "Name": "Mailboxes", "Docs": "", "Typewords": ["[]", "Mailbox"] }, { "Name": "RejectsMailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "Settings", "Docs": "", "Typewords": ["Settings"] }, { "Name": "AccountPath", "Docs": "", "Typewords": ["string"] }, { "Name": "Version", "Docs": "", "Typewords": ["string"] }] },
|
||||
"DomainAddressConfig": { "Name": "DomainAddressConfig", "Docs": "", "Fields": [{ "Name": "LocalpartCatchallSeparator", "Docs": "", "Typewords": ["string"] }, { "Name": "LocalpartCaseSensitive", "Docs": "", "Typewords": ["bool"] }] },
|
||||
"DomainAddressConfig": { "Name": "DomainAddressConfig", "Docs": "", "Fields": [{ "Name": "LocalpartCatchallSeparators", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "LocalpartCaseSensitive", "Docs": "", "Typewords": ["bool"] }] },
|
||||
"EventViewErr": { "Name": "EventViewErr", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Err", "Docs": "", "Typewords": ["string"] }] },
|
||||
"EventViewReset": { "Name": "EventViewReset", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }] },
|
||||
"EventViewMsgs": { "Name": "EventViewMsgs", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }, { "Name": "MessageItems", "Docs": "", "Typewords": ["[]", "[]", "MessageItem"] }, { "Name": "ParsedMessage", "Docs": "", "Typewords": ["nullable", "ParsedMessage"] }, { "Name": "ViewEnd", "Docs": "", "Typewords": ["bool"] }] },
|
||||
|
@ -228,8 +228,8 @@ type EventStart struct {
|
||||
// the webmail client can decide if an address matches the addresses of the
|
||||
// account.
|
||||
type DomainAddressConfig struct {
|
||||
LocalpartCatchallSeparator string // Can be empty.
|
||||
LocalpartCaseSensitive bool
|
||||
LocalpartCatchallSeparators []string // Can be empty.
|
||||
LocalpartCaseSensitive bool
|
||||
}
|
||||
|
||||
// EventViewMsgs contains messages for a view, possibly a continuation of an
|
||||
@ -764,7 +764,7 @@ func serveEvents(ctx context.Context, log mlog.Log, accountPath string, w http.R
|
||||
domainAddressConfigs := map[string]DomainAddressConfig{}
|
||||
for _, a := range addresses {
|
||||
dom, _ := mox.Conf.Domain(a.Domain)
|
||||
domainAddressConfigs[a.Domain.ASCII] = DomainAddressConfig{dom.LocalpartCatchallSeparator, dom.LocalpartCaseSensitive}
|
||||
domainAddressConfigs[a.Domain.ASCII] = DomainAddressConfig{dom.LocalpartCatchallSeparatorsEffective, dom.LocalpartCaseSensitive}
|
||||
}
|
||||
|
||||
// Write first event, allowing client to fill its UI with mailboxes.
|
||||
|
@ -314,7 +314,7 @@ var api;
|
||||
"Settings": { "Name": "Settings", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["uint8"] }, { "Name": "Signature", "Docs": "", "Typewords": ["string"] }, { "Name": "Quoting", "Docs": "", "Typewords": ["Quoting"] }, { "Name": "ShowAddressSecurity", "Docs": "", "Typewords": ["bool"] }, { "Name": "ShowHTML", "Docs": "", "Typewords": ["bool"] }, { "Name": "NoShowShortcuts", "Docs": "", "Typewords": ["bool"] }, { "Name": "ShowHeaders", "Docs": "", "Typewords": ["[]", "string"] }] },
|
||||
"Ruleset": { "Name": "Ruleset", "Docs": "", "Fields": [{ "Name": "SMTPMailFromRegexp", "Docs": "", "Typewords": ["string"] }, { "Name": "MsgFromRegexp", "Docs": "", "Typewords": ["string"] }, { "Name": "VerifiedDomain", "Docs": "", "Typewords": ["string"] }, { "Name": "HeadersRegexp", "Docs": "", "Typewords": ["{}", "string"] }, { "Name": "IsForward", "Docs": "", "Typewords": ["bool"] }, { "Name": "ListAllowDomain", "Docs": "", "Typewords": ["string"] }, { "Name": "AcceptRejectsToMailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "Mailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "Comment", "Docs": "", "Typewords": ["string"] }, { "Name": "VerifiedDNSDomain", "Docs": "", "Typewords": ["Domain"] }, { "Name": "ListAllowDNSDomain", "Docs": "", "Typewords": ["Domain"] }] },
|
||||
"EventStart": { "Name": "EventStart", "Docs": "", "Fields": [{ "Name": "SSEID", "Docs": "", "Typewords": ["int64"] }, { "Name": "LoginAddress", "Docs": "", "Typewords": ["MessageAddress"] }, { "Name": "Addresses", "Docs": "", "Typewords": ["[]", "MessageAddress"] }, { "Name": "DomainAddressConfigs", "Docs": "", "Typewords": ["{}", "DomainAddressConfig"] }, { "Name": "MailboxName", "Docs": "", "Typewords": ["string"] }, { "Name": "Mailboxes", "Docs": "", "Typewords": ["[]", "Mailbox"] }, { "Name": "RejectsMailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "Settings", "Docs": "", "Typewords": ["Settings"] }, { "Name": "AccountPath", "Docs": "", "Typewords": ["string"] }, { "Name": "Version", "Docs": "", "Typewords": ["string"] }] },
|
||||
"DomainAddressConfig": { "Name": "DomainAddressConfig", "Docs": "", "Fields": [{ "Name": "LocalpartCatchallSeparator", "Docs": "", "Typewords": ["string"] }, { "Name": "LocalpartCaseSensitive", "Docs": "", "Typewords": ["bool"] }] },
|
||||
"DomainAddressConfig": { "Name": "DomainAddressConfig", "Docs": "", "Fields": [{ "Name": "LocalpartCatchallSeparators", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "LocalpartCaseSensitive", "Docs": "", "Typewords": ["bool"] }] },
|
||||
"EventViewErr": { "Name": "EventViewErr", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Err", "Docs": "", "Typewords": ["string"] }] },
|
||||
"EventViewReset": { "Name": "EventViewReset", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }] },
|
||||
"EventViewMsgs": { "Name": "EventViewMsgs", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }, { "Name": "MessageItems", "Docs": "", "Typewords": ["[]", "[]", "MessageItem"] }, { "Name": "ParsedMessage", "Docs": "", "Typewords": ["nullable", "ParsedMessage"] }, { "Name": "ViewEnd", "Docs": "", "Typewords": ["bool"] }] },
|
||||
@ -2991,9 +2991,8 @@ const compose = (opts, listMailboxes) => {
|
||||
const normalizeUser = (a) => {
|
||||
let user = a.User;
|
||||
const domconf = domainAddressConfigs[a.Domain.ASCII];
|
||||
const localpartCatchallSeparator = domconf.LocalpartCatchallSeparator;
|
||||
if (localpartCatchallSeparator) {
|
||||
user = user.split(localpartCatchallSeparator)[0];
|
||||
for (const sep of (domconf.LocalpartCatchallSeparators || [])) {
|
||||
user = user.split(sep)[0];
|
||||
}
|
||||
const localpartCaseSensitive = domconf.LocalpartCaseSensitive;
|
||||
if (!localpartCaseSensitive) {
|
||||
|
@ -1887,9 +1887,8 @@ const compose = (opts: ComposeOptions, listMailboxes: listMailboxes) => {
|
||||
const normalizeUser = (a: api.MessageAddress) => {
|
||||
let user = a.User
|
||||
const domconf = domainAddressConfigs[a.Domain.ASCII]
|
||||
const localpartCatchallSeparator = domconf.LocalpartCatchallSeparator
|
||||
if (localpartCatchallSeparator) {
|
||||
user = user.split(localpartCatchallSeparator)[0]
|
||||
for (const sep of (domconf.LocalpartCatchallSeparators || [])) {
|
||||
user = user.split(sep)[0]
|
||||
}
|
||||
const localpartCaseSensitive = domconf.LocalpartCaseSensitive
|
||||
if (!localpartCaseSensitive) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user