8 "github.com/mjl-/mox/config"
9 "github.com/mjl-/mox/dns"
10 "github.com/mjl-/mox/smtp"
14 ErrDomainNotFound = errors.New("domain not found")
15 ErrAccountNotFound = errors.New("account not found")
18// FindAccount looks up the account for localpart and domain.
20// Can return ErrDomainNotFound and ErrAccountNotFound.
21func FindAccount(localpart smtp.Localpart, domain dns.Domain, allowPostmaster bool) (accountName string, canonicalAddress string, dest config.Destination, rerr error) {
22 if strings.EqualFold(string(localpart), "postmaster") {
23 localpart = "postmaster"
26 postmasterDomain := func() bool {
27 var zerodomain dns.Domain
28 if domain == zerodomain || domain == Conf.Static.HostnameDomain {
31 for _, l := range Conf.Static.Listeners {
32 if l.SMTP.Enabled && domain == l.HostnameDomain {
39 // Check for special mail host addresses.
40 if localpart == "postmaster" && postmasterDomain() {
42 return "", "", config.Destination{}, ErrAccountNotFound
44 return Conf.Static.Postmaster.Account, "postmaster", config.Destination{Mailbox: Conf.Static.Postmaster.Mailbox}, nil
46 if localpart == Conf.Static.HostTLSRPT.ParsedLocalpart && domain == Conf.Static.HostnameDomain {
47 // Get destination, should always be present.
48 canonical := smtp.NewAddress(localpart, domain).String()
49 accAddr, ok := Conf.AccountDestination(canonical)
51 return "", "", config.Destination{}, ErrAccountNotFound
53 return accAddr.Account, canonical, accAddr.Destination, nil
56 d, ok := Conf.Domain(domain)
57 if !ok || d.ReportsOnly {
58 // For ReportsOnly, we also return ErrDomainNotFound, so this domain isn't
59 // considered local/authoritative during delivery.
60 return "", "", config.Destination{}, ErrDomainNotFound
63 localpart, err := CanonicalLocalpart(localpart, d)
65 return "", "", config.Destination{}, fmt.Errorf("%w: %s", ErrAccountNotFound, err)
67 canonical := smtp.NewAddress(localpart, domain).String()
69 accAddr, ok := Conf.AccountDestination(canonical)
71 if accAddr, ok = Conf.AccountDestination("@" + domain.Name()); !ok {
72 if localpart == "postmaster" && allowPostmaster {
73 return Conf.Static.Postmaster.Account, "postmaster", config.Destination{Mailbox: Conf.Static.Postmaster.Mailbox}, nil
75 return "", "", config.Destination{}, ErrAccountNotFound
77 canonical = "@" + domain.Name()
79 return accAddr.Account, canonical, accAddr.Destination, nil
82// CanonicalLocalpart returns the canonical localpart, removing optional catchall
83// separator, and optionally lower-casing the string.
84func CanonicalLocalpart(localpart smtp.Localpart, d config.Domain) (smtp.Localpart, error) {
85 if d.LocalpartCatchallSeparator != "" {
86 t := strings.SplitN(string(localpart), d.LocalpartCatchallSeparator, 2)
87 localpart = smtp.Localpart(t[0])
90 if !d.LocalpartCaseSensitive {
91 localpart = smtp.Localpart(strings.ToLower(string(localpart)))