Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

target/remote: Add smtp_ports option for remote targets #767

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dist/vim/syntax/maddy-conf.vim
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ syn keyword maddyModDir
\ sig_expiry
\ sign_fields
\ sign_subdomains
\ smtp_ports
\ softfail_action
\ SOME_action
\ source
Expand Down
50 changes: 38 additions & 12 deletions docs/reference/targets/remote.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ target.remote {
```

### hostname _domain_

Default: global directive value

Hostname to use client greeting (EHLO/HELO command). Some servers require it to
Expand All @@ -25,6 +26,7 @@ address, so it is better to set it to a domain that resolves to the server IP.
---

### limits { ... }

Default: no limits

See ['limits' directive for SMTP endpoint](/reference/endpoints/smtp/#rate-concurrency-limiting).
Expand All @@ -33,14 +35,16 @@ per-source/per-destination are as observed when message exits the server.

---

### local_ip _ip-address_
### local*ip \_ip-address*

Default: empty

Choose the local IP to bind for outbound SMTP connections.

---

### force_ipv4 _boolean_
### force*ipv4 \_boolean*

Default: `false`

Force resolving outbound SMTP domains to IPv4 addresses. Some server providers
Expand All @@ -53,7 +57,17 @@ Warning: this may break sending outgoing mail to IPv6-only SMTP servers.

---

### connect_timeout _duration_
### smtp_ports `string1 [ string2 [... stringN ]]`

Default: `25`

List of ports to try for outgoing SMTP connections, in the order to be
attempted.

---

### connect*timeout \_duration*

Default: `5m`

Timeout for TCP connection establishment.
Expand All @@ -66,7 +80,8 @@ configures the former. The latter is not configurable and is hardcoded to be

---

### command_timeout _duration_
### command*timeout \_duration*

Default: `5m`

Timeout for any SMTP command (EHLO, MAIL, RCPT, DATA, etc).
Expand All @@ -78,7 +93,8 @@ DATA.

---

### submission_timeout _duration_
### submission*timeout \_duration*

Default: `12m`

Time to wait after the entire message is sent (after "final dot").
Expand All @@ -88,13 +104,15 @@ RFC 5321 recommends 10 minutes.
---

### debug _boolean_

Default: global directive value

Enable verbose logging.

---

### requiretls_override _boolean_
### requiretls*override \_boolean*

Default: `true`

Allow local security policy to be disabled using 'TLS-Required' header field in
Expand All @@ -104,7 +122,8 @@ to take effect (e.g. message should be queued using 'queue' module).

---

### relaxed_requiretls _boolean_
### relaxed*requiretls \_boolean*

Default: `true`

This option disables strict conformance with REQUIRETLS specification and
Expand All @@ -116,22 +135,25 @@ there is only need to secure communication towards it and not beyond.

---

### conn_reuse_limit _integer_
### conn*reuse_limit \_integer*

Default: `10`

Amount of times the same SMTP connection can be used.
Connections are never reused if the previous DATA command failed.

---

### conn_max_idle_count _integer_
### conn*max_idle_count \_integer*

Default: `10`

Max. amount of idle connections per recipient domains to keep in cache.

---

### conn_max_idle_time _integer_
### conn*max_idle_time \_integer*

Default: `150` (2.5 min)

Amount of time the idle connection is still considered potentially usable.
Expand All @@ -141,6 +163,7 @@ Amount of time the idle connection is still considered potentially usable.
## Security policies

### mx_auth { ... }

Default: no policies

'remote' module implements a number of of schemes and protocols necessary to
Expand Down Expand Up @@ -215,6 +238,7 @@ mtasts {
```

### cache `fs` | `ram`

Default: `fs`

Storage to use for MTA-STS cache. 'fs' is to use a filesystem directory, 'ram'
Expand All @@ -224,7 +248,8 @@ It is recommended to use 'fs' since that will not discard the cache (and thus
cause MTA-STS security to disappear) on server restart. However, using the RAM
cache can make sense for high-load configurations with good uptime.

### fs_dir _directory_
### fs*dir \_directory*

Default: `StateDirectory/mtasts_cache`

Filesystem directory to use for policies caching if 'cache' is set to 'fs'.
Expand Down Expand Up @@ -280,16 +305,17 @@ local_policy {
Using `local_policy off` is equivalent to setting both directives to `none`.

### min_tls_level `none` | `encrypted` | `authenticated`

Default: `encrypted`

Set the minimal TLS security level required for all outbound messages.

See [Security levels](/seclevels) page for details.

### min_mx_level `none` | `mtasts` | `dnssec`

Default: `none`

Set the minimal MX security level required for all outbound messages.

See [Security levels](/seclevels) page for details.

38 changes: 30 additions & 8 deletions internal/target/remote/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ func isVerifyError(err error) bool {
// - tlsLevel TLS security level that was estabilished.
// - tlsErr Error that prevented TLS from working if tlsLevel != TLSAuthenticated
func (rd *remoteDelivery) connect(ctx context.Context, conn mxConn, host string, tlsCfg *tls.Config) (tlsLevel module.TLSLevel, tlsErr, err error) {
return rd.connectPort(ctx, conn, host, smtpPort, tlsCfg)
}

// connectPort attempts to connect to the MX, first trying STARTTLS with X.509
// verification but falling back to unauthenticated TLS or plaintext as
// necessary.
//
// Return values:
// - tlsLevel TLS security level that was estabilished.
// - tlsErr Error that prevented TLS from working if tlsLevel != TLSAuthenticated
func (rd *remoteDelivery) connectPort(ctx context.Context, conn mxConn, host string, port string, tlsCfg *tls.Config) (tlsLevel module.TLSLevel, tlsErr, err error) {
tlsLevel = module.TLSAuthenticated
if rd.rt.tlsConfig != nil {
tlsCfg = rd.rt.tlsConfig.Clone()
Expand All @@ -96,7 +107,7 @@ retry:
// TLS errors separately hence starttls=false.
_, err = conn.Connect(ctx, config.Endpoint{
Host: host,
Port: smtpPort,
Port: port,
}, false, nil)
if err != nil {
return module.TLSNone, nil, err
Expand Down Expand Up @@ -151,6 +162,10 @@ retry:
}

func (rd *remoteDelivery) attemptMX(ctx context.Context, conn *mxConn, record *net.MX) error {
return rd.attemptMXWithPort(ctx, conn, record, smtpPort)
}

func (rd *remoteDelivery) attemptMXWithPort(ctx context.Context, conn *mxConn, record *net.MX, port string) error {
mxLevel := module.MXNone

connCtx, cancel := context.WithCancel(ctx)
Expand All @@ -169,7 +184,7 @@ func (rd *remoteDelivery) attemptMX(ctx context.Context, conn *mxConn, record *n
p.PrepareConn(ctx, record.Host)
}

tlsLevel, tlsErr, err := rd.connect(connCtx, *conn, record.Host, rd.rt.tlsConfig)
tlsLevel, tlsErr, err := rd.connectPort(connCtx, *conn, record.Host, port, rd.rt.tlsConfig)
if err != nil {
return err
}
Expand Down Expand Up @@ -316,7 +331,12 @@ func (rd *remoteDelivery) newConn(ctx context.Context, domain string) (*mxConn,
conn.dnssecOk = dnssecOk

var lastErr error
ports := rd.rt.smtpPorts
if len(ports) == 0 {
ports = []string{smtpPort}
}
region = trace.StartRegion(ctx, "remote/Connect+TLS")
recordsLoop:
for _, record := range records {
if record.Host == "." {
return nil, &exterrors.SMTPError{
Expand All @@ -326,14 +346,16 @@ func (rd *remoteDelivery) newConn(ctx context.Context, domain string) (*mxConn,
}
}

if err := rd.attemptMX(ctx, &conn, record); err != nil {
if len(records) != 0 {
rd.Log.Error("cannot use MX", err, "remote_server", record.Host, "domain", domain)
for _, port := range ports {
if err := rd.attemptMXWithPort(ctx, &conn, record, port); err != nil {
if len(records) != 0 {
rd.Log.Error("cannot use MX", err, "remote_server", record.Host, "remote_port", port, "domain", domain)
}
lastErr = err
continue
}
lastErr = err
continue
break recordsLoop
}
break
}
region.End()

Expand Down
3 changes: 3 additions & 0 deletions internal/target/remote/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ type Target struct {
ipv4 bool
tlsConfig *tls.Config

smtpPorts []string

resolver dns.Resolver
dialer func(ctx context.Context, network, addr string) (net.Conn, error)
extResolver *dns.ExtResolver
Expand Down Expand Up @@ -112,6 +114,7 @@ func (rt *Target) Configure(inlineArgs []string, cfg *config.Map) error {
cfg.String("local_ip", false, false, "", &rt.localIP)
cfg.Bool("force_ipv4", false, false, &rt.ipv4)
cfg.Bool("debug", true, false, &rt.Log.Debug)
cfg.StringList("smtp_ports", false, false, []string{smtpPort}, &rt.smtpPorts)
cfg.Custom("tls_client", true, false, func() (interface{}, error) {
return &tls.Config{}, nil
}, tls2.TLSClientBlock, &rt.tlsConfig)
Expand Down