From 4585a53be897257cc39b8e872e55b9538fa331d6 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Tue, 15 Mar 2022 21:10:03 +0100 Subject: [PATCH] Added error to Options so we can check inputs --- client.go | 66 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/client.go b/client.go index a5200c3..f3b3d47 100644 --- a/client.go +++ b/client.go @@ -23,6 +23,10 @@ const ( // DefaultTLSPolicy is the default STARTTLS policy DefaultTLSPolicy = TLSMandatory + + // DefaultTLSMinVersion is the minimum TLS version required for the connection + // Nowadays TLS1.2 should be the sane default + DefaultTLSMinVersion = tls.VersionTLS12 ) // Client is the SMTP client struct @@ -71,9 +75,21 @@ type Client struct { } // Option returns a function that can be used for grouping Client options -type Option func(*Client) +type Option func(*Client) error var ( + // ErrInvalidPort should be used if a port is specified that is not valid + ErrInvalidPort = errors.New("invalid port number") + + // ErrInvalidTimeout should be used if a timeout is set that is zero or negative + ErrInvalidTimeout = errors.New("timeout cannot be zero or negative") + + // ErrInvalidHELO should be used if an empty HELO sting is provided + ErrInvalidHELO = errors.New("invalid HELO/EHLO value - must not be empty") + + // ErrInvalidTLSConfig should be used if an empty tls.Config is provided + ErrInvalidTLSConfig = errors.New("invalid TLS config") + // ErrNoHostname should be used if a Client has no hostname set ErrNoHostname = errors.New("hostname for client cannot be empty") @@ -95,7 +111,7 @@ func NewClient(h string, o ...Option) (*Client, error) { cto: DefaultTimeout, host: h, port: DefaultPort, - tlsconfig: &tls.Config{ServerName: h}, + tlsconfig: &tls.Config{ServerName: h, MinVersion: DefaultTLSMinVersion}, tlspolicy: DefaultTLSPolicy, } @@ -109,7 +125,9 @@ func NewClient(h string, o ...Option) (*Client, error) { if co == nil { continue } - co(c) + if err := co(c); err != nil { + return c, fmt.Errorf("failed to apply option: %w", err) + } } // Some settings in a Client cannot be empty/unset @@ -123,71 +141,93 @@ func NewClient(h string, o ...Option) (*Client, error) { // WithPort overrides the default connection port func WithPort(p int) Option { - return func(c *Client) { + return func(c *Client) error { + if p < 1 || p > 65535 { + return ErrInvalidPort + } c.port = p + return nil } } // WithTimeout overrides the default connection timeout func WithTimeout(t time.Duration) Option { - return func(c *Client) { + return func(c *Client) error { + if t <= 0 { + return ErrInvalidTimeout + } c.cto = t + return nil } } // WithSSL tells the client to use a SSL/TLS connection func WithSSL() Option { - return func(c *Client) { + return func(c *Client) error { c.ssl = true + return nil } } // WithHELO tells the client to use the provided string as HELO/EHLO greeting host func WithHELO(h string) Option { - return func(c *Client) { + return func(c *Client) error { + if h == "" { + return ErrInvalidHELO + } c.helo = h + return nil } } // WithTLSPolicy tells the client to use the provided TLSPolicy func WithTLSPolicy(p TLSPolicy) Option { - return func(c *Client) { + return func(c *Client) error { c.tlspolicy = p + return nil } } // WithTLSConfig tells the client to use the provided *tls.Config func WithTLSConfig(co *tls.Config) Option { - return func(c *Client) { + return func(c *Client) error { + if co == nil { + return ErrInvalidTLSConfig + } c.tlsconfig = co + return nil } } // WithSMTPAuth tells the client to use the provided SMTPAuthType for authentication func WithSMTPAuth(t SMTPAuthType) Option { - return func(c *Client) { + return func(c *Client) error { c.satype = t + return nil } } // WithSMTPAuthCustom tells the client to use the provided smtp.Auth for SMTP authentication func WithSMTPAuthCustom(a smtp.Auth) Option { - return func(c *Client) { + return func(c *Client) error { c.sa = a + return nil } } // WithUsername tells the client to use the provided string as username for authentication func WithUsername(u string) Option { - return func(c *Client) { + return func(c *Client) error { c.user = u + return nil } } // WithPassword tells the client to use the provided string as password/secret for authentication func WithPassword(p string) Option { - return func(c *Client) { + return func(c *Client) error { c.pass = p + return nil } }