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(GODT-1970): Soft limits #242

Merged
merged 1 commit into from
Dec 6, 2022
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
6 changes: 5 additions & 1 deletion builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gluon

import (
"crypto/tls"
"github.com/ProtonMail/gluon/limits"
"io"
"os"
"path/filepath"
Expand Down Expand Up @@ -29,6 +30,7 @@ type serverBuilder struct {
storeBuilder store.Builder
reporter reporter.Reporter
disableParallelism bool
imapLimits limits.IMAP
}

func newBuilder() (*serverBuilder, error) {
Expand All @@ -37,7 +39,8 @@ func newBuilder() (*serverBuilder, error) {
cmdExecProfBuilder: &profiling.NullCmdExecProfilerBuilder{},
storeBuilder: &store.OnDiskStoreBuilder{},
reporter: &reporter.NullReporter{},
idleBulkTime: time.Duration(500 * time.Millisecond),
idleBulkTime: 500 * time.Millisecond,
imapLimits: limits.DefaultLimits(),
}, nil
}

Expand All @@ -60,6 +63,7 @@ func (builder *serverBuilder) build() (*Server, error) {
builder.storeBuilder,
builder.delim,
builder.loginJailTime,
builder.imapLimits,
)
if err != nil {
return nil, err
Expand Down
8 changes: 6 additions & 2 deletions internal/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package backend
import (
"context"
"fmt"
"github.com/ProtonMail/gluon/limits"
"path/filepath"
"sync"
"sync/atomic"
Expand Down Expand Up @@ -44,15 +45,18 @@ type Backend struct {
loginErrorCount int32
loginLock sync.Mutex
loginWG sync.WaitGroup

imapLimits limits.IMAP
}

func New(dir string, storeBuilder store.Builder, delim string, loginJailTime time.Duration) (*Backend, error) {
func New(dir string, storeBuilder store.Builder, delim string, loginJailTime time.Duration, imapLimits limits.IMAP) (*Backend, error) {
return &Backend{
dir: dir,
delim: delim,
users: make(map[string]*user),
storeBuilder: storeBuilder,
loginJailTime: loginJailTime,
imapLimits: imapLimits,
}, nil
}

Expand Down Expand Up @@ -82,7 +86,7 @@ func (b *Backend) AddUser(ctx context.Context, userID string, conn connector.Con
return err
}

user, err := newUser(ctx, userID, db, conn, storeBuilder, b.delim)
user, err := newUser(ctx, userID, db, conn, storeBuilder, b.delim, b.imapLimits)
if err != nil {
return err
}
Expand Down
14 changes: 12 additions & 2 deletions internal/backend/connector_updates.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,17 @@ func (user *user) applyMailboxCreated(ctx context.Context, update *imap.MailboxC
return fmt.Errorf("attempting to create protected mailbox (recovery)")
}

if err := user.imapLimits.CheckUIDValidity(user.globalUIDValidity); err != nil {
return err
}

if exists, err := db.ReadResult(ctx, user.db, func(ctx context.Context, client *ent.Client) (bool, error) {
if mailboxCount, err := db.GetMailboxCount(ctx, client); err != nil {
return false, err
} else if err := user.imapLimits.CheckMailBoxCount(mailboxCount); err != nil {
return false, err
}

return db.MailboxExistsWithRemoteID(ctx, client, update.Mailbox.ID)
}); err != nil {
return err
Expand Down Expand Up @@ -426,7 +436,7 @@ func (user *user) setMessageMailboxes(ctx context.Context, tx *ent.Tx, messageID

// applyMessagesAddedToMailbox adds the messages to the given mailbox.
func (user *user) applyMessagesAddedToMailbox(ctx context.Context, tx *ent.Tx, mboxID imap.InternalMailboxID, messageIDs []imap.InternalMessageID) ([]db.UIDWithFlags, error) {
messageUIDs, update, err := state.AddMessagesToMailbox(ctx, tx, mboxID, messageIDs, nil)
messageUIDs, update, err := state.AddMessagesToMailbox(ctx, tx, mboxID, messageIDs, nil, user.imapLimits)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -636,7 +646,7 @@ func (user *user) applyMessageUpdated(ctx context.Context, update *imap.MessageU
return err
}

_, update, err := state.AddMessagesToMailbox(ctx, tx, internalMBoxID, []imap.InternalMessageID{newInternalID}, nil)
_, update, err := state.AddMessagesToMailbox(ctx, tx, internalMBoxID, []imap.InternalMessageID{newInternalID}, nil, user.imapLimits)
if err != nil {
return err
}
Expand Down
7 changes: 7 additions & 0 deletions internal/backend/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package backend
import (
"context"
"fmt"
"github.com/ProtonMail/gluon/limits"
"sync"

"github.com/ProtonMail/gluon/connector"
Expand Down Expand Up @@ -39,6 +40,8 @@ type user struct {
globalUIDValidity imap.UID

recoveryMailboxID imap.InternalMailboxID

imapLimits limits.IMAP
}

func newUser(
Expand All @@ -48,6 +51,7 @@ func newUser(
conn connector.Connector,
store store.Store,
delimiter string,
imapLimits limits.IMAP,
) (*user, error) {
if err := database.Init(ctx); err != nil {
return nil, err
Expand Down Expand Up @@ -85,6 +89,8 @@ func newUser(
globalUIDValidity: conn.GetUIDValidity(),

recoveryMailboxID: recoveryMBox.ID,

imapLimits: imapLimits,
}

if err := user.deleteAllMessagesMarkedDeleted(ctx); err != nil {
Expand Down Expand Up @@ -209,6 +215,7 @@ func (user *user) newState() (*state.State, error) {
newState := state.NewState(
newStateUserInterfaceImpl(user, newStateConnectorImpl(user)),
user.delimiter,
user.imapLimits,
)

user.states[newState.StateID] = newState
Expand Down
27 changes: 27 additions & 0 deletions internal/db/mailbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,3 +427,30 @@ func IsMessageInMailbox(ctx context.Context, client *ent.Client, mboxID imap.Int
s.Select(uid.MessageColumn)
}).Exist(ctx)
}

func GetMailboxCount(ctx context.Context, client *ent.Client) (int, error) {
return client.Mailbox.Query().Count(ctx)
}

func GetMailboxUID(ctx context.Context, client *ent.Client, mboxID imap.InternalMailboxID) (imap.UID, error) {
mbox, err := client.Mailbox.Query().Where(mailbox.ID(mboxID)).Select(mailbox.FieldUIDNext).Only(ctx)
if err != nil {
return 0, err
}

return mbox.UIDNext, err
}

func GetMailboxMessageCountAndUID(ctx context.Context, client *ent.Client, mboxID imap.InternalMailboxID) (int, imap.UID, error) {
messageCount, err := GetMailboxMessageCount(ctx, client, mboxID)
if err != nil {
return 0, 0, err
}

uid, err := GetMailboxUID(ctx, client, mboxID)
if err != nil {
return 0, 0, err
}

return messageCount, uid, err
}
3 changes: 3 additions & 0 deletions internal/session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ 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/liner"
"github.com/ProtonMail/gluon/profiling"
"github.com/ProtonMail/gluon/reporter"
Expand Down Expand Up @@ -82,6 +83,8 @@ type Session struct {

/// errorCount error counter
errorCount int

imapLimits limits.IMAP
}

func New(
Expand Down
6 changes: 3 additions & 3 deletions internal/state/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ func (state *State) actionAddMessagesToMailbox(
st = state
}

messageUIDs, update, err := AddMessagesToMailbox(ctx, tx, mboxID.InternalID, internalIDs, st)
messageUIDs, update, err := AddMessagesToMailbox(ctx, tx, mboxID.InternalID, internalIDs, st, state.imapLimits)
if err != nil {
return nil, err
}
Expand All @@ -286,7 +286,7 @@ func (state *State) actionAddRecoveredMessagesToMailbox(
return nil, nil, err
}

return AddMessagesToMailbox(ctx, tx, mboxID.InternalID, internalIDs, state)
return AddMessagesToMailbox(ctx, tx, mboxID.InternalID, internalIDs, state, state.imapLimits)
}

func (state *State) actionImportRecoveredMessage(
Expand Down Expand Up @@ -536,7 +536,7 @@ func (state *State) actionMoveMessages(
return nil, err
}

messageUIDs, updates, err := MoveMessagesFromMailbox(ctx, tx, mboxFromID.InternalID, mboxToID.InternalID, internalIDs, state)
messageUIDs, updates, err := MoveMessagesFromMailbox(ctx, tx, mboxFromID.InternalID, mboxToID.InternalID, internalIDs, state, state.imapLimits)
if err != nil {
return nil, err
}
Expand Down
18 changes: 18 additions & 0 deletions internal/state/mailbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,24 @@ func (m *Mailbox) GetMessagesWithoutFlagCount(flag string) int {
}

func (m *Mailbox) AppendRegular(ctx context.Context, literal []byte, flags imap.FlagSet, date time.Time) (imap.UID, error) {
if err := m.state.db().Read(ctx, func(ctx context.Context, client *ent.Client) error {
if messageCount, uid, err := db.GetMailboxMessageCountAndUID(ctx, client, m.snap.mboxID.InternalID); err != nil {
return err
} else {
if err := m.state.imapLimits.CheckMailBoxMessageCount(messageCount, 1); err != nil {
return err
}

if err := m.state.imapLimits.CheckUIDCount(uid, 1); err != nil {
return err
}
}

return nil
}); err != nil {
return 0, err
}

var appendIntoDrafts bool

attr, err := m.Attributes(ctx)
Expand Down
16 changes: 15 additions & 1 deletion internal/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/ProtonMail/gluon/limits"
"strings"
"sync/atomic"

Expand Down Expand Up @@ -45,6 +46,8 @@ type State struct {

// invalid indicates whether this state became invalid and a clients needs to disconnect.
invalid bool

imapLimits limits.IMAP
}

var stateIDGenerator int64
Expand All @@ -53,14 +56,15 @@ func nextStateID() StateID {
return StateID(atomic.AddInt64(&stateIDGenerator, 1))
}

func NewState(user UserInterface, delimiter string) *State {
func NewState(user UserInterface, delimiter string, imapLimits limits.IMAP) *State {
return &State{
user: user,
StateID: nextStateID(),
doneCh: make(chan struct{}),
snap: nil,
delimiter: delimiter,
updatesQueue: queue.NewQueuedChannel[Update](32, 128),
imapLimits: imapLimits,
}
}

Expand Down Expand Up @@ -164,6 +168,10 @@ func (state *State) Examine(ctx context.Context, name string, fn func(*Mailbox)
}

func (state *State) Create(ctx context.Context, name string) error {
if err := state.imapLimits.CheckUIDValidity(state.user.GetGlobalUIDValidity()); err != nil {
return err
}

if strings.HasPrefix(strings.ToLower(name), ids.GluonRecoveryMailboxNameLowerCase) {
return fmt.Errorf("operation not allowed")
}
Expand All @@ -179,6 +187,12 @@ func (state *State) Create(ctx context.Context, name string) error {
}

mboxesToCreate, err := db.ReadResult(ctx, state.db(), func(ctx context.Context, client *ent.Client) ([]string, error) {
if mailboxCount, err := db.GetMailboxCount(ctx, client); err != nil {
return nil, err
} else if err := state.imapLimits.CheckMailBoxCount(mailboxCount); err != nil {
return nil, err
}

var mboxesToCreate []string
// If the mailbox name is suffixed with the server's hierarchy separator, remove the separator and still create
// the mailbox
Expand Down
30 changes: 29 additions & 1 deletion internal/state/updates_mailbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package state

import (
"context"
"github.com/ProtonMail/gluon/limits"

"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/gluon/internal/db"
Expand All @@ -19,7 +20,21 @@ func MoveMessagesFromMailbox(
mboxFromID, mboxToID imap.InternalMailboxID,
messageIDs []imap.InternalMessageID,
s *State,
imapLimits limits.IMAP,
) ([]db.UIDWithFlags, []Update, error) {
messageCount, uid, err := db.GetMailboxMessageCountAndUID(ctx, tx.Client(), mboxToID)
if err != nil {
return nil, nil, err
}

if err := imapLimits.CheckMailBoxMessageCount(messageCount, len(messageIDs)); err != nil {
return nil, nil, err
}

if err := imapLimits.CheckUIDCount(uid, len(messageIDs)); err != nil {
return nil, nil, err
}

if mboxFromID != mboxToID {
if err := db.RemoveMessagesFromMailbox(ctx, tx, messageIDs, mboxFromID); err != nil {
return nil, nil, err
Expand Down Expand Up @@ -50,7 +65,20 @@ func MoveMessagesFromMailbox(
}

// AddMessagesToMailbox adds the messages to the given mailbox.
func AddMessagesToMailbox(ctx context.Context, tx *ent.Tx, mboxID imap.InternalMailboxID, messageIDs []imap.InternalMessageID, s *State) ([]db.UIDWithFlags, Update, error) {
func AddMessagesToMailbox(ctx context.Context, tx *ent.Tx, mboxID imap.InternalMailboxID, messageIDs []imap.InternalMessageID, s *State, imapLimits limits.IMAP) ([]db.UIDWithFlags, Update, error) {
messageCount, uid, err := db.GetMailboxMessageCountAndUID(ctx, tx.Client(), mboxID)
if err != nil {
return nil, nil, err
}

if err := imapLimits.CheckMailBoxMessageCount(messageCount, len(messageIDs)); err != nil {
return nil, nil, err
}

if err := imapLimits.CheckUIDCount(uid, len(messageIDs)); err != nil {
return nil, nil, err
}

messageUIDs, err := db.AddMessagesToMailbox(ctx, tx, messageIDs, mboxID)
if err != nil {
return nil, nil, err
Expand Down
Loading