Skip to content

Commit 4e0e400

Browse files
committed
client: delay greeting
This makes it so NewClient never blocks, much like tls.Client. This allows callers to have better control over timeouts.
1 parent c28f507 commit 4e0e400

File tree

2 files changed

+44
-73
lines changed

2 files changed

+44
-73
lines changed

client.go

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,7 @@ func Dial(addr string) (*Client, error) {
6262
if err != nil {
6363
return nil, err
6464
}
65-
client, err := NewClient(conn)
66-
if err != nil {
67-
return nil, err
68-
}
65+
client := NewClient(conn)
6966
client.serverName, _, _ = net.SplitHostPort(addr)
7067
return client, nil
7168
}
@@ -83,17 +80,14 @@ func DialTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
8380
if err != nil {
8481
return nil, err
8582
}
86-
client, err := NewClient(conn)
87-
if err != nil {
88-
return nil, err
89-
}
83+
client := NewClient(conn)
9084
client.serverName, _, _ = net.SplitHostPort(addr)
9185
return client, nil
9286
}
9387

9488
// NewClient returns a new Client using an existing connection and host as a
9589
// server name to be used when authenticating.
96-
func NewClient(conn net.Conn) (*Client, error) {
90+
func NewClient(conn net.Conn) *Client {
9791
c := &Client{
9892
localName: "localhost",
9993
// As recommended by RFC 5321. For DATA command reply (3xx one) RFC
@@ -107,31 +101,15 @@ func NewClient(conn net.Conn) (*Client, error) {
107101

108102
c.setConn(conn)
109103

110-
// Initial greeting timeout. RFC 5321 recommends 5 minutes.
111-
c.conn.SetDeadline(time.Now().Add(5 * time.Minute))
112-
defer c.conn.SetDeadline(time.Time{})
113-
114-
_, _, err := c.text.ReadResponse(220)
115-
if err != nil {
116-
c.text.Close()
117-
if protoErr, ok := err.(*textproto.Error); ok {
118-
return nil, toSMTPErr(protoErr)
119-
}
120-
return nil, err
121-
}
122-
123-
return c, nil
104+
return c
124105
}
125106

126107
// NewClientLMTP returns a new LMTP Client (as defined in RFC 2033) using an
127108
// existing connection and host as a server name to be used when authenticating.
128-
func NewClientLMTP(conn net.Conn) (*Client, error) {
129-
c, err := NewClient(conn)
130-
if err != nil {
131-
return nil, err
132-
}
109+
func NewClientLMTP(conn net.Conn) *Client {
110+
c := NewClient(conn)
133111
c.lmtp = true
134-
return c, nil
112+
return c
135113
}
136114

137115
// setConn sets the underlying network connection for the client.
@@ -170,12 +148,30 @@ func (c *Client) Close() error {
170148
return c.text.Close()
171149
}
172150

151+
func (c *Client) greet() error {
152+
// Initial greeting timeout. RFC 5321 recommends 5 minutes.
153+
c.conn.SetDeadline(time.Now().Add(c.CommandTimeout))
154+
defer c.conn.SetDeadline(time.Time{})
155+
156+
_, _, err := c.text.ReadResponse(220)
157+
if err != nil {
158+
c.text.Close()
159+
if protoErr, ok := err.(*textproto.Error); ok {
160+
return toSMTPErr(protoErr)
161+
}
162+
return err
163+
}
164+
165+
return nil
166+
}
167+
173168
// hello runs a hello exchange if needed.
174169
func (c *Client) hello() error {
175170
if !c.didHello {
176171
c.didHello = true
177-
err := c.ehlo()
178-
if err != nil {
172+
if err := c.greet(); err != nil {
173+
c.helloError = err
174+
} else if err := c.ehlo(); err != nil {
179175
c.helloError = c.helo()
180176
}
181177
}

client_test.go

Lines changed: 17 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@ func TestClientAuthTrimSpace(t *testing.T) {
3333
strings.NewReader(server),
3434
&wrote,
3535
}
36-
c, err := NewClient(fake)
37-
if err != nil {
38-
t.Fatalf("NewClient: %v", err)
39-
}
36+
c := NewClient(fake)
4037
c.tls = true
4138
c.didHello = true
4239
c.Auth(toServerEmptyAuth{})
@@ -185,12 +182,9 @@ func TestBasic_SMTPError(t *testing.T) {
185182
strings.NewReader(faultyServer),
186183
&wrote,
187184
}
188-
c, err := NewClient(fake)
189-
if err != nil {
190-
t.Fatalf("NewClient failed: %v", err)
191-
}
185+
c := NewClient(fake)
192186

193-
err = c.Mail("whatever", nil)
187+
err := c.Mail("whatever", nil)
194188
if err == nil {
195189
t.Fatal("MAIL succeeded")
196190
}
@@ -267,12 +261,9 @@ func TestClient_TooLongLine(t *testing.T) {
267261
pr,
268262
&wrote,
269263
}
270-
c, err := NewClient(fake)
271-
if err != nil {
272-
t.Fatalf("NewClient failed: %v", err)
273-
}
264+
c := NewClient(fake)
274265

275-
err = c.Mail("whatever", nil)
266+
err := c.Mail("whatever", nil)
276267
if err != ErrTooLongLine {
277268
t.Fatal("MAIL succeeded or returned a different error:", err)
278269
}
@@ -335,10 +326,7 @@ func TestNewClient(t *testing.T) {
335326
}
336327
var fake faker
337328
fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
338-
c, err := NewClient(fake)
339-
if err != nil {
340-
t.Fatalf("NewClient: %v\n(after %v)", err, out())
341-
}
329+
c := NewClient(fake)
342330
defer c.Close()
343331
if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" {
344332
t.Fatalf("Expected AUTH supported")
@@ -376,10 +364,7 @@ func TestNewClient2(t *testing.T) {
376364
bcmdbuf := bufio.NewWriter(&cmdbuf)
377365
var fake faker
378366
fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
379-
c, err := NewClient(fake)
380-
if err != nil {
381-
t.Fatalf("NewClient: %v", err)
382-
}
367+
c := NewClient(fake)
383368
defer c.Close()
384369
if ok, _ := c.Extension("DSN"); ok {
385370
t.Fatalf("Shouldn't support DSN")
@@ -422,15 +407,12 @@ func TestHello(t *testing.T) {
422407
bcmdbuf := bufio.NewWriter(&cmdbuf)
423408
var fake faker
424409
fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
425-
c, err := NewClient(fake)
426-
if err != nil {
427-
t.Fatalf("NewClient: %v", err)
428-
}
410+
c := NewClient(fake)
429411
defer c.Close()
430412
c.serverName = "fake.host"
431413
c.localName = "customhost"
432-
err = nil
433414

415+
var err error
434416
switch i {
435417
case 0:
436418
err = c.Hello("hostinjection>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n")
@@ -553,15 +535,12 @@ func TestAuthFailed(t *testing.T) {
553535
bcmdbuf := bufio.NewWriter(&cmdbuf)
554536
var fake faker
555537
fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf)
556-
c, err := NewClient(fake)
557-
if err != nil {
558-
t.Fatalf("NewClient: %v", err)
559-
}
538+
c := NewClient(fake)
560539
defer c.Close()
561540

562541
c.tls = true
563542
c.serverName = "smtp.google.com"
564-
err = c.Auth(sasl.NewPlainClient("", "user", "pass"))
543+
err := c.Auth(sasl.NewPlainClient("", "user", "pass"))
565544

566545
if err == nil {
567546
t.Error("Auth: expected error; got none")
@@ -830,7 +809,8 @@ Goodbye.`
830809
}
831810
}
832811

833-
var lmtpServer = `250-localhost at your service
812+
var lmtpServer = `220 localhost Simple Mail Transfer Service Ready
813+
250-localhost at your service
834814
250-SIZE 35651584
835815
250 8BITMIME
836816
250 Sender OK
@@ -856,7 +836,8 @@ QUIT
856836
`
857837

858838
func TestLMTPData(t *testing.T) {
859-
var lmtpServerPartial = `250-localhost at your service
839+
var lmtpServerPartial = `220 localhost Simple Mail Transfer Service Ready
840+
250-localhost at your service
860841
250-SIZE 35651584
861842
250 8BITMIME
862843
250 Sender OK
@@ -951,10 +932,7 @@ func TestClientXtext(t *testing.T) {
951932
strings.NewReader(server),
952933
&wrote,
953934
}
954-
c, err := NewClient(fake)
955-
if err != nil {
956-
t.Fatalf("NewClient: %v", err)
957-
}
935+
c := NewClient(fake)
958936
c.didHello = true
959937
c.ext = map[string]string{"AUTH": "PLAIN", "DSN": ""}
960938
email := "e=mc2@example.com"
@@ -1001,10 +979,7 @@ func TestClientDSN(t *testing.T) {
1001979
strings.NewReader(server),
1002980
&wrote,
1003981
}
1004-
c, err := NewClient(fake)
1005-
if err != nil {
1006-
t.Fatalf("NewClient: %v", err)
1007-
}
982+
c := NewClient(fake)
1008983
c.didHello = true
1009984
c.ext = map[string]string{"DSN": ""}
1010985
c.Mail(dsnEmailRFC822, &MailOptions{

0 commit comments

Comments
 (0)