mirror of
https://github.com/mjl-/mox.git
synced 2025-06-28 10:58:16 +03:00
Compare commits
No commits in common. "main" and "v0.0.15" have entirely different histories.
@ -212,7 +212,7 @@ func DomainRecords(domConf config.Domain, domain dns.Domain, hasDNSSEC bool, cer
|
||||
records = append(records,
|
||||
"; Remote servers can use MTA-STS to verify our TLS certificate with the",
|
||||
"; WebPKI pool of CA's (certificate authorities) when delivering over SMTP with",
|
||||
"; STARTTLS.",
|
||||
"; STARTTLSTLS.",
|
||||
fmt.Sprintf(`mta-sts.%s. CNAME %s.`, d, h),
|
||||
fmt.Sprintf(`_mta-sts.%s. TXT "v=STSv1; id=%s"`, d, sts.PolicyID),
|
||||
"",
|
||||
|
@ -1,5 +0,0 @@
|
||||
Below are the incompatible changes between v0.0.15 and next, per package.
|
||||
|
||||
# smtpclient
|
||||
- GatherDestinations: changed from func(context.Context, *log/slog.Logger, github.com/mjl-/mox/dns.Resolver, github.com/mjl-/mox/dns.IPDomain) (bool, bool, bool, github.com/mjl-/mox/dns.Domain, []github.com/mjl-/mox/dns.IPDomain, bool, error) to func(context.Context, *log/slog.Logger, github.com/mjl-/mox/dns.Resolver, github.com/mjl-/mox/dns.IPDomain) (bool, bool, bool, github.com/mjl-/mox/dns.Domain, []HostPref, bool, error)
|
||||
|
@ -389,7 +389,7 @@ func xbackupctl(ctx context.Context, xctl *ctl) {
|
||||
srcAcmeDir := filepath.Join(srcDataDir, "acme")
|
||||
if _, err := os.Stat(srcAcmeDir); err == nil {
|
||||
backupDir("acme")
|
||||
} else if !os.IsNotExist(err) {
|
||||
} else if err != nil && !os.IsNotExist(err) {
|
||||
xerrx("copying acme/", err)
|
||||
}
|
||||
|
||||
|
@ -209,14 +209,12 @@ type Listener struct {
|
||||
NonTLS bool `sconf:"optional" sconf-doc:"If set, plain HTTP instead of HTTPS is spoken on the configured port. Can be useful when the mta-sts domain is reverse proxied."`
|
||||
} `sconf:"optional" sconf-doc:"Serve MTA-STS policies describing SMTP TLS requirements. Requires a TLS config."`
|
||||
WebserverHTTP struct {
|
||||
Enabled bool
|
||||
Port int `sconf:"optional" sconf-doc:"Port for plain HTTP (non-TLS) webserver."`
|
||||
RateLimitDisabled bool `sconf:"optional" sconf-doc:"Disable rate limiting for all requests to this port."`
|
||||
Enabled bool
|
||||
Port int `sconf:"optional" sconf-doc:"Port for plain HTTP (non-TLS) webserver."`
|
||||
} `sconf:"optional" sconf-doc:"All configured WebHandlers will serve on an enabled listener."`
|
||||
WebserverHTTPS struct {
|
||||
Enabled bool
|
||||
Port int `sconf:"optional" sconf-doc:"Port for HTTPS webserver."`
|
||||
RateLimitDisabled bool `sconf:"optional" sconf-doc:"Disable rate limiting for all requests to this port."`
|
||||
Enabled bool
|
||||
Port int `sconf:"optional" sconf-doc:"Port for HTTPS webserver."`
|
||||
} `sconf:"optional" sconf-doc:"All configured WebHandlers will serve on an enabled listener. Either ACME must be configured, or for each WebHandler domain a TLS certificate must be configured."`
|
||||
}
|
||||
|
||||
@ -237,7 +235,6 @@ type Transport struct {
|
||||
SMTP *TransportSMTP `sconf:"optional" sconf-doc:"SMTP over a plain connection (possibly with STARTTLS), typically for old-fashioned unauthenticated relaying to a remote queue."`
|
||||
Socks *TransportSocks `sconf:"optional" sconf-doc:"Like regular direct delivery, but makes outgoing connections through a SOCKS proxy."`
|
||||
Direct *TransportDirect `sconf:"optional" sconf-doc:"Like regular direct delivery, but allows to tweak outgoing connections."`
|
||||
Fail *TransportFail `sconf:"optional" sconf-doc:"Immediately fails the delivery attempt."`
|
||||
}
|
||||
|
||||
// TransportSMTP delivers messages by "submission" (SMTP, typically
|
||||
@ -281,16 +278,6 @@ type TransportDirect struct {
|
||||
IPFamily string `sconf:"-" json:"-"`
|
||||
}
|
||||
|
||||
// TransportFail is a transport that fails all delivery attempts.
|
||||
type TransportFail struct {
|
||||
SMTPCode int `sconf:"optional" sconf-doc:"SMTP error code and optional enhanced error code to use for the failure. If empty, 554 is used (transaction failed)."`
|
||||
SMTPMessage string `sconf:"optional" sconf-doc:"Message to include for the rejection. It will be shown in the DSN."`
|
||||
|
||||
// Effective values to use, set when parsing.
|
||||
Code int `sconf:"-"`
|
||||
Message string `sconf:"-"`
|
||||
}
|
||||
|
||||
type Domain struct {
|
||||
Disabled bool `sconf:"optional" sconf-doc:"Disabled domains can be useful during/before migrations. Domains that are disabled can still be configured like normal, including adding addresses using the domain to accounts. However, disabled domains: 1. Do not try to fetch ACME certificates. TLS connections to host names involving the email domain will fail. A TLS certificate for the hostname (that wil be used as MX) itself will be requested. 2. Incoming deliveries over SMTP are rejected with a temporary error '450 4.2.1 recipient domain temporarily disabled'. 3. Submissions over SMTP using an (envelope) SMTP MAIL FROM address or message 'From' address of a disabled domain will be rejected with a temporary error '451 4.3.0 sender domain temporarily disabled'. Note that accounts with addresses at disabled domains can still log in and read email (unless the account itself is disabled)."`
|
||||
Description string `sconf:"optional" sconf-doc:"Free-form description of domain."`
|
||||
@ -544,7 +531,6 @@ type TLS struct {
|
||||
KeyCerts []KeyCert `sconf:"optional" sconf-doc:"Keys and certificates to use for this listener. The files are opened by the privileged root process and passed to the unprivileged mox process, so no special permissions are required on the files. If the private key will not be replaced when refreshing certificates, also consider adding the private key to HostPrivateKeyFiles and configuring DANE TLSA DNS records."`
|
||||
MinVersion string `sconf:"optional" sconf-doc:"Minimum TLS version. Default: TLSv1.2."`
|
||||
HostPrivateKeyFiles []string `sconf:"optional" sconf-doc:"Private keys used for ACME certificates. Specified explicitly so DANE TLSA DNS records can be generated, even before the certificates are requested. DANE is a mechanism to authenticate remote TLS certificates based on a public key or certificate specified in DNS, protected with DNSSEC. DANE is opportunistic and attempted when delivering SMTP with STARTTLS. The private key files must be in PEM format. PKCS8 is recommended, but PKCS1 and EC private keys are recognized as well. Only RSA 2048 bit and ECDSA P-256 keys are currently used. The first of each is used when requesting new certificates through ACME."`
|
||||
ClientAuthDisabled bool `sconf:"optional" sconf-doc:"Disable TLS client authentication with certificates/keys, preventing the TLS server from requesting a TLS certificate from clients. Useful for working around clients that don't handle TLS client authentication well."`
|
||||
|
||||
Config *tls.Config `sconf:"-" json:"-"` // TLS config for non-ACME-verification connections, i.e. SMTP and IMAP, and not port 443. Connections without SNI will use a certificate for the hostname of the listener, connections with an SNI hostname that isn't allowed will be rejected.
|
||||
ConfigFallback *tls.Config `sconf:"-" json:"-"` // Like Config, but uses the certificate for the listener hostname when the requested SNI hostname is not allowed, instead of causing the connection to fail.
|
||||
|
@ -217,11 +217,6 @@ See https://pkg.go.dev/github.com/mjl-/sconf for details.
|
||||
HostPrivateKeyFiles:
|
||||
-
|
||||
|
||||
# Disable TLS client authentication with certificates/keys, preventing the TLS
|
||||
# server from requesting a TLS certificate from clients. Useful for working around
|
||||
# clients that don't handle TLS client authentication well. (optional)
|
||||
ClientAuthDisabled: false
|
||||
|
||||
# Maximum size in bytes for incoming and outgoing messages. Default is 100MB.
|
||||
# (optional)
|
||||
SMTPMaxMessageSize: 0
|
||||
@ -519,9 +514,6 @@ See https://pkg.go.dev/github.com/mjl-/sconf for details.
|
||||
# Port for plain HTTP (non-TLS) webserver. (optional)
|
||||
Port: 0
|
||||
|
||||
# Disable rate limiting for all requests to this port. (optional)
|
||||
RateLimitDisabled: false
|
||||
|
||||
# All configured WebHandlers will serve on an enabled listener. Either ACME must
|
||||
# be configured, or for each WebHandler domain a TLS certificate must be
|
||||
# configured. (optional)
|
||||
@ -531,9 +523,6 @@ See https://pkg.go.dev/github.com/mjl-/sconf for details.
|
||||
# Port for HTTPS webserver. (optional)
|
||||
Port: 0
|
||||
|
||||
# Disable rate limiting for all requests to this port. (optional)
|
||||
RateLimitDisabled: false
|
||||
|
||||
# Destination for emails delivered to postmaster addresses: a plain 'postmaster'
|
||||
# without domain, 'postmaster@<hostname>' (also for each listener with SMTP
|
||||
# enabled), and as fallback for each domain without explicitly configured
|
||||
@ -736,16 +725,6 @@ See https://pkg.go.dev/github.com/mjl-/sconf for details.
|
||||
# remote SMTP servers. (optional)
|
||||
DisableIPv6: false
|
||||
|
||||
# Immediately fails the delivery attempt. (optional)
|
||||
Fail:
|
||||
|
||||
# SMTP error code and optional enhanced error code to use for the failure. If
|
||||
# empty, 554 is used (transaction failed). (optional)
|
||||
SMTPCode: 0
|
||||
|
||||
# Message to include for the rejection. It will be shown in the DSN. (optional)
|
||||
SMTPMessage:
|
||||
|
||||
# Do not send DMARC reports (aggregate only). By default, aggregate reports on
|
||||
# DMARC evaluations are sent to domains if their DMARC policy requests them.
|
||||
# Reports are sent at whole hours, with a minimum of 1 hour and maximum of 24
|
||||
|
14
curves.go
14
curves.go
@ -1,14 +0,0 @@
|
||||
//go:build !go1.24
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
)
|
||||
|
||||
var curvesList = []tls.CurveID{
|
||||
tls.CurveP256,
|
||||
tls.CurveP384,
|
||||
tls.CurveP521,
|
||||
tls.X25519,
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
//go:build go1.24
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
)
|
||||
|
||||
var curvesList = []tls.CurveID{
|
||||
tls.CurveP256,
|
||||
tls.CurveP384,
|
||||
tls.CurveP521,
|
||||
tls.X25519,
|
||||
tls.X25519MLKEM768,
|
||||
}
|
@ -10,7 +10,7 @@
|
||||
// looked up with an DNS "A" lookup of a name similar to an IPv4 address, but with
|
||||
// 4-bit hexadecimal dot-separated characters, in reverse.
|
||||
//
|
||||
// The health of a DNSBL "zone" can be checked through a lookup of 127.0.0.1
|
||||
// The health of a DNSBL "zone" can be check through a lookup of 127.0.0.1
|
||||
// (must not be present) and 127.0.0.2 (must be present).
|
||||
package dnsbl
|
||||
|
||||
|
46
doc.go
46
doc.go
@ -114,7 +114,6 @@ any parameters. Followed by the help and usage information for each command.
|
||||
mox rdap domainage domain
|
||||
mox retrain [accountname]
|
||||
mox sendmail [-Fname] [ignoredflags] [-t] [<message]
|
||||
mox smtp dial host[:port]
|
||||
mox spf check domain ip
|
||||
mox spf lookup domain
|
||||
mox spf parse txtrecord
|
||||
@ -1528,51 +1527,6 @@ binary should be setgid that group:
|
||||
|
||||
usage: mox sendmail [-Fname] [ignoredflags] [-t] [<message]
|
||||
|
||||
# mox smtp dial
|
||||
|
||||
Dial the address, initialize the SMTP session, including using STARTTLS to enable TLS if the server supports it.
|
||||
|
||||
If no port is specified, SMTP port 25 is used.
|
||||
|
||||
Data is copied between connection and stdin/stdout until either side closes the
|
||||
connection.
|
||||
|
||||
The flags influence the TLS configuration, useful for debugging interoperability
|
||||
issues.
|
||||
|
||||
No MTA-STS or DANE verification is done.
|
||||
|
||||
Hint: Use "mox -loglevel trace smtp dial ..." to see the protocol messages
|
||||
exchanged during connection set up.
|
||||
|
||||
usage: mox smtp dial host[:port]
|
||||
-ehlohostname string
|
||||
our hostname to use during the SMTP EHLO command
|
||||
-forcetls
|
||||
use TLS, even if remote SMTP server does not announce STARTTLS extension
|
||||
-notls
|
||||
do not use TLS
|
||||
-remotehostname string
|
||||
remote hostname to use for TLS verification, if enabled; the hostname from the parameter is used by default
|
||||
-tlscerts string
|
||||
path to root ca certificates in pem form, for verification
|
||||
-tlsciphersuites string
|
||||
ciphersuites to allow, comma-separated, order is ignored, only for TLS 1.2 and earlier, empty value uses TLS stack defaults; values: tls_ecdhe_ecdsa_with_aes_128_cbc_sha, tls_ecdhe_ecdsa_with_aes_128_gcm_sha256, tls_ecdhe_ecdsa_with_aes_256_cbc_sha, tls_ecdhe_ecdsa_with_aes_256_gcm_sha384, tls_ecdhe_ecdsa_with_chacha20_poly1305_sha256, tls_ecdhe_rsa_with_aes_128_cbc_sha, tls_ecdhe_rsa_with_aes_128_gcm_sha256, tls_ecdhe_rsa_with_aes_256_cbc_sha, tls_ecdhe_rsa_with_aes_256_gcm_sha384, tls_ecdhe_rsa_with_chacha20_poly1305_sha256, and insecure: tls_ecdhe_ecdsa_with_aes_128_cbc_sha256, tls_ecdhe_ecdsa_with_rc4_128_sha, tls_ecdhe_rsa_with_3des_ede_cbc_sha, tls_ecdhe_rsa_with_aes_128_cbc_sha256, tls_ecdhe_rsa_with_rc4_128_sha, tls_rsa_with_3des_ede_cbc_sha, tls_rsa_with_aes_128_cbc_sha, tls_rsa_with_aes_128_cbc_sha256, tls_rsa_with_aes_128_gcm_sha256, tls_rsa_with_aes_256_cbc_sha, tls_rsa_with_aes_256_gcm_sha384, tls_rsa_with_rc4_128_sha
|
||||
-tlscurves string
|
||||
tls ecc key exchange mechanisms to allow, comma-separated, order is ignored, empty value uses TLS stack defaults; values: curvep256, curvep384, curvep521, x25519, x25519mlkem768
|
||||
-tlsnodynamicrecordsizing
|
||||
disable TLS dynamic record sizing
|
||||
-tlsnosessiontickets
|
||||
disable TLS session tickets
|
||||
-tlsrenegotiation string
|
||||
when to allow renegotiation; only applies to tls1.2 and earlier, not tls1.3; values: never, once, always (default "never")
|
||||
-tlsverify
|
||||
verify remote hostname during TLS
|
||||
-tlsversionmax string
|
||||
maximum TLS version, empty value uses TLS stack default; values: tls1.2, etc.
|
||||
-tlsversionmin string
|
||||
minimum TLS version, empty value uses TLS stack default; values: tls1.2, etc.
|
||||
|
||||
# mox spf check
|
||||
|
||||
Check the status of IP for the policy published in DNS for the domain.
|
||||
|
@ -1,12 +1,13 @@
|
||||
version: '3.7'
|
||||
services:
|
||||
mox:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.moximaptest
|
||||
volumes:
|
||||
- ./testdata/imaptest/config:/mox/config:z
|
||||
- ./testdata/imaptest/data:/mox/data:z
|
||||
- ./testdata/imaptest/imaptest.mbox:/mox/imaptest.mbox:z
|
||||
- ./testdata/imaptest/config:/mox/config
|
||||
- ./testdata/imaptest/data:/mox/data
|
||||
- ./testdata/imaptest/imaptest.mbox:/mox/imaptest.mbox
|
||||
working_dir: /mox
|
||||
tty: true # For job control with set -m.
|
||||
command: sh -c 'set -m; mox serve & sleep 1; echo testtest | mox setaccountpassword mjl; fg'
|
||||
@ -23,7 +24,7 @@ services:
|
||||
command: host=mox port=1143 'user=mjl@mox.example' pass=testtest mbox=/imaptest/imaptest.mbox
|
||||
working_dir: /imaptest
|
||||
volumes:
|
||||
- ./testdata/imaptest:/imaptest:z
|
||||
- ./testdata/imaptest:/imaptest
|
||||
depends_on:
|
||||
mox:
|
||||
condition: service_healthy
|
||||
|
@ -1,3 +1,4 @@
|
||||
version: '3.7'
|
||||
services:
|
||||
# We run integration_test.go from this container, it connects to the other mox instances.
|
||||
test:
|
||||
@ -8,11 +9,11 @@ services:
|
||||
# dials in integration_test.go succeed.
|
||||
command: ["sh", "-c", "set -ex; cat /integration/tmp-pebble-ca.pem /integration/tls/ca.pem >>/etc/ssl/certs/ca-certificates.crt; go test -tags integration"]
|
||||
volumes:
|
||||
- ./.go:/.go:z
|
||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf:z
|
||||
- ./testdata/integration:/integration:z
|
||||
- ./testdata/integration/moxsubmit.conf:/etc/moxsubmit.conf:z
|
||||
- .:/mox:z
|
||||
- ./.go:/.go
|
||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf
|
||||
- ./testdata/integration:/integration
|
||||
- ./testdata/integration/moxsubmit.conf:/etc/moxsubmit.conf
|
||||
- .:/mox
|
||||
environment:
|
||||
GOCACHE: /.go/.cache/go-build
|
||||
depends_on:
|
||||
@ -40,8 +41,8 @@ services:
|
||||
MOX_UID: "${MOX_UID}"
|
||||
command: ["sh", "-c", "/integration/moxacmepebble.sh"]
|
||||
volumes:
|
||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf:z
|
||||
- ./testdata/integration:/integration:z
|
||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf
|
||||
- ./testdata/integration:/integration
|
||||
healthcheck:
|
||||
test: netstat -nlt | grep ':25 '
|
||||
interval: 1s
|
||||
@ -65,8 +66,8 @@ services:
|
||||
MOX_UID: "${MOX_UID}"
|
||||
command: ["sh", "-c", "/integration/moxmail2.sh"]
|
||||
volumes:
|
||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf:z
|
||||
- ./testdata/integration:/integration:z
|
||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf
|
||||
- ./testdata/integration:/integration
|
||||
healthcheck:
|
||||
test: netstat -nlt | grep ':25 '
|
||||
interval: 1s
|
||||
@ -93,8 +94,8 @@ services:
|
||||
MOX_UID: "${MOX_UID}"
|
||||
command: ["sh", "-c", "/integration/moxacmepebblealpn.sh"]
|
||||
volumes:
|
||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf:z
|
||||
- ./testdata/integration:/integration:z
|
||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf
|
||||
- ./testdata/integration:/integration
|
||||
healthcheck:
|
||||
test: netstat -nlt | grep ':25 '
|
||||
interval: 1s
|
||||
@ -115,9 +116,9 @@ services:
|
||||
image: mox_integration_moxmail
|
||||
command: ["sh", "-c", "set -e; chmod o+r /etc/resolv.conf; mox -checkconsistency localserve -ip 172.28.1.60"]
|
||||
volumes:
|
||||
- ./.go:/.go:z
|
||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf:z
|
||||
- .:/mox:z
|
||||
- ./.go:/.go
|
||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf
|
||||
- .:/mox
|
||||
environment:
|
||||
GOCACHE: /.go/.cache/go-build
|
||||
healthcheck:
|
||||
@ -140,7 +141,7 @@ services:
|
||||
context: testdata/integration
|
||||
volumes:
|
||||
# todo: figure out how to mount files with a uid that the process in the container can read...
|
||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf:z
|
||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf
|
||||
command: ["sh", "-c", "set -e; chmod o+r /etc/resolv.conf; (echo 'maillog_file = /dev/stdout'; echo 'mydestination = $$myhostname, localhost.$$mydomain, localhost, $$mydomain'; echo 'smtp_tls_security_level = may') >>/etc/postfix/main.cf; echo 'root: postfix@mox1.example' >>/etc/postfix/aliases; newaliases; postfix start-fg"]
|
||||
healthcheck:
|
||||
test: netstat -nlt | grep ':25 '
|
||||
@ -161,8 +162,8 @@ services:
|
||||
# todo: figure out how to build from dockerfile with empty context without creating empty dirs in file system.
|
||||
context: testdata/integration
|
||||
volumes:
|
||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf:z
|
||||
- ./testdata/integration:/integration:z
|
||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf
|
||||
- ./testdata/integration:/integration
|
||||
# We start with a base example.zone, but moxacmepebble appends its records,
|
||||
# followed by moxmail2. They restart unbound after appending records.
|
||||
command: ["sh", "-c", "set -ex; ls -l /etc/resolv.conf; chmod o+r /etc/resolv.conf; install -m 640 -o unbound /integration/unbound.conf /etc/unbound/; chmod 755 /integration; chmod 644 /integration/*.zone; cp /integration/example.zone /integration/example-integration.zone; ls -ld /integration /integration/reverse.zone; unbound -d -p -v"]
|
||||
@ -182,8 +183,8 @@ services:
|
||||
hostname: acmepebble.example
|
||||
image: docker.io/letsencrypt/pebble:v2.3.1@sha256:fc5a537bf8fbc7cc63aa24ec3142283aa9b6ba54529f86eb8ff31fbde7c5b258
|
||||
volumes:
|
||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf:z
|
||||
- ./testdata/integration:/integration:z
|
||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf
|
||||
- ./testdata/integration:/integration
|
||||
command: ["sh", "-c", "set -ex; mount; ls -l /etc/resolv.conf; chmod o+r /etc/resolv.conf; pebble -config /integration/pebble-config.json"]
|
||||
ports:
|
||||
- 14000:14000 # ACME port
|
||||
|
@ -27,6 +27,7 @@
|
||||
# The -ip flag ensures connections to the published ports make it to mox, and it
|
||||
# prevents listening on ::1 (IPv6 is not enabled in docker by default).
|
||||
|
||||
version: '3.7'
|
||||
services:
|
||||
mox:
|
||||
# Replace "latest" with the version you want to run, see https://r.xmox.nl/r/mox/.
|
||||
@ -38,11 +39,11 @@ services:
|
||||
# machine, and the IPs of incoming connections for spam filtering.
|
||||
network_mode: 'host'
|
||||
volumes:
|
||||
- ./config:/mox/config:z
|
||||
- ./data:/mox/data:z
|
||||
- ./config:/mox/config
|
||||
- ./data:/mox/data
|
||||
# web is optional but recommended to bind in, useful for serving static files with
|
||||
# the webserver.
|
||||
- ./web:/mox/web:z
|
||||
- ./web:/mox/web
|
||||
working_dir: /mox
|
||||
restart: on-failure
|
||||
healthcheck:
|
||||
|
107
http/web.go
107
http/web.go
@ -352,7 +352,7 @@ func (w *loggingWriter) Done() {
|
||||
slog.Any("remoteaddr", w.R.RemoteAddr),
|
||||
slog.String("tlsinfo", tlsinfo),
|
||||
slog.String("useragent", w.R.Header.Get("User-Agent")),
|
||||
slog.String("referer", w.R.Header.Get("Referer")),
|
||||
slog.String("referrr", w.R.Header.Get("Referrer")),
|
||||
}
|
||||
if w.WebsocketRequest {
|
||||
attrs = append(attrs,
|
||||
@ -386,14 +386,11 @@ type pathHandler struct {
|
||||
Path string // Path to register, like on http.ServeMux.
|
||||
Handler http.Handler
|
||||
}
|
||||
|
||||
type serve struct {
|
||||
Kinds []string // Type of handler and protocol (e.g. acme-tls-alpn-01, account-http, admin-https, imap-https, smtp-https).
|
||||
TLSConfig *tls.Config
|
||||
NextProto tlsNextProtoMap // For HTTP server, when we do submission/imap with ALPN over the HTTPS port.
|
||||
Favicon bool
|
||||
Forwarded bool // Requests are coming from a reverse proxy, we'll use X-Forwarded-For for the IP address to ratelimit.
|
||||
RateLimitDisabled bool // Don't apply ratelimiting.
|
||||
Kinds []string // Type of handler and protocol (e.g. acme-tls-alpn-01, account-http, admin-https, imap-https, smtp-https).
|
||||
TLSConfig *tls.Config
|
||||
NextProto tlsNextProtoMap // For HTTP server, when we do submission/imap with ALPN over the HTTPS port.
|
||||
Favicon bool
|
||||
|
||||
// SystemHandlers are for MTA-STS, autoconfig, ACME validation. They can't be
|
||||
// overridden by WebHandlers. WebHandlers are evaluated next, and the internal
|
||||
@ -440,41 +437,23 @@ var (
|
||||
// metrics.
|
||||
func (s *serve) ServeHTTP(xw http.ResponseWriter, r *http.Request) {
|
||||
now := time.Now()
|
||||
// Rate limiting as early as possible.
|
||||
ipstr, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
pkglog.Debugx("split host:port client remoteaddr", err, slog.Any("remoteaddr", r.RemoteAddr))
|
||||
} else if ip := net.ParseIP(ipstr); ip == nil {
|
||||
pkglog.Debug("parsing ip for client remoteaddr", slog.Any("remoteaddr", r.RemoteAddr))
|
||||
} else if !limiterConnectionrate.Add(ip, now, 1) {
|
||||
method := metricHTTPMethod(r.Method)
|
||||
proto := "http"
|
||||
if r.TLS != nil {
|
||||
proto = "https"
|
||||
}
|
||||
metricRequest.WithLabelValues("(ratelimited)", proto, method, "429").Observe(0)
|
||||
// No logging, that's just noise.
|
||||
|
||||
// Rate limiting as early as possible, if enabled.
|
||||
if !s.RateLimitDisabled {
|
||||
// If requests are coming from a reverse proxy, use the IP from X-Forwarded-For.
|
||||
// Otherwise the remote IP for this connection.
|
||||
var ipstr string
|
||||
if s.Forwarded {
|
||||
s := r.Header.Get("X-Forwarded-For")
|
||||
ipstr = strings.TrimSpace(strings.Split(s, ",")[0])
|
||||
if ipstr == "" {
|
||||
pkglog.Debug("ratelimit: no ip address in X-Forwarded-For header")
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
ipstr, _, err = net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
pkglog.Debugx("ratelimit: parsing remote address", err, slog.String("remoteaddr", r.RemoteAddr))
|
||||
}
|
||||
}
|
||||
ip := net.ParseIP(ipstr)
|
||||
if ip == nil && ipstr != "" {
|
||||
pkglog.Debug("ratelimit: invalid ip", slog.String("ip", ipstr))
|
||||
}
|
||||
if ip != nil && !limiterConnectionrate.Add(ip, now, 1) {
|
||||
method := metricHTTPMethod(r.Method)
|
||||
proto := "http"
|
||||
if r.TLS != nil {
|
||||
proto = "https"
|
||||
}
|
||||
metricRequest.WithLabelValues("(ratelimited)", proto, method, "429").Observe(0)
|
||||
// No logging, that's just noise.
|
||||
|
||||
http.Error(xw, "429 - too many auth attempts", http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
http.Error(xw, "429 - too many auth attempts", http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), mlog.CidKey, mox.Cid())
|
||||
@ -604,11 +583,11 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
return mox.Conf.IsClientSettingsDomain(host.Domain)
|
||||
}
|
||||
|
||||
var ensureServe func(https, forwarded, noRateLimiting bool, port int, kind string, favicon bool) *serve
|
||||
ensureServe = func(https, forwarded, rateLimitDisabled bool, port int, kind string, favicon bool) *serve {
|
||||
var ensureServe func(https bool, port int, kind string, favicon bool) *serve
|
||||
ensureServe = func(https bool, port int, kind string, favicon bool) *serve {
|
||||
s := portServe[port]
|
||||
if s == nil {
|
||||
s = &serve{nil, nil, tlsNextProtoMap{}, false, false, false, nil, false, nil}
|
||||
s = &serve{nil, nil, tlsNextProtoMap{}, false, nil, false, nil}
|
||||
portServe[port] = s
|
||||
}
|
||||
s.Kinds = append(s.Kinds, kind)
|
||||
@ -616,8 +595,6 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
s.ServiceHandle("favicon", accountHostMatch, "/favicon.ico", mox.SafeHeaders(http.HandlerFunc(faviconHandle)))
|
||||
s.Favicon = true
|
||||
}
|
||||
s.Forwarded = s.Forwarded || forwarded
|
||||
s.RateLimitDisabled = s.RateLimitDisabled || rateLimitDisabled
|
||||
|
||||
// We clone TLS configs because we may modify it later on for this server, for
|
||||
// ALPN. And we need copies because multiple listeners on http.Server where the
|
||||
@ -627,7 +604,7 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
|
||||
tlsport := config.Port(mox.Conf.Static.ACME[l.TLS.ACME].Port, 443)
|
||||
if portServe[tlsport] == nil || !slices.Contains(portServe[tlsport].Kinds, "acme-tls-alpn-01") {
|
||||
ensureServe(true, false, false, tlsport, "acme-tls-alpn-01", false)
|
||||
ensureServe(true, tlsport, "acme-tls-alpn-01", false)
|
||||
}
|
||||
} else if https {
|
||||
s.TLSConfig = l.TLS.Config.Clone()
|
||||
@ -647,10 +624,10 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
|
||||
if l.TLS != nil && l.TLS.ACME != "" && (l.SMTP.Enabled && !l.SMTP.NoSTARTTLS || l.Submissions.Enabled || l.IMAPS.Enabled) {
|
||||
port := config.Port(mox.Conf.Static.ACME[l.TLS.ACME].Port, 443)
|
||||
ensureServe(true, false, false, port, "acme-tls-alpn-01", false)
|
||||
ensureServe(true, port, "acme-tls-alpn-01", false)
|
||||
}
|
||||
if l.Submissions.Enabled && l.Submissions.EnabledOnHTTPS {
|
||||
s := ensureServe(true, false, false, 443, "smtp-https", false)
|
||||
s := ensureServe(true, 443, "smtp-https", false)
|
||||
hostname := mox.Conf.Static.HostnameDomain
|
||||
if l.Hostname != "" {
|
||||
hostname = l.HostnameDomain
|
||||
@ -667,7 +644,7 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
}
|
||||
}
|
||||
if l.IMAPS.Enabled && l.IMAPS.EnabledOnHTTPS {
|
||||
s := ensureServe(true, false, false, 443, "imap-https", false)
|
||||
s := ensureServe(true, 443, "imap-https", false)
|
||||
s.NextProto["imap"] = func(_ *http.Server, conn *tls.Conn, _ http.Handler) {
|
||||
imapserver.ServeTLSConn(name, conn, s.TLSConfig)
|
||||
}
|
||||
@ -678,7 +655,7 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
if l.AccountHTTP.Path != "" {
|
||||
path = l.AccountHTTP.Path
|
||||
}
|
||||
srv := ensureServe(false, l.AccountHTTP.Forwarded, false, port, "account-http at "+path, true)
|
||||
srv := ensureServe(false, port, "account-http at "+path, true)
|
||||
handler := mox.SafeHeaders(http.StripPrefix(strings.TrimRight(path, "/"), http.HandlerFunc(webaccount.Handler(path, l.AccountHTTP.Forwarded))))
|
||||
srv.ServiceHandle("account", accountHostMatch, path, handler)
|
||||
redirectToTrailingSlash(srv, accountHostMatch, "account", path)
|
||||
@ -690,7 +667,7 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
if l.AccountHTTPS.Path != "" {
|
||||
path = l.AccountHTTPS.Path
|
||||
}
|
||||
srv := ensureServe(true, l.AccountHTTPS.Forwarded, false, port, "account-https at "+path, true)
|
||||
srv := ensureServe(true, port, "account-https at "+path, true)
|
||||
handler := mox.SafeHeaders(http.StripPrefix(strings.TrimRight(path, "/"), http.HandlerFunc(webaccount.Handler(path, l.AccountHTTPS.Forwarded))))
|
||||
srv.ServiceHandle("account", accountHostMatch, path, handler)
|
||||
redirectToTrailingSlash(srv, accountHostMatch, "account", path)
|
||||
@ -702,7 +679,7 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
if l.AdminHTTP.Path != "" {
|
||||
path = l.AdminHTTP.Path
|
||||
}
|
||||
srv := ensureServe(false, l.AdminHTTP.Forwarded, false, port, "admin-http at "+path, true)
|
||||
srv := ensureServe(false, port, "admin-http at "+path, true)
|
||||
handler := mox.SafeHeaders(http.StripPrefix(strings.TrimRight(path, "/"), http.HandlerFunc(webadmin.Handler(path, l.AdminHTTP.Forwarded))))
|
||||
srv.ServiceHandle("admin", listenerHostMatch, path, handler)
|
||||
redirectToTrailingSlash(srv, listenerHostMatch, "admin", path)
|
||||
@ -714,7 +691,7 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
if l.AdminHTTPS.Path != "" {
|
||||
path = l.AdminHTTPS.Path
|
||||
}
|
||||
srv := ensureServe(true, l.AdminHTTPS.Forwarded, false, port, "admin-https at "+path, true)
|
||||
srv := ensureServe(true, port, "admin-https at "+path, true)
|
||||
handler := mox.SafeHeaders(http.StripPrefix(strings.TrimRight(path, "/"), http.HandlerFunc(webadmin.Handler(path, l.AdminHTTPS.Forwarded))))
|
||||
srv.ServiceHandle("admin", listenerHostMatch, path, handler)
|
||||
redirectToTrailingSlash(srv, listenerHostMatch, "admin", path)
|
||||
@ -731,7 +708,7 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
if l.WebAPIHTTP.Path != "" {
|
||||
path = l.WebAPIHTTP.Path
|
||||
}
|
||||
srv := ensureServe(false, l.WebAPIHTTP.Forwarded, false, port, "webapi-http at "+path, true)
|
||||
srv := ensureServe(false, port, "webapi-http at "+path, true)
|
||||
handler := mox.SafeHeaders(http.StripPrefix(strings.TrimRight(path, "/"), webapisrv.NewServer(maxMsgSize, path, l.WebAPIHTTP.Forwarded)))
|
||||
srv.ServiceHandle("webapi", accountHostMatch, path, handler)
|
||||
redirectToTrailingSlash(srv, accountHostMatch, "webapi", path)
|
||||
@ -743,7 +720,7 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
if l.WebAPIHTTPS.Path != "" {
|
||||
path = l.WebAPIHTTPS.Path
|
||||
}
|
||||
srv := ensureServe(true, l.WebAPIHTTPS.Forwarded, false, port, "webapi-https at "+path, true)
|
||||
srv := ensureServe(true, port, "webapi-https at "+path, true)
|
||||
handler := mox.SafeHeaders(http.StripPrefix(strings.TrimRight(path, "/"), webapisrv.NewServer(maxMsgSize, path, l.WebAPIHTTPS.Forwarded)))
|
||||
srv.ServiceHandle("webapi", accountHostMatch, path, handler)
|
||||
redirectToTrailingSlash(srv, accountHostMatch, "webapi", path)
|
||||
@ -755,7 +732,7 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
if l.WebmailHTTP.Path != "" {
|
||||
path = l.WebmailHTTP.Path
|
||||
}
|
||||
srv := ensureServe(false, l.WebmailHTTP.Forwarded, false, port, "webmail-http at "+path, true)
|
||||
srv := ensureServe(false, port, "webmail-http at "+path, true)
|
||||
var accountPath string
|
||||
if l.AccountHTTP.Enabled {
|
||||
accountPath = "/"
|
||||
@ -774,7 +751,7 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
if l.WebmailHTTPS.Path != "" {
|
||||
path = l.WebmailHTTPS.Path
|
||||
}
|
||||
srv := ensureServe(true, l.WebmailHTTPS.Forwarded, false, port, "webmail-https at "+path, true)
|
||||
srv := ensureServe(true, port, "webmail-https at "+path, true)
|
||||
var accountPath string
|
||||
if l.AccountHTTPS.Enabled {
|
||||
accountPath = "/"
|
||||
@ -789,7 +766,7 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
|
||||
if l.MetricsHTTP.Enabled {
|
||||
port := config.Port(l.MetricsHTTP.Port, 8010)
|
||||
srv := ensureServe(false, false, false, port, "metrics-http", false)
|
||||
srv := ensureServe(false, port, "metrics-http", false)
|
||||
srv.SystemHandle("metrics", nil, "/metrics", mox.SafeHeaders(promhttp.Handler()))
|
||||
srv.SystemHandle("metrics", nil, "/", mox.SafeHeaders(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
@ -805,7 +782,7 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
}
|
||||
if l.AutoconfigHTTPS.Enabled {
|
||||
port := config.Port(l.AutoconfigHTTPS.Port, 443)
|
||||
srv := ensureServe(!l.AutoconfigHTTPS.NonTLS, false, false, port, "autoconfig-https", false)
|
||||
srv := ensureServe(!l.AutoconfigHTTPS.NonTLS, port, "autoconfig-https", false)
|
||||
if l.AutoconfigHTTPS.NonTLS {
|
||||
ensureACMEHTTP01(srv)
|
||||
}
|
||||
@ -835,7 +812,7 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
}
|
||||
if l.MTASTSHTTPS.Enabled {
|
||||
port := config.Port(l.MTASTSHTTPS.Port, 443)
|
||||
srv := ensureServe(!l.MTASTSHTTPS.NonTLS, false, false, port, "mtasts-https", false)
|
||||
srv := ensureServe(!l.MTASTSHTTPS.NonTLS, port, "mtasts-https", false)
|
||||
if l.MTASTSHTTPS.NonTLS {
|
||||
ensureACMEHTTP01(srv)
|
||||
}
|
||||
@ -855,19 +832,19 @@ func portServes(name string, l config.Listener) map[int]*serve {
|
||||
if _, ok := portServe[port]; ok {
|
||||
pkglog.Fatal("cannot serve pprof on same endpoint as other http services")
|
||||
}
|
||||
srv := &serve{[]string{"pprof-http"}, nil, nil, false, false, false, nil, false, nil}
|
||||
srv := &serve{[]string{"pprof-http"}, nil, nil, false, nil, false, nil}
|
||||
portServe[port] = srv
|
||||
srv.SystemHandle("pprof", nil, "/", http.DefaultServeMux)
|
||||
}
|
||||
if l.WebserverHTTP.Enabled {
|
||||
port := config.Port(l.WebserverHTTP.Port, 80)
|
||||
srv := ensureServe(false, false, l.WebserverHTTP.RateLimitDisabled, port, "webserver-http", false)
|
||||
srv := ensureServe(false, port, "webserver-http", false)
|
||||
srv.Webserver = true
|
||||
ensureACMEHTTP01(srv)
|
||||
}
|
||||
if l.WebserverHTTPS.Enabled {
|
||||
port := config.Port(l.WebserverHTTPS.Port, 443)
|
||||
srv := ensureServe(true, false, l.WebserverHTTPS.RateLimitDisabled, port, "webserver-https", false)
|
||||
srv := ensureServe(true, port, "webserver-https", false)
|
||||
srv.Webserver = true
|
||||
}
|
||||
|
||||
|
@ -362,7 +362,7 @@ func TestAuthenticateTLSClientCert(t *testing.T) {
|
||||
cid := connCounter
|
||||
go func() {
|
||||
defer serverConn.Close()
|
||||
serve("test", cid, &serverConfig, serverConn, true, false, false, false, "")
|
||||
serve("test", cid, &serverConfig, serverConn, true, false, false, "")
|
||||
close(done)
|
||||
}()
|
||||
|
||||
|
@ -144,7 +144,7 @@ func FuzzServer(f *testing.F) {
|
||||
|
||||
err = serverConn.SetDeadline(time.Now().Add(time.Second))
|
||||
flog(err, "set server deadline")
|
||||
serve("test", cid, nil, serverConn, false, false, true, false, "")
|
||||
serve("test", cid, nil, serverConn, false, true, false, "")
|
||||
cid++
|
||||
}
|
||||
|
||||
|
@ -192,10 +192,9 @@ type conn struct {
|
||||
cid int64
|
||||
state state
|
||||
conn net.Conn
|
||||
connBroken bool // Once broken, we won't flush any more data.
|
||||
tls bool // Whether TLS has been initialized.
|
||||
viaHTTPS bool // Whether this connection came in via HTTPS (using TLS ALPN).
|
||||
noTLSClientAuth bool
|
||||
connBroken bool // Once broken, we won't flush any more data.
|
||||
tls bool // Whether TLS has been initialized.
|
||||
viaHTTPS bool // Whether this connection came in via HTTPS (using TLS ALPN).
|
||||
br *bufio.Reader // From remote, with TLS unwrapped in case of TLS, and possibly wrapping inflate.
|
||||
tr *moxio.TraceReader // Kept to change trace level when reading/writing cmd/auth/data.
|
||||
line chan lineErr // If set, instead of reading from br, a line is read from this channel. For reading a line in IDLE while also waiting for mailbox/account updates.
|
||||
@ -385,23 +384,21 @@ func Listen() {
|
||||
listener := mox.Conf.Static.Listeners[name]
|
||||
|
||||
var tlsConfig *tls.Config
|
||||
var noTLSClientAuth bool
|
||||
if listener.TLS != nil {
|
||||
tlsConfig = listener.TLS.Config
|
||||
noTLSClientAuth = listener.TLS.ClientAuthDisabled
|
||||
}
|
||||
|
||||
if listener.IMAP.Enabled {
|
||||
port := config.Port(listener.IMAP.Port, 143)
|
||||
for _, ip := range listener.IPs {
|
||||
listen1("imap", name, ip, port, tlsConfig, false, noTLSClientAuth, listener.IMAP.NoRequireSTARTTLS)
|
||||
listen1("imap", name, ip, port, tlsConfig, false, listener.IMAP.NoRequireSTARTTLS)
|
||||
}
|
||||
}
|
||||
|
||||
if listener.IMAPS.Enabled {
|
||||
port := config.Port(listener.IMAPS.Port, 993)
|
||||
for _, ip := range listener.IPs {
|
||||
listen1("imaps", name, ip, port, tlsConfig, true, noTLSClientAuth, false)
|
||||
listen1("imaps", name, ip, port, tlsConfig, true, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -409,7 +406,7 @@ func Listen() {
|
||||
|
||||
var servers []func()
|
||||
|
||||
func listen1(protocol, listenerName, ip string, port int, tlsConfig *tls.Config, xtls, noTLSClientAuth, noRequireSTARTTLS bool) {
|
||||
func listen1(protocol, listenerName, ip string, port int, tlsConfig *tls.Config, xtls, noRequireSTARTTLS bool) {
|
||||
log := mlog.New("imapserver", nil)
|
||||
addr := net.JoinHostPort(ip, fmt.Sprintf("%d", port))
|
||||
if os.Getuid() == 0 {
|
||||
@ -442,7 +439,7 @@ func listen1(protocol, listenerName, ip string, port int, tlsConfig *tls.Config,
|
||||
}
|
||||
|
||||
metricIMAPConnection.WithLabelValues(protocol).Inc()
|
||||
go serve(listenerName, mox.Cid(), tlsConfig, conn, xtls, noTLSClientAuth, noRequireSTARTTLS, false, "")
|
||||
go serve(listenerName, mox.Cid(), tlsConfig, conn, xtls, noRequireSTARTTLS, false, "")
|
||||
}
|
||||
}
|
||||
|
||||
@ -451,11 +448,11 @@ func listen1(protocol, listenerName, ip string, port int, tlsConfig *tls.Config,
|
||||
|
||||
// ServeTLSConn serves IMAP on a TLS connection.
|
||||
func ServeTLSConn(listenerName string, conn *tls.Conn, tlsConfig *tls.Config) {
|
||||
serve(listenerName, mox.Cid(), tlsConfig, conn, true, true, false, true, "")
|
||||
serve(listenerName, mox.Cid(), tlsConfig, conn, true, false, true, "")
|
||||
}
|
||||
|
||||
func ServeConnPreauth(listenerName string, cid int64, conn net.Conn, preauthAddress string) {
|
||||
serve(listenerName, cid, nil, conn, false, true, true, false, preauthAddress)
|
||||
serve(listenerName, cid, nil, conn, false, true, false, preauthAddress)
|
||||
}
|
||||
|
||||
// Serve starts serving on all listeners, launching a goroutine per listener.
|
||||
@ -769,7 +766,7 @@ var cleanClose struct{} // Sentinel value for panic/recover indicating clean clo
|
||||
// preauthenticated.
|
||||
//
|
||||
// The connection is closed before returning.
|
||||
func serve(listenerName string, cid int64, tlsConfig *tls.Config, nc net.Conn, xtls, noTLSClientAuth, noRequireSTARTTLS, viaHTTPS bool, preauthAddress string) {
|
||||
func serve(listenerName string, cid int64, tlsConfig *tls.Config, nc net.Conn, xtls, noRequireSTARTTLS, viaHTTPS bool, preauthAddress string) {
|
||||
var remoteIP net.IP
|
||||
if a, ok := nc.RemoteAddr().(*net.TCPAddr); ok {
|
||||
remoteIP = a.IP
|
||||
@ -783,7 +780,6 @@ func serve(listenerName string, cid int64, tlsConfig *tls.Config, nc net.Conn, x
|
||||
conn: nc,
|
||||
tls: xtls,
|
||||
viaHTTPS: viaHTTPS,
|
||||
noTLSClientAuth: noTLSClientAuth,
|
||||
lastlog: time.Now(),
|
||||
baseTLSConfig: tlsConfig,
|
||||
remoteIP: remoteIP,
|
||||
@ -994,10 +990,6 @@ func (c *conn) makeTLSConfig() *tls.Config {
|
||||
// config, so they can be used for this connection too.
|
||||
tlsConf := c.baseTLSConfig.Clone()
|
||||
|
||||
if c.noTLSClientAuth {
|
||||
return tlsConf
|
||||
}
|
||||
|
||||
// Allow client certificate authentication, for use with the sasl "external"
|
||||
// authentication mechanism.
|
||||
tlsConf.ClientAuth = tls.RequestClientCert
|
||||
@ -1097,7 +1089,7 @@ func (c *conn) tlsClientAuthVerifyPeerCertParsed(cert *x509.Certificate) error {
|
||||
if err == bstore.ErrAbsent {
|
||||
c.loginAttempt.Result = store.AuthBadCredentials
|
||||
}
|
||||
return fmt.Errorf("looking up tls public key with fingerprint %s, subject %q, issuer %q: %v", fp, cert.Subject, cert.Issuer, err)
|
||||
return fmt.Errorf("looking up tls public key with fingerprint %s: %v", fp, err)
|
||||
}
|
||||
c.loginAttempt.LoginAddress = pubKey.LoginAddress
|
||||
|
||||
@ -1160,7 +1152,7 @@ func (c *conn) xtlsHandshakeAndAuthenticate(conn net.Conn) {
|
||||
cancel()
|
||||
|
||||
cs := tlsConn.ConnectionState()
|
||||
if cs.DidResume && len(cs.PeerCertificates) > 0 && !c.noTLSClientAuth {
|
||||
if cs.DidResume && len(cs.PeerCertificates) > 0 {
|
||||
// Verify client after session resumption.
|
||||
err := c.tlsClientAuthVerifyPeerCertParsed(cs.PeerCertificates[0])
|
||||
if err != nil {
|
||||
@ -1175,7 +1167,6 @@ func (c *conn) xtlsHandshakeAndAuthenticate(conn net.Conn) {
|
||||
slog.String("ciphersuite", ciphersuite),
|
||||
slog.String("sni", cs.ServerName),
|
||||
slog.Bool("resumed", cs.DidResume),
|
||||
slog.Bool("notlsclientauth", c.noTLSClientAuth),
|
||||
slog.Int("clientcerts", len(cs.PeerCertificates)),
|
||||
}
|
||||
if c.account != nil {
|
||||
@ -2399,7 +2390,7 @@ func (c *conn) capabilities() string {
|
||||
} else {
|
||||
caps += " LOGINDISABLED"
|
||||
}
|
||||
if c.tls && len(c.conn.(*tls.Conn).ConnectionState().PeerCertificates) > 0 && !c.viaHTTPS && !c.noTLSClientAuth {
|
||||
if c.tls && len(c.conn.(*tls.Conn).ConnectionState().PeerCertificates) > 0 && !c.viaHTTPS {
|
||||
caps += " AUTH=EXTERNAL"
|
||||
}
|
||||
return caps
|
||||
|
@ -556,7 +556,7 @@ func startArgsMore(t *testing.T, uidonly, first, immediateTLS bool, serverConfig
|
||||
cid := connCounter - 1
|
||||
go func() {
|
||||
const viaHTTPS = false
|
||||
serve("test", cid, serverConfig, serverConn, immediateTLS, false, allowLoginWithoutTLS, viaHTTPS, "")
|
||||
serve("test", cid, serverConfig, serverConn, immediateTLS, allowLoginWithoutTLS, viaHTTPS, "")
|
||||
close(done)
|
||||
}()
|
||||
var tc *testconn
|
||||
|
243
main.go
243
main.go
@ -12,7 +12,6 @@ import (
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
@ -24,7 +23,6 @@ import (
|
||||
"io/fs"
|
||||
"log"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@ -203,7 +201,6 @@ var commands = []struct {
|
||||
{"rdap domainage", cmdRDAPDomainage},
|
||||
{"retrain", cmdRetrain},
|
||||
{"sendmail", cmdSendmail},
|
||||
{"smtp dial", cmdSMTPDial},
|
||||
{"spf check", cmdSPFCheck},
|
||||
{"spf lookup", cmdSPFLookup},
|
||||
{"spf parse", cmdSPFParse},
|
||||
@ -1900,220 +1897,6 @@ with DKIM, by mox.
|
||||
xcheckf(err, "writing rsa private key")
|
||||
}
|
||||
|
||||
// todo: options for specifying the domain this is the mx host of, and enabling dane and/or mta-sts verification
|
||||
func cmdSMTPDial(c *cmd) {
|
||||
c.params = "host[:port]"
|
||||
|
||||
var tlsCerts, tlsCiphersuites, tlsCurves, tlsVersionMin, tlsVersionMax, tlsRenegotiation string
|
||||
var tlsVerify, noTLS, forceTLS, tlsNoSessionTickets, tlsNoDynamicRecordSizing bool
|
||||
var ehloHostnameStr, remoteHostnameStr string
|
||||
|
||||
ciphersuites := map[string]*tls.CipherSuite{}
|
||||
ciphersuitesInsecure := map[string]*tls.CipherSuite{}
|
||||
for _, v := range tls.CipherSuites() {
|
||||
if slices.Contains(v.SupportedVersions, tls.VersionTLS10) || slices.Contains(v.SupportedVersions, tls.VersionTLS11) || slices.Contains(v.SupportedVersions, tls.VersionTLS12) {
|
||||
ciphersuites[strings.ToLower(v.Name)] = v
|
||||
}
|
||||
}
|
||||
for _, v := range tls.InsecureCipherSuites() {
|
||||
if slices.Contains(v.SupportedVersions, tls.VersionTLS10) || slices.Contains(v.SupportedVersions, tls.VersionTLS11) || slices.Contains(v.SupportedVersions, tls.VersionTLS12) {
|
||||
ciphersuitesInsecure[strings.ToLower(v.Name)] = v
|
||||
}
|
||||
}
|
||||
|
||||
curves := map[string]tls.CurveID{}
|
||||
for _, a := range curvesList {
|
||||
curves[strings.ToLower(a.String())] = a
|
||||
}
|
||||
|
||||
c.flag.StringVar(&tlsCiphersuites, "tlsciphersuites", "", "ciphersuites to allow, comma-separated, order is ignored, only for TLS 1.2 and earlier, empty value uses TLS stack defaults; values: "+strings.Join(slices.Sorted(maps.Keys(ciphersuites)), ", ")+", and insecure: "+strings.Join(slices.Sorted(maps.Keys(ciphersuitesInsecure)), ", "))
|
||||
c.flag.StringVar(&tlsCurves, "tlscurves", "", "tls ecc key exchange mechanisms to allow, comma-separated, order is ignored, empty value uses TLS stack defaults; values: curvep256, curvep384, curvep521, x25519, x25519mlkem768")
|
||||
c.flag.StringVar(&tlsCerts, "tlscerts", "", "path to root ca certificates in pem form, for verification")
|
||||
c.flag.StringVar(&tlsVersionMin, "tlsversionmin", "", "minimum TLS version, empty value uses TLS stack default; values: tls1.2, etc.")
|
||||
c.flag.StringVar(&tlsVersionMax, "tlsversionmax", "", "maximum TLS version, empty value uses TLS stack default; values: tls1.2, etc.")
|
||||
c.flag.BoolVar(&tlsVerify, "tlsverify", false, "verify remote hostname during TLS")
|
||||
c.flag.BoolVar(&tlsNoSessionTickets, "tlsnosessiontickets", false, "disable TLS session tickets")
|
||||
c.flag.BoolVar(&tlsNoDynamicRecordSizing, "tlsnodynamicrecordsizing", false, "disable TLS dynamic record sizing")
|
||||
c.flag.BoolVar(&noTLS, "notls", false, "do not use TLS")
|
||||
c.flag.BoolVar(&forceTLS, "forcetls", false, "use TLS, even if remote SMTP server does not announce STARTTLS extension")
|
||||
c.flag.StringVar(&tlsRenegotiation, "tlsrenegotiation", "never", "when to allow renegotiation; only applies to tls1.2 and earlier, not tls1.3; values: never, once, always")
|
||||
c.flag.StringVar(&ehloHostnameStr, "ehlohostname", "", "our hostname to use during the SMTP EHLO command")
|
||||
c.flag.StringVar(&remoteHostnameStr, "remotehostname", "", "remote hostname to use for TLS verification, if enabled; the hostname from the parameter is used by default")
|
||||
|
||||
c.help = `Dial the address, initialize the SMTP session, including using STARTTLS to enable TLS if the server supports it.
|
||||
|
||||
If no port is specified, SMTP port 25 is used.
|
||||
|
||||
Data is copied between connection and stdin/stdout until either side closes the
|
||||
connection.
|
||||
|
||||
The flags influence the TLS configuration, useful for debugging interoperability
|
||||
issues.
|
||||
|
||||
No MTA-STS or DANE verification is done.
|
||||
|
||||
Hint: Use "mox -loglevel trace smtp dial ..." to see the protocol messages
|
||||
exchanged during connection set up.
|
||||
`
|
||||
args := c.Parse()
|
||||
if len(args) != 1 {
|
||||
c.Usage()
|
||||
}
|
||||
|
||||
if noTLS && forceTLS {
|
||||
log.Fatalf("cannot have both -notls and -forcetls")
|
||||
}
|
||||
|
||||
parseTLSVersion := func(s string) uint16 {
|
||||
switch s {
|
||||
case "tls1.0":
|
||||
return tls.VersionTLS10
|
||||
case "tls1.1":
|
||||
return tls.VersionTLS11
|
||||
case "tls1.2":
|
||||
return tls.VersionTLS12
|
||||
case "tls1.3":
|
||||
return tls.VersionTLS13
|
||||
case "":
|
||||
return 0
|
||||
default:
|
||||
log.Fatalf("invalid tls version %q", s)
|
||||
panic("not reached")
|
||||
}
|
||||
}
|
||||
tlsConfig := tls.Config{
|
||||
MinVersion: parseTLSVersion(tlsVersionMin),
|
||||
MaxVersion: parseTLSVersion(tlsVersionMax),
|
||||
InsecureSkipVerify: !tlsVerify,
|
||||
SessionTicketsDisabled: tlsNoSessionTickets,
|
||||
DynamicRecordSizingDisabled: tlsNoDynamicRecordSizing,
|
||||
}
|
||||
|
||||
switch tlsRenegotiation {
|
||||
case "never":
|
||||
tlsConfig.Renegotiation = tls.RenegotiateNever
|
||||
case "once":
|
||||
tlsConfig.Renegotiation = tls.RenegotiateOnceAsClient
|
||||
case "always":
|
||||
tlsConfig.Renegotiation = tls.RenegotiateFreelyAsClient
|
||||
default:
|
||||
log.Fatalf("invalid value %q for -tlsrenegotation", tlsRenegotiation)
|
||||
}
|
||||
if tlsCerts != "" {
|
||||
pool := x509.NewCertPool()
|
||||
pembuf, err := os.ReadFile(tlsCerts)
|
||||
xcheckf(err, "reading tls certificates")
|
||||
ok := pool.AppendCertsFromPEM(pembuf)
|
||||
if !ok {
|
||||
c.log.Warn("no tls certificates found", slog.String("path", tlsCerts))
|
||||
}
|
||||
tlsConfig.RootCAs = pool
|
||||
}
|
||||
if tlsCiphersuites != "" {
|
||||
for _, s := range strings.Split(tlsCiphersuites, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
c, ok := ciphersuites[s]
|
||||
if !ok {
|
||||
c, ok = ciphersuitesInsecure[s]
|
||||
}
|
||||
if !ok {
|
||||
log.Fatalf("unknown ciphersuite %q", s)
|
||||
}
|
||||
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, c.ID)
|
||||
}
|
||||
}
|
||||
if tlsCurves != "" {
|
||||
for _, s := range strings.Split(tlsCurves, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
if c, ok := curves[s]; !ok {
|
||||
log.Fatalf("unknown ecc key exchange algorithm %q", s)
|
||||
} else {
|
||||
tlsConfig.CurvePreferences = append(tlsConfig.CurvePreferences, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var host, portStr string
|
||||
var err error
|
||||
host, portStr, err = net.SplitHostPort(args[0])
|
||||
if err != nil {
|
||||
host = args[0]
|
||||
portStr = "25"
|
||||
}
|
||||
port, err := strconv.ParseInt(portStr, 10, 64)
|
||||
xcheckf(err, "parsing port %q", portStr)
|
||||
|
||||
if remoteHostnameStr == "" {
|
||||
remoteHostnameStr = host
|
||||
}
|
||||
remoteHostname, err := dns.ParseDomain(remoteHostnameStr)
|
||||
xcheckf(err, "parsing remote host")
|
||||
tlsConfig.ServerName = remoteHostname.Name()
|
||||
|
||||
resolver := dns.StrictResolver{Pkg: "smtpdial"}
|
||||
_, _, _, ips, _, err := smtpclient.GatherIPs(context.Background(), c.log.Logger, resolver, "ip", dns.IPDomain{Domain: remoteHostname}, nil)
|
||||
xcheckf(err, "resolve host")
|
||||
c.log.Info("resolved remote address", slog.Any("ips", ips))
|
||||
|
||||
dialer := &net.Dialer{Timeout: 5 * time.Second}
|
||||
dialedIPs := map[string][]net.IP{}
|
||||
conn, ip, err := smtpclient.Dial(context.Background(), c.log.Logger, dialer, dns.IPDomain{Domain: remoteHostname}, ips, int(port), dialedIPs, nil)
|
||||
xcheckf(err, "dial")
|
||||
c.log.Info("connected to remote host", slog.Any("ip", ip))
|
||||
|
||||
tlsMode := smtpclient.TLSOpportunistic
|
||||
if forceTLS {
|
||||
tlsMode = smtpclient.TLSRequiredStartTLS
|
||||
} else if noTLS {
|
||||
tlsMode = smtpclient.TLSSkip
|
||||
}
|
||||
var ehloHostname dns.Domain
|
||||
if ehloHostnameStr == "" {
|
||||
name, err := os.Hostname()
|
||||
xcheckf(err, "get hostname")
|
||||
ehloHostnameStr = name
|
||||
}
|
||||
ehloHostname, err = dns.ParseDomain(ehloHostnameStr)
|
||||
xcheckf(err, "parse hostname")
|
||||
|
||||
opts := smtpclient.Opts{
|
||||
TLSConfig: &tlsConfig,
|
||||
}
|
||||
client, err := smtpclient.New(context.Background(), c.log.Logger, conn, tlsMode, false, ehloHostname, dns.Domain{}, opts)
|
||||
xcheckf(err, "new smtp client")
|
||||
|
||||
cs := client.TLSConnectionState()
|
||||
if cs == nil {
|
||||
c.log.Info("smtp initialized without tls")
|
||||
} else {
|
||||
c.log.Info("smtp initialized with tls",
|
||||
slog.String("version", tls.VersionName(cs.Version)),
|
||||
slog.String("ciphersuite", strings.ToLower(tls.CipherSuiteName(cs.CipherSuite))),
|
||||
slog.String("sni", cs.ServerName),
|
||||
)
|
||||
for _, chain := range cs.VerifiedChains {
|
||||
var l []string
|
||||
for _, cert := range chain {
|
||||
s := fmt.Sprintf("dns names %q, common name %q, %s - %s, issuer %q)", strings.Join(cert.DNSNames, ","), cert.Subject.CommonName, cert.NotBefore.Format("2006-01-02T15:04:05"), cert.NotAfter.Format("2006-01-02T15:04:05"), cert.Issuer.CommonName)
|
||||
l = append(l, s)
|
||||
}
|
||||
c.log.Info("tls certificate verification chain", slog.String("chain", strings.Join(l, "; ")))
|
||||
}
|
||||
}
|
||||
|
||||
conn, err = client.Conn()
|
||||
xcheckf(err, "get smtp session connection")
|
||||
|
||||
go func() {
|
||||
_, err := io.Copy(os.Stdout, conn)
|
||||
xcheckf(err, "copy from connection to stdout")
|
||||
err = conn.Close()
|
||||
c.log.Check(err, "closing connection")
|
||||
}()
|
||||
_, err = io.Copy(conn, os.Stdin)
|
||||
xcheckf(err, "copy from stdin to connection")
|
||||
}
|
||||
|
||||
func cmdDANEDial(c *cmd) {
|
||||
c.params = "host:port"
|
||||
var usages string
|
||||
@ -2204,12 +1987,12 @@ sharing most of its code.
|
||||
var haveMX bool
|
||||
var expandedNextHopAuthentic bool
|
||||
var expandedNextHop dns.Domain
|
||||
var hostPrefs []smtpclient.HostPref
|
||||
var hosts []dns.IPDomain
|
||||
if len(args) == 1 {
|
||||
var permanent bool
|
||||
var origNextHopAuthentic bool
|
||||
var err error
|
||||
haveMX, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, hostPrefs, permanent, err = smtpclient.GatherDestinations(ctxbg, c.log.Logger, resolver, dns.IPDomain{Domain: origNextHop})
|
||||
haveMX, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, hosts, permanent, err = smtpclient.GatherDestinations(ctxbg, c.log.Logger, resolver, dns.IPDomain{Domain: origNextHop})
|
||||
status := "temporary"
|
||||
if permanent {
|
||||
status = "permanent"
|
||||
@ -2233,12 +2016,8 @@ sharing most of its code.
|
||||
}
|
||||
|
||||
l := []string{}
|
||||
for _, hp := range hostPrefs {
|
||||
s := hp.Host.String()
|
||||
if hp.Pref >= 0 {
|
||||
s += fmt.Sprintf(" (pref %d)", hp.Pref)
|
||||
}
|
||||
l = append(l, s)
|
||||
for _, h := range hosts {
|
||||
l = append(l, h.String())
|
||||
}
|
||||
log.Printf("destinations: %s", strings.Join(l, ", "))
|
||||
} else {
|
||||
@ -2247,14 +2026,18 @@ sharing most of its code.
|
||||
|
||||
expandedNextHopAuthentic = true
|
||||
expandedNextHop = d
|
||||
hostPrefs = []smtpclient.HostPref{{Host: dns.IPDomain{Domain: d}, Pref: -1}}
|
||||
hosts = []dns.IPDomain{{Domain: d}}
|
||||
}
|
||||
|
||||
dialedIPs := map[string][]net.IP{}
|
||||
for _, hp := range hostPrefs {
|
||||
host := hp.Host
|
||||
for _, host := range hosts {
|
||||
// It should not be possible for hosts to have IP addresses: They are not
|
||||
// allowed by dns.ParseDomain, and MX records cannot contain them.
|
||||
if host.IsIP() {
|
||||
log.Fatalf("unexpected IP address for destination host")
|
||||
}
|
||||
|
||||
log.Printf("attempting to connect to %s (pref %d)", host, hp.Pref)
|
||||
log.Printf("attempting to connect to %s", host)
|
||||
|
||||
authentic, expandedAuthentic, expandedHost, ips, _, err := smtpclient.GatherIPs(ctxbg, c.log.Logger, resolver, "ip", host, dialedIPs)
|
||||
if err != nil {
|
||||
@ -4191,7 +3974,7 @@ Opens database files directly, not going through a running mox instance.
|
||||
return nil
|
||||
}
|
||||
|
||||
wq := moxio.NewWorkQueue(procs, workqueuesize, prepareMessages, processMessage)
|
||||
wq := moxio.NewWorkQueue[store.Message, threadPrep](procs, workqueuesize, prepareMessages, processMessage)
|
||||
|
||||
err = a.DB.Write(context.Background(), func(tx *bstore.Tx) error {
|
||||
q := bstore.QueryTx[store.Message](tx)
|
||||
|
@ -1058,33 +1058,6 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
|
||||
}
|
||||
}
|
||||
|
||||
checkTransportFail := func(name string, t *config.TransportFail) {
|
||||
addTransportErrorf := func(format string, args ...any) {
|
||||
addErrorf("transport %s: %s", name, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
if t.SMTPCode == 0 {
|
||||
t.Code = smtp.C554TransactionFailed
|
||||
} else if t.SMTPCode/100 != 4 && t.SMTPCode/100 != 5 {
|
||||
addTransportErrorf("smtp code %d must be 4xx or 5xx", t.SMTPCode/100)
|
||||
} else {
|
||||
t.Code = t.SMTPCode
|
||||
}
|
||||
|
||||
if len(t.SMTPMessage) > 256 {
|
||||
addTransportErrorf("message must be <= 256 characters")
|
||||
}
|
||||
for _, c := range t.SMTPMessage {
|
||||
if c < ' ' || c >= 0x7f {
|
||||
addTransportErrorf("message cannot contain control characters including newlines, and must be ascii-only")
|
||||
}
|
||||
}
|
||||
t.Message = t.SMTPMessage
|
||||
if t.Message == "" {
|
||||
t.Message = "transport fail: explicit immediate delivery failure per configuration"
|
||||
}
|
||||
}
|
||||
|
||||
for name, t := range c.Transports {
|
||||
addTransportErrorf := func(format string, args ...any) {
|
||||
addErrorf("transport %s: %s", name, fmt.Sprintf(format, args...))
|
||||
@ -1111,10 +1084,6 @@ func PrepareStaticConfig(ctx context.Context, log mlog.Log, configFile string, c
|
||||
n++
|
||||
checkTransportDirect(name, t.Direct)
|
||||
}
|
||||
if t.Fail != nil {
|
||||
n++
|
||||
checkTransportFail(name, t.Fail)
|
||||
}
|
||||
if n > 1 {
|
||||
addTransportErrorf("cannot have multiple methods in a transport")
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ func deliverDirect(qlog mlog.Log, resolver dns.Resolver, dialer smtpclient.Diale
|
||||
// directly.
|
||||
origNextHop := m0.RecipientDomain.Domain
|
||||
ctx := mox.Shutdown
|
||||
haveMX, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, hostPrefs, permanent, err := smtpclient.GatherDestinations(ctx, qlog.Logger, resolver, m0.RecipientDomain)
|
||||
haveMX, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, hosts, permanent, err := smtpclient.GatherDestinations(ctx, qlog.Logger, resolver, m0.RecipientDomain)
|
||||
if err != nil {
|
||||
// If this is a DNSSEC authentication error, we'll collect it for TLS reporting.
|
||||
// Hopefully it's a temporary misconfiguration that is solve before we try to send
|
||||
@ -196,8 +196,7 @@ func deliverDirect(qlog mlog.Log, resolver dns.Resolver, dialer smtpclient.Diale
|
||||
var lastErr = errors.New("no error") // Can be smtpclient.Error.
|
||||
nmissingRequireTLS := 0
|
||||
// todo: should make distinction between host permanently not accepting the message, and the message not being deliverable permanently. e.g. a mx host may have a size limit, or not accept 8bitmime, while another host in the list does accept the message. same for smtputf8, ../rfc/6531:555
|
||||
for _, hp := range hostPrefs {
|
||||
h := hp.Host
|
||||
for _, h := range hosts {
|
||||
// ../rfc/8461:913
|
||||
if policy != nil && policy.Mode != mtasts.ModeNone && !policy.Matches(h.Domain) {
|
||||
// todo: perhaps only send tlsrpt failure if none of the mx hosts matched? reporting about each mismatch seems useful for domain owners, to discover mtasts policies they didn't update after changing mx. there is a risk a domain owner intentionally didn't put all mx'es in the mtasts policy, but they probably won't mind being reported about that.
|
||||
@ -349,7 +348,7 @@ func deliverDirect(qlog mlog.Log, resolver dns.Resolver, dialer smtpclient.Diale
|
||||
// If we failed due to requiretls not being satisfied, make the delivery permanent.
|
||||
// It is unlikely the recipient domain will implement requiretls during our retry
|
||||
// period. Best to let the sender know immediately.
|
||||
if len(hostPrefs) > 0 && nmissingRequireTLS == len(hostPrefs) {
|
||||
if len(hosts) > 0 && nmissingRequireTLS == len(hosts) {
|
||||
qlog.Info("marking delivery as permanently failed because recipient domain does not implement requiretls")
|
||||
err := smtpclient.Error{
|
||||
Permanent: true,
|
||||
|
@ -1528,18 +1528,6 @@ func deliver(log mlog.Log, resolver dns.Resolver, m0 Msg) {
|
||||
qlog.Debug("delivering to single recipient", slog.Any("msgid", m0.ID), slog.Any("recipient", m0.Recipient()))
|
||||
}
|
||||
|
||||
// Test for "Fail" transport before Localserve.
|
||||
if transport.Fail != nil {
|
||||
err := smtpclient.Error{
|
||||
Permanent: transport.Fail.Code/100 == 5,
|
||||
Code: transport.Fail.Code,
|
||||
Secode: smtp.SePol7Other0,
|
||||
Err: fmt.Errorf("%s", transport.Fail.Message),
|
||||
}
|
||||
failMsgsDB(qlog, msgs, msgs[0].DialedIPs, backoff, dsn.NameIP{}, err)
|
||||
return
|
||||
}
|
||||
|
||||
if Localserve {
|
||||
deliverLocalserve(ctx, qlog, msgs, backoff)
|
||||
return
|
||||
|
@ -114,7 +114,6 @@ type Client struct {
|
||||
daneMoreHostnames []dns.Domain // Additional allowed names in TLS certificate for DANE-TA.
|
||||
daneVerifiedRecord *adns.TLSA // If non-nil, then will be set to verified DANE record if any.
|
||||
clientCert *tls.Certificate // If non-nil, tls client authentication is done.
|
||||
tlsConfigOpts *tls.Config // If non-nil, tls config to use.
|
||||
|
||||
// TLS connection success/failure are added. These are always non-nil, regardless
|
||||
// of what was passed in opts. It lets us unconditionally dereference them.
|
||||
@ -236,12 +235,6 @@ type Opts struct {
|
||||
// tracked.
|
||||
RecipientDomainResult *tlsrpt.Result // MTA-STS or no policy.
|
||||
HostResult *tlsrpt.Result // DANE or no policy.
|
||||
|
||||
// If not nil, the TLS config to use instead of the default. Useful for custom
|
||||
// certificate verification or TLS parameters. The other DANE/TLS/certificate
|
||||
// fields in [Opts], and the tlsVerifyPKIX and remoteHostname parameters to [New]
|
||||
// have no effect when TLSConfig is set.
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
|
||||
// New initializes an SMTP session on the given connection, returning a client that
|
||||
@ -297,7 +290,6 @@ func New(ctx context.Context, elog *slog.Logger, conn net.Conn, tlsMode TLSMode,
|
||||
cmds: []string{"(none)"},
|
||||
recipientDomainResult: ensureResult(opts.RecipientDomainResult),
|
||||
hostResult: ensureResult(opts.HostResult),
|
||||
tlsConfigOpts: opts.TLSConfig,
|
||||
}
|
||||
c.log = mlog.New("smtpclient", elog).WithFunc(func() []slog.Attr {
|
||||
now := time.Now()
|
||||
@ -362,10 +354,6 @@ func (c *Client) tlsConfig() *tls.Config {
|
||||
// failures. And we may have to verify both PKIX and DANE, record errors for
|
||||
// each, and possibly ignore the errors.
|
||||
|
||||
if c.tlsConfigOpts != nil {
|
||||
return c.tlsConfigOpts
|
||||
}
|
||||
|
||||
verifyConnection := func(cs tls.ConnectionState) error {
|
||||
// Collect verification errors. If there are none at the end, TLS validation
|
||||
// succeeded. We may find validation problems below, record them for a TLS report
|
||||
@ -1465,10 +1453,9 @@ func (c *Client) Close() (rerr error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Conn returns the connection with the initialized SMTP session, possibly wrapping
|
||||
// a TLS connection, and handling protocol trace logging. Once the caller uses this
|
||||
// connection it is in control, and responsible for closing the connection, and
|
||||
// other functions on the client must not be called anymore.
|
||||
// Conn returns the connection with initialized SMTP session. Once the caller uses
|
||||
// this connection it is in control, and responsible for closing the connection,
|
||||
// and other functions on the client must not be called anymore.
|
||||
func (c *Client) Conn() (net.Conn, error) {
|
||||
if err := c.conn.SetDeadline(time.Time{}); err != nil {
|
||||
return nil, fmt.Errorf("clearing io deadlines: %w", err)
|
||||
|
@ -26,12 +26,6 @@ var (
|
||||
errNoMail = errors.New("domain does not accept email as indicated with single dot for mx record")
|
||||
)
|
||||
|
||||
// HostPref is a host for delivery, with preference for MX records.
|
||||
type HostPref struct {
|
||||
Host dns.IPDomain
|
||||
Pref int // -1 when not an MX record.
|
||||
}
|
||||
|
||||
// GatherDestinations looks up the hosts to deliver email to a domain ("next-hop").
|
||||
// If it is an IP address, it is the only destination to try. Otherwise CNAMEs of
|
||||
// the domain are followed. Then MX records for the expanded CNAME are looked up.
|
||||
@ -52,14 +46,14 @@ type HostPref struct {
|
||||
// were found, both the original and expanded next-hops must be authentic for DANE
|
||||
// to be option. For a non-IP with no MX records found, the authentic result can
|
||||
// be used to decide which of the names to use as TLSA base domain.
|
||||
func GatherDestinations(ctx context.Context, elog *slog.Logger, resolver dns.Resolver, origNextHop dns.IPDomain) (haveMX, origNextHopAuthentic, expandedNextHopAuthentic bool, expandedNextHop dns.Domain, hostPrefs []HostPref, permanent bool, err error) {
|
||||
func GatherDestinations(ctx context.Context, elog *slog.Logger, resolver dns.Resolver, origNextHop dns.IPDomain) (haveMX, origNextHopAuthentic, expandedNextHopAuthentic bool, expandedNextHop dns.Domain, hosts []dns.IPDomain, permanent bool, err error) {
|
||||
// ../rfc/5321:3824
|
||||
|
||||
log := mlog.New("smtpclient", elog)
|
||||
|
||||
// IP addresses are dialed directly, and don't have TLSA records.
|
||||
if len(origNextHop.IP) > 0 {
|
||||
return false, false, false, expandedNextHop, []HostPref{{origNextHop, -1}}, false, nil
|
||||
return false, false, false, expandedNextHop, []dns.IPDomain{origNextHop}, false, nil
|
||||
}
|
||||
|
||||
// We start out assuming the result is authentic. Updated with each lookup.
|
||||
@ -139,8 +133,8 @@ func GatherDestinations(ctx context.Context, elog *slog.Logger, resolver dns.Res
|
||||
}
|
||||
|
||||
// No MX record, attempt delivery directly to host. ../rfc/5321:3842
|
||||
hostPrefs = []HostPref{{dns.IPDomain{Domain: expandedNextHop}, -1}}
|
||||
return false, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, hostPrefs, false, nil
|
||||
hosts = []dns.IPDomain{{Domain: expandedNextHop}}
|
||||
return false, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, hosts, false, nil
|
||||
} else if err != nil {
|
||||
log.Infox("mx record has some invalid records, keeping only the valid mx records", err)
|
||||
}
|
||||
@ -164,12 +158,12 @@ func GatherDestinations(ctx context.Context, elog *slog.Logger, resolver dns.Res
|
||||
err = fmt.Errorf("%w: invalid host name in mx record %q: %v", errDNS, mx.Host, err)
|
||||
return true, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, nil, true, err
|
||||
}
|
||||
hostPrefs = append(hostPrefs, HostPref{dns.IPDomain{Domain: host}, int(mx.Pref)})
|
||||
hosts = append(hosts, dns.IPDomain{Domain: host})
|
||||
}
|
||||
if len(hostPrefs) > 0 {
|
||||
if len(hosts) > 0 {
|
||||
err = nil
|
||||
}
|
||||
return true, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, hostPrefs, false, err
|
||||
return true, origNextHopAuthentic, expandedNextHopAuthentic, expandedNextHop, hosts, false, err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,11 +35,11 @@ func ipdomain(s string) dns.IPDomain {
|
||||
return dns.IPDomain{Domain: d}
|
||||
}
|
||||
|
||||
func hostprefs(pref int, names ...string) (l []HostPref) {
|
||||
for _, s := range names {
|
||||
l = append(l, HostPref{Host: ipdomain(s), Pref: pref})
|
||||
func ipdomains(s ...string) (l []dns.IPDomain) {
|
||||
for _, e := range s {
|
||||
l = append(l, ipdomain(e))
|
||||
}
|
||||
return l
|
||||
return
|
||||
}
|
||||
|
||||
// Test basic MX lookup case, but also following CNAME, detecting CNAME loops and
|
||||
@ -86,10 +86,10 @@ func TestGatherDestinations(t *testing.T) {
|
||||
resolver.CNAME[s] = next
|
||||
}
|
||||
|
||||
test := func(ipd dns.IPDomain, expHostPrefs []HostPref, expDomain dns.Domain, expPerm, expAuthic, expExpAuthic bool, expErr error) {
|
||||
test := func(ipd dns.IPDomain, expHosts []dns.IPDomain, expDomain dns.Domain, expPerm, expAuthic, expExpAuthic bool, expErr error) {
|
||||
t.Helper()
|
||||
|
||||
_, authic, authicExp, ed, hostPrefs, perm, err := GatherDestinations(ctxbg, log.Logger, resolver, ipd)
|
||||
_, authic, authicExp, ed, hosts, perm, err := GatherDestinations(ctxbg, log.Logger, resolver, ipd)
|
||||
if (err == nil) != (expErr == nil) || err != nil && !errors.Is(err, expErr) {
|
||||
// todo: could also check the individual errors? code currently does not have structured errors.
|
||||
t.Fatalf("gather hosts: %v, expected %v", err, expErr)
|
||||
@ -97,8 +97,8 @@ func TestGatherDestinations(t *testing.T) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(hostPrefs, expHostPrefs) || ed != expDomain || perm != expPerm || authic != expAuthic || authicExp != expExpAuthic {
|
||||
t.Fatalf("got hosts %#v, effectiveDomain %#v, permanent %#v, authic %v %v, expected %#v %#v %#v %v %v", hostPrefs, ed, perm, authic, authicExp, expHostPrefs, expDomain, expPerm, expAuthic, expExpAuthic)
|
||||
if !reflect.DeepEqual(hosts, expHosts) || ed != expDomain || perm != expPerm || authic != expAuthic || authicExp != expExpAuthic {
|
||||
t.Fatalf("got hosts %#v, effectiveDomain %#v, permanent %#v, authic %v %v, expected %#v %#v %#v %v %v", hosts, ed, perm, authic, authicExp, expHosts, expDomain, expPerm, expAuthic, expExpAuthic)
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,18 +108,18 @@ func TestGatherDestinations(t *testing.T) {
|
||||
authic := i == 1
|
||||
resolver.AllAuthentic = authic
|
||||
// Basic with simple MX.
|
||||
test(ipdomain("basic.example"), hostprefs(10, "mail.basic.example"), domain("basic.example"), false, authic, authic, nil)
|
||||
test(ipdomain("multimx.example"), hostprefs(10, "mail1.multimx.example", "mail2.multimx.example"), domain("multimx.example"), false, authic, authic, nil)
|
||||
test(ipdomain("basic.example"), ipdomains("mail.basic.example"), domain("basic.example"), false, authic, authic, nil)
|
||||
test(ipdomain("multimx.example"), ipdomains("mail1.multimx.example", "mail2.multimx.example"), domain("multimx.example"), false, authic, authic, nil)
|
||||
// Only an A record.
|
||||
test(ipdomain("justhost.example"), hostprefs(-1, "justhost.example"), domain("justhost.example"), false, authic, authic, nil)
|
||||
test(ipdomain("justhost.example"), ipdomains("justhost.example"), domain("justhost.example"), false, authic, authic, nil)
|
||||
// Only an AAAA record.
|
||||
test(ipdomain("justhost6.example"), hostprefs(-1, "justhost6.example"), domain("justhost6.example"), false, authic, authic, nil)
|
||||
test(ipdomain("justhost6.example"), ipdomains("justhost6.example"), domain("justhost6.example"), false, authic, authic, nil)
|
||||
// Follow CNAME.
|
||||
test(ipdomain("cname.example"), hostprefs(10, "mail.basic.example"), domain("basic.example"), false, authic, authic, nil)
|
||||
test(ipdomain("cname.example"), ipdomains("mail.basic.example"), domain("basic.example"), false, authic, authic, nil)
|
||||
// No MX/CNAME, non-existence of host will be found out later.
|
||||
test(ipdomain("absent.example"), hostprefs(-1, "absent.example"), domain("absent.example"), false, authic, authic, nil)
|
||||
test(ipdomain("absent.example"), ipdomains("absent.example"), domain("absent.example"), false, authic, authic, nil)
|
||||
// Followed CNAME, has no MX, non-existence of host will be found out later.
|
||||
test(ipdomain("danglingcname.example"), hostprefs(-1, "absent.example"), domain("absent.example"), false, authic, authic, nil)
|
||||
test(ipdomain("danglingcname.example"), ipdomains("absent.example"), domain("absent.example"), false, authic, authic, nil)
|
||||
test(ipdomain("cnamelimit1.example"), nil, zerodom, true, authic, authic, errCNAMELimit)
|
||||
test(ipdomain("cnameloop.example"), nil, zerodom, true, authic, authic, errCNAMELoop)
|
||||
test(ipdomain("nullmx.example"), nil, zerodom, true, authic, authic, errNoMail)
|
||||
@ -127,9 +127,9 @@ func TestGatherDestinations(t *testing.T) {
|
||||
test(ipdomain("temperror-cname.example"), nil, zerodom, false, authic, authic, errDNS)
|
||||
}
|
||||
|
||||
test(ipdomain("10.0.0.1"), hostprefs(-1, "10.0.0.1"), zerodom, false, false, false, nil)
|
||||
test(ipdomain("cnameinauthentic.example"), hostprefs(10, "mail.basic.example"), domain("basic.example"), false, false, false, nil)
|
||||
test(ipdomain("cname-to-inauthentic.example"), hostprefs(10, "mail.basic.example"), domain("basic.example"), false, true, false, nil)
|
||||
test(ipdomain("10.0.0.1"), ipdomains("10.0.0.1"), zerodom, false, false, false, nil)
|
||||
test(ipdomain("cnameinauthentic.example"), ipdomains("mail.basic.example"), domain("basic.example"), false, false, false, nil)
|
||||
test(ipdomain("cname-to-inauthentic.example"), ipdomains("mail.basic.example"), domain("basic.example"), false, true, false, nil)
|
||||
}
|
||||
|
||||
func TestGatherIPs(t *testing.T) {
|
||||
|
@ -113,7 +113,7 @@ func FuzzServer(f *testing.F) {
|
||||
const viaHTTPS = false
|
||||
err := serverConn.SetDeadline(time.Now().Add(time.Second))
|
||||
flog(err, "set server deadline")
|
||||
serve("test", cid, dns.Domain{ASCII: "mox.example"}, nil, serverConn, resolver, submission, false, viaHTTPS, false, 100<<10, false, false, false, nil, 0)
|
||||
serve("test", cid, dns.Domain{ASCII: "mox.example"}, nil, serverConn, resolver, submission, false, viaHTTPS, 100<<10, false, false, false, nil, 0)
|
||||
cid++
|
||||
}
|
||||
|
||||
|
@ -206,14 +206,12 @@ func Listen() {
|
||||
listener := mox.Conf.Static.Listeners[name]
|
||||
|
||||
var tlsConfig, tlsConfigDelivery *tls.Config
|
||||
var noTLSClientAuth bool
|
||||
if listener.TLS != nil {
|
||||
tlsConfig = listener.TLS.Config
|
||||
// For SMTP delivery, if we get a TLS handshake for an SNI hostname that we don't
|
||||
// allow, we'll fallback to a certificate for the listener hostname instead of
|
||||
// causing the connection to fail. May improve interoperability.
|
||||
tlsConfigDelivery = listener.TLS.ConfigFallback
|
||||
noTLSClientAuth = listener.TLS.ClientAuthDisabled
|
||||
}
|
||||
|
||||
maxMsgSize := listener.SMTPMaxMessageSize
|
||||
@ -236,7 +234,7 @@ func Listen() {
|
||||
// https://github.com/golang/go/issues/70232.
|
||||
tlsConfigDelivery.SessionTicketsDisabled = listener.SMTP.TLSSessionTicketsDisabled == nil || *listener.SMTP.TLSSessionTicketsDisabled
|
||||
}
|
||||
listen1("smtp", name, ip, port, hostname, tlsConfigDelivery, false, false, noTLSClientAuth, maxMsgSize, false, listener.SMTP.RequireSTARTTLS, !listener.SMTP.NoRequireTLS, listener.SMTP.DNSBLZones, firstTimeSenderDelay)
|
||||
listen1("smtp", name, ip, port, hostname, tlsConfigDelivery, false, false, maxMsgSize, false, listener.SMTP.RequireSTARTTLS, !listener.SMTP.NoRequireTLS, listener.SMTP.DNSBLZones, firstTimeSenderDelay)
|
||||
}
|
||||
}
|
||||
if listener.Submission.Enabled {
|
||||
@ -246,7 +244,7 @@ func Listen() {
|
||||
}
|
||||
port := config.Port(listener.Submission.Port, 587)
|
||||
for _, ip := range listener.IPs {
|
||||
listen1("submission", name, ip, port, hostname, tlsConfig, true, false, noTLSClientAuth, maxMsgSize, !listener.Submission.NoRequireSTARTTLS, !listener.Submission.NoRequireSTARTTLS, true, nil, 0)
|
||||
listen1("submission", name, ip, port, hostname, tlsConfig, true, false, maxMsgSize, !listener.Submission.NoRequireSTARTTLS, !listener.Submission.NoRequireSTARTTLS, true, nil, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,7 +255,7 @@ func Listen() {
|
||||
}
|
||||
port := config.Port(listener.Submissions.Port, 465)
|
||||
for _, ip := range listener.IPs {
|
||||
listen1("submissions", name, ip, port, hostname, tlsConfig, true, true, noTLSClientAuth, maxMsgSize, true, true, true, nil, 0)
|
||||
listen1("submissions", name, ip, port, hostname, tlsConfig, true, true, maxMsgSize, true, true, true, nil, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -265,7 +263,7 @@ func Listen() {
|
||||
|
||||
var servers []func()
|
||||
|
||||
func listen1(protocol, name, ip string, port int, hostname dns.Domain, tlsConfig *tls.Config, submission, xtls, noTLSClientAuth bool, maxMessageSize int64, requireTLSForAuth, requireTLSForDelivery, requireTLS bool, dnsBLs []dns.Domain, firstTimeSenderDelay time.Duration) {
|
||||
func listen1(protocol, name, ip string, port int, hostname dns.Domain, tlsConfig *tls.Config, submission, xtls bool, maxMessageSize int64, requireTLSForAuth, requireTLSForDelivery, requireTLS bool, dnsBLs []dns.Domain, firstTimeSenderDelay time.Duration) {
|
||||
log := mlog.New("smtpserver", nil)
|
||||
addr := net.JoinHostPort(ip, fmt.Sprintf("%d", port))
|
||||
if os.Getuid() == 0 {
|
||||
@ -299,7 +297,7 @@ func listen1(protocol, name, ip string, port int, hostname dns.Domain, tlsConfig
|
||||
|
||||
// Package is set on the resolver by the dkim/spf/dmarc/etc packages.
|
||||
resolver := dns.StrictResolver{Log: log.Logger}
|
||||
go serve(name, mox.Cid(), hostname, tlsConfig, conn, resolver, submission, xtls, false, noTLSClientAuth, maxMessageSize, requireTLSForAuth, requireTLSForDelivery, requireTLS, dnsBLs, firstTimeSenderDelay)
|
||||
go serve(name, mox.Cid(), hostname, tlsConfig, conn, resolver, submission, xtls, false, maxMessageSize, requireTLSForAuth, requireTLSForDelivery, requireTLS, dnsBLs, firstTimeSenderDelay)
|
||||
}
|
||||
}
|
||||
|
||||
@ -323,11 +321,10 @@ type conn struct {
|
||||
origConn net.Conn
|
||||
conn net.Conn
|
||||
|
||||
tls bool
|
||||
extRequireTLS bool // Whether to announce and allow the REQUIRETLS extension.
|
||||
viaHTTPS bool // Whether the connection came in via the HTTPS port (using TLS ALPN).
|
||||
noTLSClientAuth bool
|
||||
resolver dns.Resolver
|
||||
tls bool
|
||||
extRequireTLS bool // Whether to announce and allow the REQUIRETLS extension.
|
||||
viaHTTPS bool // Whether the connection came in via the HTTPS port (using TLS ALPN).
|
||||
resolver dns.Resolver
|
||||
// The "x" in the readers and writes indicate Read and Write errors use panic to
|
||||
// propagate the error.
|
||||
xbr *bufio.Reader
|
||||
@ -438,7 +435,7 @@ func (c *conn) loginAttempt(useTLS bool, authMech string) store.LoginAttempt {
|
||||
// makeTLSConfig makes a new tls config that is bound to the connection for
|
||||
// possible client certificate authentication in case of submission.
|
||||
func (c *conn) makeTLSConfig() *tls.Config {
|
||||
if !c.submission || c.noTLSClientAuth {
|
||||
if !c.submission {
|
||||
return c.baseTLSConfig
|
||||
}
|
||||
|
||||
@ -543,7 +540,7 @@ func (c *conn) tlsClientAuthVerifyPeerCertParsed(cert *x509.Certificate) error {
|
||||
if err == bstore.ErrAbsent {
|
||||
la.Result = store.AuthBadCredentials
|
||||
}
|
||||
return fmt.Errorf("looking up tls public key with fingerprint %s, subject %q, issuer %q: %v", fp, cert.Subject, cert.Issuer, err)
|
||||
return fmt.Errorf("looking up tls public key with fingerprint %s: %v", fp, err)
|
||||
}
|
||||
la.LoginAddress = pubKey.LoginAddress
|
||||
|
||||
@ -623,7 +620,7 @@ func (c *conn) xtlsHandshakeAndAuthenticate(conn net.Conn) {
|
||||
cancel()
|
||||
|
||||
cs := tlsConn.ConnectionState()
|
||||
if cs.DidResume && len(cs.PeerCertificates) > 0 && !c.noTLSClientAuth {
|
||||
if cs.DidResume && len(cs.PeerCertificates) > 0 {
|
||||
// Verify client after session resumption.
|
||||
err := c.tlsClientAuthVerifyPeerCertParsed(cs.PeerCertificates[0])
|
||||
if err != nil {
|
||||
@ -637,7 +634,6 @@ func (c *conn) xtlsHandshakeAndAuthenticate(conn net.Conn) {
|
||||
slog.String("ciphersuite", ciphersuite),
|
||||
slog.String("sni", cs.ServerName),
|
||||
slog.Bool("resumed", cs.DidResume),
|
||||
slog.Bool("notlsclientauth", c.noTLSClientAuth),
|
||||
slog.Int("clientcerts", len(cs.PeerCertificates)),
|
||||
}
|
||||
if c.account != nil {
|
||||
@ -864,10 +860,10 @@ var cleanClose struct{} // Sentinel value for panic/recover indicating clean clo
|
||||
func ServeTLSConn(listenerName string, hostname dns.Domain, conn *tls.Conn, tlsConfig *tls.Config, submission, viaHTTPS bool, maxMsgSize int64, requireTLS bool) {
|
||||
log := mlog.New("smtpserver", nil)
|
||||
resolver := dns.StrictResolver{Log: log.Logger}
|
||||
serve(listenerName, mox.Cid(), hostname, tlsConfig, conn, resolver, submission, true, viaHTTPS, true, maxMsgSize, true, true, requireTLS, nil, 0)
|
||||
serve(listenerName, mox.Cid(), hostname, tlsConfig, conn, resolver, submission, true, viaHTTPS, maxMsgSize, true, true, requireTLS, nil, 0)
|
||||
}
|
||||
|
||||
func serve(listenerName string, cid int64, hostname dns.Domain, tlsConfig *tls.Config, nc net.Conn, resolver dns.Resolver, submission, xtls, viaHTTPS, noTLSClientAuth bool, maxMessageSize int64, requireTLSForAuth, requireTLSForDelivery, requireTLS bool, dnsBLs []dns.Domain, firstTimeSenderDelay time.Duration) {
|
||||
func serve(listenerName string, cid int64, hostname dns.Domain, tlsConfig *tls.Config, nc net.Conn, resolver dns.Resolver, submission, xtls, viaHTTPS bool, maxMessageSize int64, requireTLSForAuth, requireTLSForDelivery, requireTLS bool, dnsBLs []dns.Domain, firstTimeSenderDelay time.Duration) {
|
||||
var localIP, remoteIP net.IP
|
||||
if a, ok := nc.LocalAddr().(*net.TCPAddr); ok {
|
||||
localIP = a.IP
|
||||
@ -894,7 +890,6 @@ func serve(listenerName string, cid int64, hostname dns.Domain, tlsConfig *tls.C
|
||||
submission: submission,
|
||||
tls: xtls,
|
||||
viaHTTPS: viaHTTPS,
|
||||
noTLSClientAuth: noTLSClientAuth,
|
||||
extRequireTLS: requireTLS,
|
||||
resolver: resolver,
|
||||
lastlog: time.Now(),
|
||||
@ -1214,7 +1209,7 @@ func (c *conn) cmdHello(p *parser, ehlo bool) {
|
||||
// case, or it would trigger the mechanism downgrade detection.
|
||||
mechs = "SCRAM-SHA-256-PLUS SCRAM-SHA-256 SCRAM-SHA-1-PLUS SCRAM-SHA-1 CRAM-MD5 PLAIN LOGIN"
|
||||
}
|
||||
if c.tls && len(c.conn.(*tls.Conn).ConnectionState().PeerCertificates) > 0 && !c.viaHTTPS && !c.noTLSClientAuth {
|
||||
if c.tls && len(c.conn.(*tls.Conn).ConnectionState().PeerCertificates) > 0 && !c.viaHTTPS {
|
||||
mechs = "EXTERNAL " + mechs
|
||||
}
|
||||
c.xbwritelinef("250-AUTH %s", mechs)
|
||||
|
@ -257,7 +257,7 @@ func (ts *testserver) runRaw(fn func(clientConn net.Conn)) {
|
||||
defer func() { <-serverdone }()
|
||||
|
||||
go func() {
|
||||
serve("test", ts.cid-2, dns.Domain{ASCII: "mox.example"}, ts.serverConfig, serverConn, ts.resolver, ts.submission, ts.immediateTLS, false, false, 100<<20, false, false, ts.requiretls, ts.dnsbls, 0)
|
||||
serve("test", ts.cid-2, dns.Domain{ASCII: "mox.example"}, ts.serverConfig, serverConn, ts.resolver, ts.submission, ts.immediateTLS, false, 100<<20, false, false, ts.requiretls, ts.dnsbls, 0)
|
||||
close(serverdone)
|
||||
}()
|
||||
|
||||
@ -476,7 +476,7 @@ func TestSubmission(t *testing.T) {
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{fakeCert(ts.t, false)},
|
||||
}
|
||||
serve("test", ts.cid-2, dns.Domain{ASCII: "mox.example"}, tlsConfig, serverConn, ts.resolver, ts.submission, ts.immediateTLS, false, false, 100<<20, false, false, false, ts.dnsbls, 0)
|
||||
serve("test", ts.cid-2, dns.Domain{ASCII: "mox.example"}, tlsConfig, serverConn, ts.resolver, ts.submission, ts.immediateTLS, false, 100<<20, false, false, false, ts.dnsbls, 0)
|
||||
close(serverdone)
|
||||
}()
|
||||
|
||||
@ -1426,7 +1426,7 @@ func TestNonSMTP(t *testing.T) {
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{fakeCert(ts.t, false)},
|
||||
}
|
||||
serve("test", ts.cid-2, dns.Domain{ASCII: "mox.example"}, tlsConfig, serverConn, ts.resolver, ts.submission, false, false, false, 100<<20, false, false, false, ts.dnsbls, 0)
|
||||
serve("test", ts.cid-2, dns.Domain{ASCII: "mox.example"}, tlsConfig, serverConn, ts.resolver, ts.submission, false, false, 100<<20, false, false, false, ts.dnsbls, 0)
|
||||
close(serverdone)
|
||||
}()
|
||||
|
||||
|
@ -348,7 +348,7 @@ possibly making them potentially no longer readable by the previous version.
|
||||
if du.MessageSize != totalSize {
|
||||
checkf(errors.New(`wrong total message size, see mox recalculatemailboxcounts"`), dbpath, "account has wrong total message size %d, should be %d", du.MessageSize, totalSize)
|
||||
}
|
||||
} else if !errors.Is(err, bstore.ErrAbsent) {
|
||||
} else if err != nil && !errors.Is(err, bstore.ErrAbsent) {
|
||||
checkf(err, dbpath, "get disk usage")
|
||||
}
|
||||
}
|
||||
|
@ -263,7 +263,7 @@ var api;
|
||||
AuthResult["AuthError"] = "error";
|
||||
AuthResult["AuthAborted"] = "aborted";
|
||||
})(AuthResult = api.AuthResult || (api.AuthResult = {}));
|
||||
api.structTypes = { "Account": true, "Address": true, "AddressAlias": true, "Alias": true, "AliasAddress": true, "AuthResults": true, "AutoconfCheckResult": true, "AutodiscoverCheckResult": true, "AutodiscoverSRV": true, "AutomaticJunkFlags": true, "Canonicalization": true, "CheckResult": true, "ClientConfigs": true, "ClientConfigsEntry": true, "ConfigDomain": true, "DANECheckResult": true, "DKIM": true, "DKIMAuthResult": true, "DKIMCheckResult": true, "DKIMRecord": true, "DMARC": true, "DMARCCheckResult": true, "DMARCRecord": true, "DMARCSummary": true, "DNSSECResult": true, "DateRange": true, "Destination": true, "Directive": true, "Domain": true, "DomainFeedback": true, "Dynamic": true, "Evaluation": true, "EvaluationStat": true, "Extension": true, "FailureDetails": true, "Filter": true, "HoldRule": true, "Hook": true, "HookFilter": true, "HookResult": true, "HookRetired": true, "HookRetiredFilter": true, "HookRetiredSort": true, "HookSort": true, "IPDomain": true, "IPRevCheckResult": true, "Identifiers": true, "IncomingWebhook": true, "JunkFilter": true, "LoginAttempt": true, "MTASTS": true, "MTASTSCheckResult": true, "MTASTSRecord": true, "MX": true, "MXCheckResult": true, "Modifier": true, "Msg": true, "MsgResult": true, "MsgRetired": true, "OutgoingWebhook": true, "Pair": true, "Policy": true, "PolicyEvaluated": true, "PolicyOverrideReason": true, "PolicyPublished": true, "PolicyRecord": true, "Record": true, "Report": true, "ReportMetadata": true, "ReportRecord": true, "Result": true, "ResultPolicy": true, "RetiredFilter": true, "RetiredSort": true, "Reverse": true, "Route": true, "Row": true, "Ruleset": true, "SMTPAuth": true, "SPFAuthResult": true, "SPFCheckResult": true, "SPFRecord": true, "SRV": true, "SRVConfCheckResult": true, "STSMX": true, "Selector": true, "Sort": true, "SubjectPass": true, "Summary": true, "SuppressAddress": true, "TLSCheckResult": true, "TLSPublicKey": true, "TLSRPT": true, "TLSRPTCheckResult": true, "TLSRPTDateRange": true, "TLSRPTRecord": true, "TLSRPTSummary": true, "TLSRPTSuppressAddress": true, "TLSReportRecord": true, "TLSResult": true, "Transport": true, "TransportDirect": true, "TransportFail": true, "TransportSMTP": true, "TransportSocks": true, "URI": true, "WebForward": true, "WebHandler": true, "WebInternal": true, "WebRedirect": true, "WebStatic": true, "WebserverConfig": true };
|
||||
api.structTypes = { "Account": true, "Address": true, "AddressAlias": true, "Alias": true, "AliasAddress": true, "AuthResults": true, "AutoconfCheckResult": true, "AutodiscoverCheckResult": true, "AutodiscoverSRV": true, "AutomaticJunkFlags": true, "Canonicalization": true, "CheckResult": true, "ClientConfigs": true, "ClientConfigsEntry": true, "ConfigDomain": true, "DANECheckResult": true, "DKIM": true, "DKIMAuthResult": true, "DKIMCheckResult": true, "DKIMRecord": true, "DMARC": true, "DMARCCheckResult": true, "DMARCRecord": true, "DMARCSummary": true, "DNSSECResult": true, "DateRange": true, "Destination": true, "Directive": true, "Domain": true, "DomainFeedback": true, "Dynamic": true, "Evaluation": true, "EvaluationStat": true, "Extension": true, "FailureDetails": true, "Filter": true, "HoldRule": true, "Hook": true, "HookFilter": true, "HookResult": true, "HookRetired": true, "HookRetiredFilter": true, "HookRetiredSort": true, "HookSort": true, "IPDomain": true, "IPRevCheckResult": true, "Identifiers": true, "IncomingWebhook": true, "JunkFilter": true, "LoginAttempt": true, "MTASTS": true, "MTASTSCheckResult": true, "MTASTSRecord": true, "MX": true, "MXCheckResult": true, "Modifier": true, "Msg": true, "MsgResult": true, "MsgRetired": true, "OutgoingWebhook": true, "Pair": true, "Policy": true, "PolicyEvaluated": true, "PolicyOverrideReason": true, "PolicyPublished": true, "PolicyRecord": true, "Record": true, "Report": true, "ReportMetadata": true, "ReportRecord": true, "Result": true, "ResultPolicy": true, "RetiredFilter": true, "RetiredSort": true, "Reverse": true, "Route": true, "Row": true, "Ruleset": true, "SMTPAuth": true, "SPFAuthResult": true, "SPFCheckResult": true, "SPFRecord": true, "SRV": true, "SRVConfCheckResult": true, "STSMX": true, "Selector": true, "Sort": true, "SubjectPass": true, "Summary": true, "SuppressAddress": true, "TLSCheckResult": true, "TLSPublicKey": true, "TLSRPT": true, "TLSRPTCheckResult": true, "TLSRPTDateRange": true, "TLSRPTRecord": true, "TLSRPTSummary": true, "TLSRPTSuppressAddress": true, "TLSReportRecord": true, "TLSResult": true, "Transport": true, "TransportDirect": true, "TransportSMTP": true, "TransportSocks": true, "URI": true, "WebForward": true, "WebHandler": true, "WebInternal": true, "WebRedirect": true, "WebStatic": true, "WebserverConfig": true };
|
||||
api.stringsTypes = { "Align": true, "AuthResult": true, "CSRFToken": true, "DMARCPolicy": true, "IP": true, "Localpart": true, "Mode": true, "RUA": true };
|
||||
api.intsTypes = {};
|
||||
api.types = {
|
||||
@ -365,12 +365,11 @@ var api;
|
||||
"WebRedirect": { "Name": "WebRedirect", "Docs": "", "Fields": [{ "Name": "BaseURL", "Docs": "", "Typewords": ["string"] }, { "Name": "OrigPathRegexp", "Docs": "", "Typewords": ["string"] }, { "Name": "ReplacePath", "Docs": "", "Typewords": ["string"] }, { "Name": "StatusCode", "Docs": "", "Typewords": ["int32"] }] },
|
||||
"WebForward": { "Name": "WebForward", "Docs": "", "Fields": [{ "Name": "StripPath", "Docs": "", "Typewords": ["bool"] }, { "Name": "URL", "Docs": "", "Typewords": ["string"] }, { "Name": "ResponseHeaders", "Docs": "", "Typewords": ["{}", "string"] }] },
|
||||
"WebInternal": { "Name": "WebInternal", "Docs": "", "Fields": [{ "Name": "BasePath", "Docs": "", "Typewords": ["string"] }, { "Name": "Service", "Docs": "", "Typewords": ["string"] }] },
|
||||
"Transport": { "Name": "Transport", "Docs": "", "Fields": [{ "Name": "Submissions", "Docs": "", "Typewords": ["nullable", "TransportSMTP"] }, { "Name": "Submission", "Docs": "", "Typewords": ["nullable", "TransportSMTP"] }, { "Name": "SMTP", "Docs": "", "Typewords": ["nullable", "TransportSMTP"] }, { "Name": "Socks", "Docs": "", "Typewords": ["nullable", "TransportSocks"] }, { "Name": "Direct", "Docs": "", "Typewords": ["nullable", "TransportDirect"] }, { "Name": "Fail", "Docs": "", "Typewords": ["nullable", "TransportFail"] }] },
|
||||
"Transport": { "Name": "Transport", "Docs": "", "Fields": [{ "Name": "Submissions", "Docs": "", "Typewords": ["nullable", "TransportSMTP"] }, { "Name": "Submission", "Docs": "", "Typewords": ["nullable", "TransportSMTP"] }, { "Name": "SMTP", "Docs": "", "Typewords": ["nullable", "TransportSMTP"] }, { "Name": "Socks", "Docs": "", "Typewords": ["nullable", "TransportSocks"] }, { "Name": "Direct", "Docs": "", "Typewords": ["nullable", "TransportDirect"] }] },
|
||||
"TransportSMTP": { "Name": "TransportSMTP", "Docs": "", "Fields": [{ "Name": "Host", "Docs": "", "Typewords": ["string"] }, { "Name": "Port", "Docs": "", "Typewords": ["int32"] }, { "Name": "STARTTLSInsecureSkipVerify", "Docs": "", "Typewords": ["bool"] }, { "Name": "NoSTARTTLS", "Docs": "", "Typewords": ["bool"] }, { "Name": "Auth", "Docs": "", "Typewords": ["nullable", "SMTPAuth"] }] },
|
||||
"SMTPAuth": { "Name": "SMTPAuth", "Docs": "", "Fields": [{ "Name": "Username", "Docs": "", "Typewords": ["string"] }, { "Name": "Password", "Docs": "", "Typewords": ["string"] }, { "Name": "Mechanisms", "Docs": "", "Typewords": ["[]", "string"] }] },
|
||||
"TransportSocks": { "Name": "TransportSocks", "Docs": "", "Fields": [{ "Name": "Address", "Docs": "", "Typewords": ["string"] }, { "Name": "RemoteIPs", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "RemoteHostname", "Docs": "", "Typewords": ["string"] }] },
|
||||
"TransportDirect": { "Name": "TransportDirect", "Docs": "", "Fields": [{ "Name": "DisableIPv4", "Docs": "", "Typewords": ["bool"] }, { "Name": "DisableIPv6", "Docs": "", "Typewords": ["bool"] }] },
|
||||
"TransportFail": { "Name": "TransportFail", "Docs": "", "Fields": [{ "Name": "SMTPCode", "Docs": "", "Typewords": ["int32"] }, { "Name": "SMTPMessage", "Docs": "", "Typewords": ["string"] }, { "Name": "Code", "Docs": "", "Typewords": ["int32"] }, { "Name": "Message", "Docs": "", "Typewords": ["string"] }] },
|
||||
"EvaluationStat": { "Name": "EvaluationStat", "Docs": "", "Fields": [{ "Name": "Domain", "Docs": "", "Typewords": ["Domain"] }, { "Name": "Dispositions", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Count", "Docs": "", "Typewords": ["int32"] }, { "Name": "SendReport", "Docs": "", "Typewords": ["bool"] }] },
|
||||
"Evaluation": { "Name": "Evaluation", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "PolicyDomain", "Docs": "", "Typewords": ["string"] }, { "Name": "Evaluated", "Docs": "", "Typewords": ["timestamp"] }, { "Name": "Optional", "Docs": "", "Typewords": ["bool"] }, { "Name": "IntervalHours", "Docs": "", "Typewords": ["int32"] }, { "Name": "Addresses", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "PolicyPublished", "Docs": "", "Typewords": ["PolicyPublished"] }, { "Name": "SourceIP", "Docs": "", "Typewords": ["string"] }, { "Name": "Disposition", "Docs": "", "Typewords": ["string"] }, { "Name": "AlignedDKIMPass", "Docs": "", "Typewords": ["bool"] }, { "Name": "AlignedSPFPass", "Docs": "", "Typewords": ["bool"] }, { "Name": "OverrideReasons", "Docs": "", "Typewords": ["[]", "PolicyOverrideReason"] }, { "Name": "EnvelopeTo", "Docs": "", "Typewords": ["string"] }, { "Name": "EnvelopeFrom", "Docs": "", "Typewords": ["string"] }, { "Name": "HeaderFrom", "Docs": "", "Typewords": ["string"] }, { "Name": "DKIMResults", "Docs": "", "Typewords": ["[]", "DKIMAuthResult"] }, { "Name": "SPFResults", "Docs": "", "Typewords": ["[]", "SPFAuthResult"] }] },
|
||||
"SuppressAddress": { "Name": "SuppressAddress", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Inserted", "Docs": "", "Typewords": ["timestamp"] }, { "Name": "ReportingAddress", "Docs": "", "Typewords": ["string"] }, { "Name": "Until", "Docs": "", "Typewords": ["timestamp"] }, { "Name": "Comment", "Docs": "", "Typewords": ["string"] }] },
|
||||
@ -492,7 +491,6 @@ var api;
|
||||
SMTPAuth: (v) => api.parse("SMTPAuth", v),
|
||||
TransportSocks: (v) => api.parse("TransportSocks", v),
|
||||
TransportDirect: (v) => api.parse("TransportDirect", v),
|
||||
TransportFail: (v) => api.parse("TransportFail", v),
|
||||
EvaluationStat: (v) => api.parse("EvaluationStat", v),
|
||||
Evaluation: (v) => api.parse("Evaluation", v),
|
||||
SuppressAddress: (v) => api.parse("SuppressAddress", v),
|
||||
|
@ -6901,14 +6901,6 @@
|
||||
"nullable",
|
||||
"TransportDirect"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Fail",
|
||||
"Docs": "",
|
||||
"Typewords": [
|
||||
"nullable",
|
||||
"TransportFail"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -7030,40 +7022,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "TransportFail",
|
||||
"Docs": "TransportFail is a transport that fails all delivery attempts.",
|
||||
"Fields": [
|
||||
{
|
||||
"Name": "SMTPCode",
|
||||
"Docs": "",
|
||||
"Typewords": [
|
||||
"int32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "SMTPMessage",
|
||||
"Docs": "",
|
||||
"Typewords": [
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Code",
|
||||
"Docs": "Effective values to use, set when parsing.",
|
||||
"Typewords": [
|
||||
"int32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Message",
|
||||
"Docs": "",
|
||||
"Typewords": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "EvaluationStat",
|
||||
"Docs": "EvaluationStat summarizes stored evaluations, for inclusion in an upcoming\naggregate report, for a domain.",
|
||||
|
@ -948,7 +948,6 @@ export interface Transport {
|
||||
SMTP?: TransportSMTP | null
|
||||
Socks?: TransportSocks | null
|
||||
Direct?: TransportDirect | null
|
||||
Fail?: TransportFail | null
|
||||
}
|
||||
|
||||
// TransportSMTP delivers messages by "submission" (SMTP, typically
|
||||
@ -981,14 +980,6 @@ export interface TransportDirect {
|
||||
DisableIPv6: boolean
|
||||
}
|
||||
|
||||
// TransportFail is a transport that fails all delivery attempts.
|
||||
export interface TransportFail {
|
||||
SMTPCode: number
|
||||
SMTPMessage: string
|
||||
Code: number // Effective values to use, set when parsing.
|
||||
Message: string
|
||||
}
|
||||
|
||||
// EvaluationStat summarizes stored evaluations, for inclusion in an upcoming
|
||||
// aggregate report, for a domain.
|
||||
export interface EvaluationStat {
|
||||
@ -1160,7 +1151,7 @@ export enum AuthResult {
|
||||
AuthAborted = "aborted",
|
||||
}
|
||||
|
||||
export const structTypes: {[typename: string]: boolean} = {"Account":true,"Address":true,"AddressAlias":true,"Alias":true,"AliasAddress":true,"AuthResults":true,"AutoconfCheckResult":true,"AutodiscoverCheckResult":true,"AutodiscoverSRV":true,"AutomaticJunkFlags":true,"Canonicalization":true,"CheckResult":true,"ClientConfigs":true,"ClientConfigsEntry":true,"ConfigDomain":true,"DANECheckResult":true,"DKIM":true,"DKIMAuthResult":true,"DKIMCheckResult":true,"DKIMRecord":true,"DMARC":true,"DMARCCheckResult":true,"DMARCRecord":true,"DMARCSummary":true,"DNSSECResult":true,"DateRange":true,"Destination":true,"Directive":true,"Domain":true,"DomainFeedback":true,"Dynamic":true,"Evaluation":true,"EvaluationStat":true,"Extension":true,"FailureDetails":true,"Filter":true,"HoldRule":true,"Hook":true,"HookFilter":true,"HookResult":true,"HookRetired":true,"HookRetiredFilter":true,"HookRetiredSort":true,"HookSort":true,"IPDomain":true,"IPRevCheckResult":true,"Identifiers":true,"IncomingWebhook":true,"JunkFilter":true,"LoginAttempt":true,"MTASTS":true,"MTASTSCheckResult":true,"MTASTSRecord":true,"MX":true,"MXCheckResult":true,"Modifier":true,"Msg":true,"MsgResult":true,"MsgRetired":true,"OutgoingWebhook":true,"Pair":true,"Policy":true,"PolicyEvaluated":true,"PolicyOverrideReason":true,"PolicyPublished":true,"PolicyRecord":true,"Record":true,"Report":true,"ReportMetadata":true,"ReportRecord":true,"Result":true,"ResultPolicy":true,"RetiredFilter":true,"RetiredSort":true,"Reverse":true,"Route":true,"Row":true,"Ruleset":true,"SMTPAuth":true,"SPFAuthResult":true,"SPFCheckResult":true,"SPFRecord":true,"SRV":true,"SRVConfCheckResult":true,"STSMX":true,"Selector":true,"Sort":true,"SubjectPass":true,"Summary":true,"SuppressAddress":true,"TLSCheckResult":true,"TLSPublicKey":true,"TLSRPT":true,"TLSRPTCheckResult":true,"TLSRPTDateRange":true,"TLSRPTRecord":true,"TLSRPTSummary":true,"TLSRPTSuppressAddress":true,"TLSReportRecord":true,"TLSResult":true,"Transport":true,"TransportDirect":true,"TransportFail":true,"TransportSMTP":true,"TransportSocks":true,"URI":true,"WebForward":true,"WebHandler":true,"WebInternal":true,"WebRedirect":true,"WebStatic":true,"WebserverConfig":true}
|
||||
export const structTypes: {[typename: string]: boolean} = {"Account":true,"Address":true,"AddressAlias":true,"Alias":true,"AliasAddress":true,"AuthResults":true,"AutoconfCheckResult":true,"AutodiscoverCheckResult":true,"AutodiscoverSRV":true,"AutomaticJunkFlags":true,"Canonicalization":true,"CheckResult":true,"ClientConfigs":true,"ClientConfigsEntry":true,"ConfigDomain":true,"DANECheckResult":true,"DKIM":true,"DKIMAuthResult":true,"DKIMCheckResult":true,"DKIMRecord":true,"DMARC":true,"DMARCCheckResult":true,"DMARCRecord":true,"DMARCSummary":true,"DNSSECResult":true,"DateRange":true,"Destination":true,"Directive":true,"Domain":true,"DomainFeedback":true,"Dynamic":true,"Evaluation":true,"EvaluationStat":true,"Extension":true,"FailureDetails":true,"Filter":true,"HoldRule":true,"Hook":true,"HookFilter":true,"HookResult":true,"HookRetired":true,"HookRetiredFilter":true,"HookRetiredSort":true,"HookSort":true,"IPDomain":true,"IPRevCheckResult":true,"Identifiers":true,"IncomingWebhook":true,"JunkFilter":true,"LoginAttempt":true,"MTASTS":true,"MTASTSCheckResult":true,"MTASTSRecord":true,"MX":true,"MXCheckResult":true,"Modifier":true,"Msg":true,"MsgResult":true,"MsgRetired":true,"OutgoingWebhook":true,"Pair":true,"Policy":true,"PolicyEvaluated":true,"PolicyOverrideReason":true,"PolicyPublished":true,"PolicyRecord":true,"Record":true,"Report":true,"ReportMetadata":true,"ReportRecord":true,"Result":true,"ResultPolicy":true,"RetiredFilter":true,"RetiredSort":true,"Reverse":true,"Route":true,"Row":true,"Ruleset":true,"SMTPAuth":true,"SPFAuthResult":true,"SPFCheckResult":true,"SPFRecord":true,"SRV":true,"SRVConfCheckResult":true,"STSMX":true,"Selector":true,"Sort":true,"SubjectPass":true,"Summary":true,"SuppressAddress":true,"TLSCheckResult":true,"TLSPublicKey":true,"TLSRPT":true,"TLSRPTCheckResult":true,"TLSRPTDateRange":true,"TLSRPTRecord":true,"TLSRPTSummary":true,"TLSRPTSuppressAddress":true,"TLSReportRecord":true,"TLSResult":true,"Transport":true,"TransportDirect":true,"TransportSMTP":true,"TransportSocks":true,"URI":true,"WebForward":true,"WebHandler":true,"WebInternal":true,"WebRedirect":true,"WebStatic":true,"WebserverConfig":true}
|
||||
export const stringsTypes: {[typename: string]: boolean} = {"Align":true,"AuthResult":true,"CSRFToken":true,"DMARCPolicy":true,"IP":true,"Localpart":true,"Mode":true,"RUA":true}
|
||||
export const intsTypes: {[typename: string]: boolean} = {}
|
||||
export const types: TypenameMap = {
|
||||
@ -1262,12 +1253,11 @@ export const types: TypenameMap = {
|
||||
"WebRedirect": {"Name":"WebRedirect","Docs":"","Fields":[{"Name":"BaseURL","Docs":"","Typewords":["string"]},{"Name":"OrigPathRegexp","Docs":"","Typewords":["string"]},{"Name":"ReplacePath","Docs":"","Typewords":["string"]},{"Name":"StatusCode","Docs":"","Typewords":["int32"]}]},
|
||||
"WebForward": {"Name":"WebForward","Docs":"","Fields":[{"Name":"StripPath","Docs":"","Typewords":["bool"]},{"Name":"URL","Docs":"","Typewords":["string"]},{"Name":"ResponseHeaders","Docs":"","Typewords":["{}","string"]}]},
|
||||
"WebInternal": {"Name":"WebInternal","Docs":"","Fields":[{"Name":"BasePath","Docs":"","Typewords":["string"]},{"Name":"Service","Docs":"","Typewords":["string"]}]},
|
||||
"Transport": {"Name":"Transport","Docs":"","Fields":[{"Name":"Submissions","Docs":"","Typewords":["nullable","TransportSMTP"]},{"Name":"Submission","Docs":"","Typewords":["nullable","TransportSMTP"]},{"Name":"SMTP","Docs":"","Typewords":["nullable","TransportSMTP"]},{"Name":"Socks","Docs":"","Typewords":["nullable","TransportSocks"]},{"Name":"Direct","Docs":"","Typewords":["nullable","TransportDirect"]},{"Name":"Fail","Docs":"","Typewords":["nullable","TransportFail"]}]},
|
||||
"Transport": {"Name":"Transport","Docs":"","Fields":[{"Name":"Submissions","Docs":"","Typewords":["nullable","TransportSMTP"]},{"Name":"Submission","Docs":"","Typewords":["nullable","TransportSMTP"]},{"Name":"SMTP","Docs":"","Typewords":["nullable","TransportSMTP"]},{"Name":"Socks","Docs":"","Typewords":["nullable","TransportSocks"]},{"Name":"Direct","Docs":"","Typewords":["nullable","TransportDirect"]}]},
|
||||
"TransportSMTP": {"Name":"TransportSMTP","Docs":"","Fields":[{"Name":"Host","Docs":"","Typewords":["string"]},{"Name":"Port","Docs":"","Typewords":["int32"]},{"Name":"STARTTLSInsecureSkipVerify","Docs":"","Typewords":["bool"]},{"Name":"NoSTARTTLS","Docs":"","Typewords":["bool"]},{"Name":"Auth","Docs":"","Typewords":["nullable","SMTPAuth"]}]},
|
||||
"SMTPAuth": {"Name":"SMTPAuth","Docs":"","Fields":[{"Name":"Username","Docs":"","Typewords":["string"]},{"Name":"Password","Docs":"","Typewords":["string"]},{"Name":"Mechanisms","Docs":"","Typewords":["[]","string"]}]},
|
||||
"TransportSocks": {"Name":"TransportSocks","Docs":"","Fields":[{"Name":"Address","Docs":"","Typewords":["string"]},{"Name":"RemoteIPs","Docs":"","Typewords":["[]","string"]},{"Name":"RemoteHostname","Docs":"","Typewords":["string"]}]},
|
||||
"TransportDirect": {"Name":"TransportDirect","Docs":"","Fields":[{"Name":"DisableIPv4","Docs":"","Typewords":["bool"]},{"Name":"DisableIPv6","Docs":"","Typewords":["bool"]}]},
|
||||
"TransportFail": {"Name":"TransportFail","Docs":"","Fields":[{"Name":"SMTPCode","Docs":"","Typewords":["int32"]},{"Name":"SMTPMessage","Docs":"","Typewords":["string"]},{"Name":"Code","Docs":"","Typewords":["int32"]},{"Name":"Message","Docs":"","Typewords":["string"]}]},
|
||||
"EvaluationStat": {"Name":"EvaluationStat","Docs":"","Fields":[{"Name":"Domain","Docs":"","Typewords":["Domain"]},{"Name":"Dispositions","Docs":"","Typewords":["[]","string"]},{"Name":"Count","Docs":"","Typewords":["int32"]},{"Name":"SendReport","Docs":"","Typewords":["bool"]}]},
|
||||
"Evaluation": {"Name":"Evaluation","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"PolicyDomain","Docs":"","Typewords":["string"]},{"Name":"Evaluated","Docs":"","Typewords":["timestamp"]},{"Name":"Optional","Docs":"","Typewords":["bool"]},{"Name":"IntervalHours","Docs":"","Typewords":["int32"]},{"Name":"Addresses","Docs":"","Typewords":["[]","string"]},{"Name":"PolicyPublished","Docs":"","Typewords":["PolicyPublished"]},{"Name":"SourceIP","Docs":"","Typewords":["string"]},{"Name":"Disposition","Docs":"","Typewords":["string"]},{"Name":"AlignedDKIMPass","Docs":"","Typewords":["bool"]},{"Name":"AlignedSPFPass","Docs":"","Typewords":["bool"]},{"Name":"OverrideReasons","Docs":"","Typewords":["[]","PolicyOverrideReason"]},{"Name":"EnvelopeTo","Docs":"","Typewords":["string"]},{"Name":"EnvelopeFrom","Docs":"","Typewords":["string"]},{"Name":"HeaderFrom","Docs":"","Typewords":["string"]},{"Name":"DKIMResults","Docs":"","Typewords":["[]","DKIMAuthResult"]},{"Name":"SPFResults","Docs":"","Typewords":["[]","SPFAuthResult"]}]},
|
||||
"SuppressAddress": {"Name":"SuppressAddress","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"Inserted","Docs":"","Typewords":["timestamp"]},{"Name":"ReportingAddress","Docs":"","Typewords":["string"]},{"Name":"Until","Docs":"","Typewords":["timestamp"]},{"Name":"Comment","Docs":"","Typewords":["string"]}]},
|
||||
@ -1390,7 +1380,6 @@ export const parser = {
|
||||
SMTPAuth: (v: any) => parse("SMTPAuth", v) as SMTPAuth,
|
||||
TransportSocks: (v: any) => parse("TransportSocks", v) as TransportSocks,
|
||||
TransportDirect: (v: any) => parse("TransportDirect", v) as TransportDirect,
|
||||
TransportFail: (v: any) => parse("TransportFail", v) as TransportFail,
|
||||
EvaluationStat: (v: any) => parse("EvaluationStat", v) as EvaluationStat,
|
||||
Evaluation: (v: any) => parse("Evaluation", v) as Evaluation,
|
||||
SuppressAddress: (v: any) => parse("SuppressAddress", v) as SuppressAddress,
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"log/slog"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"os"
|
||||
@ -421,7 +422,7 @@ func (s server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
log.Check(werr, "writing error response")
|
||||
}
|
||||
|
||||
la := loginAttempt(remoteIP.String(), r, "webapi", "httpbasic")
|
||||
la := loginAttempt(r, "webapi", "httpbasic")
|
||||
la.LoginAddress = email
|
||||
defer func() {
|
||||
store.LoginAttemptAdd(context.Background(), log, la)
|
||||
@ -529,7 +530,12 @@ func (s server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// loginAttempt initializes a store.LoginAttempt, for adding to the store after
|
||||
// filling in the results and other details.
|
||||
func loginAttempt(remoteIP string, r *http.Request, protocol, authMech string) store.LoginAttempt {
|
||||
func loginAttempt(r *http.Request, protocol, authMech string) store.LoginAttempt {
|
||||
remoteIP, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||
if remoteIP == "" {
|
||||
remoteIP = r.RemoteAddr
|
||||
}
|
||||
|
||||
return store.LoginAttempt{
|
||||
RemoteIP: remoteIP,
|
||||
TLS: store.LoginAttemptTLS(r.TLS),
|
||||
|
@ -80,7 +80,12 @@ type SessionAuth interface {
|
||||
}
|
||||
|
||||
// loginAttempt initializes a loginAttempt, for adding to the store after filling in the results and other details.
|
||||
func loginAttempt(remoteIP string, r *http.Request, protocol, authMech string) store.LoginAttempt {
|
||||
func loginAttempt(r *http.Request, protocol, authMech string) store.LoginAttempt {
|
||||
remoteIP, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||
if remoteIP == "" {
|
||||
remoteIP = r.RemoteAddr
|
||||
}
|
||||
|
||||
return store.LoginAttempt{
|
||||
RemoteIP: remoteIP,
|
||||
TLS: store.LoginAttemptTLS(r.TLS),
|
||||
@ -158,7 +163,7 @@ func Check(ctx context.Context, log mlog.Log, sessionAuth SessionAuth, kind stri
|
||||
return
|
||||
}
|
||||
|
||||
la := loginAttempt(ip.String(), r, kind, "websession")
|
||||
la := loginAttempt(r, kind, "websession")
|
||||
defer func() {
|
||||
store.LoginAttemptAdd(context.Background(), log, la)
|
||||
}()
|
||||
@ -266,7 +271,7 @@ func Login(ctx context.Context, log mlog.Log, sessionAuth SessionAuth, kind, coo
|
||||
|
||||
username = norm.NFC.String(username)
|
||||
valid, disabled, accountName, err := sessionAuth.login(ctx, log, username, password)
|
||||
la := loginAttempt(ip.String(), r, kind, "weblogin")
|
||||
la := loginAttempt(r, kind, "weblogin")
|
||||
la.LoginAddress = username
|
||||
la.AccountName = accountName
|
||||
defer func() {
|
||||
|
@ -1717,7 +1717,7 @@ func recipientSecurity(ctx context.Context, log mlog.Log, resolver dns.Resolver,
|
||||
defer logPanic(ctx)
|
||||
defer wg.Done()
|
||||
|
||||
_, origNextHopAuthentic, expandedNextHopAuthentic, _, hostPrefs, _, err := smtpclient.GatherDestinations(ctx, log.Logger, resolver, dns.IPDomain{Domain: addr.Domain})
|
||||
_, origNextHopAuthentic, expandedNextHopAuthentic, _, hosts, _, err := smtpclient.GatherDestinations(ctx, log.Logger, resolver, dns.IPDomain{Domain: addr.Domain})
|
||||
if err != nil {
|
||||
rs.DNSSEC = SecurityResultError
|
||||
return
|
||||
@ -1734,10 +1734,10 @@ func recipientSecurity(ctx context.Context, log mlog.Log, resolver dns.Resolver,
|
||||
}
|
||||
|
||||
// We're only looking at the first host to deliver to (typically first mx destination).
|
||||
if len(hostPrefs) == 0 || hostPrefs[0].Host.Domain.IsZero() {
|
||||
if len(hosts) == 0 || hosts[0].Domain.IsZero() {
|
||||
return // Should not happen.
|
||||
}
|
||||
host := hostPrefs[0].Host
|
||||
host := hosts[0]
|
||||
|
||||
// Resolve the IPs. Required for DANE to prevent bad DNS servers from causing an
|
||||
// error result instead of no-DANE result.
|
||||
|
Loading…
x
Reference in New Issue
Block a user