From c69571740b15375684eb8fc1caba8f3139b80c81 Mon Sep 17 00:00:00 2001 From: Christoph Haas Date: Tue, 30 Dec 2025 19:10:08 +0100 Subject: [PATCH] add quota configuration --- public/ispmail.sh | 165 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 164 insertions(+), 1 deletion(-) diff --git a/public/ispmail.sh b/public/ispmail.sh index 5a4d19c..75b6efa 100755 --- a/public/ispmail.sh +++ b/public/ispmail.sh @@ -5,12 +5,17 @@ # # License: Creative Commons BY-NC-SA license # +# Please report bugs at https://github.com/Signum/ispmail-workaround-org/issues +# # Version 0.1 / 2025-12-14 # First release. # # Version 0.2 / 2025-12-28 # Enable plugins (managesieve, password) in Roundcube # +# Version 0.3 / 2025-12-30 +# Add quota setup +# usage() { echo "Usage: $0 -f fqdn" @@ -296,7 +301,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 } @@ -885,6 +892,159 @@ EOF echo "✅ Catch-all aliases ready to use." } +############ Quota config ############# + +quota_config() { + 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 one warning level to 100% + warning warn-100 { + quota_storage_percentage = 100 + execute quota-warning { + args = 100 %{user} + } + } + + # Set another warning level to 80% for early warnings + warning warn-80 { + quota_storage_percentage = 80 + execute quota-warning { + args = 80 %{user} + } + } + + # User is no longer over quota + warning warn-under { + quota_storage_percentage = 100 + threshold = under + execute quota-warning { + args = below %{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 + +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 +# 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 + + # Apply the configuration + systemctl reload dovecot + + # Check if Dovecot tells us quota information + lines=$(doveadm quota get -A | grep -c john@example.org) + if [ $lines -ne 2 ]; then + echo "❌ The quota setup is not working. Check 'doveadm quota get -A'." + exit 1 + fi + + mariadb mailserver -Bse 'update virtual_users set quota=0 where email="john@example.org";' + quota_storage_check=$(dovecot quota get -u john@example.org | grep STORAGE | awk '{print $5}') + if [ $quota_storage_check -ne 5242880 ]; then + echo "❌ The quota storage value for John should be 5 GB. But it's not." + exit 1 + fi + + mariadb mailserver -Bse 'update virtual_users set quota=1024000 where email="john@example.org";' + quota_storage_check=$(dovecot quota get -u john@example.org | grep STORAGE | awk '{print $5}') + if [ $quota_storage_check -ne 1000 ]; then + echo "❌ The overriden quota storage value for John should be 1000 KB. But it's not." + exit 1 + fi + + echo "✅ Quotas are ready to use." +} + ############ Going-live ############# going_live() { @@ -994,6 +1154,9 @@ dkim_config banner "Setting up catch-all aliases" catchall_config +banner "Configuring quotas" +quota_config + banner "Final steps – changing dummy passwords" going_live