Compare commits

...

3 commits

Author SHA1 Message Date
bcffb86972 link typo
All checks were successful
/ build (push) Successful in 41s
2025-12-30 14:53:44 +01:00
f6059e5804 added quota section
All checks were successful
/ build (push) Successful in 40s
2025-12-30 01:18:23 +01:00
510be4f4be enable managesieve,password plugins in Roundcube
All checks were successful
/ build (push) Successful in 38s
2025-12-28 15:19:46 +01:00
4 changed files with 298 additions and 7 deletions

View file

@ -5,8 +5,11 @@
#
# License: Creative Commons BY-NC-SA license
#
# Version 0.1
# 2025-12-14
# Version 0.1 / 2025-12-14
# First release.
#
# Version 0.2 / 2025-12-28
# Enable plugins (managesieve, password) in Roundcube
#
usage() {
@ -434,6 +437,13 @@ EOF
sed -i "s|^\s*\$config\['imap_host'\]\s*=.*|\$config['imap_host'] = 'tls://$FQDN:143';|" /etc/roundcube/config.inc.php
sed -i "s|^\s*\$config\['smtp_host'\]\s*=.*|\$config['smtp_host'] = 'tls://$FQDN:587';|" /etc/roundcube/config.inc.php
# Enable plugins
sed -i "/\$config\['plugins'\] = \[/,/\];/c\\
\$config['plugins'] = [\\
'managesieve',\\
'password',\\
];" /etc/roundcube/config.inc.php
# Check if web interface is loading
curl -s https://$FQDN | grep title | grep -q "Roundcube Webmail"
if [[ $? -ne 0 ]]; then

View file

@ -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">

View file

@ -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`.

View file

@ -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 users 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 Johns 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. Thats
explained in the [Dovecot documentation](https://doc.dovecot.org/configuration_manual/quota/#quota-rules).
- If you directly remove files from a users 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