mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 16:33:47 +03:00
smtpclient: handle server closing connection after writing its response to RCPT TO
if icloud.com has your ip blocklisted, it will close the smtp connection after writing a response to RCPT TO, before writing a response to a pipelined DATA command. this is similar to the case (already handled) where a mail server would close the connection after a response to MAIL FROM when pipelined. we now recognize this situation (unexpected EOF before we get a response to DATA, with all RCPT TO's failed), and treat the last response to RCPT TO as the result. for issue #198 by soheilpro, thanks for reporting and sending an smtpclient trace that showed the behaviour.
This commit is contained in:
parent
c16162eebc
commit
17346d6def
2 changed files with 36 additions and 0 deletions
|
@ -1265,6 +1265,15 @@ func (c *Client) DeliverMultiple(ctx context.Context, mailFrom string, rcptTo []
|
||||||
c.xbotchf(0, "", "", nil, "writing pipelined mail/rcpt/data: %w", writeerr)
|
c.xbotchf(0, "", "", nil, "writing pipelined mail/rcpt/data: %w", writeerr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If remote closed the connection before writing a DATA response, and the RCPT
|
||||||
|
// TO's failed (e.g. after deciding we're on a blocklist), use the last response
|
||||||
|
// for a rcptto as result.
|
||||||
|
if dataerr != nil && errors.Is(dataerr, io.ErrUnexpectedEOF) && nok == 0 {
|
||||||
|
c.botched = true
|
||||||
|
r := rcptResps[len(rcptResps)-1]
|
||||||
|
c.xerrorf(r.Permanent, r.Code, r.Secode, r.Line, r.MoreLines, "%w: server closed connection just before responding to data command", ErrStatus)
|
||||||
|
}
|
||||||
|
|
||||||
// If the data command had an i/o or protocol error, it's also a failure for the
|
// If the data command had an i/o or protocol error, it's also a failure for the
|
||||||
// entire transaction.
|
// entire transaction.
|
||||||
if dataerr != nil {
|
if dataerr != nil {
|
||||||
|
|
|
@ -758,6 +758,33 @@ func TestErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Remote closes connection after 554 response to RCPT TO in pipelined
|
||||||
|
// connection. Should result in permanent error, not temporary read error.
|
||||||
|
// E.g. icloud.com that has your IP blocklisted.
|
||||||
|
run(t, func(s xserver) {
|
||||||
|
s.writeline("220 mox.example")
|
||||||
|
s.readline("EHLO")
|
||||||
|
s.writeline("250-mox.example")
|
||||||
|
s.writeline("250-ENHANCEDSTATUSCODES")
|
||||||
|
s.writeline("250 PIPELINING")
|
||||||
|
s.readline("MAIL FROM:")
|
||||||
|
s.writeline("250 2.1.0 ok")
|
||||||
|
s.readline("RCPT TO:")
|
||||||
|
s.writeline("554 5.7.0 Blocked")
|
||||||
|
}, func(conn net.Conn) {
|
||||||
|
c, err := New(ctx, log.Logger, conn, TLSOpportunistic, false, localhost, zerohost, Opts{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := ""
|
||||||
|
err = c.Deliver(ctx, "postmaster@other.example", "mjl@mox.example", int64(len(msg)), strings.NewReader(msg), false, false, false)
|
||||||
|
var xerr Error
|
||||||
|
if err == nil || !errors.Is(err, ErrStatus) || !errors.As(err, &xerr) || !xerr.Permanent {
|
||||||
|
panic(fmt.Errorf("got %#v, expected ErrStatus with Permanent", err))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// If we try multiple recipients and first is 452, it is an error and a
|
// If we try multiple recipients and first is 452, it is an error and a
|
||||||
// non-pipelined deliver will be aborted.
|
// non-pipelined deliver will be aborted.
|
||||||
run(t, func(s xserver) {
|
run(t, func(s xserver) {
|
||||||
|
|
Loading…
Reference in a new issue