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

WIP: client: use standard port when setting SSL/TLS #106

Closed
wants to merge 1 commit into from
Closed
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
100 changes: 97 additions & 3 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,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

Expand Down Expand Up @@ -107,8 +113,9 @@ type Client struct {
// pass is the corresponding SMTP AUTH password
pass string

// Port of the SMTP server cto connect cto
port int
// Port of the SMTP server to connect to
port int
fallbackPort int

// sa is a pointer to smtp.Auth
sa smtp.Auth
Expand Down Expand Up @@ -236,13 +243,27 @@ 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
return nil
}
}

// 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(fallback bool) Option {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a cosmetical "issue". So far we've tried to follow the Go "best practices" for variable names. So for consistency I suggest f instead of fallback

return func(c *Client) error {
c.SetSSLPort(true, fallback)
return nil
}
}

// WithDebugLog tells the client to log incoming and outgoing messages of the SMTP client
// to StdErr
func WithDebugLog() Option {
Expand All @@ -264,13 +285,29 @@ 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
return nil
}
}

// 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 {
Expand Down Expand Up @@ -394,6 +431,12 @@ func (c *Client) TLSPolicy() string {
return c.tlspolicy.String()
}

// 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)
}

// ServerAddr returns the currently set combination of hostname and port
func (c *Client) ServerAddr() string {
return fmt.Sprintf("%s:%d", c.host, c.port)
Expand All @@ -404,11 +447,54 @@ func (c *Client) SetTLSPolicy(p TLSPolicy) {
c.tlspolicy = p
}

// SetTLSPolicy 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, fallback bool) {
if ssl {
c.port = DefaultPortSSL
} else {
c.port = DefaultPort
}

if fallback {
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
Expand Down Expand Up @@ -473,6 +559,14 @@ func (c *Client) DialWithContext(pc context.Context) error {
if !c.ssl {
c.co, err = nd.DialContext(ctx, "tcp", c.ServerAddr())
}

if err != nil && c.fallbackPort != 0 {
ctx, cfn := context.WithTimeout(pc, c.cto)
defer cfn()

// TODO: should we somehow log or append the previous error?
c.co, err = nd.DialContext(ctx, "tcp", c.serverFallbackAddr())
}
if err != nil {
return err
}
Expand Down