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}
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 haveIMAP = true
56 }
57 if !haveIMAP && l.IMAP.Enabled {
58 rconfig.IMAP.Host = host
59 rconfig.IMAP.Port = config.Port(l.IMAP.Port, 143)
60 rconfig.IMAP.TLSMode = TLSModeSTARTTLS
61 if l.TLS == nil {
62 rconfig.IMAP.TLSMode = TLSModeNone
63 }
64 haveIMAP = true
65 }
66 if !haveSubmission && l.Submissions.Enabled {
67 rconfig.Submission.Host = host
68 rconfig.Submission.Port = config.Port(l.Submissions.Port, 465)
69 rconfig.Submission.TLSMode = TLSModeImmediate
70 haveSubmission = true
71 }
72 if !haveSubmission && l.Submission.Enabled {
73 rconfig.Submission.Host = host
74 rconfig.Submission.Port = config.Port(l.Submission.Port, 587)
75 rconfig.Submission.TLSMode = TLSModeSTARTTLS
76 if l.TLS == nil {
77 rconfig.Submission.TLSMode = TLSModeNone
78 }
79 haveSubmission = true
80 }
81 return haveIMAP && haveSubmission
82 }
83
84 // Look at the public listener first. Most likely the intended configuration.
85 if public, ok := mox.Conf.Static.Listeners["public"]; ok {
86 if gather(public) {
87 return
88 }
89 }
90 // Go through the other listeners in consistent order.
91 names := maps.Keys(mox.Conf.Static.Listeners)
92 sort.Strings(names)
93 for _, name := range names {
94 if gather(mox.Conf.Static.Listeners[name]) {
95 return
96 }
97 }
98 return ClientConfig{}, fmt.Errorf("%w: no listeners found for imap and/or submission", ErrRequest)
99}
100
101// ClientConfigs holds the client configuration for IMAP/Submission for a
102// domain.
103type ClientConfigs struct {
104 Entries []ClientConfigsEntry
105}
106
107type ClientConfigsEntry struct {
108 Protocol string
109 Host dns.Domain
110 Port int
111 Listener string
112 Note string
113}
114
115// ClientConfigsDomain returns the client configs for IMAP/Submission for a
116// domain.
117func ClientConfigsDomain(d dns.Domain) (ClientConfigs, error) {
118 domConf, ok := mox.Conf.Domain(d)
119 if !ok {
120 return ClientConfigs{}, fmt.Errorf("%w: unknown domain", ErrRequest)
121 }
122
123 c := ClientConfigs{}
124 c.Entries = []ClientConfigsEntry{}
125 var listeners []string
126
127 for name := range mox.Conf.Static.Listeners {
128 listeners = append(listeners, name)
129 }
130 sort.Slice(listeners, func(i, j int) bool {
131 return listeners[i] < listeners[j]
132 })
133
134 note := func(tls bool, requiretls bool) string {
135 if !tls {
136 return "plain text, no STARTTLS configured"
137 }
138 if requiretls {
139 return "STARTTLS required"
140 }
141 return "STARTTLS optional"
142 }
143
144 for _, name := range listeners {
145 l := mox.Conf.Static.Listeners[name]
146 host := mox.Conf.Static.HostnameDomain
147 if l.Hostname != "" {
148 host = l.HostnameDomain
149 }
150 if domConf.ClientSettingsDomain != "" {
151 host = domConf.ClientSettingsDNSDomain
152 }
153 if l.Submissions.Enabled {
154 c.Entries = append(c.Entries, ClientConfigsEntry{"Submission (SMTP)", host, config.Port(l.Submissions.Port, 465), name, "with TLS"})
155 }
156 if l.IMAPS.Enabled {
157 c.Entries = append(c.Entries, ClientConfigsEntry{"IMAP", host, config.Port(l.IMAPS.Port, 993), name, "with TLS"})
158 }
159 if l.Submission.Enabled {
160 c.Entries = append(c.Entries, ClientConfigsEntry{"Submission (SMTP)", host, config.Port(l.Submission.Port, 587), name, note(l.TLS != nil, !l.Submission.NoRequireSTARTTLS)})
161 }
162 if l.IMAP.Enabled {
163 c.Entries = append(c.Entries, ClientConfigsEntry{"IMAP", host, config.Port(l.IMAPS.Port, 143), name, note(l.TLS != nil, !l.IMAP.NoRequireSTARTTLS)})
164 }
165 }
166
167 return c, nil
168}
169