38 Commits

Author SHA1 Message Date
Spitap
9276953f87 Created 15-mailboxes.conf.tpl 2025-11-01 10:42:20 +01:00
Spitap
11f6f646d1 Fixed typos 2025-10-31 22:51:01 +01:00
Spitap
46d7c6acca Added mailbox file and activate the auto subscribe 2025-10-31 16:36:08 +01:00
github-actions[bot]
0e64b92199 [GitHub Action] Updated version file 2025-10-13 07:27:25 +00:00
Antoine Nguyen
1d701353d9 Merge pull request #604 from CooperDActor-bytes/patch-1
Fix Grammar
2025-10-13 09:26:14 +02:00
Cooper D'Andilly
3acec87ab5 Fix Grammar 2025-10-12 17:05:05 +11:00
github-actions[bot]
dd9c7457c8 [GitHub Action] Updated version file 2025-09-26 15:02:26 +00:00
Antoine Nguyen
73feb967fe cover mysql use case for dict definition in dovecot 2.4
see #602
2025-09-26 17:00:29 +02:00
github-actions[bot]
ad3f9f8cef [GitHub Action] Updated version file 2025-09-25 11:59:29 +00:00
Antoine Nguyen
765b56d48a Do not install radicale-storage-by-index radicale plugin anymore 2025-09-25 13:58:05 +02:00
github-actions[bot]
a9a8f8888c [GitHub Action] Updated version file 2025-09-24 09:24:28 +00:00
Antoine Nguyen
355e8d9b98 Complete MariaDB fix on debian 12
fix #598
2025-09-24 11:22:51 +02:00
github-actions[bot]
212fa9b5c1 [GitHub Action] Updated version file 2025-09-24 09:06:37 +00:00
Antoine Nguyen
65961209c8 Fixed issue with MariaDB and Debian 13
see #598
2025-09-24 11:05:09 +02:00
github-actions[bot]
5a6f5e7d71 [GitHub Action] Updated version file 2025-09-23 16:38:15 +00:00
Antoine Nguyen
eed7603ba1 Complete fix for #601 2025-09-23 18:36:41 +02:00
github-actions[bot]
731df935e5 [GitHub Action] Updated version file 2025-09-23 14:28:39 +00:00
Antoine Nguyen
6f1e717fc4 Fixed issue with autoconfig setup
see #601
2025-09-23 16:27:05 +02:00
github-actions[bot]
c7f24218aa [GitHub Action] Updated version file 2025-09-23 09:28:12 +00:00
Antoine Nguyen
3d80bc3131 Merge pull request #597 from modoboa/automx-replacement
Replaced automx by Modoboa autoconfig service
2025-09-23 11:27:06 +02:00
github-actions[bot]
67971ee981 [GitHub Action] Updated version file 2025-09-23 08:14:31 +00:00
Antoine Nguyen
34bf98452d Merge pull request #600 from FranMercedesG/master
Fix amavis installation on Debian 13 (LZ4)
2025-09-23 10:14:02 +02:00
github-actions[bot]
6e9fc2e7c2 [GitHub Action] Updated version file 2025-09-23 08:13:44 +00:00
Antoine Nguyen
90d4aac001 Merge pull request #599 from bergerc/master
Add missing dependancy on unzip for postwhite.
2025-09-23 10:12:37 +02:00
FranMercedesG
22cbc2e278 fix amavis installation on debian 13 2025-09-23 01:02:15 -04:00
Chris
2dbec9d2a5 Add missing dependancy on unzip for postwhite. 2025-09-22 22:13:09 +02:00
github-actions[bot]
9d91a25293 [GitHub Action] Updated version file 2025-09-20 09:48:04 +00:00
Antoine Nguyen
246419407a Fixed wrong path 2025-09-20 11:46:45 +02:00
Antoine Nguyen
fc81c04220 Replaced automx by Modoboa autoconfig service 2025-09-16 18:12:11 +02:00
github-actions[bot]
ff485f0d25 [GitHub Action] Updated version file 2025-09-16 13:43:42 +00:00
Antoine Nguyen
56be1be372 Merge pull request #596 from modoboa/dovecot-24-support
Dovecot 2.4 support
2025-09-16 15:42:31 +02:00
Antoine Nguyen
b95bf58488 Configuration fixes 2025-09-16 15:40:27 +02:00
Antoine Nguyen
9e12fa3dda Removed temp. change 2025-09-16 09:44:54 +02:00
Antoine Nguyen
4b0c9e4376 WIP: dovecot 2.4 support 2025-09-16 09:43:18 +02:00
github-actions[bot]
e56868072f [GitHub Action] Updated version file 2025-09-08 10:31:45 +00:00
Antoine Nguyen
506b59cda7 Make sure to install modoboa apps according to selected content filter 2025-09-08 12:30:09 +02:00
github-actions[bot]
327238d917 [GitHub Action] Updated version file 2025-08-19 15:41:04 +00:00
Antoine Nguyen
af1fb15953 Merge pull request #493 from modoboa/rspamd
Added Rspamd installation
2025-08-19 17:40:00 +02:00
58 changed files with 2084 additions and 339 deletions

View File

@@ -3,11 +3,11 @@
|workflow| |codecov|
An installer which deploy a complete mail server based on Modoboa.
An installer which deploys a complete mail server based on Modoboa.
.. warning::
This tool is still in beta stage, it has been tested on:
This tool is still in beta, it has been tested on:
* Debian 12 and upper
* Ubuntu Focal Fossa (20.04) and upper
@@ -44,7 +44,6 @@ The following components are installed by the installer:
* Postfix
* Dovecot
* Amavis (with SpamAssassin and ClamAV) or Rspamd
* automx (autoconfiguration service)
* OpenDKIM
* Radicale (CalDAV and CardDAV server)

View File

@@ -222,35 +222,6 @@ ConfigDictTemplate = [
},
]
},
{
"name": "automx",
"values": [
{
"option": "enabled",
"default": "true",
},
{
"option": "user",
"default": "automx",
},
{
"option": "config_dir",
"default": "/etc",
},
{
"option": "home_dir",
"default": "/srv/automx",
},
{
"option": "venv_path",
"default": "%(home_dir)s/env",
},
{
"option": "instance_path",
"default": "%(home_dir)s/instance",
},
]
},
{
"name": "rspamd",
"if": ["antispam.enabled=true", "antispam.type=rspamd"],

View File

@@ -3,19 +3,19 @@
import os
import pwd
import stat
from typing import Optional
from . import package
from . import system
from . import utils
class Database(object):
class Database:
"""Common database backend."""
default_port = None
packages = None
service = None
default_port: Optional[int] = None
packages: Optional[dict[str, list[str]]] = None
service: Optional[str] = None
def __init__(self, config):
"""Install if necessary."""
@@ -36,7 +36,6 @@ class Database(object):
class PostgreSQL(Database):
"""Postgres."""
default_port = 5432
@@ -157,7 +156,6 @@ class PostgreSQL(Database):
class MySQL(Database):
"""MySQL backend."""
default_port = 3306
@@ -178,7 +176,7 @@ class MySQL(Database):
if name.startswith("debian"):
if version.startswith("8"):
self.packages["deb"].append("libmysqlclient-dev")
elif version.startswith("11") or version.startswith("12"):
elif int(version[:2]) >= 11:
self.packages["deb"].append("libmariadb-dev")
else:
self.packages["deb"].append("libmariadbclient-dev")
@@ -188,7 +186,7 @@ class MySQL(Database):
self.packages["deb"].append("libmariadb-dev")
else:
self.packages["deb"].append("libmysqlclient-dev")
super(MySQL, self).install_package()
super().install_package()
queries = []
if name.startswith("debian"):
if version.startswith("8"):
@@ -200,7 +198,7 @@ class MySQL(Database):
self.dbpassword)
return
if (
(name.startswith("debian") and (version.startswith("11") or version.startswith("12"))) or
(name.startswith("debian") and int(version[:2]) >= 11) or
(name.startswith("ubuntu") and int(version[:2]) >= 22)
):
queries = [

View File

@@ -52,7 +52,17 @@ class Amavis(base.Installer):
packages = super(Amavis, self).get_packages()
if package.backend.FORMAT == "deb":
db_driver = "pg" if self.db_driver == "pgsql" else self.db_driver
return packages + ["libdbd-{}-perl".format(db_driver)]
packages += ["libdbd-{}-perl".format(db_driver)]
name, version = utils.dist_info()
try:
major_version = int(version.split(".")[0])
except ValueError:
major_version = 0
if major_version >= 13:
packages = [p if p != "liblz4-tool" else "lz4" for p in packages]
return packages
if self.db_driver == "pgsql":
db_driver = "Pg"
elif self.db_driver == "mysql":

View File

@@ -1,101 +0,0 @@
"""Automx related tasks."""
import os
import pwd
import shutil
import stat
from .. import python
from .. import system
from .. import utils
from . import base
class Automx(base.Installer):
"""Automx installation."""
appname = "automx"
config_files = ["automx.conf"]
no_daemon = True
packages = {
"deb": ["memcached", "unzip"],
"rpm": ["memcached", "unzip"]
}
with_user = True
def __init__(self, *args, **kwargs):
"""Get configuration."""
super(Automx, self).__init__(*args, **kwargs)
self.venv_path = self.config.get("automx", "venv_path")
self.instance_path = self.config.get("automx", "instance_path")
def get_template_context(self):
"""Additional variables."""
context = super(Automx, self).get_template_context()
sql_dsn = "{}://{}:{}@{}:{}/{}".format(
"postgresql" if self.dbengine == "postgres" else self.dbengine,
self.config.get("modoboa", "dbuser"),
self.config.get("modoboa", "dbpassword"),
self.dbhost,
self.dbport,
self.config.get("modoboa", "dbname"))
if self.db_driver == "pgsql":
sql_query = (
"SELECT first_name || ' ' || last_name AS display_name, email"
", SPLIT_PART(email, '@', 2) AS domain "
"FROM core_user WHERE email='%s' AND is_active;")
else:
sql_query = (
"SELECT concat(first_name, ' ', last_name) AS display_name, "
"email, SUBSTRING_INDEX(email, '@', -1) AS domain "
"FROM core_user WHERE email='%s' AND is_active=1;"
)
context.update({"sql_dsn": sql_dsn, "sql_query": sql_query})
return context
def _setup_venv(self):
"""Prepare a python virtualenv."""
python.setup_virtualenv(self.venv_path, sudo_user=self.user)
packages = [
"future", "lxml", "ipaddress", "sqlalchemy < 2.0", "python-memcached",
"python-dateutil", "configparser"
]
if self.dbengine == "postgres":
packages.append("psycopg2-binary")
else:
packages.append("mysqlclient")
python.install_packages(packages, self.venv_path, sudo_user=self.user)
target = "{}/master.zip".format(self.home_dir)
if os.path.exists(target):
os.unlink(target)
utils.exec_cmd(
"wget https://github.com/sys4/automx/archive/master.zip",
sudo_user=self.user, cwd=self.home_dir)
self.repo_dir = "{}/automx-master".format(self.home_dir)
if os.path.exists(self.repo_dir):
shutil.rmtree(self.repo_dir)
utils.exec_cmd(
"unzip master.zip", sudo_user=self.user, cwd=self.home_dir)
utils.exec_cmd(
"{} setup.py install".format(
python.get_path("python", self.venv_path)),
cwd=self.repo_dir)
def _deploy_instance(self):
"""Copy files to instance dir."""
if not os.path.exists(self.instance_path):
pw = pwd.getpwnam(self.user)
mode = (
stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH)
utils.mkdir(self.instance_path, mode, pw[2], pw[3])
path = "{}/src/automx_wsgi.py".format(self.repo_dir)
utils.exec_cmd("cp {} {}".format(path, self.instance_path),
sudo_user=self.user, cwd=self.home_dir)
def post_run(self):
"""Additional tasks."""
self._setup_venv()
self._deploy_instance()
system.enable_and_start_service("memcached")

View File

@@ -2,6 +2,7 @@
import os
import sys
from typing import Optional
from .. import database
from .. import package
@@ -13,15 +14,15 @@ from .. import utils
class Installer:
"""Simple installer for one application."""
appname = None
no_daemon = False
daemon_name = None
packages = {}
with_user = False
with_db = False
config_files = []
appname: str
no_daemon: bool = False
daemon_name: Optional[str] = None
packages: dict[str, list[str]] = {}
with_user: bool = False
with_db: bool = False
config_files: list[str] = []
def __init__(self, config, upgrade: bool, archive_path: str):
def __init__(self, config, upgrade: bool, archive_path: str) -> None:
"""Get configuration."""
self.config = config
self.upgrade = upgrade
@@ -44,7 +45,7 @@ class Installer:
self.dbpasswd = self.config.get(self.appname, "dbpassword")
@property
def modoboa_2_2_or_greater(self):
def modoboa_2_2_or_greater(self) -> bool:
# Check if modoboa version > 2.2
modoboa_version = python.get_package_version(
"modoboa",

View File

@@ -5,7 +5,6 @@ import os
import pwd
import shutil
import stat
import uuid
from .. import database
from .. import package
@@ -16,52 +15,88 @@ from . import base
class Dovecot(base.Installer):
"""Dovecot installer."""
appname = "dovecot"
packages = {
"deb": [
"dovecot-imapd", "dovecot-lmtpd", "dovecot-managesieved",
"dovecot-sieve"],
"rpm": [
"dovecot", "dovecot-pigeonhole"]
"dovecot-imapd",
"dovecot-lmtpd",
"dovecot-managesieved",
"dovecot-sieve",
],
"rpm": ["dovecot", "dovecot-pigeonhole"],
}
per_version_config_files = {
"2.3": [
"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",
],
"2.4": [
"dovecot.conf",
"conf.d/10-mail.conf",
"conf.d/10-master.conf",
"conf.d/10-ssl.conf",
"conf.d/10-ssl-keys.try",
"conf.d/15-mailboxes.conf",
"conf.d/20-lmtp.conf",
"conf.d/auth-oauth2.conf.ext",
],
}
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",
]
with_user = True
@property
def version(self) -> str:
if not hasattr(self, "_version"):
self._version = package.backend.get_installed_version("dovecot-core")[:3]
return self._version
def setup_user(self):
"""Setup mailbox user."""
super().setup_user()
self.mailboxes_owner = self.app_config["mailboxes_owner"]
system.create_user(self.mailboxes_owner, self.home_dir)
def get_config_files(self):
"""Additional config files."""
_config_files = self.config_files
def _get_config_files_for_version(self, version: str) -> list[str]:
files = self.per_version_config_files[version]
if version == "2.4":
files += [
f"conf.d/auth-sql-{self.dbengine}.conf.ext=conf.d/auth-sql.conf.ext",
f"conf.d/auth-master-{self.dbengine}.conf.ext=conf.d/auth-master.conf.ext",
f"conf.d/30-dict-server-{self.dbengine}.conf=conf.d/30-dict-server.conf",
]
else:
files += [
f"dovecot-sql-{self.dbengine}.conf.ext=dovecot-sql.conf.ext",
f"dovecot-sql-master-{self.dbengine}.conf.ext=dovecot-sql-master.conf.ext",
]
result = []
for path in files:
if "=" not in path:
result.append(f"{version}/{path}={path}")
else:
src, dst = path.split("=")
result.append(f"{version}/{src}={dst}")
return result
def get_config_files(self) -> list[str]:
"""Additional config files."""
_config_files = self._get_config_files_for_version(self.version)
_config_files.append(
f"postlogin-{self.dbengine}.sh=/usr/local/bin/postlogin.sh"
)
if self.app_config["move_spam_to_junk"]:
_config_files += [
"conf.d/custom_after_sieve/spam-to-junk.sieve",
"conf.d/90-sieve.conf",
"custom_after_sieve/spam-to-junk.sieve=conf.d/custom_after_sieve/spam-to-junk.sieve",
f"{self.version}/conf.d/90-sieve.conf=conf.d/90-sieve.conf",
]
return _config_files + [
"dovecot-sql-{}.conf.ext=dovecot-sql.conf.ext"
.format(self.dbengine),
"dovecot-sql-master-{}.conf.ext=dovecot-sql-master.conf.ext"
.format(self.dbengine),
"postlogin-{}.sh=/usr/local/bin/postlogin.sh"
.format(self.dbengine),
]
return _config_files
def get_packages(self):
"""Additional packages."""
@@ -72,7 +107,9 @@ class Dovecot(base.Installer):
packages += super().get_packages()
backports_codename = getattr(self, "backports_codename", None)
if backports_codename:
packages = [f"{package}/{backports_codename}-backports" for package in packages]
packages = [
f"{package}/{backports_codename}-backports" for package in packages
]
return packages
def install_packages(self):
@@ -83,7 +120,8 @@ class Dovecot(base.Installer):
package.backend.enable_backports("bookworm")
self.backports_codename = "bookworm"
package.backend.preconfigure(
"dovecot-core", "create-ssl-cert", "boolean", "false")
"dovecot-core", "create-ssl-cert", "boolean", "false"
)
super().install_packages()
def get_template_context(self):
@@ -92,14 +130,20 @@ class Dovecot(base.Installer):
pw_mailbox = pwd.getpwnam(self.mailboxes_owner)
dovecot_package = {"deb": "dovecot-core", "rpm": "dovecot"}
ssl_protocol_parameter = "ssl_protocols"
if package.backend.get_installed_version(dovecot_package[package.backend.FORMAT]) > "2.3":
if (
package.backend.get_installed_version(
dovecot_package[package.backend.FORMAT]
)
> "2.3"
):
ssl_protocol_parameter = "ssl_min_protocol"
ssl_protocols = "!SSLv2 !SSLv3"
if package.backend.get_installed_version("openssl").startswith("1.1") \
or package.backend.get_installed_version("openssl").startswith("3"):
if package.backend.get_installed_version("openssl").startswith(
"1.1"
) or package.backend.get_installed_version("openssl").startswith("3"):
ssl_protocols = "!SSLv3"
if ssl_protocol_parameter == "ssl_min_protocol":
ssl_protocols = "TLSv1"
ssl_protocols = "TLSv1.2"
if "centos" in utils.dist_name():
protocols = "protocols = imap lmtp sieve"
extra_protocols = self.config.get("dovecot", "extra_protocols")
@@ -110,30 +154,38 @@ class Dovecot(base.Installer):
protocols = ""
oauth2_client_id, oauth2_client_secret = utils.create_oauth2_app(
"Dovecot", "dovecot", self.config)
"Dovecot", "dovecot", self.config
)
hostname = self.config.get("general", "hostname")
oauth2_introspection_url = (
f"https://{oauth2_client_id}:{oauth2_client_secret}"
f"@{hostname}/api/o/introspect/"
)
context.update({
"db_driver": self.db_driver,
"mailboxes_owner_uid": pw_mailbox[2],
"mailboxes_owner_gid": pw_mailbox[3],
"mailbox_owner": self.mailboxes_owner,
"modoboa_user": self.config.get("modoboa", "user"),
"modoboa_dbname": self.config.get("modoboa", "dbname"),
"modoboa_dbuser": self.config.get("modoboa", "dbuser"),
"modoboa_dbpassword": self.config.get("modoboa", "dbpassword"),
"protocols": protocols,
"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 "#",
"do_move_spam_to_junk": "" if self.app_config["move_spam_to_junk"] else "#",
"oauth2_introspection_url": oauth2_introspection_url
})
context.update(
{
"db_driver": self.db_driver,
"mailboxes_owner_uid": pw_mailbox[2],
"mailboxes_owner_gid": pw_mailbox[3],
"mailbox_owner": self.mailboxes_owner,
"modoboa_user": self.config.get("modoboa", "user"),
"modoboa_dbname": self.config.get("modoboa", "dbname"),
"modoboa_dbuser": self.config.get("modoboa", "dbuser"),
"modoboa_dbpassword": self.config.get("modoboa", "dbpassword"),
"protocols": protocols,
"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 "#"
),
"do_move_spam_to_junk": (
"" if self.app_config["move_spam_to_junk"] else "#"
),
"oauth2_introspection_url": oauth2_introspection_url,
"radicale_user": self.config.get("radicale", "user"),
}
)
return context
def install_config_files(self):
@@ -141,28 +193,36 @@ class Dovecot(base.Installer):
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
)
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":
if self.version == "2.3" and self.dbengine == "postgres":
dbname = self.config.get("modoboa", "dbname")
dbuser = self.config.get("modoboa", "dbuser")
dbpassword = self.config.get("modoboa", "dbpassword")
backend = database.get_backend(self.config)
backend.load_sql_file(
dbname, dbuser, dbpassword,
self.get_file_path("install_modoboa_postgres_trigger.sql")
dbname,
dbuser,
dbpassword,
self.get_file_path("install_modoboa_postgres_trigger.sql"),
)
backend.load_sql_file(
dbname, dbuser, dbpassword,
self.get_file_path("fix_modoboa_postgres_schema.sql")
dbname,
dbuser,
dbpassword,
self.get_file_path("fix_modoboa_postgres_schema.sql"),
)
for f in glob.glob("{}/*".format(self.get_file_path("conf.d"))):
for f in glob.glob(f"{self.get_file_path(f'{self.version}/conf.d')}/*"):
if os.path.isfile(f):
utils.copy_file(f, "{}/conf.d".format(self.config_dir))
# Make postlogin script executable
@@ -174,9 +234,11 @@ 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 = f"{self.config_dir}/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')
system.add_user_to_group(self.mailboxes_owner, "dovecot")
def restart_daemon(self):
"""Restart daemon process.
@@ -190,7 +252,8 @@ class Dovecot(base.Installer):
action = "start" if code else "restart"
utils.exec_cmd(
"service {} {} > /dev/null 2>&1".format(self.appname, action),
capture_output=False)
capture_output=False,
)
system.enable_service(self.get_daemon_name())
def backup(self, path):
@@ -198,8 +261,10 @@ class Dovecot(base.Installer):
home_dir = self.config.get("dovecot", "home_dir")
utils.printcolor("Backing up mails", utils.MAGENTA)
if not os.path.exists(home_dir) or os.path.isfile(home_dir):
utils.error("Error backing up emails, provided path "
f" ({home_dir}) seems not right...")
utils.error(
"Error backing up emails, provided path "
f" ({home_dir}) seems not right..."
)
return
dst = os.path.join(path, "mails/")
@@ -221,10 +286,13 @@ class Dovecot(base.Installer):
for dirpath, dirnames, filenames in os.walk(home_dir):
shutil.chown(dirpath, self.mailboxes_owner, self.mailboxes_owner)
for filename in filenames:
shutil.chown(os.path.join(dirpath, filename),
self.mailboxes_owner, self.mailboxes_owner)
shutil.chown(
os.path.join(dirpath, filename),
self.mailboxes_owner,
self.mailboxes_owner,
)
else:
utils.printcolor(
"It seems that emails were not backed up, skipping restoration.",
utils.MAGENTA
utils.MAGENTA,
)

View File

@@ -1,39 +0,0 @@
[automx]
provider = %domain
domains = *
#debug=yes
#logfile = /srv/automx/automx.log
# Protect against DoS
memcache = 127.0.0.1:11211
memcache_ttl = 600
client_error_limit = 20
rate_limit_exception_networks = 127.0.0.0/8, ::1/128
[global]
backend = sql
action = settings
account_type = email
host = %sql_dsn
query = %sql_query
result_attrs = display_name, email
display_name = ${display_name}
smtp = yes
smtp_server = %hostname
smtp_port = 587
smtp_encryption = starttls
smtp_auth = plaintext
smtp_auth_identity = ${email}
smtp_refresh_ttl = 6
smtp_default = yes
imap = yes
imap_server = %hostname
imap_port = 143
imap_encryption = starttls
imap_auth = plaintext
imap_auth_identity = ${email}
imap_refresh_ttl = 6

View File

@@ -0,0 +1,122 @@
#log_debug=category=auth
#auth_debug_passwords = yes
##
## Authentication processes
##
# Enable LOGIN command and all other plaintext authentications even if
# SSL/TLS is not used (LOGINDISABLED capability). Note that if the remote IP
# matches the local IP (ie. you're connecting from the same computer), the
# connection is considered secure and plaintext authentication is allowed,
# unless ssl = required.
#auth_allow_cleartext = yes
# Authentication cache size (e.g. 10M). 0 means it's disabled. Note that
# bsdauth, PAM and vpopmail require cache_key to be set for caching to be used.
#auth_cache_size = 0
# Time to live for cached data. After TTL expires the cached record is no
# longer used, *except* if the main database lookup returns internal failure.
# We also try to handle password changes automatically: If user's previous
# authentication was successful, but this one wasn't, the cache isn't used.
# For now this works only with plaintext authentication.
#auth_cache_ttl = 1 hour
# TTL for negative hits (user not found, password mismatch).
# 0 disables caching them completely.
#auth_cache_negative_ttl = 1 hour
# Space separated list of realms for SASL authentication mechanisms that need
# them. You can leave it empty if you don't want to support multiple realms.
# Many clients simply use the first one listed here, so keep the default realm
# first.
#auth_realms =
#
# Default realm/domain to use if none was specified. This is used for both
# SASL realms and appending @domain to username in plaintext logins.
#auth_default_domain =
# List of allowed characters in username. If the user-given username contains
# a character not listed in here, the login automatically fails. This is just
# an extra check to make sure user can't exploit any potential quote escaping
# vulnerabilities with SQL/LDAP databases. If you want to allow all characters,
# set this value to empty.
#auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@
# Username character translations before it's looked up from databases. The
# value contains series of from -> to characters. For example "#@/@" means
# that '#' and '/' characters are translated to '@'.
#auth_username_translation =
# Username formatting before it's looked up from databases.
auth_username_format = %{user|lower}
#auth_username_format = %{user|username|lower}
# If you want to allow master users to log in by specifying the master
# username within the normal username string (ie. not using SASL mechanism's
# support for it), you can specify the separator character here. The format
# is then <username><separator><master username>. UW-IMAP uses "*" as the
# separator, so that could be a good choice.
auth_master_user_separator = *
# Username to use for users logging in with ANONYMOUS SASL mechanism
#auth_anonymous_username = anonymous
# Host name to use in GSSAPI principal names. The default is to use the
# name returned by gethostname(). Use "$ALL" (with quotes) to allow all keytab
# entries.
#auth_gssapi_hostname =
# Kerberos keytab to use for the GSSAPI mechanism. Will use the system
# default (usually /etc/krb5.keytab) if not specified. You may need to change
# the auth service to run as root to be able to read this file.
#auth_krb5_keytab =
# Do NTLM and GSS-SPNEGO authentication using Samba's winbind daemon and
# ntlm_auth helper. <https://doc.dovecot.org/latest/core/config/auth/mechanisms/winbind.html>
#auth_use_winbind = no
# Path for Samba's ntlm_auth helper binary.
#auth_winbind_helper_path = /usr/bin/ntlm_auth
# Time to delay before replying to failed authentications.
#auth_failure_delay = 2 secs
# Require a valid SSL client certificate or the authentication fails.
#auth_ssl_require_client_cert = no
# Take the username from client's SSL certificate, using
# X509_NAME_get_text_by_NID() which returns the subject's DN's
# CommonName.
#auth_ssl_username_from_cert = no
# Space separated list of wanted authentication mechanisms:
# plain login digest-md5 cram-md5 ntlm anonymous gssapi
# gss-spnego xoauth2 oauthbearer
# NOTE: See also auth_allow_cleartext setting.
auth_mechanisms = plain login oauthbearer xoauth2
##
## Password and user databases
##
#
# Password database is used to verify user's password (and nothing more).
# You can have multiple passdbs and userdbs. This is useful if you want to
# allow both system users (/etc/passwd) and virtual users to login without
# duplicating the system users into virtual database.
#
# <https://doc.dovecot.org/latest/core/config/auth/passdb.html>
#
# User database specifies where mails are located and what user/group IDs
# own them. For single-UID configuration use "static" userdb.
#
# <https://doc.dovecot.org/latest/core/config/auth/userdb.html>
#!include auth-deny.conf.ext
!include auth-master.conf.ext
!include auth-oauth2.conf.ext
#!include auth-system.conf.ext
!include auth-sql.conf.ext
#!include auth-ldap.conf.ext
#!include auth-passwdfile.conf.ext
#!include auth-static.conf.ext

View File

@@ -0,0 +1,417 @@
##
## Mailbox locations and namespaces
##
# Location for users' mailboxes. The default is empty, which means that Dovecot
# tries to find the mailboxes automatically. This won't work if the user
# doesn't yet have any mail, so you should explicitly tell Dovecot the full
# location.
#
# If you're using mbox, giving a path to the INBOX file (eg. /var/mail/%%u)
# isn't enough. You'll also need to tell Dovecot where the other mailboxes are
# kept. This is called the "root mail directory", and it must be the first
# path given in the mail_location setting.
#
# There are a few special variables you can use, eg.:
#
# %%{user} - username
# %%{user|username} - user part in user@domain, same as %%u if there's no domain
# %%{user|domain} - domain part in user@domain, empty if there's no domain
# %%{home} - home directory
#
# See https://doc.dovecot.org/latest/core/settings/variables.html for full list
# of variables.
#
# Example:
# mail_driver = maildir
# mail_path = ~/Maildir
# mail_inbox_path = ~/Maildir/.INBOX
#
# Debian defaults
# Note that upstream considers mbox deprecated and strongly recommends
# against its use in production environments. See further information
# at
# https://doc.dovecot.org/2.4.0/core/config/mailbox/formats/mbox.html
# mail_driver = mbox
# mail_home = /home/%%{user|username}
# mail_path = %%{home}/mail
# mail_inbox_path = /var/mail/%%{user}
mail_driver = maildir
mail_home = %{home_dir}/%%{user|domain}/%%{user|username}
mail_path = %%{home}/Maildir
# If you need to set multiple mailbox locations or want to change default
# namespace settings, you can do it by defining namespace sections.
#
# You can have private, shared and public namespaces. Private namespaces
# are for user's personal mails. Shared namespaces are for accessing other
# users' mailboxes that have been shared. Public namespaces are for shared
# mailboxes that are managed by sysadmin. If you create any shared or public
# namespaces you'll typically want to enable ACL plugin also, otherwise all
# users can access all the shared mailboxes, assuming they have permissions
# on filesystem level to do so.
namespace inbox {
# Namespace type: private, shared or public
#type = private
# Hierarchy separator to use. You should use the same separator for all
# namespaces or some clients get confused. '/' is usually a good one.
# The default however depends on the underlying mail storage format.
#separator =
# Prefix required to access this namespace. This needs to be different for
# all namespaces. For example "Public/".
#prefix =
# Physical location of the mailbox. This is in same format as
# mail location, which is also the default for it.
# mail_driver =
# mail_path =
#
# There can be only one INBOX, and this setting defines which namespace
# has it.
inbox = yes
# If namespace is hidden, it's not advertised to clients via NAMESPACE
# extension. You'll most likely also want to set list=no. This is mostly
# useful when converting from another server with different namespaces which
# you want to deprecate but still keep working. For example you can create
# hidden namespaces with prefixes "~/mail/", "~%%u/mail/" and "mail/".
#hidden = no
# Show the mailboxes under this namespace with LIST command. This makes the
# namespace visible for clients that don't support NAMESPACE extension.
# "children" value lists child mailboxes, but hides the namespace prefix.
#list = yes
# Namespace handles its own subscriptions. If set to "no", the parent
# namespace handles them (empty prefix should always have this as "yes")
#subscriptions = yes
# See 15-mailboxes.conf for definitions of special mailboxes.
}
# Example shared namespace configuration
#namespace shared {
#type = shared
#separator = /
# Mailboxes are visible under "shared/user@domain/"
# $user, $domain and $username are expanded to the destination user.
#prefix = shared/$user/
# Mail location for other users' mailboxes. Note that %%{variables} and ~/
# expands to the logged in user's data. %%{owner_user} and %%{owner_home}
# destination user's data.
#mail_driver = maildir
#mail_path = %%{owner_home}/Maildir
#mail_index_path = ~/Maildir/shared/%%{owner_user}
# Use the default namespace for saving subscriptions.
#subscriptions = no
# List the shared/ namespace only if there are visible shared mailboxes.
#list = children
#}
# Should shared INBOX be visible as "shared/user" or "shared/user/INBOX"?
#mail_shared_explicit_inbox = no
# System user and group used to access mails. If you use multiple, userdb
# can override these by returning uid or gid fields. You can use either numbers
# or names. <https://doc.dovecot.org/latest/core/config/system_users.html#uids>
#mail_uid =
#mail_gid =
# Group to enable temporarily for privileged operations. Currently this is
# used only with INBOX when either its initial creation or dotlocking fails.
# Typically this is set to "mail" to give access to /var/mail.
mail_privileged_group = mail
# Grant access to these supplementary groups for mail processes. Typically
# these are used to set up access to shared mailboxes. Note that it may be
# dangerous to set these if users can create symlinks (e.g. if "mail" group is
# set here, ln -s /var/mail ~/mail/var could allow a user to delete others'
# mailboxes, or ln -s /secret/shared/box ~/mail/mybox would allow reading it).
#mail_access_groups =
# Allow full filesystem access to clients. There's no access checks other than
# what the operating system does for the active UID/GID. It works with both
# maildir and mboxes, allowing you to prefix mailboxes names with eg. /path/
# or ~user/.
#mail_full_filesystem_access = no
# Dictionary for key=value mailbox attributes. This is used for example by
# URLAUTH and METADATA extensions.
#mail_attribute {
# dict file {
# path = %%{home}/Maildir/dovecot-attributes
# }
#}
# A comment or note that is associated with the server. This value is
# accessible for authenticated users through the IMAP METADATA server
# entry "/shared/comment".
#mail_server_comment = ""
# Indicates a method for contacting the server administrator. According to
# RFC 5464, this value MUST be a URI (e.g., a mailto: or tel: URL), but that
# is currently not enforced. Use for example mailto:admin@example.com. This
# value is accessible for authenticated users through the IMAP METADATA server
# entry "/shared/admin".
#mail_server_admin =
##
## Mail processes
##
# Don't use mmap() at all. This is required if you store indexes to shared
# filesystems (NFS or clustered filesystem).
#mmap_disable = no
# Rely on O_EXCL to work when creating dotlock files. NFS supports O_EXCL
# since version 3, so this should be safe to use nowadays by default.
#dotlock_use_excl = yes
# When to use fsync() or fdatasync() calls:
# optimized (default): Whenever necessary to avoid losing important data
# always: Useful with e.g. NFS when write()s are delayed
# never: Never use it (best performance, but crashes can lose data)
#mail_fsync = optimized
# Locking method for index files. Alternatives are fcntl, flock and dotlock.
# Dotlocking uses some tricks which may create more disk I/O than other locking
# methods. NFS users: flock doesn't work, remember to change mmap_disable.
#lock_method = fcntl
# Directory where mails can be temporarily stored. Usually it's used only for
# mails larger than >= 128 kB. It's used by various parts of Dovecot, for
# example LDA/LMTP while delivering large mails or zlib plugin for keeping
# uncompressed mails.
#mail_temp_dir = /tmp
# Valid UID range for users, defaults to 500 and above. This is mostly
# to make sure that users can't log in as daemons or other system users.
# Note that denying root logins is hardcoded to dovecot binary and can't
# be done even if first_valid_uid is set to 0.
#first_valid_uid = 500
#last_valid_uid = 0
# Valid GID range for users, defaults to non-root/wheel. Users having
# non-valid GID as primary group ID aren't allowed to log in. If user
# belongs to supplementary groups with non-valid GIDs, those groups are
# not set.
#first_valid_gid = 1
#last_valid_gid = 0
# Maximum allowed length for mail keyword name. It's only forced when trying
# to create new keywords.
#mail_max_keyword_length = 50
# ':' separated list of directories under which chrooting is allowed for mail
# processes (ie. /var/mail will allow chrooting to /var/mail/foo/bar too).
# This setting doesn't affect login_chroot, mail_chroot or auth chroot
# settings. If this setting is empty, "/./" in home dirs are ignored.
# WARNING: Never add directories here which local users can modify, that
# may lead to root exploit. Usually this should be done only if you don't
# allow shell access for users. <doc/wiki/Chrooting.txt>
#valid_chroot_dirs =
# Default chroot directory for mail processes. This can be overridden for
# specific users in user database by giving /./ in user's home directory
# (eg. /home/./user chroots into /home). Note that usually there is no real
# need to do chrooting, Dovecot doesn't allow users to access files outside
# their mail directory anyway. If your home directories are prefixed with
# the chroot directory, append "/." to mail_chroot. <doc/wiki/Chrooting.txt>
#mail_chroot =
# UNIX socket path to master authentication server to find users.
# This is used by imap (for shared users) and lda.
#auth_socket_path = /var/run/dovecot/auth-userdb
# Directory where to look up mail plugins.
#mail_plugin_dir = /usr/lib/dovecot
# Space separated list of plugins to load for all services. Plugins specific to
# IMAP, LDA, etc. are added to this list in their own .conf files.
#mail_plugins =
#
# To add plugins, use
#mail_plugins {
# plugin = yes
#}
mail_plugins {
quota = yes
quota_clone = yes
}
##
## Mailbox handling optimizations
##
# Mailbox list indexes can be used to optimize IMAP STATUS commands. They are
# also required for IMAP NOTIFY extension to be enabled.
#mailbox_list_index = yes
# Trust mailbox list index to be up-to-date. This reduces disk I/O at the cost
# of potentially returning out-of-date results after e.g. server crashes.
# The results will be automatically fixed once the folders are opened.
#mailbox_list_index_very_dirty_syncs = yes
# Should INBOX be kept up-to-date in the mailbox list index? By default it's
# not, because most of the mailbox accesses will open INBOX anyway.
#mailbox_list_index_include_inbox = no
# The minimum number of mails in a mailbox before updates are done to cache
# file. This allows optimizing Dovecot's behavior to do less disk writes at
# the cost of more disk reads.
#mail_cache_min_mail_count = 0
# When IDLE command is running, mailbox is checked once in a while to see if
# there are any new mails or other changes. This setting defines the minimum
# time to wait between those checks. Dovecot can also use inotify and
# kqueue to find out immediately when changes occur.
#mailbox_idle_check_interval = 30 secs
# Save mails with CR+LF instead of plain LF. This makes sending those mails
# take less CPU, especially with sendfile() syscall with Linux and FreeBSD.
# But it also creates a bit more disk I/O which may just make it slower.
# Also note that if other software reads the mboxes/maildirs, they may handle
# the extra CRs wrong and cause problems.
#mail_save_crlf = no
# Max number of mails to keep open and prefetch to memory. This only works with
# some mailbox formats and/or operating systems.
#mail_prefetch_count = 0
# How often to scan for stale temporary files and delete them (0 = never).
# These should exist only after Dovecot dies in the middle of saving mails.
#mail_temp_scan_interval = 1w
# How many slow mail accesses sorting can perform before it returns failure.
# With IMAP the reply is: NO [LIMIT] Requested sort would have taken too long.
# The untagged SORT reply is still returned, but it's likely not correct.
#mail_sort_max_read_count = 0
protocol !indexer-worker {
# If folder vsize calculation requires opening more than this many mails from
# disk (i.e. mail sizes aren't in cache already), return failure and finish
# the calculation via indexer process. Disabled by default. This setting must
# be 0 for indexer-worker processes.
#mail_vsize_bg_after_count = 0
}
##
## Maildir-specific settings
##
# By default LIST command returns all entries in maildir beginning with a dot.
# Enabling this option makes Dovecot return only entries which are directories.
# This is done by stat()ing each entry, so it causes more disk I/O.
# (For systems setting struct dirent->d_type, this check is free and it's
# done always regardless of this setting)
#maildir_stat_dirs = no
# When copying a message, do it with hard links whenever possible. This makes
# the performance much better, and it's unlikely to have any side effects.
#maildir_copy_with_hardlinks = yes
# Assume Dovecot is the only MUA accessing Maildir: Scan cur/ directory only
# when its mtime changes unexpectedly or when we can't find the mail otherwise.
#maildir_very_dirty_syncs = no
# If enabled, Dovecot doesn't use the S=<size> in the Maildir filenames for
# getting the mail's physical size, except when recalculating Maildir++ quota.
# This can be useful in systems where a lot of the Maildir filenames have a
# broken size. The performance hit for enabling this is very small.
#maildir_broken_filename_sizes = no
# Always move mails from new/ directory to cur/, even when the \Recent flags
# aren't being reset.
#maildir_empty_new = no
##
## mbox-specific settings
##
# Which locking methods to use for locking mbox. There are four available:
# dotlock: Create <mailbox>.lock file. This is the oldest and most NFS-safe
# solution. If you want to use /var/mail/ like directory, the users
# will need write access to that directory.
# dotlock_try: Same as dotlock, but if it fails because of permissions or
# because there isn't enough disk space, just skip it.
# fcntl : Use this if possible. Works with NFS too if lockd is used.
# flock : May not exist in all systems. Doesn't work with NFS.
# lockf : May not exist in all systems. Doesn't work with NFS.
#
# You can use multiple locking methods; if you do the order they're declared
# in is important to avoid deadlocks if other MTAs/MUAs are using multiple
# locking methods as well. Some operating systems don't allow using some of
# them simultaneously.
#mbox_read_locks = fcntl
#mbox_write_locks = dotlock fcntl
# Maximum time to wait for lock (all of them) before aborting.
#mbox_lock_timeout = 5 mins
# If dotlock exists but the mailbox isn't modified in any way, override the
# lock file after this much time.
#mbox_dotlock_change_timeout = 2 mins
# When mbox changes unexpectedly we have to fully read it to find out what
# changed. If the mbox is large this can take a long time. Since the change
# is usually just a newly appended mail, it'd be faster to simply read the
# new mails. If this setting is enabled, Dovecot does this but still safely
# fallbacks to re-reading the whole mbox file whenever something in mbox isn't
# how it's expected to be. The only real downside to this setting is that if
# some other MUA changes message flags, Dovecot doesn't notice it immediately.
# Note that a full sync is done with SELECT, EXAMINE, EXPUNGE and CHECK
# commands.
#mbox_dirty_syncs = yes
# Like mbox_dirty_syncs, but don't do full syncs even with SELECT, EXAMINE,
# EXPUNGE or CHECK commands. If this is set, mbox_dirty_syncs is ignored.
#mbox_very_dirty_syncs = no
# Delay writing mbox headers until doing a full write sync (EXPUNGE and CHECK
# commands and when closing the mailbox). This is especially useful for POP3
# where clients often delete all mails. The downside is that our changes
# aren't immediately visible to other MUAs.
#mbox_lazy_writes = yes
# If mbox size is smaller than this (e.g. 100k), don't write index files.
# If an index file already exists it's still read, just not updated.
#mbox_min_index_size = 0
# Mail header selection algorithm to use for MD5 POP3 UIDLs when
# pop3_uidl_format=%%m. For backwards compatibility we use apop3d inspired
# algorithm, but it fails if the first Received: header isn't unique in all
# mails. An alternative algorithm is "all" that selects all headers.
#mbox_md5 = apop3d
##
## mdbox-specific settings
##
# Maximum dbox file size until it's rotated.
#mdbox_rotate_size = 10M
# Maximum dbox file age until it's rotated. Typically in days. Day begins
# from midnight, so 1d = today, 2d = yesterday, etc. 0 = check disabled.
#mdbox_rotate_interval = 0
# When creating new mdbox files, immediately preallocate their size to
# mdbox_rotate_size. This setting currently works only in Linux with some
# filesystems (ext4, xfs).
#mdbox_preallocate_space = no
# Settings to control adding $HasAttachment or $HasNoAttachment keywords.
# By default, all MIME parts with Content-Disposition=attachment, or inlines
# with filename parameter are consired attachments.
# add-flags - Add the keywords when saving new mails or when fetching can
# do it efficiently.
# content-type=type or !type - Include/exclude content type. Excluding will
# never consider the matched MIME part as attachment. Including will only
# negate an exclusion (e.g. content-type=!foo/* content-type=foo/bar).
# exclude-inlined - Exclude any Content-Disposition=inline MIME part.
#mail_attachment_detection_options =

View File

@@ -0,0 +1,174 @@
#default_process_limit = 100
#default_client_limit = 1000
# Default VSZ (virtual memory size) limit for service processes. This is mainly
# intended to catch and kill processes that leak memory before they eat up
# everything.
#default_vsz_limit = 256M
# Login user is internally used by login processes. This is the most untrusted
# user in Dovecot system. It shouldn't have access to anything at all.
#default_login_user = dovenull
# Internal user is used by unprivileged processes. It should be separate from
# login user, so that login processes can't disturb other processes.
#default_internal_user = dovecot
service imap-login {
inet_listener imap {
#port = 143
}
inet_listener imaps {
#port = 993
#ssl = yes
}
# Number of connections to handle before starting a new process. Typically
# the only useful values are 0 (unlimited) or 1. 1 is more secure, but 0
# is faster. <d>
#service_restart_request_count = 1
# Number of processes to always keep waiting for more connections.
#process_min_avail = 0
# If you set service_restart_request_count=0, you probably need to grow this.
#vsz_limit = 256M # default
}
service pop3-login {
inet_listener pop3 {
#port = 110
}
inet_listener pop3s {
#port = 995
#ssl = yes
}
}
service submission-login {
inet_listener submission {
#port = 587
}
inet_listener submissions {
#port = 465
}
}
service lmtp {
unix_listener lmtp {
#mode = 0666
}
# Create inet listener only if you can't use the above UNIX socket
#inet_listener lmtp {
# Avoid making LMTP visible for the entire internet
#listen = 127.0.0.1
#port = 24
#}
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
}
service imap {
# Most of the memory goes to mmap()ing files. You may need to increase this
# limit if you have huge mailboxes.
#vsz_limit = 256M # default
# Max. number of IMAP processes (connections)
#process_limit = 1024
executable = imap postlogin
}
service pop3 {
# Max. number of POP3 processes (connections)
#process_limit = 1024
executable = imap postlogin
}
service submission {
# Max. number of SMTP Submission processes (connections)
#process_limit = 1024
}
service postlogin {
executable = script-login /usr/local/bin/postlogin.sh
user = %modoboa_user
unix_listener postlogin {
}
}
service stats {
# To allow modoboa to access available cipher list.
unix_listener stats-reader {
user = %{mailboxes_owner}
group = %{mailboxes_owner}
mode = 0660
}
unix_listener stats-writer {
user = %{mailboxes_owner}
group = %{mailboxes_owner}
mode = 0660
}
}
service auth {
# auth_socket_path points to this userdb socket by default. It's typically
# used by dovecot-lda, doveadm, possibly imap process, etc. Users that have
# full permissions to this socket are able to get a list of all usernames and
# get the results of everyone's userdb lookups.
#
# The default 0666 mode allows anyone to connect to the socket, but the
# userdb lookups will succeed only if the userdb returns an "uid" field that
# matches the caller process's UID. Also if caller's uid or gid matches the
# socket's uid or gid the lookup succeeds. Anything else causes a failure.
#
# To give the caller full permissions to lookup all users, set the mode to
# something else than 0666 and Dovecot lets the kernel enforce the
# permissions (e.g. 0777 allows everyone full permissions).
unix_listener auth-userdb {
#mode = 0666
user = %{mailboxes_owner}
#group =
}
# Postfix smtp-auth
unix_listener /var/spool/postfix/private/auth {
mode = 0666
user = postfix
group = postfix
}
%{radicale_enabled}unix_listener auth-radicale {
%{radicale_enabled} mode = 0666
%{radicale_enabled} user = %{radicale_user}
%{radicale_enabled} group = %{radicale_user}
%{radicale_enabled} type = auth-legacy
%{radicale_enabled}}
# Auth process is run as this user.
#user = $SET:default_internal_user
}
service auth-worker {
# Auth worker process is run as root by default, so that it can access
# /etc/shadow. If this isn't necessary, the user should be changed to
# $SET:default_internal_user.
#user = root
}
service dict {
# If dict proxy is used, mail processes should have access to its socket.
# For example: mode=0660, group=vmail and global mail_access_groups=vmail
unix_listener dict {
mode = 0600
user = %{mailboxes_owner}
#group =
}
}

View File

@@ -0,0 +1,6 @@
# PEM encoded X.509 SSL/TLS certificate and private key. They're opened before
# dropping root privileges, so keep the key file unreadable by anyone but
# root. Included doc/mkcert.sh can be used to easily generate self-signed
# certificate, just make sure to update the domains in dovecot-openssl.cnf
ssl_server_cert_file = %tls_cert_file
ssl_server_key_file = %tls_key_file

View File

@@ -0,0 +1,59 @@
##
## SSL settings
##
# SSL/TLS support: yes, no, required. <https://doc.dovecot.org/latest/core/config/ssl.html>
ssl = yes
# PEM encoded X.509 SSL/TLS certificate and private key. By default, Debian
# installs a self-signed certificate. This is useful for testing, but you
# should obtain a real certificate from a recognized certificate authority.
#
# These files are opened before dropping root privileges, so keep the key file
# unreadable by anyone but root. Included /usr/share/dovecot/mkcert.sh can be
# used to easily generate self-signed certificate, just make sure to update the
# domains in dovecot-openssl.cnf
#
# Preferred permissions: root:root 0444
# ssl_server_cert_file = /etc/dovecot/private/dovecot.pem
# Preferred permissions: root:root 0400
# ssl_server_key_file = /etc/dovecot/private/dovecot.key
!include_try /etc/dovecot/conf.d/10-ssl-keys.try
# If key file is password protected, give the password here. Alternatively
# give it when starting dovecot with -p parameter. Since this file is often
# world-readable, you may want to place this setting instead to a different
# root owned 0600 file by using ssl_key_password = <path.
#ssl_server_key_password =
# PEM encoded trusted certificate authority. Set this only if you intend to use
# ssl_request_client_cert=yes. The file should contain the CA certificate(s)
# followed by the matching CRL(s). (e.g. ssl_server_ca_file = /etc/ssl/certs/ca.pem)
#ssl_server_ca_file =
# Require that CRL check succeeds for client certificates.
#ssl_server_require_crl = yes
# Request client to send a certificate. If you also want to require it, set
# auth_ssl_require_client_cert=yes in auth section.
#ssl_server_request_client_cert = no
# Which field from certificate to use for username. commonName and
# x500UniqueIdentifier are the usual choices. You'll also need to set
# auth_ssl_username_from_cert=yes.
#ssl_server_cert_username_field = commonName
# SSL protocols to use. Debian systems specify TLSv1.2 by default, which should
# be reasonbly secure and compatible with existing clients.
%ssl_protocol_parameter = %ssl_protocols
# Diffie-Hellman parameters are no longer required and should be phased out.
# They do not work with ECDH(E) and require DH(E) ciphers.
#ssl_server_dh_file = /etc/dovecot/dh.pem
# SSL ciphers to use
#ssl_cipher_list = ALL:!kRSA:!SRP:!kDHd:!DSS:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK:!RC4:!ADH:!LOW@STRENGTH
ssl_cipher_list = EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA+RC4:EECDH:EDH+aRSA:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4
# SSL crypto device to use, for valid values run "openssl engine"
#ssl_crypto_device = /dev/crypto

View File

@@ -0,0 +1,90 @@
##
## Mailbox definitions
##
# Each mailbox is specified in a separate mailbox section. The section name
# specifies the mailbox name. If it has spaces, you can put the name
# "in quotes". These sections can contain the following mailbox settings:
#
# auto:
# Indicates whether the mailbox with this name is automatically created
# implicitly when it is first accessed. The user can also be automatically
# subscribed to the mailbox after creation. The following values are
# defined for this setting:
#
# no - Never created automatically.
# create - Automatically created, but no automatic subscription.
# subscribe - Automatically created and subscribed.
#
# special_use:
# A space-separated list of SPECIAL-USE flags (RFC 6154) to use for the
# mailbox. There are no validity checks, so you could specify anything
# you want in here, but it's not a good idea to use flags other than the
# standard ones specified in the RFC:
#
# \All - This (virtual) mailbox presents all messages in the
# user's message store.
# \Archive - This mailbox is used to archive messages.
# \Drafts - This mailbox is used to hold draft messages.
# \Flagged - This (virtual) mailbox presents all messages in the
# user's message store marked with the IMAP \Flagged flag.
# \Important - This (virtual) mailbox presents all messages in the
# user's message store deemed important to user.
# \Junk - This mailbox is where messages deemed to be junk mail
# are held.
# \Sent - This mailbox is used to hold copies of messages that
# have been sent.
# \Trash - This mailbox is used to hold messages that have been
# deleted.
#
# comment:
# Defines a default comment or note associated with the mailbox. This
# value is accessible through the IMAP METADATA mailbox entries
# "/shared/comment" and "/private/comment". Users with sufficient
# privileges can override the default value for entries with a custom
# value.
# NOTE: Assumes "namespace inbox" has been defined in 10-mail.conf.
namespace inbox {
# These mailboxes are widely used and could perhaps be created automatically:
mailbox Drafts {
auto = subscribe
special_use = \Drafts
}
mailbox Junk {
auto = subscribe
special_use = \Junk
}
mailbox Trash {
auto = subscribe
special_use = \Trash
}
# For \Sent mailboxes there are two widely used names. We'll mark both of
# them as \Sent. User typically deletes one of them if duplicates are created.
mailbox Sent {
auto = subscribe
special_use = \Sent
}
# mailbox "Sent Messages" {
# special_use = \Sent
# }
# If you have a virtual "All messages" mailbox:
#mailbox virtual/All {
# special_use = \All
# comment = All my messages
#}
# If you have a virtual "Flagged" mailbox:
#mailbox virtual/Flagged {
# special_use = \Flagged
# comment = All my flagged messages
#}
# If you have a virtual "Important" mailbox:
#mailbox virtual/Important {
# special_use = \Important
# comment = All my important messages
#}
}

View File

@@ -0,0 +1,108 @@
##
## IMAP specific settings
##
# If nothing happens for this long while client is IDLEing, move the connection
# to imap-hibernate process and close the old imap process. This saves memory,
# because connections use very little memory in imap-hibernate process. The
# downside is that recreating the imap process back uses some resources.
#imap_hibernate_timeout = 0
# Maximum IMAP command line length. Some clients generate very long command
# lines with huge mailboxes, so you may need to raise this if you get
# "Too long argument" or "IMAP command line too large" errors often.
#imap_max_line_length = 64k
# IMAP logout format string:
# %{input} - total number of bytes read from client
# %{output} - total number of bytes sent to client
# %{fetch_hdr_count} - Number of mails with mail header data sent to client
# %{fetch_hdr_bytes} - Number of bytes with mail header data sent to client
# %{fetch_body_count} - Number of mails with mail body data sent to client
# %{fetch_body_bytes} - Number of bytes with mail body data sent to client
# %{deleted} - Number of mails where client added \Deleted flag
# %{expunged} - Number of mails that client expunged, which does not
# include automatically expunged mails
# %{autoexpunged} - Number of mails that were automatically expunged after
# client disconnected
# %{trashed} - Number of mails that client copied/moved to the
# special_use=\Trash mailbox.
# %{appended} - Number of mails saved during the session
#imap_logout_format = in=%i out=%o deleted=%{deleted} expunged=%{expunged} \
# trashed=%{trashed} hdr_count=%{fetch_hdr_count} \
# hdr_bytes=%{fetch_hdr_bytes} body_count=%{fetch_body_count} \
# body_bytes=%{fetch_body_bytes}
# Amend or override the IMAP capability response. To override, set the value
# with imap_capability =
#
# To amend, you can use a boolean list to specify which capabilities to turn
# on and off
#imap_capability {
# SPECIAL-USE = yes
# "LITERAL+" = no
#}
# How long to wait between "OK Still here" notifications when client is
# IDLEing.
#imap_idle_notify_interval = 2 mins
# ID field names and values to send to clients. Using * as the value makes
# Dovecot use the default value. The following fields have default values
# currently: name, version, os, os-version, support-url, support-email,
# revision.
#imap_id_send =
# Use imap_id_received event to log IMAP id
# Workarounds for various client bugs:
# delay-newmail:
# Send EXISTS/RECENT new mail notifications only when replying to NOOP
# and CHECK commands. Some clients ignore them otherwise, for example OSX
# Mail (<v2.1). Outlook Express breaks more badly though, without this it
# may show user "Message no longer in server" errors. Note that OE6 still
# breaks even with this workaround if synchronization is set to
# "Headers Only".
# tb-extra-mailbox-sep:
# Thunderbird gets somehow confused with LAYOUT=fs (mbox and dbox) and
# adds extra '/' suffixes to mailbox names. This option causes Dovecot to
# ignore the extra '/' instead of treating it as invalid mailbox name.
# tb-lsub-flags:
# Show \Noselect flags for LSUB replies with LAYOUT=fs (e.g. mbox).
# This makes Thunderbird realize they aren't selectable and show them
# greyed out, instead of only later giving "not selectable" popup error.
#
# This is a boolean list
#imap_client_workarounds {
# delay-newmail = yes
#}
# Host allowed in URLAUTH URLs sent by client. "*" allows all.
#imap_urlauth_host =
# Enable IMAP LITERAL- extension (replaces LITERAL+)
#imap_literal_minus = no
# What happens when FETCH fails due to some internal error:
# disconnect-immediately:
# The FETCH is aborted immediately and the IMAP client is disconnected.
# disconnect-after:
# The FETCH runs for all the requested mails returning as much data as
# possible. The client is finally disconnected without a tagged reply.
# no-after:
# Same as disconnect-after, but tagged NO reply is sent instead of
# disconnecting the client. If the client attempts to FETCH the same failed
# mail more than once, the client is disconnected. This is to avoid clients
# from going into infinite loops trying to FETCH a broken mail.
#imap_fetch_failure = disconnect-immediately
protocol imap {
# Space separated list of plugins to load (default is global mail_plugins).
mail_plugins {
quota = yes
}
# Maximum number of IMAP connections allowed for a user from each IP address.
# NOTE: The username is compared case-sensitively.
#mail_max_userip_connections = 10
}

View File

@@ -0,0 +1,53 @@
##
#i
## LMTP specific settings
##
# Support proxying to other LMTP/SMTP servers by performing passdb lookups.
#lmtp_proxy = no
# When recipient address includes the detail (e.g. user+detail), try to save
# the mail to the detail mailbox. See also recipient_delimiter and
# lda_mailbox_autocreate settings.
#lmtp_save_to_detail_mailbox = no
# Verify quota before replying to RCPT TO. This adds a small overhead.
lmtp_rcpt_check_quota = yes
# Add "Received:" header to mails delivered.
#lmtp_add_received_header = yes
# Which recipient address to use for Delivered-To: header and Received:
# header. The default is "final", which is the same as the one given to
# RCPT TO command. "original" uses the address given in RCPT TO's ORCPT
# parameter, "none" uses nothing. Note that "none" is currently always used
# when a mail has multiple recipients.
#lmtp_hdr_delivery_address = final
# Workarounds for various client bugs:
# whitespace-before-path:
# Allow one or more spaces or tabs between `MAIL FROM:' and path and between
# `RCPT TO:' and path.
# mailbox-for-path:
# Allow using bare Mailbox syntax (i.e., without <...>) instead of full path
# syntax.
#
#lmtp_client_workarounds {
# whitespace-before-path = yes
#}
protocol lmtp {
mail_plugins {
quota = yes
sieve = yes
}
postmaster_address = %postmaster_address
# This strips the domain name before delivery, since the default
# userdb in Debian is /etc/passwd, which doesn't include domain
# names in the user. If you're using a different userdb backend
# that does include domain names, you may wish to remove this. See
# https://doc.dovecot.org/2.4.0/howto/lmtp/exim.html and
# https://doc.dovecot.org/2.4.0/core/summaries/settings.html#auth_username_format
# auth_username_format = %%{user | username}
}

View File

@@ -0,0 +1,45 @@
##
## Dictionary server settings
##
# Dictionary can be used to store key=value lists. This is used by several
# plugins. The dictionary can be accessed either directly or though a
# dictionary server. The following dict block maps dictionary names to URIs
# when the server is used. These can then be referenced using URIs in format
# "proxy::<name>".
dict_server {
mysql %dbhost {
port = %dbport
dbname = %modoboa_dbname
user = %modoboa_dbuser
password = %modoboa_dbpassword
}
dict quota {
driver = sql
sql_driver = %db_driver
hostname = %dbhost
dict_map priv/quota/storage {
sql_table = admin_quota
username_field = username
value_field bytes {
type = uint
}
}
dict_map priv/quota/messages {
sql_table = admin_quota
username_field = username
value_field messages {
type = uint
}
}
}
}
quota_clone {
dict proxy {
name = quota
}
}

View File

@@ -0,0 +1,47 @@
##
## Dictionary server settings
##
# Dictionary can be used to store key=value lists. This is used by several
# plugins. The dictionary can be accessed either directly or though a
# dictionary server. The following dict block maps dictionary names to URIs
# when the server is used. These can then be referenced using URIs in format
# "proxy::<name>".
dict_server {
pgsql %dbhost {
parameters {
port = %dbport
dbname = %modoboa_dbname
user = %modoboa_dbuser
password = %modoboa_dbpassword
}
}
dict quota {
driver = sql
sql_driver = %db_driver
hostname = %dbhost
dict_map priv/quota/storage {
sql_table = admin_quota
username_field = username
value_field bytes {
type = uint
}
}
dict_map priv/quota/messages {
sql_table = admin_quota
username_field = username
value_field messages {
type = uint
}
}
}
}
quota_clone {
dict proxy {
name = quota
}
}

View File

@@ -0,0 +1,79 @@
##
## Quota configuration.
##
# Note that you also have to enable quota plugin in mail_plugins setting.
## <https://doc.dovecot.org/latest/core/plugins/quota.html>
##
## Quota limits
##
# Quota limits are set using "quota_rule" parameters. To get per-user quota
# limits, you can set/override them by returning "quota_rule" extra field
# from userdb. It's also possible to give mailbox-specific limits, for example
# to give additional 100 MB when saving to Trash:
#mail_plugins {
# quota = yes
#}
quota "User quota" {
# storage_size = 1G
}
#
#namespace inbox {
# mailbox Trash {
# quota_storage_extra = 100M
# }
#}
##
## Quota warnings
##
# You can execute a given command when user exceeds a specified quota limit.
# Each quota root has separate limits. Only the command for the first
# exceeded limit is excecuted, so put the highest limit first.
# The commands are executed via script service by connecting to the named
# UNIX socket (quota-warning below).
# Note that % needs to be escaped as %%, otherwise "% " expands to empty.
#quota "User quota" {
# warning warn-95 {
# quota_storage_percentage = 95
# execute quota-warning {
# args = 95 %{user}
# }
# }
# warning warn-80 {
# quota_storage_percentage = 80
# execute quota-warning {
# args = 80 %{user}
# }
# }
#}
# Example quota-warning service. The unix listener's permissions should be
# set in a way that mail processes can connect to it. Below example assumes
# that mail processes run as vmail user. If you use mode=0666, all system users
# can generate quota warnings to anyone.
#service quota-warning {
# executable = script /usr/local/bin/quota-warning.sh
# user = dovecot
# unix_listener quota-warning {
# user = vmail
# }
#}
##
## Quota backends
##
# Multiple backends are supported:
# count: Default and recommended, quota driver tracks the quota internally within Dovecot's index files.
# maildir: Maildir++ quota
# fs: Read-only support for filesystem quota
#quota "User quota" {
# driver = count
#}

View File

@@ -0,0 +1,118 @@
##
## Settings for the Sieve interpreter
##
# Do not forget to enable the Sieve plugin in 15-lda.conf and 20-lmtp.conf
# by adding it to the respective mail_plugins { sieve = yes } settings.
# See https://doc.dovecot.org/latest/core/plugins/sieve.html
# Personal sieve script location
#sieve_script personal {
# driver = file
# path = ~/sieve
# active_path = ~/.dovecot.sieve
#}
# Default sieve script location
#sieve_script default {
# type = default
# name = default
# driver = file
# path = /etc/dovecot/sieve/default/
#}
%{do_move_spam_to_junk}sieve_script after {
%{do_move_spam_to_junk} type = after
%{do_move_spam_to_junk} path = /etc/dovecot/conf.d/custom_after_sieve
%{do_move_spam_to_junk}}
# Which Sieve language extensions are available to users. By default, all
# supported extensions are available, except for deprecated extensions or
# those that are still under development. Some system administrators may want
# to disable certain Sieve extensions or enable those that are not available
# by default. This setting can use 'yes' and 'no' to specify differences relative
# to the default. For example `imapflags = yes' will enable the
# deprecated imapflags extension in addition to all extensions were already
# enabled by default.
#sieve_extensions {
# mboxmetadata = yes
# vnd.dovecot.debug = yes
#}
# Which Sieve language extensions are ONLY available in global scripts. This
# can be used to restrict the use of certain Sieve extensions to administrator
# control, for instance when these extensions can cause security concerns.
# This setting has higher precedence than the `sieve_extensions' setting
# (above), meaning that the extensions enabled with this setting are never
# available to the user's personal script no matter what is specified for the
# `sieve_extensions' setting. The syntax of this setting is similar to the
# `sieve_extensions' setting, with the difference that extensions are
# enabled or disabled for exclusive use in global scripts. Currently, no
# extensions are marked as such by default.
#sieve_global_extensions =
# The Pigeonhole Sieve interpreter can have plugins of its own. Using this
# setting, the used plugins can be specified. Check the Dovecot documentation
# https://doc.dovecot.org/latest/core/plugins/sieve.html
#sieve_plugins = sieve_imapsieve sieve_extprograms
#sieve_pipe_bin_dir = /usr/share/dovecot-pigeonhole/sieve
#sieve_execute_bin_dir = /usr/share/dovecot-pigeonhole/sieve
#sieve_global_extensions {
# vnd.dovecot.pipe = yes
# vnd.dovecot.execute = yes
#}
#imapsieve_url =
# The separator that is expected between the :user and :detail
# address parts introduced by the subaddress extension. This may
# also be a sequence of characters (e.g. '--'). The current
# implementation looks for the separator from the left of the
# localpart and uses the first one encountered. The :user part is
# left of the separator and the :detail part is right. This setting
# is also used by Dovecot's LMTP service.
#recipient_delimiter = +-_
# The maximum size of a Sieve script. The compiler will refuse to compile any
# script larger than this limit. If set to 0, no limit on the script size is
# enforced.
#sieve_max_script_size = 1M
# The maximum number of actions that can be performed during a single script
# execution. If set to 0, no limit on the total number of actions is enforced.
#sieve_max_actions = 32
# The maximum number of redirect actions that can be performed during a single
# script execution. If set to 0, no redirect actions are allowed.
#sieve_max_redirects = 4
# The maximum number of personal Sieve scripts a single user can have. If set
# to 0, no limit on the number of scripts is enforced.
# (Currently only relevant for ManageSieve)
#sieve_quota_script_count = 0
# The maximum amount of disk storage a single user's scripts may occupy. If
# set to 0, no limit on the used amount of disk storage is enforced.
# (Currently only relevant for ManageSieve)
#sieve_quota_storage_size = 0
#mailbox Spam {
## From elsewhere to Spam folder
# sieve_script report-spam {
# type = before
# cause = copy
# path = /etc/dovecot/report-spam.sieve
# }
#}
## From Spam folder to elsewhere
#imapsieve_from Spam {
# sieve_script report-ham {
# type = before
# cause = copy
# path = /etc/dovecot/report-ham.sieve
# }
#}

View File

@@ -0,0 +1,28 @@
# Authentication for master users. Included from auth.conf.
# By adding master=yes setting inside a passdb you make the passdb a list
# of "master users", who can log in as anyone else.
# <https://doc.dovecot.org/latest/core/config/auth/master_users.html>
# Example master user passdb using passwd-file. You can use any passdb though.
#passdb master-passwd-file {
# driver = passwd-file
# master = yes
# passwd_file_path = /etc/dovecot/master-users
#}
sql_driver = %db_driver
mysql %dbhost {
port = %dbport
dbname = %modoboa_dbname
user = %modoboa_dbuser
password = %modoboa_dbpassword
}
passdb db1 {
driver = sql
sql_query = SELECT email AS user, password FROM core_user WHERE email='%%{user}' and is_active=1 and master_user=1
master = yes
result_success = continue
}

View File

@@ -0,0 +1,30 @@
# Authentication for master users. Included from auth.conf.
# By adding master=yes setting inside a passdb you make the passdb a list
# of "master users", who can log in as anyone else.
# <https://doc.dovecot.org/latest/core/config/auth/master_users.html>
# Example master user passdb using passwd-file. You can use any passdb though.
#passdb master-passwd-file {
# driver = passwd-file
# master = yes
# passwd_file_path = /etc/dovecot/master-users
#}
sql_driver = %db_driver
pgsql %dbhost {
parameters {
port = %dbport
dbname = %modoboa_dbname
user = %modoboa_dbuser
password = %modoboa_dbpassword
}
}
passdb db1 {
driver = sql
sql_query = SELECT email AS user, password FROM core_user WHERE email='%%{user}' and is_active and master_user
master = yes
result_success = continue
}

View File

@@ -0,0 +1,24 @@
auth_mechanisms {
xoauth2 = yes
oauthbearer = yes
}
oauth2 {
introspection_mode = post
introspection_url = %{oauth2_introspection_url}
#force_introspection = yes
username_attribute = username
}
# with local validation
#oauth2 {
# introspection_mode = local
# username_attribute = email
# oauth2_local_validation {
# dict fs {
# fs posix {
# prefix = /etc/dovecot/oauth2-keys/
# }
# }
# }
#}

View File

@@ -0,0 +1,195 @@
# Authentication for SQL users. Included from auth.conf.
#
# <https://doc.dovecot.org/latest/core/config/auth/databases/sql.html>
# For the sql passdb module, you'll need a database with a table that
# contains fields for at least the username and password. If you want to
# use the user@domain syntax, you might want to have a separate domain
# field as well.
#
# If your users all have the same uig/gid, and have predictable home
# directories, you can use the static userdb module to generate the home
# dir based on the username and domain. In this case, you won't need fields
# for home, uid, or gid in the database.
#
# If you prefer to use the sql userdb module, you'll want to add fields
# for home, uid, and gid. Here is an example table:
#
# CREATE TABLE users (
# username VARCHAR(128) NOT NULL,
# domain VARCHAR(128) NOT NULL,
# password VARCHAR(64) NOT NULL,
# home VARCHAR(255) NOT NULL,
# uid INTEGER NOT NULL,
# gid INTEGER NOT NULL,
# active CHAR(1) DEFAULT 'Y' NOT NULL
# );
# Database driver: mysql, pgsql, sqlite
sql_driver = %db_driver
# Database connection string. This is driver-specific setting.
#
# HA / round-robin load-balancing is supported by giving multiple host
# settings, like: host=sql1.host.org host=sql2.host.org
#
# pgsql:
# For available options, see the PostgreSQL documention for the
# PQconnectdb function of libpq.
# Use maxconns=n (default 5) to change how many connections Dovecot can
# create to pgsql.
#
# mysql:
# Basic options emulate PostgreSQL option names:
# host, port, user, password, dbname
#
# But also adds some new settings:
# client_flags - See MySQL manual
# ssl_ca, ssl_ca_path - Set either one or both to enable SSL
# ssl_cert, ssl_key - For sending client-side certificates to server
# ssl_cipher - Set minimum allowed cipher security (default: HIGH)
# option_file - Read options from the given file instead of
# the default my.cnf location
# option_group - Read options from the given group (default: client)
#
# You can connect to UNIX sockets by using host: host=/var/run/mysql.sock
# Note that currently you can't use spaces in parameters.
#
# sqlite:
# The path to the database file.
#
# Examples:
# mysql 192.168.1.1 {
# dbname = users
# }
# mysql sql.example.com {
# ssl = yes
# user = virtual
# password = blarg
# dbname = virtual
# }
# sqlite /etc/dovecot/authdb.sqlite {
# }
#
#mysql /var/run/mysqld/mysqld.sock {
# user = dovecot
# password = dvmail
# dbname = dovecot
#}
#mysql localhost {
# ...
#}
mysql %dbhost {
port = %dbport
dbname = %modoboa_dbname
user = %modoboa_dbuser
password = %modoboa_dbpassword
}
#passdb sql {
# default_password_scheme = SHA256
# passdb query to retrieve the password. It can return fields:
# password - The user's password. This field must be returned.
# user - user@domain from the database. Needed with case-insensitive lookups.
# username and domain - An alternative way to represent the "user" field.
#
# The "user" field is often necessary with case-insensitive lookups to avoid
# e.g. "name" and "nAme" logins creating two different mail directories. If
# your user and domain names are in separate fields, you can return "username"
# and "domain" fields instead of "user".
#
# The query can also return other fields which have a special meaning, see
# https://doc.dovecot.org/latest/core/config/auth/passdb.html#extra-fields
#
# Commonly used available substitutions (see https://doc.dovecot.org/latest/core/settings/variables.html
# for full list):
# %%{user} = entire user@domain
# %%{user|username} = user part of user@domain
# %%{user|domain} = domain part of user@domain
#
# Note that these can be used only as input to SQL query. If the query outputs
# any of these substitutions, they're not touched. Otherwise it would be
# difficult to have eg. usernames containing '%%' characters.
#
# Example:
# query = SELECT userid AS user, pw AS password \
# FROM users WHERE userid = '%%u' AND active = 'Y'
#
# query = \
# SELECT userid as username, domain, password \
# FROM users WHERE userid = '%%{user|username}' AND domain = '%%{user|domain}'
#}
passdb sql {
query = SELECT email AS user, password FROM core_user u INNER JOIN admin_mailbox mb ON u.id=mb.user_id INNER JOIN admin_domain dom ON mb.domain_id=dom.id WHERE (mb.is_send_only=0 OR '%%{protocol}' NOT IN ('imap', 'pop3')) AND u.email='%%{user}' AND u.is_active=1 AND dom.enabled=1
}
#userdb sql {
# userdb query to retrieve the user information. It can return fields:
# uid - System UID (overrides mail_uid setting)
# gid - System GID (overrides mail_gid setting)
# home - Home directory
# mail_driver - Mail driver
# mail_path - Mail storage path
#
# None of these are strictly required. If you use a single UID and GID, and
# home or mail directory fits to a template string, you could use userdb static
# instead. For a list of all fields that can be returned, see
# Examples:
# query = SELECT home, uid, gid FROM users WHERE userid = '%%{user}'
# query = SELECT dir AS home, user AS uid, group AS gid FROM users where userid = '%%{user}'
# query = SELECT home, 501 AS uid, 501 AS gid FROM users WHERE userid = '%%{user}'
#
# query = \
# SELECT home, uid, gid \
# FROM users WHERE userid = '%%{user|username}' AND domain = '%%{user|domain}'
# Query to get a list of all usernames.
# iterate_query = SELECT username AS user,domain FROM users
# userdb_ldap {
# iterate_fields {
# home = /var/vmail/%%{home}
# }
# }
#}
userdb sql {
query = SELECT '%{home_dir}/%%{user|domain}/%%{user|username}' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, CONCAT(mb.quota, 'M') AS quota_storage_size FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE (mb.is_send_only=0 OR '%%{protocol}' NOT IN ('imap', 'pop3', 'lmtp')) AND mb.address='%%{user|username}' AND dom.name='%%{user|domain}'
iterate_query = SELECT email AS user FROM core_user
}
#passdb static {
# fields {
# user=%%{user|username|lower}
# noauthenticate=yes
# }
## you can remove next line if you want to always normalize your usernames
# skip = authenticated
#}
# "prefetch" user database means that the passdb already provided the
# needed information and there's no need to do a separate userdb lookup.
# <https://doc.dovecot.org/latest/core/config/auth/databases/prefetch.html>
#userdb prefetch {
#}
#userdb static {
# fields {
# user=%%{user|lower}
# }
# you can remove next line if you want to always normalize your usernames
# skip = found
#}
# If you don't have any user-specific settings, you can avoid the user_query
# by using userdb static instead of userdb sql, for example:
# <https://doc.dovecot.org/latest/core/config/auth/databases/static.html>
#userdb static {
#fields {
# uid = vmail
# gid = vmail
# home = /var/vmail/%%{user}
#}
#}

View File

@@ -0,0 +1,195 @@
# Authentication for SQL users. Included from auth.conf.
#
# <https://doc.dovecot.org/latest/core/config/auth/databases/sql.html>
# For the sql passdb module, you'll need a database with a table that
# contains fields for at least the username and password. If you want to
# use the user@domain syntax, you might want to have a separate domain
# field as well.
#
# If your users all have the same uig/gid, and have predictable home
# directories, you can use the static userdb module to generate the home
# dir based on the username and domain. In this case, you won't need fields
# for home, uid, or gid in the database.
#
# If you prefer to use the sql userdb module, you'll want to add fields
# for home, uid, and gid. Here is an example table:
#
# CREATE TABLE users (
# username VARCHAR(128) NOT NULL,
# domain VARCHAR(128) NOT NULL,
# password VARCHAR(64) NOT NULL,
# home VARCHAR(255) NOT NULL,
# uid INTEGER NOT NULL,
# gid INTEGER NOT NULL,
# active CHAR(1) DEFAULT 'Y' NOT NULL
# );
# Database driver: mysql, pgsql, sqlite
sql_driver = %db_driver
# Database connection string. This is driver-specific setting.
#
# HA / round-robin load-balancing is supported by giving multiple host
# settings, like: host=sql1.host.org host=sql2.host.org
#
# pgsql:
# For available options, see the PostgreSQL documention for the
# PQconnectdb function of libpq.
# Use maxconns=n (default 5) to change how many connections Dovecot can
# create to pgsql.
#
# mysql:
# Basic options emulate PostgreSQL option names:
# host, port, user, password, dbname
#
# But also adds some new settings:
# client_flags - See MySQL manual
# ssl_ca, ssl_ca_path - Set either one or both to enable SSL
# ssl_cert, ssl_key - For sending client-side certificates to server
# ssl_cipher - Set minimum allowed cipher security (default: HIGH)
# option_file - Read options from the given file instead of
# the default my.cnf location
# option_group - Read options from the given group (default: client)
#
# You can connect to UNIX sockets by using host: host=/var/run/mysql.sock
# Note that currently you can't use spaces in parameters.
#
# sqlite:
# The path to the database file.
#
# Examples:
# mysql 192.168.1.1 {
# dbname = users
# }
# mysql sql.example.com {
# ssl = yes
# user = virtual
# password = blarg
# dbname = virtual
# }
# sqlite /etc/dovecot/authdb.sqlite {
# }
#
#mysql /var/run/mysqld/mysqld.sock {
# user = dovecot
# password = dvmail
# dbname = dovecot
#}
#mysql localhost {
# ...
#}
pgsql %dbhost {
parameters {
port = %dbport
dbname = %modoboa_dbname
user = %modoboa_dbuser
password = %modoboa_dbpassword
}
}
#passdb sql {
# default_password_scheme = SHA256
# passdb query to retrieve the password. It can return fields:
# password - The user's password. This field must be returned.
# user - user@domain from the database. Needed with case-insensitive lookups.
# username and domain - An alternative way to represent the "user" field.
#
# The "user" field is often necessary with case-insensitive lookups to avoid
# e.g. "name" and "nAme" logins creating two different mail directories. If
# your user and domain names are in separate fields, you can return "username"
# and "domain" fields instead of "user".
#
# The query can also return other fields which have a special meaning, see
# https://doc.dovecot.org/latest/core/config/auth/passdb.html#extra-fields
#
# Commonly used available substitutions (see https://doc.dovecot.org/latest/core/settings/variables.html
# for full list):
# %%{user} = entire user@domain
# %%{user|username} = user part of user@domain
# %%{user|domain} = domain part of user@domain
#
# Note that these can be used only as input to SQL query. If the query outputs
# any of these substitutions, they're not touched. Otherwise it would be
# difficult to have eg. usernames containing '%%' characters.
#
# Example:
# query = SELECT userid AS user, pw AS password \
# FROM users WHERE userid = '%%u' AND active = 'Y'
#
# query = \
# SELECT userid as username, domain, password \
# FROM users WHERE userid = '%%{user|username}' AND domain = '%%{user|domain}'
#}
passdb sql {
query = SELECT email AS user, password FROM core_user u INNER JOIN admin_mailbox mb ON u.id=mb.user_id INNER JOIN admin_domain dom ON mb.domain_id=dom.id WHERE (mb.is_send_only IS NOT TRUE OR '%%{protocol}' NOT IN ('imap', 'pop3')) AND email='%%{user}' AND is_active AND dom.enabled
}
#userdb sql {
# userdb query to retrieve the user information. It can return fields:
# uid - System UID (overrides mail_uid setting)
# gid - System GID (overrides mail_gid setting)
# home - Home directory
# mail_driver - Mail driver
# mail_path - Mail storage path
#
# None of these are strictly required. If you use a single UID and GID, and
# home or mail directory fits to a template string, you could use userdb static
# instead. For a list of all fields that can be returned, see
# Examples:
# query = SELECT home, uid, gid FROM users WHERE userid = '%%{user}'
# query = SELECT dir AS home, user AS uid, group AS gid FROM users where userid = '%%{user}'
# query = SELECT home, 501 AS uid, 501 AS gid FROM users WHERE userid = '%%{user}'
#
# query = \
# SELECT home, uid, gid \
# FROM users WHERE userid = '%%{user|username}' AND domain = '%%{user|domain}'
# Query to get a list of all usernames.
# iterate_query = SELECT username AS user,domain FROM users
# userdb_ldap {
# iterate_fields {
# home = /var/vmail/%%{home}
# }
# }
#}
userdb sql {
query = SELECT '%{home_dir}/%%{user|domain}/%%{user|username}' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, mb.quota || 'M' AS quota_storage_size FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE (mb.is_send_only IS NOT TRUE OR '%%{protocol}' NOT IN ('imap', 'pop3', 'lmtp')) AND mb.address='%%{user|username}' AND dom.name='%%{user|domain}'
iterate_query = SELECT email AS user FROM core_user
}
#passdb static {
# fields {
# user=%%{user|username|lower}
# noauthenticate=yes
# }
## you can remove next line if you want to always normalize your usernames
# skip = authenticated
#}
# "prefetch" user database means that the passdb already provided the
# needed information and there's no need to do a separate userdb lookup.
# <https://doc.dovecot.org/latest/core/config/auth/databases/prefetch.html>
#userdb prefetch {
#}
#userdb static {
# fields {
# user=%%{user|lower}
# }
# you can remove next line if you want to always normalize your usernames
# skip = found
#}
# If you don't have any user-specific settings, you can avoid the user_query
# by using userdb static instead of userdb sql, for example:
# <https://doc.dovecot.org/latest/core/config/auth/databases/static.html>
#userdb static {
#fields {
# uid = vmail
# gid = vmail
# home = /var/vmail/%%{user}
#}
#}

View File

@@ -0,0 +1,87 @@
## Dovecot configuration file
# If you're in a hurry, see https://doc.dovecot.org/latest/core/config/guides/quick.html
# "doveconf -n" command gives a clean output of the changed settings. Use it
# instead of copy&pasting files when posting to the Dovecot mailing list.
# '#' character and everything after it is treated as comments. Extra spaces
# and tabs are ignored. If you want to use either of these explicitly, put the
# value inside quotes, eg.: key = "# char and trailing whitespace "
# Default values are shown for each setting, it's not required to uncomment
# those. These are exceptions to this though: No sections (e.g. namespace {})
# or plugin settings are added by default, they're listed only as examples.
# Paths are also just examples with the real defaults being based on configure
# options. The paths listed here are for configure --prefix=/usr/local
# --sysconfdir=/usr/local/etc --localstatedir=/var
dovecot_config_version = 2.4.0
dovecot_storage_version = 2.4.0
# Protocols we want to be serving.
%protocols
!include_try /usr/share/dovecot/protocols.d/*.protocol
# A comma separated list of IPs or hosts where to listen in for connections.
# "*" listens in all IPv4 interfaces, "::" listens in all IPv6 interfaces.
# If you want to specify non-default ports or anything more complex,
# edit conf.d/master.conf.
#listen = *, ::
# Base directory where to store runtime data.
#base_dir = /var/run/dovecot/
# Name of this instance. In multi-instance setup doveadm and other commands
# can use -i <instance_name> to select which instance is used (an alternative
# to -c <config_path>). The instance name is also added to Dovecot processes
# in ps output.
#instance_name = dovecot
# Greeting message for clients.
#login_greeting = Dovecot ready.
# Space separated list of trusted network ranges. Connections from these
# IPs are allowed to override their IP addresses and ports (for logging and
# for authentication checks). disable_plaintext_auth is also ignored for
# these networks, unless ssl=required.
# Typically you'd specify your IMAP proxy servers here.
#login_trusted_networks =
# With proxy_maybe=yes if proxy destination matches any of these IPs, don't do
# proxying. This isn't necessary normally, but may be useful if the destination
# IP is e.g. a load balancer's IP.
#auth_proxy_self =
# Show more verbose process titles (in ps). Currently shows user name and
# IP address. Useful for seeing who are actually using the IMAP processes
# (eg. shared mailboxes or if same uid is used for multiple accounts).
#verbose_proctitle = yes
# Should all processes be killed when Dovecot master process shuts down.
# Setting this to "no" means that Dovecot can be upgraded without
# forcing existing client connections to close (although that could also be
# a problem if the upgrade is e.g. because of a security fix).
#shutdown_clients = yes
# If non-zero, run mail commands via this many connections to doveadm server,
# instead of running them directly in the same process.
#doveadm_worker_count = 0
# UNIX socket or host:port used for connecting to doveadm server
#doveadm_socket_path = doveadm-server
# Space separated list of environment variables that are preserved on Dovecot
# startup and passed down to all of its child processes. You can also give
# key=value pairs to always set specific settings.
#import_environment {
# TZ=%%{env:TZ}
#}
# Most of the actual configuration gets included below. The filenames are
# first sorted by their ASCII value and parsed in that order. The 00-prefixes
# in filenames are intended to make it easier to understand the ordering.
!include conf.d/*.conf
# A config file can also tried to be included without giving an error if
# it's not found:
!include_try local.conf

View File

@@ -1,18 +1,14 @@
upstream automx {
server unix:%uwsgi_socket_path fail_timeout=0;
}
server {
listen 80;
listen [::]:80;
server_name %hostname;
root /srv/automx/instance;
root %app_instance_path;
access_log /var/log/nginx/%{hostname}-access.log;
error_log /var/log/nginx/%{hostname}-error.log;
location /mail/config-v1.1.xml {
location ~ ^/(mail/config-v1.1.xml|mobileconfig) {
include uwsgi_params;
uwsgi_pass automx;
uwsgi_pass modoboa;
}
}

View File

@@ -92,7 +92,7 @@ oauth2_introspection_endpoint = %{oauth2_introspection_url}
# Rights backend
# Value: none | authenticated | owner_only | owner_write | from_file
type = from_file
type = from_file
# File for rights management from_file
file = %{config_dir}/rights
@@ -102,8 +102,6 @@ file = %{config_dir}/rights
# Storage backend
# Value: multifilesystem
type = radicale_storage_by_index
radicale_storage_by_index_fields = dtstart, dtend, uid, summary
# Folder for storing local collections, created if not present
filesystem_folder = %{home_dir}/collections
@@ -134,7 +132,7 @@ filesystem_folder = %{home_dir}/collections
# Web interface backend
# Value: none | internal
type = none
type = none
[logging]

View File

@@ -1,14 +0,0 @@
[uwsgi]
uid = %app_user
gid = %app_user
plugins = %uwsgi_plugin
home = %app_venv_path
chdir = %app_instance_path
module = automx_wsgi
master = true
vhost = true
harakiri = 60
processes = %nb_processes
socket = %uwsgi_socket_path
chmod-socket = 660
vacuum = true

View File

@@ -27,7 +27,7 @@ class Modoboa(base.Installer):
"build-essential", "python3-dev", "libxml2-dev", "libxslt-dev",
"libjpeg-dev", "librrd-dev", "rrdtool", "libffi-dev", "cron",
"libssl-dev", "redis-server", "supervisor", "pkg-config",
"libcairo2-dev"
"libcairo2-dev", "libmagic-dev"
],
"rpm": [
"gcc", "gcc-c++", "python3-devel", "libxml2-devel", "libxslt-devel",
@@ -50,6 +50,7 @@ class Modoboa(base.Installer):
self.extensions = self.config.get("modoboa", "extensions").split()
self.devmode = self.config.getboolean("modoboa", "devmode")
self.amavis_enabled = self.config.getboolean("amavis", "enabled")
self.rspamd_enabled = self.config.getboolean("rspamd", "enabled")
self.dovecot_enabled = self.config.getboolean("dovecot", "enabled")
self.opendkim_enabled = self.config.getboolean("opendkim", "enabled")
self.dkim_cron_enabled = False
@@ -70,7 +71,7 @@ class Modoboa(base.Installer):
def _setup_venv(self):
"""Prepare a dedicated virtualenv."""
python.setup_virtualenv(self.venv_path, sudo_user=self.user)
packages = ["rrdtool"]
packages = []
version = self.config.get("modoboa", "version")
extras = "postgresql"
if self.dbengine != "postgres":
@@ -134,6 +135,10 @@ class Modoboa(base.Installer):
prefix = ". {}; ".format(
os.path.join(self.venv_path, "bin", "activate"))
if self.amavis_enabled:
self.extensions += ["modoboa.amavis"]
if self.rspamd_enabled:
self.extensions += ["modoboa.rspamd"]
args = [
"--collectstatic",
"--timezone", self.config.get("modoboa", "timezone"),
@@ -235,7 +240,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.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 "#",
@@ -257,9 +262,6 @@ class Modoboa(base.Installer):
"handle_mailboxes": True,
"account_auto_removal": True
},
"modoboa_amavis": {
"am_pdp_mode": "inet",
},
"maillog": {
"rrd_rootdir": rrd_root_dir,
},
@@ -280,13 +282,17 @@ class Modoboa(base.Installer):
settings["admin"]["dkim_keys_storage_dir"] = (
self.config.get("opendkim", "keys_storage_dir"))
if self.config.getboolean("rspamd", "enabled"):
if self.rspamd_enabled:
settings["admin"]["dkim_keys_storage_dir"] = (
self.config.get("rspamd", "dkim_keys_storage_dir"))
settings["modoboa_rspamd"] = {
settings["rspamd"] = {
"key_map_path": self.config.get("rspamd", "key_map_path"),
"selector_map_path": self.config.get("rspamd", "selector_map_path")
}
if self.config.getboolean("amavis", "enabled"):
settings["amavis"] = {
"am_pdp_mode": "inet",
}
settings = json.dumps(settings)
query = (

View File

@@ -19,14 +19,15 @@ class Nginx(base.Installer):
"rpm": ["nginx"]
}
def get_template_context(self, app):
def get_template_context(self):
"""Additionnal variables."""
context = super().get_template_context()
context.update({
"app_instance_path": (
self.config.get(app, "instance_path")),
self.config.get("modoboa", "instance_path")),
"uwsgi_socket_path": (
Uwsgi(self.config, self.upgrade, self.restore).get_socket_path(app))
Uwsgi(self.config, self.upgrade, self.restore).get_socket_path("modoboa")
)
})
return context
@@ -34,9 +35,10 @@ class Nginx(base.Installer):
"""Custom app configuration."""
if hostname is None:
hostname = self.config.get("general", "hostname")
context = self.get_template_context(app)
context = self.get_template_context()
context.update({"hostname": hostname, "extra_config": extra_config})
src = self.get_file_path("{}.conf.tpl".format(app))
group = None
if package.backend.FORMAT == "deb":
dst = os.path.join(
self.config_dir, "sites-available", "{}.conf".format(hostname))
@@ -46,7 +48,8 @@ class Nginx(base.Installer):
if os.path.exists(link):
return
os.symlink(dst, link)
group = self.config.get(app, "user")
if self.config.has_section(app):
group = self.config.get(app, "user")
user = "www-data"
else:
dst = os.path.join(
@@ -54,25 +57,17 @@ class Nginx(base.Installer):
utils.copy_from_template(src, dst, context)
group = "uwsgi"
user = "nginx"
system.add_user_to_group(user, group)
if user and group:
system.add_user_to_group(user, group)
def post_run(self):
"""Additionnal tasks."""
extra_modoboa_config = ""
if self.config.getboolean("automx", "enabled"):
hostname = "autoconfig.{}".format(
self.config.get("general", "domain"))
self._setup_config("automx", hostname)
extra_modoboa_config = """
location ~* ^/autodiscover/autodiscover.xml {
include uwsgi_params;
uwsgi_pass automx;
}
location /mobileconfig {
include uwsgi_params;
uwsgi_pass automx;
}
"""
hostname = "autoconfig.{}".format(
self.config.get("general", "domain"))
self._setup_config("autoconfig", hostname)
if self.config.get("radicale", "enabled"):
extra_modoboa_config += """
location /radicale/ {

View File

@@ -20,8 +20,8 @@ class Postwhite(base.Installer):
]
no_daemon = True
packages = {
"deb": ["bind9-host"],
"rpm": ["bind-utils"]
"deb": ["bind9-host", "unzip"],
"rpm": ["bind-utils", "unzip"]
}
def install_from_archive(self, repository, target_dir):

View File

@@ -36,10 +36,6 @@ class Radicale(base.Installer):
"Radicale", "pytz", "radicale-modoboa-auth-oauth2"
]
python.install_packages(packages, self.venv_path, sudo_user=self.user)
python.install_package_from_repository(
"radicale-storage-by-index",
"https://github.com/tonioo/RadicaleStorageByIndex",
venv=self.venv_path, sudo_user=self.user)
def get_template_context(self):
"""Additional variables."""

View File

@@ -83,24 +83,9 @@ class Uwsgi(base.Installer):
utils.exec_cmd(
"perl -pi -e '{}' /etc/uwsgi.ini".format(pattern))
def _setup_automx_config(self):
"""Custom automx configuration."""
dst = self._setup_config("automx")
if package.backend.FORMAT == "deb":
self._enable_config_debian(dst)
else:
system.add_user_to_group(
"uwsgi", self.config.get("automx", "user"))
pattern = (
"s/emperor-tyrant = true/emperor-tyrant = false/")
utils.exec_cmd(
"perl -pi -e '{}' /etc/uwsgi.ini".format(pattern))
def post_run(self):
"""Additionnal tasks."""
self._setup_modoboa_config()
if self.config.getboolean("automx", "enabled"):
self._setup_automx_config()
def restart_daemon(self):
"""Restart daemon process."""

1
run.py
View File

@@ -22,7 +22,6 @@ from modoboa_installer import disclaimers
PRIMARY_APPS = [
"fail2ban",
"modoboa",
"automx",
"radicale",
"uwsgi",
"nginx",

View File

@@ -126,7 +126,7 @@ class ConfigFileTestCase(unittest.TestCase):
"example.test"])
self.assertTrue(os.path.exists(self.cfgfile))
self.assertIn(
"fail2ban modoboa automx amavis clamav dovecot nginx "
"fail2ban modoboa amavis clamav dovecot nginx "
"postfix postwhite spamassassin uwsgi radicale opendkim",
out.getvalue()
)

View File

@@ -1 +1 @@
53669b48de7ce85341a547ed2583380fcb06841b
1d701353d900f4b6e2f7ffba6f6b7a46d304f58b