diff --git a/client.go b/client.go index aae425c..f0e604f 100644 --- a/client.go +++ b/client.go @@ -550,6 +550,46 @@ func (c *Client) Data() (io.WriteCloser, error) { return &dataCloser{c, c.Text.DotWriter(), nil}, nil } +// SendMail will use an existing connection to send an email from +// address from, to addresses to, with message r. +// +// This function does not start TLS, nor does it perform authentication. Use +// StartTLS and Auth before-hand if desirable. +// +// The addresses in the to parameter are the SMTP RCPT addresses. +// +// The r parameter should be an RFC 822-style email with headers +// first, a blank line, and then the message body. The lines of r +// should be CRLF terminated. The r headers should usually include +// fields such as "From", "To", "Subject", and "Cc". Sending "Bcc" +// messages is accomplished by including an email address in the to +// parameter but not including it in the r headers. +func (c *Client) SendMail(from string, to []string, r io.Reader) error { + var err error + + if err = c.Mail(from, nil); err != nil { + return err + } + for _, addr := range to { + if err = c.Rcpt(addr, nil); err != nil { + return err + } + } + w, err := c.Data() + if err != nil { + return err + } + _, err = io.Copy(w, r) + if err != nil { + return err + } + err = w.Close() + if err != nil { + return err + } + return c.Quit() +} + // LMTPData is the LMTP-specific version of the Data method. It accepts a callback // that will be called for each status response received from the server. // @@ -624,27 +664,7 @@ func SendMail(addr string, a sasl.Client, from string, to []string, r io.Reader) return err } } - if err = c.Mail(from, nil); err != nil { - return err - } - for _, addr := range to { - if err = c.Rcpt(addr, nil); err != nil { - return err - } - } - w, err := c.Data() - if err != nil { - return err - } - _, err = io.Copy(w, r) - if err != nil { - return err - } - err = w.Close() - if err != nil { - return err - } - return c.Quit() + return c.SendMail(from, to, r) } // Extension reports whether an extension is support by the server. diff --git a/conn.go b/conn.go index 6276683..862c0f9 100644 --- a/conn.go +++ b/conn.go @@ -787,6 +787,7 @@ func (c *Conn) handleStartTLS() { session.Logout() c.SetSession(nil) } + c.helo = "" c.didAuth = false c.reset() } diff --git a/go.mod b/go.mod index ae2641e..12b5518 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,14 @@ module github.com/linanh/go-smtp +go 1.15 + require ( github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 github.com/gomodule/redigo v2.0.0+incompatible // indirect - github.com/google/uuid v1.2.0 - github.com/throttled/throttled/v2 v2.9.0 + github.com/google/uuid v1.3.0 + github.com/throttled/throttled/v2 v2.9.1 github.com/txthinking/socks5 v0.0.0-20210716140126-fa1f52a8f2da go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.7.0 // indirect go.uber.org/zap v1.19.0 ) - -go 1.15 diff --git a/go.sum b/go.sum index f1df00b..dda7ab1 100644 --- a/go.sum +++ b/go.sum @@ -21,8 +21,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -46,8 +46,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/throttled/throttled/v2 v2.9.0 h1:DOkCb1el7NYzRoPb1pyeHVghsUoonVWEjmo34vrcp/8= -github.com/throttled/throttled/v2 v2.9.0/go.mod h1:0JHxhGAidPyqbgD4HF8Y1sNFfG0ffVXK6C8EpkNdLEM= +github.com/throttled/throttled/v2 v2.9.1 h1:Es7fBRL04IUOvs4RwbieshgyccyztfaAjzQdKbrpqyo= +github.com/throttled/throttled/v2 v2.9.1/go.mod h1:SxVlv4wUgeS/hWOSMDeb9Ez+stPqP7tWY5wI5BUiGqs= github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf h1:7PflaKRtU4np/epFxRXlFhlzLXZzKFrH5/I4so5Ove0= github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM= github.com/txthinking/socks5 v0.0.0-20210716140126-fa1f52a8f2da h1:7x8pJcBTdKTBpQbRjZZc9o6CDquXBbvm9UIrR6ZSRJ4= @@ -77,7 +77,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11 h1:Yq9t9jnGoR+dBuitxdo9l6Q7xh/zOyNnYUtDKaQ3x0E= diff --git a/server.go b/server.go index 8afa5c1..c154b70 100644 --- a/server.go +++ b/server.go @@ -113,6 +113,8 @@ func (s *Server) Serve(l net.Listener) error { s.listeners = append(s.listeners, l) s.locker.Unlock() + var tempDelay time.Duration // how long to sleep on accept failure + for { c, err := l.Accept() if err != nil { @@ -121,11 +123,29 @@ func (s *Server) Serve(l net.Listener) error { // we called Close() return nil default: - return err } + if ne, ok := err.(net.Error); ok && ne.Temporary() { + if tempDelay == 0 { + tempDelay = 5 * time.Millisecond + } else { + tempDelay *= 2 + } + if max := 1 * time.Second; tempDelay > max { + tempDelay = max + } + s.Logger.Infof("smtp/server accept error: %s; retrying in %s", err, tempDelay) + time.Sleep(tempDelay) + continue + } + return err } - go s.handleConn(newConn(c, s)) + go func() { + err := s.handleConn(newConn(c, s)) + if err != nil { + s.Logger.Infof("smtp/server error: %s", err) + } + }() } } @@ -161,6 +181,19 @@ func (s *Server) handleConn(c *Conn) error { s.locker.Unlock() }() + if tlsConn, ok := c.conn.(*tls.Conn); ok { + if d := s.ReadTimeout; d != 0 { + c.conn.SetReadDeadline(time.Now().Add(d)) + } + if d := s.WriteTimeout; d != 0 { + c.conn.SetWriteDeadline(time.Now().Add(d)) + } + if err := tlsConn.Handshake(); err != nil { + s.Logger.Infof("smtp/server sid=%s reject: TLS handshake error for %s, %s", c.sid, addr, err.Error()) + return err + } + } + c.greet() for {