From 907c05299c7799d009e37427db9a31855be4ed57 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Mon, 28 Jan 2019 09:24:09 +0200 Subject: [PATCH 01/51] Add quota support --- README.md | 12 +++ .../conf.d/15-mailboxes.conf} | 0 conf/dovecot/conf.d/20-imap.conf | 94 +++++++++++++++++++ conf/dovecot/conf.d/90-quota.conf | 84 +++++++++++++++++ conf/dovecot/dovecot-sql.conf.ext | 6 ++ conf/roundcube/config.inc.php | 37 ++++++++ setup/mail-dovecot.sh | 6 +- 7 files changed, 238 insertions(+), 1 deletion(-) rename conf/{dovecot-mailboxes.conf => dovecot/conf.d/15-mailboxes.conf} (100%) create mode 100644 conf/dovecot/conf.d/20-imap.conf create mode 100644 conf/dovecot/conf.d/90-quota.conf create mode 100644 conf/dovecot/dovecot-sql.conf.ext create mode 100644 conf/roundcube/config.inc.php diff --git a/README.md b/README.md index 300fe496..38b3ce86 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,15 @@ +Mail-in-a-Box with Quotas +========================= + +This is an experimental implementation of Main-in-a-box with quotas. Follow the directions below to install except +clone from this repository instead of the official repository. + +There is no support for quotas in control panel at the moment. To set quotas for a user you need to set the `quota` +column for the user in the `users.sqlite` database. The `quota` column is text and allows for the `M` and `G` suffixes +for megabytes and gigabytes respectively. No spaces should be used in the quota value (e.g. `2G` or `100M`). + +\[BEGIN Official README] + Mail-in-a-Box ============= diff --git a/conf/dovecot-mailboxes.conf b/conf/dovecot/conf.d/15-mailboxes.conf similarity index 100% rename from conf/dovecot-mailboxes.conf rename to conf/dovecot/conf.d/15-mailboxes.conf diff --git a/conf/dovecot/conf.d/20-imap.conf b/conf/dovecot/conf.d/20-imap.conf new file mode 100644 index 00000000..b9855b89 --- /dev/null +++ b/conf/dovecot/conf.d/20-imap.conf @@ -0,0 +1,94 @@ +## +## IMAP specific settings +## + +# If nothing happens for this long while client is IDLEing, move the connection +# to imap-hibernate process and close the old imap process. This saves memory, +# because connections use very little memory in imap-hibernate process. The +# downside is that recreating the imap process back uses some resources. +#imap_hibernate_timeout = 0 + +# Maximum IMAP command line length. Some clients generate very long command +# lines with huge mailboxes, so you may need to raise this if you get +# "Too long argument" or "IMAP command line too large" errors often. +#imap_max_line_length = 64k + +# IMAP logout format string: +# %i - total number of bytes read from client +# %o - total number of bytes sent to client +# %{fetch_hdr_count} - Number of mails with mail header data sent to client +# %{fetch_hdr_bytes} - Number of bytes with mail header data sent to client +# %{fetch_body_count} - Number of mails with mail body data sent to client +# %{fetch_body_bytes} - Number of bytes with mail body data sent to client +# %{deleted} - Number of mails where client added \Deleted flag +# %{expunged} - Number of mails that client expunged, which does not +# include automatically expunged mails +# %{autoexpunged} - Number of mails that were automatically expunged after +# client disconnected +# %{trashed} - Number of mails that client copied/moved to the +# special_use=\Trash mailbox. +# %{appended} - Number of mails saved during the session +#imap_logout_format = in=%i out=%o + +# Override the IMAP CAPABILITY response. If the value begins with '+', +# add the given capabilities on top of the defaults (e.g. +XFOO XBAR). +#imap_capability = + +# How long to wait between "OK Still here" notifications when client is +# IDLEing. +#imap_idle_notify_interval = 2 mins +imap_idle_notify_interval=4 mins + +# ID field names and values to send to clients. Using * as the value makes +# Dovecot use the default value. The following fields have default values +# currently: name, version, os, os-version, support-url, support-email. +#imap_id_send = + +# ID fields sent by client to log. * means everything. +#imap_id_log = + +# Workarounds for various client bugs: +# delay-newmail: +# Send EXISTS/RECENT new mail notifications only when replying to NOOP +# and CHECK commands. Some clients ignore them otherwise, for example OSX +# Mail ( + +## +## Quota limits +## + +# Quota limits are set using "quota_rule" parameters. To get per-user quota +# limits, you can set/override them by returning "quota_rule" extra field +# from userdb. It's also possible to give mailbox-specific limits, for example +# to give additional 100 MB when saving to Trash: + +plugin { + #quota_rule = *:storage=1G + #quota_rule2 = Trash:storage=+100M + + # LDA/LMTP allows saving the last mail to bring user from under quota to + # over quota, if the quota doesn't grow too high. Default is to allow as + # long as quota will stay under 10% above the limit. Also allowed e.g. 10M. + #quota_grace = 10%% + + # Quota plugin can also limit the maximum accepted mail size. + #quota_max_mail_size = 100M +} + +## +## Quota warnings +## + +# You can execute a given command when user exceeds a specified quota limit. +# Each quota root has separate limits. Only the command for the first +# exceeded limit is excecuted, so put the highest limit first. +# The commands are executed via script service by connecting to the named +# UNIX socket (quota-warning below). +# Note that % needs to be escaped as %%, otherwise "% " expands to empty. + +plugin { + #quota_warning = storage=95%% quota-warning 95 %u + #quota_warning2 = storage=80%% quota-warning 80 %u +} + +# Example quota-warning service. The unix listener's permissions should be +# set in a way that mail processes can connect to it. Below example assumes +# that mail processes run as vmail user. If you use mode=0666, all system users +# can generate quota warnings to anyone. +#service quota-warning { +# executable = script /usr/local/bin/quota-warning.sh +# user = dovecot +# unix_listener quota-warning { +# user = vmail +# } +#} + +## +## Quota backends +## + +# Multiple backends are supported: +# dirsize: Find and sum all the files found from mail directory. +# Extremely SLOW with Maildir. It'll eat your CPU and disk I/O. +# dict: Keep quota stored in dictionary (eg. SQL) +# maildir: Maildir++ quota +# fs: Read-only support for filesystem quota + +plugin { + quota = maildir + #quota = dirsize:User quota + #quota = maildir:User quota + #quota = dict:User quota::proxy::quota + #quota = fs:User quota +} + +# Multiple quota roots are also possible, for example this gives each user +# their own 100MB quota and one shared 1GB quota within the domain: +plugin { + #quota = dict:user::proxy::quota + #quota2 = dict:domain:%d:proxy::quota_domain + #quota_rule = *:storage=102400 + #quota2_rule = *:storage=1048576 +} diff --git a/conf/dovecot/dovecot-sql.conf.ext b/conf/dovecot/dovecot-sql.conf.ext new file mode 100644 index 00000000..497043c0 --- /dev/null +++ b/conf/dovecot/dovecot-sql.conf.ext @@ -0,0 +1,6 @@ +driver = sqlite +connect = /home/user-data/mail/users.sqlite +default_pass_scheme = SHA512-CRYPT +password_query = SELECT email as user, password FROM users WHERE email='%u'; +user_query = SELECT email AS user, "mail" as uid, "mail" as gid, "/home/user-data/mail/mailboxes/%d/%n" as home, '*:bytes=' || quota AS quota_rule FROM users WHERE email='%u'; +iterate_query = SELECT email AS user FROM users; diff --git a/conf/roundcube/config.inc.php b/conf/roundcube/config.inc.php new file mode 100644 index 00000000..86be2529 --- /dev/null +++ b/conf/roundcube/config.inc.php @@ -0,0 +1,37 @@ + array( + 'verify_peer' => false, + 'verify_peer_name' => false, + ), + ); +$config['imap_timeout'] = 15; +$config['smtp_server'] = 'tls://127.0.0.1'; +$config['smtp_port'] = 587; +$config['smtp_user'] = '%u'; +$config['smtp_pass'] = '%p'; +$config['smtp_conn_options'] = array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + ), + ); +$config['support_url'] = 'https://mailinabox.email/'; +$config['product_name'] = 'box.supplee.com Webmail'; +$config['des_key'] = 'eE4MCgtZQwgVZVBalTwPWMaC'; +$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'persistent_login', 'carddav'); +$config['skin'] = 'larry'; +$config['login_autocomplete'] = 2; +$config['password_charset'] = 'UTF-8'; +$config['junk_mbox'] = 'Spam'; +$config['quota_zero_as_unlimited'] = true; +?> diff --git a/setup/mail-dovecot.sh b/setup/mail-dovecot.sh index 4bcc53aa..46f5ae6f 100755 --- a/setup/mail-dovecot.sh +++ b/setup/mail-dovecot.sh @@ -66,7 +66,11 @@ tools/editconf.py /etc/dovecot/conf.d/10-mail.conf \ first_valid_uid=0 # Create, subscribe, and mark as special folders: INBOX, Drafts, Sent, Trash, Spam and Archive. -cp conf/dovecot-mailboxes.conf /etc/dovecot/conf.d/15-mailboxes.conf +cp conf/dovecot/conf.d/15-mailboxes.conf /etc/dovecot/conf.d/ +cp conf/dovecot/conf.d/20-imap.conf /etc/dovecot/conf.d/ +cp conf/dovecot/conf.d/90-quota.conf /etc/dovecot/conf.d/ +cp conf/dovecot/dovecot-sql.conf.ext /etc/dovecot/ +cp conf/roundcube/config.inc.php /usr/local/lib/roundcubemail/config/ # ### IMAP/POP From 0b68bf87609edf4711b21dab621ae109d229606a Mon Sep 17 00:00:00 2001 From: John Supplee Date: Mon, 28 Jan 2019 09:38:32 +0200 Subject: [PATCH 02/51] put roundcube config change in the right place --- setup/mail-dovecot.sh | 1 - setup/webmail.sh | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/setup/mail-dovecot.sh b/setup/mail-dovecot.sh index 46f5ae6f..2c4378a6 100755 --- a/setup/mail-dovecot.sh +++ b/setup/mail-dovecot.sh @@ -70,7 +70,6 @@ cp conf/dovecot/conf.d/15-mailboxes.conf /etc/dovecot/conf.d/ cp conf/dovecot/conf.d/20-imap.conf /etc/dovecot/conf.d/ cp conf/dovecot/conf.d/90-quota.conf /etc/dovecot/conf.d/ cp conf/dovecot/dovecot-sql.conf.ext /etc/dovecot/ -cp conf/roundcube/config.inc.php /usr/local/lib/roundcubemail/config/ # ### IMAP/POP diff --git a/setup/webmail.sh b/setup/webmail.sh index b0e11c9b..078f4143 100755 --- a/setup/webmail.sh +++ b/setup/webmail.sh @@ -127,7 +127,7 @@ cat > $RCM_CONFIG < +\$config['quota_zero_as_unlimited'] = true; EOF # Configure CardDav From d443135155cb872b1e446c433bd3e7ccea316c50 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Mon, 28 Jan 2019 11:23:49 +0200 Subject: [PATCH 03/51] changes to users.sqlite * add quota column * modify users_query to return quota_rule --- setup/mail-dovecot.sh | 1 - setup/mail-users.sh | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/setup/mail-dovecot.sh b/setup/mail-dovecot.sh index 2c4378a6..ac18adfd 100755 --- a/setup/mail-dovecot.sh +++ b/setup/mail-dovecot.sh @@ -69,7 +69,6 @@ tools/editconf.py /etc/dovecot/conf.d/10-mail.conf \ cp conf/dovecot/conf.d/15-mailboxes.conf /etc/dovecot/conf.d/ cp conf/dovecot/conf.d/20-imap.conf /etc/dovecot/conf.d/ cp conf/dovecot/conf.d/90-quota.conf /etc/dovecot/conf.d/ -cp conf/dovecot/dovecot-sql.conf.ext /etc/dovecot/ # ### IMAP/POP diff --git a/setup/mail-users.sh b/setup/mail-users.sh index e54485bb..d5f45417 100755 --- a/setup/mail-users.sh +++ b/setup/mail-users.sh @@ -20,7 +20,7 @@ db_path=$STORAGE_ROOT/mail/users.sqlite # Create an empty database if it doesn't yet exist. if [ ! -f $db_path ]; then echo Creating new user database: $db_path; - echo "CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT NOT NULL UNIQUE, password TEXT NOT NULL, extra, privileges TEXT NOT NULL DEFAULT '');" | sqlite3 $db_path; + echo "CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT NOT NULL UNIQUE, password TEXT NOT NULL, extra, privileges TEXT NOT NULL DEFAULT '', quota TEXT NOT NULL DEFAULT '0');" | sqlite3 $db_path; echo "CREATE TABLE aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, source TEXT NOT NULL UNIQUE, destination TEXT NOT NULL, permitted_senders TEXT);" | sqlite3 $db_path; fi @@ -49,7 +49,7 @@ driver = sqlite connect = $db_path default_pass_scheme = SHA512-CRYPT password_query = SELECT email as user, password FROM users WHERE email='%u'; -user_query = SELECT email AS user, "mail" as uid, "mail" as gid, "$STORAGE_ROOT/mail/mailboxes/%d/%n" as home FROM users WHERE email='%u'; +user_query = SELECT email AS user, "mail" as uid, "mail" as gid, "$STORAGE_ROOT/mail/mailboxes/%d/%n" as home, '*:bytes=' || quota AS quota_rule FROM users WHERE email='%u'; iterate_query = SELECT email AS user FROM users; EOF chmod 0600 /etc/dovecot/dovecot-sql.conf.ext # per Dovecot instructions From 3cd14bd4b8e1b6573b76800c173687f29be11aa0 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Mon, 28 Jan 2019 16:19:36 +0200 Subject: [PATCH 04/51] Add display of quotas --- management/mailconfig.py | 3 ++- management/templates/users.html | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/management/mailconfig.py b/management/mailconfig.py index 28e1c623..469a33a3 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -128,13 +128,14 @@ def get_mail_users_ex(env, with_archived=False): users = [] active_accounts = set() c = open_database(env) - c.execute('SELECT email, privileges FROM users') + c.execute('SELECT email, privileges, quota FROM users') for email, privileges in c.fetchall(): active_accounts.add(email) user = { "email": email, "privileges": parse_privs(privileges), + "user": quota, "status": "active", } users.append(user) diff --git a/management/templates/users.html b/management/templates/users.html index dee79d42..09a66bc6 100644 --- a/management/templates/users.html +++ b/management/templates/users.html @@ -42,6 +42,7 @@ Email Address + Quota Actions @@ -54,6 +55,8 @@ + + @@ -152,7 +155,8 @@ function show_users() { n2.addClass("account_" + user.status); n.attr('data-email', user.email); - n.find('.address').text(user.email) + n.find('.address').text(user.email); + n.find('.quota').text(user.quota); n2.find('.restore_info tt').text(user.mailbox); if (user.status == 'inactive') continue; From 587f33b6c03eb8e31dd27100e99a01e104be2be7 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Mon, 28 Jan 2019 16:34:34 +0200 Subject: [PATCH 05/51] bug fix for displaying quotas --- management/mailconfig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management/mailconfig.py b/management/mailconfig.py index 469a33a3..d9c89eb2 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -129,7 +129,7 @@ def get_mail_users_ex(env, with_archived=False): active_accounts = set() c = open_database(env) c.execute('SELECT email, privileges, quota FROM users') - for email, privileges in c.fetchall(): + for email, privileges, quota in c.fetchall(): active_accounts.add(email) user = { From dad22f726149a17a8132b67580b1fc7b092a8faa Mon Sep 17 00:00:00 2001 From: John Supplee Date: Mon, 28 Jan 2019 17:17:43 +0200 Subject: [PATCH 06/51] bug fix --- management/mailconfig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management/mailconfig.py b/management/mailconfig.py index d9c89eb2..5d5f5343 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -135,7 +135,7 @@ def get_mail_users_ex(env, with_archived=False): user = { "email": email, "privileges": parse_privs(privileges), - "user": quota, + "quota": quota, "status": "active", } users.append(user) From b557e69313fdfd5fc18b2f07d735664c0013e0ce Mon Sep 17 00:00:00 2001 From: John Supplee Date: Mon, 28 Jan 2019 17:24:11 +0200 Subject: [PATCH 07/51] display unlimited when quota is 0 --- management/mailconfig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management/mailconfig.py b/management/mailconfig.py index 5d5f5343..783461ec 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -135,7 +135,7 @@ def get_mail_users_ex(env, with_archived=False): user = { "email": email, "privileges": parse_privs(privileges), - "quota": quota, + "quota": 'unlimited' if quota == '0' else quota, "status": "active", } users.append(user) From e2d71d4636f11adadbb5e95f1ab02640b623d088 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Mon, 28 Jan 2019 17:30:21 +0200 Subject: [PATCH 08/51] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 38b3ce86..7c78198d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Mail-in-a-Box with Quotas ========================= -This is an experimental implementation of Main-in-a-box with quotas. Follow the directions below to install except +This is an experimental implementation of Mail-in-a-box with quotas. Follow the directions below to install except clone from this repository instead of the official repository. There is no support for quotas in control panel at the moment. To set quotas for a user you need to set the `quota` From 8b95fac8c56b643e55994a2e0df6f5ed3667520e Mon Sep 17 00:00:00 2001 From: John Supplee Date: Mon, 28 Jan 2019 19:25:26 +0200 Subject: [PATCH 09/51] remove unnecessary configuration files --- conf/dovecot/dovecot-sql.conf.ext | 6 ----- conf/roundcube/config.inc.php | 37 ------------------------------- 2 files changed, 43 deletions(-) delete mode 100644 conf/dovecot/dovecot-sql.conf.ext delete mode 100644 conf/roundcube/config.inc.php diff --git a/conf/dovecot/dovecot-sql.conf.ext b/conf/dovecot/dovecot-sql.conf.ext deleted file mode 100644 index 497043c0..00000000 --- a/conf/dovecot/dovecot-sql.conf.ext +++ /dev/null @@ -1,6 +0,0 @@ -driver = sqlite -connect = /home/user-data/mail/users.sqlite -default_pass_scheme = SHA512-CRYPT -password_query = SELECT email as user, password FROM users WHERE email='%u'; -user_query = SELECT email AS user, "mail" as uid, "mail" as gid, "/home/user-data/mail/mailboxes/%d/%n" as home, '*:bytes=' || quota AS quota_rule FROM users WHERE email='%u'; -iterate_query = SELECT email AS user FROM users; diff --git a/conf/roundcube/config.inc.php b/conf/roundcube/config.inc.php deleted file mode 100644 index 86be2529..00000000 --- a/conf/roundcube/config.inc.php +++ /dev/null @@ -1,37 +0,0 @@ - array( - 'verify_peer' => false, - 'verify_peer_name' => false, - ), - ); -$config['imap_timeout'] = 15; -$config['smtp_server'] = 'tls://127.0.0.1'; -$config['smtp_port'] = 587; -$config['smtp_user'] = '%u'; -$config['smtp_pass'] = '%p'; -$config['smtp_conn_options'] = array( - 'ssl' => array( - 'verify_peer' => false, - 'verify_peer_name' => false, - ), - ); -$config['support_url'] = 'https://mailinabox.email/'; -$config['product_name'] = 'box.supplee.com Webmail'; -$config['des_key'] = 'eE4MCgtZQwgVZVBalTwPWMaC'; -$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'persistent_login', 'carddav'); -$config['skin'] = 'larry'; -$config['login_autocomplete'] = 2; -$config['password_charset'] = 'UTF-8'; -$config['junk_mbox'] = 'Spam'; -$config['quota_zero_as_unlimited'] = true; -?> From ccad47937e509d065fbe4bc9173f5ce4a391a0ab Mon Sep 17 00:00:00 2001 From: John Supplee Date: Mon, 28 Jan 2019 23:27:03 +0200 Subject: [PATCH 10/51] Add components to user interface for setting quotas --- management/daemon.py | 10 ++++++++- management/mailconfig.py | 27 ++++++++++++++++++++++- management/templates/users.html | 39 +++++++++++++++++++++++++++++++-- 3 files changed, 72 insertions(+), 4 deletions(-) diff --git a/management/daemon.py b/management/daemon.py index 572b6b4a..5b3bd326 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -9,7 +9,7 @@ import auth, utils, multiprocessing.pool from mailconfig import get_mail_users, get_mail_users_ex, get_admins, add_mail_user, set_mail_password, remove_mail_user from mailconfig import get_mail_user_privileges, add_remove_mail_user_privilege from mailconfig import get_mail_aliases, get_mail_aliases_ex, get_mail_domains, add_mail_alias, remove_mail_alias - +from mailconfig import set_mail_quota env = utils.load_environment() auth_service = auth.KeyAuthService() @@ -158,6 +158,14 @@ def mail_users_add(): except ValueError as e: return (str(e), 400) +@app.route('/mail/users/quota', methods=['POST']) +@authorized_personnel_only +def mail_users_quota(): + try: + return set_mail_quota(request.form.get('email', ''), request.form.get('quota'), env) + except ValueError as e: + return (str(e), 400) + @app.route('/mail/users/password', methods=['POST']) @authorized_personnel_only def mail_users_password(): diff --git a/management/mailconfig.py b/management/mailconfig.py index 783461ec..62ca1d39 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -135,7 +135,7 @@ def get_mail_users_ex(env, with_archived=False): user = { "email": email, "privileges": parse_privs(privileges), - "quota": 'unlimited' if quota == '0' else quota, + "quota": quota, "status": "active", } users.append(user) @@ -333,6 +333,31 @@ def hash_password(pw): # http://wiki2.dovecot.org/Authentication/PasswordSchemes return utils.shell('check_output', ["/usr/bin/doveadm", "pw", "-s", "SHA512-CRYPT", "-p", pw]).strip() +def set_mail_quota(email, quota, env): + # validate that password is acceptable + quota = validate_quota(quota) + + # update the database + conn, c = open_database(env, with_connection=True) + c.execute("UPDATE users SET quota=? WHERE email=?", (quota, email)) + if c.rowcount != 1: + return ("That's not a user (%s)." % email, 400) + conn.commit() + return "OK" + +def validate_quota(quota): + # validate quota + quota = quota.strip().upper() + + if quota == "": + raise ValueError("No quota provided.") + if re.search(r"[\s,.]", quota): + raise ValueError("Quotas cannot contain spaces or commas.") + if not re.match(r'^\d+[GM]?$', quota): + raise ValueError("Invalid quota.") + + return quota + def get_mail_password(email, env): # Gets the hashed password for a user. Passwords are stored in Dovecot's # password format, with a prefixed scheme. diff --git a/management/templates/users.html b/management/templates/users.html index 09a66bc6..40f3be08 100644 --- a/management/templates/users.html +++ b/management/templates/users.html @@ -41,7 +41,7 @@ - + @@ -61,6 +61,10 @@ + + set quota + + | set password @@ -155,8 +159,9 @@ function show_users() { n2.addClass("account_" + user.status); n.attr('data-email', user.email); + n.attr('data-quota', user.quota); n.find('.address').text(user.email); - n.find('.quota').text(user.quota); + n.find('.quota').text((user.quota == '0') ? 'unlimited' : user.quota); n2.find('.restore_info tt').text(user.mailbox); if (user.status == 'inactive') continue; @@ -233,6 +238,36 @@ function users_set_password(elem) { }); } +function users_set_quota(elem) { + var email = $(elem).parents('tr').attr('data-email'); + var quota = $(elem).parents('tr').attr('data-quota'); + + show_modal_confirm( + "Set Quota", + $("

Set quota for " + email + "?

" + + "

" + + "" + + "

" + + "

Quotas may not contain any numbers or commas. Suffixes of G (gigabytes) and M (megabytes) are allowed.

" + + "

For unlimited storage enter 0 (zero)

"), + "Set Quota", + function() { + api( + "/mail/users/quota", + "POST", + { + email: email, + quota: $('#users_set_quota').val() + }, + function(r) { + show_users(); + }, + function(r) { + show_modal_error("Set Quota", r); + }); + }); +} + function users_remove(elem) { var email = $(elem).parents('tr').attr('data-email'); From 832d2ca3756877c907214fcd5bc12acc8a453c72 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Mon, 28 Jan 2019 23:38:04 +0200 Subject: [PATCH 11/51] Update README to note support for control panel --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c78198d..36f298e9 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Mail-in-a-Box with Quotas This is an experimental implementation of Mail-in-a-box with quotas. Follow the directions below to install except clone from this repository instead of the official repository. -There is no support for quotas in control panel at the moment. To set quotas for a user you need to set the `quota` +There is baisc support for quotas in the control panel now. To set quotas from the command line, set the `quota` column for the user in the `users.sqlite` database. The `quota` column is text and allows for the `M` and `G` suffixes for megabytes and gigabytes respectively. No spaces should be used in the quota value (e.g. `2G` or `100M`). From 3840443159987984a08a1f282fd5bdf0e2366913 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Tue, 29 Jan 2019 13:51:18 +0200 Subject: [PATCH 12/51] enable setting quotas from the command line --- tools/mail.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tools/mail.py b/tools/mail.py index 566971e4..7a7b8b8e 100755 --- a/tools/mail.py +++ b/tools/mail.py @@ -57,9 +57,10 @@ def setup_key_auth(mgmt_uri): if len(sys.argv) < 2: print("Usage: ") - print(" tools/mail.py user (lists users)") + print(" tools/mail.py user (lists users with quotas)") print(" tools/mail.py user add user@domain.com [password]") print(" tools/mail.py user password user@domain.com [password]") + print(" tools/mail.py user quota user@domain new-quota") print(" tools/mail.py user remove user@domain.com") print(" tools/mail.py user make-admin user@domain.com") print(" tools/mail.py user remove-admin user@domain.com") @@ -81,6 +82,10 @@ elif sys.argv[1] == "user" and len(sys.argv) == 2: print(user['email'], end='') if "admin" in user['privileges']: print("*", end='') + if user['quota'] == '0': + print(" unlimited", end='') + else: + print(" " + user['quota'], end='') print() elif sys.argv[1] == "user" and sys.argv[2] in ("add", "password"): @@ -116,6 +121,10 @@ elif sys.argv[1] == "user" and sys.argv[2] == "admins": if "admin" in user['privileges']: print(user['email']) +elif sys.argv[1] == "user" and sys.argv[2] == "quota" and len(sys.argv) == 5: + # Set a user's quota + users = mgmt("/mail/users/quota", { "email": sys.argv[3], "quota": sys.argv[4] }) + elif sys.argv[1] == "alias" and len(sys.argv) == 2: print(mgmt("/mail/aliases")) From c5c7de8b2b0681c75bc306ea47389951582f170b Mon Sep 17 00:00:00 2001 From: John Supplee Date: Tue, 29 Jan 2019 17:42:17 +0200 Subject: [PATCH 13/51] allow decimal points in quotas --- management/mailconfig.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/management/mailconfig.py b/management/mailconfig.py index 62ca1d39..73b166a1 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -351,9 +351,9 @@ def validate_quota(quota): if quota == "": raise ValueError("No quota provided.") - if re.search(r"[\s,.]", quota): + if re.search(r"[\s,]", quota): raise ValueError("Quotas cannot contain spaces or commas.") - if not re.match(r'^\d+[GM]?$', quota): + if not re.match(r'^[\d\.]+[GM]?$', quota): raise ValueError("Invalid quota.") return quota From f68ef70b949f11877c4dd3d8c60f6a26bbd2dcd1 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Tue, 29 Jan 2019 19:18:48 +0200 Subject: [PATCH 14/51] Calculate and display mailbox sizes in user list --- management/mailconfig.py | 17 +++++++++++++++++ management/templates/users.html | 3 +++ 2 files changed, 20 insertions(+) diff --git a/management/mailconfig.py b/management/mailconfig.py index 73b166a1..cdd05079 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -132,10 +132,27 @@ def get_mail_users_ex(env, with_archived=False): for email, privileges, quota in c.fetchall(): active_accounts.add(email) + (user, domain) = email.split('@') + box_size = 0 + box_count = 0 + box_quota = '' + try: + with open('/home/user-data/mail/mailboxes/%s/%s/maildirsize' % (domain, user), 'r') as f: + box_quota = f.readline() + for line in f.readlines(): + (size, count) = line.split(' ') + box_size += int(size) + box_count += int(count) + except: + box_size = '?' + user = { "email": email, "privileges": parse_privs(privileges), "quota": quota, + "box_quota": box_quota, + "box_size": '%iK' % int(box_size / 1024), + "box_count": box_count, "status": "active", } users.append(user) diff --git a/management/templates/users.html b/management/templates/users.html index 40f3be08..0175b929 100644 --- a/management/templates/users.html +++ b/management/templates/users.html @@ -42,6 +42,7 @@
+ @@ -55,6 +56,7 @@ + - + + @@ -56,6 +57,7 @@ + @@ -163,6 +165,7 @@ function show_users() { n.attr('data-email', user.email); n.attr('data-quota', user.quota); n.find('.address').text(user.email); + n.find('.box-count').text(user.box_count); n.find('.box-size').text(user.box_size); n.find('.quota').text((user.quota == '0') ? 'unlimited' : user.quota); n2.find('.restore_info tt').text(user.mailbox); From a23f186c65ee743a98d0b88b162b635fd88ce717 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Wed, 30 Jan 2019 08:48:04 +0200 Subject: [PATCH 18/51] initial test config --- conf/dovecot/conf.d/90-quota.conf | 14 ++++++++++++++ setup/mail-postfix.sh | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/conf/dovecot/conf.d/90-quota.conf b/conf/dovecot/conf.d/90-quota.conf index d294c9e5..270fbaf7 100644 --- a/conf/dovecot/conf.d/90-quota.conf +++ b/conf/dovecot/conf.d/90-quota.conf @@ -68,12 +68,26 @@ plugin { plugin { quota = maildir + + quota_grace = 10% + + quota_status_success = DUNNO + quota_status_nouser = DUNNO + quota_status_overquota = "522 5.2.2 Mailbox is full" + #quota = dirsize:User quota #quota = maildir:User quota #quota = dict:User quota::proxy::quota #quota = fs:User quota } +service quota-status { + executable = quota-status -p postfix + inet_listener { + port = 12340 + } +} + # Multiple quota roots are also possible, for example this gives each user # their own 100MB quota and one shared 1GB quota within the domain: plugin { diff --git a/setup/mail-postfix.sh b/setup/mail-postfix.sh index 0c9bc97c..8aa65d76 100755 --- a/setup/mail-postfix.sh +++ b/setup/mail-postfix.sh @@ -195,7 +195,7 @@ tools/editconf.py /etc/postfix/main.cf virtual_transport=lmtp:[127.0.0.1]:10025 # "450 4.7.1 Client host rejected: Service unavailable". This is a retry code, so the mail doesn't properly bounce. #NODOC tools/editconf.py /etc/postfix/main.cf \ smtpd_sender_restrictions="reject_non_fqdn_sender,reject_unknown_sender_domain,reject_authenticated_sender_login_mismatch,reject_rhsbl_sender dbl.spamhaus.org" \ - smtpd_recipient_restrictions=permit_sasl_authenticated,permit_mynetworks,"reject_rbl_client zen.spamhaus.org",reject_unlisted_recipient,"check_policy_service inet:127.0.0.1:10023" + smtpd_recipient_restrictions=permit_sasl_authenticated,permit_mynetworks,"reject_rbl_client zen.spamhaus.org",reject_unlisted_recipient,"check_policy_service inet:127.0.0.1:10023","check_policy_service inet:127.0.0.1:12340" # Postfix connects to Postgrey on the 127.0.0.1 interface specifically. Ensure that # Postgrey listens on the same interface (and not IPv6, for instance). From fbf990092cbd3242a81a2d7afab53f4d2a1a3f02 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Wed, 30 Jan 2019 09:44:23 +0200 Subject: [PATCH 19/51] Add progress to README --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cb2cdf86..8224b7ef 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,13 @@ for megabytes and gigabytes respectively. No spaces should be used in the quota Todo ---- -* Get `postfix` to support quotas. See https://blog.sys4.de/postfix-dovecot-mailbox-quota-en.html for a start. Right - now the quota message store size is not calculated unless the user accesses the IMAP server (Dovecot). Right now postfix - does not take quotas into account before delivering a message. +* Get `postfix` to support quotas. Right now the quota message store size is not calculated unless the user accesses + the IMAP server (Dovecot). Right now postfix does not take quotas into account before delivering a message. + + * https://blog.sys4.de/postfix-dovecot-mailbox-quota-en.html + * https://linuxize.com/post/install-and-configure-postfix-and-dovecot/ + + Initial work on support for this has been added in the `postfix-quota` branch. * Allow Trash to have a grace percentage to allow users whose quota is full to delete messages. From 2bebd727c211b4bd6d590da92690f6d4250fbccb Mon Sep 17 00:00:00 2001 From: John Supplee Date: Wed, 30 Jan 2019 10:47:07 +0200 Subject: [PATCH 20/51] Fix README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8224b7ef..ef659660 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Todo ---- * Get `postfix` to support quotas. Right now the quota message store size is not calculated unless the user accesses - the IMAP server (Dovecot). Right now postfix does not take quotas into account before delivering a message. + the IMAP server (Dovecot). So `postfix` does not take quotas into account before delivering a message. * https://blog.sys4.de/postfix-dovecot-mailbox-quota-en.html * https://linuxize.com/post/install-and-configure-postfix-and-dovecot/ From 98e04bb11f7802bafc7b46c420ad10cbdc435a41 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Wed, 30 Jan 2019 12:52:02 +0200 Subject: [PATCH 21/51] Update README for first alpha release --- README.md | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ef659660..5b858004 100644 --- a/README.md +++ b/README.md @@ -8,20 +8,30 @@ There is baisc support for quotas in the control panel now. To set quotas from column for the user in the `users.sqlite` database. The `quota` column is text and allows for the `M` and `G` suffixes for megabytes and gigabytes respectively. No spaces should be used in the quota value (e.g. `2G` or `100M`). +Please report any bugs on github. + Todo ---- -* Get `postfix` to support quotas. Right now the quota message store size is not calculated unless the user accesses - the IMAP server (Dovecot). So `postfix` does not take quotas into account before delivering a message. - - * https://blog.sys4.de/postfix-dovecot-mailbox-quota-en.html - * https://linuxize.com/post/install-and-configure-postfix-and-dovecot/ - - Initial work on support for this has been added in the `postfix-quota` branch. - * Allow Trash to have a grace percentage to allow users whose quota is full to delete messages. +Changes +------- + +### .40-quota-0.1-alpha + +* First experimental release of Mail-in-a-Box for quotas. +* Quotas are working and there is basic support in the control panel and `tools/mail.py`. + + +Reference Documents +------------------- + +* https://blog.sys4.de/postfix-dovecot-mailbox-quota-en.html +* https://linuxize.com/post/install-and-configure-postfix-and-dovecot/ + + \[BEGIN Official README] Mail-in-a-Box From ede5e09ad07898eafe8eec5198b88315ac28b4cf Mon Sep 17 00:00:00 2001 From: John Supplee Date: Wed, 30 Jan 2019 15:31:15 +0200 Subject: [PATCH 22/51] make version checks from this repository --- management/status_checks.py | 2 +- setup/bootstrap.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/management/status_checks.py b/management/status_checks.py index 6f9bb1ef..0294c7ba 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -787,7 +787,7 @@ def get_latest_miab_version(): from socket import timeout try: - return re.search(b'TAG=(.*)', urlopen("https://mailinabox.email/setup.sh?ping=1", timeout=5).read()).group(1).decode("utf8") + return re.search(b'TAG=(.*)', urlopen("https://raw.githubusercontent.com/jrsupplee/mailinabox/master/setup/bootstrap.sh", timeout=5).read()).group(1).decode("utf8") except (HTTPError, URLError, timeout): return None diff --git a/setup/bootstrap.sh b/setup/bootstrap.sh index 3442499d..aadb4147 100644 --- a/setup/bootstrap.sh +++ b/setup/bootstrap.sh @@ -20,7 +20,7 @@ if [ -z "$TAG" ]; then # want to display in status checks. if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then # This machine is running Ubuntu 18.04. - TAG=v0.40 + TAG=0.40-quota-0.1-alpha elif [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" == "Ubuntu 14.04 LTS" ]; then # This machine is running Ubuntu 14.04. From e40566967e539a61e5f030ab16c391fd3f82f9f4 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Wed, 30 Jan 2019 15:35:17 +0200 Subject: [PATCH 23/51] Change up to date message --- management/status_checks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management/status_checks.py b/management/status_checks.py index 0294c7ba..e267a0ed 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -805,7 +805,7 @@ def check_miab_version(env, output): latest_ver = get_latest_miab_version() if this_ver == latest_ver: - output.print_ok("Mail-in-a-Box is up to date. You are running version %s." % this_ver) + output.print_ok("Mail-in-a-Box with quota support is up to date. You are running version %s." % this_ver) elif latest_ver is None: output.print_error("Latest Mail-in-a-Box version could not be determined. You are running version %s." % this_ver) else: From 863e8895a0e863bd55a9e7d2f169c8bea11fe6d9 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Wed, 30 Jan 2019 17:14:48 +0200 Subject: [PATCH 24/51] Add the quota column to the users table --- README.md | 12 +++++++----- setup/mail-users.sh | 4 ++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5b858004..be0fd721 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,17 @@ Mail-in-a-Box with Quotas ========================= -This is an experimental implementation of Mail-in-a-box with quotas. Follow the directions below to install except -clone from this repository instead of the official repository. +This is an experimental implementation of Mail-in-a-box with quotas. Follow the directions below to install except clone from this repository instead of the official repository. -There is baisc support for quotas in the control panel now. To set quotas from the command line, set the `quota` -column for the user in the `users.sqlite` database. The `quota` column is text and allows for the `M` and `G` suffixes -for megabytes and gigabytes respectively. No spaces should be used in the quota value (e.g. `2G` or `100M`). +There is baisc support for quotas in the control panel now. To set quotas from the command line, either use `tools/mail.py` or set the `quota` +column for the user in the `users.sqlite` database. If you modify `users` table using SQL note the following: + +* The `quota` column is text and allows for the `M` and `G` suffixes for megabytes and gigabytes respectively. +* No spaces should be used in the quota value (e.g. `2G` or `100M`). Please report any bugs on github. + Todo ---- diff --git a/setup/mail-users.sh b/setup/mail-users.sh index d5f45417..ef0a99de 100755 --- a/setup/mail-users.sh +++ b/setup/mail-users.sh @@ -22,6 +22,10 @@ if [ ! -f $db_path ]; then echo Creating new user database: $db_path; echo "CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT NOT NULL UNIQUE, password TEXT NOT NULL, extra, privileges TEXT NOT NULL DEFAULT '', quota TEXT NOT NULL DEFAULT '0');" | sqlite3 $db_path; echo "CREATE TABLE aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, source TEXT NOT NULL UNIQUE, destination TEXT NOT NULL, permitted_senders TEXT);" | sqlite3 $db_path; +else + set +e + echo "ALTER TABLE users ADD COLUMN quota TEXT NOT NULL DEFAULT '0';" | sqlite3 $db_path; + set -e fi # ### User Authentication From f58745c04d2e201b76940f0e2591b016beb59c52 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Thu, 31 Jan 2019 09:49:07 +0200 Subject: [PATCH 25/51] Update version to v0.40-quota-0.11-alpha --- setup/bootstrap.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/bootstrap.sh b/setup/bootstrap.sh index aadb4147..9be40167 100644 --- a/setup/bootstrap.sh +++ b/setup/bootstrap.sh @@ -20,7 +20,7 @@ if [ -z "$TAG" ]; then # want to display in status checks. if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then # This machine is running Ubuntu 18.04. - TAG=0.40-quota-0.1-alpha + TAG=v0.40-quota-0.11-alpha elif [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" == "Ubuntu 14.04 LTS" ]; then # This machine is running Ubuntu 14.04. From 9a9e0116a195224c45576dbe580b9c7f28bd2ab2 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Thu, 31 Jan 2019 10:23:42 +0200 Subject: [PATCH 26/51] Update to version v0.40-quota-0.12-alpha --- README.md | 24 +++++++++++++++++++++++- setup/bootstrap.sh | 2 +- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index be0fd721..1c9c2c75 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,20 @@ column for the user in the `users.sqlite` database. If you modify `users` table Please report any bugs on github. +Upgrading v0.40 to v.0.40-quota +------------------------------- + +This is experimental software. You have been warned. + +* Rename your `mailinabox` directory to something like `miab.old` + +* Clone this repository using: + + `git clone https://github.com/jrsupplee/mailinabox.git` + +* cd into `mailinabox` and run `setup/start.sh` with root privileges. + +* On occasion there are lock errors when updating `Munin`. Just re-run `setup/start.sh` until the error does not occur. Todo ---- @@ -21,7 +35,15 @@ Todo Changes ------- -### .40-quota-0.1-alpha +### v0.40-quota-0.12-alpha + +* Update README + +### v0.40-quota-0.11-alpha + +* Read latest version from this repository not the Mail-in-a-Box master repository + +### v0.40-quota-0.1-alpha * First experimental release of Mail-in-a-Box for quotas. * Quotas are working and there is basic support in the control panel and `tools/mail.py`. diff --git a/setup/bootstrap.sh b/setup/bootstrap.sh index 9be40167..cd09e8da 100644 --- a/setup/bootstrap.sh +++ b/setup/bootstrap.sh @@ -20,7 +20,7 @@ if [ -z "$TAG" ]; then # want to display in status checks. if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then # This machine is running Ubuntu 18.04. - TAG=v0.40-quota-0.11-alpha + TAG=v0.40-quota-0.12-alpha elif [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" == "Ubuntu 14.04 LTS" ]; then # This machine is running Ubuntu 14.04. From 6de34b046464c41234cb4c877f77ff31eb780974 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Thu, 31 Jan 2019 12:28:32 +0200 Subject: [PATCH 27/51] Update README to have section for installation and upgrading --- README.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1c9c2c75..4991ebb1 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,23 @@ Mail-in-a-Box with Quotas ========================= -This is an experimental implementation of Mail-in-a-box with quotas. Follow the directions below to install except clone from this repository instead of the official repository. +This is an experimental implementation of Mail-in-a-box with quota support. There is baisc support for quotas in the control panel now. To set quotas from the command line, either use `tools/mail.py` or set the `quota` -column for the user in the `users.sqlite` database. If you modify `users` table using SQL note the following: +column for the user in the `users.sqlite` database. If you modify the `users` table using SQL note the following: * The `quota` column is text and allows for the `M` and `G` suffixes for megabytes and gigabytes respectively. * No spaces should be used in the quota value (e.g. `2G` or `100M`). Please report any bugs on github. + +Installing v0.40-quota +---------------------- + +Follow the directions below for installing from a repository except clone from this repository instead of the official one. + + Upgrading v0.40 to v.0.40-quota ------------------------------- @@ -26,11 +33,25 @@ This is experimental software. You have been warned. * On occasion there are lock errors when updating `Munin`. Just re-run `setup/start.sh` until the error does not occur. + +Upgrading v.0.40-quota to a New Version +--------------------------------------- + +* Remember that this is experimental software and review the changes in the repository. + +* `cd` into the `mailinabox` directory. + +* Execute `git pull` to download the latest changes. + +* Execute `setup/start.sh` with root privileges. + + Todo ---- * Allow Trash to have a grace percentage to allow users whose quota is full to delete messages. +* Allow configuration of default quota value on installation. Changes ------- From d1906bd055064276a070a7b350e853396b23bf60 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Thu, 31 Jan 2019 22:57:04 +0200 Subject: [PATCH 28/51] Add support for a default quota value and allow setting quota when adding user --- management/daemon.py | 22 ++++++++++++++++++++-- management/mailconfig.py | 15 ++++++++++++--- management/templates/users.html | 8 +++++++- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/management/daemon.py b/management/daemon.py index 3bf95ec5..a007d6c8 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -9,7 +9,7 @@ import auth, utils, multiprocessing.pool from mailconfig import get_mail_users, get_mail_users_ex, get_admins, add_mail_user, set_mail_password, remove_mail_user from mailconfig import get_mail_user_privileges, add_remove_mail_user_privilege from mailconfig import get_mail_aliases, get_mail_aliases_ex, get_mail_domains, add_mail_alias, remove_mail_alias -from mailconfig import set_mail_quota +from mailconfig import set_mail_quota, get_default_quota, validate_quota env = utils.load_environment() auth_service = auth.KeyAuthService() @@ -154,7 +154,7 @@ def mail_users(): @authorized_personnel_only def mail_users_add(): try: - return add_mail_user(request.form.get('email', ''), request.form.get('password', ''), request.form.get('privileges', ''), env) + return add_mail_user(request.form.get('email', ''), request.form.get('password', ''), request.form.get('privileges', ''), request.form.get('quota', ''), env) except ValueError as e: return (str(e), 400) @@ -528,6 +528,24 @@ def privacy_status_set(): utils.write_settings(config, env) return "OK" +@app.route('/system/default-quota', methods=["GET"]) +@authorized_personnel_only +def default_quota_get(): + return get_default_quota(env) + +@app.route('/system/default-quota', methods=["POST"]) +@authorized_personnel_only +def default_quota_set(): + config = utils.load_settings(env) + try: + config["default-quota"] = validate_quota(request.form.get('default_quota')) + utils.write_settings(config, env) + + except ValueError as e: + return ("ERROR: %s" % str(e), 400) + + return "OK" + # MUNIN @app.route('/munin/') diff --git a/management/mailconfig.py b/management/mailconfig.py index 9cb79b0d..92f26dfc 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -298,7 +298,7 @@ def get_mail_domains(env, filter_aliases=lambda alias : True): + [get_domain(address, as_unicode=False) for address, *_ in get_mail_aliases(env) if filter_aliases(address) ] ) -def add_mail_user(email, pw, privs, env): +def add_mail_user(email, pw, privs, quota, env): # validate email if email.strip() == "": return ("No email address provided.", 400) @@ -324,6 +324,11 @@ def add_mail_user(email, pw, privs, env): validation = validate_privilege(p) if validation: return validation + try: + quota = validate_quota(quota) + except ValueError as e: + return (str(e), 400) + # get the database conn, c = open_database(env, with_connection=True) @@ -332,8 +337,8 @@ def add_mail_user(email, pw, privs, env): # add the user to the database try: - c.execute("INSERT INTO users (email, password, privileges) VALUES (?, ?, ?)", - (email, pw, "\n".join(privs))) + c.execute("INSERT INTO users (email, password, privileges, quota) VALUES (?, ?, ?, ?)", + (email, pw, "\n".join(privs)), quota) except sqlite3.IntegrityError: return ("User already exists.", 400) @@ -376,6 +381,10 @@ def set_mail_quota(email, quota, env): conn.commit() return "OK" +def get_default_quota(env): + config = utils.load_settings(env) + return config.get("default-quota", '0') + def validate_quota(quota): # validate quota quota = quota.strip().upper() diff --git a/management/templates/users.html b/management/templates/users.html index ee22b316..ba3985e0 100644 --- a/management/templates/users.html +++ b/management/templates/users.html @@ -28,6 +28,10 @@ +
+ + +
    @@ -196,13 +200,15 @@ function do_add_user() { var email = $("#adduserEmail").val(); var pw = $("#adduserPassword").val(); var privs = $("#adduserPrivs").val(); + var quota = $("#adduserQuota").val(); api( "/mail/users/add", "POST", { email: email, password: pw, - privileges: privs + privileges: privs, + quota: quota }, function(r) { // Responses are multiple lines of pre-formatted text. From 70c607e2566107463e848f15454e7b8fbb04da00 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Thu, 31 Jan 2019 23:58:10 +0200 Subject: [PATCH 29/51] more default quota work --- management/daemon.py | 18 ++++++++++-------- management/templates/users.html | 11 +++++++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/management/daemon.py b/management/daemon.py index a007d6c8..eb467681 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -531,20 +531,22 @@ def privacy_status_set(): @app.route('/system/default-quota', methods=["GET"]) @authorized_personnel_only def default_quota_get(): - return get_default_quota(env) + return json_response({ + "default-quota": get_default_quota(env) + }) @app.route('/system/default-quota', methods=["POST"]) @authorized_personnel_only def default_quota_set(): - config = utils.load_settings(env) - try: - config["default-quota"] = validate_quota(request.form.get('default_quota')) - utils.write_settings(config, env) + config = utils.load_settings(env) + try: + config["default-quota"] = validate_quota(request.form.get('default_quota')) + utils.write_settings(config, env) - except ValueError as e: - return ("ERROR: %s" % str(e), 400) + except ValueError as e: + return ("ERROR: %s" % str(e), 400) - return "OK" + return "OK" # MUNIN diff --git a/management/templates/users.html b/management/templates/users.html index ba3985e0..a4cbdce6 100644 --- a/management/templates/users.html +++ b/management/templates/users.html @@ -348,4 +348,15 @@ function generate_random_password() { show_modal_error("Random Password", "

    Here, try this:

    " + pw + " From 1a67c94db01dbf3d0760983ec0b26029d20fb361 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Fri, 1 Feb 2019 00:56:25 +0200 Subject: [PATCH 30/51] fix problem with jQuery being loaded too late --- management/templates/index.html | 2 +- management/templates/users.html | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/management/templates/index.html b/management/templates/index.html index 2c0d5a9a..77c305b8 100644 --- a/management/templates/index.html +++ b/management/templates/index.html @@ -10,6 +10,7 @@ +

    Add a mail user

    @@ -47,9 +48,10 @@
- - - + + + + @@ -62,9 +64,10 @@ - - - + + + "); + var hdr = $(""); hdr.find('h4').text(r[i].domain); $('#user_table tbody').append(hdr); @@ -180,7 +183,14 @@ function show_users() { n.attr('data-quota', user.quota); n.find('.address').text(user.email); n.find('.box-count').text(user.box_count); + if (user.box_count == '?') { + n.find('.box-count').attr('title', 'Message count is unkown') + } n.find('.box-size').text(user.box_size); + if (user.box_size == '?') { + n.find('.box-size').attr('title', 'Mailbox size is unkown') + } + n.find('.percent').text(user.percent) n.find('.quota').text((user.quota == '0') ? 'unlimited' : user.quota); n2.find('.restore_info tt').text(user.mailbox); diff --git a/tools/mail.py b/tools/mail.py index 7a7b8b8e..2807d87d 100755 --- a/tools/mail.py +++ b/tools/mail.py @@ -57,10 +57,11 @@ def setup_key_auth(mgmt_uri): if len(sys.argv) < 2: print("Usage: ") + print(" tools/mail.py system default-quota [new default]") print(" tools/mail.py user (lists users with quotas)") print(" tools/mail.py user add user@domain.com [password]") print(" tools/mail.py user password user@domain.com [password]") - print(" tools/mail.py user quota user@domain new-quota") + print(" tools/mail.py user quota user@domain [new-quota]") print(" tools/mail.py user remove user@domain.com") print(" tools/mail.py user make-admin user@domain.com") print(" tools/mail.py user remove-admin user@domain.com") @@ -121,6 +122,10 @@ elif sys.argv[1] == "user" and sys.argv[2] == "admins": if "admin" in user['privileges']: print(user['email']) +elif sys.argv[1] == "user" and sys.argv[2] == "quota" and len(sys.argv) == 4: + # Set a user's quota + print(mgmt("/mail/users/quota?text=1&email=%s" % sys.argv[3])) + elif sys.argv[1] == "user" and sys.argv[2] == "quota" and len(sys.argv) == 5: # Set a user's quota users = mgmt("/mail/users/quota", { "email": sys.argv[3], "quota": sys.argv[4] }) @@ -134,6 +139,12 @@ elif sys.argv[1] == "alias" and sys.argv[2] == "add" and len(sys.argv) == 5: elif sys.argv[1] == "alias" and sys.argv[2] == "remove" and len(sys.argv) == 4: print(mgmt("/mail/aliases/remove", { "address": sys.argv[3] })) +elif sys.argv[1] == "system" and sys.argv[2] == "default-quota" and len(sys.argv) == 3: + print(mgmt("/system/default-quota?text=1")) + +elif sys.argv[1] == "system" and sys.argv[2] == "default-quota" and len(sys.argv) == 4: + print(mgmt("/system/default-quota", { "default_quota": sys.argv[3]})) + else: print("Invalid command-line arguments.") sys.exit(1) From 2c6aea24cb09c8361698fc165c18cef639fb208b Mon Sep 17 00:00:00 2001 From: John Supplee Date: Fri, 1 Feb 2019 18:38:00 +0200 Subject: [PATCH 36/51] Update README to reflect the latest changes --- README.md | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 680ac766..84228cc3 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,19 @@ Mail-in-a-Box with Quotas This is an experimental implementation of Mail-in-a-box with quota support. -There is baisc support for quotas in the control panel now. To set quotas from the command line, either use `tools/mail.py` or set the `quota` -column for the user in the `users.sqlite` database. If you modify the `users` table using SQL note the following: +Quotas can be set and viewed in the control panel -* The `quota` column is text and allows for the `M` and `G` suffixes for megabytes and gigabytes respectively. -* No spaces should be used in the quota value (e.g. `2G` or `100M`). +To set quotas from the command line, use: + + tools/mail.py user quota + +To set the system default quota for new users, use: + + tools/mail.py system default-quota + +Mailbox size recalculation by Dovecot can be forced using the command: + + doveadm quota recalc -A Please report any bugs on github. @@ -29,9 +37,9 @@ This is experimental software. You have been warned. `git clone https://github.com/jrsupplee/mailinabox.git` -* cd into `mailinabox` and run `setup/start.sh` with root privileges. +* cd into `mailinabox` and run `setup/start.sh` with root privileges. On occasion there are lock errors when updating `Munin`. Just re-run `setup/start.sh` until the error does not occur. -* On occasion there are lock errors when updating `Munin`. Just re-run `setup/start.sh` until the error does not occur. +* Optionally execute `sudo doveadm quota recalc -A` to calculate mailbox sizes. If this is not done, a mailbox's size will be recalculated when mail is delivered to it. Upgrading v.0.40-quota to a New Version @@ -53,20 +61,21 @@ Todo * Allow configuration of default quota value on installation. + Changes ------- ### v0.40-quota-0.13-alpha -* Add a `default-quota` setting in `settings.yaml` +* Add a `default-quota` setting in `settings.yaml`. -* Add input for setting quota when entering a new user in control panel +* Add input for setting quota when entering a new user in control panel. -* Modify `tools/mail.py` to allow for setting and getting the default system quota +* Modify `tools/mail.py` to allow for setting and getting the default system quota. -* Modify `tools/mail.py` to allow for getting a user's quota setting +* Modify `tools/mail.py` to allow for getting a user's quota setting. -* Modify the mail users list in control panel to display percentage used +* Modify the mail users list in control panel to display percentage of quota used. ### v0.40-quota-0.12-alpha From d51a32bcfe52d3d6568776f0352627f24b3fd6b8 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Fri, 1 Feb 2019 18:39:47 +0200 Subject: [PATCH 37/51] update latest version to v0.40-quota-0.13-alpha --- setup/bootstrap.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/bootstrap.sh b/setup/bootstrap.sh index cd09e8da..5c02f1ea 100644 --- a/setup/bootstrap.sh +++ b/setup/bootstrap.sh @@ -20,7 +20,7 @@ if [ -z "$TAG" ]; then # want to display in status checks. if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then # This machine is running Ubuntu 18.04. - TAG=v0.40-quota-0.12-alpha + TAG=v0.40-quota-0.13-alpha elif [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" == "Ubuntu 14.04 LTS" ]; then # This machine is running Ubuntu 14.04. From 6964ed238a9d015f7502124bf615163c6c33b86a Mon Sep 17 00:00:00 2001 From: John Supplee Date: Fri, 1 Feb 2019 19:01:59 +0200 Subject: [PATCH 38/51] Show correct reference for updating versions --- README.md | 2 +- management/status_checks.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 84228cc3..ef3cf3a7 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Upgrading v.0.40-quota to a New Version * `cd` into the `mailinabox` directory. -* Execute `git pull` to download the latest changes. +* Execute `git pull --tags` to download the latest changes with tags. * Execute `setup/start.sh` with root privileges. diff --git a/management/status_checks.py b/management/status_checks.py index e267a0ed..a172b256 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -809,7 +809,7 @@ def check_miab_version(env, output): elif latest_ver is None: output.print_error("Latest Mail-in-a-Box version could not be determined. You are running version %s." % this_ver) else: - output.print_error("A new version of Mail-in-a-Box is available. You are running version %s. The latest version is %s. For upgrade instructions, see https://mailinabox.email. " + output.print_error("A new version of Mail-in-a-Box is available. You are running version %s. The latest version is %s. For upgrade instructions, see https://github.com/jrsupplee/mailinabox/blob/master/README.md. " % (this_ver, latest_ver)) def run_and_output_changes(env, pool): From 937ef47c2a63d9be4cce7fe08c4729c2d584fdbd Mon Sep 17 00:00:00 2001 From: John Supplee Date: Fri, 1 Feb 2019 21:51:49 +0200 Subject: [PATCH 39/51] Remove default quota support from the TODOs --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index ef3cf3a7..7bc4382a 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,6 @@ Todo * Allow Trash to have a grace percentage to allow users whose quota is full to delete messages. -* Allow configuration of default quota value on installation. - Changes ------- From 686a074fab462d6a3ef9017c73f5b2eaabaeb10e Mon Sep 17 00:00:00 2001 From: John Supplee Date: Tue, 5 Feb 2019 01:47:09 +0200 Subject: [PATCH 40/51] Force recalculation of a user's quota after setting a new value --- management/mailconfig.py | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/management/mailconfig.py b/management/mailconfig.py index 8e4198a6..11283c78 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -361,6 +361,8 @@ def add_mail_user(email, pw, privs, quota, env): # write databasebefore next step conn.commit() + dovecot_quota_recalc(email) + # Update things in case any new domains are added. return kick(env, "mail user added") @@ -385,6 +387,7 @@ def hash_password(pw): # http://wiki2.dovecot.org/Authentication/PasswordSchemes return utils.shell('check_output', ["/usr/bin/doveadm", "pw", "-s", "SHA512-CRYPT", "-p", pw]).strip() + def get_mail_quota(email, env): conn, c = open_database(env, with_connection=True) c.execute("SELECT quota FROM users WHERE email=?", (email,)) @@ -394,17 +397,31 @@ def get_mail_quota(email, env): return rows[0][0] -def set_mail_quota(email, quota, env): - # validate that password is acceptable - quota = validate_quota(quota) - # update the database - conn, c = open_database(env, with_connection=True) - c.execute("UPDATE users SET quota=? WHERE email=?", (quota, email)) - if c.rowcount != 1: - return ("That's not a user (%s)." % email, 400) - conn.commit() - return "OK" +def set_mail_quota(email, quota, env): + # validate that password is acceptable + quota = validate_quota(quota) + + # update the database + conn, c = open_database(env, with_connection=True) + c.execute("UPDATE users SET quota=? WHERE email=?", (quota, email)) + if c.rowcount != 1: + return ("That's not a user (%s)." % email, 400) + conn.commit() + + dovecot_quota_recalc(email) + + return "OK" + +def dovecot_quota_recalc(email): + # dovecot processes running for the user will not recognize the new quota setting + # a reload is necessary to reread the quota setting, but it will also shut down + # running dovecot processes. Email clients generally log back in when they lose + # a connection. + subprocess.call(['doveadm', 'reload']) + + # force dovecot to recalculate the quota info for the user. + subprocess.call(["doveadm", "quota", "recalc" "-u", "%s" % email]) def get_default_quota(env): config = utils.load_settings(env) From a60e1c274a84da4c7c964d200d7dca7db3247271 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Tue, 5 Feb 2019 01:47:39 +0200 Subject: [PATCH 41/51] Do not allow decimal points in quota sizes --- management/mailconfig.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/management/mailconfig.py b/management/mailconfig.py index 11283c78..f8c88a8a 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -428,17 +428,17 @@ def get_default_quota(env): return config.get("default-quota", '0') def validate_quota(quota): - # validate quota - quota = quota.strip().upper() + # validate quota + quota = quota.strip().upper() - if quota == "": - raise ValueError("No quota provided.") - if re.search(r"[\s,]", quota): - raise ValueError("Quotas cannot contain spaces or commas.") - if not re.match(r'^[\d\.]+[GM]?$', quota): - raise ValueError("Invalid quota.") + if quota == "": + raise ValueError("No quota provided.") + if re.search(r"[\s,.]", quota): + raise ValueError("Quotas cannot contain spaces, commas, or decimal points.") + if not re.match(r'^[\d]+[GM]?$', quota): + raise ValueError("Invalid quota.") - return quota + return quota def get_mail_password(email, env): # Gets the hashed password for a user. Passwords are stored in Dovecot's From d1378a55121ffa6a744df91930ea70bc8f6cf19b Mon Sep 17 00:00:00 2001 From: John Supplee Date: Tue, 5 Feb 2019 01:48:38 +0200 Subject: [PATCH 42/51] Add a thousands separator for message count --- management/templates/users.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/management/templates/users.html b/management/templates/users.html index 1d176de8..5ea45da9 100644 --- a/management/templates/users.html +++ b/management/templates/users.html @@ -182,7 +182,7 @@ function show_users() { n.attr('data-email', user.email); n.attr('data-quota', user.quota); n.find('.address').text(user.email); - n.find('.box-count').text(user.box_count); + n.find('.box-count').text((user.box_count).toLocaleString('en')); if (user.box_count == '?') { n.find('.box-count').attr('title', 'Message count is unkown') } @@ -190,7 +190,7 @@ function show_users() { if (user.box_size == '?') { n.find('.box-size').attr('title', 'Mailbox size is unkown') } - n.find('.percent').text(user.percent) + n.find('.percent').text(user.percent); n.find('.quota').text((user.quota == '0') ? 'unlimited' : user.quota); n2.find('.restore_info tt').text(user.mailbox); From 19f204a9afad6a5189ec9cf0dbdbe4484c08226f Mon Sep 17 00:00:00 2001 From: John Supplee Date: Tue, 5 Feb 2019 13:32:43 +0200 Subject: [PATCH 43/51] Do not execute a dovecot reload on every quota update --- management/mailconfig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management/mailconfig.py b/management/mailconfig.py index f8c88a8a..9a9f8407 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -418,7 +418,7 @@ def dovecot_quota_recalc(email): # a reload is necessary to reread the quota setting, but it will also shut down # running dovecot processes. Email clients generally log back in when they lose # a connection. - subprocess.call(['doveadm', 'reload']) + # subprocess.call(['doveadm', 'reload']) # force dovecot to recalculate the quota info for the user. subprocess.call(["doveadm", "quota", "recalc" "-u", "%s" % email]) From e44dd937784d8e15b07c6a206ec55b00250965e6 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Tue, 5 Feb 2019 13:34:37 +0200 Subject: [PATCH 44/51] mail setup fixes * check for quota column before trying to add it * force recalculation of quotas --- setup/mail-users.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/setup/mail-users.sh b/setup/mail-users.sh index ef0a99de..8c7be06a 100755 --- a/setup/mail-users.sh +++ b/setup/mail-users.sh @@ -22,10 +22,8 @@ if [ ! -f $db_path ]; then echo Creating new user database: $db_path; echo "CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT NOT NULL UNIQUE, password TEXT NOT NULL, extra, privileges TEXT NOT NULL DEFAULT '', quota TEXT NOT NULL DEFAULT '0');" | sqlite3 $db_path; echo "CREATE TABLE aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, source TEXT NOT NULL UNIQUE, destination TEXT NOT NULL, permitted_senders TEXT);" | sqlite3 $db_path; -else - set +e +elif sqlite3 $db_path ".schema users" | grep --invert-match quota; then echo "ALTER TABLE users ADD COLUMN quota TEXT NOT NULL DEFAULT '0';" | sqlite3 $db_path; - set -e fi # ### User Authentication @@ -157,4 +155,5 @@ EOF restart_service postfix restart_service dovecot - +# force a recalculation of all user quotas +doveadm quota recalc -A From 44a31733ac4736712a260c7cff42cccc5a33e5b4 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Tue, 5 Feb 2019 13:36:53 +0200 Subject: [PATCH 45/51] update latest version and update README --- README.md | 25 ++++++++++++++++--------- setup/bootstrap.sh | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7bc4382a..1c148a63 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,7 @@ This is experimental software. You have been warned. `git clone https://github.com/jrsupplee/mailinabox.git` -* cd into `mailinabox` and run `setup/start.sh` with root privileges. On occasion there are lock errors when updating `Munin`. Just re-run `setup/start.sh` until the error does not occur. - -* Optionally execute `sudo doveadm quota recalc -A` to calculate mailbox sizes. If this is not done, a mailbox's size will be recalculated when mail is delivered to it. +* cd into `mailinabox` and run `sudo setup/start.sh` On occasion there are lock errors when updating `Munin`. Just re-run `sudo setup/start.sh` until the error does not occur. Upgrading v.0.40-quota to a New Version @@ -49,20 +47,30 @@ Upgrading v.0.40-quota to a New Version * `cd` into the `mailinabox` directory. -* Execute `git pull --tags` to download the latest changes with tags. +* Execute `git pull` to download the latest changes. -* Execute `setup/start.sh` with root privileges. +* Execute `sudo bash setup/bootstrap.sh` to checkout the latest version and re-run setup. -Todo ----- +Issues +------ -* Allow Trash to have a grace percentage to allow users whose quota is full to delete messages. +* When a user's quota is changed, any IMAP session running for that user will not recognize the new quota. To solve this a `dovecot reload` could be issued causing all current IMAP sessions to be terminated. On a system with many users, it might not be desirable to reset all users sessions to fix the quota for one user. Also if the administrator is setting the quota for several users it would result in the continual reset of those connections. Changes ------- +### v0.40-quota-0.14-alpha + +* When updating a user's quota, execute `doveadm quota recalc -u ` to forces an immediate recalculation of the user's quota. + +* Add a thousands separator (,) to the messages count in the control panel user list. + +* Execute `doveadm quota recalc -A` to force a recalculation of all user quotas when running `start.sh`. + +* Get rid of the error message complaining that the `quota` column already exists when upgrading from a previous version of `v0.40-quota`. + ### v0.40-quota-0.13-alpha * Add a `default-quota` setting in `settings.yaml`. @@ -75,7 +83,6 @@ Changes * Modify the mail users list in control panel to display percentage of quota used. - ### v0.40-quota-0.12-alpha * Update README diff --git a/setup/bootstrap.sh b/setup/bootstrap.sh index 5c02f1ea..ea3a5044 100644 --- a/setup/bootstrap.sh +++ b/setup/bootstrap.sh @@ -20,7 +20,7 @@ if [ -z "$TAG" ]; then # want to display in status checks. if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then # This machine is running Ubuntu 18.04. - TAG=v0.40-quota-0.13-alpha + TAG=v0.40-quota-0.14-alpha elif [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" == "Ubuntu 14.04 LTS" ]; then # This machine is running Ubuntu 14.04. From 4bed2221629eea27aacc22db1a5c14c8d0671dc4 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Wed, 6 Feb 2019 12:53:46 +0200 Subject: [PATCH 46/51] Fix bug where quotas are not being recalculated --- management/mailconfig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management/mailconfig.py b/management/mailconfig.py index 9a9f8407..0bca7b2a 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -421,7 +421,7 @@ def dovecot_quota_recalc(email): # subprocess.call(['doveadm', 'reload']) # force dovecot to recalculate the quota info for the user. - subprocess.call(["doveadm", "quota", "recalc" "-u", "%s" % email]) + subprocess.call(["doveadm", "quota", "recalc", "-u", email]) def get_default_quota(env): config = utils.load_settings(env) From 53d1c1e4e95cf7eb6377ce28ae467f3cc1257596 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Wed, 6 Feb 2019 12:58:05 +0200 Subject: [PATCH 47/51] Release new version v0.40-quota-0.15-alpha --- README.md | 4 ++++ setup/bootstrap.sh | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c148a63..e484ff64 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,10 @@ Issues Changes ------- +### v0.40-quota-0.15-alpha + +* Fix bug where quotas are not recalculated when a user's quota is changed in control panel + ### v0.40-quota-0.14-alpha * When updating a user's quota, execute `doveadm quota recalc -u ` to forces an immediate recalculation of the user's quota. diff --git a/setup/bootstrap.sh b/setup/bootstrap.sh index ea3a5044..58ddc304 100644 --- a/setup/bootstrap.sh +++ b/setup/bootstrap.sh @@ -20,7 +20,7 @@ if [ -z "$TAG" ]; then # want to display in status checks. if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then # This machine is running Ubuntu 18.04. - TAG=v0.40-quota-0.14-alpha + TAG=v0.40-quota-0.15-alpha elif [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" == "Ubuntu 14.04 LTS" ]; then # This machine is running Ubuntu 14.04. From ae6394c879649fc8d440b0f250df8f89ed5e761f Mon Sep 17 00:00:00 2001 From: John Supplee Date: Wed, 6 Feb 2019 14:47:30 +0200 Subject: [PATCH 48/51] Fix instructions for quotas on the users page in control panel --- management/templates/users.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management/templates/users.html b/management/templates/users.html index 5ea45da9..4ed33189 100644 --- a/management/templates/users.html +++ b/management/templates/users.html @@ -40,7 +40,7 @@
  • Use aliases to create email addresses that forward to existing accounts.
  • Administrators get access to this control panel.
  • User accounts cannot contain any international (non-ASCII) characters, but aliases can.
  • -
  • Quotas may not contain any spaces or commas. Suffixes of G (gigabytes) and M (megabytes) are allowed. For unlimited storage enter 0 (zero)
  • +
  • Quotas may not contain any spaces, commas or decimal points. Suffixes of G (gigabytes) and M (megabytes) are allowed. For unlimited storage enter 0 (zero)
  • Existing mail users

    From 7f8336e459060eeba4acf2bfed8f9f1bf21498d0 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Mon, 11 Feb 2019 16:10:09 +0200 Subject: [PATCH 49/51] Fix bug with quota input that prevented adding users --- README.md | 4 ++++ management/templates/users.html | 2 +- setup/bootstrap.sh | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e484ff64..332d6e67 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,10 @@ Issues Changes ------- +### v0.40-quota-0.16-alpha + +* Fix problem with quota field on control panel that prevented adding users. + ### v0.40-quota-0.15-alpha * Fix bug where quotas are not recalculated when a user's quota is changed in control panel diff --git a/management/templates/users.html b/management/templates/users.html index 4ed33189..dd2fefbe 100644 --- a/management/templates/users.html +++ b/management/templates/users.html @@ -31,7 +31,7 @@
    - +
    diff --git a/setup/bootstrap.sh b/setup/bootstrap.sh index 58ddc304..418b0557 100644 --- a/setup/bootstrap.sh +++ b/setup/bootstrap.sh @@ -20,7 +20,7 @@ if [ -z "$TAG" ]; then # want to display in status checks. if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then # This machine is running Ubuntu 18.04. - TAG=v0.40-quota-0.15-alpha + TAG=v0.40-quota-0.16-alpha elif [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" == "Ubuntu 14.04 LTS" ]; then # This machine is running Ubuntu 14.04. From 2b70277a3587d0fddb0bf883fd1f9ccefee6d719 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Fri, 15 Feb 2019 16:08:06 +0200 Subject: [PATCH 50/51] Update README instructions for installing and upgrading --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 332d6e67..6c86de63 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,15 @@ Please report any bugs on github. Installing v0.40-quota ---------------------- -Follow the directions below for installing from a repository except clone from this repository instead of the official one. +To install the latest version, log into your box and execute the following commands: + + $ git clone https://github.com/jrsupplee/mailinabox.git + $ cd mailinabox + $ sudo bash setup/bootstrap.sh + +Follow the standard directions for setting up an MiaB installation. There are no special installation steps for installing this version. + +The default quota is set to `0` which means unlimited. If you want to set a different default quota, follow the directions above. Upgrading v0.40 to v.0.40-quota @@ -37,7 +45,9 @@ This is experimental software. You have been warned. `git clone https://github.com/jrsupplee/mailinabox.git` -* cd into `mailinabox` and run `sudo setup/start.sh` On occasion there are lock errors when updating `Munin`. Just re-run `sudo setup/start.sh` until the error does not occur. +* cd into `mailinabox` and run `sudo setup/bootstrap.sh` On occasion there are lock errors when updating `Munin`. Just re-run `sudo setup/start.sh` until the error does not occur. + +* Note: all existing users at the time of the upgrade will have there quota set to `0` (unlimited). Upgrading v.0.40-quota to a New Version From adf7a31866ed76cb6d8231c97516b020c3a11111 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Wed, 27 Feb 2019 15:02:11 +0200 Subject: [PATCH 51/51] Update README and bump the version --- README.md | 16 +++++++++++----- setup/bootstrap.sh | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index fedbfd95..79d62f33 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Mailbox size recalculation by Dovecot can be forced using the command: Please report any bugs on github. -Installing v0.40-quota +Installing v0.41-quota ---------------------- To install the latest version, log into your box and execute the following commands: @@ -34,7 +34,7 @@ Follow the standard directions for setting up an MiaB installation. There are n The default quota is set to `0` which means unlimited. If you want to set a different default quota, follow the directions above. -Upgrading v0.40 to v.0.40-quota +Upgrading v0.41 to v.0.41-quota ------------------------------- This is experimental software. You have been warned. @@ -50,11 +50,9 @@ This is experimental software. You have been warned. * Note: all existing users at the time of the upgrade will have there quota set to `0` (unlimited). -Upgrading v.0.40-quota to a New Version +Upgrading MiaB with quotas to a New Version --------------------------------------- -* Remember that this is experimental software and review the changes in the repository. - * `cd` into the `mailinabox` directory. * Execute `git pull` to download the latest changes. @@ -71,6 +69,14 @@ Issues Changes ------- +### v0.41-quota-0.17-alpha + +* Update the README + +### v0.41-quota-0.16-alpha + +* Update to v0.41 of Mail-in-a-Box + ### v0.40-quota-0.16-alpha * Fix problem with quota field on control panel that prevented adding users. diff --git a/setup/bootstrap.sh b/setup/bootstrap.sh index 7b57ee64..3d30e438 100644 --- a/setup/bootstrap.sh +++ b/setup/bootstrap.sh @@ -20,7 +20,7 @@ if [ -z "$TAG" ]; then # want to display in status checks. if [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/18\.04\.[0-9]/18.04/' `" == "Ubuntu 18.04 LTS" ]; then # This machine is running Ubuntu 18.04. - TAG=v0.41-quota-0.16-alpha + TAG=v0.41-quota-0.17-alpha elif [ "`lsb_release -d | sed 's/.*:\s*//' | sed 's/14\.04\.[0-9]/14.04/' `" == "Ubuntu 14.04 LTS" ]; then # This machine is running Ubuntu 14.04.
    Email AddressEmail Address Quota Actions
    Email AddressBox Size Quota Actions
    @@ -161,6 +163,7 @@ function show_users() { n.attr('data-email', user.email); n.attr('data-quota', user.quota); n.find('.address').text(user.email); + n.find('.box-size').text(user.box_size); n.find('.quota').text((user.quota == '0') ? 'unlimited' : user.quota); n2.find('.restore_info tt').text(user.mailbox); From b3f9063ae798901c5821031b14e44cda17627f23 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Tue, 29 Jan 2019 19:41:54 +0200 Subject: [PATCH 15/51] comment out code generating errors When the `maildirsize` file does not exist it causes the script to fail. The IOError is not caught by the execpt --- management/mailconfig.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/management/mailconfig.py b/management/mailconfig.py index cdd05079..3a471a80 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -136,15 +136,15 @@ def get_mail_users_ex(env, with_archived=False): box_size = 0 box_count = 0 box_quota = '' - try: - with open('/home/user-data/mail/mailboxes/%s/%s/maildirsize' % (domain, user), 'r') as f: - box_quota = f.readline() - for line in f.readlines(): - (size, count) = line.split(' ') - box_size += int(size) - box_count += int(count) - except: - box_size = '?' + # try: + # with open('/home/user-data/mail/mailboxes/%s/%s/maildirsize' % (domain, user), 'r') as f: + # box_quota = f.readline() + # for line in f.readlines(): + # (size, count) = line.split(' ') + # box_size += int(size) + # box_count += int(count) + # except: + # box_size = '?' user = { "email": email, From 8bd9cf38abbecbad60f56df9d9d48e661cfb8cbb Mon Sep 17 00:00:00 2001 From: John Supplee Date: Tue, 29 Jan 2019 23:31:56 +0200 Subject: [PATCH 16/51] Use tabs for indentation --- management/daemon.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/management/daemon.py b/management/daemon.py index 5b3bd326..3bf95ec5 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -161,10 +161,10 @@ def mail_users_add(): @app.route('/mail/users/quota', methods=['POST']) @authorized_personnel_only def mail_users_quota(): - try: - return set_mail_quota(request.form.get('email', ''), request.form.get('quota'), env) - except ValueError as e: - return (str(e), 400) + try: + return set_mail_quota(request.form.get('email', ''), request.form.get('quota'), env) + except ValueError as e: + return (str(e), 400) @app.route('/mail/users/password', methods=['POST']) @authorized_personnel_only From a8f02c1eb05075383ca9c464c813b795d1e19bb3 Mon Sep 17 00:00:00 2001 From: John Supplee Date: Wed, 30 Jan 2019 00:01:16 +0200 Subject: [PATCH 17/51] Fix problems with users that do not have maildirsize file --- README.md | 10 ++++++++++ management/mailconfig.py | 34 +++++++++++++++++++++++---------- management/templates/users.html | 5 ++++- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 36f298e9..cb2cdf86 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,16 @@ There is baisc support for quotas in the control panel now. To set quotas from column for the user in the `users.sqlite` database. The `quota` column is text and allows for the `M` and `G` suffixes for megabytes and gigabytes respectively. No spaces should be used in the quota value (e.g. `2G` or `100M`). +Todo +---- + +* Get `postfix` to support quotas. See https://blog.sys4.de/postfix-dovecot-mailbox-quota-en.html for a start. Right + now the quota message store size is not calculated unless the user accesses the IMAP server (Dovecot). Right now postfix + does not take quotas into account before delivering a message. + +* Allow Trash to have a grace percentage to allow users whose quota is full to delete messages. + + \[BEGIN Official README] Mail-in-a-Box diff --git a/management/mailconfig.py b/management/mailconfig.py index 3a471a80..9cb79b0d 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -105,6 +105,18 @@ def get_mail_users(env): users = [ row[0] for row in c.fetchall() ] return utils.sort_email_addresses(users, env) +def sizeof_fmt(num): + for unit in ['','K','M','G','T']: + if abs(num) < 1024.0: + if abs(num) > 99: + return "%3.0f%s" % (num, unit) + else: + return "%2.1f%s" % (num, unit) + + num /= 1024.0 + + return str(num) + def get_mail_users_ex(env, with_archived=False): # Returns a complex data structure of all user accounts, optionally # including archived (status="inactive") accounts. @@ -136,22 +148,24 @@ def get_mail_users_ex(env, with_archived=False): box_size = 0 box_count = 0 box_quota = '' - # try: - # with open('/home/user-data/mail/mailboxes/%s/%s/maildirsize' % (domain, user), 'r') as f: - # box_quota = f.readline() - # for line in f.readlines(): - # (size, count) = line.split(' ') - # box_size += int(size) - # box_count += int(count) - # except: - # box_size = '?' + try: + dirsize_file = os.path.join(env['STORAGE_ROOT'], 'mail/mailboxes/%s/%s/maildirsize' % (domain, user)) + with open(dirsize_file, 'r') as f: + box_quota = f.readline() + for line in f.readlines(): + (size, count) = line.split(' ') + box_size += int(size) + box_count += int(count) + except: + box_size = '?' + box_count = '?' user = { "email": email, "privileges": parse_privs(privileges), "quota": quota, "box_quota": box_quota, - "box_size": '%iK' % int(box_size / 1024), + "box_size": sizeof_fmt(box_size) if box_size != '?' else box_size, "box_count": box_count, "status": "active", } diff --git a/management/templates/users.html b/management/templates/users.html index 0175b929..ee22b316 100644 --- a/management/templates/users.html +++ b/management/templates/users.html @@ -42,7 +42,8 @@
    Email AddressBox SizeMessagesSize Quota Actions
    Email AddressMessagesSizeQuotaMessagesSizeUsedQuota Actions
    + @@ -159,7 +162,7 @@ function show_users() { function(r) { $('#user_table tbody').html(""); for (var i = 0; i < r.length; i++) { - var hdr = $("