Skip to content

Commit

Permalink
client: add new functions to specify options
Browse files Browse the repository at this point in the history
Expose a new set of functions to create a client with custom options.

Signed-off-by: Robin Jarry <robin@jarry.cc>
  • Loading branch information
rjarry committed Jul 7, 2024
1 parent b63eede commit 68208fe
Showing 1 changed file with 85 additions and 14 deletions.
99 changes: 85 additions & 14 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,29 @@ type Client struct {
DebugWriter io.Writer
}

// Options used when creating an SMTP client. If a field is left to its zero
// value, the default will be used instead.
type ClientOptions struct {
// the name to use in HELO/EHLO/LHLO (default: "localhost")
LocalName string
// Time to wait for command responses (this includes 3xx reply to DATA)
// (default: 5 minutes)
CommandTimeout time.Duration
// Time to wait for responses after final dot (default 12 minutes).
SubmissionTimeout time.Duration
}

const (
DefaultLocalName = "localhost"
// As recommended by RFC 5321. For DATA command reply (3xx one) RFC
// recommends a slightly shorter timeout but we do not bother
// differentiating these.
DefaultCommandTimeout = 5 * time.Minute
// 10 minutes + 2 minute buffer in case the server is doing transparent
// forwarding and also follows recommended timeouts.
DefaultSubmissionTimeout = 12 * time.Minute
)

// 30 seconds was chosen as it's the same duration as http.DefaultTransport's
// timeout.
var defaultDialer = net.Dialer{Timeout: 30 * time.Second}
Expand All @@ -54,11 +77,17 @@ var defaultDialer = net.Dialer{Timeout: 30 * time.Second}
// This function returns a plaintext connection. To enable TLS, use
// DialStartTLS.
func Dial(addr string) (*Client, error) {
return DialWithOptions(addr, nil)
}

// DialWithOpts returns a new Client connected to an SMTP server at addr using
// custom options instead of the defaults.
func DialWithOptions(addr string, opts *ClientOptions) (*Client, error) {
conn, err := defaultDialer.Dial("tcp", addr)
if err != nil {
return nil, err
}
client := NewClient(conn)
client := NewClientWithOptions(conn, opts)
client.serverName, _, _ = net.SplitHostPort(addr)
return client, nil
}
Expand All @@ -68,6 +97,14 @@ func Dial(addr string) (*Client, error) {
//
// A nil tlsConfig is equivalent to a zero tls.Config.
func DialTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
return DialTLSWithOptions(addr, tlsConfig, nil)
}

// DialTLSWithOpts returns a new Client connected to an SMTP server via TLS at
// addr using custom options instead of the defaults.
func DialTLSWithOptions(
addr string, tlsConfig *tls.Config, opts *ClientOptions,
) (*Client, error) {
tlsDialer := tls.Dialer{
NetDialer: &defaultDialer,
Config: tlsConfig,
Expand All @@ -76,7 +113,7 @@ func DialTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
if err != nil {
return nil, err
}
client := NewClient(conn)
client := NewClientWithOptions(conn, opts)
client.serverName, _, _ = net.SplitHostPort(addr)
return client, nil
}
Expand All @@ -86,7 +123,15 @@ func DialTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
//
// A nil tlsConfig is equivalent to a zero tls.Config.
func DialStartTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
c, err := Dial(addr)
return DialStartTLSWithOptions(addr, tlsConfig, nil)
}

// DialStartTLSWithOpts retruns a new Client connected to an SMTP server via
// STARTTLS at addr using custom options instead of the defaults.
func DialStartTLSWithOptions(
addr string, tlsConfig *tls.Config, opts *ClientOptions,
) (*Client, error) {
c, err := DialWithOptions(addr, opts)
if err != nil {
return nil, err
}
Expand All @@ -100,25 +145,45 @@ func DialStartTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
// NewClient returns a new Client using an existing connection and host as a
// server name to be used when authenticating.
func NewClient(conn net.Conn) *Client {
return NewClientWithOptions(conn, nil)
}

// NewClient returns a new Client using an existing connection using custom
// options instead of the defaults.
func NewClientWithOptions(conn net.Conn, opts *ClientOptions) *Client {
if opts == nil {
opts = new(ClientOptions)
}
c := &Client{
localName: "localhost",
// As recommended by RFC 5321. For DATA command reply (3xx one) RFC
// recommends a slightly shorter timeout but we do not bother
// differentiating these.
CommandTimeout: 5 * time.Minute,
// 10 minutes + 2 minute buffer in case the server is doing transparent
// forwarding and also follows recommended timeouts.
SubmissionTimeout: 12 * time.Minute,
localName: opts.LocalName,
CommandTimeout: opts.CommandTimeout,
SubmissionTimeout: opts.SubmissionTimeout,
}
if c.localName == "" {
c.localName = DefaultLocalName
}
if c.CommandTimeout == 0 {
c.CommandTimeout = DefaultCommandTimeout
}
if c.SubmissionTimeout == 0 {
c.SubmissionTimeout = DefaultSubmissionTimeout
}

c.setConn(conn)

return c
}

// NewClientStartTLS creates a new Client and performs a STARTTLS command.
func NewClientStartTLS(conn net.Conn, tlsConfig *tls.Config) (*Client, error) {
c := NewClient(conn)
return NewClientStartTLSWithOptions(conn, tlsConfig, nil)
}

// NewClientStartTLS creates a new Client and performs a STARTTLS command.
// It allows using custom options instead of the defaults.
func NewClientStartTLSWithOptions(
conn net.Conn, tlsConfig *tls.Config, opts *ClientOptions,
) (*Client, error) {
c := NewClientWithOptions(conn, opts)
if err := initStartTLS(c, tlsConfig); err != nil {
c.Close()
return nil, err
Expand All @@ -142,7 +207,13 @@ func initStartTLS(c *Client, tlsConfig *tls.Config) error {
// NewClientLMTP returns a new LMTP Client (as defined in RFC 2033) using an
// existing connection and host as a server name to be used when authenticating.
func NewClientLMTP(conn net.Conn) *Client {
c := NewClient(conn)
return NewClientLMTPWithOptions(conn, nil)
}

// NewClientLMTP returns a new LMTP Client (as defined in RFC 2033) using
// custom options instead of the defaults.
func NewClientLMTPWithOptions(conn net.Conn, opts *ClientOptions) *Client {
c := NewClientWithOptions(conn, opts)
c.lmtp = true
return c
}
Expand Down

0 comments on commit 68208fe

Please sign in to comment.