forked from Silverfish/proton-bridge
Other: Bump go-smtp version to fix race condition
There was a race condition internal to the go-smtp library. In order to fix it, a version bump was necessary. However, this significantly changed the library interface.
This commit is contained in:
@ -38,7 +38,6 @@ 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](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-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-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)
|
* [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)
|
* [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)
|
* [sentry-go](https://github.com/getsentry/sentry-go) available under [license](https://github.com/getsentry/sentry-go/blob/master/LICENSE)
|
||||||
@ -84,6 +83,7 @@ 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)
|
* [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-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-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-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-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)
|
* [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,8 +20,7 @@ require (
|
|||||||
github.com/emersion/go-imap v1.2.1-0.20220429085312-746087b7a317
|
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-imap-id v0.0.0-20190926060100-f94a56b9ecde
|
||||||
github.com/emersion/go-message v0.16.0
|
github.com/emersion/go-message v0.16.0
|
||||||
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead
|
github.com/emersion/go-smtp v0.15.1-0.20221018181223-201c9ab124e4
|
||||||
github.com/emersion/go-smtp v0.15.0
|
|
||||||
github.com/fatih/color v1.13.0
|
github.com/fatih/color v1.13.0
|
||||||
github.com/getsentry/sentry-go v0.13.0
|
github.com/getsentry/sentry-go v0.13.0
|
||||||
github.com/go-resty/resty/v2 v2.7.0
|
github.com/go-resty/resty/v2 v2.7.0
|
||||||
@ -69,6 +68,7 @@ require (
|
|||||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/elastic/go-windows v1.0.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-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
|
||||||
github.com/emersion/go-vcard v0.0.0-20220507122617-d4056df0ec4a // indirect
|
github.com/emersion/go-vcard v0.0.0-20220507122617-d4056df0ec4a // indirect
|
||||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // 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-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 h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y=
|
||||||
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
github.com/emersion/go-smtp v0.15.0 h1:3+hMGMGrqP/lqd7qoxZc1hTU8LY8gHV9RFGWlqSDmP8=
|
github.com/emersion/go-smtp v0.15.1-0.20221018181223-201c9ab124e4 h1:KGRcxZDpW5w18HFaoOwC9oDKE/M2F2lkB1PtK4gsmgc=
|
||||||
github.com/emersion/go-smtp v0.15.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
github.com/emersion/go-smtp v0.15.1-0.20221018181223-201c9ab124e4/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 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
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=
|
github.com/emersion/go-vcard v0.0.0-20220507122617-d4056df0ec4a h1:cltZpe6s0SJtqK5c/5y2VrIYi8BAtDM6qjmiGYqfTik=
|
||||||
|
|||||||
@ -64,8 +64,7 @@ type Bridge struct {
|
|||||||
imapListener net.Listener
|
imapListener net.Listener
|
||||||
|
|
||||||
// smtpServer is the bridge's SMTP server.
|
// smtpServer is the bridge's SMTP server.
|
||||||
smtpServer *smtp.Server
|
smtpServer *smtp.Server
|
||||||
smtpBackend *smtpBackend
|
|
||||||
|
|
||||||
// updater is the bridge's updater.
|
// updater is the bridge's updater.
|
||||||
updater Updater
|
updater Updater
|
||||||
@ -131,8 +130,6 @@ func New( //nolint:funlen
|
|||||||
return nil, nil, fmt.Errorf("failed to get Gluon directory: %w", err)
|
return nil, nil, fmt.Errorf("failed to get Gluon directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
smtpBackend := newSMTPBackend()
|
|
||||||
|
|
||||||
imapServer, err := newIMAPServer(gluonDir, curVersion, tlsConfig, logIMAPClient, logIMAPServer)
|
imapServer, err := newIMAPServer(gluonDir, curVersion, tlsConfig, logIMAPClient, logIMAPServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to create IMAP server: %w", err)
|
return nil, nil, fmt.Errorf("failed to create IMAP server: %w", err)
|
||||||
@ -159,7 +156,6 @@ func New( //nolint:funlen
|
|||||||
// Service stuff
|
// Service stuff
|
||||||
tlsConfig,
|
tlsConfig,
|
||||||
imapServer,
|
imapServer,
|
||||||
smtpBackend,
|
|
||||||
focusService,
|
focusService,
|
||||||
|
|
||||||
// Logging stuff
|
// Logging stuff
|
||||||
@ -191,11 +187,10 @@ func newBridge(
|
|||||||
|
|
||||||
tlsConfig *tls.Config,
|
tlsConfig *tls.Config,
|
||||||
imapServer *gluon.Server,
|
imapServer *gluon.Server,
|
||||||
smtpBackend *smtpBackend,
|
|
||||||
focusService *focus.Service,
|
focusService *focus.Service,
|
||||||
logIMAPClient, logIMAPServer, logSMTP bool,
|
logIMAPClient, logIMAPServer, logSMTP bool,
|
||||||
) *Bridge {
|
) *Bridge {
|
||||||
return &Bridge{
|
bridge := &Bridge{
|
||||||
vault: vault,
|
vault: vault,
|
||||||
|
|
||||||
users: safe.NewMap[string, *user.User](nil),
|
users: safe.NewMap[string, *user.User](nil),
|
||||||
@ -205,10 +200,8 @@ func newBridge(
|
|||||||
proxyCtl: proxyCtl,
|
proxyCtl: proxyCtl,
|
||||||
identifier: identifier,
|
identifier: identifier,
|
||||||
|
|
||||||
tlsConfig: tlsConfig,
|
tlsConfig: tlsConfig,
|
||||||
imapServer: imapServer,
|
imapServer: imapServer,
|
||||||
smtpServer: newSMTPServer(smtpBackend, tlsConfig, logSMTP),
|
|
||||||
smtpBackend: smtpBackend,
|
|
||||||
|
|
||||||
updater: updater,
|
updater: updater,
|
||||||
curVersion: curVersion,
|
curVersion: curVersion,
|
||||||
@ -226,6 +219,10 @@ func newBridge(
|
|||||||
|
|
||||||
stopCh: make(chan struct{}),
|
stopCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bridge.smtpServer = newSMTPServer(&smtpBackend{bridge}, tlsConfig, logSMTP)
|
||||||
|
|
||||||
|
return bridge
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) init(tlsReporter TLSReporter) error {
|
func (bridge *Bridge) init(tlsReporter TLSReporter) error {
|
||||||
|
|||||||
@ -26,7 +26,6 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v2/internal/logging"
|
"github.com/ProtonMail/proton-bridge/v2/internal/logging"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
|
||||||
"github.com/emersion/go-sasl"
|
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -67,7 +66,7 @@ func (bridge *Bridge) restartSMTP() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.smtpServer = newSMTPServer(bridge.smtpBackend, bridge.tlsConfig, bridge.logSMTP)
|
bridge.smtpServer = newSMTPServer(&smtpBackend{bridge}, bridge.tlsConfig, bridge.logSMTP)
|
||||||
|
|
||||||
return bridge.serveSMTP()
|
return bridge.serveSMTP()
|
||||||
}
|
}
|
||||||
@ -99,18 +98,5 @@ func newSMTPServer(smtpBackend *smtpBackend, tlsConfig *tls.Config, shouldLog bo
|
|||||||
smtpServer.Debug = logging.NewSMTPDebugLogger()
|
smtpServer.Debug = logging.NewSMTPDebugLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
smtpServer.EnableAuth(sasl.Login, func(conn *smtp.Conn) sasl.Server {
|
|
||||||
return sasl.NewLoginServer(func(address, password string) error {
|
|
||||||
user, err := conn.Server().Backend.Login(nil, address, password)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.SetSession(user)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return smtpServer
|
return smtpServer
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,69 +18,82 @@
|
|||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/user"
|
"github.com/ProtonMail/proton-bridge/v2/internal/user"
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type smtpBackend struct {
|
type smtpBackend struct {
|
||||||
users map[string]*user.User
|
bridge *Bridge
|
||||||
usersLock sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSMTPBackend() *smtpBackend {
|
type smtpSession struct {
|
||||||
return &smtpBackend{
|
bridge *Bridge
|
||||||
users: make(map[string]*user.User),
|
|
||||||
}
|
userID string
|
||||||
|
authID string
|
||||||
|
|
||||||
|
from string
|
||||||
|
to []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (backend *smtpBackend) Login(_ *smtp.ConnectionState, email, password string) (smtp.Session, error) {
|
func (be *smtpBackend) NewSession(_ *smtp.Conn) (smtp.Session, error) {
|
||||||
backend.usersLock.RLock()
|
return &smtpSession{
|
||||||
defer backend.usersLock.RUnlock()
|
bridge: be.bridge,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
for _, user := range backend.users {
|
func (s *smtpSession) AuthPlain(username, password string) error {
|
||||||
session, err := user.NewSMTPSession(email, []byte(password))
|
return s.bridge.users.ValuesErr(func(users []*user.User) error {
|
||||||
if err != nil {
|
for _, user := range users {
|
||||||
continue
|
addrID, err := user.CheckAuth(username, []byte(password))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
s.userID = user.ID()
|
||||||
|
s.authID = addrID
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return session, nil
|
return fmt.Errorf("invalid username or password")
|
||||||
}
|
})
|
||||||
|
|
||||||
return nil, ErrNoSuchUser
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (backend *smtpBackend) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) {
|
func (s *smtpSession) Reset() {
|
||||||
return nil, ErrNotImplemented
|
s.from = ""
|
||||||
|
s.to = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addUser adds the given user to the backend.
|
func (s *smtpSession) Logout() error {
|
||||||
// It returns an error if a user with the same ID already exists.
|
s.Reset()
|
||||||
func (backend *smtpBackend) addUser(newUser *user.User) error {
|
return nil
|
||||||
backend.usersLock.Lock()
|
}
|
||||||
defer backend.usersLock.Unlock()
|
|
||||||
|
|
||||||
if _, ok := backend.users[newUser.ID()]; ok {
|
func (s *smtpSession) Mail(from string, opts *smtp.MailOptions) error {
|
||||||
return ErrUserAlreadyExists
|
s.from = from
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *smtpSession) Rcpt(to string) error {
|
||||||
|
if len(to) > 0 {
|
||||||
|
s.to = append(s.to, to)
|
||||||
}
|
}
|
||||||
|
|
||||||
backend.users[newUser.ID()] = newUser
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeUser removes the given user from the backend.
|
func (s *smtpSession) Data(r io.Reader) error {
|
||||||
// It returns an error if the user doesn't exist.
|
if ok, err := s.bridge.users.GetErr(s.userID, func(user *user.User) error {
|
||||||
func (backend *smtpBackend) removeUser(user *user.User) error {
|
return user.SendMail(s.authID, s.from, s.to, r)
|
||||||
backend.usersLock.Lock()
|
}); !ok {
|
||||||
defer backend.usersLock.Unlock()
|
return fmt.Errorf("no such user %q", s.userID)
|
||||||
|
} else if err != nil {
|
||||||
if _, ok := backend.users[user.ID()]; !ok {
|
return fmt.Errorf("failed to send mail: %w", err)
|
||||||
return ErrNoSuchUser
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(backend.users, user.ID())
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -401,11 +401,6 @@ func (bridge *Bridge) addUserWithVault(
|
|||||||
return fmt.Errorf("failed to add IMAP user: %w", err)
|
return fmt.Errorf("failed to add IMAP user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect the user's address(es) to the SMTP server.
|
|
||||||
if err := bridge.smtpBackend.addUser(user); err != nil {
|
|
||||||
return fmt.Errorf("failed to add user to SMTP backend: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle events coming from the user before forwarding them to the bridge.
|
// Handle events coming from the user before forwarding them to the bridge.
|
||||||
// For example, if the user's addresses change, we need to update them in gluon.
|
// For example, if the user's addresses change, we need to update them in gluon.
|
||||||
go func() {
|
go func() {
|
||||||
@ -497,10 +492,6 @@ func (bridge *Bridge) addIMAPUser(ctx context.Context, user *user.User) error {
|
|||||||
// logoutUser logs the given user out from bridge.
|
// logoutUser logs the given user out from bridge.
|
||||||
func (bridge *Bridge) logoutUser(ctx context.Context, userID string) error {
|
func (bridge *Bridge) logoutUser(ctx context.Context, userID string) error {
|
||||||
if ok, err := bridge.users.GetDeleteErr(userID, func(user *user.User) error {
|
if ok, err := bridge.users.GetDeleteErr(userID, func(user *user.User) error {
|
||||||
if err := bridge.smtpBackend.removeUser(user); err != nil {
|
|
||||||
logrus.WithError(err).Error("Failed to remove user from SMTP backend")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, gluonID := range user.GetGluonIDs() {
|
for _, gluonID := range user.GetGluonIDs() {
|
||||||
if err := bridge.imapServer.RemoveUser(ctx, gluonID, false); err != nil {
|
if err := bridge.imapServer.RemoveUser(ctx, gluonID, false); err != nil {
|
||||||
logrus.WithError(err).Error("Failed to remove IMAP user")
|
logrus.WithError(err).Error("Failed to remove IMAP user")
|
||||||
@ -528,10 +519,6 @@ func (bridge *Bridge) logoutUser(ctx context.Context, userID string) error {
|
|||||||
// deleteUser deletes the given user from bridge.
|
// deleteUser deletes the given user from bridge.
|
||||||
func (bridge *Bridge) deleteUser(ctx context.Context, userID string) {
|
func (bridge *Bridge) deleteUser(ctx context.Context, userID string) {
|
||||||
if ok := bridge.users.GetDelete(userID, func(user *user.User) {
|
if ok := bridge.users.GetDelete(userID, func(user *user.User) {
|
||||||
if err := bridge.smtpBackend.removeUser(user); err != nil {
|
|
||||||
logrus.WithError(err).Error("Failed to remove user from SMTP backend")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, gluonID := range user.GetGluonIDs() {
|
for _, gluonID := range user.GetGluonIDs() {
|
||||||
if err := bridge.imapServer.RemoveUser(ctx, gluonID, true); err != nil {
|
if err := bridge.imapServer.RemoveUser(ctx, gluonID, true); err != nil {
|
||||||
logrus.WithError(err).Error("Failed to remove IMAP user")
|
logrus.WithError(err).Error("Failed to remove IMAP user")
|
||||||
|
|||||||
@ -67,7 +67,7 @@ func newIMAPConnector(user *User, addrID string) *imapConnector {
|
|||||||
|
|
||||||
// Authorize returns whether the given username/password combination are valid for this connector.
|
// Authorize returns whether the given username/password combination are valid for this connector.
|
||||||
func (conn *imapConnector) Authorize(username string, password []byte) bool {
|
func (conn *imapConnector) Authorize(username string, password []byte) bool {
|
||||||
addrID, err := conn.checkAuth(username, password)
|
addrID, err := conn.CheckAuth(username, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,6 +56,33 @@ func (user *User) withAddrKR(addrID string, fn func(*crypto.KeyRing, *crypto.Key
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (user *User) withAddrKRByEmail(email string, fn func(*crypto.KeyRing, *crypto.KeyRing) error) error {
|
||||||
|
return user.apiAddrs.ValuesErr(func(apiAddrs []liteapi.Address) error {
|
||||||
|
addrID, err := getAddrID(apiAddrs, email)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get address ID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.withUserKR(func(userKR *crypto.KeyRing) error {
|
||||||
|
if ok, err := user.apiAddrs.GetErr(addrID, func(apiAddr liteapi.Address) error {
|
||||||
|
addrKR, err := apiAddr.Keys.Unlock(user.vault.KeyPass(), userKR)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to unlock address keys: %w", err)
|
||||||
|
}
|
||||||
|
defer userKR.ClearPrivateParams()
|
||||||
|
|
||||||
|
return fn(userKR, addrKR)
|
||||||
|
}); !ok {
|
||||||
|
return fmt.Errorf("no such address %q", addrID)
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (user *User) withAddrKRs(fn func(*crypto.KeyRing, map[string]*crypto.KeyRing) error) error {
|
func (user *User) withAddrKRs(fn func(*crypto.KeyRing, map[string]*crypto.KeyRing) error) error {
|
||||||
return user.withUserKR(func(userKR *crypto.KeyRing) error {
|
return user.withUserKR(func(userKR *crypto.KeyRing) error {
|
||||||
return user.apiAddrs.ValuesErr(func(apiAddrs []liteapi.Address) error {
|
return user.apiAddrs.ValuesErr(func(apiAddrs []liteapi.Address) error {
|
||||||
|
|||||||
@ -30,204 +30,83 @@ import (
|
|||||||
"github.com/ProtonMail/gluon/rfc822"
|
"github.com/ProtonMail/gluon/rfc822"
|
||||||
"github.com/ProtonMail/go-rfc5322"
|
"github.com/ProtonMail/go-rfc5322"
|
||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/safe"
|
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/pkg/message"
|
"github.com/ProtonMail/proton-bridge/v2/pkg/message"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/pkg/message/parser"
|
"github.com/ProtonMail/proton-bridge/v2/pkg/message/parser"
|
||||||
"github.com/bradenaw/juniper/parallel"
|
"github.com/bradenaw/juniper/parallel"
|
||||||
"github.com/bradenaw/juniper/xslices"
|
"github.com/bradenaw/juniper/xslices"
|
||||||
"github.com/emersion/go-smtp"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"gitlab.protontech.ch/go/liteapi"
|
"gitlab.protontech.ch/go/liteapi"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
type smtpSession struct {
|
func (user *User) sendMail(authID string, emails []string, from string, to []string, r io.Reader) error { //nolint:funlen
|
||||||
*User
|
|
||||||
|
|
||||||
// authID holds the ID of the address that the SMTP client authenticated with to send the message.
|
|
||||||
authID string
|
|
||||||
|
|
||||||
// from is the current sending address (taken from the return path).
|
|
||||||
from string
|
|
||||||
|
|
||||||
// fromAddrID is the ID of the current sending address (taken from the return path).
|
|
||||||
fromAddrID string
|
|
||||||
|
|
||||||
// to holds all to for the current message.
|
|
||||||
to []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSMTPSession(user *User, email string) (*smtpSession, error) {
|
|
||||||
return safe.MapValuesRetErr(user.apiAddrs, func(apiAddrs []liteapi.Address) (*smtpSession, error) {
|
|
||||||
authID, err := getAddrID(apiAddrs, email)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get address ID: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &smtpSession{
|
|
||||||
User: user,
|
|
||||||
authID: authID,
|
|
||||||
}, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset Discard currently processed message.
|
|
||||||
func (session *smtpSession) Reset() {
|
|
||||||
logrus.Info("SMTP session reset")
|
|
||||||
|
|
||||||
// Clear the from and to fields.
|
|
||||||
session.from = ""
|
|
||||||
session.fromAddrID = ""
|
|
||||||
session.to = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logout Free all resources associated with session.
|
|
||||||
func (session *smtpSession) Logout() error {
|
|
||||||
defer session.Reset()
|
|
||||||
|
|
||||||
logrus.Info("SMTP session logout")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mail Set return path for currently processed message.
|
|
||||||
func (session *smtpSession) Mail(from string, opts smtp.MailOptions) error {
|
|
||||||
logrus.Info("SMTP session mail")
|
|
||||||
|
|
||||||
return session.apiAddrs.ValuesErr(func(apiAddrs []liteapi.Address) error {
|
|
||||||
switch {
|
|
||||||
case opts.RequireTLS:
|
|
||||||
return ErrNotImplemented
|
|
||||||
|
|
||||||
case opts.UTF8:
|
|
||||||
return ErrNotImplemented
|
|
||||||
|
|
||||||
case opts.Auth != nil:
|
|
||||||
email, err := getAddrEmail(apiAddrs, session.authID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid auth address: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *opts.Auth != "" && *opts.Auth != email {
|
|
||||||
return ErrNotImplemented
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addrID, err := getAddrID(apiAddrs, sanitizeEmail(from))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid return path: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
session.from = from
|
|
||||||
|
|
||||||
session.fromAddrID = addrID
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rcpt Add recipient for currently processed message.
|
|
||||||
func (session *smtpSession) Rcpt(to string) error {
|
|
||||||
logrus.Info("SMTP session rcpt")
|
|
||||||
|
|
||||||
if to == "" {
|
|
||||||
return ErrInvalidRecipient
|
|
||||||
}
|
|
||||||
|
|
||||||
if !slices.Contains(session.to, to) {
|
|
||||||
session.to = append(session.to, to)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data Set currently processed message contents and send it.
|
|
||||||
func (session *smtpSession) Data(r io.Reader) error { //nolint:funlen
|
|
||||||
logrus.Info("SMTP session data")
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
switch {
|
// Create a new message parser from the reader.
|
||||||
case session.from == "":
|
|
||||||
return ErrInvalidReturnPath
|
|
||||||
|
|
||||||
case len(session.to) == 0:
|
|
||||||
return ErrInvalidRecipient
|
|
||||||
}
|
|
||||||
|
|
||||||
parser, err := parser.New(r)
|
parser, err := parser.New(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create parser: %w", err)
|
return fmt.Errorf("failed to create parser: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return session.apiAddrs.ValuesErr(func(apiAddrs []liteapi.Address) error {
|
// If the message contains a sender, use it instead of the one from the return path.
|
||||||
return session.withAddrKR(session.fromAddrID, func(userKR, addrKR *crypto.KeyRing) error {
|
if sender, ok := getMessageSender(parser); ok {
|
||||||
// Use the first key for encrypting the message.
|
from = sender
|
||||||
addrKR, err := addrKR.FirstKey()
|
}
|
||||||
|
|
||||||
|
// Load the user's mail settings.
|
||||||
|
settings, err := user.client.GetMailSettings(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get mail settings: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.withAddrKRByEmail(from, func(userKR, addrKR *crypto.KeyRing) error {
|
||||||
|
// Use the first key for encrypting the message.
|
||||||
|
addrKR, err := addrKR.FirstKey()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get first key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have to attach the public key, do it now.
|
||||||
|
if settings.AttachPublicKey == liteapi.AttachPublicKeyEnabled {
|
||||||
|
key, err := addrKR.GetKey(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get first key: %w", err)
|
return fmt.Errorf("failed to get sending key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the message contains a sender, use it instead of the one from the return path.
|
pubKey, err := key.GetArmoredPublicKey()
|
||||||
if sender, ok := getMessageSender(parser); ok {
|
|
||||||
session.from = sender
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the user's mail settings.
|
|
||||||
settings, err := session.client.GetMailSettings(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get mail settings: %w", err)
|
return fmt.Errorf("failed to get public key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have to attach the public key, do it now.
|
parser.AttachPublicKey(pubKey, fmt.Sprintf("publickey - %v - %v", addrKR.GetIdentities()[0].Name, key.GetFingerprint()[:8]))
|
||||||
if settings.AttachPublicKey == liteapi.AttachPublicKeyEnabled {
|
}
|
||||||
key, err := addrKR.GetKey(0)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get sending key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKey, err := key.GetArmoredPublicKey()
|
// Parse the message we want to send (after we have attached the public key).
|
||||||
if err != nil {
|
message, err := message.ParseWithParser(parser)
|
||||||
return fmt.Errorf("failed to get public key: %w", err)
|
if err != nil {
|
||||||
}
|
return fmt.Errorf("failed to parse message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
parser.AttachPublicKey(pubKey, fmt.Sprintf("publickey - %v - %v", addrKR.GetIdentities()[0].Name, key.GetFingerprint()[:8]))
|
// Send the message using the correct key.
|
||||||
}
|
sent, err := sendWithKey(
|
||||||
|
ctx,
|
||||||
|
user.client,
|
||||||
|
authID,
|
||||||
|
user.vault.AddressMode(),
|
||||||
|
settings,
|
||||||
|
userKR, addrKR,
|
||||||
|
emails, from, to,
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to send message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the message we want to send (after we have attached the public key).
|
logrus.WithField("messageID", sent.ID).Info("Message sent")
|
||||||
message, err := message.ParseWithParser(parser)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to parse message: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect all the user's emails so we can match them to the outgoing message.
|
return nil
|
||||||
emails := xslices.Map(apiAddrs, func(addr liteapi.Address) string {
|
|
||||||
return addr.Email
|
|
||||||
})
|
|
||||||
|
|
||||||
sent, err := sendWithKey(
|
|
||||||
ctx,
|
|
||||||
session.client,
|
|
||||||
session.authID,
|
|
||||||
session.vault.AddressMode(),
|
|
||||||
settings,
|
|
||||||
userKR,
|
|
||||||
addrKR,
|
|
||||||
emails,
|
|
||||||
session.from,
|
|
||||||
session.to,
|
|
||||||
message,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to send message: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.WithField("messageID", sent.ID).Info("Message sent")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,20 +131,15 @@ func sendWithKey( //nolint:funlen
|
|||||||
|
|
||||||
var decBody string
|
var decBody string
|
||||||
|
|
||||||
switch message.MIMEType {
|
switch message.MIMEType { //nolint:exhaustive
|
||||||
case rfc822.TextHTML:
|
case rfc822.TextHTML:
|
||||||
decBody = string(message.RichBody)
|
decBody = string(message.RichBody)
|
||||||
|
|
||||||
case rfc822.TextPlain:
|
case rfc822.TextPlain:
|
||||||
decBody = string(message.PlainBody)
|
decBody = string(message.PlainBody)
|
||||||
case rfc822.MultipartRelated:
|
|
||||||
fallthrough
|
|
||||||
case rfc822.MultipartMixed:
|
|
||||||
fallthrough
|
|
||||||
case rfc822.MessageRFC822:
|
|
||||||
fallthrough
|
|
||||||
default:
|
default:
|
||||||
break
|
return liteapi.Message{}, fmt.Errorf("unsupported MIME type: %v", message.MIMEType)
|
||||||
}
|
}
|
||||||
|
|
||||||
encBody, err := addrKR.Encrypt(crypto.NewPlainMessageFromString(decBody), nil)
|
encBody, err := addrKR.Encrypt(crypto.NewPlainMessageFromString(decBody), nil)
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gitlab.protontech.ch/go/liteapi"
|
"gitlab.protontech.ch/go/liteapi"
|
||||||
)
|
)
|
||||||
@ -84,7 +85,7 @@ func hexDecode(b []byte) ([]byte, error) {
|
|||||||
// getAddrID returns the address ID for the given email address.
|
// getAddrID returns the address ID for the given email address.
|
||||||
func getAddrID(apiAddrs []liteapi.Address, email string) (string, error) {
|
func getAddrID(apiAddrs []liteapi.Address, email string) (string, error) {
|
||||||
for _, addr := range apiAddrs {
|
for _, addr := range apiAddrs {
|
||||||
if addr.Email == email {
|
if strings.EqualFold(addr.Email, sanitizeEmail(email)) {
|
||||||
return addr.ID, nil
|
return addr.ID, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,17 +93,6 @@ func getAddrID(apiAddrs []liteapi.Address, email string) (string, error) {
|
|||||||
return "", fmt.Errorf("address %s not found", email)
|
return "", fmt.Errorf("address %s not found", email)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAddrEmail returns the email address of the given address ID.
|
|
||||||
func getAddrEmail(apiAddrs []liteapi.Address, addrID string) (string, error) {
|
|
||||||
for _, addr := range apiAddrs {
|
|
||||||
if addr.ID == addrID {
|
|
||||||
return addr.Email, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("address %s not found", addrID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// contextWithStopCh returns a new context that is cancelled when the stop channel is closed or a value is sent to it.
|
// contextWithStopCh returns a new context that is cancelled when the stop channel is closed or a value is sent to it.
|
||||||
func contextWithStopCh(ctx context.Context, stopCh ...<-chan struct{}) (context.Context, context.CancelFunc) {
|
func contextWithStopCh(ctx context.Context, stopCh ...<-chan struct{}) (context.Context, context.CancelFunc) {
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -33,7 +34,6 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v2/internal/try"
|
"github.com/ProtonMail/proton-bridge/v2/internal/try"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
|
||||||
"github.com/bradenaw/juniper/xslices"
|
"github.com/bradenaw/juniper/xslices"
|
||||||
"github.com/emersion/go-smtp"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"gitlab.protontech.ch/go/liteapi"
|
"gitlab.protontech.ch/go/liteapi"
|
||||||
)
|
)
|
||||||
@ -315,13 +315,46 @@ func (user *User) NewIMAPConnectors() (map[string]connector.Connector, error) {
|
|||||||
return imapConn, nil
|
return imapConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSMTPSession returns an SMTP session for the user.
|
// SendMail sends an email from the given address to the given recipients.
|
||||||
func (user *User) NewSMTPSession(email string, password []byte) (smtp.Session, error) {
|
func (user *User) SendMail(authID string, from string, to []string, r io.Reader) error {
|
||||||
if _, err := user.checkAuth(email, password); err != nil {
|
if len(to) == 0 {
|
||||||
return nil, err
|
return ErrInvalidRecipient
|
||||||
}
|
}
|
||||||
|
|
||||||
return newSMTPSession(user, email)
|
return user.apiAddrs.ValuesErr(func(apiAddrs []liteapi.Address) error {
|
||||||
|
if _, err := getAddrID(apiAddrs, from); err != nil {
|
||||||
|
return ErrInvalidReturnPath
|
||||||
|
}
|
||||||
|
|
||||||
|
emails := xslices.Map(apiAddrs, func(addr liteapi.Address) string {
|
||||||
|
return addr.Email
|
||||||
|
})
|
||||||
|
|
||||||
|
return user.sendMail(authID, emails, from, to, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckAuth returns whether the given email and password can be used to authenticate over IMAP or SMTP with this user.
|
||||||
|
// It returns the address ID of the authenticated address.
|
||||||
|
func (user *User) CheckAuth(email string, password []byte) (string, error) {
|
||||||
|
dec, err := hexDecode(password)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to decode password: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if subtle.ConstantTimeCompare(user.vault.BridgePass(), dec) != 1 {
|
||||||
|
return "", fmt.Errorf("invalid password")
|
||||||
|
}
|
||||||
|
|
||||||
|
return safe.MapValuesRetErr(user.apiAddrs, func(apiAddrs []liteapi.Address) (string, error) {
|
||||||
|
for _, addr := range apiAddrs {
|
||||||
|
if strings.EqualFold(addr.Email, email) {
|
||||||
|
return addr.ID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("invalid email")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnStatusUp is called when the connection goes up.
|
// OnStatusUp is called when the connection goes up.
|
||||||
@ -347,9 +380,6 @@ func (user *User) Logout(ctx context.Context) error {
|
|||||||
// Cancel ongoing syncs.
|
// Cancel ongoing syncs.
|
||||||
user.stopSync()
|
user.stopSync()
|
||||||
|
|
||||||
// Wait for ongoing syncs to stop.
|
|
||||||
user.waitSync()
|
|
||||||
|
|
||||||
if err := user.client.AuthDelete(ctx); err != nil {
|
if err := user.client.AuthDelete(ctx); err != nil {
|
||||||
return fmt.Errorf("failed to delete auth: %w", err)
|
return fmt.Errorf("failed to delete auth: %w", err)
|
||||||
}
|
}
|
||||||
@ -369,9 +399,6 @@ func (user *User) Close() error {
|
|||||||
// Cancel ongoing syncs.
|
// Cancel ongoing syncs.
|
||||||
user.stopSync()
|
user.stopSync()
|
||||||
|
|
||||||
// Wait for ongoing syncs to stop.
|
|
||||||
user.waitSync()
|
|
||||||
|
|
||||||
// Close the user's API client.
|
// Close the user's API client.
|
||||||
user.client.Close()
|
user.client.Close()
|
||||||
|
|
||||||
@ -395,11 +422,13 @@ func (user *User) Close() error {
|
|||||||
|
|
||||||
func (user *User) SetShowAllMail(show bool) {
|
func (user *User) SetShowAllMail(show bool) {
|
||||||
var value int32
|
var value int32
|
||||||
|
|
||||||
if show {
|
if show {
|
||||||
value = 1
|
value = 1
|
||||||
} else {
|
} else {
|
||||||
value = 0
|
value = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic.StoreInt32(&user.showAllMail, value)
|
atomic.StoreInt32(&user.showAllMail, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,27 +436,6 @@ func (user *User) GetShowAllMail() bool {
|
|||||||
return atomic.LoadInt32(&user.showAllMail) == 1
|
return atomic.LoadInt32(&user.showAllMail) == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) checkAuth(email string, password []byte) (string, error) {
|
|
||||||
dec, err := hexDecode(password)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to decode password: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if subtle.ConstantTimeCompare(user.vault.BridgePass(), dec) != 1 {
|
|
||||||
return "", fmt.Errorf("invalid password")
|
|
||||||
}
|
|
||||||
|
|
||||||
return safe.MapValuesRetErr(user.apiAddrs, func(apiAddrs []liteapi.Address) (string, error) {
|
|
||||||
for _, addr := range apiAddrs {
|
|
||||||
if strings.EqualFold(addr.Email, email) {
|
|
||||||
return addr.ID, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("invalid email")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// streamEvents begins streaming API events for the user.
|
// streamEvents begins streaming API events for the user.
|
||||||
// When we receive an API event, we attempt to handle it.
|
// When we receive an API event, we attempt to handle it.
|
||||||
// If successful, we update the event ID in the vault.
|
// If successful, we update the event ID in the vault.
|
||||||
@ -496,6 +504,8 @@ func (user *User) startSync() <-chan error {
|
|||||||
// AbortSync aborts any ongoing sync.
|
// AbortSync aborts any ongoing sync.
|
||||||
// GODT-1947: Should probably be done automatically when one of the user's IMAP connectors is closed.
|
// GODT-1947: Should probably be done automatically when one of the user's IMAP connectors is closed.
|
||||||
func (user *User) stopSync() {
|
func (user *User) stopSync() {
|
||||||
|
defer user.syncLock.Wait()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case user.syncStopCh <- struct{}{}:
|
case user.syncStopCh <- struct{}{}:
|
||||||
logrus.Debug("Sent sync abort signal")
|
logrus.Debug("Sent sync abort signal")
|
||||||
@ -514,8 +524,3 @@ func (user *User) lockSync() {
|
|||||||
func (user *User) unlockSync() {
|
func (user *User) unlockSync() {
|
||||||
user.syncLock.Unlock()
|
user.syncLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// waitSync waits for any ongoing sync to finish.
|
|
||||||
func (user *User) waitSync() {
|
|
||||||
user.syncLock.Wait()
|
|
||||||
}
|
|
||||||
|
|||||||
@ -39,13 +39,23 @@ Feature: SMTP initiation
|
|||||||
Then it fails with error "Missing RCPT TO command"
|
Then it fails with error "Missing RCPT TO command"
|
||||||
|
|
||||||
Scenario: Send with empty FROM
|
Scenario: Send with empty FROM
|
||||||
When SMTP client "1" sends MAIL FROM "<>"
|
When SMTP client "1" sends the following message from "<>" to "bridgetest@protonmail.com":
|
||||||
Then it fails with error "invalid return path"
|
"""
|
||||||
|
To: Internal Bridge <bridgetest@protonmail.com>
|
||||||
|
|
||||||
|
this should fail
|
||||||
|
"""
|
||||||
|
Then it fails
|
||||||
|
|
||||||
Scenario: Send with empty TO
|
Scenario: Send with empty TO
|
||||||
When SMTP client "1" sends MAIL FROM "<user@pm.me>"
|
When SMTP client "1" sends MAIL FROM "<user@pm.me>"
|
||||||
Then it succeeds
|
Then it succeeds
|
||||||
When SMTP client "1" sends RCPT TO "<>"
|
When SMTP client "1" sends RCPT TO "<>"
|
||||||
|
Then it succeeds
|
||||||
|
When SMTP client "1" sends DATA:
|
||||||
|
"""
|
||||||
|
Subject: test
|
||||||
|
"""
|
||||||
Then it fails with error "invalid recipient"
|
Then it fails with error "invalid recipient"
|
||||||
|
|
||||||
Scenario: Allow BODY parameter of MAIL FROM command
|
Scenario: Allow BODY parameter of MAIL FROM command
|
||||||
@ -53,5 +63,10 @@ Feature: SMTP initiation
|
|||||||
Then it succeeds
|
Then it succeeds
|
||||||
|
|
||||||
Scenario: FROM not owned by user
|
Scenario: FROM not owned by user
|
||||||
When SMTP client "1" sends MAIL FROM "<user@pm.test>"
|
When SMTP client "1" sends the following message from "other@pm.me" to "bridgetest@protonmail.com":
|
||||||
Then it fails with error "invalid return path"
|
"""
|
||||||
|
From: Bridge Test <user@pm.me>
|
||||||
|
To: Internal Bridge <bridgetest@protonmail.com>
|
||||||
|
|
||||||
|
this should fail
|
||||||
|
"""
|
||||||
Reference in New Issue
Block a user