This commit is contained in:
parent
510be4f4be
commit
f6059e5804
3 changed files with 286 additions and 5 deletions
|
|
@ -159,7 +159,7 @@ in this table.
|
|||
different kinds of hashes. So you can easily migrate your old passwords without locking out users. Users with older
|
||||
schemes should get a new password if possible to increase security.
|
||||
- **quota**: The number of bytes that this mailbox can store. You can use this value to limit how much space a mailbox
|
||||
can take up. The default value is 0 which means that there is no limit. This is an optional feature that is discussed
|
||||
can take up. The default value is 0 which applies the default quota. This is an optional feature that is discussed
|
||||
later.
|
||||
|
||||
<Aside type="tip" icon="right-caret" title="Example">
|
||||
|
|
|
|||
|
|
@ -160,7 +160,9 @@ mysql /var/run/mysqld/mysqld.sock {
|
|||
}
|
||||
|
||||
userdb sql {
|
||||
query = SELECT email as user FROM virtual_users WHERE email='%{user}'
|
||||
query = SELECT email as user, \
|
||||
IF(quota > 0, CONCAT(quota, 'B'), NULL) AS quota_storage_size \
|
||||
FROM virtual_users WHERE email='%{user}'
|
||||
iterate_query = SELECT email as user FROM virtual_users
|
||||
}
|
||||
|
||||
|
|
@ -174,6 +176,15 @@ chown root:root /etc/dovecot/conf.d/99-ispmail-sql.conf
|
|||
chmod go= /etc/dovecot/conf.d/99-ispmail-sql.conf
|
||||
```
|
||||
|
||||
That configuration file defines how to get a user's data from the database. The two lookups are:
|
||||
|
||||
- `userdb sql`: get information on the user; e.g. if the user exists and what the _quota_ is
|
||||
- `passdb sql`: authenticate the user / validate the password
|
||||
|
||||
The _quota_ logic is explained and used later in this guide. _Quota_ is the amount of space that a user can use to store
|
||||
their emails. If the quota is reached then further incoming emails will get rejected until the user removes mails to
|
||||
make some space.
|
||||
|
||||
Dovecot can also read the path to a user's home directory and the user-ID and group-ID from the database. Our setup has
|
||||
a fixed schema for the home directory (`/var/vmail/DOMAIN/USER`) (as defined by `mail_home`) and the user and group are
|
||||
always `vmail` and `vmail`.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Quotas
|
||||
lastUpdated: 2025-11-01
|
||||
lastUpdated: 2025-12-28
|
||||
slug: ispmail-trixie/quotas
|
||||
sidebar:
|
||||
order: 330
|
||||
|
|
@ -8,6 +8,276 @@ sidebar:
|
|||
|
||||
import { Aside } from "@astrojs/starlight/components";
|
||||
|
||||
<Aside type="tip" title="Work in progress">
|
||||
This page is currently being rewritten. Please be patient.
|
||||
<Aside type="tip" title="Optional feature">
|
||||
This feature is completely optional. If you are eager to get finished then skip this page and maybe come back later.
|
||||
</Aside>
|
||||
|
||||
Quotas are size limits for users. You can make sure that users do not waste arbitrary amounts of disk space but are
|
||||
forced to clean up old emails every now and then.
|
||||
|
||||
The magic happens in two places:
|
||||
|
||||
1. Dovecot needs to keep track of the quota and how much the user has already used up of it.
|
||||
1. Postfix needs to reject new emails if the user’s mailbox is over quota.
|
||||
|
||||
## Dovecot
|
||||
|
||||
### Enable quota service
|
||||
|
||||
As usual we need a new piece of configuration to enable the _quota_ plugin and define how we want it to behave:
|
||||
|
||||
```sh title="Run this on your server"
|
||||
cat > /etc/dovecot/conf.d/99-ispmail-quota.conf << 'EOF'
|
||||
# Enable the quota plugin
|
||||
mail_plugins {
|
||||
quota = yes
|
||||
}
|
||||
|
||||
# Set global defaults. Keep this outside of a "quota {}" section so that it
|
||||
# can be overriden with data from the virtual_users table.
|
||||
# Allow 5 GB of space per default.
|
||||
quota_storage_size = 5G
|
||||
|
||||
# It is important to allow it so that Postfix can deliver an email to
|
||||
# bring the user above 100% of quota usage. Otherwise you risk that the user
|
||||
# is at 99.99% of quota but the last email cannot be delivered and gets stuck
|
||||
# between Postfix and Dovecot. No email should be larger than this.
|
||||
quota_storage_grace = 50M
|
||||
|
||||
# Define two warnings at 80% and 95% of quota usage.
|
||||
quota "User quota" {
|
||||
# Set another warning level to 80% for early warnings
|
||||
warning warn-80 {
|
||||
quota_storage_percentage = 80
|
||||
execute quota-warning {
|
||||
args = 80 %{user}
|
||||
}
|
||||
}
|
||||
|
||||
# Set one warning level to 95%
|
||||
warning warn-95 {
|
||||
quota_storage_percentage = 95
|
||||
execute quota-warning {
|
||||
args = 95 %{user}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Define what happens if the user goes over quota. Point to a script that
|
||||
# creates a warning email to the user. The name "quota-warning" corresponds
|
||||
# to the "execute" statement in the warning definitions.
|
||||
service quota-warning {
|
||||
executable = script /usr/local/bin/ispmail-quota-warning.sh
|
||||
unix_listener quota-warning {
|
||||
mode = 0660
|
||||
user = vmail
|
||||
group = vmail
|
||||
}
|
||||
}
|
||||
|
||||
# Add a new service so that Postfix can check if a user is over quota.
|
||||
# It listens on TCP port 13373.
|
||||
service quota-status {
|
||||
executable = quota-status -p postfix
|
||||
inet_listener quota-status {
|
||||
listen = 127.0.0.1
|
||||
port = 13373
|
||||
}
|
||||
client_limit = 1
|
||||
}
|
||||
|
||||
# Enable the IMAP QUOTA extension, allowing IMAP clients to ask for the
|
||||
# current quota usage. Roundcube displays the current space usage in
|
||||
# the lower left corner.
|
||||
protocol imap {
|
||||
mail_plugins {
|
||||
imap_quota = yes
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Apply the configuration
|
||||
systemctl reload dovecot
|
||||
```
|
||||
|
||||
The script that is launched by the `quota-warning` service is pretty simple. It sends an email to the affected user. It
|
||||
uses the special way of `dovecot-lda -o quota_enforce=no` though so that the warning email itself is always delivered
|
||||
even if the user is over quota.
|
||||
|
||||
```sh title="Run this on your server"
|
||||
cat > /usr/local/bin/ispmail-quota-warning.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
#
|
||||
# This script is part of the ISPmail configuration.
|
||||
# It is called from /etc/dovecot/conf.d/99-ispmail-quota.conf
|
||||
# and sends an email to the user if the quota is at 80%, over 100%
|
||||
# or back below 100%.
|
||||
#
|
||||
# Christoph Haas <ispmail@christoph-haas.de>
|
||||
# Version 1.0 – 2025-12-30
|
||||
|
||||
LEVEL=$1
|
||||
USER=$2
|
||||
MAIL="From: postmaster@${USER#*@}"
|
||||
MAIL+="\nSubject: Quota information"
|
||||
MAIL+="\nX-Priority: 1"
|
||||
MAIL+="\n\nThis is an automatic email from your friendly mail server.\n\n"
|
||||
|
||||
if [ $LEVEL = "below" ]; then
|
||||
MAIL+="Thanks for making some space. You will now be able to receive emails again."
|
||||
elif [ $LEVEL -eq 80 ]; then
|
||||
MAIL+="Your mailbox is currently using up 80% of the maximum space."
|
||||
MAIL+="\nPlease consider deleting some emails. If your used space reached"
|
||||
MAIL+="\n100% then you would not be able to receive further emails. I will"
|
||||
MAIL+="\nhowever send you another email in that case."
|
||||
MAIL+="\n"
|
||||
MAIL+="\nThanks for your attention."
|
||||
elif [ $LEVEL -eq 100 ]; then
|
||||
MAIL+="I am afraid to tell you that your mailbox is now 100% full."
|
||||
MAIL+="\nNew emails to your address will be rejected by the server."
|
||||
MAIL+="\nPlease delete some emails immediately. I will send you another"
|
||||
MAIL+="\nemail once you have made some space."
|
||||
MAIL+="\n"
|
||||
MAIL+="\nThanks for your attention."
|
||||
fi
|
||||
|
||||
echo -e $MAIL | /usr/lib/dovecot/dovecot-lda -d $USER -o quota_enforce=no
|
||||
EOF
|
||||
|
||||
chmod u=rwx,g=rx,o= /usr/local/bin/ispmail-quota-warning.sh
|
||||
chown vmail:vmail /usr/local/bin/ispmail-quota-warning.sh
|
||||
```
|
||||
|
||||
Please change `@domain.com` to your main domain.
|
||||
|
||||
### Global versus local
|
||||
|
||||
By default you will probably allow all users to use a certain amount of disk space for their emails. That would be the
|
||||
global value that you set using `quota_storage_size = …`.
|
||||
|
||||
But maybe you have some special users that need a different value. For a power user you could allow more space than for
|
||||
other users. Then set the `quota` field in his record in the `virtual_users` table to a value larger than 0.
|
||||
|
||||
So the logic for the `quota` field in the database is:
|
||||
|
||||
- **0** means: take the global setting of `quota_storage_size`
|
||||
- **anything else**: override by this value (in bytes)
|
||||
|
||||
We added that logic into the `userdb sql` section earlier:
|
||||
|
||||
```sql
|
||||
IF(quota > 0, CONCAT(quota, 'B'), NULL) AS quota_storage_size
|
||||
```
|
||||
|
||||
That's the SQL way of saying: if "quota" is 0, then return NULL. NULL tells Dovecot to fall back to the global value.
|
||||
Otherwise add a "B" (for "bytes" – Dovecot requires that) and return that.
|
||||
|
||||
### Showing current usage
|
||||
|
||||
Now that quota restrictions are in place, you can ask Dovecot for the current status of all mailboxes:
|
||||
|
||||
```sh title="Run this on your server"
|
||||
doveadm quota get -A
|
||||
```
|
||||
|
||||
You will get output like this:
|
||||
|
||||
```
|
||||
Username Quota name Type Value Limit %
|
||||
john@example.org User quota STORAGE 22856 1048576 2
|
||||
john@example.org User quota MESSAGE 19 - 0
|
||||
```
|
||||
|
||||
John is shown twice because Dovecot keeps track of:
|
||||
|
||||
- the **amount of storage** John uses (`STORAGE`)
|
||||
- the **number of emails** that John has (`MESSAGE`)
|
||||
|
||||
The current configuration just cares about the space that is used on disk. So in the above example he has 22,856 KB used
|
||||
of the 1 GB limit – which is 2%. And he has a total of 19 messages but there is no limit to that.
|
||||
|
||||
### Testing the quota service (optional)
|
||||
|
||||
You may wonder why there is a `quota-status` service. If a user is over quota then it would reject an more incoming
|
||||
emails. But Postfix – which sits in front front of Dovecot – would still happily accept more emails. For a sender on a
|
||||
remote server it looks like the email was delivered. But it's now stuck between Postfix and Dovecot until the user makes
|
||||
some space.
|
||||
|
||||
The obvious solution: Postfix needs to know if a user is over quota so that it can reject further emails. That's the
|
||||
purpose of the `quota-status` service which is now listening on TCP port 13373 (on the _local_ interface) for such
|
||||
queries. The protocol is very simple and you can even play with it as a human:
|
||||
|
||||
```sh
|
||||
echo -e "recipient=john@example.org\n" | nc localhost 13373
|
||||
```
|
||||
|
||||
And the service will respond with:
|
||||
|
||||
```
|
||||
action=OK
|
||||
```
|
||||
|
||||
The response tells you whether the user is over quota:
|
||||
|
||||
- `action=OK` or
|
||||
- `action=554 5.2.2 Quota exceeded (mailbox for user is full)`
|
||||
|
||||
## Postfix
|
||||
|
||||
Telling Postfix to use the `quota-status` service is suprisingly simple:
|
||||
|
||||
```sh title="Run this on your server"
|
||||
postconf smtpd_recipient_restrictions="check_policy_service inet:localhost:13373"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Test it
|
||||
|
||||
If you are curious to see this working, then set John’s mailbox quota to 5 KB:
|
||||
|
||||
```sql
|
||||
# mariadb mailserver
|
||||
mysql> update virtual_users set quota=5120 where email='john@example.org';
|
||||
```
|
||||
|
||||
Verify that he now has a limit of 5 KB:
|
||||
|
||||
```sh
|
||||
doveadm quota get -u john@example.org
|
||||
```
|
||||
|
||||
Send him a few emails using the ‘swaks’ tool:
|
||||
|
||||
```
|
||||
swaks --server localhost --to john@example.org -tls
|
||||
```
|
||||
|
||||
After a few emails you will see the rejection message:
|
||||
|
||||
```
|
||||
-> RCPT TO:john@example.org
|
||||
<** 452 4.2.2 john@example.org: Recipient address rejected: Mailbox is full and cannot receive any more emails
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
These are things you should consider if quotas do not seem to work properly:
|
||||
|
||||
- Check that your `/etc/dovecot/conf.d/99-ispmail-sql.conf` file looks as [described]
|
||||
(/ispmail-trixie/dovecot#99-ispmail-sqlconf). Earlier version of this Trixie guide did not contain the
|
||||
`quota_storage_size` field in the `userdb sql` query.
|
||||
- Run `tcpdump -Ai lo port 13373` on the server to see if Postfix talks to the `quota-status` service.
|
||||
- Your users may complain that they have deleted many emails but are still over quota. Let them check if they actually
|
||||
emptied the _Trash_ folder. Of course emails in that folder also contribute to the disk space usage. Once the Trash
|
||||
folder is expunged the problem should be gone. You may also allow your users more space in the Trash folder. That’s
|
||||
explained in the [Dovecot documentation](https://doc.dovecot.org/configuration_manual/quota/#quota-rules).
|
||||
- If you directly remove files from a user’s Maildir instead of properly accessing the mailbox using IMAP then you will
|
||||
screw up the quota calculation. In that case let Dovecot recalculate the quota:
|
||||
`doveadm quota recalc -u john@example.org`
|
||||
|
||||
### See also
|
||||
|
||||
- https://doc.dovecot.org/2.4.2/core/plugins/quota.html#quota-service
|
||||
- https://sys4.de/en/blog/postfix-dovecot-mailbox-quota/
|
||||
- https://www.postfix.org/SMTPD_POLICY_README.html
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue