#105: Add default ports and fallback behavior for SSL and TLS

Introduced default ports for SSL/TLS and STARTTLS connections in SMTP client. Also added fallback behavior, allowing the client to attempt connections on port 25 using plaintext if secured connections fail. Deprecated old methods and implemented new ones to enforce these changes effectively.

This is a copy of the PR muhlemmer:enhance-default-tls-port by @muhlemmer. Since they unfortunately didn't reply in the PR anymore I cloned the PR. They will be fully attributed in the PR, though.
This commit is contained in:
Winni Neessen 2024-01-25 13:39:02 +01:00
parent 161381d880
commit 7a2d9ff938
Signed by: wneessen
GPG key ID: 5F3AF39B820C119D

View file

@ -20,9 +20,15 @@ import (
// Defaults
const (
// DefaultPort is the default connection port cto the SMTP server
// DefaultPort is the default connection port to the SMTP server
DefaultPort = 25
// DefaultPortSSL is the default connection port for SSL/TLS to the SMTP server
DefaultPortSSL = 465
// DefaultPortTLS is the default connection port for STARTTLS to the SMTP server
DefaultPortTLS = 587
// DefaultTimeout is the default connection timeout
DefaultTimeout = time.Second * 15
@ -105,14 +111,15 @@ type Client struct {
// HELO/EHLO string for the greeting the target SMTP server
helo string
// Hostname of the target SMTP server cto connect cto
// Hostname of the target SMTP server to connect to
host string
// pass is the corresponding SMTP AUTH password
pass string
// Port of the SMTP server cto connect cto
// Port of the SMTP server to connect to
port int
fallbackPort int
// sa is a pointer to smtp.Auth
sa smtp.Auth
@ -246,6 +253,8 @@ func WithTimeout(t time.Duration) Option {
}
// WithSSL tells the client to use a SSL/TLS connection
//
// Deprecated: use WithSSLPort instead.
func WithSSL() Option {
return func(c *Client) error {
c.ssl = true
@ -253,6 +262,18 @@ func WithSSL() Option {
}
}
// WithSSLPort tells the client to use a SSL/TLS connection.
// It automatically sets the port to 465.
//
// When the SSL connection fails and fallback is set to true,
// the client will attempt to connect on port 25 using plaintext.
func WithSSLPort(fb bool) Option {
return func(c *Client) error {
c.SetSSLPort(true, fb)
return nil
}
}
// WithDebugLog tells the client to log incoming and outgoing messages of the SMTP client
// to StdErr
func WithDebugLog() Option {
@ -282,6 +303,8 @@ func WithHELO(h string) Option {
}
// WithTLSPolicy tells the client to use the provided TLSPolicy
//
// Deprecated: use WithTLSPortPolicy instead.
func WithTLSPolicy(p TLSPolicy) Option {
return func(c *Client) error {
c.tlspolicy = p
@ -289,6 +312,20 @@ func WithTLSPolicy(p TLSPolicy) Option {
}
}
// WithTLSPortPolicy tells the client to use the provided TLSPolicy,
// The correct port is automatically set.
//
// Port 587 is used for TLSMandatory and TLSOpportunistic.
// If the connection fails with TLSOpportunistic,
// a plaintext connection is attempted on port 25 as a fallback.
// NoTLS will allways use port 25.
func WithTLSPortPolicy(p TLSPolicy) Option {
return func(c *Client) error {
c.SetTLSPortPolicy(p)
return nil
}
}
// WithTLSConfig tells the client to use the provided *tls.Config
func WithTLSConfig(co *tls.Config) Option {
return func(c *Client) error {
@ -430,11 +467,54 @@ func (c *Client) SetTLSPolicy(p TLSPolicy) {
c.tlspolicy = p
}
// SetTLSPortPolicy overrides the current TLSPolicy with the given TLSPolicy
// value. The correct port is automatically set.
//
// Port 587 is used for TLSMandatory and TLSOpportunistic.
// If the connection fails with TLSOpportunistic, a plaintext connection is
// attempted on port 25 as a fallback.
// NoTLS will allways use port 25.
func (c *Client) SetTLSPortPolicy(p TLSPolicy) {
c.port = DefaultPortTLS
if p == TLSOpportunistic {
c.fallbackPort = DefaultPort
}
if p == NoTLS {
c.port = DefaultPort
}
c.tlspolicy = p
}
// SetSSL tells the Client wether to use SSL or not
func (c *Client) SetSSL(s bool) {
c.ssl = s
}
// SetSSLPort tells the Client wether or not to use SSL and fallback.
// The correct port is automatically set.
//
// Port 465 is used when SSL set (true).
// Port 25 is used when SSL is unset (false).
// When the SSL connection fails and fallback is set to true,
// the client will attempt to connect on port 25 using plaintext.
func (c *Client) SetSSLPort(ssl bool, fb bool) {
if ssl {
c.port = DefaultPortSSL
} else {
c.port = DefaultPort
}
if fb {
c.fallbackPort = DefaultPort
} else {
c.fallbackPort = 0
}
c.ssl = ssl
}
// SetDebugLog tells the Client whether debug logging is enabled or not
func (c *Client) SetDebugLog(v bool) {
c.dl = v
@ -507,6 +587,10 @@ func (c *Client) DialWithContext(pc context.Context) error {
}
var err error
c.co, err = c.dialContextFunc(ctx, "tcp", c.ServerAddr())
if err != nil && c.fallbackPort != 0 {
// TODO: should we somehow log or append the previous error?
c.co, err = c.dialContextFunc(ctx, "tcp", c.serverFallbackAddr())
}
if err != nil {
return err
}
@ -606,6 +690,12 @@ func (c *Client) checkConn() error {
return nil
}
// serverFallbackAddr returns the currently set combination of hostname
// and fallback port.
func (c *Client) serverFallbackAddr() string {
return fmt.Sprintf("%s:%d", c.host, c.fallbackPort)
}
// tls tries to make sure that the STARTTLS requirements are satisfied
func (c *Client) tls() error {
if c.co == nil {