diff --git a/internal/smtpproxy/LICENSE b/internal/smtpproxy/LICENSE new file mode 100644 index 0000000..0d50487 --- /dev/null +++ b/internal/smtpproxy/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 emersion + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/internal/smtpproxy/backend.go b/internal/smtpproxy/backend.go new file mode 100644 index 0000000..8c864c5 --- /dev/null +++ b/internal/smtpproxy/backend.go @@ -0,0 +1,137 @@ +package smtpproxy + +import ( + "crypto/tls" + "errors" + "net" + + "github.com/emersion/go-sasl" + "github.com/emersion/go-smtp" +) + +type Security int + +const ( + SecurityTLS Security = iota + SecurityStartTLS + SecurityNone +) + +type Backend struct { + Addr string + Security Security + TLSConfig *tls.Config + LMTP bool + Host string + LocalName string + + unexported struct{} +} + +func New(addr string) *Backend { + return &Backend{Addr: addr, Security: SecurityStartTLS} +} + +func NewTLS(addr string, tlsConfig *tls.Config) *Backend { + return &Backend{ + Addr: addr, + Security: SecurityTLS, + TLSConfig: tlsConfig, + } +} + +func NewLMTP(addr string, host string) *Backend { + return &Backend{ + Addr: addr, + Security: SecurityNone, + LMTP: true, + Host: host, + } +} + +func (be *Backend) newConn() (*smtp.Client, error) { + var conn net.Conn + var err error + if be.LMTP { + if be.Security != SecurityNone { + return nil, errors.New("smtp-proxy: LMTP doesn't support TLS") + } + conn, err = net.Dial("unix", be.Addr) + } else if be.Security == SecurityTLS { + conn, err = tls.Dial("tcp", be.Addr, be.TLSConfig) + } else { + conn, err = net.Dial("tcp", be.Addr) + } + if err != nil { + return nil, err + } + + var c *smtp.Client + if be.LMTP { + c, err = smtp.NewClientLMTP(conn, be.Host) + } else { + host := be.Host + if host == "" { + host, _, _ = net.SplitHostPort(be.Addr) + } + c, err = smtp.NewClient(conn, host) + } + if err != nil { + return nil, err + } + + if be.LocalName != "" { + err = c.Hello(be.LocalName) + if err != nil { + return nil, err + } + } + + if be.Security == SecurityStartTLS { + if err := c.StartTLS(be.TLSConfig); err != nil { + return nil, err + } + } + + return c, nil +} + +func (be *Backend) login(username, password string) (*smtp.Client, error) { + c, err := be.newConn() + if err != nil { + return nil, err + } + + auth := sasl.NewPlainClient("", username, password) + if err := c.Auth(auth); err != nil { + return nil, err + } + + return c, nil +} + +func (be *Backend) Login(_ *smtp.Conn, username, password string) (smtp.Session, error) { + c, err := be.login(username, password) + if err != nil { + return nil, err + } + + s := &session{ + c: c, + be: be, + } + return s, nil +} + +func (be *Backend) AnonymousLogin(state *smtp.Conn) (smtp.Session, error) { + c, err := be.newConn() + if err != nil { + return nil, err + } + + s := &session{ + c: c, + be: be, + } + return s, nil +} diff --git a/internal/smtpproxy/session.go b/internal/smtpproxy/session.go new file mode 100644 index 0000000..9c3e8e2 --- /dev/null +++ b/internal/smtpproxy/session.go @@ -0,0 +1,48 @@ +package smtpproxy + +import ( + "fmt" + "io" + + "github.com/emersion/go-smtp" +) + +type session struct { + c *smtp.Client + be *Backend +} + +func (s *session) Reset() { + s.c.Reset() +} + +func (s *session) Mail(from string, opts *smtp.MailOptions) error { + return s.c.Mail(from, opts) +} + +func (s *session) Rcpt(to string) error { + return s.c.Rcpt(to) +} + +func (s *session) Data(r io.Reader) error { + wc, err := s.c.Data() + if err != nil { + return err + } + + _, err = io.Copy(wc, r) + if err != nil { + wc.Close() + return err + } + + return wc.Close() +} + +func (s *session) Logout() error { + return s.c.Quit() +} + +func (s *session) AuthPlain(username, password string) error { + return fmt.Errorf("AUTH not implemented") +}