From d89441b47bd83570f89dd6ed74f1530f132e8965 Mon Sep 17 00:00:00 2001 From: Spitap Date: Tue, 13 Jun 2023 15:59:37 +0200 Subject: [PATCH 01/12] Send only mailboxes --- doc/upgrade.rst | 57 +++++++++++++++++++++++++++++---- modoboa/admin/handlers.py | 3 +- modoboa/admin/models/mailbox.py | 2 ++ modoboa/admin/postfix_maps.py | 26 +++++++++++++++ 4 files changed, 80 insertions(+), 8 deletions(-) diff --git a/doc/upgrade.rst b/doc/upgrade.rst index 953c00661..8b250a3c8 100644 --- a/doc/upgrade.rst +++ b/doc/upgrade.rst @@ -119,9 +119,9 @@ Specific instructions .. note:: This version has not been released yet. -* Django has been updated to 4.2LTS. Please upgrade all your extensions alongside modoboa. -* Support for Python 3.7 has been dropped, minimum Python version is now 3.8. -* Support for Postgres 11 has been dropped, minimum Postgres version is now 12. +* Django has been updated to 4.2LTS. Please upgrade all your extensions alongside modoboa +* Support for Python 3.7 has been dropped, minimum Python version is now 3.8 +* Support for Postgres 11 has been dropped, minimum Postgres version is now 12 If you use Postgresql, you need to install pyscopg3+: @@ -131,7 +131,7 @@ If you use Postgresql, you need to install pyscopg3+: RQ has been added. This should replace the use of cron jobs in the future. For now, only ``manage_dkim_keys`` has been migrated. This will make the dkim -key generation asynchronious but the task will be started as soon as the generation +key generation asynchronous but the task will be started as soon as the generation is required. Follow these instructions to perform the update in case you used supervisord for the @@ -140,9 +140,10 @@ installation (this apply if you used ``modoboa-installer``): 1. Edit settings.py (``/srv/modoboa/instance/instance/settings.py`` by default) and add: .. sourcecode:: python + INSTALLED_APPS = ( - ..., - 'django_rq', + ..., + 'django_rq', ) and add the RQ section bellow the ``#REDIS`` section: @@ -168,7 +169,7 @@ and add the RQ section bellow the ``#REDIS`` section: } } -Then by default you will need to restart ``uwsgi`` service. ``systemctl restart uwsgi`` on Debian. +Then by default you will need to restart ``uwsgi`` service (``systemctl restart uwsgi`` on Debian). 2. Create a new supervisord config (``/etc/supervisor/conf.d/modoboaworkerd.conf`` by default) : @@ -192,6 +193,48 @@ You can help you with ``/etc/supervisor/conf.d/policyd.conf`` (by default). Then restart supervisor. ``#> supervisorctl reread && supervisorctl update`` on Debian. +Admins now have the option to setup a receive-only mailbox or a send-only mailbox. + +Generate new maps for postfix from your virtual environment: + +.. sourcecode:: python + > python3 manage.py generate_postfix_maps --destdir /etc/postfix/ --force-overwrite + +Send-only mailboxes do not have access to IMAP. +You need to change your dovecot configuration to enable it. + +If you use ``Postgres``: +Change ``user_query`` in ``/etc/dovecot/dovecot-sql-master.conf.ext``: + +.. sourcecode:: + user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, '*:bytes=' || mb.quota || 'M' AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE mb.is_send_only AND mb.address='%%n' AND dom.name='%%d' + + +You need to add ``proxy:pgsql:/etc/postfix/sql-send-only.cf`` to ``smtpd_recipient_restrictions`` in ``/etc/postfix/main.cf``: + +.. sourcecode:: + smtpd_recipient_restrictions = + ... + check_recipient_access + proxy:pgsql:/etc/postfix/sql-send-only.cf + ... + + +If you use ``MySQL``: +Change ``password_query`` in ``/etc/dovecot/dovecot-sql-master.conf.ext``: + +.. sourcecode:: + user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, CONCAT('*:bytes=', mb.quota, 'M') AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE mb.is_send_only=1 AND mb.address='%%n' AND dom.name='%%d' + +You need to add ``proxy:mysql:/etc/postfix/sql-send-only.cf`` to ``smtpd_recipient_restrictions`` in ``/etc/postfix/main.cf``: + +.. sourcecode:: + smtpd_recipient_restrictions = + ... + check_recipient_access + proxy:pgsql:/etc/postfix/sql-send-only.cf + ... + 2.1.0 ===== diff --git a/modoboa/admin/handlers.py b/modoboa/admin/handlers.py index 78ac894e9..d88648bf1 100644 --- a/modoboa/admin/handlers.py +++ b/modoboa/admin/handlers.py @@ -159,7 +159,8 @@ def register_postfix_maps(sender, **kwargs): return [ postfix_maps.DomainsMap, postfix_maps.DomainsAliasesMap, postfix_maps.AliasesMap, postfix_maps.MaintainMap, - postfix_maps.SenderLoginMap + postfix_maps.SenderLoginMap, + postfix_maps.SendOnlyAccountMap ] diff --git a/modoboa/admin/models/mailbox.py b/modoboa/admin/models/mailbox.py index 25075b03e..fd8d4bdc9 100644 --- a/modoboa/admin/models/mailbox.py +++ b/modoboa/admin/models/mailbox.py @@ -103,6 +103,8 @@ class Mailbox(mixins.MessageLimitMixin, AdminObject): domain = models.ForeignKey(Domain, on_delete=models.CASCADE) user = models.OneToOneField(User, on_delete=models.CASCADE) + is_send_only = models.BooleanField(default=False) + objects = MailboxManager() class Meta: diff --git a/modoboa/admin/postfix_maps.py b/modoboa/admin/postfix_maps.py index d7c1eda64..6b7131a35 100644 --- a/modoboa/admin/postfix_maps.py +++ b/modoboa/admin/postfix_maps.py @@ -94,6 +94,32 @@ class MaintainMap(object): ) +class SendOnlyAccountMap(object): + + filename = "sql-send-only.cf" + mysql = ( + "SELECT '550 Requested mail action not taken: mailbox disabled' " + "FROM admin_mailbox mb INNER JOIN admin_domain dom " + "ON mb.domain_id=dom.id " + "WHERE mb.is_send_only=1 AND dom.name='%d' AND mb.address='%u' " + "LIMIT 1" + ) + postgres = ( + "SELECT '550 Requested mail action not taken: mailbox disabled' " + "FROM admin_mailbox mb INNER JOIN admin_domain dom " + "ON mb.domain_id=dom.id " + "WHERE mb.is_send_only AND dom.name='%d' AND mb.address='%u' " + "LIMIT 1" + ) + sqlite = ( + "SELECT '550 Requested mail action not taken: mailbox disabled' " + "FROM admin_mailbox mb INNER JOIN admin_domain dom " + "ON mb.domain_id=dom.id " + "WHERE mb.is_send_only=1 AND dom.name='%d' AND mb.address='%u' " + "LIMIT 1" + ) + + class SenderLoginMap(object): """Map file to list authorized sender addresses for a given account: * Its mailbox From 3733998f58e4e6ac94b79366afd563244df4207d Mon Sep 17 00:00:00 2001 From: Spitap Date: Tue, 13 Jun 2023 19:26:42 +0200 Subject: [PATCH 02/12] Updated doc and created migration --- doc/upgrade.rst | 4 ++-- .../migrations/0021_mailbox_is_send_only.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 modoboa/admin/migrations/0021_mailbox_is_send_only.py diff --git a/doc/upgrade.rst b/doc/upgrade.rst index 8b250a3c8..a17f81024 100644 --- a/doc/upgrade.rst +++ b/doc/upgrade.rst @@ -207,7 +207,7 @@ If you use ``Postgres``: Change ``user_query`` in ``/etc/dovecot/dovecot-sql-master.conf.ext``: .. sourcecode:: - user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, '*:bytes=' || mb.quota || 'M' AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE mb.is_send_only AND mb.address='%%n' AND dom.name='%%d' + user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, '*:bytes=' || mb.quota || 'M' AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE (mb.is_send_only IS NOT TRUE OR %s NOT IN ('imap', 'pop3')) AND mb.address='%%n' AND dom.name='%%d' You need to add ``proxy:pgsql:/etc/postfix/sql-send-only.cf`` to ``smtpd_recipient_restrictions`` in ``/etc/postfix/main.cf``: @@ -224,7 +224,7 @@ If you use ``MySQL``: Change ``password_query`` in ``/etc/dovecot/dovecot-sql-master.conf.ext``: .. sourcecode:: - user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, CONCAT('*:bytes=', mb.quota, 'M') AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE mb.is_send_only=1 AND mb.address='%%n' AND dom.name='%%d' + user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, CONCAT('*:bytes=', mb.quota, 'M') AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE (mb.is_send_only=0 OR %s NOT IN ('imap', 'pop3')) AND mb.address='%%n' AND dom.name='%%d' You need to add ``proxy:mysql:/etc/postfix/sql-send-only.cf`` to ``smtpd_recipient_restrictions`` in ``/etc/postfix/main.cf``: diff --git a/modoboa/admin/migrations/0021_mailbox_is_send_only.py b/modoboa/admin/migrations/0021_mailbox_is_send_only.py new file mode 100644 index 000000000..0f8cda14e --- /dev/null +++ b/modoboa/admin/migrations/0021_mailbox_is_send_only.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.1 on 2023-06-13 16:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("admin", "0020_auto_20210130_2226"), + ] + + operations = [ + migrations.AddField( + model_name="mailbox", + name="is_send_only", + field=models.BooleanField(default=False), + ), + ] From c05b4a7d26239403950f720a4444eb2a4ba3f3f3 Mon Sep 17 00:00:00 2001 From: Spitap Date: Wed, 14 Jun 2023 14:57:04 +0200 Subject: [PATCH 03/12] Handle alias to sender-only address --- .../identities/AccountGeneralForm.vue | 1 - .../identities/AccountMailboxForm.vue | 12 +++++++++++ modoboa/admin/api/v2/serializers.py | 1 + modoboa/admin/postfix_maps.py | 21 +++++++++++-------- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/identities/AccountGeneralForm.vue b/frontend/src/components/identities/AccountGeneralForm.vue index 6ef5fb71b..b680756ca 100644 --- a/frontend/src/components/identities/AccountGeneralForm.vue +++ b/frontend/src/components/identities/AccountGeneralForm.vue @@ -81,7 +81,6 @@ export default { return await this.$refs.observer.validate() && await this.$refs.passwordForm.validate() }, update () { - console.log(this.form) this.$emit('input', this.form) } }, diff --git a/frontend/src/components/identities/AccountMailboxForm.vue b/frontend/src/components/identities/AccountMailboxForm.vue index 6e69a0347..cab3c44f6 100644 --- a/frontend/src/components/identities/AccountMailboxForm.vue +++ b/frontend/src/components/identities/AccountMailboxForm.vue @@ -39,6 +39,18 @@ @input="update" /> + + + diff --git a/modoboa/admin/api/v2/serializers.py b/modoboa/admin/api/v2/serializers.py index c4f12b446..8acfebd7f 100644 --- a/modoboa/admin/api/v2/serializers.py +++ b/modoboa/admin/api/v2/serializers.py @@ -393,6 +393,7 @@ class MailboxSerializer(serializers.ModelSerializer): model = models.Mailbox fields = ( "pk", "use_domain_quota", "quota", "message_limit", + "is_send_only", ) def validate_quota(self, value): diff --git a/modoboa/admin/postfix_maps.py b/modoboa/admin/postfix_maps.py index 6b7131a35..ad8a04b85 100644 --- a/modoboa/admin/postfix_maps.py +++ b/modoboa/admin/postfix_maps.py @@ -99,23 +99,26 @@ class SendOnlyAccountMap(object): filename = "sql-send-only.cf" mysql = ( "SELECT '550 Requested mail action not taken: mailbox disabled' " - "FROM admin_mailbox mb INNER JOIN admin_domain dom " - "ON mb.domain_id=dom.id " - "WHERE mb.is_send_only=1 AND dom.name='%d' AND mb.address='%u' " + "FROM modoboa_admin_aliasrecipient amb " + "INNER JOIN admin_mailbox mb " + "ON mb.id=amb.r_mailbox_id " + "WHERE mb.is_send_only=1 AND amb.address=CONCAT('%u', '@','%d') " "LIMIT 1" ) postgres = ( "SELECT '550 Requested mail action not taken: mailbox disabled' " - "FROM admin_mailbox mb INNER JOIN admin_domain dom " - "ON mb.domain_id=dom.id " - "WHERE mb.is_send_only AND dom.name='%d' AND mb.address='%u' " + "FROM modoboa_admin_aliasrecipient amb " + "INNER JOIN admin_mailbox mb " + "ON mb.id=amb.r_mailbox_id " + "WHERE mb.is_send_only AND amb.address='%u' || '@' || '%d' " "LIMIT 1" ) sqlite = ( "SELECT '550 Requested mail action not taken: mailbox disabled' " - "FROM admin_mailbox mb INNER JOIN admin_domain dom " - "ON mb.domain_id=dom.id " - "WHERE mb.is_send_only=1 AND dom.name='%d' AND mb.address='%u' " + "FROM modoboa_admin_aliasrecipient amb " + "INNER JOIN admin_mailbox mb " + "ON mb.id=amb.r_mailbox_id " + "WHERE mb.is_send_only=1 AND mb.address='%u' || '@' || '%d' " "LIMIT 1" ) From fe3a47d71acd5b6b9ebc90fb69dbc0ab00c7f8e7 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 6 Jul 2023 19:44:16 +0200 Subject: [PATCH 04/12] Updated documentation --- doc/upgrade.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/upgrade.rst b/doc/upgrade.rst index a17f81024..9a999f646 100644 --- a/doc/upgrade.rst +++ b/doc/upgrade.rst @@ -193,7 +193,7 @@ You can help you with ``/etc/supervisor/conf.d/policyd.conf`` (by default). Then restart supervisor. ``#> supervisorctl reread && supervisorctl update`` on Debian. -Admins now have the option to setup a receive-only mailbox or a send-only mailbox. +Admins now have the option to setup a send-only mailbox. Generate new maps for postfix from your virtual environment: From 2c08bd7b882c17d5dcdbcb6f11257f1bb521749e Mon Sep 17 00:00:00 2001 From: Spitap Date: Wed, 19 Jul 2023 10:38:30 +0200 Subject: [PATCH 05/12] Updated doc --- doc/upgrade.rst | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/doc/upgrade.rst b/doc/upgrade.rst index 9a999f646..829f4fd56 100644 --- a/doc/upgrade.rst +++ b/doc/upgrade.rst @@ -6,7 +6,7 @@ Modoboa ******* .. note:: - In this doc, ``by default`` mean that you used `modoboa installer + In this doc, ``by default`` means that you used `modoboa installer `_ to install modoboa and that your didn't change your configuration. @@ -123,6 +123,16 @@ Specific instructions * Support for Python 3.7 has been dropped, minimum Python version is now 3.8 * Support for Postgres 11 has been dropped, minimum Postgres version is now 12 +You need to change the first line in ``urls.py`` in ``{instance_dir}/instance/urls.py``: + +.. sourcecode:: python + + # from django.conf.urls import include, url + from django.urls import include, path + +And replace `url` with `path` in the file. + + If you use Postgresql, you need to install pyscopg3+: .. sourcecode:: bash @@ -149,8 +159,8 @@ installation (this apply if you used ``modoboa-installer``): and add the RQ section bellow the ``#REDIS`` section: .. sourcecode:: python - # RQ + # RQ RQ_QUEUES = { 'default': { 'HOST': REDIS_HOST, @@ -171,14 +181,15 @@ and add the RQ section bellow the ``#REDIS`` section: Then by default you will need to restart ``uwsgi`` service (``systemctl restart uwsgi`` on Debian). -2. Create a new supervisord config (``/etc/supervisor/conf.d/modoboaworkerd.conf`` by default) : +2. Create a new supervisord config (``/etc/supervisor/conf.d/modoboa-worker.conf`` by default) : .. sourcecode:: ini [program:modoboa-worker] autostart=true autorestart=true - command={%python env path%} {% manage.py instance path%} worker high default low + command={%python env path%} {% manage.py instance path%} rqworker high default low directory={%modoboa home dir%} + user=%{modoboa user} numprocs=1 stopsignal=TERM @@ -188,6 +199,7 @@ You will find it here ``/srv/modoboa/venv/bin/python`` by default. You will find it here : ``/srv/modoboa/instance/manage.py``by default. ``Modoboa home dir``: Home dir of the user running modooba. You will find it here ``/srv/modoboa/`` by default. +``modoboa user`` : User owning modoboa home dir. You can help you with ``/etc/supervisor/conf.d/policyd.conf`` (by default). From 780303f780a64f35f06b51327dd1c0e13857175b Mon Sep 17 00:00:00 2001 From: Spitap Date: Wed, 19 Jul 2023 11:23:52 +0200 Subject: [PATCH 06/12] Changed how secret key is generated on deploy --- modoboa/core/commands/deploy.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/modoboa/core/commands/deploy.py b/modoboa/core/commands/deploy.py index dc6e1b8b3..7c5a760da 100644 --- a/modoboa/core/commands/deploy.py +++ b/modoboa/core/commands/deploy.py @@ -3,7 +3,9 @@ import getpass import os from os.path import isfile +import secrets import shutil +import string import subprocess import sys @@ -81,6 +83,10 @@ class DeployCommand(Command): "--admin-username", default="admin", help="Username of the initial super administrator" ) + self._parser.add_argument( + "--secret-key-length", default=60, type=int, + help="Specify the length of the secret key" + ) def _exec_django_command(self, name, cwd, *args): """Run a django command for the freshly created project @@ -156,6 +162,14 @@ class DeployCommand(Command): official_exts = ModoAPIClient(url).list_extensions() return [extension["name"] for extension in official_exts] + def _generate_secret_key(self, secret_key_length): + """Generate a secret key since the one generated by django + is marked as insecure. + """ + c = string.ascii_letters + string.digits + string.punctuation + secret_key = ''.join(secrets.choice(c) for i in range(secret_key_length)) + return secret_key + def find_extra_settings(self, extensions): """Install one or more extensions. @@ -233,7 +247,7 @@ class DeployCommand(Command): tpl = self._render_template( "%s/settings.py.tpl" % self._templates_dir, { "db_connections": connections, - "secret_key": mod.settings.SECRET_KEY, + "secret_key": _generate_secret_key(parsed_args.secret_key_length), "name": parsed_args.name, "allowed_host": allowed_host, "lang": parsed_args.lang, From 2d25f50fe6518dc3d81f458dc79dee70d1aff8c6 Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 20 Jul 2023 22:38:16 +0200 Subject: [PATCH 07/12] Updated how sendonly mailboxes work --- doc/upgrade.rst | 41 ++++++++++++++++++++--------------- modoboa/admin/handlers.py | 3 +-- modoboa/admin/postfix_maps.py | 29 ------------------------- 3 files changed, 24 insertions(+), 49 deletions(-) diff --git a/doc/upgrade.rst b/doc/upgrade.rst index 829f4fd56..55a7cf021 100644 --- a/doc/upgrade.rst +++ b/doc/upgrade.rst @@ -119,11 +119,15 @@ Specific instructions .. note:: This version has not been released yet. +.. note:: + If you use the installer to perform the upgrade, you only need to edit ``urls.py`` and ``settings.py`` + * Django has been updated to 4.2LTS. Please upgrade all your extensions alongside modoboa * Support for Python 3.7 has been dropped, minimum Python version is now 3.8 * Support for Postgres 11 has been dropped, minimum Postgres version is now 12 -You need to change the first line in ``urls.py`` in ``{instance_dir}/instance/urls.py``: +You need to change the first line in ``urls.py`` in +(``/srv/modoboa/instance/instance/urls.py`` by default): .. sourcecode:: python @@ -184,6 +188,7 @@ Then by default you will need to restart ``uwsgi`` service (``systemctl restart 2. Create a new supervisord config (``/etc/supervisor/conf.d/modoboa-worker.conf`` by default) : .. sourcecode:: ini + [program:modoboa-worker] autostart=true autorestart=true @@ -207,40 +212,40 @@ Then restart supervisor. ``#> supervisorctl reread && supervisorctl update`` on Admins now have the option to setup a send-only mailbox. -Generate new maps for postfix from your virtual environment: - -.. sourcecode:: python - > python3 manage.py generate_postfix_maps --destdir /etc/postfix/ --force-overwrite - Send-only mailboxes do not have access to IMAP. You need to change your dovecot configuration to enable it. +.. note:: + modoboa-webmail may not work with send-only user for now. + If you use ``Postgres``: -Change ``user_query`` in ``/etc/dovecot/dovecot-sql-master.conf.ext``: +Change ``user_query`` and ``password_query`` in ``/etc/dovecot/dovecot-sql.conf.ext``: .. sourcecode:: - user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, '*:bytes=' || mb.quota || 'M' AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE (mb.is_send_only IS NOT TRUE OR %s NOT IN ('imap', 'pop3')) AND mb.address='%%n' AND dom.name='%%d' + + user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, '*:bytes=' || mb.quota || 'M' AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE (mb.is_send_only IS NOT TRUE OR '%s' NOT IN ('imap', 'pop3', 'lmtp')) AND mb.address='%%n' AND dom.name='%%d' + + password_query = SELECT email AS user, password, '%{home_dir}/%%d/%%n' AS userdb_home, %mailboxes_owner_uid AS userdb_uid, %mailboxes_owner_gid AS userdb_gid, CONCAT('*:bytes=', mb.quota, 'M') AS userdb_quota_rule FROM core_user u INNER JOIN admin_mailbox mb ON u.id=mb.user_id INNER JOIN admin_domain dom ON mb.domain_id=dom.id WHERE (mb.is_send_only IS NOT TRUE OR '%s' NOT IN ('imap', 'pop3')) AND email='%%u' AND is_active AND dom.enabled -You need to add ``proxy:pgsql:/etc/postfix/sql-send-only.cf`` to ``smtpd_recipient_restrictions`` in ``/etc/postfix/main.cf``: - -.. sourcecode:: - smtpd_recipient_restrictions = - ... - check_recipient_access - proxy:pgsql:/etc/postfix/sql-send-only.cf - ... +You basically simply need to add ``(mb.is_send_only IS NOT TRUE OR '%s' NOT IN ('imap', 'pop3', 'lmtp')) AND`` for ``user_query`` after ``WHERE`` and ``(mb.is_send_only IS NOT TRUE OR '%s' NOT IN ('imap', 'pop3')) AND`` for ``password_query`` after ``WHERE``. If you use ``MySQL``: -Change ``password_query`` in ``/etc/dovecot/dovecot-sql-master.conf.ext``: +Change ``user_query`` and ``password_query`` in ``/etc/dovecot/dovecot-sql-master.ext``: .. sourcecode:: - user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, CONCAT('*:bytes=', mb.quota, 'M') AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE (mb.is_send_only=0 OR %s NOT IN ('imap', 'pop3')) AND mb.address='%%n' AND dom.name='%%d' + + user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, CONCAT('*:bytes=', mb.quota, 'M') AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE (mb.is_send_only=0 OR '%s' NOT IN ('imap', 'pop3', 'lmtp')) AND mb.address='%%n' AND dom.name='%%d' + + password_query = SELECT email AS user, password, '%{home_dir}/%%d/%%n' AS userdb_home, %mailboxes_owner_uid AS userdb_uid, %mailboxes_owner_gid AS userdb_gid, CONCAT('*:bytes=', mb.quota, 'M') AS userdb_quota_rule FROM core_user u INNER JOIN admin_mailbox mb ON u.id=mb.user_id INNER JOIN admin_domain dom ON mb.domain_id=dom.id WHERE (mb.is_send_only=0 OR '%s' NOT IN ('imap', 'pop3')) AND u.email='%%u' AND u.is_active=1 AND dom.enabled=1 + +You basically simply need to add ``(mb.is_send_only=0 OR '%s' NOT IN ('imap', 'pop3', 'lmtp')) AND`` for ``user_query`` after ``WHERE`` and ``(mb.is_send_only=0 OR '%s' NOT IN ('imap', 'pop3'))`` for ``password_query`` after ``WHERE``. You need to add ``proxy:mysql:/etc/postfix/sql-send-only.cf`` to ``smtpd_recipient_restrictions`` in ``/etc/postfix/main.cf``: .. sourcecode:: + smtpd_recipient_restrictions = ... check_recipient_access diff --git a/modoboa/admin/handlers.py b/modoboa/admin/handlers.py index d88648bf1..78ac894e9 100644 --- a/modoboa/admin/handlers.py +++ b/modoboa/admin/handlers.py @@ -159,8 +159,7 @@ def register_postfix_maps(sender, **kwargs): return [ postfix_maps.DomainsMap, postfix_maps.DomainsAliasesMap, postfix_maps.AliasesMap, postfix_maps.MaintainMap, - postfix_maps.SenderLoginMap, - postfix_maps.SendOnlyAccountMap + postfix_maps.SenderLoginMap ] diff --git a/modoboa/admin/postfix_maps.py b/modoboa/admin/postfix_maps.py index ad8a04b85..d7c1eda64 100644 --- a/modoboa/admin/postfix_maps.py +++ b/modoboa/admin/postfix_maps.py @@ -94,35 +94,6 @@ class MaintainMap(object): ) -class SendOnlyAccountMap(object): - - filename = "sql-send-only.cf" - mysql = ( - "SELECT '550 Requested mail action not taken: mailbox disabled' " - "FROM modoboa_admin_aliasrecipient amb " - "INNER JOIN admin_mailbox mb " - "ON mb.id=amb.r_mailbox_id " - "WHERE mb.is_send_only=1 AND amb.address=CONCAT('%u', '@','%d') " - "LIMIT 1" - ) - postgres = ( - "SELECT '550 Requested mail action not taken: mailbox disabled' " - "FROM modoboa_admin_aliasrecipient amb " - "INNER JOIN admin_mailbox mb " - "ON mb.id=amb.r_mailbox_id " - "WHERE mb.is_send_only AND amb.address='%u' || '@' || '%d' " - "LIMIT 1" - ) - sqlite = ( - "SELECT '550 Requested mail action not taken: mailbox disabled' " - "FROM modoboa_admin_aliasrecipient amb " - "INNER JOIN admin_mailbox mb " - "ON mb.id=amb.r_mailbox_id " - "WHERE mb.is_send_only=1 AND mb.address='%u' || '@' || '%d' " - "LIMIT 1" - ) - - class SenderLoginMap(object): """Map file to list authorized sender addresses for a given account: * Its mailbox From 06b13fcce019ab264d1280a6872bddf3e3ac6722 Mon Sep 17 00:00:00 2001 From: Spitfireap <45575529+Spitfireap@users.noreply.github.com> Date: Thu, 20 Jul 2023 20:55:10 +0000 Subject: [PATCH 08/12] Update deploy.py --- modoboa/core/commands/deploy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa/core/commands/deploy.py b/modoboa/core/commands/deploy.py index 7c5a760da..db80ee3c4 100644 --- a/modoboa/core/commands/deploy.py +++ b/modoboa/core/commands/deploy.py @@ -247,7 +247,7 @@ class DeployCommand(Command): tpl = self._render_template( "%s/settings.py.tpl" % self._templates_dir, { "db_connections": connections, - "secret_key": _generate_secret_key(parsed_args.secret_key_length), + "secret_key": self._generate_secret_key(parsed_args.secret_key_length), "name": parsed_args.name, "allowed_host": allowed_host, "lang": parsed_args.lang, From 41bc9875153e10cb6dca90d2bc86fdfb66b4e5fa Mon Sep 17 00:00:00 2001 From: Spitap Date: Fri, 21 Jul 2023 11:33:44 +0200 Subject: [PATCH 09/12] Changed user for dkim management --- doc/upgrade.rst | 18 ++++-------------- docker-compose.yml | 2 +- modoboa/admin/handlers.py | 3 ++- .../core/commands/templates/settings.py.tpl | 12 +----------- test_project/test_project/settings.py | 8 +------- 5 files changed, 9 insertions(+), 34 deletions(-) diff --git a/doc/upgrade.rst b/doc/upgrade.rst index 55a7cf021..16a53465e 100644 --- a/doc/upgrade.rst +++ b/doc/upgrade.rst @@ -166,21 +166,11 @@ and add the RQ section bellow the ``#REDIS`` section: # RQ RQ_QUEUES = { - 'default': { + 'dkim': { 'HOST': REDIS_HOST, 'PORT': REDIS_PORT, 'DB': 0, }, - 'high': { - 'HOST': REDIS_HOST, - 'PORT': REDIS_PORT, - 'DB': 0, - }, - 'low': { - 'HOST': REDIS_HOST, - 'PORT': REDIS_PORT, - 'DB': 0, - } } Then by default you will need to restart ``uwsgi`` service (``systemctl restart uwsgi`` on Debian). @@ -192,9 +182,9 @@ Then by default you will need to restart ``uwsgi`` service (``systemctl restart [program:modoboa-worker] autostart=true autorestart=true - command={%python env path%} {% manage.py instance path%} rqworker high default low + command={%python env path%} {% manage.py instance path%} rqworker dkim directory={%modoboa home dir%} - user=%{modoboa user} + user=%{dkim manager user} numprocs=1 stopsignal=TERM @@ -204,7 +194,7 @@ You will find it here ``/srv/modoboa/venv/bin/python`` by default. You will find it here : ``/srv/modoboa/instance/manage.py``by default. ``Modoboa home dir``: Home dir of the user running modooba. You will find it here ``/srv/modoboa/`` by default. -``modoboa user`` : User owning modoboa home dir. +``dkim manager user`` : User managing dkim signing (opendkim by default). You can help you with ``/etc/supervisor/conf.d/policyd.conf`` (by default). diff --git a/docker-compose.yml b/docker-compose.yml index 7de4e402e..7a98b2d29 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,7 @@ services: DEBUG: "True" REDIS_HOST: redis command: > - sh -c "cd test_project; python3 manage.py rqworker high default low" + sh -c "cd test_project; python3 manage.py rqworker dkim" volumes: - .:/code diff --git a/modoboa/admin/handlers.py b/modoboa/admin/handlers.py index 78ac894e9..4a6fcf15b 100644 --- a/modoboa/admin/handlers.py +++ b/modoboa/admin/handlers.py @@ -51,7 +51,8 @@ def update_domain_mxs_and_mailboxes(sender, instance, **kwargs): def create_dkim_key(sender, instance, **kwargs): if not instance.enable_dkim: return - django_rq.enqueue(call_command, + queue = django_rq.get_queue('dkim') + queue.enqueue(call_command, "modo", "manage_dkim_keys", f"--domain={instance.name}") diff --git a/modoboa/core/commands/templates/settings.py.tpl b/modoboa/core/commands/templates/settings.py.tpl index 7fc8a4a7b..c9e3874ee 100644 --- a/modoboa/core/commands/templates/settings.py.tpl +++ b/modoboa/core/commands/templates/settings.py.tpl @@ -257,21 +257,11 @@ REDIS_URL = 'redis://{}:{}/{}'.format(REDIS_HOST, REDIS_PORT, REDIS_QUOTA_DB) # RQ RQ_QUEUES = { - 'default': { + 'dkim': { 'HOST': REDIS_HOST, 'PORT': REDIS_PORT, 'DB': 0, }, - 'high': { - 'HOST': REDIS_HOST, - 'PORT': REDIS_PORT, - 'DB': 0, - }, - 'low': { - 'HOST': REDIS_HOST, - 'PORT': REDIS_PORT, - 'DB': 0, - } } # Password validation diff --git a/test_project/test_project/settings.py b/test_project/test_project/settings.py index dd3575a04..aa02ff62e 100644 --- a/test_project/test_project/settings.py +++ b/test_project/test_project/settings.py @@ -240,15 +240,9 @@ REDIS_URL = 'redis://{}:{}/{}'.format(REDIS_HOST, REDIS_PORT, REDIS_QUOTA_DB) # RQ RQ_QUEUES = { - 'default': { + 'dkim': { 'URL': REDIS_URL, }, - 'high': { - 'URL': REDIS_URL, - }, - 'low': { - 'URL': REDIS_URL, - } } # Password validation From ec73504fd69762519981547ba9e5b83e72fd21dc Mon Sep 17 00:00:00 2001 From: Spitap Date: Fri, 21 Jul 2023 14:52:01 +0200 Subject: [PATCH 10/12] Updated sendonly forms --- .../src/components/identities/AccountCreationForm.vue | 5 +++++ frontend/src/components/identities/AccountForm.vue | 5 +++++ modoboa/admin/api/v1/serializers.py | 2 +- modoboa/admin/forms/account.py | 9 ++++++++- modoboa/admin/models/mailbox.py | 2 ++ modoboa/admin/templates/admin/mailform.html | 1 + 6 files changed, 22 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/identities/AccountCreationForm.vue b/frontend/src/components/identities/AccountCreationForm.vue index dcfcad41a..018ea2788 100644 --- a/frontend/src/components/identities/AccountCreationForm.vue +++ b/frontend/src/components/identities/AccountCreationForm.vue @@ -107,6 +107,11 @@ export default { { key: this.$gettext('Message sending limit'), value: this.account.mailbox.message_limit + }, + { + key: this.$gettext('Send only account'), + value: this.account.mailbox.is_send_only, + type: 'yesno' } ] }, diff --git a/frontend/src/components/identities/AccountForm.vue b/frontend/src/components/identities/AccountForm.vue index 480b5bb72..260064f9c 100644 --- a/frontend/src/components/identities/AccountForm.vue +++ b/frontend/src/components/identities/AccountForm.vue @@ -88,6 +88,11 @@ Quota: {{ account.mailbox.quota }} + + Send only + mdi-check-circle-outline + mdi-close-circle-outline + diff --git a/modoboa/admin/api/v1/serializers.py b/modoboa/admin/api/v1/serializers.py index 545028bdf..c9d2ea9f3 100644 --- a/modoboa/admin/api/v1/serializers.py +++ b/modoboa/admin/api/v1/serializers.py @@ -156,7 +156,7 @@ class MailboxSerializer(serializers.ModelSerializer): model = models.Mailbox fields = ( "pk", "full_address", "use_domain_quota", "quota", "message_limit", - "quota_usage" + "quota_usage", "is_send_only" ) def get_quota_usage(self, mb) -> int: diff --git a/modoboa/admin/forms/account.py b/modoboa/admin/forms/account.py index fac6c1e32..d08d3fff4 100644 --- a/modoboa/admin/forms/account.py +++ b/modoboa/admin/forms/account.py @@ -249,6 +249,11 @@ class AccountFormMail(forms.Form, DynamicForm): help_text=gettext_lazy( "Number of messages this mailbox can send per day") ) + is_send_only = forms.BooleanField( + required=False, + help_text=gettext_lazy( + "Block IMAP connection and incoming mails for this mailbox") + ) aliases = lib_fields.UTF8AndEmptyUserEmailField( label=gettext_lazy("Alias(es)"), required=False, @@ -296,6 +301,7 @@ class AccountFormMail(forms.Form, DynamicForm): self.fields["quota"].initial = self.mb.quota if self.mb.message_limit: self.fields["message_limit"].initial = self.mb.message_limit + self.fields["is_send_only"].initial = self.mb.is_send_only self.fields["create_alias_with_old_address"].initial = ( params["create_alias_on_mbox_rename"] ) @@ -384,7 +390,8 @@ class AccountFormMail(forms.Form, DynamicForm): self.mb = models.Mailbox( address=self.locpart, domain=self.domain, user=account, use_domain_quota=self.cleaned_data["quota_act"], - message_limit=self.cleaned_data.get("message_limit") + message_limit=self.cleaned_data.get("message_limit"), + is_send_only=self.cleaned_data.get("is_send_only", False) ) override_rules = ( user.is_superuser or diff --git a/modoboa/admin/models/mailbox.py b/modoboa/admin/models/mailbox.py index fd8d4bdc9..0cdf01e69 100644 --- a/modoboa/admin/models/mailbox.py +++ b/modoboa/admin/models/mailbox.py @@ -326,6 +326,8 @@ class Mailbox(mixins.MessageLimitMixin, AdminObject): self.set_quota(values["quota"], override_rules) if "message_limit" in values: self.message_limit = values["message_limit"] + if "is_send_only" in values: + self.is_send_only = values["is_send_only"] if newaddress: self.rename(local_part, domain) self.save() diff --git a/modoboa/admin/templates/admin/mailform.html b/modoboa/admin/templates/admin/mailform.html index 253214f7b..6d10646b7 100644 --- a/modoboa/admin/templates/admin/mailform.html +++ b/modoboa/admin/templates/admin/mailform.html @@ -6,5 +6,6 @@ {% endif %} {% include "common/generic_field.html" with field=form.quota help_display_mode="tooltip" label_width="col-sm-4" activator=True activator_value=form.quota_act.value deactivate_if_empty=False activator_label=_("Use domain default value") activator_size="col-sm-5" %} {% render_field form.message_limit %} +{% render_field form.is_send_only %} {% render_fields_group form "aliases" %} {% render_fields_group form "senderaddress" %} From cdae9a4a27cb44f5b5fa43b0e57006da81238352 Mon Sep 17 00:00:00 2001 From: Spitap Date: Fri, 21 Jul 2023 14:53:31 +0200 Subject: [PATCH 11/12] Updated doc --- doc/upgrade.rst | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/doc/upgrade.rst b/doc/upgrade.rst index 16a53465e..d6e11e836 100644 --- a/doc/upgrade.rst +++ b/doc/upgrade.rst @@ -232,16 +232,6 @@ Change ``user_query`` and ``password_query`` in ``/etc/dovecot/dovecot-sql-maste You basically simply need to add ``(mb.is_send_only=0 OR '%s' NOT IN ('imap', 'pop3', 'lmtp')) AND`` for ``user_query`` after ``WHERE`` and ``(mb.is_send_only=0 OR '%s' NOT IN ('imap', 'pop3'))`` for ``password_query`` after ``WHERE``. -You need to add ``proxy:mysql:/etc/postfix/sql-send-only.cf`` to ``smtpd_recipient_restrictions`` in ``/etc/postfix/main.cf``: - -.. sourcecode:: - - smtpd_recipient_restrictions = - ... - check_recipient_access - proxy:pgsql:/etc/postfix/sql-send-only.cf - ... - 2.1.0 ===== From dec6808a41f907456de77be16bc766e153b89230 Mon Sep 17 00:00:00 2001 From: Spitap Date: Mon, 24 Jul 2023 10:19:14 +0200 Subject: [PATCH 12/12] Added test --- modoboa/admin/api/v2/tests.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modoboa/admin/api/v2/tests.py b/modoboa/admin/api/v2/tests.py index 819d68378..f07eb132a 100644 --- a/modoboa/admin/api/v2/tests.py +++ b/modoboa/admin/api/v2/tests.py @@ -258,7 +258,7 @@ class AccountViewSetTestCase(ModoAPITestCase): resp = self.client.patch(url, data, format="json") self.assertEqual(resp.status_code, 200) - def test_message_limit_update(self): + def test_mailbox_options_update(self): account = core_models.User.objects.get(username="user@test.com") url = reverse("v2:account-detail", args=[account.pk]) data = { @@ -266,13 +266,15 @@ class AccountViewSetTestCase(ModoAPITestCase): "role": "SimpleUsers", "password": "Toto12345", "mailbox": { - "message_limit": 10 + "message_limit": 10, + "is_send_only": True, } } resp = self.client.patch(url, data, format="json") self.assertEqual(resp.status_code, 200) account.refresh_from_db() self.assertEqual(account.mailbox.message_limit, 10) + self.assertEqual(account.mailbox.is_send_only, True) data = { "username": "user@test.com", @@ -292,13 +294,15 @@ class AccountViewSetTestCase(ModoAPITestCase): "role": "SimpleUsers", "password": "Toto12345", "mailbox": { - "message_limit": None + "message_limit": None, + "is_send_only": False, } } resp = self.client.patch(url, data, format="json") self.assertEqual(resp.status_code, 200) account.refresh_from_db() self.assertEqual(account.mailbox.message_limit, None) + self.assertEqual(account.mailbox.is_send_only, False) def test_update_admin(self): account = core_models.User.objects.get(username="admin")