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