2023-01-30 16:27:06 +03:00
|
|
|
package mox
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/mjl-/mox/config"
|
|
|
|
"github.com/mjl-/mox/dns"
|
|
|
|
"github.com/mjl-/mox/smtp"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrDomainNotFound = errors.New("domain not found")
|
2024-04-24 20:15:30 +03:00
|
|
|
ErrAddressNotFound = errors.New("address not found")
|
2023-01-30 16:27:06 +03:00
|
|
|
)
|
|
|
|
|
2023-03-29 11:55:05 +03:00
|
|
|
// FindAccount looks up the account for localpart and domain.
|
2023-01-30 16:27:06 +03:00
|
|
|
//
|
2024-04-24 20:15:30 +03:00
|
|
|
// Can return ErrDomainNotFound and ErrAddressNotFound.
|
|
|
|
func LookupAddress(localpart smtp.Localpart, domain dns.Domain, allowPostmaster, allowAlias bool) (accountName string, alias *config.Alias, canonicalAddress string, dest config.Destination, rerr error) {
|
2023-01-30 16:27:06 +03:00
|
|
|
if strings.EqualFold(string(localpart), "postmaster") {
|
|
|
|
localpart = "postmaster"
|
|
|
|
}
|
2023-04-24 13:04:46 +03:00
|
|
|
|
|
|
|
postmasterDomain := func() bool {
|
|
|
|
var zerodomain dns.Domain
|
|
|
|
if domain == zerodomain || domain == Conf.Static.HostnameDomain {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
for _, l := range Conf.Static.Listeners {
|
|
|
|
if l.SMTP.Enabled && domain == l.HostnameDomain {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-11-12 13:37:15 +03:00
|
|
|
// Check for special mail host addresses.
|
2023-04-24 13:04:46 +03:00
|
|
|
if localpart == "postmaster" && postmasterDomain() {
|
2023-01-30 16:27:06 +03:00
|
|
|
if !allowPostmaster {
|
2024-04-24 20:15:30 +03:00
|
|
|
return "", nil, "", config.Destination{}, ErrAddressNotFound
|
2023-01-30 16:27:06 +03:00
|
|
|
}
|
2024-04-24 20:15:30 +03:00
|
|
|
return Conf.Static.Postmaster.Account, nil, "postmaster", config.Destination{Mailbox: Conf.Static.Postmaster.Mailbox}, nil
|
2023-01-30 16:27:06 +03:00
|
|
|
}
|
2023-11-12 13:37:15 +03:00
|
|
|
if localpart == Conf.Static.HostTLSRPT.ParsedLocalpart && domain == Conf.Static.HostnameDomain {
|
|
|
|
// Get destination, should always be present.
|
|
|
|
canonical := smtp.NewAddress(localpart, domain).String()
|
2024-04-24 20:15:30 +03:00
|
|
|
accAddr, a, ok := Conf.AccountDestination(canonical)
|
|
|
|
if !ok || a != nil {
|
|
|
|
return "", nil, "", config.Destination{}, ErrAddressNotFound
|
2023-11-12 13:37:15 +03:00
|
|
|
}
|
2024-04-24 20:15:30 +03:00
|
|
|
return accAddr.Account, nil, canonical, accAddr.Destination, nil
|
2023-11-12 13:37:15 +03:00
|
|
|
}
|
2023-01-30 16:27:06 +03:00
|
|
|
|
|
|
|
d, ok := Conf.Domain(domain)
|
2024-01-26 21:51:23 +03:00
|
|
|
if !ok || d.ReportsOnly {
|
|
|
|
// For ReportsOnly, we also return ErrDomainNotFound, so this domain isn't
|
|
|
|
// considered local/authoritative during delivery.
|
2024-04-24 20:15:30 +03:00
|
|
|
return "", nil, "", config.Destination{}, ErrDomainNotFound
|
2023-01-30 16:27:06 +03:00
|
|
|
}
|
|
|
|
|
2024-04-24 20:15:30 +03:00
|
|
|
localpart = CanonicalLocalpart(localpart, d)
|
2023-01-30 16:27:06 +03:00
|
|
|
canonical := smtp.NewAddress(localpart, domain).String()
|
|
|
|
|
2024-04-24 20:15:30 +03:00
|
|
|
accAddr, alias, ok := Conf.AccountDestination(canonical)
|
2024-11-11 01:13:38 +03:00
|
|
|
if ok && alias != nil {
|
|
|
|
if !allowAlias {
|
|
|
|
return "", nil, "", config.Destination{}, ErrAddressNotFound
|
|
|
|
}
|
2024-04-24 20:15:30 +03:00
|
|
|
return "", alias, canonical, config.Destination{}, nil
|
|
|
|
} else if !ok {
|
|
|
|
if accAddr, alias, ok = Conf.AccountDestination("@" + domain.Name()); !ok || alias != nil {
|
2023-04-24 13:04:46 +03:00
|
|
|
if localpart == "postmaster" && allowPostmaster {
|
2024-04-24 20:15:30 +03:00
|
|
|
return Conf.Static.Postmaster.Account, nil, "postmaster", config.Destination{Mailbox: Conf.Static.Postmaster.Mailbox}, nil
|
2023-04-24 13:04:46 +03:00
|
|
|
}
|
2024-04-24 20:15:30 +03:00
|
|
|
return "", nil, "", config.Destination{}, ErrAddressNotFound
|
2023-03-29 22:11:43 +03:00
|
|
|
}
|
|
|
|
canonical = "@" + domain.Name()
|
2023-01-30 16:27:06 +03:00
|
|
|
}
|
2024-04-24 20:15:30 +03:00
|
|
|
return accAddr.Account, nil, canonical, accAddr.Destination, nil
|
2023-01-30 16:27:06 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// CanonicalLocalpart returns the canonical localpart, removing optional catchall
|
|
|
|
// separator, and optionally lower-casing the string.
|
2024-04-24 20:15:30 +03:00
|
|
|
func CanonicalLocalpart(localpart smtp.Localpart, d config.Domain) smtp.Localpart {
|
2023-01-30 16:27:06 +03:00
|
|
|
if d.LocalpartCatchallSeparator != "" {
|
|
|
|
t := strings.SplitN(string(localpart), d.LocalpartCatchallSeparator, 2)
|
|
|
|
localpart = smtp.Localpart(t[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
if !d.LocalpartCaseSensitive {
|
|
|
|
localpart = smtp.Localpart(strings.ToLower(string(localpart)))
|
|
|
|
}
|
2024-04-24 20:15:30 +03:00
|
|
|
return localpart
|
|
|
|
}
|
|
|
|
|
|
|
|
// AllowMsgFrom returns whether account is allowed to submit messages with address
|
|
|
|
// as message From header, based on configured addresses and membership of aliases
|
|
|
|
// that allow using its address.
|
|
|
|
func AllowMsgFrom(accountName string, msgFrom smtp.Address) bool {
|
|
|
|
accName, alias, _, _, err := LookupAddress(msgFrom.Localpart, msgFrom.Domain, false, true)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if alias != nil && alias.AllowMsgFrom {
|
|
|
|
for _, aa := range alias.ParsedAddresses {
|
|
|
|
if aa.AccountName == accountName {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return accName == accountName
|
2023-01-30 16:27:06 +03:00
|
|
|
}
|