mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 16:33:47 +03:00
make http(s) path for serving the account and admin pages configurable
so you can use the host (domain) name of the mail server for serving other resources too. the default is is still that account is served on /, and so takes all incoming requests before giving webhandlers a chance. mox localserve now serves the account pages on /account/
This commit is contained in:
parent
0099197d00
commit
10daf3cb81
10 changed files with 92 additions and 59 deletions
|
@ -123,19 +123,23 @@ type Listener struct {
|
||||||
} `sconf:"optional" sconf-doc:"IMAP over TLS for reading email, by email applications. Requires a TLS config."`
|
} `sconf:"optional" sconf-doc:"IMAP over TLS for reading email, by email applications. Requires a TLS config."`
|
||||||
AccountHTTP struct {
|
AccountHTTP struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Port int `sconf:"optional" sconf-doc:"Default 80."`
|
Port int `sconf:"optional" sconf-doc:"Default 80."`
|
||||||
|
Path string `sconf:"optional" sconf-doc:"Path to serve account requests on, e.g. /mox/. Useful if domain serves other resources. Default is /."`
|
||||||
} `sconf:"optional" sconf-doc:"Account web interface, for email users wanting to change their accounts, e.g. set new password, set new delivery rulesets. Served at /."`
|
} `sconf:"optional" sconf-doc:"Account web interface, for email users wanting to change their accounts, e.g. set new password, set new delivery rulesets. Served at /."`
|
||||||
AccountHTTPS struct {
|
AccountHTTPS struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Port int `sconf:"optional" sconf-doc:"Default 80."`
|
Port int `sconf:"optional" sconf-doc:"Default 80."`
|
||||||
|
Path string `sconf:"optional" sconf-doc:"Path to serve account requests on, e.g. /mox/. Useful if domain serves other resources. Default is /."`
|
||||||
} `sconf:"optional" sconf-doc:"Account web interface listener for HTTPS. Requires a TLS config."`
|
} `sconf:"optional" sconf-doc:"Account web interface listener for HTTPS. Requires a TLS config."`
|
||||||
AdminHTTP struct {
|
AdminHTTP struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Port int `sconf:"optional" sconf-doc:"Default 80."`
|
Port int `sconf:"optional" sconf-doc:"Default 80."`
|
||||||
|
Path string `sconf:"optional" sconf-doc:"Path to serve admin requests on, e.g. /moxadmin/. Useful if domain serves other resources. Default is /admin/."`
|
||||||
} `sconf:"optional" sconf-doc:"Admin web interface, for managing domains, accounts, etc. Served at /admin/. Preferrably only enable on non-public IPs. Hint: use 'ssh -L 8080:localhost:80 you@yourmachine' and open http://localhost:8080/admin/, or set up a tunnel (e.g. WireGuard) and add its IP to the mox 'internal' listener."`
|
} `sconf:"optional" sconf-doc:"Admin web interface, for managing domains, accounts, etc. Served at /admin/. Preferrably only enable on non-public IPs. Hint: use 'ssh -L 8080:localhost:80 you@yourmachine' and open http://localhost:8080/admin/, or set up a tunnel (e.g. WireGuard) and add its IP to the mox 'internal' listener."`
|
||||||
AdminHTTPS struct {
|
AdminHTTPS struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Port int `sconf:"optional" sconf-doc:"Default 443."`
|
Port int `sconf:"optional" sconf-doc:"Default 443."`
|
||||||
|
Path string `sconf:"optional" sconf-doc:"Path to serve admin requests on, e.g. /moxadmin/. Useful if domain serves other resources. Default is /admin/."`
|
||||||
} `sconf:"optional" sconf-doc:"Admin web interface listener for HTTPS. Requires a TLS config. Preferrably only enable on non-public IPs."`
|
} `sconf:"optional" sconf-doc:"Admin web interface listener for HTTPS. Requires a TLS config. Preferrably only enable on non-public IPs."`
|
||||||
MetricsHTTP struct {
|
MetricsHTTP struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
|
|
|
@ -214,6 +214,10 @@ describe-static" and "mox config describe-domains":
|
||||||
# Default 80. (optional)
|
# Default 80. (optional)
|
||||||
Port: 0
|
Port: 0
|
||||||
|
|
||||||
|
# Path to serve account requests on, e.g. /mox/. Useful if domain serves other
|
||||||
|
# resources. Default is /. (optional)
|
||||||
|
Path:
|
||||||
|
|
||||||
# Account web interface listener for HTTPS. Requires a TLS config. (optional)
|
# Account web interface listener for HTTPS. Requires a TLS config. (optional)
|
||||||
AccountHTTPS:
|
AccountHTTPS:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
@ -221,6 +225,10 @@ describe-static" and "mox config describe-domains":
|
||||||
# Default 80. (optional)
|
# Default 80. (optional)
|
||||||
Port: 0
|
Port: 0
|
||||||
|
|
||||||
|
# Path to serve account requests on, e.g. /mox/. Useful if domain serves other
|
||||||
|
# resources. Default is /. (optional)
|
||||||
|
Path:
|
||||||
|
|
||||||
# Admin web interface, for managing domains, accounts, etc. Served at /admin/.
|
# Admin web interface, for managing domains, accounts, etc. Served at /admin/.
|
||||||
# Preferrably only enable on non-public IPs. Hint: use 'ssh -L 8080:localhost:80
|
# Preferrably only enable on non-public IPs. Hint: use 'ssh -L 8080:localhost:80
|
||||||
# you@yourmachine' and open http://localhost:8080/admin/, or set up a tunnel (e.g.
|
# you@yourmachine' and open http://localhost:8080/admin/, or set up a tunnel (e.g.
|
||||||
|
@ -231,6 +239,10 @@ describe-static" and "mox config describe-domains":
|
||||||
# Default 80. (optional)
|
# Default 80. (optional)
|
||||||
Port: 0
|
Port: 0
|
||||||
|
|
||||||
|
# Path to serve admin requests on, e.g. /moxadmin/. Useful if domain serves other
|
||||||
|
# resources. Default is /admin/. (optional)
|
||||||
|
Path:
|
||||||
|
|
||||||
# Admin web interface listener for HTTPS. Requires a TLS config. Preferrably only
|
# Admin web interface listener for HTTPS. Requires a TLS config. Preferrably only
|
||||||
# enable on non-public IPs. (optional)
|
# enable on non-public IPs. (optional)
|
||||||
AdminHTTPS:
|
AdminHTTPS:
|
||||||
|
@ -239,6 +251,10 @@ describe-static" and "mox config describe-domains":
|
||||||
# Default 443. (optional)
|
# Default 443. (optional)
|
||||||
Port: 0
|
Port: 0
|
||||||
|
|
||||||
|
# Path to serve admin requests on, e.g. /moxadmin/. Useful if domain serves other
|
||||||
|
# resources. Default is /admin/. (optional)
|
||||||
|
Path:
|
||||||
|
|
||||||
# Serve prometheus metrics, for monitoring. You should not enable this on a public
|
# Serve prometheus metrics, for monitoring. You should not enable this on a public
|
||||||
# IP. (optional)
|
# IP. (optional)
|
||||||
MetricsHTTP:
|
MetricsHTTP:
|
||||||
|
|
|
@ -52,7 +52,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Account exports web API functions for the account web interface. All its
|
// Account exports web API functions for the account web interface. All its
|
||||||
// methods are exported under /api/. Function calls require valid HTTP
|
// methods are exported under api/. Function calls require valid HTTP
|
||||||
// Authentication credentials of a user.
|
// Authentication credentials of a user.
|
||||||
type Account struct{}
|
type Account struct{}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"Name": "Account",
|
"Name": "Account",
|
||||||
"Docs": "Account exports web API functions for the account web interface. All its\nmethods are exported under /api/. Function calls require valid HTTP\nAuthentication credentials of a user.",
|
"Docs": "Account exports web API functions for the account web interface. All its\nmethods are exported under api/. Function calls require valid HTTP\nAuthentication credentials of a user.",
|
||||||
"Functions": [
|
"Functions": [
|
||||||
{
|
{
|
||||||
"Name": "SetPassword",
|
"Name": "SetPassword",
|
||||||
|
|
|
@ -77,14 +77,14 @@ func init() {
|
||||||
xlog.Fatalx("creating sherpa prometheus collector", err)
|
xlog.Fatalx("creating sherpa prometheus collector", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
adminSherpaHandler, err = sherpa.NewHandler("/admin/api/", moxvar.Version, Admin{}, &adminDoc, &sherpa.HandlerOpts{Collector: collector, AdjustFunctionNames: "none"})
|
adminSherpaHandler, err = sherpa.NewHandler("/api/", moxvar.Version, Admin{}, &adminDoc, &sherpa.HandlerOpts{Collector: collector, AdjustFunctionNames: "none"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xlog.Fatalx("sherpa handler", err)
|
xlog.Fatalx("sherpa handler", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Admin exports web API functions for the admin web interface. All its methods are
|
// Admin exports web API functions for the admin web interface. All its methods are
|
||||||
// exported under /admin/api/. Function calls require valid HTTP Authentication
|
// exported under api/. Function calls require valid HTTP Authentication
|
||||||
// credentials of a user.
|
// credentials of a user.
|
||||||
type Admin struct{}
|
type Admin struct{}
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ func adminHandle(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Method == "GET" && r.URL.Path == "/admin/" {
|
if r.Method == "GET" && r.URL.Path == "/" {
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
w.Header().Set("Cache-Control", "no-cache; max-age=0")
|
w.Header().Set("Cache-Control", "no-cache; max-age=0")
|
||||||
// We typically return the embedded admin.html, but during development it's handy
|
// We typically return the embedded admin.html, but during development it's handy
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"Name": "Admin",
|
"Name": "Admin",
|
||||||
"Docs": "Admin exports web API functions for the admin web interface. All its methods are\nexported under /admin/api/. Function calls require valid HTTP Authentication\ncredentials of a user.",
|
"Docs": "Admin exports web API functions for the admin web interface. All its methods are\nexported under api/. Function calls require valid HTTP Authentication\ncredentials of a user.",
|
||||||
"Functions": [
|
"Functions": [
|
||||||
{
|
{
|
||||||
"Name": "CheckDomain",
|
"Name": "CheckDomain",
|
||||||
|
|
78
http/web.go
78
http/web.go
|
@ -170,15 +170,15 @@ func (w *loggingWriter) Done() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set some http headers that should prevent potential abuse. Better safe than sorry.
|
// Set some http headers that should prevent potential abuse. Better safe than sorry.
|
||||||
func safeHeaders(fn http.HandlerFunc) http.HandlerFunc {
|
func safeHeaders(fn http.Handler) http.Handler {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
h := w.Header()
|
h := w.Header()
|
||||||
h.Set("X-Frame-Options", "deny")
|
h.Set("X-Frame-Options", "deny")
|
||||||
h.Set("X-Content-Type-Options", "nosniff")
|
h.Set("X-Content-Type-Options", "nosniff")
|
||||||
h.Set("Content-Security-Policy", "default-src 'self' 'unsafe-inline' data:")
|
h.Set("Content-Security-Policy", "default-src 'self' 'unsafe-inline' data:")
|
||||||
h.Set("Referrer-Policy", "same-origin")
|
h.Set("Referrer-Policy", "same-origin")
|
||||||
fn(w, r)
|
fn.ServeHTTP(w, r)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Built-in handlers, e.g. mta-sts and autoconfig.
|
// Built-in handlers, e.g. mta-sts and autoconfig.
|
||||||
|
@ -186,7 +186,7 @@ type pathHandler struct {
|
||||||
Name string // For logging/metrics.
|
Name string // For logging/metrics.
|
||||||
HostMatch func(dom dns.Domain) bool // If not nil, called to see if domain of requests matches. Only called if requested host is a valid domain.
|
HostMatch func(dom dns.Domain) bool // If not nil, called to see if domain of requests matches. Only called if requested host is a valid domain.
|
||||||
Path string // Path to register, like on http.ServeMux.
|
Path string // Path to register, like on http.ServeMux.
|
||||||
Handle http.HandlerFunc
|
Handler http.Handler
|
||||||
}
|
}
|
||||||
type serve struct {
|
type serve struct {
|
||||||
Kinds []string // Type of handler and protocol (e.g. acme-tls-alpn-01, account-http, admin-https).
|
Kinds []string // Type of handler and protocol (e.g. acme-tls-alpn-01, account-http, admin-https).
|
||||||
|
@ -195,9 +195,10 @@ type serve struct {
|
||||||
Webserver bool // Whether serving WebHandler. PathHandlers are always evaluated before WebHandlers.
|
Webserver bool // Whether serving WebHandler. PathHandlers are always evaluated before WebHandlers.
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleFunc registers a named handler for a path and optional host. If path ends with a slash, it
|
// Handle registers a named handler for a path and optional host. If path ends with
|
||||||
// is used as prefix match, otherwise a full path match is required. If hostOpt is set, only requests to those host are handled by this handler.
|
// a slash, it is used as prefix match, otherwise a full path match is required. If
|
||||||
func (s *serve) HandleFunc(name string, hostMatch func(dns.Domain) bool, path string, fn http.HandlerFunc) {
|
// hostOpt is set, only requests to those host are handled by this handler.
|
||||||
|
func (s *serve) Handle(name string, hostMatch func(dns.Domain) bool, path string, fn http.Handler) {
|
||||||
s.PathHandlers = append(s.PathHandlers, pathHandler{name, hostMatch, path, fn})
|
s.PathHandlers = append(s.PathHandlers, pathHandler{name, hostMatch, path, fn})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,7 +279,7 @@ func (s *serve) ServeHTTP(xw http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
if r.URL.Path == h.Path || strings.HasSuffix(h.Path, "/") && strings.HasPrefix(r.URL.Path, h.Path) {
|
if r.URL.Path == h.Path || strings.HasSuffix(h.Path, "/") && strings.HasPrefix(r.URL.Path, h.Path) {
|
||||||
nw.Handler = h.Name
|
nw.Handler = h.Name
|
||||||
h.Handle(nw, r)
|
h.Handler.ServeHTTP(nw, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -325,35 +326,49 @@ func Listen() {
|
||||||
if l.AccountHTTP.Enabled {
|
if l.AccountHTTP.Enabled {
|
||||||
port := config.Port(l.AccountHTTP.Port, 80)
|
port := config.Port(l.AccountHTTP.Port, 80)
|
||||||
srv := ensureServe(false, port, "account-http")
|
srv := ensureServe(false, port, "account-http")
|
||||||
srv.HandleFunc("account", nil, "/", safeHeaders(accountHandle))
|
path := "/"
|
||||||
|
if l.AccountHTTP.Path != "" {
|
||||||
|
path = l.AccountHTTP.Path
|
||||||
|
}
|
||||||
|
handler := safeHeaders(http.StripPrefix(path[:len(path)-1], http.HandlerFunc(accountHandle)))
|
||||||
|
srv.Handle("account", nil, path, handler)
|
||||||
}
|
}
|
||||||
if l.AccountHTTPS.Enabled {
|
if l.AccountHTTPS.Enabled {
|
||||||
port := config.Port(l.AccountHTTPS.Port, 443)
|
port := config.Port(l.AccountHTTPS.Port, 443)
|
||||||
srv := ensureServe(true, port, "account-https")
|
srv := ensureServe(true, port, "account-https")
|
||||||
srv.HandleFunc("account", nil, "/", safeHeaders(accountHandle))
|
path := "/"
|
||||||
|
if l.AccountHTTPS.Path != "" {
|
||||||
|
path = l.AccountHTTPS.Path
|
||||||
|
}
|
||||||
|
handler := safeHeaders(http.StripPrefix(path[:len(path)-1], http.HandlerFunc(accountHandle)))
|
||||||
|
srv.Handle("account", nil, path, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.AdminHTTP.Enabled {
|
if l.AdminHTTP.Enabled {
|
||||||
port := config.Port(l.AdminHTTP.Port, 80)
|
port := config.Port(l.AdminHTTP.Port, 80)
|
||||||
srv := ensureServe(false, port, "admin-http")
|
srv := ensureServe(false, port, "admin-http")
|
||||||
if !l.AccountHTTP.Enabled {
|
path := "/admin/"
|
||||||
srv.HandleFunc("admin", nil, "/", safeHeaders(adminIndex))
|
if l.AdminHTTP.Path != "" {
|
||||||
|
path = l.AdminHTTP.Path
|
||||||
}
|
}
|
||||||
srv.HandleFunc("admin", nil, "/admin/", safeHeaders(adminHandle))
|
handler := safeHeaders(http.StripPrefix(path[:len(path)-1], http.HandlerFunc(adminHandle)))
|
||||||
|
srv.Handle("admin", nil, path, handler)
|
||||||
}
|
}
|
||||||
if l.AdminHTTPS.Enabled {
|
if l.AdminHTTPS.Enabled {
|
||||||
port := config.Port(l.AdminHTTPS.Port, 443)
|
port := config.Port(l.AdminHTTPS.Port, 443)
|
||||||
srv := ensureServe(true, port, "admin-https")
|
srv := ensureServe(true, port, "admin-https")
|
||||||
if !l.AccountHTTPS.Enabled {
|
path := "/admin/"
|
||||||
srv.HandleFunc("admin", nil, "/", safeHeaders(adminIndex))
|
if l.AdminHTTPS.Path != "" {
|
||||||
|
path = l.AdminHTTPS.Path
|
||||||
}
|
}
|
||||||
srv.HandleFunc("admin", nil, "/admin/", safeHeaders(adminHandle))
|
handler := safeHeaders(http.StripPrefix(path[:len(path)-1], http.HandlerFunc(adminHandle)))
|
||||||
|
srv.Handle("admin", nil, path, handler)
|
||||||
}
|
}
|
||||||
if l.MetricsHTTP.Enabled {
|
if l.MetricsHTTP.Enabled {
|
||||||
port := config.Port(l.MetricsHTTP.Port, 8010)
|
port := config.Port(l.MetricsHTTP.Port, 8010)
|
||||||
srv := ensureServe(false, port, "metrics-http")
|
srv := ensureServe(false, port, "metrics-http")
|
||||||
srv.HandleFunc("metrics", nil, "/metrics", safeHeaders(promhttp.Handler().ServeHTTP))
|
srv.Handle("metrics", nil, "/metrics", safeHeaders(promhttp.Handler()))
|
||||||
srv.HandleFunc("metrics", nil, "/", safeHeaders(func(w http.ResponseWriter, r *http.Request) {
|
srv.Handle("metrics", nil, "/", safeHeaders(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/" {
|
if r.URL.Path != "/" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
|
@ -363,7 +378,7 @@ func Listen() {
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
fmt.Fprint(w, `<html><body>see <a href="/metrics">/metrics</a></body></html>`)
|
fmt.Fprint(w, `<html><body>see <a href="/metrics">/metrics</a></body></html>`)
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
if l.AutoconfigHTTPS.Enabled {
|
if l.AutoconfigHTTPS.Enabled {
|
||||||
port := config.Port(l.AutoconfigHTTPS.Port, 443)
|
port := config.Port(l.AutoconfigHTTPS.Port, 443)
|
||||||
|
@ -372,8 +387,8 @@ func Listen() {
|
||||||
// todo: may want to check this against the configured domains, could in theory be just a webserver.
|
// todo: may want to check this against the configured domains, could in theory be just a webserver.
|
||||||
return strings.HasPrefix(dom.ASCII, "autoconfig.")
|
return strings.HasPrefix(dom.ASCII, "autoconfig.")
|
||||||
}
|
}
|
||||||
srv.HandleFunc("autoconfig", autoconfigMatch, "/mail/config-v1.1.xml", safeHeaders(autoconfHandle))
|
srv.Handle("autoconfig", autoconfigMatch, "/mail/config-v1.1.xml", safeHeaders(http.HandlerFunc(autoconfHandle)))
|
||||||
srv.HandleFunc("autodiscover", autoconfigMatch, "/autodiscover/autodiscover.xml", safeHeaders(autodiscoverHandle))
|
srv.Handle("autodiscover", autoconfigMatch, "/autodiscover/autodiscover.xml", safeHeaders(http.HandlerFunc(autodiscoverHandle)))
|
||||||
}
|
}
|
||||||
if l.MTASTSHTTPS.Enabled {
|
if l.MTASTSHTTPS.Enabled {
|
||||||
port := config.Port(l.MTASTSHTTPS.Port, 443)
|
port := config.Port(l.MTASTSHTTPS.Port, 443)
|
||||||
|
@ -382,7 +397,7 @@ func Listen() {
|
||||||
// todo: may want to check this against the configured domains, could in theory be just a webserver.
|
// todo: may want to check this against the configured domains, could in theory be just a webserver.
|
||||||
return strings.HasPrefix(dom.ASCII, "mta-sts.")
|
return strings.HasPrefix(dom.ASCII, "mta-sts.")
|
||||||
}
|
}
|
||||||
srv.HandleFunc("mtasts", mtastsMatch, "/.well-known/mta-sts.txt", safeHeaders(mtastsPolicyHandle))
|
srv.Handle("mtasts", mtastsMatch, "/.well-known/mta-sts.txt", safeHeaders(http.HandlerFunc(mtastsPolicyHandle)))
|
||||||
}
|
}
|
||||||
if l.PprofHTTP.Enabled {
|
if l.PprofHTTP.Enabled {
|
||||||
// Importing net/http/pprof registers handlers on the default serve mux.
|
// Importing net/http/pprof registers handlers on the default serve mux.
|
||||||
|
@ -392,7 +407,7 @@ func Listen() {
|
||||||
}
|
}
|
||||||
srv := &serve{[]string{"pprof-http"}, nil, nil, false}
|
srv := &serve{[]string{"pprof-http"}, nil, nil, false}
|
||||||
portServe[port] = srv
|
portServe[port] = srv
|
||||||
srv.HandleFunc("pprof", nil, "/", http.DefaultServeMux.ServeHTTP)
|
srv.Handle("pprof", nil, "/", http.DefaultServeMux)
|
||||||
}
|
}
|
||||||
if l.WebserverHTTP.Enabled {
|
if l.WebserverHTTP.Enabled {
|
||||||
port := config.Port(l.WebserverHTTP.Port, 80)
|
port := config.Port(l.WebserverHTTP.Port, 80)
|
||||||
|
@ -412,7 +427,7 @@ func Listen() {
|
||||||
// validation handler.
|
// validation handler.
|
||||||
if srv, ok := portServe[80]; ok && srv.TLSConfig == nil {
|
if srv, ok := portServe[80]; ok && srv.TLSConfig == nil {
|
||||||
srv.Kinds = append(srv.Kinds, "acme-http-01")
|
srv.Kinds = append(srv.Kinds, "acme-http-01")
|
||||||
srv.HandleFunc("acme-http-01", nil, "/.well-known/acme-challenge/", m.Manager.HTTPHandler(nil).ServeHTTP)
|
srv.Handle("acme-http-01", nil, "/.well-known/acme-challenge/", m.Manager.HTTPHandler(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
hosts := map[dns.Domain]struct{}{
|
hosts := map[dns.Domain]struct{}{
|
||||||
|
@ -452,19 +467,6 @@ func Listen() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only used when the account page is not active on the same listener.
|
|
||||||
func adminIndex(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.URL.Path != "/" {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.Method != "GET" {
|
|
||||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
http.Redirect(w, r, "/admin/", http.StatusSeeOther)
|
|
||||||
}
|
|
||||||
|
|
||||||
// functions to be launched in goroutine that will serve on a listener.
|
// functions to be launched in goroutine that will serve on a listener.
|
||||||
var servers []func()
|
var servers []func()
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,9 @@ func TestServeHTTP(t *testing.T) {
|
||||||
return strings.HasPrefix(dom.ASCII, "mta-sts.")
|
return strings.HasPrefix(dom.ASCII, "mta-sts.")
|
||||||
},
|
},
|
||||||
Path: "/.well-known/mta-sts.txt",
|
Path: "/.well-known/mta-sts.txt",
|
||||||
Handle: func(w http.ResponseWriter, r *http.Request) {
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write([]byte("mta-sts!"))
|
w.Write([]byte("mta-sts!"))
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Webserver: true,
|
Webserver: true,
|
||||||
|
|
|
@ -129,15 +129,15 @@ during those commands instead of during "data".
|
||||||
golog.Print("")
|
golog.Print("")
|
||||||
golog.Printf(`if the localpart begins with "mailfrom" or "rcptto", the error is returned during those commands instead of during "data"`)
|
golog.Printf(`if the localpart begins with "mailfrom" or "rcptto", the error is returned during those commands instead of during "data"`)
|
||||||
golog.Print("")
|
golog.Print("")
|
||||||
golog.Print(" smtp://localhost:1025 - receive email")
|
golog.Print(" smtp://localhost:1025 - receive email")
|
||||||
golog.Print("smtps://mox%40localhost:moxmoxmox@localhost:1465 - send email")
|
golog.Print("smtps://mox%40localhost:moxmoxmox@localhost:1465 - send email")
|
||||||
golog.Print(" smtp://mox%40localhost:moxmoxmox@localhost:1587 - send email (without tls)")
|
golog.Print(" smtp://mox%40localhost:moxmoxmox@localhost:1587 - send email (without tls)")
|
||||||
golog.Print("imaps://mox%40localhost:moxmoxmox@localhost:1993 - read email")
|
golog.Print("imaps://mox%40localhost:moxmoxmox@localhost:1993 - read email")
|
||||||
golog.Print(" imap://mox%40localhost:moxmoxmox@localhost:1143 - read email (without tls)")
|
golog.Print(" imap://mox%40localhost:moxmoxmox@localhost:1143 - read email (without tls)")
|
||||||
golog.Print("https://mox%40localhost:moxmoxmox@localhost:1443 - account https")
|
golog.Print("https://mox%40localhost:moxmoxmox@localhost:1443/account/ - account https")
|
||||||
golog.Print(" http://mox%40localhost:moxmoxmox@localhost:1080 - account http (without tls)")
|
golog.Print(" http://mox%40localhost:moxmoxmox@localhost:1080/account/ - account http (without tls)")
|
||||||
golog.Print("https://admin:moxadmin@localhost:1443/admin/ - admin https")
|
golog.Print("https://admin:moxadmin@localhost:1443/admin/ - admin https")
|
||||||
golog.Print(" http://admin:moxadmin@localhost:1080/admin/ - admin http (without tls)")
|
golog.Print(" http://admin:moxadmin@localhost:1080/admin/ - admin http (without tls)")
|
||||||
golog.Print("")
|
golog.Print("")
|
||||||
golog.Printf("serving from %s", dir)
|
golog.Printf("serving from %s", dir)
|
||||||
|
|
||||||
|
@ -275,8 +275,10 @@ func writeLocalConfig(log *mlog.Log, dir string) (rerr error) {
|
||||||
local.IMAPS.Port = 1993
|
local.IMAPS.Port = 1993
|
||||||
local.AccountHTTP.Enabled = true
|
local.AccountHTTP.Enabled = true
|
||||||
local.AccountHTTP.Port = 1080
|
local.AccountHTTP.Port = 1080
|
||||||
|
local.AccountHTTP.Path = "/account/"
|
||||||
local.AccountHTTPS.Enabled = true
|
local.AccountHTTPS.Enabled = true
|
||||||
local.AccountHTTPS.Port = 1443
|
local.AccountHTTPS.Port = 1443
|
||||||
|
local.AccountHTTPS.Path = "/account/"
|
||||||
local.AdminHTTP.Enabled = true
|
local.AdminHTTP.Enabled = true
|
||||||
local.AdminHTTP.Port = 1080
|
local.AdminHTTP.Port = 1080
|
||||||
local.AdminHTTPS.Enabled = true
|
local.AdminHTTPS.Enabled = true
|
||||||
|
|
|
@ -604,6 +604,15 @@ func PrepareStaticConfig(ctx context.Context, configFile string, config *Config,
|
||||||
}
|
}
|
||||||
l.SMTP.DNSBLZones = append(l.SMTP.DNSBLZones, d)
|
l.SMTP.DNSBLZones = append(l.SMTP.DNSBLZones, d)
|
||||||
}
|
}
|
||||||
|
checkPath := func(kind string, enabled bool, path string) {
|
||||||
|
if enabled && path != "" && !strings.HasPrefix(path, "/") {
|
||||||
|
addErrorf("listener %q has %s with path %q that must start with a slash", name, kind, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkPath("AccountHTTP", l.AccountHTTP.Enabled, l.AccountHTTP.Path)
|
||||||
|
checkPath("AccountHTTPS", l.AccountHTTPS.Enabled, l.AccountHTTPS.Path)
|
||||||
|
checkPath("AdminHTTP", l.AdminHTTP.Enabled, l.AdminHTTP.Path)
|
||||||
|
checkPath("AdminHTTPS", l.AdminHTTPS.Enabled, l.AdminHTTPS.Path)
|
||||||
c.Listeners[name] = l
|
c.Listeners[name] = l
|
||||||
}
|
}
|
||||||
if haveUnspecifiedSMTPListener {
|
if haveUnspecifiedSMTPListener {
|
||||||
|
|
Loading…
Reference in a new issue