Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(BRIDGE-268): add option to disable AUTHENTICATE command. #417

Merged
merged 1 commit into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 39 additions & 37 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,25 @@ import (
)

type serverBuilder struct {
dataDir string
databaseDir string
delim string
loginJailTime time.Duration
tlsConfig *tls.Config
idleBulkTime time.Duration
inLogger io.Writer
outLogger io.Writer
versionInfo version.Info
cmdExecProfBuilder profiling.CmdProfilerBuilder
storeBuilder store.Builder
reporter reporter.Reporter
disableParallelism bool
imapLimits limits.IMAP
uidValidityGenerator imap.UIDValidityGenerator
panicHandler async.PanicHandler
dbCI db.ClientInterface
observabilitySender observability.Sender
dataDir string
databaseDir string
delim string
loginJailTime time.Duration
tlsConfig *tls.Config
idleBulkTime time.Duration
inLogger io.Writer
outLogger io.Writer
versionInfo version.Info
cmdExecProfBuilder profiling.CmdProfilerBuilder
storeBuilder store.Builder
reporter reporter.Reporter
disableParallelism bool
imapLimits limits.IMAP
disableIMAPAuthenticate bool
uidValidityGenerator imap.UIDValidityGenerator
panicHandler async.PanicHandler
dbCI db.ClientInterface
observabilitySender observability.Sender
}

func newBuilder() (*serverBuilder, error) {
Expand Down Expand Up @@ -106,25 +107,26 @@ func (builder *serverBuilder) build() (*Server, error) {
}

s := &Server{
dataDir: builder.dataDir,
databaseDir: builder.databaseDir,
backend: backend,
sessions: make(map[int]*session.Session),
serveErrCh: async.NewQueuedChannel[error](1, 1, builder.panicHandler, "server-err-ch"),
serveDoneCh: make(chan struct{}),
serveWG: async.MakeWaitGroup(builder.panicHandler),
inLogger: builder.inLogger,
outLogger: builder.outLogger,
tlsConfig: builder.tlsConfig,
idleBulkTime: builder.idleBulkTime,
storeBuilder: builder.storeBuilder,
cmdExecProfBuilder: builder.cmdExecProfBuilder,
versionInfo: builder.versionInfo,
reporter: builder.reporter,
disableParallelism: builder.disableParallelism,
uidValidityGenerator: builder.uidValidityGenerator,
panicHandler: builder.panicHandler,
observabilitySender: builder.observabilitySender,
dataDir: builder.dataDir,
databaseDir: builder.databaseDir,
backend: backend,
sessions: make(map[int]*session.Session),
serveErrCh: async.NewQueuedChannel[error](1, 1, builder.panicHandler, "server-err-ch"),
serveDoneCh: make(chan struct{}),
serveWG: async.MakeWaitGroup(builder.panicHandler),
inLogger: builder.inLogger,
outLogger: builder.outLogger,
tlsConfig: builder.tlsConfig,
idleBulkTime: builder.idleBulkTime,
storeBuilder: builder.storeBuilder,
cmdExecProfBuilder: builder.cmdExecProfBuilder,
versionInfo: builder.versionInfo,
reporter: builder.reporter,
disableParallelism: builder.disableParallelism,
disableIMAPAuthenticate: builder.disableIMAPAuthenticate,
uidValidityGenerator: builder.uidValidityGenerator,
panicHandler: builder.panicHandler,
observabilitySender: builder.observabilitySender,
}

return s, nil
Expand Down
17 changes: 14 additions & 3 deletions imap/command/authenticate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func TestParser_Authenticate(t *testing.T) {
authString := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("\x00%s\x00%s", data.UserID, data.Password)))
input := toIMAPLine(tag+` AUTHENTICATE PLAIN`, authString)
s := rfcparser.NewScanner(bytes.NewReader(input))
p := NewParserWithLiteralContinuationCb(s, continuationChecker(&continued))
p := NewParser(s, WithLiteralContinuationCallback(continuationChecker(&continued)))
cmd, err := p.Parse()
message := fmt.Sprintf(" test failed for input %#v", data)

Expand All @@ -46,7 +46,7 @@ func TestParser_AuthenticationWithIdentity(t *testing.T) {

authString := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("identity\x00user\x00pass")))
s := rfcparser.NewScanner(bytes.NewReader(toIMAPLine(`A0001 authenticate plain`, authString)))
p := NewParserWithLiteralContinuationCb(s, continuationChecker(&continued))
p := NewParser(s, WithLiteralContinuationCallback(continuationChecker(&continued)))
cmd, err := p.Parse()

require.NoError(t, err, "error test failed")
Expand Down Expand Up @@ -123,7 +123,7 @@ func TestParser_AuthenticateFailures(t *testing.T) {
var continued bool

s := rfcparser.NewScanner(bytes.NewReader(toIMAPLine(test.input...)))
p := NewParserWithLiteralContinuationCb(s, continuationChecker(&continued))
p := NewParser(s, WithLiteralContinuationCallback(continuationChecker(&continued)))
_, err := p.Parse()
failureDescription := fmt.Sprintf(" test failed for input %#v", test)

Expand All @@ -134,3 +134,14 @@ func TestParser_AuthenticateFailures(t *testing.T) {
require.Equal(t, test.continuationExpected, continued, "continuation"+failureDescription)
}
}

func TestParser_AuthenticateDisabled(t *testing.T) {
s := rfcparser.NewScanner(bytes.NewReader(toIMAPLine(`A0001 authenticate plain`)))
p := NewParser(s, WithDisableIMAPAuthenticate())
_, err := p.Parse()

var parserError *rfcparser.Error

require.ErrorAs(t, err, &parserError)
require.Equal(t, "unknown command 'authenticate'", parserError.Message)
}
4 changes: 2 additions & 2 deletions imap/command/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ func TestParser_ListCommandLiteral(t *testing.T) {
input := toIMAPLine(`tag LIST {5}`, `"bar" %`)
s := rfcparser.NewScanner(bytes.NewReader(input))
continuationCalled := false
p := NewParserWithLiteralContinuationCb(s, func(string) error {
p := NewParser(s, WithLiteralContinuationCallback(func(string) error {
continuationCalled = true
return nil
})
}))
expected := Command{Tag: "tag", Payload: &List{
Mailbox: `"bar"`,
ListMailbox: "%",
Expand Down
113 changes: 76 additions & 37 deletions imap/command/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,39 @@ type Builder interface {
FromParser(p *rfcparser.Parser) (Payload, error)
}

type parserBuilder struct {
continuationCallback func(string) error
disableIMAPAuthenticate bool
}

type Option interface {
config(*parserBuilder)
}

type withLiteralContinuationCallback struct {
callback func(string) error
}

func (opt withLiteralContinuationCallback) config(builder *parserBuilder) {
builder.continuationCallback = opt.callback
}

func WithLiteralContinuationCallback(callback func(string) error) Option {
return &withLiteralContinuationCallback{
callback: callback,
}
}

type withDisableIMAPAuthenticate struct{}

func (opt withDisableIMAPAuthenticate) config(builder *parserBuilder) {
builder.disableIMAPAuthenticate = true
}

func WithDisableIMAPAuthenticate() Option {
return &withDisableIMAPAuthenticate{}
}

// Parser parses IMAP Commands.
type Parser struct {
parser *rfcparser.Parser
Expand All @@ -20,45 +53,51 @@ type Parser struct {
lastCmd string
}

func NewParser(s *rfcparser.Scanner) *Parser {
return NewParserWithLiteralContinuationCb(s, nil)
}
func NewParser(s *rfcparser.Scanner, options ...Option) *Parser {
var builder parserBuilder
for _, option := range options {
option.config(&builder)
}

commands := map[string]Builder{
"list": &ListCommandParser{},
"append": &AppendCommandParser{},
"search": &SearchCommandParser{},
"fetch": &FetchCommandParser{},
"capability": &CapabilityCommandParser{},
"idle": &IdleCommandParser{},
"noop": &NoopCommandParser{},
"logout": &LogoutCommandParser{},
"check": &CheckCommandParser{},
"close": &CloseCommandParser{},
"expunge": &ExpungeCommandParser{},
"unselect": &UnselectCommandParser{},
"starttls": &StartTLSCommandParser{},
"status": &StatusCommandParser{},
"select": &SelectCommandParser{},
"examine": &ExamineCommandParser{},
"create": &CreateCommandParser{},
"delete": &DeleteCommandParser{},
"subscribe": &SubscribeCommandParser{},
"unsubscribe": &UnsubscribeCommandParser{},
"rename": &RenameCommandParser{},
"lsub": &LSubCommandParser{},
"login": &LoginCommandParser{},
"store": &StoreCommandParser{},
"copy": &CopyCommandParser{},
"move": &MoveCommandParser{},
"uid": NewUIDCommandParser(),
"id": &IDCommandParser{},
}

if !builder.disableIMAPAuthenticate {
commands["authenticate"] = &AuthenticateCommandParser{}
}

func NewParserWithLiteralContinuationCb(s *rfcparser.Scanner, cb func(string) error) *Parser {
return &Parser{
scanner: s,
parser: rfcparser.NewParserWithLiteralContinuationCb(s, cb),
commands: map[string]Builder{
"list": &ListCommandParser{},
"append": &AppendCommandParser{},
"search": &SearchCommandParser{},
"fetch": &FetchCommandParser{},
"capability": &CapabilityCommandParser{},
"idle": &IdleCommandParser{},
"noop": &NoopCommandParser{},
"logout": &LogoutCommandParser{},
"check": &CheckCommandParser{},
"close": &CloseCommandParser{},
"expunge": &ExpungeCommandParser{},
"unselect": &UnselectCommandParser{},
"starttls": &StartTLSCommandParser{},
"status": &StatusCommandParser{},
"select": &SelectCommandParser{},
"examine": &ExamineCommandParser{},
"create": &CreateCommandParser{},
"delete": &DeleteCommandParser{},
"subscribe": &SubscribeCommandParser{},
"unsubscribe": &UnsubscribeCommandParser{},
"rename": &RenameCommandParser{},
"lsub": &LSubCommandParser{},
"login": &LoginCommandParser{},
"store": &StoreCommandParser{},
"copy": &CopyCommandParser{},
"move": &MoveCommandParser{},
"uid": NewUIDCommandParser(),
"id": &IDCommandParser{},
"authenticate": &AuthenticateCommandParser{},
},
scanner: s,
parser: rfcparser.NewParserWithLiteralContinuationCb(s, builder.continuationCallback),
commands: commands,
}
}

Expand Down
4 changes: 2 additions & 2 deletions imap/command/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,10 @@ func TestParser_LiteralWithContinuationSubmission(t *testing.T) {
}()

s := rfcparser.NewScanner(reader)
p := NewParserWithLiteralContinuationCb(s, func(string) error {
p := NewParser(s, WithLiteralContinuationCallback(func(string) error {
close(continueCh)
return nil
})
}))

expected := Command{Tag: "A003", Payload: &Append{
Mailbox: "saved-messages",
Expand Down
9 changes: 8 additions & 1 deletion internal/session/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,14 @@ func (s *Session) startCommandReader(ctx context.Context) <-chan commandResult {
{0x16, 0x00, 0x00}, // 0.0
}

parser := command.NewParserWithLiteralContinuationCb(s.scanner, func(message string) error { return response.Continuation().Send(s, message) })
options := []command.Option{
command.WithLiteralContinuationCallback(func(message string) error { return response.Continuation().Send(s, message) }),
}
if s.disableIMAPAuthenticate {
options = append(options, command.WithDisableIMAPAuthenticate())
}

parser := command.NewParser(s.scanner, options...)

for {
s.inputCollector.Reset()
Expand Down
41 changes: 25 additions & 16 deletions internal/session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"github.com/ProtonMail/gluon/internal/backend"
"github.com/ProtonMail/gluon/internal/response"
"github.com/ProtonMail/gluon/internal/state"
"github.com/ProtonMail/gluon/limits"
"github.com/ProtonMail/gluon/profiling"
"github.com/ProtonMail/gluon/reporter"
"github.com/ProtonMail/gluon/rfcparser"
Expand Down Expand Up @@ -84,13 +83,16 @@ type Session struct {
// handleWG is used to wait for all commands to finish before closing the session.
handleWG async.WaitGroup

/// errorCount error counter
/// errorCount error counter.
errorCount int

imapLimits limits.IMAP
// disableIMAPAuthenticate disables the IMAP AUTHENTICATE command (client can then only authenticate using LOGIN).
disableIMAPAuthenticate bool

// panicHandler The panic handler.
panicHandler async.PanicHandler

// log The log for the session.
log *logrus.Entry
}

Expand All @@ -102,25 +104,32 @@ func New(
profiler profiling.CmdProfilerBuilder,
eventCh chan<- events.Event,
idleBulkTime time.Duration,
disableIMAPAuthenticate bool,
panicHandler async.PanicHandler,
) *Session {
inputCollector := command.NewInputCollector(bufio.NewReader(conn))
scanner := rfcparser.NewScannerWithReader(inputCollector)

caps := []imap.Capability{imap.IMAP4rev1, imap.IDLE, imap.UNSELECT, imap.UIDPLUS, imap.MOVE, imap.ID}
if !disableIMAPAuthenticate {
caps = append(caps, imap.AUTHPLAIN)
}

return &Session{
conn: conn,
inputCollector: inputCollector,
scanner: scanner,
backend: backend,
caps: []imap.Capability{imap.IMAP4rev1, imap.IDLE, imap.UNSELECT, imap.UIDPLUS, imap.MOVE, imap.ID, imap.AUTHPLAIN},
sessionID: sessionID,
eventCh: eventCh,
idleBulkTime: idleBulkTime,
version: version,
cmdProfilerBuilder: profiler,
handleWG: async.MakeWaitGroup(panicHandler),
panicHandler: panicHandler,
log: logrus.WithField("pkg", "gluon/session").WithField("session", sessionID),
conn: conn,
inputCollector: inputCollector,
scanner: scanner,
backend: backend,
caps: caps,
sessionID: sessionID,
eventCh: eventCh,
idleBulkTime: idleBulkTime,
version: version,
cmdProfilerBuilder: profiler,
handleWG: async.MakeWaitGroup(panicHandler),
disableIMAPAuthenticate: disableIMAPAuthenticate,
panicHandler: panicHandler,
log: logrus.WithField("pkg", "gluon/session").WithField("session", sessionID),
}
}

Expand Down
10 changes: 10 additions & 0 deletions option.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,16 @@ func WithIMAPLimits(limits limits2.IMAP) Option {
}
}

type withDisableIMAPAuthenticate struct{}

func (withDisableIMAPAuthenticate) config(builder *serverBuilder) {
builder.disableIMAPAuthenticate = true
}

func WithDisableIMAPAuthenticate() Option {
return &withDisableIMAPAuthenticate{}
}

type withUIDValidityGenerator struct {
generator imap.UIDValidityGenerator
}
Expand Down
Loading
Loading