mirror of
https://github.com/mjl-/mox.git
synced 2025-07-12 17:44:35 +03:00
add per-account quota for total message size disk usage
so a single user cannot fill up the disk. by default, there is (still) no limit. a default can be set in the config file for all accounts, and a per-account max size can be set that would override any global setting. this does not take into account disk usage of the index database. and also not of any file system overhead.
This commit is contained in:
@ -78,4 +78,15 @@ func TestAppend(t *testing.T) {
|
||||
},
|
||||
}
|
||||
tc2.xuntagged(imapclient.UntaggedFetch{Seq: 2, Attrs: []imapclient.FetchAttr{uid2, xbs}})
|
||||
|
||||
tclimit := startArgs(t, false, false, true, true, "limit")
|
||||
defer tclimit.close()
|
||||
tclimit.client.Login("limit@mox.example", "testtest")
|
||||
tclimit.client.Select("inbox")
|
||||
// First message of 1 byte is within limits.
|
||||
tclimit.transactf("ok", "append inbox (\\Seen Label1 $label2) \" 1-Jan-2022 10:10:00 +0100\" {1+}\r\nx")
|
||||
tclimit.xuntagged(imapclient.UntaggedExists(1))
|
||||
// Second message would take account past limit.
|
||||
tclimit.transactf("no", "append inbox (\\Seen Label1 $label2) \" 1-Jan-2022 10:10:00 +0100\" {1+}\r\nx")
|
||||
tclimit.xcode("OVERQUOTA")
|
||||
}
|
||||
|
@ -58,4 +58,15 @@ func TestCopy(t *testing.T) {
|
||||
imapclient.UntaggedFetch{Seq: 3, Attrs: []imapclient.FetchAttr{imapclient.FetchUID(3), imapclient.FetchFlags(nil)}},
|
||||
imapclient.UntaggedFetch{Seq: 4, Attrs: []imapclient.FetchAttr{imapclient.FetchUID(4), imapclient.FetchFlags(nil)}},
|
||||
)
|
||||
|
||||
tclimit := startArgs(t, false, false, true, true, "limit")
|
||||
defer tclimit.close()
|
||||
tclimit.client.Login("limit@mox.example", "testtest")
|
||||
tclimit.client.Select("inbox")
|
||||
// First message of 1 byte is within limits.
|
||||
tclimit.transactf("ok", "append inbox (\\Seen Label1 $label2) \" 1-Jan-2022 10:10:00 +0100\" {1+}\r\nx")
|
||||
tclimit.xuntagged(imapclient.UntaggedExists(1))
|
||||
// Second message would take account past limit.
|
||||
tclimit.transactf("no", "copy 1:* Trash")
|
||||
tclimit.xcode("OVERQUOTA")
|
||||
}
|
||||
|
@ -243,6 +243,7 @@ func (c *conn) cmdxFetch(isUID bool, tag, cmdstr string, p *parser) {
|
||||
err := tx.Update(&mb)
|
||||
xcheckf(err, "updating mailbox counts")
|
||||
cmd.changes = append(cmd.changes, mb.ChangeCounts())
|
||||
// No need to update account total message size.
|
||||
}
|
||||
})
|
||||
|
||||
@ -349,6 +350,7 @@ func (cmd *fetchCmd) process(atts []fetchAtt) {
|
||||
m.ModSeq = cmd.xmodseq()
|
||||
err := cmd.tx.Update(m)
|
||||
xcheckf(err, "marking message as seen")
|
||||
// No need to update account total message size.
|
||||
|
||||
cmd.changes = append(cmd.changes, m.ChangeFlags(origFlags))
|
||||
}
|
||||
|
@ -2750,13 +2750,20 @@ func (c *conn) cmdAppend(tag, cmd string, p *parser) {
|
||||
Size: mw.Size,
|
||||
}
|
||||
|
||||
ok, maxSize, err := c.account.CanAddMessageSize(tx, m.Size)
|
||||
xcheckf(err, "checking quota")
|
||||
if !ok {
|
||||
// ../rfc/9051:5155
|
||||
xusercodeErrorf("OVERQUOTA", "account over maximum total message size %d", maxSize)
|
||||
}
|
||||
|
||||
mb.Add(m.MailboxCounts())
|
||||
|
||||
// Update mailbox before delivering, which updates uidnext which we mustn't overwrite.
|
||||
err = tx.Update(&mb)
|
||||
xcheckf(err, "updating mailbox counts")
|
||||
|
||||
err := c.account.DeliverMessage(c.log, tx, &m, msgFile, true, false, false)
|
||||
err = c.account.DeliverMessage(c.log, tx, &m, msgFile, true, false, false, true)
|
||||
xcheckf(err, "delivering message")
|
||||
})
|
||||
|
||||
@ -2923,10 +2930,12 @@ func (c *conn) xexpunge(uidSet *numSet, missingMailboxOK bool) (remove []store.M
|
||||
|
||||
removeIDs := make([]int64, len(remove))
|
||||
anyIDs := make([]any, len(remove))
|
||||
var totalSize int64
|
||||
for i, m := range remove {
|
||||
removeIDs[i] = m.ID
|
||||
anyIDs[i] = m.ID
|
||||
mb.Sub(m.MailboxCounts())
|
||||
totalSize += m.Size
|
||||
// Update "remove", because RetrainMessage below will save the message.
|
||||
remove[i].Expunged = true
|
||||
remove[i].ModSeq = modseq
|
||||
@ -2947,6 +2956,9 @@ func (c *conn) xexpunge(uidSet *numSet, missingMailboxOK bool) (remove []store.M
|
||||
err = tx.Update(&mb)
|
||||
xcheckf(err, "updating mailbox counts")
|
||||
|
||||
err = c.account.AddMessageSize(c.log, tx, -totalSize)
|
||||
xcheckf(err, "updating disk usage")
|
||||
|
||||
// Mark expunged messages as not needing training, then retrain them, so if they
|
||||
// were trained, they get untrained.
|
||||
for i := range remove {
|
||||
@ -3208,6 +3220,20 @@ func (c *conn) cmdxCopy(isUID bool, tag, cmd string, p *parser) {
|
||||
xserverErrorf("uid and message mismatch")
|
||||
}
|
||||
|
||||
// See if quota allows copy.
|
||||
var totalSize int64
|
||||
for _, m := range xmsgs {
|
||||
totalSize += m.Size
|
||||
}
|
||||
if ok, maxSize, err := c.account.CanAddMessageSize(tx, totalSize); err != nil {
|
||||
xcheckf(err, "checking quota")
|
||||
} else if !ok {
|
||||
// ../rfc/9051:5155
|
||||
xusercodeErrorf("OVERQUOTA", "account over maximum total message size %d", maxSize)
|
||||
}
|
||||
err = c.account.AddMessageSize(c.log, tx, totalSize)
|
||||
xcheckf(err, "updating disk usage")
|
||||
|
||||
msgs := map[store.UID]store.Message{}
|
||||
for _, m := range xmsgs {
|
||||
msgs[m.UID] = m
|
||||
|
@ -327,14 +327,14 @@ func xparseNumSet(s string) imapclient.NumSet {
|
||||
var connCounter int64
|
||||
|
||||
func start(t *testing.T) *testconn {
|
||||
return startArgs(t, true, false, true)
|
||||
return startArgs(t, true, false, true, true, "mjl")
|
||||
}
|
||||
|
||||
func startNoSwitchboard(t *testing.T) *testconn {
|
||||
return startArgs(t, false, false, true)
|
||||
return startArgs(t, false, false, true, false, "mjl")
|
||||
}
|
||||
|
||||
func startArgs(t *testing.T, first, isTLS, allowLoginWithoutTLS bool) *testconn {
|
||||
func startArgs(t *testing.T, first, isTLS, allowLoginWithoutTLS, setPassword bool, accname string) *testconn {
|
||||
limitersInit() // Reset rate limiters.
|
||||
|
||||
if first {
|
||||
@ -343,9 +343,9 @@ func startArgs(t *testing.T, first, isTLS, allowLoginWithoutTLS bool) *testconn
|
||||
mox.Context = ctxbg
|
||||
mox.ConfigStaticPath = filepath.FromSlash("../testdata/imap/mox.conf")
|
||||
mox.MustLoadConfig(true, false)
|
||||
acc, err := store.OpenAccount(pkglog, "mjl")
|
||||
acc, err := store.OpenAccount(pkglog, accname)
|
||||
tcheck(t, err, "open account")
|
||||
if first {
|
||||
if setPassword {
|
||||
err = acc.SetPassword(pkglog, "testtest")
|
||||
tcheck(t, err, "set password")
|
||||
}
|
||||
|
@ -13,11 +13,11 @@ func TestStarttls(t *testing.T) {
|
||||
tc.client.Login("mjl@mox.example", "testtest")
|
||||
tc.close()
|
||||
|
||||
tc = startArgs(t, true, true, false)
|
||||
tc = startArgs(t, true, true, false, true, "mjl")
|
||||
tc.transactf("bad", "starttls") // TLS already active.
|
||||
tc.close()
|
||||
|
||||
tc = startArgs(t, true, false, false)
|
||||
tc = startArgs(t, true, false, false, true, "mjl")
|
||||
tc.transactf("no", `login "mjl@mox.example" "testtest"`)
|
||||
tc.xcode("PRIVACYREQUIRED")
|
||||
tc.transactf("no", "authenticate PLAIN %s", base64.StdEncoding.EncodeToString([]byte("\u0000mjl@mox.example\u0000testtest")))
|
||||
|
Reference in New Issue
Block a user