1package admin
2
3import (
4 "fmt"
5 "maps"
6 "slices"
7
8 "github.com/mjl-/mox/config"
9 "github.com/mjl-/mox/dns"
10 "github.com/mjl-/mox/mox-"
11)
12
13type TLSMode uint8
14
15const (
16 TLSModeImmediate TLSMode = 0
17 TLSModeSTARTTLS TLSMode = 1
18 TLSModeNone TLSMode = 2
19)
20
21type ProtocolConfig struct {
22 Host dns.Domain
23 Port int
24 TLSMode TLSMode
25 EnabledOnHTTPS bool
26}
27
28type ClientConfig struct {
29 IMAP ProtocolConfig
30 Submission ProtocolConfig
31}
32
33// ClientConfigDomain returns a single IMAP and Submission client configuration for
34// a domain.
35func ClientConfigDomain(d dns.Domain) (rconfig ClientConfig, rerr error) {
36 var haveIMAP, haveSubmission bool
37
38 domConf, ok := mox.Conf.Domain(d)
39 if !ok {
40 return ClientConfig{}, fmt.Errorf("%w: unknown domain", ErrRequest)
41 }
42
43 gather := func(l config.Listener) (done bool) {
44 host := mox.Conf.Static.HostnameDomain
45 if l.Hostname != "" {
46 host = l.HostnameDomain
47 }
48 if domConf.ClientSettingsDomain != "" {
49 host = domConf.ClientSettingsDNSDomain
50 }
51 if !haveIMAP && l.IMAPS.Enabled {
52 rconfig.IMAP.Host = host
53 rconfig.IMAP.Port = config.Port(l.IMAPS.Port, 993)
54 rconfig.IMAP.TLSMode = TLSModeImmediate
55 rconfig.IMAP.EnabledOnHTTPS = l.IMAPS.EnabledOnHTTPS
56 haveIMAP = true
57 }
58 if !haveIMAP && l.IMAP.Enabled {
59 rconfig.IMAP.Host = host
60 rconfig.IMAP.Port = config.Port(l.IMAP.Port, 143)
61 rconfig.IMAP.TLSMode = TLSModeSTARTTLS
62 if l.TLS == nil {
63 rconfig.IMAP.TLSMode = TLSModeNone
64 }
65 haveIMAP = true
66 }
67 if !haveSubmission && l.Submissions.Enabled {
68 rconfig.Submission.Host = host
69 rconfig.Submission.Port = config.Port(l.Submissions.Port, 465)
70 rconfig.Submission.TLSMode = TLSModeImmediate
71 rconfig.Submission.EnabledOnHTTPS = l.Submissions.EnabledOnHTTPS
72 haveSubmission = true
73 }
74 if !haveSubmission && l.Submission.Enabled {
75 rconfig.Submission.Host = host
76 rconfig.Submission.Port = config.Port(l.Submission.Port, 587)
77 rconfig.Submission.TLSMode = TLSModeSTARTTLS
78 if l.TLS == nil {
79 rconfig.Submission.TLSMode = TLSModeNone
80 }
81 haveSubmission = true
82 }
83 return haveIMAP && haveSubmission
84 }
85
86 // Look at the public listener first. Most likely the intended configuration.
87 if public, ok := mox.Conf.Static.Listeners["public"]; ok {
88 if gather(public) {
89 return
90 }
91 }
92 // Go through the other listeners in consistent order.
93 names := slices.Sorted(maps.Keys(mox.Conf.Static.Listeners))
94 for _, name := range names {
95 if gather(mox.Conf.Static.Listeners[name]) {
96 return
97 }
98 }
99 return ClientConfig{}, fmt.Errorf("%w: no listeners found for imap and/or submission", ErrRequest)
100}
101
102// ClientConfigs holds the client configuration for IMAP/Submission for a
103// domain.
104type ClientConfigs struct {
105 Entries []ClientConfigsEntry
106}
107
108type ClientConfigsEntry struct {
109 Protocol string
110 Host dns.Domain
111 Port int
112 Listener string
113 Note string
114}
115
116// ClientConfigsDomain returns the client configs for IMAP/Submission for a
117// domain.
118func ClientConfigsDomain(d dns.Domain) (ClientConfigs, error) {
119 domConf, ok := mox.Conf.Domain(d)
120 if !ok {
121 return ClientConfigs{}, fmt.Errorf("%w: unknown domain", ErrRequest)
122 }
123
124 c := ClientConfigs{}
125 c.Entries = []ClientConfigsEntry{}
126 var listeners []string
127
128 for name := range mox.Conf.Static.Listeners {
129 listeners = append(listeners, name)
130 }
131 slices.Sort(listeners)
132
133 note := func(tls bool, requiretls bool) string {
134 if !tls {
135 return "plain text, no STARTTLS configured"
136 }
137 if requiretls {
138 return "STARTTLS required"
139 }
140 return "STARTTLS optional"
141 }
142
143 for _, name := range listeners {
144 l := mox.Conf.Static.Listeners[name]
145 host := mox.Conf.Static.HostnameDomain
146 if l.Hostname != "" {
147 host = l.HostnameDomain
148 }
149 if domConf.ClientSettingsDomain != "" {
150 host = domConf.ClientSettingsDNSDomain
151 }
152 if l.Submissions.Enabled {
153 note := "with TLS"
154 if l.Submissions.EnabledOnHTTPS {
155 note += "; also served on port 443 with TLS ALPN \"smtp\""
156 }
157 c.Entries = append(c.Entries, ClientConfigsEntry{"Submission (SMTP)", host, config.Port(l.Submissions.Port, 465), name, note})
158 }
159 if l.IMAPS.Enabled {
160 note := "with TLS"
161 if l.IMAPS.EnabledOnHTTPS {
162 note += "; also served on port 443 with TLS ALPN \"imap\""
163 }
164 c.Entries = append(c.Entries, ClientConfigsEntry{"IMAP", host, config.Port(l.IMAPS.Port, 993), name, note})
165 }
166 if l.Submission.Enabled {
167 c.Entries = append(c.Entries, ClientConfigsEntry{"Submission (SMTP)", host, config.Port(l.Submission.Port, 587), name, note(l.TLS != nil, !l.Submission.NoRequireSTARTTLS)})
168 }
169 if l.IMAP.Enabled {
170 c.Entries = append(c.Entries, ClientConfigsEntry{"IMAP", host, config.Port(l.IMAPS.Port, 143), name, note(l.TLS != nil, !l.IMAP.NoRequireSTARTTLS)})
171 }
172 }
173
174 return c, nil
175}
176