@@ -14,6 +14,7 @@ import (
14
14
"net/textproto"
15
15
"strconv"
16
16
"strings"
17
+ "time"
17
18
18
19
"github.com/emersion/go-sasl"
19
20
)
@@ -38,6 +39,12 @@ type Client struct {
38
39
didHello bool // whether we've said HELO/EHLO/LHLO
39
40
helloError error // the error from the hello
40
41
rcpts []string // recipients accumulated for the current session
42
+
43
+ // Time to wait for command responses (this includes 3xx reply to DATA).
44
+ CommandTimeout time.Duration
45
+
46
+ // Time to wait for responses after final dot.
47
+ SubmissionTimeout time.Duration
41
48
}
42
49
43
50
// Dial returns a new Client connected to an SMTP server at addr.
@@ -89,7 +96,20 @@ func NewClient(conn net.Conn, host string) (*Client, error) {
89
96
return nil , err
90
97
}
91
98
_ , isTLS := conn .(* tls.Conn )
92
- c := & Client {Text : text , conn : conn , serverName : host , localName : "localhost" , tls : isTLS }
99
+ c := & Client {
100
+ Text : text ,
101
+ conn : conn ,
102
+ serverName : host ,
103
+ localName : "localhost" ,
104
+ tls : isTLS ,
105
+ // As recommended by RFC 5321. For DATA command reply (3xx one) RFC
106
+ // recommends a slightly shorter timeout but we do not bother
107
+ // differentiating these.
108
+ CommandTimeout : 5 * time .Minute ,
109
+ // 10 minutes + 2 minute buffer in case the server is doing transparent
110
+ // forwarding and also follows recommended timeouts.
111
+ SubmissionTimeout : 12 * time .Minute ,
112
+ }
93
113
return c , nil
94
114
}
95
115
@@ -142,6 +162,9 @@ func (c *Client) Hello(localName string) error {
142
162
// cmd is a convenience function that sends a command and returns the response
143
163
// textproto.Error returned by c.Text.ReadResponse is converted into SMTPError.
144
164
func (c * Client ) cmd (expectCode int , format string , args ... interface {}) (int , string , error ) {
165
+ c .conn .SetDeadline (time .Now ().Add (c .CommandTimeout ))
166
+ defer c .conn .SetDeadline (time.Time {})
167
+
145
168
id , err := c .Text .Cmd (format , args ... )
146
169
if err != nil {
147
170
return 0 , "" , err
@@ -174,6 +197,7 @@ func (c *Client) ehlo() error {
174
197
if c .lmtp {
175
198
cmd = "LHLO"
176
199
}
200
+
177
201
_ , msg , err := c .cmd (250 , "%s %s" , cmd , c .localName )
178
202
if err != nil {
179
203
return err
@@ -375,6 +399,10 @@ type dataCloser struct {
375
399
376
400
func (d * dataCloser ) Close () error {
377
401
d .WriteCloser .Close ()
402
+
403
+ d .c .conn .SetDeadline (time .Now ().Add (d .c .SubmissionTimeout ))
404
+ defer d .c .conn .SetDeadline (time.Time {})
405
+
378
406
expectedResponses := len (d .c .rcpts )
379
407
if d .c .lmtp {
380
408
for expectedResponses > 0 {
0 commit comments