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 4, 2024
1 parent b63eede commit cd7fde0
Showing 1 changed file with 73 additions and 14 deletions.
87 changes: 73 additions & 14 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,26 @@ type Client struct {
DebugWriter io.Writer
}

type ClientOptions struct {
// the name to use in HELO/EHLO/LHLO
LocalName string
// Time to wait for command responses (this includes 3xx reply to DATA).
CommandTimeout time.Duration
// Time to wait for responses after final dot.
SubmissionTimeout time.Duration
}

var DefaultOpts = ClientOptions{
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,
}

// 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 +74,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 DialWithOpts(addr, nil)
}

// DialWithOpts returns a new Client connected to an SMTP server at addr using
// custom options instead of the defaults.
func DialWithOpts(addr string, opts *ClientOptions) (*Client, error) {
conn, err := defaultDialer.Dial("tcp", addr)
if err != nil {
return nil, err
}
client := NewClient(conn)
client := NewClientWithOpts(conn, opts)
client.serverName, _, _ = net.SplitHostPort(addr)
return client, nil
}
Expand All @@ -68,6 +94,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 DialTLSWithOpts(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 DialTLSWithOpts(
addr string, tlsConfig *tls.Config, opts *ClientOptions,
) (*Client, error) {
tlsDialer := tls.Dialer{
NetDialer: &defaultDialer,
Config: tlsConfig,
Expand All @@ -76,7 +110,7 @@ func DialTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
if err != nil {
return nil, err
}
client := NewClient(conn)
client := NewClientWithOpts(conn, opts)
client.serverName, _, _ = net.SplitHostPort(addr)
return client, nil
}
Expand All @@ -86,7 +120,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 DialStartTLSWithOpts(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 DialStartTLSWithOpts(
addr string, tlsConfig *tls.Config, opts *ClientOptions,
) (*Client, error) {
c, err := DialWithOpts(addr, opts)
if err != nil {
return nil, err
}
Expand All @@ -100,25 +142,36 @@ 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 NewClientWithOpts(conn, nil)
}

// NewClient returns a new Client using an existing connection using custom
// options instead of the defaults.
func NewClientWithOpts(conn net.Conn, opts *ClientOptions) *Client {
if opts == nil {
opts = &DefaultOpts
}
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,
}

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 NewClientStartTLSWithOpts(conn, tlsConfig, nil)
}

// NewClientStartTLS creates a new Client and performs a STARTTLS command.
// It allows using custom options instead of the defaults.
func NewClientStartTLSWithOpts(
conn net.Conn, tlsConfig *tls.Config, opts *ClientOptions,
) (*Client, error) {
c := NewClientWithOpts(conn, opts)
if err := initStartTLS(c, tlsConfig); err != nil {
c.Close()
return nil, err
Expand All @@ -142,7 +195,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 NewClientLMTPWithOpts(conn, nil)
}

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

0 comments on commit cd7fde0

Please sign in to comment.