Skip to content

Commit

Permalink
feat(BRIDGE-268): add option to disable AUTHENTICATE command.
Browse files Browse the repository at this point in the history
  • Loading branch information
xmichelo committed Nov 21, 2024
1 parent 364b828 commit 300503e
Show file tree
Hide file tree
Showing 12 changed files with 230 additions and 112 deletions.
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

0 comments on commit 300503e

Please sign in to comment.