From 4082d5790d60c926802d41866eb10fe1f4f0e1ac Mon Sep 17 00:00:00 2001 From: Spitap Date: Tue, 4 Apr 2023 17:34:48 +0200 Subject: [PATCH 01/56] Added Rspamd installation --- modoboa_installer/config_dict_template.py | 39 ++++++++- .../scripts/files/nginx/modoboa.conf.tpl | 7 ++ .../scripts/files/postfix/main.cf.tpl | 25 +++--- .../files/rspamd/local.d/antivirus.conf.tpl | 11 +++ .../rspamd/local.d/dkim_signing.conf.tpl | 3 + .../files/rspamd/local.d/greylisting.conf.tpl | 2 + .../files/rspamd/local.d/mx_check.conf.tpl | 1 + .../scripts/files/rspamd/local.d/rbl.conf.tpl | 6 ++ .../scripts/files/rspamd/local.d/spf.conf.tpl | 6 ++ .../rspamd/local.d/worker-controller.inc | 1 + .../rspamd/local.d/worker-controller.inc.tpl | 1 + .../rspamd/local.d/worker-normal.inc.tpl | 1 + .../files/rspamd/local.d/worker-proxy.inc.tpl | 3 + modoboa_installer/scripts/postfix.py | 4 +- modoboa_installer/scripts/rspamd.py | 82 +++++++++++++++++++ 15 files changed, 178 insertions(+), 14 deletions(-) create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/antivirus.conf.tpl create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/dkim_signing.conf.tpl create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/greylisting.conf.tpl create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/mx_check.conf.tpl create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/rbl.conf.tpl create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/spf.conf.tpl create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/worker-controller.inc create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/worker-controller.inc.tpl create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/worker-normal.inc.tpl create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/worker-proxy.inc.tpl create mode 100644 modoboa_installer/scripts/rspamd.py diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index 72e4ebd..093bc5b 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -225,12 +225,45 @@ ConfigDictTemplate = [ ] }, { - "name": "amavis", + "name": "rspamd", "values": [ { "option": "enabled", "default": "true", }, + { + "option": "password", + "default": make_password, + } + { + "option": "dnsbl", + "default": "true", + }, + { + "option": "dkim_keys_storage_dir", + "default": "/var/lib/dkim" + }, + { + "option": "keys_path_map", + "default": "/var/lib/dkim/keys.path.map" + }, + { + "option": "selectors_path_map", + "default": "/var/lib/dkim/selectors.path.map" + }, + { + "option": "greylisting", + "default": "true" + } + ], + }, + { + "name": "amavis", + "values": [ + { + "option": "enabled", + "default": "false", + }, { "option": "user", "default": "amavis", @@ -366,7 +399,7 @@ ConfigDictTemplate = [ "values": [ { "option": "enabled", - "default": "true", + "default": "false", }, { "option": "config_dir", @@ -435,7 +468,7 @@ ConfigDictTemplate = [ "values": [ { "option": "enabled", - "default": "true", + "default": "false", }, { "option": "user", diff --git a/modoboa_installer/scripts/files/nginx/modoboa.conf.tpl b/modoboa_installer/scripts/files/nginx/modoboa.conf.tpl index 725402c..c52e710 100644 --- a/modoboa_installer/scripts/files/nginx/modoboa.conf.tpl +++ b/modoboa_installer/scripts/files/nginx/modoboa.conf.tpl @@ -37,6 +37,13 @@ server { try_files $uri $uri/ =404; } +%{rspamd_enabled} location /rspamd/ { +%{rspamd_enabled} proxy_pass http://localhost:11334/; +%{rspamd_enabled} +%{rspamd_enabled} proxy_set_header Host $host; +%{rspamd_enabled} proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +%{rspamd_enabled} } + location ~ ^/(api|accounts) { include uwsgi_params; uwsgi_param UWSGI_SCRIPT instance.wsgi:application; diff --git a/modoboa_installer/scripts/files/postfix/main.cf.tpl b/modoboa_installer/scripts/files/postfix/main.cf.tpl index 294c2a0..c112975 100644 --- a/modoboa_installer/scripts/files/postfix/main.cf.tpl +++ b/modoboa_installer/scripts/files/postfix/main.cf.tpl @@ -122,6 +122,11 @@ strict_rfc821_envelopes = yes %{opendkim_enabled}milter_default_action = accept %{opendkim_enabled}milter_content_timeout = 30s +# Rspamd setup +%{rspamd_enabled}smtpd_milters = inet:localhost:11332 +%{rspamd_enabled}milter_default_action = accept +%{rspamd_enabled}milter_protocol = 6 + # List of authorized senders smtpd_sender_login_maps = proxy:%{db_driver}:/etc/postfix/sql-sender-login-map.cf @@ -142,18 +147,18 @@ smtpd_recipient_restrictions = ## Postcreen settings # -postscreen_access_list = - permit_mynetworks - cidr:/etc/postfix/postscreen_spf_whitelist.cidr -postscreen_blacklist_action = enforce +%{rspamd_disabled}postscreen_access_list = +%{rspamd_disabled} permit_mynetworks +%{rspamd_disabled} cidr:/etc/postfix/postscreen_spf_whitelist.cidr +%{rspamd_disabled}postscreen_blacklist_action = enforce # Use some DNSBL -postscreen_dnsbl_sites = - zen.spamhaus.org=127.0.0.[2..11]*3 - bl.spameatingmonkey.net=127.0.0.2*2 - bl.spamcop.net=127.0.0.2 -postscreen_dnsbl_threshold = 3 -postscreen_dnsbl_action = enforce +%{rspamd_disabled}postscreen_dnsbl_sites = +%{rspamd_disabled} zen.spamhaus.org=127.0.0.[2..11]*3 +%{rspamd_disabled} bl.spameatingmonkey.net=127.0.0.2*2 +%{rspamd_disabled} bl.spamcop.net=127.0.0.2 +%{rspamd_disabled}postscreen_dnsbl_threshold = 3 +%{rspamd_disabled}postscreen_dnsbl_action = enforce postscreen_greet_banner = Welcome, please wait... postscreen_greet_action = enforce diff --git a/modoboa_installer/scripts/files/rspamd/local.d/antivirus.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/antivirus.conf.tpl new file mode 100644 index 0000000..235ea0f --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/antivirus.conf.tpl @@ -0,0 +1,11 @@ +clamav { + symbol = "CLAM_VIRUS"; + type = "clamav"; + servers = "127.0.0.1:3310"; + patterns { + # symbol_name = "pattern"; + JUST_EICAR = '^Eicar-Test-Signature$'; + } +} + + diff --git a/modoboa_installer/scripts/files/rspamd/local.d/dkim_signing.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/dkim_signing.conf.tpl new file mode 100644 index 0000000..0025c3b --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/dkim_signing.conf.tpl @@ -0,0 +1,3 @@ +try_fallback = false; +selector_map = "%selectors_path_map"; +path_map = "%keys_path_map"; diff --git a/modoboa_installer/scripts/files/rspamd/local.d/greylisting.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/greylisting.conf.tpl new file mode 100644 index 0000000..cc44e3a --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/greylisting.conf.tpl @@ -0,0 +1,2 @@ +servers = "127.0.0.1:6379"; +%{postwhite_enabled}whitelisted_ip = "/etc/postfix/postscreen_spf_whitelist.cidr" diff --git a/modoboa_installer/scripts/files/rspamd/local.d/mx_check.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/mx_check.conf.tpl new file mode 100644 index 0000000..1ead4ee --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/mx_check.conf.tpl @@ -0,0 +1 @@ +enabled = true; diff --git a/modoboa_installer/scripts/files/rspamd/local.d/rbl.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/rbl.conf.tpl new file mode 100644 index 0000000..35b23ba --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/rbl.conf.tpl @@ -0,0 +1,6 @@ +# to disable all predefined rules if the user doesn't want dnsbl + +url_whitelist = []; + +rbls { +} diff --git a/modoboa_installer/scripts/files/rspamd/local.d/spf.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/spf.conf.tpl new file mode 100644 index 0000000..85a98bc --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/spf.conf.tpl @@ -0,0 +1,6 @@ +spf_cache_size = 1k; +spf_cache_expire = 1d; +max_dns_nesting = 10; +max_dns_requests = 30; +min_cache_ttl = 5m; +disable_ipv6 = false; diff --git a/modoboa_installer/scripts/files/rspamd/local.d/worker-controller.inc b/modoboa_installer/scripts/files/rspamd/local.d/worker-controller.inc new file mode 100644 index 0000000..8490a18 --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/worker-controller.inc @@ -0,0 +1 @@ +enable_password = %controller_password diff --git a/modoboa_installer/scripts/files/rspamd/local.d/worker-controller.inc.tpl b/modoboa_installer/scripts/files/rspamd/local.d/worker-controller.inc.tpl new file mode 100644 index 0000000..8490a18 --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/worker-controller.inc.tpl @@ -0,0 +1 @@ +enable_password = %controller_password diff --git a/modoboa_installer/scripts/files/rspamd/local.d/worker-normal.inc.tpl b/modoboa_installer/scripts/files/rspamd/local.d/worker-normal.inc.tpl new file mode 100644 index 0000000..a6ee831 --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/worker-normal.inc.tpl @@ -0,0 +1 @@ +enabled = false; diff --git a/modoboa_installer/scripts/files/rspamd/local.d/worker-proxy.inc.tpl b/modoboa_installer/scripts/files/rspamd/local.d/worker-proxy.inc.tpl new file mode 100644 index 0000000..f64333f --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/worker-proxy.inc.tpl @@ -0,0 +1,3 @@ +upstream "local" { + self_scan = yes; +} diff --git a/modoboa_installer/scripts/postfix.py b/modoboa_installer/scripts/postfix.py index 6a2d743..ba1fcbc 100644 --- a/modoboa_installer/scripts/postfix.py +++ b/modoboa_installer/scripts/postfix.py @@ -60,7 +60,9 @@ class Postfix(base.Installer): "modoboa_instance_path": self.config.get( "modoboa", "instance_path"), "opendkim_port": self.config.get( - "opendkim", "port") + "opendkim", "port"), + "rspamd_disabled": "" if not self.config.get( + "rspamd", "enabled") else "#" }) return context diff --git a/modoboa_installer/scripts/rspamd.py b/modoboa_installer/scripts/rspamd.py new file mode 100644 index 0000000..5e7640b --- /dev/null +++ b/modoboa_installer/scripts/rspamd.py @@ -0,0 +1,82 @@ +"""Amavis related functions.""" + +import os + +from .. import package +from .. import utils + +from . import base +from . import backup, install + + +class Rspamd(base.Installer): + + """Rspamd installer.""" + + appname = "rspamd" + packages = { + "deb": [ + "rspamd", "redis" + ] + } + config_files = ["local.d/dkim_signing.conf", + "local.d/mx_check.conf", + "local.d/spf.conf", + "local.d/worker-controller.inc", + "local.d/worker-normal.inc", + "local.d/worker-proxy.inc"] + + @property + def config_dir(self): + """Return appropriate config dir.""" + return "/etc/rspamd" + + def get_config_files(self): + """Return appropriate config files.""" + _config_files = self.config_files + if self.config.get("clamav", "enabled"): + _config_files.append("local.d/antivirus.conf") + if self.app_config["dnsbl"]: + _config_files.append("local.d/greylisting.conf") + if not self.app_config["dnsbl"]: + _config_files.append("local.d/rbl.conf") + return _config_files + + def get_template_context(self): + _context = super().get_template_context() + code, controller_password = utils.exec_cmd( + r"rspamadm pw -p {}".format(self.app_config["password"])) + if code != 0: + utils.error("Error setting rspamd password. " + "Please make sure it is not 'q1' or 'q2'." + "Storing the password in plain. See" + "https://rspamd.com/doc/quickstart.html#setting-the-controller-password") + _context["controller_password"] = password + else: + _context["controller_password"] = controller_password + return _context + + def custom_backup(self, path): + """Backup custom configuration if any.""" + custom_config_dir = os.path.join(self.config_dir, + "/local.d/") + custom_backup_dir = os.path.join(path, "/rspamd/") + local_files = [f for f in os.listdir(custom_config_dir) + if os.path.isfile(custom_config_dir, f) + ] + for file in local_files: + utils.copy_file(file, custom_backup_dir) + if len(local_files) != 0: + utils.success("Rspamd custom configuration saved!") + + def restore(self): + """Restore custom config files.""" + custom_config_dir = os.path.join(self.config_dir, + "/local.d/") + custom_backup_dir = os.path.join(path, "/rspamd/") + backed_up_files = [f for f in os.listdir(custom_backup_dir) + if os.path.isfile(custom_backup_dir, f) + ] + for file in backed_up_files: + utils.copy_file(file, custom_config_dir) + utils.success("Custom Rspamd configuration restored.") From 45870e20efa3efa7a68c8e692c71889cc179c99c Mon Sep 17 00:00:00 2001 From: Spitap Date: Tue, 4 Apr 2023 17:48:56 +0200 Subject: [PATCH 02/56] Fixed dict, few fixes --- modoboa_installer/config_dict_template.py | 2 +- modoboa_installer/scripts/modoboa.py | 21 ++++++++++++++------- modoboa_installer/scripts/rspamd.py | 17 +++++++++++++++++ 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index 093bc5b..4488625 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -234,7 +234,7 @@ ConfigDictTemplate = [ { "option": "password", "default": make_password, - } + }, { "option": "dnsbl", "default": "true", diff --git a/modoboa_installer/scripts/modoboa.py b/modoboa_installer/scripts/modoboa.py index c2d01bf..6336d85 100644 --- a/modoboa_installer/scripts/modoboa.py +++ b/modoboa_installer/scripts/modoboa.py @@ -49,17 +49,24 @@ class Modoboa(base.Installer): self.instance_path = self.config.get("modoboa", "instance_path") self.extensions = self.config.get("modoboa", "extensions").split() self.devmode = self.config.getboolean("modoboa", "devmode") - # Sanity check for amavis - self.amavis_enabled = False - if "modoboa-amavis" in self.extensions: - if self.config.getboolean("amavis", "enabled"): - self.amavis_enabled = True - else: - self.extensions.remove("modoboa-amavis") + # Sanity check for amavis and rspamd + self.amavis_enabled = sanity_check("modoboa-amavis", "amavis") + sanity_check("modoboa-rspamd", "rspamd") + self.dovecot_enabled = self.config.getboolean("dovecot", "enabled") self.opendkim_enabled = self.config.getboolean("opendkim", "enabled") self.dkim_cron_enabled = False + def sanity_check(extension, plugin): + # Sanity check for plugin requirements + enabled = False + if extension in self.extensions: + if self.config.getboolean(plugin, "enabled"): + enabled = True + else: + self.extensions.remove(extension) + return enabled + def is_extension_ok_for_version(self, extension, version): """Check if extension can be installed with this modo version.""" version = utils.convert_version_to_int(version) diff --git a/modoboa_installer/scripts/rspamd.py b/modoboa_installer/scripts/rspamd.py index 5e7640b..cc34f21 100644 --- a/modoboa_installer/scripts/rspamd.py +++ b/modoboa_installer/scripts/rspamd.py @@ -31,6 +31,23 @@ class Rspamd(base.Installer): """Return appropriate config dir.""" return "/etc/rspamd" + def install_config_files(self): + """Make sure config directory exists.""" + user = self.config.get("modoboa", "user") + pw = pwd.getpwnam(user) + targets = [ + [self.app_config["dkim_keys_storage_dir"], pw[2], pw[3]] + ] + for target in targets: + if not os.path.exists(target[0]): + utils.mkdir( + target[0], + stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | + stat.S_IROTH | stat.S_IXOTH, + target[1], target[2] + ) + super().install_config_files() + def get_config_files(self): """Return appropriate config files.""" _config_files = self.config_files From 1423fe0e6e5c67a0d17c2b61a9ef817413cd7319 Mon Sep 17 00:00:00 2001 From: Spitap Date: Tue, 4 Apr 2023 23:17:49 +0200 Subject: [PATCH 03/56] Better configuration --- installer.cfg | 131 ++++++++++++++++++ modoboa_installer/config_dict_template.py | 2 - .../scripts/files/postfix/main.cf.tpl | 17 +-- .../scripts/files/postfix/master.cf.tpl | 3 +- .../files/rspamd/local.d/antivirus.conf.tpl | 11 +- ...greylisting.conf.tpl => greylist.conf.tpl} | 1 + .../files/rspamd/local.d/metrics.conf.tpl | 20 +++ .../rspamd/local.d/milter_headers.conf.tpl | 33 +++++ .../rspamd/local.d/worker-controller.inc | 1 - modoboa_installer/scripts/postfix.py | 2 +- modoboa_installer/scripts/rspamd.py | 19 ++- 11 files changed, 218 insertions(+), 22 deletions(-) create mode 100644 installer.cfg rename modoboa_installer/scripts/files/rspamd/local.d/{greylisting.conf.tpl => greylist.conf.tpl} (73%) create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/metrics.conf.tpl create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/milter_headers.conf.tpl delete mode 100644 modoboa_installer/scripts/files/rspamd/local.d/worker-controller.inc diff --git a/installer.cfg b/installer.cfg new file mode 100644 index 0000000..62f6c28 --- /dev/null +++ b/installer.cfg @@ -0,0 +1,131 @@ +[general] +hostname = mail.%(domain)s + +[certificate] +generate = true +type = letsencrypt + +[letsencrypt] +email = aa@aa.fr + +[database] +engine = postgres +host = 127.0.0.1 +install = true + +[postgres] +user = postgres +password = + +[mysql] +user = root +password = DPnHqZYHZ3gegiVT +charset = utf8 +collation = utf8_general_ci + +[fail2ban] +enabled = true +config_dir = /etc/fail2ban +max_retry = 20 +ban_time = 3600 +find_time = 30 + +[modoboa] +user = modoboa +home_dir = /srv/modoboa +venv_path = %(home_dir)s/env +instance_path = %(home_dir)s/instance +timezone = Europe/Paris +dbname = modoboa +dbuser = modoboa +dbpassword = Zj3PY6G2M8Hw6Gig +extensions = modoboa-rspamd modoboa-pdfcredentials modoboa-postfix-autoreply modoboa-sievefilters modoboa-webmail modoboa-contacts modoboa-radicale +devmode = false + +[automx] +enabled = true +user = automx +config_dir = /etc +home_dir = /srv/automx +venv_path = %(home_dir)s/env +instance_path = %(home_dir)s/instance + +[rspamd] +enabled = true +password = B7ugujmFa2LLwu93 +dnsbl = true +dkim_keys_storage_dir = /var/lib/dkim +keys_path_map = /var/lib/dkim/keys.path.map +selectors_path_map = /var/lib/dkim/selectors.path.map +greylisting = true + +[amavis] +enabled = false +user = amavis +max_servers = 2 +dbname = amavis +dbuser = amavis +dbpassword = YSidxAfIqPC191Ir + +[clamav] +enabled = true +user = clamav + +[dovecot] +enabled = true +config_dir = /etc/dovecot +user = dovecot +home_dir = /srv/vmail +mailboxes_owner = vmail +extra_protocols = +postmaster_address = postmaster@%(domain)s +radicale_auth_socket_path = /var/run/dovecot/auth-radicale + +[nginx] +enabled = true +config_dir = /etc/nginx + +[razor] +enabled = true +config_dir = /etc/razor + +[postfix] +enabled = true +config_dir = /etc/postfix +message_size_limit = 11534336 + +[postwhite] +enabled = true +config_dir = /etc + +[spamassassin] +enabled = false +config_dir = /etc/mail/spamassassin +dbname = spamassassin +dbuser = spamassassin +dbpassword = s44EHekTTwOboebX + +[uwsgi] +enabled = true +config_dir = /etc/uwsgi +nb_processes = 2 + +[radicale] +enabled = true +user = radicale +config_dir = /etc/radicale +home_dir = /srv/radicale +venv_path = %(home_dir)s/env + +[opendkim] +enabled = false +user = opendkim +config_dir = /etc +port = 12345 +keys_storage_dir = /var/lib/dkim +dbuser = opendkim +dbpassword = acTggtM3vZeVBYRn + +[backup] +default_path = ./modoboa_backup/ + diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index 4488625..cfc9482 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -283,8 +283,6 @@ ConfigDictTemplate = [ { "option": "dbpassword", "default": make_password, - "customizable": True, - "question": "Please enter amavis db password" }, ], }, diff --git a/modoboa_installer/scripts/files/postfix/main.cf.tpl b/modoboa_installer/scripts/files/postfix/main.cf.tpl index c112975..dd110bb 100644 --- a/modoboa_installer/scripts/files/postfix/main.cf.tpl +++ b/modoboa_installer/scripts/files/postfix/main.cf.tpl @@ -124,6 +124,7 @@ strict_rfc821_envelopes = yes # Rspamd setup %{rspamd_enabled}smtpd_milters = inet:localhost:11332 +%{rspamd_enabled}non_smtpd_milters = inet:localhost:11332 %{rspamd_enabled}milter_default_action = accept %{rspamd_enabled}milter_protocol = 6 @@ -160,14 +161,14 @@ smtpd_recipient_restrictions = %{rspamd_disabled}postscreen_dnsbl_threshold = 3 %{rspamd_disabled}postscreen_dnsbl_action = enforce -postscreen_greet_banner = Welcome, please wait... -postscreen_greet_action = enforce +%{rspamd_disabled}postscreen_greet_banner = Welcome, please wait... +%{rspamd_disabled}postscreen_greet_action = enforce -postscreen_pipelining_enable = yes -postscreen_pipelining_action = enforce +%{rspamd_disabled}postscreen_pipelining_enable = yes +%{rspamd_disabled}postscreen_pipelining_action = enforce -postscreen_non_smtp_command_enable = yes -postscreen_non_smtp_command_action = enforce +%{rspamd_disabled}postscreen_non_smtp_command_enable = yes +%{rspamd_disabled}postscreen_non_smtp_command_action = enforce -postscreen_bare_newline_enable = yes -postscreen_bare_newline_action = enforce +%{rspamd_disabled}postscreen_bare_newline_enable = yes +%{rspamd_disabled}postscreen_bare_newline_action = enforce diff --git a/modoboa_installer/scripts/files/postfix/master.cf.tpl b/modoboa_installer/scripts/files/postfix/master.cf.tpl index 72b2369..9f25b43 100644 --- a/modoboa_installer/scripts/files/postfix/master.cf.tpl +++ b/modoboa_installer/scripts/files/postfix/master.cf.tpl @@ -9,7 +9,8 @@ # service type private unpriv chroot wakeup maxproc command + args # (yes) (yes) (yes) (never) (100) # ========================================================================== -smtp inet n - - - 1 postscreen +%{rspamd_disabled}smtp inet n - - - 1 postscreen +%{rspamd_enabled}smtp inet n - - - - smtpd smtpd pass - - - - - smtpd %{amavis_enabled} -o smtpd_proxy_filter=inet:[127.0.0.1]:10024 %{amavis_enabled} -o smtpd_proxy_options=speed_adjust diff --git a/modoboa_installer/scripts/files/rspamd/local.d/antivirus.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/antivirus.conf.tpl index 235ea0f..9aafe74 100644 --- a/modoboa_installer/scripts/files/rspamd/local.d/antivirus.conf.tpl +++ b/modoboa_installer/scripts/files/rspamd/local.d/antivirus.conf.tpl @@ -1,11 +1,14 @@ clamav { + scan_mime_parts = true; + scan_text_mime = true; + scan_image_mime = true; + symbol = "CLAM_VIRUS"; type = "clamav"; - servers = "127.0.0.1:3310"; + servers = "/var/run/clamd.amavisd/clamd.sock"; + patterns { # symbol_name = "pattern"; - JUST_EICAR = '^Eicar-Test-Signature$'; + JUST_EICAR = "Test.EICAR"; } } - - diff --git a/modoboa_installer/scripts/files/rspamd/local.d/greylisting.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/greylist.conf.tpl similarity index 73% rename from modoboa_installer/scripts/files/rspamd/local.d/greylisting.conf.tpl rename to modoboa_installer/scripts/files/rspamd/local.d/greylist.conf.tpl index cc44e3a..cf6c036 100644 --- a/modoboa_installer/scripts/files/rspamd/local.d/greylisting.conf.tpl +++ b/modoboa_installer/scripts/files/rspamd/local.d/greylist.conf.tpl @@ -1,2 +1,3 @@ +%{greylisting_disabled}enabled = false; servers = "127.0.0.1:6379"; %{postwhite_enabled}whitelisted_ip = "/etc/postfix/postscreen_spf_whitelist.cidr" diff --git a/modoboa_installer/scripts/files/rspamd/local.d/metrics.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/metrics.conf.tpl new file mode 100644 index 0000000..896e746 --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/metrics.conf.tpl @@ -0,0 +1,20 @@ +actions { + reject = 15; # normal value is 15, 150 so it will never be rejected + add_header = 6; # set to 0.1 for testing, 6 for normal operation. + rewrite_subject = 8; # Default: 8 + greylist = 4; # Default: 4 +} + +group "antivirus" { + symbol "JUST_EICAR" { + weight = 10; + description = "Eicar test signature"; + } + symbol "CLAM_VIRUS_FAIL" { + weight = 0; + } + symbol "CLAM_VIRUS" { + weight = 10; + description = "ClamAV found a Virus"; + } +} diff --git a/modoboa_installer/scripts/files/rspamd/local.d/milter_headers.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/milter_headers.conf.tpl new file mode 100644 index 0000000..de91d0b --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/milter_headers.conf.tpl @@ -0,0 +1,33 @@ +use = ["x-spam-status", "my-x-spam-score" ,"x-virus","authentication-results" ]; +extended_spam_headers = false; +skip_local = false; +skip_authenticated = false; + +# Write the score as a header +custom { + my-x-spam-score = < Date: Tue, 4 Apr 2023 23:50:08 +0200 Subject: [PATCH 04/56] Removed installer.cfg --- .gitignore | 2 + installer.cfg | 131 -------------------------------------------------- 2 files changed, 2 insertions(+), 131 deletions(-) delete mode 100644 installer.cfg diff --git a/.gitignore b/.gitignore index e12944a..2a204ee 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,5 @@ target/ # PyCharm .idea/ + +installer.cfg \ No newline at end of file diff --git a/installer.cfg b/installer.cfg deleted file mode 100644 index 62f6c28..0000000 --- a/installer.cfg +++ /dev/null @@ -1,131 +0,0 @@ -[general] -hostname = mail.%(domain)s - -[certificate] -generate = true -type = letsencrypt - -[letsencrypt] -email = aa@aa.fr - -[database] -engine = postgres -host = 127.0.0.1 -install = true - -[postgres] -user = postgres -password = - -[mysql] -user = root -password = DPnHqZYHZ3gegiVT -charset = utf8 -collation = utf8_general_ci - -[fail2ban] -enabled = true -config_dir = /etc/fail2ban -max_retry = 20 -ban_time = 3600 -find_time = 30 - -[modoboa] -user = modoboa -home_dir = /srv/modoboa -venv_path = %(home_dir)s/env -instance_path = %(home_dir)s/instance -timezone = Europe/Paris -dbname = modoboa -dbuser = modoboa -dbpassword = Zj3PY6G2M8Hw6Gig -extensions = modoboa-rspamd modoboa-pdfcredentials modoboa-postfix-autoreply modoboa-sievefilters modoboa-webmail modoboa-contacts modoboa-radicale -devmode = false - -[automx] -enabled = true -user = automx -config_dir = /etc -home_dir = /srv/automx -venv_path = %(home_dir)s/env -instance_path = %(home_dir)s/instance - -[rspamd] -enabled = true -password = B7ugujmFa2LLwu93 -dnsbl = true -dkim_keys_storage_dir = /var/lib/dkim -keys_path_map = /var/lib/dkim/keys.path.map -selectors_path_map = /var/lib/dkim/selectors.path.map -greylisting = true - -[amavis] -enabled = false -user = amavis -max_servers = 2 -dbname = amavis -dbuser = amavis -dbpassword = YSidxAfIqPC191Ir - -[clamav] -enabled = true -user = clamav - -[dovecot] -enabled = true -config_dir = /etc/dovecot -user = dovecot -home_dir = /srv/vmail -mailboxes_owner = vmail -extra_protocols = -postmaster_address = postmaster@%(domain)s -radicale_auth_socket_path = /var/run/dovecot/auth-radicale - -[nginx] -enabled = true -config_dir = /etc/nginx - -[razor] -enabled = true -config_dir = /etc/razor - -[postfix] -enabled = true -config_dir = /etc/postfix -message_size_limit = 11534336 - -[postwhite] -enabled = true -config_dir = /etc - -[spamassassin] -enabled = false -config_dir = /etc/mail/spamassassin -dbname = spamassassin -dbuser = spamassassin -dbpassword = s44EHekTTwOboebX - -[uwsgi] -enabled = true -config_dir = /etc/uwsgi -nb_processes = 2 - -[radicale] -enabled = true -user = radicale -config_dir = /etc/radicale -home_dir = /srv/radicale -venv_path = %(home_dir)s/env - -[opendkim] -enabled = false -user = opendkim -config_dir = /etc -port = 12345 -keys_storage_dir = /var/lib/dkim -dbuser = opendkim -dbpassword = acTggtM3vZeVBYRn - -[backup] -default_path = ./modoboa_backup/ - From 69a8f082468818c10f5de14c3a8ff4c22e2f2c68 Mon Sep 17 00:00:00 2001 From: Spitap Date: Wed, 5 Apr 2023 18:09:54 +0200 Subject: [PATCH 05/56] fixed test --- tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests.py b/tests.py index 6f8fdf6..e77d6ae 100644 --- a/tests.py +++ b/tests.py @@ -99,7 +99,7 @@ class ConfigFileTestCase(unittest.TestCase): def test_interactive_mode_letsencrypt(self, mock_user_input): """Check interactive mode.""" mock_user_input.side_effect = [ - "1", "admin@example.test", "0", "", "", "", "", "" + "1", "admin@example.test", "0", "", "", "", "" ] with open(os.devnull, "w") as fp: sys.stdout = fp @@ -126,8 +126,8 @@ class ConfigFileTestCase(unittest.TestCase): "example.test"]) self.assertTrue(os.path.exists(self.cfgfile)) self.assertIn( - "modoboa automx amavis clamav dovecot nginx razor postfix" - " postwhite spamassassin uwsgi", + "fail2ban modoboa automx rspamd clamav dovecot nginx razor " + "postfix postwhite uwsgi radicale", out.getvalue() ) self.assertNotIn("It seems that your config file is outdated.", From 46bbb1039bb90e96976ae1c1597e967a4a21f5ae Mon Sep 17 00:00:00 2001 From: Spitap Date: Wed, 21 Jun 2023 16:04:38 +0200 Subject: [PATCH 06/56] updated rspamd config --- modoboa_installer/config_dict_template.py | 8 +++++ modoboa_installer/scripts/clamav.py | 7 ++-- .../files/rspamd/local.d/antivirus.conf.tpl | 5 +-- .../files/rspamd/local.d/greylist.conf.tpl | 1 - .../files/rspamd/local.d/groups.conf.tpl | 5 +++ .../files/rspamd/local.d/redis.conf.tpl | 2 ++ .../files/rspamd/local.d/settings.conf.tpl | 8 +++++ modoboa_installer/scripts/postfix.py | 14 ++++++-- modoboa_installer/scripts/rspamd.py | 35 +++++++++++++++++-- 9 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/groups.conf.tpl create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/redis.conf.tpl create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/settings.conf.tpl diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index cfc9482..c7c1b97 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -254,6 +254,14 @@ ConfigDictTemplate = [ { "option": "greylisting", "default": "true" + }, + { + "option": "whitelist_auth", + "default": "true" + }, + { + "option": "whitelist_auth_weigth", + "default": "-5" } ], }, diff --git a/modoboa_installer/scripts/clamav.py b/modoboa_installer/scripts/clamav.py index 2ff4868..a62eda6 100644 --- a/modoboa_installer/scripts/clamav.py +++ b/modoboa_installer/scripts/clamav.py @@ -42,9 +42,10 @@ class Clamav(base.Installer): """Additional tasks.""" if package.backend.FORMAT == "deb": user = self.config.get(self.appname, "user") - system.add_user_to_group( - user, self.config.get("amavis", "user") - ) + if self.config.get("amavis", "enabled").lower() == "true": + system.add_user_to_group( + user, self.config.get("amavis", "user") + ) pattern = ( "s/^AllowSupplementaryGroups false/" "AllowSupplementaryGroups true/") diff --git a/modoboa_installer/scripts/files/rspamd/local.d/antivirus.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/antivirus.conf.tpl index 9aafe74..5e50a4e 100644 --- a/modoboa_installer/scripts/files/rspamd/local.d/antivirus.conf.tpl +++ b/modoboa_installer/scripts/files/rspamd/local.d/antivirus.conf.tpl @@ -2,10 +2,11 @@ clamav { scan_mime_parts = true; scan_text_mime = true; scan_image_mime = true; - + retransmits = 2; + timeout = 30; symbol = "CLAM_VIRUS"; type = "clamav"; - servers = "/var/run/clamd.amavisd/clamd.sock"; + servers = "127.0.0.1:3310" patterns { # symbol_name = "pattern"; diff --git a/modoboa_installer/scripts/files/rspamd/local.d/greylist.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/greylist.conf.tpl index cf6c036..bf90f46 100644 --- a/modoboa_installer/scripts/files/rspamd/local.d/greylist.conf.tpl +++ b/modoboa_installer/scripts/files/rspamd/local.d/greylist.conf.tpl @@ -1,3 +1,2 @@ %{greylisting_disabled}enabled = false; servers = "127.0.0.1:6379"; -%{postwhite_enabled}whitelisted_ip = "/etc/postfix/postscreen_spf_whitelist.cidr" diff --git a/modoboa_installer/scripts/files/rspamd/local.d/groups.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/groups.conf.tpl new file mode 100644 index 0000000..0e10663 --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/groups.conf.tpl @@ -0,0 +1,5 @@ +symbols { + "WHITELIST_AUTHENTICATED" { + weight = %whitelist_auth_weigth; + } +} diff --git a/modoboa_installer/scripts/files/rspamd/local.d/redis.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/redis.conf.tpl new file mode 100644 index 0000000..6b6c00d --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/redis.conf.tpl @@ -0,0 +1,2 @@ +write_servers = "localhost"; +read_servers = "localhost"; diff --git a/modoboa_installer/scripts/files/rspamd/local.d/settings.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/settings.conf.tpl new file mode 100644 index 0000000..1eae1c0 --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/settings.conf.tpl @@ -0,0 +1,8 @@ +authenticated { + priority = high; + authenticated = yes; + apply { + groups_disabled = ["rbl", "spf"]; + } +%{whitelist_auth_enabled} symbols ["WHITELIST_AUTHENTICATED"]; +} diff --git a/modoboa_installer/scripts/postfix.py b/modoboa_installer/scripts/postfix.py index 19e6427..ea56b75 100644 --- a/modoboa_installer/scripts/postfix.py +++ b/modoboa_installer/scripts/postfix.py @@ -103,8 +103,18 @@ class Postfix(base.Installer): utils.exec_cmd("postalias {}".format(aliases_file)) # Postwhite - install("postwhite", self.config, self.upgrade, self.archive_path) + condition = ( + not self.config.getboolean("rspamd", "enabled") and + self.config.getboolean("postwhite", "enabled") + ) + if condition: + install("postwhite", self.config, self.upgrade, self.archive_path) def backup(self, path): """Launch postwhite backup.""" - backup("postwhite", self.config, path) + condition = ( + not self.config.getboolean("rspamd", "enabled") and + self.config.getboolean("postwhite", "enabled") + ) + if condition: + backup("postwhite", self.config, path) diff --git a/modoboa_installer/scripts/rspamd.py b/modoboa_installer/scripts/rspamd.py index 1a4fbc0..7313ffa 100644 --- a/modoboa_installer/scripts/rspamd.py +++ b/modoboa_installer/scripts/rspamd.py @@ -4,6 +4,7 @@ import os from .. import package from .. import utils +from .. import system from . import base from . import backup, install @@ -34,6 +35,29 @@ class Rspamd(base.Installer): """Return appropriate config dir.""" return "/etc/rspamd" + def install_packages(self): + status, codename = utils.exec_cmd("lsb_release -c -s") + + if codename.lower() in ["bionic", "bookworm", "bullseye", "buster", + "focal", "jammy", "jessie", "sid", "stretch", + "trusty", "wheezy", "xenial"]: + utils.mkdir_safe("/etc/apt/keyrings") + + if codename.lower() == "bionic": + package.backend.install("software-properties-common") + utils.exec_cmd("add-apt-repository ppa:ubuntu-toolchain-r/test") + + utils.exec_cmd("wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -") + utils.exec_cmd(f"echo \"deb http://apt.llvm.org/{codename}/ llvm-toolchain-{codename}-16 main\" | sudo tee /etc/apt/sources.list.d/llvm-16.list") + utils.exec_cmd(f"echo \"deb-src http://apt.llvm.org/{codename}/ llvm-toolchain-{codename}-16 main\" | sudo tee -a /etc/apt/sources.list.d/llvm-16.list") + + utils.exec_cmd("wget -O- https://rspamd.com/apt-stable/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/rspamd.gpg > /dev/null") + utils.exec_cmd(f"echo \"deb [arch=amd64 signed-by=/etc/apt/keyrings/rspamd.gpg] http://rspamd.com/apt-stable/ {codename} main\" | sudo tee /etc/apt/sources.list.d/rspamd.list") + utils.exec_cmd(f"echo \"deb-src [arch=amd64 signed-by=/etc/apt/keyrings/rspamd.gpg] http://rspamd.com/apt-stable/ {codename} main\" | sudo tee -a /etc/apt/sources.list.d/rspamd.list") + package.backend.update() + + return super().install_packages() + def install_config_files(self): """Make sure config directory exists.""" user = self.config.get("modoboa", "user") @@ -58,6 +82,8 @@ class Rspamd(base.Installer): _config_files.append("local.d/antivirus.conf") if self.app_config["dnsbl"].lower() == "true": _config_files.append("local.d/rbl.conf") + if self.app_config["whitelist_auth"].lower() == "true": + _config_files.append("local.d/groups.conf") return _config_files def get_template_context(self): @@ -72,13 +98,16 @@ class Rspamd(base.Installer): _context["controller_password"] = password else: _context["controller_password"] = controller_password - _context["greylisting_disabled"] = "" if not self.app_config["greylisting"] else "#" - if not self.app_config["greylisting"]: - _context["postwhite_enabled"] = "#" + _context["greylisting_disabled"] = "" if not self.app_config["greylisting"].lower() == "true" else "#" + _context["whitelist_auth_enabled"] = "" if self.app_config["whitelist_auth"].lower() == "true" else "#" return _context def post_run(self): """Additional tasks.""" + system.add_user_to_group( + self.config.get("modoboa", "user"), + "_rspamd" + ) if self.config("clamav", "enabled"): install("clamav", self.config, self.upgrade, self.archive_path) From df23f4e1812cc0b7787de4d1bf8dd29516ebb058 Mon Sep 17 00:00:00 2001 From: Spitap Date: Tue, 5 Sep 2023 11:32:16 +0200 Subject: [PATCH 07/56] fix --- modoboa_installer/scripts/modoboa.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modoboa_installer/scripts/modoboa.py b/modoboa_installer/scripts/modoboa.py index 6336d85..e98a5b7 100644 --- a/modoboa_installer/scripts/modoboa.py +++ b/modoboa_installer/scripts/modoboa.py @@ -50,14 +50,14 @@ class Modoboa(base.Installer): self.extensions = self.config.get("modoboa", "extensions").split() self.devmode = self.config.getboolean("modoboa", "devmode") # Sanity check for amavis and rspamd - self.amavis_enabled = sanity_check("modoboa-amavis", "amavis") - sanity_check("modoboa-rspamd", "rspamd") + self.amavis_enabled = self.sanity_check("modoboa-amavis", "amavis") + self.sanity_check("modoboa-rspamd", "rspamd") self.dovecot_enabled = self.config.getboolean("dovecot", "enabled") self.opendkim_enabled = self.config.getboolean("opendkim", "enabled") self.dkim_cron_enabled = False - def sanity_check(extension, plugin): + def sanity_check(self, extension, plugin): # Sanity check for plugin requirements enabled = False if extension in self.extensions: From 70e9cffd87fe4f576260c17a6dc6be35a4f4573f Mon Sep 17 00:00:00 2001 From: Spitap Date: Wed, 6 Sep 2023 12:46:20 +0200 Subject: [PATCH 08/56] App incompatibility detection, updated for 2.2.0 --- modoboa_installer/compatibility_matrix.py | 7 +++++++ modoboa_installer/config_dict_template.py | 6 +++--- .../scripts/files/modoboa/supervisor-rq-dkim.tpl | 2 +- modoboa_installer/scripts/modoboa.py | 10 ++++++++++ modoboa_installer/scripts/rspamd.py | 3 +-- modoboa_installer/utils.py | 13 +++++++++++++ run.py | 7 +++++-- 7 files changed, 40 insertions(+), 8 deletions(-) diff --git a/modoboa_installer/compatibility_matrix.py b/modoboa_installer/compatibility_matrix.py index 9377c49..ac68194 100644 --- a/modoboa_installer/compatibility_matrix.py +++ b/modoboa_installer/compatibility_matrix.py @@ -37,3 +37,10 @@ REMOVED_EXTENSIONS = { "modoboa-radicale": "2.4.0", "modoboa-webmail": "2.4.0", } + +APP_INCOMPATIBILITY = { + "opendkim": ["rspamd"], + "amavis": ["rspamd"], + "postwhite": ["rspamd"], + "spamassassin": ["rspamd"] +} diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index c7c1b97..4d82cee 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -244,11 +244,11 @@ ConfigDictTemplate = [ "default": "/var/lib/dkim" }, { - "option": "keys_path_map", + "option": "key_map_path", "default": "/var/lib/dkim/keys.path.map" }, { - "option": "selectors_path_map", + "option": "selector_map_path", "default": "/var/lib/dkim/selectors.path.map" }, { @@ -392,7 +392,7 @@ ConfigDictTemplate = [ "values": [ { "option": "enabled", - "default": "true", + "default": "false", }, { "option": "config_dir", diff --git a/modoboa_installer/scripts/files/modoboa/supervisor-rq-dkim.tpl b/modoboa_installer/scripts/files/modoboa/supervisor-rq-dkim.tpl index 42d9bd0..531650e 100644 --- a/modoboa_installer/scripts/files/modoboa/supervisor-rq-dkim.tpl +++ b/modoboa_installer/scripts/files/modoboa/supervisor-rq-dkim.tpl @@ -3,7 +3,7 @@ autostart=true autorestart=true command=%{venv_path}/bin/python %{home_dir}/instance/manage.py rqworker dkim directory=%{home_dir} -user=%{opendkim_user} +user=%{dkim_user} redirect_stderr=true numprocs=1 stopsignal=TERM diff --git a/modoboa_installer/scripts/modoboa.py b/modoboa_installer/scripts/modoboa.py index e98a5b7..835ebe4 100644 --- a/modoboa_installer/scripts/modoboa.py +++ b/modoboa_installer/scripts/modoboa.py @@ -248,6 +248,7 @@ class Modoboa(base.Installer): "dovecot_mailboxes_owner": ( self.config.get("dovecot", "mailboxes_owner")), "opendkim_user": self.config.get("opendkim", "user"), + "dkim_user": "_rspamd" if self.config.getboolean("rspamd", "enabled") else self.config.get("opendkim", "user") "minutes": random.randint(1, 59), "hours": f"{random_hour},{random_hour+12}", "modoboa_2_2_or_greater": "" if self.modoboa_2_2_or_greater else "#", @@ -291,6 +292,15 @@ class Modoboa(base.Installer): if self.config.getboolean("opendkim", "enabled"): settings["admin"]["dkim_keys_storage_dir"] = ( self.config.get("opendkim", "keys_storage_dir")) + + if self.config.getboolean("rspamd", "enabled"): + settings["admin"]["dkim_keys_storage_dir"] = ( + self.config.get("rspamd", "dkim_keys_storage_dir")) + settings["modoboa_rspamd"]["key_map_path"] = ( + self.config.get("rspamd", "key_map_path")) + settings["modoboa_rspamd"]["selector_map_path"] = ( + self.config.get("rspamd", "selector_map_path")) + settings = json.dumps(settings) query = ( "UPDATE core_localconfig SET _parameters='{}'" diff --git a/modoboa_installer/scripts/rspamd.py b/modoboa_installer/scripts/rspamd.py index 7313ffa..d0c9bd1 100644 --- a/modoboa_installer/scripts/rspamd.py +++ b/modoboa_installer/scripts/rspamd.py @@ -60,8 +60,7 @@ class Rspamd(base.Installer): def install_config_files(self): """Make sure config directory exists.""" - user = self.config.get("modoboa", "user") - pw = pwd.getpwnam(user) + pw = pwd.getpwnam("_rspamd") targets = [ [self.app_config["dkim_keys_storage_dir"], pw[2], pw[3]] ] diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index a9cd5f4..6c6c178 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -19,6 +19,7 @@ except ImportError: import ConfigParser as configparser from . import config_dict_template +from . import compatibility_matrix.APP_INCOMPATIBILITY ENV = {} @@ -504,3 +505,15 @@ def create_oauth2_app(app_name: str, client_id: str, config) -> tuple[str, str]: ) exec_cmd(cmd) return client_id, client_secret + + +def check_app_compatibility(section, config): + """Check that the app can be installed in regards to other enabled apps.""" + incompatible_app = [] + if section in APP_INCOMPATIBILITY.keys(): + for app in APP_INCOMPATIBILITY[section]: + if config.getboolean(app, "enabled"): + error(f"{section} cannont be installed if {app} is enabled. " + "Please disable one of them.") + incompatible_app.append(app) + return len(incompatible_app) == 0 diff --git a/run.py b/run.py index 22cfa14..fa8c60c 100755 --- a/run.py +++ b/run.py @@ -22,14 +22,13 @@ from modoboa_installer import utils PRIMARY_APPS = [ - "amavis", "fail2ban", "modoboa", "automx", "radicale", "uwsgi", "nginx", - "opendkim", + "rspamd", "postfix", "dovecot" ] @@ -261,6 +260,7 @@ def main(input_args): # Show concerned components components = [] + incompatible_app_detected = False for section in config.sections(): if section in ["general", "database", "mysql", "postgres", "certificate", "letsencrypt", "backup"]: @@ -268,7 +268,10 @@ def main(input_args): if (config.has_option(section, "enabled") and not config.getboolean(section, "enabled")): continue + incompatible_app_detected = utils.check_app_compatibility(section, config) components.append(section) + if incompatible_app_detected: + sys.exit(0) utils.printcolor(" ".join(components), utils.YELLOW) if not args.force: answer = utils.user_input("Do you confirm? (Y/n) ") From 077e84349ae7b3875e594d4c8ac2dcf1c02c5653 Mon Sep 17 00:00:00 2001 From: Spitap Date: Wed, 6 Sep 2023 12:47:54 +0200 Subject: [PATCH 09/56] import fix --- modoboa_installer/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index 6c6c178..00b30b8 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -19,7 +19,7 @@ except ImportError: import ConfigParser as configparser from . import config_dict_template -from . import compatibility_matrix.APP_INCOMPATIBILITY +from .compatibility_matrix import APP_INCOMPATIBILITY ENV = {} From 35e9ea4bde7b7e6bc779e688763d027a884553ca Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 12 Sep 2023 16:58:23 +0200 Subject: [PATCH 10/56] Few fixes --- modoboa_installer/scripts/modoboa.py | 2 +- run.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/modoboa_installer/scripts/modoboa.py b/modoboa_installer/scripts/modoboa.py index 835ebe4..8c0cf8e 100644 --- a/modoboa_installer/scripts/modoboa.py +++ b/modoboa_installer/scripts/modoboa.py @@ -248,7 +248,7 @@ class Modoboa(base.Installer): "dovecot_mailboxes_owner": ( self.config.get("dovecot", "mailboxes_owner")), "opendkim_user": self.config.get("opendkim", "user"), - "dkim_user": "_rspamd" if self.config.getboolean("rspamd", "enabled") else self.config.get("opendkim", "user") + "dkim_user": "_rspamd" if self.config.getboolean("rspamd", "enabled") else self.config.get("opendkim", "user"), "minutes": random.randint(1, 59), "hours": f"{random_hour},{random_hour+12}", "modoboa_2_2_or_greater": "" if self.modoboa_2_2_or_greater else "#", diff --git a/run.py b/run.py index fa8c60c..6f7e689 100755 --- a/run.py +++ b/run.py @@ -260,7 +260,6 @@ def main(input_args): # Show concerned components components = [] - incompatible_app_detected = False for section in config.sections(): if section in ["general", "database", "mysql", "postgres", "certificate", "letsencrypt", "backup"]: @@ -268,10 +267,10 @@ def main(input_args): if (config.has_option(section, "enabled") and not config.getboolean(section, "enabled")): continue - incompatible_app_detected = utils.check_app_compatibility(section, config) + incompatible_app_detected = not utils.check_app_compatibility(section, config) + if incompatible_app_detected: + sys.exit(0) components.append(section) - if incompatible_app_detected: - sys.exit(0) utils.printcolor(" ".join(components), utils.YELLOW) if not args.force: answer = utils.user_input("Do you confirm? (Y/n) ") From 0b85e2c7efeccc3c4214d23e3e5b8802118177f2 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 14 Sep 2023 09:50:40 +0200 Subject: [PATCH 11/56] Fixed wrong settings initialization --- modoboa_installer/scripts/modoboa.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modoboa_installer/scripts/modoboa.py b/modoboa_installer/scripts/modoboa.py index 8c0cf8e..4e8d28a 100644 --- a/modoboa_installer/scripts/modoboa.py +++ b/modoboa_installer/scripts/modoboa.py @@ -296,10 +296,10 @@ class Modoboa(base.Installer): if self.config.getboolean("rspamd", "enabled"): settings["admin"]["dkim_keys_storage_dir"] = ( self.config.get("rspamd", "dkim_keys_storage_dir")) - settings["modoboa_rspamd"]["key_map_path"] = ( - self.config.get("rspamd", "key_map_path")) - settings["modoboa_rspamd"]["selector_map_path"] = ( - self.config.get("rspamd", "selector_map_path")) + settings["modoboa_rspamd"] = { + "key_map_path": self.config.get("rspamd", "key_map_path"), + "selector_map_path": self.config.get("rspamd", "selector_map_path") + } settings = json.dumps(settings) query = ( From 92864aa28888ba925f0551f1f9215016c3a5a4f7 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 14 Sep 2023 10:10:07 +0200 Subject: [PATCH 12/56] Fixed issues in rspamd script --- modoboa_installer/config_dict_template.py | 4 +++ modoboa_installer/scripts/rspamd.py | 37 ++++++++++++----------- modoboa_installer/utils.py | 11 +++++++ 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index 4d82cee..48fc84b 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -231,6 +231,10 @@ ConfigDictTemplate = [ "option": "enabled", "default": "true", }, + { + "option": "user", + "default": "_rspamd", + }, { "option": "password", "default": make_password, diff --git a/modoboa_installer/scripts/rspamd.py b/modoboa_installer/scripts/rspamd.py index d0c9bd1..2c82043 100644 --- a/modoboa_installer/scripts/rspamd.py +++ b/modoboa_installer/scripts/rspamd.py @@ -1,17 +1,18 @@ """Amavis related functions.""" import os +import pwd +import stat from .. import package from .. import utils from .. import system from . import base -from . import backup, install +from . import install class Rspamd(base.Installer): - """Rspamd installer.""" appname = "rspamd" @@ -36,11 +37,8 @@ class Rspamd(base.Installer): return "/etc/rspamd" def install_packages(self): - status, codename = utils.exec_cmd("lsb_release -c -s") - - if codename.lower() in ["bionic", "bookworm", "bullseye", "buster", - "focal", "jammy", "jessie", "sid", "stretch", - "trusty", "wheezy", "xenial"]: + debian_based_dist, codename = utils.is_dist_debian_based() + if debian_based_dist: utils.mkdir_safe("/etc/apt/keyrings") if codename.lower() == "bionic": @@ -60,7 +58,8 @@ class Rspamd(base.Installer): def install_config_files(self): """Make sure config directory exists.""" - pw = pwd.getpwnam("_rspamd") + user = self.config.get(self.appname, "user") + pw = pwd.getpwnam(user) targets = [ [self.app_config["dkim_keys_storage_dir"], pw[2], pw[3]] ] @@ -94,7 +93,7 @@ class Rspamd(base.Installer): "Please make sure it is not 'q1' or 'q2'." "Storing the password in plain. See" "https://rspamd.com/doc/quickstart.html#setting-the-controller-password") - _context["controller_password"] = password + _context["controller_password"] = self.app_config["password"] else: _context["controller_password"] = controller_password _context["greylisting_disabled"] = "" if not self.app_config["greylisting"].lower() == "true" else "#" @@ -103,10 +102,11 @@ class Rspamd(base.Installer): def post_run(self): """Additional tasks.""" + user = self.config.get(self.appname, "user") system.add_user_to_group( self.config.get("modoboa", "user"), - "_rspamd" - ) + user + ) if self.config("clamav", "enabled"): install("clamav", self.config, self.upgrade, self.archive_path) @@ -127,10 +127,11 @@ class Rspamd(base.Installer): """Restore custom config files.""" custom_config_dir = os.path.join(self.config_dir, "/local.d/") - custom_backup_dir = os.path.join(path, "/rspamd/") - backed_up_files = [f for f in os.listdir(custom_backup_dir) - if os.path.isfile(custom_backup_dir, f) - ] - for file in backed_up_files: - utils.copy_file(file, custom_config_dir) - utils.success("Custom Rspamd configuration restored.") + custom_backup_dir = os.path.join(self.archive_path, "/rspamd/") + backed_up_files = [ + f for f in os.listdir(custom_backup_dir) + if os.path.isfile(custom_backup_dir, f) + ] + for f in backed_up_files: + utils.copy_file(f, custom_config_dir) + utils.success("Custom Rspamd configuration restored.") diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index 00b30b8..2979cc9 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -103,6 +103,17 @@ def dist_name(): return dist_info()[0].lower() +def is_dist_debian_based() -> (bool, str): + """Check if current OS is Debian based or not.""" + status, codename = exec_cmd("lsb_release -c -s") + codename = codename.lower() + return codename in [ + "bionic", "bookworm", "bullseye", "buster", + "focal", "jammy", "jessie", "sid", "stretch", + "trusty", "wheezy", "xenial" + ], codename + + def mkdir(path, mode, uid, gid): """Create a directory.""" if not os.path.exists(path): From 2564f856bdd1809aaf6f2c4f1ed5dbb82c67cc5f Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 14 Sep 2023 10:18:12 +0200 Subject: [PATCH 13/56] Fixed wrong setting names --- modoboa_installer/config_dict_template.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index 48fc84b..0ed9462 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -248,11 +248,11 @@ ConfigDictTemplate = [ "default": "/var/lib/dkim" }, { - "option": "key_map_path", + "option": "keys_map_path", "default": "/var/lib/dkim/keys.path.map" }, { - "option": "selector_map_path", + "option": "selectors_map_path", "default": "/var/lib/dkim/selectors.path.map" }, { From d44faf96b1bb44a831403acccfe1e86caa40c1af Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 14 Sep 2023 10:21:19 +0200 Subject: [PATCH 14/56] Consistency for variable names --- modoboa_installer/config_dict_template.py | 4 ++-- .../scripts/files/rspamd/local.d/dkim_signing.conf.tpl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index 0ed9462..48fc84b 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -248,11 +248,11 @@ ConfigDictTemplate = [ "default": "/var/lib/dkim" }, { - "option": "keys_map_path", + "option": "key_map_path", "default": "/var/lib/dkim/keys.path.map" }, { - "option": "selectors_map_path", + "option": "selector_map_path", "default": "/var/lib/dkim/selectors.path.map" }, { diff --git a/modoboa_installer/scripts/files/rspamd/local.d/dkim_signing.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/dkim_signing.conf.tpl index 0025c3b..d3a5174 100644 --- a/modoboa_installer/scripts/files/rspamd/local.d/dkim_signing.conf.tpl +++ b/modoboa_installer/scripts/files/rspamd/local.d/dkim_signing.conf.tpl @@ -1,3 +1,3 @@ try_fallback = false; -selector_map = "%selectors_path_map"; -path_map = "%keys_path_map"; +selector_map = "%selector_path_map"; +path_map = "%key_path_map"; From ec82b346a34b32e32cd01105d06ba3590753735f Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 14 Sep 2023 10:25:00 +0200 Subject: [PATCH 15/56] Fixed wrong setting names! --- .../scripts/files/rspamd/local.d/dkim_signing.conf.tpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modoboa_installer/scripts/files/rspamd/local.d/dkim_signing.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/dkim_signing.conf.tpl index d3a5174..3dcf992 100644 --- a/modoboa_installer/scripts/files/rspamd/local.d/dkim_signing.conf.tpl +++ b/modoboa_installer/scripts/files/rspamd/local.d/dkim_signing.conf.tpl @@ -1,3 +1,3 @@ try_fallback = false; -selector_map = "%selector_path_map"; -path_map = "%key_path_map"; +selector_map = "%selector_map_path"; +path_map = "%key_map_path"; From fb42636df0875d1a3a7b5fa6407b71963bb5821d Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 14 Sep 2023 10:33:02 +0200 Subject: [PATCH 16/56] Escape % character in config file --- .../scripts/files/rspamd/local.d/milter_headers.conf.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa_installer/scripts/files/rspamd/local.d/milter_headers.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/milter_headers.conf.tpl index de91d0b..f3c489a 100644 --- a/modoboa_installer/scripts/files/rspamd/local.d/milter_headers.conf.tpl +++ b/modoboa_installer/scripts/files/rspamd/local.d/milter_headers.conf.tpl @@ -11,7 +11,7 @@ custom { -- return no error return nil, -- header(s) to add - {['X-Spam-Score'] = string.format('%.2f', sc[1])}, + {['X-Spam-Score'] = string.format('%%.2f', sc[1])}, -- header(s) to remove {['X-Spam-Score'] = 1}, -- metadata to store From dea95ee1baa435b7468a9662a8207d7837d583a7 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 14 Sep 2023 10:41:45 +0200 Subject: [PATCH 17/56] Fixed wrong access to config option --- modoboa_installer/scripts/rspamd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa_installer/scripts/rspamd.py b/modoboa_installer/scripts/rspamd.py index 2c82043..452038d 100644 --- a/modoboa_installer/scripts/rspamd.py +++ b/modoboa_installer/scripts/rspamd.py @@ -107,7 +107,7 @@ class Rspamd(base.Installer): self.config.get("modoboa", "user"), user ) - if self.config("clamav", "enabled"): + if self.config.getboolean("clamav", "enabled"): install("clamav", self.config, self.upgrade, self.archive_path) def custom_backup(self, path): From 576c6964725708ee6093b86c07500efefc710365 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 14 Sep 2023 13:27:24 +0200 Subject: [PATCH 18/56] Fixed tests --- tests.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests.py b/tests.py index e77d6ae..cfb0d14 100644 --- a/tests.py +++ b/tests.py @@ -127,11 +127,12 @@ class ConfigFileTestCase(unittest.TestCase): self.assertTrue(os.path.exists(self.cfgfile)) self.assertIn( "fail2ban modoboa automx rspamd clamav dovecot nginx razor " - "postfix postwhite uwsgi radicale", + "postfix uwsgi radicale", out.getvalue() ) - self.assertNotIn("It seems that your config file is outdated.", - out.getvalue() + self.assertNotIn( + "It seems that your config file is outdated.", + out.getvalue() ) @patch("modoboa_installer.utils.user_input") From daf5338ee16ff1aeafa5463efb22ea94fcdcc9e3 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 21 Sep 2023 09:53:59 +0200 Subject: [PATCH 19/56] Make rspamd installation work --- modoboa_installer/scripts/rspamd.py | 4 ---- modoboa_installer/utils.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/modoboa_installer/scripts/rspamd.py b/modoboa_installer/scripts/rspamd.py index 452038d..87b36e4 100644 --- a/modoboa_installer/scripts/rspamd.py +++ b/modoboa_installer/scripts/rspamd.py @@ -45,10 +45,6 @@ class Rspamd(base.Installer): package.backend.install("software-properties-common") utils.exec_cmd("add-apt-repository ppa:ubuntu-toolchain-r/test") - utils.exec_cmd("wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -") - utils.exec_cmd(f"echo \"deb http://apt.llvm.org/{codename}/ llvm-toolchain-{codename}-16 main\" | sudo tee /etc/apt/sources.list.d/llvm-16.list") - utils.exec_cmd(f"echo \"deb-src http://apt.llvm.org/{codename}/ llvm-toolchain-{codename}-16 main\" | sudo tee -a /etc/apt/sources.list.d/llvm-16.list") - utils.exec_cmd("wget -O- https://rspamd.com/apt-stable/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/rspamd.gpg > /dev/null") utils.exec_cmd(f"echo \"deb [arch=amd64 signed-by=/etc/apt/keyrings/rspamd.gpg] http://rspamd.com/apt-stable/ {codename} main\" | sudo tee /etc/apt/sources.list.d/rspamd.list") utils.exec_cmd(f"echo \"deb-src [arch=amd64 signed-by=/etc/apt/keyrings/rspamd.gpg] http://rspamd.com/apt-stable/ {codename} main\" | sudo tee -a /etc/apt/sources.list.d/rspamd.list") diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index 2979cc9..75b5707 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -106,7 +106,7 @@ def dist_name(): def is_dist_debian_based() -> (bool, str): """Check if current OS is Debian based or not.""" status, codename = exec_cmd("lsb_release -c -s") - codename = codename.lower() + codename = codename.strip().lower() return codename in [ "bionic", "bookworm", "bullseye", "buster", "focal", "jammy", "jessie", "sid", "stretch", From 9ab1b5f18e393c460e510839f4d084d76f904384 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 21 Sep 2023 09:55:23 +0200 Subject: [PATCH 20/56] Convert codename to str --- modoboa_installer/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index 75b5707..61143e5 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -106,7 +106,7 @@ def dist_name(): def is_dist_debian_based() -> (bool, str): """Check if current OS is Debian based or not.""" status, codename = exec_cmd("lsb_release -c -s") - codename = codename.strip().lower() + codename = codename.decode().strip().lower() return codename in [ "bionic", "bookworm", "bullseye", "buster", "focal", "jammy", "jessie", "sid", "stretch", From b4b5fa288f6cf1a4b523c377658144cd1419a162 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 21 Sep 2023 10:05:45 +0200 Subject: [PATCH 21/56] Fixed wrong call to mkdir_safe --- modoboa_installer/scripts/rspamd.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modoboa_installer/scripts/rspamd.py b/modoboa_installer/scripts/rspamd.py index 87b36e4..49af4a8 100644 --- a/modoboa_installer/scripts/rspamd.py +++ b/modoboa_installer/scripts/rspamd.py @@ -39,7 +39,11 @@ class Rspamd(base.Installer): def install_packages(self): debian_based_dist, codename = utils.is_dist_debian_based() if debian_based_dist: - utils.mkdir_safe("/etc/apt/keyrings") + utils.mkdir_safe( + "/etc/apt/keyrings", + stat.S_IRWXU | stat.S_IRUSR | stat.S_IXUSR, + 0, 0 + ) if codename.lower() == "bionic": package.backend.install("software-properties-common") From 9f5542f07e03d78af9955e6e67acfca8493aa5c2 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 21 Sep 2023 10:24:43 +0200 Subject: [PATCH 22/56] Better custom repo installation --- modoboa_installer/package.py | 26 ++++++++++++++++++++++++-- modoboa_installer/scripts/rspamd.py | 14 +++++++++----- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/modoboa_installer/package.py b/modoboa_installer/package.py index 58ee940..195d034 100644 --- a/modoboa_installer/package.py +++ b/modoboa_installer/package.py @@ -49,7 +49,29 @@ class DEBPackage(Package): def restore_system(self): utils.exec_cmd("rm -f {}".format(self.policy_file)) - def update(self, force=False): + def add_custom_repository(self, + name: str, + url: str, + key_url: str, + codename: str, + with_source: bool = True): + key_file = f"/etc/apt/keyrings/{name}.gpg" + utils.exec_cmd( + f"wget -O - {key_url} | gpg --dearmor | sudo tee {key_file} > /dev/null" + ) + line_types = ["deb"] + if with_source: + line_types.append("deb-src") + for line_type in line_types: + line = ( + f"{line_type} [arch=amd64 signed-by={key_file}] " + f"{url} {codename} main" + ) + target_file = f"/etc/apt/source.list.d/{name}.list" + utils.exec_cmd(f'echo "{line} | sude tee {target_file}') + self.index_updated = False + + def update(self): """Update local cache.""" if self.index_updated and not force: return @@ -89,7 +111,7 @@ class RPMPackage(Package): def __init__(self, dist_name): """Initialize backend.""" - super(RPMPackage, self).__init__(dist_name) + super().__init__(dist_name) if "centos" in dist_name: self.install("epel-release") diff --git a/modoboa_installer/scripts/rspamd.py b/modoboa_installer/scripts/rspamd.py index 49af4a8..105f036 100644 --- a/modoboa_installer/scripts/rspamd.py +++ b/modoboa_installer/scripts/rspamd.py @@ -41,17 +41,21 @@ class Rspamd(base.Installer): if debian_based_dist: utils.mkdir_safe( "/etc/apt/keyrings", - stat.S_IRWXU | stat.S_IRUSR | stat.S_IXUSR, + stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | + stat.S_IROTH | stat.S_IXOTH, 0, 0 ) - if codename.lower() == "bionic": + if codename == "bionic": package.backend.install("software-properties-common") utils.exec_cmd("add-apt-repository ppa:ubuntu-toolchain-r/test") - utils.exec_cmd("wget -O- https://rspamd.com/apt-stable/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/rspamd.gpg > /dev/null") - utils.exec_cmd(f"echo \"deb [arch=amd64 signed-by=/etc/apt/keyrings/rspamd.gpg] http://rspamd.com/apt-stable/ {codename} main\" | sudo tee /etc/apt/sources.list.d/rspamd.list") - utils.exec_cmd(f"echo \"deb-src [arch=amd64 signed-by=/etc/apt/keyrings/rspamd.gpg] http://rspamd.com/apt-stable/ {codename} main\" | sudo tee -a /etc/apt/sources.list.d/rspamd.list") + package.backend.add_custom_repository( + "rspamd", + "http://rspamd.com/apt-stable/", + "https://rspamd.com/apt-stable/gpg.key", + codename + ) package.backend.update() return super().install_packages() From eb1a8ece555d753887305c281f87109431ebd348 Mon Sep 17 00:00:00 2001 From: Spitap Date: Mon, 25 Sep 2023 11:40:26 +0200 Subject: [PATCH 23/56] Updated config and interactive mode --- .gitignore | 5 ++- modoboa_installer/config_dict_template.py | 32 ++++++++++++++--- modoboa_installer/scripts/rspamd.py | 2 +- modoboa_installer/utils.py | 44 ++++++++++++++--------- 4 files changed, 61 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 2a204ee..3f88a5e 100644 --- a/.gitignore +++ b/.gitignore @@ -59,4 +59,7 @@ target/ # PyCharm .idea/ -installer.cfg \ No newline at end of file +#KDE +*.kdev4 + +installer.cfg diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index 48fc84b..c6a449c 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -27,6 +27,22 @@ ConfigDictTemplate = [ } ] }, + { + "name": "antispam", + "values": [ + { + "option": "enabled", + "default": "true", + "question": "Do you want to setup an antispam utility?" + }, + { + "option": "type", + "default": "rspamd", + "question": "Please select your antispam utility", + "values": ["rspamd", "amavis"] + } + ] + }, { "name": "certificate", "values": [ @@ -50,7 +66,7 @@ ConfigDictTemplate = [ }, { "name": "letsencrypt", - "if": "certificate.type=letsencrypt", + "if": ["certificate.type=letsencrypt"], "values": [ { "option": "email", @@ -85,7 +101,7 @@ ConfigDictTemplate = [ }, { "name": "postgres", - "if": "database.engine=postgres", + "if": ["database.engine=postgres"], "values": [ { "option": "user", @@ -101,7 +117,7 @@ ConfigDictTemplate = [ }, { "name": "mysql", - "if": "database.engine=mysql", + "if": ["database.engine=mysql"], "values": [ { "option": "user", @@ -271,9 +287,11 @@ ConfigDictTemplate = [ }, { "name": "amavis", + "if": ["antispam.enabled=true", "antispam.type=amavis"], "values": [ { "option": "enabled", + "default-if": "true", "default": "false", }, { @@ -362,7 +380,7 @@ ConfigDictTemplate = [ "values": [ { "option": "enabled", - "default": "true", + "default": "false", }, { "option": "config_dir", @@ -393,9 +411,11 @@ ConfigDictTemplate = [ }, { "name": "postwhite", + "if": ["antispam.enabled=true", "antispam.type=amavis"], "values": [ { "option": "enabled", + "default-if": "true", "default": "false", }, { @@ -406,9 +426,11 @@ ConfigDictTemplate = [ }, { "name": "spamassassin", + "if": ["antispam.enabled=true", "antispam.type=amavis"], "values": [ { "option": "enabled", + "default-if": "true", "default": "false", }, { @@ -475,9 +497,11 @@ ConfigDictTemplate = [ }, { "name": "opendkim", + "if": ["antispam.enabled=true", "antispam.type=amavis"], "values": [ { "option": "enabled", + "default-if": "true", "default": "false", }, { diff --git a/modoboa_installer/scripts/rspamd.py b/modoboa_installer/scripts/rspamd.py index 105f036..4fcd9c9 100644 --- a/modoboa_installer/scripts/rspamd.py +++ b/modoboa_installer/scripts/rspamd.py @@ -1,4 +1,4 @@ -"""Amavis related functions.""" +"""Rspamd related functions.""" import os import pwd diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index 61143e5..d8ca3ee 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -185,25 +185,29 @@ def copy_from_template(template, dest, context): fp.write(ConfigFileTemplate(buf).substitute(context)) -def check_config_file(dest, interactive=False, upgrade=False, backup=False, restore=False): +def check_config_file(dest, + interactive=False, + upgrade=False, + backup=False, + restore=False): """Create a new installer config file if needed.""" is_present = True if os.path.exists(dest): return is_present, update_config(dest, False) if upgrade: - printcolor( + error( "You cannot upgrade an existing installation without a " - "configuration file.", RED) + "configuration file.") sys.exit(1) elif backup: is_present = False - printcolor( + error( "Your configuration file hasn't been found. A new one will be generated. " - "Please edit it with correct password for the databases !", RED) + "Please edit it with correct password for the databases !") elif restore: - printcolor( + error( "You cannot restore an existing installation without a " - f"configuration file. (file : {dest} has not been found...", RED) + f"configuration file. (file : {dest} has not been found...") sys.exit(1) printcolor( @@ -310,10 +314,17 @@ def validate(value, config_entry): def get_entry_value(entry, interactive): - if callable(entry["default"]): - default_value = entry["default"]() + if entry.get("default-if") is not None and interactive: + # In case in interactive we try to look for a default-if + default_entry = entry["default-if"] else: - default_value = entry["default"] + default_entry = entry["default"] + + if callable(default_entry): + default_value = default_entry() + else: + default_value = default_entry + user_value = None if entry.get("customizable") and interactive: while (user_value != '' and not validate(user_value, entry)): @@ -349,13 +360,14 @@ def load_config_template(interactive): config = configparser.ConfigParser() # only ask about options we need, else still generate default for section in tpl_dict: + interactive_section = interactive if "if" in section: - config_key, value = section.get("if").split("=") - section_name, option = config_key.split(".") - interactive_section = ( - config.get(section_name, option) == value and interactive) - else: - interactive_section = interactive + for condition in section.get("if"): + config_key, value = condition.split("=") + section_name, option = config_key.split(".") + interactive_section = interactive_section and ( + config.get(section_name, option) == value) + config.add_section(section["name"]) for config_entry in section["values"]: value = get_entry_value(config_entry, interactive_section) From c0ca901353a7d052660adb412a91a22befc823fe Mon Sep 17 00:00:00 2001 From: Spitap Date: Mon, 25 Sep 2023 11:52:45 +0200 Subject: [PATCH 24/56] Fixed config --- modoboa_installer/config_dict_template.py | 10 ++++++++++ modoboa_installer/scripts/files/modoboa/crontab.tpl | 1 + 2 files changed, 11 insertions(+) diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index c6a449c..2147a8c 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -33,11 +33,14 @@ ConfigDictTemplate = [ { "option": "enabled", "default": "true", + "customizable": True, + "values": ["true", "false"], "question": "Do you want to setup an antispam utility?" }, { "option": "type", "default": "rspamd", + "customizable": True, "question": "Please select your antispam utility", "values": ["rspamd", "amavis"] } @@ -201,6 +204,13 @@ ConfigDictTemplate = [ "customizable": True, "question": "Please enter Modoboa db password", }, + { + "option": "cron_error_recipient", + "default": "root", + "customizable": True, + "question": + "Please enter a mail recipient for cron error reports" + }, { "option": "extensions", "default": "" diff --git a/modoboa_installer/scripts/files/modoboa/crontab.tpl b/modoboa_installer/scripts/files/modoboa/crontab.tpl index 84d9427..f5d36d2 100644 --- a/modoboa_installer/scripts/files/modoboa/crontab.tpl +++ b/modoboa_installer/scripts/files/modoboa/crontab.tpl @@ -3,6 +3,7 @@ # PYTHON=%{venv_path}/bin/python INSTANCE=%{instance_path} +MAILTO=%{cron_error_recipient} # Operations on mailboxes %{dovecot_enabled}* * * * * %{dovecot_mailboxes_owner} $PYTHON $INSTANCE/manage.py handle_mailbox_operations From b667636dcb868acd37fad15d06d05ed3955b242b Mon Sep 17 00:00:00 2001 From: Spitap Date: Mon, 25 Sep 2023 12:09:38 +0200 Subject: [PATCH 25/56] Added possibility of if directive in each entry --- modoboa_installer/config_dict_template.py | 3 ++- modoboa_installer/utils.py | 26 +++++++++++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index 2147a8c..7a2c38d 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -42,7 +42,8 @@ ConfigDictTemplate = [ "default": "rspamd", "customizable": True, "question": "Please select your antispam utility", - "values": ["rspamd", "amavis"] + "values": ["rspamd", "amavis"], + "if": ["antispam.enabled=true"] } ] }, diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index d8ca3ee..7ca9c62 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -293,6 +293,16 @@ def random_key(l=16): return key +def check_if_condition(config, entry): + """Check if the "if" directive is present and computes it""" + section_if = True + for condition in entry: + config_key, value = condition.split("=") + section_name, option = config_key.split(".") + section_if = config.get(section_name, option) == value + return section_if + + def validate(value, config_entry): if value is None: return False @@ -362,15 +372,19 @@ def load_config_template(interactive): for section in tpl_dict: interactive_section = interactive if "if" in section: - for condition in section.get("if"): - config_key, value = condition.split("=") - section_name, option = config_key.split(".") - interactive_section = interactive_section and ( - config.get(section_name, option) == value) + condition = check_if_condition(config, section["if"]) + interactive_section = condition and interactive config.add_section(section["name"]) for config_entry in section["values"]: - value = get_entry_value(config_entry, interactive_section) + if config_entry.get("if") is not None: + interactive_section = (interactive_section and + check_if_condition( + config, config_entry["if"] + ) + ) + value = get_entry_value(config_entry, + interactive_section) config.set(section["name"], config_entry["option"], value) return config From bd91c858882fd2817c5d5c3305ddbff4824627f3 Mon Sep 17 00:00:00 2001 From: Spitap Date: Sun, 15 Oct 2023 10:57:49 +0200 Subject: [PATCH 26/56] Fixed new source bug, removed bionic, added dynamic defaults --- modoboa_installer/config_dict_template.py | 19 ++++++++----------- modoboa_installer/package.py | 2 +- modoboa_installer/scripts/rspamd.py | 4 ---- modoboa_installer/utils.py | 19 ++++++++----------- 4 files changed, 17 insertions(+), 27 deletions(-) diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index 7a2c38d..c782226 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -253,10 +253,11 @@ ConfigDictTemplate = [ }, { "name": "rspamd", + "if": ["antispam.enabled=true", "antispam.type=amavis"], "values": [ { "option": "enabled", - "default": "true", + "default": ["antispam.enabled=true", "antispam.type=amavis"], }, { "option": "user", @@ -265,6 +266,8 @@ ConfigDictTemplate = [ { "option": "password", "default": make_password, + "customizable": True, + "question": "Please enter Rspamd interface password", }, { "option": "dnsbl", @@ -298,12 +301,10 @@ ConfigDictTemplate = [ }, { "name": "amavis", - "if": ["antispam.enabled=true", "antispam.type=amavis"], "values": [ { "option": "enabled", - "default-if": "true", - "default": "false", + "default": ["antispam.enabled=true", "antispam.type=amavis"], }, { "option": "user", @@ -422,12 +423,10 @@ ConfigDictTemplate = [ }, { "name": "postwhite", - "if": ["antispam.enabled=true", "antispam.type=amavis"], "values": [ { "option": "enabled", - "default-if": "true", - "default": "false", + "default": ["antispam.enabled=true", "antispam.type=amavis"], }, { "option": "config_dir", @@ -441,8 +440,7 @@ ConfigDictTemplate = [ "values": [ { "option": "enabled", - "default-if": "true", - "default": "false", + "default": ["antispam.enabled=true", "antispam.type=amavis"], }, { "option": "config_dir", @@ -512,8 +510,7 @@ ConfigDictTemplate = [ "values": [ { "option": "enabled", - "default-if": "true", - "default": "false", + "default": ["antispam.enabled=true", "antispam.type=amavis"], }, { "option": "user", diff --git a/modoboa_installer/package.py b/modoboa_installer/package.py index 195d034..b25392e 100644 --- a/modoboa_installer/package.py +++ b/modoboa_installer/package.py @@ -57,7 +57,7 @@ class DEBPackage(Package): with_source: bool = True): key_file = f"/etc/apt/keyrings/{name}.gpg" utils.exec_cmd( - f"wget -O - {key_url} | gpg --dearmor | sudo tee {key_file} > /dev/null" + f"wget -O - {key_url} | gpg --dearmor | tee {key_file} > /dev/null" ) line_types = ["deb"] if with_source: diff --git a/modoboa_installer/scripts/rspamd.py b/modoboa_installer/scripts/rspamd.py index 4fcd9c9..0ecbf48 100644 --- a/modoboa_installer/scripts/rspamd.py +++ b/modoboa_installer/scripts/rspamd.py @@ -46,10 +46,6 @@ class Rspamd(base.Installer): 0, 0 ) - if codename == "bionic": - package.backend.install("software-properties-common") - utils.exec_cmd("add-apt-repository ppa:ubuntu-toolchain-r/test") - package.backend.add_custom_repository( "rspamd", "http://rspamd.com/apt-stable/", diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index 7ca9c62..c900e55 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -323,18 +323,14 @@ def validate(value, config_entry): return True -def get_entry_value(entry, interactive): - if entry.get("default-if") is not None and interactive: - # In case in interactive we try to look for a default-if - default_entry = entry["default-if"] - else: - default_entry = entry["default"] - +def get_entry_value(entry, interactive, config): + default_entry = entry("default") + if type(default_entry) is type(list()): + default_value = check_if_condition(config, default_entry) if callable(default_entry): - default_value = default_entry() + default_value = entry["default"]() else: default_value = default_entry - user_value = None if entry.get("customizable") and interactive: while (user_value != '' and not validate(user_value, entry)): @@ -384,7 +380,8 @@ def load_config_template(interactive): ) ) value = get_entry_value(config_entry, - interactive_section) + interactive_section, + config) config.set(section["name"], config_entry["option"], value) return config @@ -550,7 +547,7 @@ def check_app_compatibility(section, config): if section in APP_INCOMPATIBILITY.keys(): for app in APP_INCOMPATIBILITY[section]: if config.getboolean(app, "enabled"): - error(f"{section} cannont be installed if {app} is enabled. " + error(f"{section} cannot be installed if {app} is enabled. " "Please disable one of them.") incompatible_app.append(app) return len(incompatible_app) == 0 From e4d68498ddc622ba370f7c38267c980c91ed13a4 Mon Sep 17 00:00:00 2001 From: Spitap Date: Sun, 15 Oct 2023 11:30:16 +0200 Subject: [PATCH 27/56] Fixed capped default choice, removed old py2 code --- modoboa_installer/utils.py | 20 +++++----------- run.py | 47 +++++--------------------------------- 2 files changed, 12 insertions(+), 55 deletions(-) diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index c900e55..7ab6cc5 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -1,5 +1,6 @@ """Utility functions.""" +import configparser import contextlib import datetime import getpass @@ -13,10 +14,6 @@ import string import subprocess import sys import uuid -try: - import configparser -except ImportError: - import ConfigParser as configparser from . import config_dict_template from .compatibility_matrix import APP_INCOMPATIBILITY @@ -35,12 +32,7 @@ class FatalError(Exception): def user_input(message): """Ask something to the user.""" - try: - from builtins import input - except ImportError: - answer = raw_input(message) - else: - answer = input(message) + answer = input(message) return answer @@ -323,8 +315,8 @@ def validate(value, config_entry): return True -def get_entry_value(entry, interactive, config): - default_entry = entry("default") +def get_entry_value(entry: dict, interactive: bool, config: configparser.ConfigParser) -> string: + default_entry = entry["default"] if type(default_entry) is type(list()): default_value = check_if_condition(config, default_entry) if callable(default_entry): @@ -481,7 +473,7 @@ def validate_backup_path(path: str, silent_mode: bool): if not path_exists: if not silent_mode: create_dir = input( - f"\"{path}\" doesn't exist, would you like to create it? [Y/n]\n" + f"\"{path}\" doesn't exist, would you like to create it? [y/N]\n" ).lower() if silent_mode or (not silent_mode and create_dir.startswith("y")): @@ -496,7 +488,7 @@ def validate_backup_path(path: str, silent_mode: bool): if len(os.listdir(path)) != 0: if not silent_mode: delete_dir = input( - "Warning: backup directory is not empty, it will be purged if you continue... [Y/n]\n").lower() + "Warning: backup directory is not empty, it will be purged if you continue... [y/N]\n").lower() if silent_mode or (not silent_mode and delete_dir.startswith("y")): try: diff --git a/run.py b/run.py index 6f7e689..2aa5d4b 100755 --- a/run.py +++ b/run.py @@ -11,7 +11,6 @@ except ImportError: import ConfigParser as configparser import sys -import checks from modoboa_installer import compatibility_matrix from modoboa_installer import constants from modoboa_installer import package @@ -37,12 +36,6 @@ PRIMARY_APPS = [ def installation_disclaimer(args, config): """Display installation disclaimer.""" hostname = config.get("general", "hostname") - utils.printcolor( - "Notice:\n" - "It is recommanded to run this installer on a FRESHLY installed server.\n" - "(ie. with nothing special already installed on it)\n", - utils.CYAN - ) utils.printcolor( "Warning:\n" "Before you start the installation, please make sure the following " @@ -53,7 +46,7 @@ def installation_disclaimer(args, config): hostname.replace(".{}".format(args.domain), ""), hostname ), - utils.YELLOW + utils.CYAN ) utils.printcolor( "Your mail server will be installed with the following components:", @@ -119,9 +112,6 @@ def backup_system(config, args): utils.copy_file(args.configfile, backup_path) # Backup applications for app in PRIMARY_APPS: - if app == "dovecot" and args.no_mail: - utils.printcolor("Skipping mail backup", utils.BLUE) - continue scripts.backup(app, config, backup_path) @@ -173,17 +163,11 @@ def main(input_args): help="For script usage, do not require user interaction " "backup will be saved at ./modoboa_backup/Backup_M_Y_d_H_M " "if --backup-path is not provided") - parser.add_argument( - "--no-mail", action="store_true", default=False, - help="Disable mail backup (save space)") parser.add_argument( "--restore", type=str, metavar="path", help="Restore a previously backup up modoboa instance on a NEW machine. " "You MUST provide backup directory" - ), - parser.add_argument( - "--skip-checks", action="store_true", default=False, - help="Skip the checks the installer performs initially") + ) parser.add_argument("domain", type=str, help="The main domain of your future mail server") args = parser.parse_args(input_args) @@ -204,12 +188,6 @@ def main(input_args): utils.success("Welcome to Modoboa installer!\n") - # Checks - if not args.skip_checks: - utils.printcolor("Checking the installer...", utils.BLUE) - checks.handle() - utils.success("Checks complete\n") - is_config_file_available, outdate_config = utils.check_config_file( args.configfile, args.interactive, args.upgrade, args.backup, is_restoring) @@ -221,12 +199,12 @@ def main(input_args): # Check if config is outdated and ask user if it needs to be updated if is_config_file_available and outdate_config: answer = utils.user_input("It seems that your config file is outdated. " - "Would you like to update it? (Y/n) ") - if not answer or answer.lower().startswith("y"): + "Would you like to update it? (y/N) ") + if answer.lower().startswith("y"): config_file_update_complete(utils.update_config(args.configfile)) if not args.stop_after_configfile_check: - answer = utils.user_input("Would you like to stop to review the updated config? (Y/n)") - if not answer or answer.lower().startswith("y"): + answer = utils.user_input("Would you like to stop to review the updated config? (y/N)") + if answer.lower().startswith("y"): return else: utils.error("You might encounter unexpected errors ! " @@ -300,19 +278,6 @@ def main(input_args): "Restore complete! You can enjoy Modoboa at https://{} (same credentials as before)" .format(config.get("general", "hostname")) ) - utils.success( - "\n" - "Modoboa is a free software maintained by volunteers.\n" - "You like the project and want it to be sustainable?\n" - "Then don't wait anymore and go sponsor it here:\n" - ) - utils.printcolor( - "https://github.com/sponsors/modoboa\n", - utils.YELLOW - ) - utils.success( - "Thank you for your help :-)\n" - ) if __name__ == "__main__": From a92c92c06c6a4b72908ed1d740f95fd7d985f015 Mon Sep 17 00:00:00 2001 From: Spitap Date: Sun, 15 Oct 2023 11:42:58 +0200 Subject: [PATCH 28/56] small fix --- modoboa_installer/config_dict_template.py | 2 +- modoboa_installer/utils.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index c782226..3ae2d72 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -253,7 +253,7 @@ ConfigDictTemplate = [ }, { "name": "rspamd", - "if": ["antispam.enabled=true", "antispam.type=amavis"], + "if": ["antispam.enabled=true", "antispam.type=rspamd"], "values": [ { "option": "enabled", diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index 7ab6cc5..372da1d 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -318,8 +318,8 @@ def validate(value, config_entry): def get_entry_value(entry: dict, interactive: bool, config: configparser.ConfigParser) -> string: default_entry = entry["default"] if type(default_entry) is type(list()): - default_value = check_if_condition(config, default_entry) - if callable(default_entry): + default_value = str(check_if_condition(config, default_entry)).lower() + elif callable(default_entry): default_value = entry["default"]() else: default_value = default_entry From 6771ea0028655069ecff4598c34bcb1a4b1f1e1e Mon Sep 17 00:00:00 2001 From: Spitap Date: Sun, 15 Oct 2023 11:49:57 +0200 Subject: [PATCH 29/56] small fix part 2 --- modoboa_installer/config_dict_template.py | 2 +- run.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index 3ae2d72..b4304be 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -257,7 +257,7 @@ ConfigDictTemplate = [ "values": [ { "option": "enabled", - "default": ["antispam.enabled=true", "antispam.type=amavis"], + "default": ["antispam.enabled=true", "antispam.type=rspamd"], }, { "option": "user", diff --git a/run.py b/run.py index 2aa5d4b..7ae4f7c 100755 --- a/run.py +++ b/run.py @@ -239,7 +239,7 @@ def main(input_args): # Show concerned components components = [] for section in config.sections(): - if section in ["general", "database", "mysql", "postgres", + if section in ["general", "antispam", "database", "mysql", "postgres", "certificate", "letsencrypt", "backup"]: continue if (config.has_option(section, "enabled") and From e7e5dce778f2f3809f042dfeabf1fd915cf4e20c Mon Sep 17 00:00:00 2001 From: Spitap Date: Sun, 15 Oct 2023 12:22:57 +0200 Subject: [PATCH 30/56] Bug fix --- modoboa_installer/package.py | 5 ++++- modoboa_installer/scripts/rspamd.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/modoboa_installer/package.py b/modoboa_installer/package.py index b25392e..483e2e9 100644 --- a/modoboa_installer/package.py +++ b/modoboa_installer/package.py @@ -2,6 +2,8 @@ import re +from os.path import isfile as file_exists + from . import utils @@ -68,7 +70,8 @@ class DEBPackage(Package): f"{url} {codename} main" ) target_file = f"/etc/apt/source.list.d/{name}.list" - utils.exec_cmd(f'echo "{line} | sude tee {target_file}') + tee_option = "-a" if file_exists(target_file) else "" + utils.exec_cmd(f'echo "{line}" | sude tee {tee_option} {target_file}') self.index_updated = False def update(self): diff --git a/modoboa_installer/scripts/rspamd.py b/modoboa_installer/scripts/rspamd.py index 0ecbf48..6c03103 100644 --- a/modoboa_installer/scripts/rspamd.py +++ b/modoboa_installer/scripts/rspamd.py @@ -95,6 +95,7 @@ class Rspamd(base.Installer): "https://rspamd.com/doc/quickstart.html#setting-the-controller-password") _context["controller_password"] = self.app_config["password"] else: + controller_password = controller_password.decode().replace("\n", "") _context["controller_password"] = controller_password _context["greylisting_disabled"] = "" if not self.app_config["greylisting"].lower() == "true" else "#" _context["whitelist_auth_enabled"] = "" if self.app_config["whitelist_auth"].lower() == "true" else "#" From 183bfd274292bc7c74bd73e6fedf425f95f5a2f0 Mon Sep 17 00:00:00 2001 From: Spitap Date: Sun, 15 Oct 2023 12:31:47 +0200 Subject: [PATCH 31/56] small fix part 3 --- modoboa_installer/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa_installer/package.py b/modoboa_installer/package.py index 483e2e9..c10dacf 100644 --- a/modoboa_installer/package.py +++ b/modoboa_installer/package.py @@ -71,7 +71,7 @@ class DEBPackage(Package): ) target_file = f"/etc/apt/source.list.d/{name}.list" tee_option = "-a" if file_exists(target_file) else "" - utils.exec_cmd(f'echo "{line}" | sude tee {tee_option} {target_file}') + utils.exec_cmd(f'echo "{line}" | tee {tee_option} {target_file}') self.index_updated = False def update(self): From 5156ad0468924876c973752430b7c583feb3465c Mon Sep 17 00:00:00 2001 From: Spitap Date: Sun, 15 Oct 2023 12:40:39 +0200 Subject: [PATCH 32/56] small fix part 4 --- modoboa_installer/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa_installer/package.py b/modoboa_installer/package.py index c10dacf..a2719d5 100644 --- a/modoboa_installer/package.py +++ b/modoboa_installer/package.py @@ -69,7 +69,7 @@ class DEBPackage(Package): f"{line_type} [arch=amd64 signed-by={key_file}] " f"{url} {codename} main" ) - target_file = f"/etc/apt/source.list.d/{name}.list" + target_file = f"/etc/apt/sources.list.d/{name}.list" tee_option = "-a" if file_exists(target_file) else "" utils.exec_cmd(f'echo "{line}" | tee {tee_option} {target_file}') self.index_updated = False From 38eae741bf720946fc76e96a1669f53a024a0d67 Mon Sep 17 00:00:00 2001 From: Spitfireap Date: Fri, 12 Jan 2024 18:08:28 +0100 Subject: [PATCH 33/56] added sieve rule to move spam to junk folder --- modoboa_installer/config_dict_template.py | 8 ++++++++ modoboa_installer/scripts/dovecot.py | 12 +++++++++++- .../scripts/files/dovecot/conf.d/90-sieve.conf | 2 +- .../conf.d/custom_after_sieve/spam-to-junk.sieve.tpl | 4 ++++ 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 modoboa_installer/scripts/files/dovecot/conf.d/custom_after_sieve/spam-to-junk.sieve.tpl diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index b4304be..d03dde1 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -372,6 +372,14 @@ ConfigDictTemplate = [ "option": "postmaster_address", "default": "postmaster@%(domain)s", }, + { + "option": "radicale_auth_socket_path", + "default": "/var/run/dovecot/auth-radicale", + }, + { + "option": "move_spam_to_junk", + "default": "true", + }, ] }, { diff --git a/modoboa_installer/scripts/dovecot.py b/modoboa_installer/scripts/dovecot.py index 6c38e85..eb3d4a3 100644 --- a/modoboa_installer/scripts/dovecot.py +++ b/modoboa_installer/scripts/dovecot.py @@ -40,7 +40,13 @@ class Dovecot(base.Installer): def get_config_files(self): """Additional config files.""" - return self.config_files + [ + _config_files = self.config_files + + if self.app_config["move_spam_to_junk"]: + _config_files += ["conf.d/90-sieve.conf", + "conf.d/custom_after_sieve/spam-to-junk.sieve"] + + return _config_files + [ "dovecot-sql-{}.conf.ext=dovecot-sql.conf.ext" .format(self.dbengine), "dovecot-sql-master-{}.conf.ext=dovecot-sql-master.conf.ext" @@ -145,6 +151,10 @@ class Dovecot(base.Installer): utils.exec_cmd("chmod 600 /etc/dovecot/conf.d/10-ssl-keys.try") # Add mailboxes user to dovecot group for modoboa mailbox commands. # See https://github.com/modoboa/modoboa/issues/2157. + if self.app_config["move_spam_to_junk"]: + # Compile sieve script + sieve_file = "/etc/dovecot/conf.d/custom_after_sieve/spam-to-junk.sieve" + utils.exec_cmd(f"/usr/bin/sievec {sieve_file}") system.add_user_to_group(self.mailboxes_owner, 'dovecot') def restart_daemon(self): diff --git a/modoboa_installer/scripts/files/dovecot/conf.d/90-sieve.conf b/modoboa_installer/scripts/files/dovecot/conf.d/90-sieve.conf index 35a9f5d..dd76330 100644 --- a/modoboa_installer/scripts/files/dovecot/conf.d/90-sieve.conf +++ b/modoboa_installer/scripts/files/dovecot/conf.d/90-sieve.conf @@ -38,7 +38,7 @@ plugin { # Identical to sieve_before, only the specified scripts are executed after the # user's script (only when keep is still in effect!). Multiple script file or # directory paths can be specified by appending an increasing number. - #sieve_after = + {%dovecot_enabled}sieve_after = /etc/dovecot/conf.d/custom_after_sieve #sieve_after2 = #sieve_after2 = (etc...) diff --git a/modoboa_installer/scripts/files/dovecot/conf.d/custom_after_sieve/spam-to-junk.sieve.tpl b/modoboa_installer/scripts/files/dovecot/conf.d/custom_after_sieve/spam-to-junk.sieve.tpl new file mode 100644 index 0000000..8eb572c --- /dev/null +++ b/modoboa_installer/scripts/files/dovecot/conf.d/custom_after_sieve/spam-to-junk.sieve.tpl @@ -0,0 +1,4 @@ +require "fileinto"; +if header :contains "X-Spam-Status" "Yes" { + fileinto "Junk"; +} From e73d318e143c61db727b7928e8dbff461b46bcdf Mon Sep 17 00:00:00 2001 From: Spitfireap Date: Fri, 12 Jan 2024 18:26:35 +0100 Subject: [PATCH 34/56] Made 90-sieve a template --- modoboa_installer/scripts/dovecot.py | 17 +++++++---------- .../conf.d/{90-sieve.conf => 90-sieve.conf.tpl} | 0 2 files changed, 7 insertions(+), 10 deletions(-) rename modoboa_installer/scripts/files/dovecot/conf.d/{90-sieve.conf => 90-sieve.conf.tpl} (100%) diff --git a/modoboa_installer/scripts/dovecot.py b/modoboa_installer/scripts/dovecot.py index eb3d4a3..02fece5 100644 --- a/modoboa_installer/scripts/dovecot.py +++ b/modoboa_installer/scripts/dovecot.py @@ -4,7 +4,6 @@ import glob import os import pwd import shutil -import uuid from .. import database from .. import package @@ -15,6 +14,7 @@ from . import base class Dovecot(base.Installer): + """Dovecot installer.""" appname = "dovecot" @@ -27,9 +27,8 @@ class Dovecot(base.Installer): } config_files = [ "dovecot.conf", "dovecot-dict-sql.conf.ext", "conf.d/10-ssl.conf", - "conf.d/10-master.conf", "conf.d/20-lmtp.conf", "conf.d/10-ssl-keys.try", - "conf.d/dovecot-oauth2.conf.ext" - ] + "conf.d/10-master.conf", "conf.d/20-lmtp.conf", + "conf.d/10-ssl-keys.try", "conf.d/90-sieve.conf"] with_user = True def setup_user(self): @@ -43,8 +42,7 @@ class Dovecot(base.Installer): _config_files = self.config_files if self.app_config["move_spam_to_junk"]: - _config_files += ["conf.d/90-sieve.conf", - "conf.d/custom_after_sieve/spam-to-junk.sieve"] + _config_files += ["conf.d/custom_after_sieve/spam-to-junk.sieve"] return _config_files + [ "dovecot-sql-{}.conf.ext=dovecot-sql.conf.ext" @@ -80,7 +78,7 @@ class Dovecot(base.Installer): def get_template_context(self): """Additional variables.""" - context = super().get_template_context() + context = super(Dovecot, self).get_template_context() pw_mailbox = pwd.getpwnam(self.mailboxes_owner) dovecot_package = {"deb": "dovecot-core", "rpm": "dovecot"} ssl_protocol_parameter = "ssl_protocols" @@ -122,8 +120,7 @@ class Dovecot(base.Installer): "ssl_protocols": ssl_protocols, "ssl_protocol_parameter": ssl_protocol_parameter, "modoboa_2_2_or_greater": "" if self.modoboa_2_2_or_greater else "#", - "not_modoboa_2_2_or_greater": "" if not self.modoboa_2_2_or_greater else "#", - "oauth2_introspection_url": oauth2_introspection_url + "not_modoboa_2_2_or_greater": "" if not self.modoboa_2_2_or_greater else "#" }) return context @@ -153,7 +150,7 @@ class Dovecot(base.Installer): # See https://github.com/modoboa/modoboa/issues/2157. if self.app_config["move_spam_to_junk"]: # Compile sieve script - sieve_file = "/etc/dovecot/conf.d/custom_after_sieve/spam-to-junk.sieve" + sieve_file = f"{self.config_dir}/conf.d/custom_after_sieve/spam-to-junk.sieve" utils.exec_cmd(f"/usr/bin/sievec {sieve_file}") system.add_user_to_group(self.mailboxes_owner, 'dovecot') diff --git a/modoboa_installer/scripts/files/dovecot/conf.d/90-sieve.conf b/modoboa_installer/scripts/files/dovecot/conf.d/90-sieve.conf.tpl similarity index 100% rename from modoboa_installer/scripts/files/dovecot/conf.d/90-sieve.conf rename to modoboa_installer/scripts/files/dovecot/conf.d/90-sieve.conf.tpl From 86481417cfaf6e0b915522dfcaeaa9f1b22d0651 Mon Sep 17 00:00:00 2001 From: Spitfireap Date: Fri, 12 Jan 2024 18:38:18 +0100 Subject: [PATCH 35/56] Made junk sieve optional --- modoboa_installer/scripts/dovecot.py | 5 +++-- .../scripts/files/dovecot/conf.d/90-sieve.conf.tpl | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modoboa_installer/scripts/dovecot.py b/modoboa_installer/scripts/dovecot.py index 02fece5..ca2f9cb 100644 --- a/modoboa_installer/scripts/dovecot.py +++ b/modoboa_installer/scripts/dovecot.py @@ -78,7 +78,7 @@ class Dovecot(base.Installer): def get_template_context(self): """Additional variables.""" - context = super(Dovecot, self).get_template_context() + context = super().get_template_context() pw_mailbox = pwd.getpwnam(self.mailboxes_owner) dovecot_package = {"deb": "dovecot-core", "rpm": "dovecot"} ssl_protocol_parameter = "ssl_protocols" @@ -120,7 +120,8 @@ class Dovecot(base.Installer): "ssl_protocols": ssl_protocols, "ssl_protocol_parameter": ssl_protocol_parameter, "modoboa_2_2_or_greater": "" if self.modoboa_2_2_or_greater else "#", - "not_modoboa_2_2_or_greater": "" if not self.modoboa_2_2_or_greater else "#" + "not_modoboa_2_2_or_greater": "" if not self.modoboa_2_2_or_greater else "#", + "do_move_spam_to_junk": "" if self.app_config["move_spam_to_junk"] else "#" }) return context diff --git a/modoboa_installer/scripts/files/dovecot/conf.d/90-sieve.conf.tpl b/modoboa_installer/scripts/files/dovecot/conf.d/90-sieve.conf.tpl index dd76330..480d2c2 100644 --- a/modoboa_installer/scripts/files/dovecot/conf.d/90-sieve.conf.tpl +++ b/modoboa_installer/scripts/files/dovecot/conf.d/90-sieve.conf.tpl @@ -38,7 +38,7 @@ plugin { # Identical to sieve_before, only the specified scripts are executed after the # user's script (only when keep is still in effect!). Multiple script file or # directory paths can be specified by appending an increasing number. - {%dovecot_enabled}sieve_after = /etc/dovecot/conf.d/custom_after_sieve + %{do_move_spam_to_junk}sieve_after = /etc/dovecot/conf.d/custom_after_sieve #sieve_after2 = #sieve_after2 = (etc...) From a8b2f9f015e06b5e977b6031c98857b99f6f4119 Mon Sep 17 00:00:00 2001 From: Spitfireap Date: Fri, 12 Jan 2024 18:52:40 +0100 Subject: [PATCH 36/56] create sieve dir if needed --- modoboa_installer/scripts/dovecot.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/modoboa_installer/scripts/dovecot.py b/modoboa_installer/scripts/dovecot.py index ca2f9cb..58c58de 100644 --- a/modoboa_installer/scripts/dovecot.py +++ b/modoboa_installer/scripts/dovecot.py @@ -4,6 +4,7 @@ import glob import os import pwd import shutil +import stat from .. import database from .. import package @@ -125,6 +126,17 @@ class Dovecot(base.Installer): }) return context + def install_config_files(self): + """Create sieve dir if needed.""" + if self.app_config["move_spam_to_junk"]: + utils.mkdir_safe( + f"{self.config_dir}/conf.d/custom_after_sieve", + stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | + stat.S_IROTH | stat.S_IXOTH, + 0, 0 + ) + super().install_config_files() + def post_run(self): """Additional tasks.""" if self.dbengine == "postgres": From b7106bb15a914dacc6dca824e966340e1dc560a3 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Fri, 23 Feb 2024 11:15:41 +0100 Subject: [PATCH 37/56] Fixed file copy issue --- modoboa_installer/scripts/dovecot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modoboa_installer/scripts/dovecot.py b/modoboa_installer/scripts/dovecot.py index 58c58de..965f933 100644 --- a/modoboa_installer/scripts/dovecot.py +++ b/modoboa_installer/scripts/dovecot.py @@ -153,7 +153,8 @@ class Dovecot(base.Installer): self.get_file_path("fix_modoboa_postgres_schema.sql") ) for f in glob.glob("{}/*".format(self.get_file_path("conf.d"))): - utils.copy_file(f, "{}/conf.d".format(self.config_dir)) + if os.path.isfile(f): + utils.copy_file(f, "{}/conf.d".format(self.config_dir)) # Make postlogin script executable utils.exec_cmd("chmod +x /usr/local/bin/postlogin.sh") # Only root should have read access to the 10-ssl-keys.try From 9a582fb1d09597ed127e805ad6fe3a8f8d9e524c Mon Sep 17 00:00:00 2001 From: Spitfireap Date: Thu, 3 Oct 2024 11:36:55 +0200 Subject: [PATCH 38/56] Update after rebase --- modoboa_installer/disclaimers.py | 51 +++++++++++++++++++ modoboa_installer/scripts/dovecot.py | 8 +-- run.py | 74 +++++++++------------------- 3 files changed, 78 insertions(+), 55 deletions(-) create mode 100644 modoboa_installer/disclaimers.py diff --git a/modoboa_installer/disclaimers.py b/modoboa_installer/disclaimers.py new file mode 100644 index 0000000..9faff69 --- /dev/null +++ b/modoboa_installer/disclaimers.py @@ -0,0 +1,51 @@ +from . import utils + + +def installation_disclaimer(args, config): + """Display installation disclaimer.""" + hostname = config.get("general", "hostname") + utils.printcolor( + "Notice:\n" + "It is recommanded to run this installer on a FRESHLY installed server.\n" + "(ie. with nothing special already installed on it)\n", + utils.CYAN + ) + utils.printcolor( + "Warning:\n" + "Before you start the installation, please make sure the following " + "DNS records exist for domain '{}':\n" + " {} IN A \n" + " @ IN MX {}.\n".format( + args.domain, + hostname.replace(".{}".format(args.domain), ""), + hostname + ), + utils.YELLOW + ) + utils.printcolor( + "Your mail server will be installed with the following components:", + utils.BLUE) + + +def upgrade_disclaimer(config): + """Display upgrade disclaimer.""" + utils.printcolor( + "Your mail server is about to be upgraded and the following components" + " will be impacted:", utils.BLUE + ) + + +def backup_disclaimer(): + """Display backup disclamer. """ + utils.printcolor( + "Your mail server will be backed up locally.\n" + " !! You should really transfer the backup somewhere else...\n" + " !! Custom configuration (like for postfix) won't be saved.", utils.BLUE) + + +def restore_disclaimer(): + """Display restore disclamer. """ + utils.printcolor( + "You are about to restore a previous installation of Modoboa.\n" + "If a new version has been released in between, please update your database!", + utils.BLUE) diff --git a/modoboa_installer/scripts/dovecot.py b/modoboa_installer/scripts/dovecot.py index 965f933..d1de329 100644 --- a/modoboa_installer/scripts/dovecot.py +++ b/modoboa_installer/scripts/dovecot.py @@ -28,8 +28,9 @@ class Dovecot(base.Installer): } config_files = [ "dovecot.conf", "dovecot-dict-sql.conf.ext", "conf.d/10-ssl.conf", - "conf.d/10-master.conf", "conf.d/20-lmtp.conf", - "conf.d/10-ssl-keys.try", "conf.d/90-sieve.conf"] + "conf.d/10-master.conf", "conf.d/20-lmtp.conf", "conf.d/10-ssl-keys.try", + "conf.d/dovecot-oauth2.conf.ext" + ] with_user = True def setup_user(self): @@ -122,7 +123,8 @@ class Dovecot(base.Installer): "ssl_protocol_parameter": ssl_protocol_parameter, "modoboa_2_2_or_greater": "" if self.modoboa_2_2_or_greater else "#", "not_modoboa_2_2_or_greater": "" if not self.modoboa_2_2_or_greater else "#", - "do_move_spam_to_junk": "" if self.app_config["move_spam_to_junk"] else "#" + "do_move_spam_to_junk": "" if self.app_config["move_spam_to_junk"] else "#", + "oauth2_introspection_url": oauth2_introspection_url }) return context diff --git a/run.py b/run.py index 7ae4f7c..b12bfb4 100755 --- a/run.py +++ b/run.py @@ -18,6 +18,7 @@ from modoboa_installer import scripts from modoboa_installer import ssl from modoboa_installer import system from modoboa_installer import utils +from modoboa_installer import disclaimers PRIMARY_APPS = [ @@ -33,53 +34,9 @@ PRIMARY_APPS = [ ] -def installation_disclaimer(args, config): - """Display installation disclaimer.""" - hostname = config.get("general", "hostname") - utils.printcolor( - "Warning:\n" - "Before you start the installation, please make sure the following " - "DNS records exist for domain '{}':\n" - " {} IN A \n" - " @ IN MX {}.\n".format( - args.domain, - hostname.replace(".{}".format(args.domain), ""), - hostname - ), - utils.CYAN - ) - utils.printcolor( - "Your mail server will be installed with the following components:", - utils.BLUE) - - -def upgrade_disclaimer(config): - """Display upgrade disclaimer.""" - utils.printcolor( - "Your mail server is about to be upgraded and the following components" - " will be impacted:", utils.BLUE - ) - - -def backup_disclaimer(): - """Display backup disclamer. """ - utils.printcolor( - "Your mail server will be backed up locally.\n" - " !! You should really transfer the backup somewhere else...\n" - " !! Custom configuration (like for postfix) won't be saved.", utils.BLUE) - - -def restore_disclaimer(): - """Display restore disclamer. """ - utils.printcolor( - "You are about to restore a previous installation of Modoboa.\n" - "If a new version has been released in between, please update your database!", - utils.BLUE) - - def backup_system(config, args): """Launch backup procedure.""" - backup_disclaimer() + disclaimers.backup_disclaimer() backup_path = None if args.silent_backup: if not args.backup_path: @@ -199,12 +156,12 @@ def main(input_args): # Check if config is outdated and ask user if it needs to be updated if is_config_file_available and outdate_config: answer = utils.user_input("It seems that your config file is outdated. " - "Would you like to update it? (y/N) ") - if answer.lower().startswith("y"): + "Would you like to update it? (Y/n) ") + if not answer or answer.lower().startswith("y"): config_file_update_complete(utils.update_config(args.configfile)) if not args.stop_after_configfile_check: - answer = utils.user_input("Would you like to stop to review the updated config? (y/N)") - if answer.lower().startswith("y"): + answer = utils.user_input("Would you like to stop to review the updated config? (Y/n)") + if not answer or answer.lower().startswith("y"): return else: utils.error("You might encounter unexpected errors ! " @@ -229,12 +186,12 @@ def main(input_args): # Display disclaimer python 3 linux distribution if args.upgrade: - upgrade_disclaimer(config) + disclaimers.upgrade_disclaimer(config) elif args.restore: - restore_disclaimer() + disclaimers.restore_disclaimer() scripts.restore_prep(args.restore) else: - installation_disclaimer(args, config) + disclaimers.installation_disclaimer(args, config) # Show concerned components components = [] @@ -278,6 +235,19 @@ def main(input_args): "Restore complete! You can enjoy Modoboa at https://{} (same credentials as before)" .format(config.get("general", "hostname")) ) + utils.success( + "\n" + "Modoboa is a free software maintained by volunteers.\n" + "You like the project and want it to be sustainable?\n" + "Then don't wait anymore and go sponsor it here:\n" + ) + utils.printcolor( + "https://github.com/sponsors/modoboa\n", + utils.YELLOW + ) + utils.success( + "Thank you for your help :-)\n" + ) if __name__ == "__main__": From f0a84c81b9c5f0a545fa9ea841e9fb555f952497 Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 31 Oct 2024 11:57:02 +0100 Subject: [PATCH 39/56] imported checks --- run.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/run.py b/run.py index b12bfb4..a647ab4 100755 --- a/run.py +++ b/run.py @@ -11,6 +11,7 @@ except ImportError: import ConfigParser as configparser import sys +from . import checks from modoboa_installer import compatibility_matrix from modoboa_installer import constants from modoboa_installer import package @@ -69,6 +70,9 @@ def backup_system(config, args): utils.copy_file(args.configfile, backup_path) # Backup applications for app in PRIMARY_APPS: + if app == "dovecot" and args.no_mail: + utils.printcolor("Skipping mail backup", utils.BLUE) + continue scripts.backup(app, config, backup_path) @@ -145,6 +149,12 @@ def main(input_args): utils.success("Welcome to Modoboa installer!\n") + # Checks + if not args.skip_checks: + utils.printcolor("Checking the installer...", utils.BLUE) + checks.handle() + utils.success("Checks complete\n") + is_config_file_available, outdate_config = utils.check_config_file( args.configfile, args.interactive, args.upgrade, args.backup, is_restoring) From 84e82199efec64a7c2a5349dbfa177492e47f5bc Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 31 Oct 2024 11:58:08 +0100 Subject: [PATCH 40/56] fix import --- checks.py => modoboa_installer/checks.py | 0 run.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename checks.py => modoboa_installer/checks.py (100%) diff --git a/checks.py b/modoboa_installer/checks.py similarity index 100% rename from checks.py rename to modoboa_installer/checks.py diff --git a/run.py b/run.py index a647ab4..90e243a 100755 --- a/run.py +++ b/run.py @@ -11,7 +11,7 @@ except ImportError: import ConfigParser as configparser import sys -from . import checks +from modoboa_installer import checks from modoboa_installer import compatibility_matrix from modoboa_installer import constants from modoboa_installer import package From 06d65f79218628aa962f73529fd2876a11a07a2f Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 31 Oct 2024 12:01:18 +0100 Subject: [PATCH 41/56] imported arguments --- run.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/run.py b/run.py index 90e243a..78cd78b 100755 --- a/run.py +++ b/run.py @@ -124,11 +124,17 @@ def main(input_args): help="For script usage, do not require user interaction " "backup will be saved at ./modoboa_backup/Backup_M_Y_d_H_M " "if --backup-path is not provided") + parser.add_argument( + "--no-mail", action="store_true", default=False, + help="Disable mail backup (save space)") parser.add_argument( "--restore", type=str, metavar="path", help="Restore a previously backup up modoboa instance on a NEW machine. " "You MUST provide backup directory" ) + parser.add_argument( + "--skip-checks", action="store_true", default=False, + help="Skip the checks the installer performs initially") parser.add_argument("domain", type=str, help="The main domain of your future mail server") args = parser.parse_args(input_args) From 5c7f230647fe759ffb2b59cbda6141793539d76e Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 31 Oct 2024 12:22:41 +0100 Subject: [PATCH 42/56] Fixed dovecot --- modoboa_installer/checks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa_installer/checks.py b/modoboa_installer/checks.py index a8022a7..5f2cb0f 100644 --- a/modoboa_installer/checks.py +++ b/modoboa_installer/checks.py @@ -26,7 +26,7 @@ def check_version(): "Check README file for instructions about how to update.\n" "No support will be provided without an up-to-date installer!" ) - answer = utils.user_input("Continue anyway? (Y/n) ") + answer = utils.user_input("Continue anyway? (y/N) ") if not answer.lower().startswith("y"): sys.exit(0) else: From 9b7489ea58011515ccc6f63f8041ef8ef2536f8c Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 31 Oct 2024 12:26:17 +0100 Subject: [PATCH 43/56] Fixed dovecot #2 --- modoboa_installer/scripts/dovecot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modoboa_installer/scripts/dovecot.py b/modoboa_installer/scripts/dovecot.py index d1de329..98381e6 100644 --- a/modoboa_installer/scripts/dovecot.py +++ b/modoboa_installer/scripts/dovecot.py @@ -5,6 +5,7 @@ import os import pwd import shutil import stat +import uuid from .. import database from .. import package From f980d4e86f53613cc2e4955134d18d311ba09ebe Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 31 Oct 2024 12:36:02 +0100 Subject: [PATCH 44/56] Added rspamd dashboard info --- run.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/run.py b/run.py index 78cd78b..e4e8f9a 100755 --- a/run.py +++ b/run.py @@ -246,6 +246,10 @@ def main(input_args): "Congratulations! You can enjoy Modoboa at https://{} (admin:password)" .format(config.get("general", "hostname")) ) + if config.get("rspamd", "enabled"): + utils.success( + f"You can also enjoy rspamd at https://{config.get("general", "hostname")} ({config.get("rspamd", "password")})" + ) else: utils.success( "Restore complete! You can enjoy Modoboa at https://{} (same credentials as before)" From cd280f054b7751521f4a3fb30da42ed79a1bed6b Mon Sep 17 00:00:00 2001 From: Spitap Date: Thu, 31 Oct 2024 12:48:50 +0100 Subject: [PATCH 45/56] Added Arc signing --- modoboa_installer/scripts/files/rspamd/local.d/arc.conf | 3 +++ modoboa_installer/scripts/rspamd.py | 1 + run.py | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/arc.conf diff --git a/modoboa_installer/scripts/files/rspamd/local.d/arc.conf b/modoboa_installer/scripts/files/rspamd/local.d/arc.conf new file mode 100644 index 0000000..3dcf992 --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/arc.conf @@ -0,0 +1,3 @@ +try_fallback = false; +selector_map = "%selector_map_path"; +path_map = "%key_map_path"; diff --git a/modoboa_installer/scripts/rspamd.py b/modoboa_installer/scripts/rspamd.py index 6c03103..3890f28 100644 --- a/modoboa_installer/scripts/rspamd.py +++ b/modoboa_installer/scripts/rspamd.py @@ -22,6 +22,7 @@ class Rspamd(base.Installer): ] } config_files = ["local.d/dkim_signing.conf", + "local.d/arc.conf", "local.d/mx_check.conf", "local.d/spf.conf", "local.d/worker-controller.inc", diff --git a/run.py b/run.py index e4e8f9a..4dab5a3 100755 --- a/run.py +++ b/run.py @@ -248,7 +248,7 @@ def main(input_args): ) if config.get("rspamd", "enabled"): utils.success( - f"You can also enjoy rspamd at https://{config.get("general", "hostname")} ({config.get("rspamd", "password")})" + f"You can also enjoy rspamd at https://{config.get("general", "hostname")}/rspamd ({config.get("rspamd", "password")})" ) else: utils.success( From fd50d62f974ceb732896702efaad0c74dbd82610 Mon Sep 17 00:00:00 2001 From: Spitfireap Date: Wed, 18 Dec 2024 17:41:03 +0100 Subject: [PATCH 46/56] Updated ARC --- .../scripts/files/rspamd/local.d/arc.conf.tpl | 3 +++ .../rspamd/local.d/milter_headers.conf.tpl | 19 +------------------ 2 files changed, 4 insertions(+), 18 deletions(-) create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/arc.conf.tpl diff --git a/modoboa_installer/scripts/files/rspamd/local.d/arc.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/arc.conf.tpl new file mode 100644 index 0000000..3dcf992 --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/arc.conf.tpl @@ -0,0 +1,3 @@ +try_fallback = false; +selector_map = "%selector_map_path"; +path_map = "%key_map_path"; diff --git a/modoboa_installer/scripts/files/rspamd/local.d/milter_headers.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/milter_headers.conf.tpl index f3c489a..e0b3743 100644 --- a/modoboa_installer/scripts/files/rspamd/local.d/milter_headers.conf.tpl +++ b/modoboa_installer/scripts/files/rspamd/local.d/milter_headers.conf.tpl @@ -1,25 +1,8 @@ -use = ["x-spam-status", "my-x-spam-score" ,"x-virus","authentication-results" ]; +use = ["x-spam-status","x-virus","authentication-results" ]; extended_spam_headers = false; skip_local = false; skip_authenticated = false; -# Write the score as a header -custom { - my-x-spam-score = < Date: Fri, 10 Jan 2025 11:24:13 +0100 Subject: [PATCH 47/56] Added missing method parameter --- modoboa_installer/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modoboa_installer/package.py b/modoboa_installer/package.py index a2719d5..8301464 100644 --- a/modoboa_installer/package.py +++ b/modoboa_installer/package.py @@ -74,7 +74,7 @@ class DEBPackage(Package): utils.exec_cmd(f'echo "{line}" | tee {tee_option} {target_file}') self.index_updated = False - def update(self): + def update(self, force=False): """Update local cache.""" if self.index_updated and not force: return From b9539fa33cfda0e1dc663915ae403a218d9183c4 Mon Sep 17 00:00:00 2001 From: Spitfireap Date: Mon, 13 Jan 2025 15:14:55 +0100 Subject: [PATCH 48/56] Updated rspamd config --- README.rst | 22 ++++++++++-- modoboa_installer/config_dict_template.py | 2 +- .../files/rspamd/local.d/antivirus.conf.tpl | 1 - .../scripts/files/rspamd/local.d/arc.conf | 3 -- .../files/rspamd/local.d/dmarc.conf.tpl | 21 +++++++++++ .../rspamd/local.d/force_actions.conf.tpl | 5 +++ modoboa_installer/scripts/rspamd.py | 33 ++++++++++------- run.py | 35 +++++++++++++------ 8 files changed, 91 insertions(+), 31 deletions(-) delete mode 100644 modoboa_installer/scripts/files/rspamd/local.d/arc.conf create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/dmarc.conf.tpl create mode 100644 modoboa_installer/scripts/files/rspamd/local.d/force_actions.conf.tpl diff --git a/README.rst b/README.rst index 3b1759d..7bdeb2c 100644 --- a/README.rst +++ b/README.rst @@ -9,8 +9,8 @@ An installer which deploy a complete mail server based on Modoboa. This tool is still in beta stage, it has been tested on: - * Debian 10 and upper - * Ubuntu Bionic Beaver (18.04) and upper + * Debian 12 and upper + * Ubuntu Focal Fossa (20.04) and upper .. warning:: @@ -43,7 +43,7 @@ The following components are installed by the installer: * Nginx and uWSGI * Postfix * Dovecot -* Amavis (with SpamAssassin and ClamAV) +* Amavis (with SpamAssassin and ClamAV) or Rspamd * automx (autoconfiguration service) * OpenDKIM * Radicale (CalDAV and CardDAV server) @@ -229,6 +229,22 @@ If you want to use already generated certs, simply edit the tls_cert_file_path = *path to tls fullchain file* tls_key_file_path = *path to tls key file* +Antispam +======== + +You have 3 options regarding antispam : disabled, Amavis, Rspamd + +Amavis +------ + +Amavis + +Rspamd +------ + +Rspamd + + .. |workflow| image:: https://github.com/modoboa/modoboa-installer/workflows/Modoboa%20installer/badge.svg .. |codecov| image:: https://codecov.io/gh/modoboa/modoboa-installer/graph/badge.svg?token=Fo2o1GdHZq :target: https://codecov.io/gh/modoboa/modoboa-installer diff --git a/modoboa_installer/config_dict_template.py b/modoboa_installer/config_dict_template.py index d03dde1..4e3b918 100644 --- a/modoboa_installer/config_dict_template.py +++ b/modoboa_installer/config_dict_template.py @@ -39,7 +39,7 @@ ConfigDictTemplate = [ }, { "option": "type", - "default": "rspamd", + "default": "amavis", "customizable": True, "question": "Please select your antispam utility", "values": ["rspamd", "amavis"], diff --git a/modoboa_installer/scripts/files/rspamd/local.d/antivirus.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/antivirus.conf.tpl index 5e50a4e..f1d98eb 100644 --- a/modoboa_installer/scripts/files/rspamd/local.d/antivirus.conf.tpl +++ b/modoboa_installer/scripts/files/rspamd/local.d/antivirus.conf.tpl @@ -7,7 +7,6 @@ clamav { symbol = "CLAM_VIRUS"; type = "clamav"; servers = "127.0.0.1:3310" - patterns { # symbol_name = "pattern"; JUST_EICAR = "Test.EICAR"; diff --git a/modoboa_installer/scripts/files/rspamd/local.d/arc.conf b/modoboa_installer/scripts/files/rspamd/local.d/arc.conf deleted file mode 100644 index 3dcf992..0000000 --- a/modoboa_installer/scripts/files/rspamd/local.d/arc.conf +++ /dev/null @@ -1,3 +0,0 @@ -try_fallback = false; -selector_map = "%selector_map_path"; -path_map = "%key_map_path"; diff --git a/modoboa_installer/scripts/files/rspamd/local.d/dmarc.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/dmarc.conf.tpl new file mode 100644 index 0000000..bfe456a --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/dmarc.conf.tpl @@ -0,0 +1,21 @@ +reporting { + # Required attributes + enabled = true; # Enable reports in general + email = 'postmaster@%hostname'; # Source of DMARC reports + domain = '%hostname'; # Domain to serve + org_name = '%hostname'; # Organisation + # Optional parameters + #bcc_addrs = ["postmaster@example.com"]; # additional addresses to copy on reports + report_local_controller = false; # Store reports for local/controller scans (for testing only) + #helo = 'rspamd.localhost'; # Helo used in SMTP dialog + #smtp = '127.0.0.1'; # SMTP server IP + #smtp_port = 25; # SMTP server port + from_name = '%hostname DMARC REPORT'; # SMTP FROM + msgid_from = 'rspamd'; # Msgid format + #max_entries = 1k; # Maxiumum amount of entries per domain + #keys_expire = 2d; # Expire date for Redis keys + #only_domains = '/path/to/map'; # Only store reports from domains or eSLDs listed in this map + # Available from 3.3 + #exclude_domains = '/path/to/map'; # Exclude reports from domains or eSLDs listed in this map + #exclude_domains = ["example.com", "another.com"]; # Alternative, use array to exclude reports from domains or eSLDs +} diff --git a/modoboa_installer/scripts/files/rspamd/local.d/force_actions.conf.tpl b/modoboa_installer/scripts/files/rspamd/local.d/force_actions.conf.tpl new file mode 100644 index 0000000..6a1b331 --- /dev/null +++ b/modoboa_installer/scripts/files/rspamd/local.d/force_actions.conf.tpl @@ -0,0 +1,5 @@ +rules { + DMARC_POLICY_QUARANTINE { + action = "add header"; + } +} diff --git a/modoboa_installer/scripts/rspamd.py b/modoboa_installer/scripts/rspamd.py index 3890f28..dddd534 100644 --- a/modoboa_installer/scripts/rspamd.py +++ b/modoboa_installer/scripts/rspamd.py @@ -25,13 +25,19 @@ class Rspamd(base.Installer): "local.d/arc.conf", "local.d/mx_check.conf", "local.d/spf.conf", - "local.d/worker-controller.inc", "local.d/worker-normal.inc", "local.d/worker-proxy.inc", "local.d/greylist.conf", "local.d/milter_headers.conf", "local.d/metrics.conf"] + def __init__(self, *args, **kwargs): + super().__init__(self, *args, **kwargs) + self.generate_password_condition = (not self.upgrade or + utils.user_input( + "Do you want to (re)generate rspamd password ? (y/N)").lower().startswith("y") + ) + @property def config_dir(self): """Return appropriate config dir.""" @@ -83,23 +89,26 @@ class Rspamd(base.Installer): _config_files.append("local.d/rbl.conf") if self.app_config["whitelist_auth"].lower() == "true": _config_files.append("local.d/groups.conf") + if self.generate_password_condition: + _config_files.append("local.d/worker-controller.inc") return _config_files def get_template_context(self): _context = super().get_template_context() - code, controller_password = utils.exec_cmd( - r"rspamadm pw -p {}".format(self.app_config["password"])) - if code != 0: - utils.error("Error setting rspamd password. " - "Please make sure it is not 'q1' or 'q2'." - "Storing the password in plain. See" - "https://rspamd.com/doc/quickstart.html#setting-the-controller-password") - _context["controller_password"] = self.app_config["password"] - else: - controller_password = controller_password.decode().replace("\n", "") - _context["controller_password"] = controller_password _context["greylisting_disabled"] = "" if not self.app_config["greylisting"].lower() == "true" else "#" _context["whitelist_auth_enabled"] = "" if self.app_config["whitelist_auth"].lower() == "true" else "#" + if self.generate_password_condition: + code, controller_password = utils.exec_cmd( + r"rspamadm pw -p {}".format(self.app_config["password"])) + if code != 0: + utils.error("Error setting rspamd password. " + "Please make sure it is not 'q1' or 'q2'." + "Storing the password in plain. See" + "https://rspamd.com/doc/quickstart.html#setting-the-controller-password") + _context["controller_password"] = self.app_config["password"] + else: + controller_password = controller_password.decode().replace("\n", "") + _context["controller_password"] = controller_password return _context def post_run(self): diff --git a/run.py b/run.py index 4dab5a3..7bb2d70 100755 --- a/run.py +++ b/run.py @@ -85,12 +85,11 @@ def config_file_update_complete(backup_location): utils.BLUE) -def main(input_args): - """Install process.""" +def parser_setup(input_args): parser = argparse.ArgumentParser() versions = ( ["latest"] + list(compatibility_matrix.COMPATIBILITY_MATRIX.keys()) - ) + ) parser.add_argument("--debug", action="store_true", default=False, help="Enable debug output") parser.add_argument("--force", action="store_true", default=False, @@ -118,7 +117,7 @@ def main(input_args): parser.add_argument( "--backup", action="store_true", default=False, help="Backing up interactively previously installed instance" - ) + ) parser.add_argument( "--silent-backup", action="store_true", default=False, help="For script usage, do not require user interaction " @@ -131,13 +130,18 @@ def main(input_args): "--restore", type=str, metavar="path", help="Restore a previously backup up modoboa instance on a NEW machine. " "You MUST provide backup directory" - ) + ) parser.add_argument( "--skip-checks", action="store_true", default=False, help="Skip the checks the installer performs initially") parser.add_argument("domain", type=str, help="The main domain of your future mail server") - args = parser.parse_args(input_args) + return parser.parse_args(input_args) + + +def main(input_args): + """Install process.""" + args = parser_setup(input_args) if args.debug: utils.ENV["debug"] = True @@ -241,20 +245,29 @@ def main(input_args): scripts.install(appname, config, args.upgrade, args.restore) system.restart_service("cron") package.backend.restore_system() + hostname = config.get("general", "hostname") if not args.restore: utils.success( - "Congratulations! You can enjoy Modoboa at https://{} (admin:password)" - .format(config.get("general", "hostname")) + f"Congratulations! You can enjoy Modoboa at https://{hostname} " + "(admin:password)" ) if config.get("rspamd", "enabled"): + rspamd_password = config.get("rspamd", "password") utils.success( - f"You can also enjoy rspamd at https://{config.get("general", "hostname")}/rspamd ({config.get("rspamd", "password")})" + f"You can also enjoy rspamd at https://{hostname}/rspamd " + f"(password: {rspamd_password})" ) else: utils.success( - "Restore complete! You can enjoy Modoboa at https://{} (same credentials as before)" - .format(config.get("general", "hostname")) + f"Restore complete! You can enjoy Modoboa at https://{hostname} " + "(same credentials as before)" ) + if config.get("rspamd", "enabled"): + rspamd_password = config.get("rspamd", "password") + utils.success( + f"You can also enjoy rspamd at https://{hostname}/rspamd " + "(password: {rspamd_password})" + ) utils.success( "\n" "Modoboa is a free software maintained by volunteers.\n" From 0056ef20aab9e6e92af3e9d65f84e4441e9b877a Mon Sep 17 00:00:00 2001 From: Spitfireap Date: Mon, 13 Jan 2025 15:19:05 +0100 Subject: [PATCH 49/56] Hide sender client IP --- .../scripts/files/postfix/anonymize_headers.pcre.tpl | 11 +++++++++++ modoboa_installer/scripts/files/postfix/master.cf.tpl | 3 +++ modoboa_installer/scripts/postfix.py | 5 ++--- 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 modoboa_installer/scripts/files/postfix/anonymize_headers.pcre.tpl diff --git a/modoboa_installer/scripts/files/postfix/anonymize_headers.pcre.tpl b/modoboa_installer/scripts/files/postfix/anonymize_headers.pcre.tpl new file mode 100644 index 0000000..b4eef17 --- /dev/null +++ b/modoboa_installer/scripts/files/postfix/anonymize_headers.pcre.tpl @@ -0,0 +1,11 @@ +if /^\s*Received:.*Authenticated sender.*\(Postfix\)/ +/^Received: from .*? \([\w\-.]* \[.*?\]\)(.*|\n.*)\(Authenticated sender: (.+)\)\s+by.+\(Postfix\) with (.*)/ + REPLACE Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with $3 +endif +if /^\s*Received: from .*rspamd.localhost .*\(Postfix\)/ +/^Received: from.* (.*|\n.*)\((.+) (.+)\)\s+by (.+) \(Postfix\) with (.*)/ + REPLACE Received: from rspamd (rspamd $3) by $4 (Postfix) with $5 +endif +/^\s*X-Enigmail/ IGNORE +/^\s*X-Originating-IP/ IGNORE +/^\s*X-Forward/ IGNORE diff --git a/modoboa_installer/scripts/files/postfix/master.cf.tpl b/modoboa_installer/scripts/files/postfix/master.cf.tpl index 9f25b43..c35b4f4 100644 --- a/modoboa_installer/scripts/files/postfix/master.cf.tpl +++ b/modoboa_installer/scripts/files/postfix/master.cf.tpl @@ -27,6 +27,7 @@ submission inet n - - - - smtpd -o smtpd_helo_restrictions= -o smtpd_sender_restrictions=reject_sender_login_mismatch -o milter_macro_daemon_name=ORIGINATING + -o cleanup_service_name=ascleanup %{amavis_enabled} -o smtpd_proxy_filter=inet:[127.0.0.1]:10026 #smtps inet n - - - - smtpd # -o syslog_name=postfix/smtps @@ -42,6 +43,8 @@ submission inet n - - - - smtpd #628 inet n - - - - qmqpd pickup unix n - - 60 1 pickup cleanup unix n - - - 0 cleanup +ascleanup unix n - - - 0 cleanup + -o header_checks=pcre:/etc/postfix/anonymize_headers.pcre qmgr unix n - n 300 1 qmgr #qmgr unix n - n 300 1 oqmgr tlsmgr unix - - - 1000? 1 tlsmgr diff --git a/modoboa_installer/scripts/postfix.py b/modoboa_installer/scripts/postfix.py index ea56b75..cf0c361 100644 --- a/modoboa_installer/scripts/postfix.py +++ b/modoboa_installer/scripts/postfix.py @@ -18,10 +18,9 @@ class Postfix(base.Installer): appname = "postfix" packages = { - "deb": ["postfix"], - "rpm": ["postfix"], + "deb": ["postfix", "postfix-pcre"], } - config_files = ["main.cf", "master.cf"] + config_files = ["main.cf", "master.cf", "anonymize_headers.pcre"] def get_packages(self): """Additional packages.""" From 757c1dd48b1b488b58cf86391cbd5353315a93bb Mon Sep 17 00:00:00 2001 From: Spitfireap Date: Mon, 13 Jan 2025 15:28:44 +0100 Subject: [PATCH 50/56] Hide sender client IP v2 --- modoboa_installer/scripts/files/postfix/main.cf.tpl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modoboa_installer/scripts/files/postfix/main.cf.tpl b/modoboa_installer/scripts/files/postfix/main.cf.tpl index dd110bb..2070134 100644 --- a/modoboa_installer/scripts/files/postfix/main.cf.tpl +++ b/modoboa_installer/scripts/files/postfix/main.cf.tpl @@ -132,6 +132,9 @@ strict_rfc821_envelopes = yes smtpd_sender_login_maps = proxy:%{db_driver}:/etc/postfix/sql-sender-login-map.cf +# Add authenticated header to hide public client IP +smtpd_sasl_authenticated_header = yes + # Recipient restriction rules smtpd_recipient_restrictions = check_policy_service inet:127.0.0.1:9999 From 97b98c9d098d6adcc9c2ce84fb1c66694ee998f5 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 28 Jan 2025 13:33:55 +0100 Subject: [PATCH 51/56] Fixed unit tests --- tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests.py b/tests.py index cfb0d14..80929fb 100644 --- a/tests.py +++ b/tests.py @@ -47,7 +47,7 @@ class ConfigFileTestCase(unittest.TestCase): def test_interactive_mode(self, mock_user_input): """Check interactive mode.""" mock_user_input.side_effect = [ - "0", "0", "", "", "", "", "" + "0", "0", "", "", "", "", "", "" ] with open(os.devnull, "w") as fp: sys.stdout = fp @@ -99,7 +99,7 @@ class ConfigFileTestCase(unittest.TestCase): def test_interactive_mode_letsencrypt(self, mock_user_input): """Check interactive mode.""" mock_user_input.side_effect = [ - "1", "admin@example.test", "0", "", "", "", "" + "0", "0", "1", "admin@example.test", "0", "", "", "", "" ] with open(os.devnull, "w") as fp: sys.stdout = fp @@ -126,8 +126,8 @@ class ConfigFileTestCase(unittest.TestCase): "example.test"]) self.assertTrue(os.path.exists(self.cfgfile)) self.assertIn( - "fail2ban modoboa automx rspamd clamav dovecot nginx razor " - "postfix uwsgi radicale", + "fail2ban modoboa automx amavis clamav dovecot nginx " + "postfix postwhite spamassassin uwsgi radicale opendkim", out.getvalue() ) self.assertNotIn( From 95e2010957056823529f6c4c92645be03bf955ea Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 28 Jan 2025 13:43:59 +0100 Subject: [PATCH 52/56] Few fixes --- modoboa_installer/scripts/dovecot.py | 17 ++++++++++++----- modoboa_installer/scripts/rspamd.py | 24 +++++++++++++++--------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/modoboa_installer/scripts/dovecot.py b/modoboa_installer/scripts/dovecot.py index 98381e6..00dd8c7 100644 --- a/modoboa_installer/scripts/dovecot.py +++ b/modoboa_installer/scripts/dovecot.py @@ -28,10 +28,14 @@ class Dovecot(base.Installer): "dovecot", "dovecot-pigeonhole"] } config_files = [ - "dovecot.conf", "dovecot-dict-sql.conf.ext", "conf.d/10-ssl.conf", - "conf.d/10-master.conf", "conf.d/20-lmtp.conf", "conf.d/10-ssl-keys.try", - "conf.d/dovecot-oauth2.conf.ext" - ] + "dovecot.conf", + "dovecot-dict-sql.conf.ext", + "conf.d/10-ssl.conf", + "conf.d/10-master.conf", + "conf.d/20-lmtp.conf", + "conf.d/10-ssl-keys.try", + "conf.d/dovecot-oauth2.conf.ext", + ] with_user = True def setup_user(self): @@ -45,7 +49,10 @@ class Dovecot(base.Installer): _config_files = self.config_files if self.app_config["move_spam_to_junk"]: - _config_files += ["conf.d/custom_after_sieve/spam-to-junk.sieve"] + _config_files += [ + "conf.d/custom_after_sieve/spam-to-junk.sieve", + "conf.d/90-sieve.conf", + ] return _config_files + [ "dovecot-sql-{}.conf.ext=dovecot-sql.conf.ext" diff --git a/modoboa_installer/scripts/rspamd.py b/modoboa_installer/scripts/rspamd.py index dddd534..6e58fcf 100644 --- a/modoboa_installer/scripts/rspamd.py +++ b/modoboa_installer/scripts/rspamd.py @@ -21,15 +21,21 @@ class Rspamd(base.Installer): "rspamd", "redis" ] } - config_files = ["local.d/dkim_signing.conf", - "local.d/arc.conf", - "local.d/mx_check.conf", - "local.d/spf.conf", - "local.d/worker-normal.inc", - "local.d/worker-proxy.inc", - "local.d/greylist.conf", - "local.d/milter_headers.conf", - "local.d/metrics.conf"] + config_files = [ + "local.d/arc.conf", + "local.d/dkim_signing.conf", + "local.d/dmarc.conf", + "local.d/force_actions.conf", + "local.d/greylist.conf", + "local.d/metrics.conf", + "local.d/milter_headers.conf", + "local.d/mx_check.conf", + "local.d/redis.conf", + "local.d/settings.conf", + "local.d/spf.conf", + "local.d/worker-normal.inc", + "local.d/worker-proxy.inc", + ] def __init__(self, *args, **kwargs): super().__init__(self, *args, **kwargs) From a9ae8c50ad2034616350329487b2093396326312 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 13 May 2025 10:43:37 +0200 Subject: [PATCH 53/56] Removed wrong constructor argument --- modoboa_installer/scripts/rspamd.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modoboa_installer/scripts/rspamd.py b/modoboa_installer/scripts/rspamd.py index 6e58fcf..c45681c 100644 --- a/modoboa_installer/scripts/rspamd.py +++ b/modoboa_installer/scripts/rspamd.py @@ -38,11 +38,11 @@ class Rspamd(base.Installer): ] def __init__(self, *args, **kwargs): - super().__init__(self, *args, **kwargs) - self.generate_password_condition = (not self.upgrade or - utils.user_input( - "Do you want to (re)generate rspamd password ? (y/N)").lower().startswith("y") - ) + super().__init__(*args, **kwargs) + self.generate_password_condition = ( + not self.upgrade or utils.user_input( + "Do you want to (re)generate rspamd password ? (y/N)").lower().startswith("y") + ) @property def config_dir(self): From 4d49f182ec11b37ec41d339951121ca6696d813f Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 19 Aug 2025 17:26:33 +0200 Subject: [PATCH 54/56] Make sure amavis can still be installed --- run.py | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/run.py b/run.py index 7bb2d70..5d0aa3a 100755 --- a/run.py +++ b/run.py @@ -3,12 +3,9 @@ """An installer for Modoboa.""" import argparse +import configparser import datetime import os -try: - import configparser -except ImportError: - import ConfigParser as configparser import sys from modoboa_installer import checks @@ -29,7 +26,6 @@ PRIMARY_APPS = [ "radicale", "uwsgi", "nginx", - "rspamd", "postfix", "dovecot" ] @@ -200,6 +196,10 @@ def main(input_args): config.set("modoboa", "version", args.version) config.set("modoboa", "install_beta", str(args.beta)) + PRIMARY_APPS.append( + "amavis" if config.get("antispam", "type") == "amavis" else "rspamd" + ) + if args.backup or args.silent_backup: backup_system(config, args) return @@ -251,36 +251,30 @@ def main(input_args): f"Congratulations! You can enjoy Modoboa at https://{hostname} " "(admin:password)" ) - if config.get("rspamd", "enabled"): - rspamd_password = config.get("rspamd", "password") - utils.success( - f"You can also enjoy rspamd at https://{hostname}/rspamd " - f"(password: {rspamd_password})" - ) else: utils.success( f"Restore complete! You can enjoy Modoboa at https://{hostname} " "(same credentials as before)" ) - if config.get("rspamd", "enabled"): - rspamd_password = config.get("rspamd", "password") - utils.success( - f"You can also enjoy rspamd at https://{hostname}/rspamd " - "(password: {rspamd_password})" - ) + if config.getboolean("rspamd", "enabled"): + rspamd_password = config.get("rspamd", "password") + utils.success( + f"You can also enjoy rspamd at https://{hostname}/rspamd " + f"(password: {rspamd_password})" + ) utils.success( "\n" "Modoboa is a free software maintained by volunteers.\n" "You like the project and want it to be sustainable?\n" "Then don't wait anymore and go sponsor it here:\n" - ) + ) utils.printcolor( "https://github.com/sponsors/modoboa\n", utils.YELLOW - ) + ) utils.success( "Thank you for your help :-)\n" - ) + ) if __name__ == "__main__": From 21a6f85786754e35ef401e40af70fbcc6b7ab162 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 19 Aug 2025 17:34:55 +0200 Subject: [PATCH 55/56] Also install opendkim if antispam is amavis --- modoboa_installer/scripts/clamav.py | 2 +- modoboa_installer/scripts/modoboa.py | 15 +-------------- run.py | 7 ++++--- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/modoboa_installer/scripts/clamav.py b/modoboa_installer/scripts/clamav.py index a62eda6..181eaf3 100644 --- a/modoboa_installer/scripts/clamav.py +++ b/modoboa_installer/scripts/clamav.py @@ -42,7 +42,7 @@ class Clamav(base.Installer): """Additional tasks.""" if package.backend.FORMAT == "deb": user = self.config.get(self.appname, "user") - if self.config.get("amavis", "enabled").lower() == "true": + if self.config.getboolean("amavis", "enabled"): system.add_user_to_group( user, self.config.get("amavis", "user") ) diff --git a/modoboa_installer/scripts/modoboa.py b/modoboa_installer/scripts/modoboa.py index 4e8d28a..4d9b778 100644 --- a/modoboa_installer/scripts/modoboa.py +++ b/modoboa_installer/scripts/modoboa.py @@ -49,24 +49,11 @@ class Modoboa(base.Installer): self.instance_path = self.config.get("modoboa", "instance_path") self.extensions = self.config.get("modoboa", "extensions").split() self.devmode = self.config.getboolean("modoboa", "devmode") - # Sanity check for amavis and rspamd - self.amavis_enabled = self.sanity_check("modoboa-amavis", "amavis") - self.sanity_check("modoboa-rspamd", "rspamd") - + self.amavis_enabled = self.config.getboolean("amavis", "enabled") self.dovecot_enabled = self.config.getboolean("dovecot", "enabled") self.opendkim_enabled = self.config.getboolean("opendkim", "enabled") self.dkim_cron_enabled = False - def sanity_check(self, extension, plugin): - # Sanity check for plugin requirements - enabled = False - if extension in self.extensions: - if self.config.getboolean(plugin, "enabled"): - enabled = True - else: - self.extensions.remove(extension) - return enabled - def is_extension_ok_for_version(self, extension, version): """Check if extension can be installed with this modo version.""" version = utils.convert_version_to_int(version) diff --git a/run.py b/run.py index 5d0aa3a..1b1c24f 100755 --- a/run.py +++ b/run.py @@ -196,9 +196,10 @@ def main(input_args): config.set("modoboa", "version", args.version) config.set("modoboa", "install_beta", str(args.beta)) - PRIMARY_APPS.append( - "amavis" if config.get("antispam", "type") == "amavis" else "rspamd" - ) + if config.get("antispam", "type") == "amavis": + PRIMARY_APPS += ["amavis", "opendkim"] + else: + PRIMARY_APPS += ["rspamd"] if args.backup or args.silent_backup: backup_system(config, args) From 2c6c3a7573314b0b44f27e898f8f3d4e197ffece Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 19 Aug 2025 17:38:25 +0200 Subject: [PATCH 56/56] Do not alter global variable --- run.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/run.py b/run.py index 1b1c24f..83027d8 100755 --- a/run.py +++ b/run.py @@ -197,9 +197,9 @@ def main(input_args): config.set("modoboa", "install_beta", str(args.beta)) if config.get("antispam", "type") == "amavis": - PRIMARY_APPS += ["amavis", "opendkim"] + antispam_apps = ["amavis", "opendkim"] else: - PRIMARY_APPS += ["rspamd"] + antispam_apps = ["rspamd"] if args.backup or args.silent_backup: backup_system(config, args) @@ -242,7 +242,7 @@ def main(input_args): ssl_backend = ssl.get_backend(config) if ssl_backend and not args.upgrade: ssl_backend.generate_cert() - for appname in PRIMARY_APPS: + for appname in PRIMARY_APPS + antispam_apps: scripts.install(appname, config, args.upgrade, args.restore) system.restart_service("cron") package.backend.restore_system()