GODT-2070: Implement SASL login for SMTP
go-smtp now comes with out of the box support for SASL PLAIN but it still requires manual implementation of SASL LOGIN (deprecated).
This commit is contained in:
parent
59278913ca
commit
31fb878bbd
|
@ -38,6 +38,7 @@ Proton Mail Bridge includes the following 3rd party software:
|
|||
* [go-imap](https://github.com/emersion/go-imap) available under [license](https://github.com/emersion/go-imap/blob/master/LICENSE)
|
||||
* [go-imap-id](https://github.com/emersion/go-imap-id) available under [license](https://github.com/emersion/go-imap-id/blob/master/LICENSE)
|
||||
* [go-message](https://github.com/emersion/go-message) available under [license](https://github.com/emersion/go-message/blob/master/LICENSE)
|
||||
* [go-sasl](https://github.com/emersion/go-sasl) available under [license](https://github.com/emersion/go-sasl/blob/master/LICENSE)
|
||||
* [go-smtp](https://github.com/emersion/go-smtp) available under [license](https://github.com/emersion/go-smtp/blob/master/LICENSE)
|
||||
* [color](https://github.com/fatih/color) available under [license](https://github.com/fatih/color/blob/master/LICENSE)
|
||||
* [sentry-go](https://github.com/getsentry/sentry-go) available under [license](https://github.com/getsentry/sentry-go/blob/master/LICENSE)
|
||||
|
@ -85,7 +86,6 @@ Proton Mail Bridge includes the following 3rd party software:
|
|||
* [wincred](https://github.com/danieljoos/wincred) available under [license](https://github.com/danieljoos/wincred/blob/master/LICENSE)
|
||||
* [go-spew](https://github.com/davecgh/go-spew) available under [license](https://github.com/davecgh/go-spew/blob/master/LICENSE)
|
||||
* [go-windows](https://github.com/elastic/go-windows) available under [license](https://github.com/elastic/go-windows/blob/master/LICENSE)
|
||||
* [go-sasl](https://github.com/emersion/go-sasl) available under [license](https://github.com/emersion/go-sasl/blob/master/LICENSE)
|
||||
* [go-textwrapper](https://github.com/emersion/go-textwrapper) available under [license](https://github.com/emersion/go-textwrapper/blob/master/LICENSE)
|
||||
* [go-vcard](https://github.com/emersion/go-vcard) available under [license](https://github.com/emersion/go-vcard/blob/master/LICENSE)
|
||||
* [go-shlex](https://github.com/flynn-archive/go-shlex) available under [license](https://github.com/flynn-archive/go-shlex/blob/master/LICENSE)
|
||||
|
|
4
go.mod
4
go.mod
|
@ -20,7 +20,8 @@ require (
|
|||
github.com/emersion/go-imap v1.2.1-0.20220429085312-746087b7a317
|
||||
github.com/emersion/go-imap-id v0.0.0-20190926060100-f94a56b9ecde
|
||||
github.com/emersion/go-message v0.16.0
|
||||
github.com/emersion/go-smtp v0.15.1-0.20221018181223-201c9ab124e4
|
||||
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead
|
||||
github.com/emersion/go-smtp v0.15.1-0.20221021114529-49b17434419d
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/getsentry/sentry-go v0.13.0
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
|
@ -70,7 +71,6 @@ require (
|
|||
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/elastic/go-windows v1.0.1 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead // indirect
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
|
||||
github.com/emersion/go-vcard v0.0.0-20220507122617-d4056df0ec4a // indirect
|
||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -126,8 +126,8 @@ github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu
|
|||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y=
|
||||
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-smtp v0.15.1-0.20221018181223-201c9ab124e4 h1:KGRcxZDpW5w18HFaoOwC9oDKE/M2F2lkB1PtK4gsmgc=
|
||||
github.com/emersion/go-smtp v0.15.1-0.20221018181223-201c9ab124e4/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||
github.com/emersion/go-smtp v0.15.1-0.20221021114529-49b17434419d h1:hFRM6zCBSc+Xa0rBOqSlG6Qe9dKC/2vLhGAuZlWxTsc=
|
||||
github.com/emersion/go-smtp v0.15.1-0.20221021114529-49b17434419d/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||
github.com/emersion/go-vcard v0.0.0-20220507122617-d4056df0ec4a h1:cltZpe6s0SJtqK5c/5y2VrIYi8BAtDM6qjmiGYqfTik=
|
||||
|
|
|
@ -19,8 +19,10 @@ package bridge_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -28,6 +30,8 @@ import (
|
|||
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/client"
|
||||
"github.com/emersion/go-sasl"
|
||||
"github.com/emersion/go-smtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gitlab.protontech.ch/go/liteapi"
|
||||
"gitlab.protontech.ch/go/liteapi/server"
|
||||
|
@ -52,47 +56,60 @@ func TestBridge_Send(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
// Send an email from sender to recipient.
|
||||
smtpClient, err := smtp.Dial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetSMTPPort()))
|
||||
// Dial the server.
|
||||
client, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
|
||||
require.NoError(t, err)
|
||||
defer smtpClient.Close() //nolint:errcheck
|
||||
defer client.Close() //nolint:errcheck
|
||||
|
||||
require.NoError(t, smtpClient.Auth(smtp.PlainAuth("", senderInfo.Addresses[0], string(senderInfo.BridgePass), constants.Host)))
|
||||
require.NoError(t, smtpClient.Mail(senderInfo.Addresses[0]))
|
||||
require.NoError(t, smtpClient.Rcpt("recipient@pm.me"))
|
||||
// Upgrade to TLS.
|
||||
require.NoError(t, client.StartTLS(&tls.Config{InsecureSkipVerify: true}))
|
||||
|
||||
wc, err := smtpClient.Data()
|
||||
require.NoError(t, err)
|
||||
if i%2 == 0 {
|
||||
// Authorize with SASL PLAIN.
|
||||
require.NoError(t, client.Auth(sasl.NewPlainClient(
|
||||
senderInfo.Addresses[0],
|
||||
senderInfo.Addresses[0],
|
||||
string(senderInfo.BridgePass)),
|
||||
))
|
||||
} else {
|
||||
// Authorize with SASL LOGIN.
|
||||
require.NoError(t, client.Auth(sasl.NewLoginClient(
|
||||
senderInfo.Addresses[0],
|
||||
string(senderInfo.BridgePass)),
|
||||
))
|
||||
}
|
||||
|
||||
n, err := fmt.Fprintf(wc, "Subject: Test %v\r\n\r\nHello world!", i)
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, n, 0)
|
||||
require.NoError(t, wc.Close())
|
||||
|
||||
// Sender should see the message in the Sent folder.
|
||||
senderIMAPClient, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, senderIMAPClient.Login(senderInfo.Addresses[0], string(senderInfo.BridgePass)))
|
||||
defer senderIMAPClient.Logout() //nolint:errcheck
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
status, err := senderIMAPClient.Status(`Sent`, []imap.StatusItem{imap.StatusMessages})
|
||||
require.NoError(t, err)
|
||||
return status.Messages == uint32(i+1)
|
||||
}, 10*time.Second, 100*time.Millisecond)
|
||||
|
||||
// Recipient should see the message in the Inbox.
|
||||
recipientIMAPClient, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, recipientIMAPClient.Login(recipientInfo.Addresses[0], string(recipientInfo.BridgePass)))
|
||||
defer recipientIMAPClient.Logout() //nolint:errcheck
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
status, err := recipientIMAPClient.Status(`Inbox`, []imap.StatusItem{imap.StatusMessages})
|
||||
require.NoError(t, err)
|
||||
return status.Messages == uint32(i+1)
|
||||
}, 10*time.Second, 100*time.Millisecond)
|
||||
// Send the message.
|
||||
require.NoError(t, client.SendMail(
|
||||
senderInfo.Addresses[0],
|
||||
[]string{recipientInfo.Addresses[0]},
|
||||
strings.NewReader(fmt.Sprintf("Subject: Test %v\r\n\r\nHello world!", i)),
|
||||
))
|
||||
}
|
||||
|
||||
// Connect the sender IMAP client.
|
||||
senderIMAPClient, err := client.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, senderIMAPClient.Login(senderInfo.Addresses[0], string(senderInfo.BridgePass)))
|
||||
defer senderIMAPClient.Logout() //nolint:errcheck
|
||||
|
||||
// Connect the recipient IMAP client.
|
||||
recipientIMAPClient, err := client.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, recipientIMAPClient.Login(recipientInfo.Addresses[0], string(recipientInfo.BridgePass)))
|
||||
defer recipientIMAPClient.Logout() //nolint:errcheck
|
||||
|
||||
// Sender should have 10 messages in the sent folder.
|
||||
// Recipient should have 0 messages in inbox.
|
||||
require.Eventually(t, func() bool {
|
||||
sent, err := senderIMAPClient.Status(`Sent`, []imap.StatusItem{imap.StatusMessages})
|
||||
require.NoError(t, err)
|
||||
|
||||
inbox, err := recipientIMAPClient.Status(`Inbox`, []imap.StatusItem{imap.StatusMessages})
|
||||
require.NoError(t, err)
|
||||
|
||||
return sent.Messages == 10 && inbox.Messages == 10
|
||||
}, 10*time.Second, 100*time.Millisecond)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/ProtonMail/proton-bridge/v2/internal/logging"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
|
||||
"github.com/emersion/go-sasl"
|
||||
"github.com/emersion/go-smtp"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
@ -95,6 +96,13 @@ func newSMTPServer(bridge *Bridge, tlsConfig *tls.Config, logSMTP bool) *smtp.Se
|
|||
smtpServer.MaxLineLength = 1 << 16
|
||||
smtpServer.ErrorLog = logging.NewSMTPLogger()
|
||||
|
||||
// go-smtp suppors SASL PLAIN but not LOGIN. We need to add LOGIN support ourselves.
|
||||
smtpServer.EnableAuth(sasl.Login, func(conn *smtp.Conn) sasl.Server {
|
||||
return sasl.NewLoginServer(func(username, password string) error {
|
||||
return conn.Session().AuthPlain(username, password)
|
||||
})
|
||||
})
|
||||
|
||||
if logSMTP {
|
||||
log := logrus.WithField("protocol", "SMTP")
|
||||
log.Warning("================================================")
|
||||
|
|
Loading…
Reference in New Issue