2023-01-30 16:27:06 +03:00
|
|
|
// Package http provides HTTP listeners/servers, for
|
|
|
|
// autoconfiguration/autodiscovery, the account and admin web interface and
|
|
|
|
// MTA-STS policies.
|
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
|
|
|
"fmt"
|
|
|
|
golog "log"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
_ "net/http/pprof"
|
|
|
|
|
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
|
|
|
|
|
|
"github.com/mjl-/mox/config"
|
|
|
|
"github.com/mjl-/mox/dns"
|
|
|
|
"github.com/mjl-/mox/mlog"
|
|
|
|
"github.com/mjl-/mox/mox-"
|
|
|
|
)
|
|
|
|
|
|
|
|
var xlog = mlog.New("http")
|
|
|
|
|
|
|
|
// Set some http headers that should prevent potential abuse. Better safe than sorry.
|
|
|
|
func safeHeaders(fn http.HandlerFunc) http.HandlerFunc {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
h := w.Header()
|
|
|
|
h.Set("X-Frame-Options", "deny")
|
|
|
|
h.Set("X-Content-Type-Options", "nosniff")
|
|
|
|
h.Set("Content-Security-Policy", "default-src 'self' 'unsafe-inline' data:")
|
|
|
|
h.Set("Referrer-Policy", "same-origin")
|
|
|
|
fn(w, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListenAndServe starts listeners for HTTP, including those required for ACME to
|
|
|
|
// generate TLS certificates.
|
|
|
|
func ListenAndServe() {
|
|
|
|
type serve struct {
|
|
|
|
kinds []string
|
|
|
|
tlsConfig *tls.Config
|
|
|
|
mux *http.ServeMux
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, l := range mox.Conf.Static.Listeners {
|
|
|
|
portServe := map[int]serve{}
|
|
|
|
|
|
|
|
var ensureServe func(https bool, port int, kind string) serve
|
|
|
|
ensureServe = func(https bool, port int, kind string) serve {
|
|
|
|
s, ok := portServe[port]
|
|
|
|
if !ok {
|
|
|
|
s = serve{nil, nil, &http.ServeMux{}}
|
|
|
|
}
|
|
|
|
s.kinds = append(s.kinds, kind)
|
2023-02-18 18:53:06 +03:00
|
|
|
if https && l.TLS.ACME != "" {
|
2023-01-30 16:27:06 +03:00
|
|
|
s.tlsConfig = l.TLS.ACMEConfig
|
|
|
|
} else if https {
|
|
|
|
s.tlsConfig = l.TLS.Config
|
|
|
|
if l.TLS.ACME != "" {
|
2023-02-18 18:53:06 +03:00
|
|
|
ensureServe(true, config.Port(mox.Conf.Static.ACME[l.TLS.ACME].Port, 443), "acme-tls-alpn-01")
|
2023-01-30 16:27:06 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
portServe[port] = s
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2023-02-23 01:22:42 +03:00
|
|
|
if l.TLS != nil && l.TLS.ACME != "" && (l.SMTP.Enabled && !l.SMTP.NoSTARTTLS || l.Submissions.Enabled || l.IMAPS.Enabled) {
|
|
|
|
ensureServe(true, config.Port(mox.Conf.Static.ACME[l.TLS.ACME].Port, 443), "acme-tls-alpn01")
|
2023-01-30 16:27:06 +03:00
|
|
|
}
|
|
|
|
|
2023-02-13 15:53:47 +03:00
|
|
|
if l.AccountHTTP.Enabled {
|
|
|
|
srv := ensureServe(false, config.Port(l.AccountHTTP.Port, 80), "account-http")
|
|
|
|
srv.mux.HandleFunc("/", safeHeaders(accountHandle))
|
|
|
|
}
|
|
|
|
if l.AccountHTTPS.Enabled {
|
2023-02-26 17:57:22 +03:00
|
|
|
srv := ensureServe(true, config.Port(l.AccountHTTPS.Port, 443), "account-https")
|
2023-02-13 15:53:47 +03:00
|
|
|
srv.mux.HandleFunc("/", safeHeaders(accountHandle))
|
|
|
|
}
|
|
|
|
|
2023-01-30 16:27:06 +03:00
|
|
|
if l.AdminHTTP.Enabled {
|
|
|
|
srv := ensureServe(false, config.Port(l.AdminHTTP.Port, 80), "admin-http")
|
2023-02-13 15:53:47 +03:00
|
|
|
if !l.AccountHTTP.Enabled {
|
|
|
|
srv.mux.HandleFunc("/", safeHeaders(adminIndex))
|
|
|
|
}
|
2023-01-30 16:27:06 +03:00
|
|
|
srv.mux.HandleFunc("/admin/", safeHeaders(adminHandle))
|
|
|
|
}
|
|
|
|
if l.AdminHTTPS.Enabled {
|
|
|
|
srv := ensureServe(true, config.Port(l.AdminHTTPS.Port, 443), "admin-https")
|
2023-02-26 17:57:22 +03:00
|
|
|
if !l.AccountHTTPS.Enabled {
|
2023-02-13 15:53:47 +03:00
|
|
|
srv.mux.HandleFunc("/", safeHeaders(adminIndex))
|
|
|
|
}
|
2023-01-30 16:27:06 +03:00
|
|
|
srv.mux.HandleFunc("/admin/", safeHeaders(adminHandle))
|
|
|
|
}
|
|
|
|
if l.MetricsHTTP.Enabled {
|
|
|
|
srv := ensureServe(false, config.Port(l.MetricsHTTP.Port, 8010), "metrics-http")
|
|
|
|
srv.mux.Handle("/metrics", safeHeaders(promhttp.Handler().ServeHTTP))
|
|
|
|
srv.mux.HandleFunc("/", safeHeaders(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.URL.Path != "/" {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
} else if r.Method != "GET" {
|
|
|
|
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
|
|
fmt.Fprint(w, `<html><body>see <a href="/metrics">/metrics</a></body></html>`)
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
if l.AutoconfigHTTPS.Enabled {
|
2023-02-25 13:28:15 +03:00
|
|
|
srv := ensureServe(!l.AutoconfigHTTPS.NonTLS, config.Port(l.AutoconfigHTTPS.Port, 443), "autoconfig-https")
|
2023-01-30 16:27:06 +03:00
|
|
|
srv.mux.HandleFunc("/mail/config-v1.1.xml", safeHeaders(autoconfHandle(l)))
|
|
|
|
srv.mux.HandleFunc("/autodiscover/autodiscover.xml", safeHeaders(autodiscoverHandle(l)))
|
|
|
|
}
|
|
|
|
if l.MTASTSHTTPS.Enabled {
|
2023-02-25 13:28:15 +03:00
|
|
|
srv := ensureServe(!l.AutoconfigHTTPS.NonTLS, config.Port(l.MTASTSHTTPS.Port, 443), "mtasts-https")
|
2023-01-30 16:27:06 +03:00
|
|
|
srv.mux.HandleFunc("/.well-known/mta-sts.txt", safeHeaders(mtastsPolicyHandle))
|
|
|
|
}
|
|
|
|
if l.PprofHTTP.Enabled {
|
|
|
|
// Importing net/http/pprof registers handlers on the default serve mux.
|
|
|
|
port := config.Port(l.PprofHTTP.Port, 8011)
|
|
|
|
if _, ok := portServe[port]; ok {
|
|
|
|
xlog.Fatal("cannot serve pprof on same endpoint as other http services")
|
|
|
|
}
|
|
|
|
portServe[port] = serve{[]string{"pprof-http"}, nil, http.DefaultServeMux}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We'll explicitly ensure these TLS certs exist (e.g. are created with ACME)
|
|
|
|
// immediately after startup. We only do so for our explicitly hostnames, not for
|
|
|
|
// autoconfig or mta-sts DNS records, they can be requested on demand (perhaps
|
|
|
|
// never).
|
|
|
|
ensureHosts := map[dns.Domain]struct{}{}
|
|
|
|
|
|
|
|
if l.TLS != nil && l.TLS.ACME != "" {
|
|
|
|
m := mox.Conf.Static.ACME[l.TLS.ACME].Manager
|
|
|
|
|
|
|
|
m.AllowHostname(mox.Conf.Static.HostnameDomain)
|
|
|
|
ensureHosts[mox.Conf.Static.HostnameDomain] = struct{}{}
|
|
|
|
if l.HostnameDomain.ASCII != "" {
|
|
|
|
m.AllowHostname(l.HostnameDomain)
|
|
|
|
ensureHosts[l.HostnameDomain] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
// Just in case someone adds quite some domains to their config. We don't want to
|
|
|
|
// hit any ACME rate limits.
|
|
|
|
if len(ensureHosts) > 10 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
i := 0
|
|
|
|
for hostname := range ensureHosts {
|
|
|
|
if i > 0 {
|
|
|
|
// Sleep just a little. We don't want to hammer our ACME provider, e.g. Let's Encrypt.
|
|
|
|
time.Sleep(10 * time.Second)
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
|
|
|
|
hello := &tls.ClientHelloInfo{
|
|
|
|
ServerName: hostname.ASCII,
|
|
|
|
|
|
|
|
// Make us fetch an ECDSA P256 cert.
|
|
|
|
// We add TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 to get around the ecDSA check in autocert.
|
|
|
|
CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_AES_128_GCM_SHA256},
|
|
|
|
SupportedCurves: []tls.CurveID{tls.CurveP256},
|
|
|
|
SignatureSchemes: []tls.SignatureScheme{tls.ECDSAWithP256AndSHA256},
|
|
|
|
SupportedVersions: []uint16{tls.VersionTLS13},
|
|
|
|
}
|
|
|
|
xlog.Print("ensuring certificate availability", mlog.Field("hostname", hostname))
|
|
|
|
if _, err := m.Manager.GetCertificate(hello); err != nil {
|
|
|
|
xlog.Errorx("requesting automatic certificate", err, mlog.Field("hostname", hostname))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
for port, srv := range portServe {
|
|
|
|
for _, ip := range l.IPs {
|
|
|
|
listenAndServe(ip, port, srv.tlsConfig, name, srv.kinds, srv.mux)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-13 15:53:47 +03:00
|
|
|
// Only used when the account page is not active on the same listener.
|
2023-01-30 16:27:06 +03:00
|
|
|
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
|
|
|
|
}
|
2023-02-13 15:53:47 +03:00
|
|
|
http.Redirect(w, r, "/admin/", http.StatusSeeOther)
|
2023-01-30 16:27:06 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func listenAndServe(ip string, port int, tlsConfig *tls.Config, name string, kinds []string, mux *http.ServeMux) {
|
|
|
|
addr := net.JoinHostPort(ip, fmt.Sprintf("%d", port))
|
|
|
|
|
|
|
|
var protocol string
|
|
|
|
var ln net.Listener
|
|
|
|
var err error
|
|
|
|
if tlsConfig == nil {
|
|
|
|
protocol = "http"
|
|
|
|
xlog.Print("http listener", mlog.Field("name", name), mlog.Field("kinds", strings.Join(kinds, ",")), mlog.Field("address", addr))
|
|
|
|
ln, err = net.Listen(mox.Network(ip), addr)
|
|
|
|
if err != nil {
|
|
|
|
xlog.Fatalx("http: listen"+mox.LinuxSetcapHint(err), err, mlog.Field("addr", addr))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
protocol = "https"
|
|
|
|
xlog.Print("https listener", mlog.Field("name", name), mlog.Field("kinds", strings.Join(kinds, ",")), mlog.Field("address", addr))
|
|
|
|
ln, err = tls.Listen(mox.Network(ip), addr, tlsConfig)
|
|
|
|
if err != nil {
|
|
|
|
xlog.Fatalx("https: listen"+mox.LinuxSetcapHint(err), err, mlog.Field("addr", addr))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
server := &http.Server{
|
|
|
|
Handler: mux,
|
|
|
|
TLSConfig: tlsConfig,
|
|
|
|
ErrorLog: golog.New(mlog.ErrWriter(xlog.Fields(mlog.Field("pkg", "net/http")), mlog.LevelInfo, protocol+" error"), "", 0),
|
|
|
|
}
|
|
|
|
go func() {
|
|
|
|
err := server.Serve(ln)
|
|
|
|
xlog.Fatalx(protocol+": serve", err)
|
|
|
|
}()
|
|
|
|
}
|