mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-04-29 10:10:19 +00:00
Compare commits
5 Commits
38810e566b
...
7424564074
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7424564074 | ||
|
|
2cf88e5f53 | ||
|
|
38b3962bbc | ||
|
|
271603a435 | ||
|
|
cc0a02ad1c |
@ -108,14 +108,12 @@ class DropinConfigs(app_module.FollowerComponent):
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def get_target_path(path):
|
||||
def get_target_path(self, path):
|
||||
"""Return Path object for a target path."""
|
||||
target = pathlib.Path(DropinConfigs.ROOT)
|
||||
target /= DropinConfigs.DROPIN_CONFIG_ROOT.lstrip('/')
|
||||
target = pathlib.Path(self.ROOT)
|
||||
target /= self.DROPIN_CONFIG_ROOT.lstrip('/')
|
||||
return target / path.lstrip('/')
|
||||
|
||||
@staticmethod
|
||||
def get_etc_path(path):
|
||||
def get_etc_path(self, path):
|
||||
"""Return Path object for etc path."""
|
||||
return pathlib.Path(DropinConfigs.ROOT) / path.lstrip('/')
|
||||
return pathlib.Path(self.ROOT) / path.lstrip('/')
|
||||
|
||||
@ -8,9 +8,9 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-04-21 20:08-0400\n"
|
||||
"PO-Revision-Date: 2025-02-25 21:04+0000\n"
|
||||
"Last-Translator: 109247019824 <109247019824@users.noreply.hosted.weblate."
|
||||
"org>\n"
|
||||
"PO-Revision-Date: 2025-07-20 18:01+0000\n"
|
||||
"Last-Translator: 109247019824 <109247019824@users.noreply.hosted.weblate.org>"
|
||||
"\n"
|
||||
"Language-Team: Bulgarian <https://hosted.weblate.org/projects/freedombox/"
|
||||
"freedombox/bg/>\n"
|
||||
"Language: bg\n"
|
||||
@ -18,7 +18,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.10.2-dev\n"
|
||||
"X-Generator: Weblate 5.13-dev\n"
|
||||
|
||||
#: config.py:103
|
||||
#, python-brace-format
|
||||
@ -199,7 +199,7 @@ msgstr "Домейн в местната мрежа"
|
||||
|
||||
#: modules/avahi/manifest.py:14
|
||||
msgid "Auto-discovery"
|
||||
msgstr ""
|
||||
msgstr "Автоматично откриване"
|
||||
|
||||
#: modules/avahi/manifest.py:14 modules/backups/manifest.py:17
|
||||
msgid "Local"
|
||||
@ -207,7 +207,7 @@ msgstr "Местно"
|
||||
|
||||
#: modules/avahi/manifest.py:14
|
||||
msgid "mDNS"
|
||||
msgstr ""
|
||||
msgstr "mDNS"
|
||||
|
||||
#: modules/backups/__init__.py:24
|
||||
msgid "Backups allows creating and managing backup archives."
|
||||
@ -8203,10 +8203,8 @@ msgstr ""
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade-notification.html:9
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade.html:11
|
||||
#: modules/upgrades/templates/upgrades_configure.html:16
|
||||
#, fuzzy
|
||||
#| msgid "Distribution update started"
|
||||
msgid "Distribution Update"
|
||||
msgstr "Започнато е обновяване на дистрибуцията"
|
||||
msgstr "Начало на бновяване на дистрибуцията"
|
||||
|
||||
#: modules/upgrades/__init__.py:396
|
||||
msgid "Check for package holds"
|
||||
@ -8267,10 +8265,8 @@ msgid "Next"
|
||||
msgstr "Напред"
|
||||
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade-confirm.html:11
|
||||
#, fuzzy
|
||||
#| msgid "Could not start distribution update"
|
||||
msgid "Confirm Distribution Update?"
|
||||
msgstr "Обновяването на дистрибуцията не може да бъде стартирано"
|
||||
msgstr "Потвърждавате ли бновяване на дистрибуцията?"
|
||||
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade-confirm.html:21
|
||||
#, python-format
|
||||
@ -8303,10 +8299,8 @@ msgid "If the process is interrupted, you should be able to continue it."
|
||||
msgstr ""
|
||||
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade-confirm.html:66
|
||||
#, fuzzy
|
||||
#| msgid "Could not start distribution update"
|
||||
msgid "Confirm & Start Distribution Update"
|
||||
msgstr "Обновяването на дистрибуцията не може да бъде стартирано"
|
||||
msgstr "Потвърждаване и обновяване на дистрибуцията"
|
||||
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade-notification.html:15
|
||||
msgid ""
|
||||
@ -8336,10 +8330,8 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade-notification.html:42
|
||||
#, fuzzy
|
||||
#| msgid "Test Distribution Upgrade"
|
||||
msgid "Go to Distribution Update"
|
||||
msgstr "Надграждане на дистрибуцията до тестова"
|
||||
msgstr "Към обновяване на дистрибуцията"
|
||||
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade-notification.html:46
|
||||
#: modules/upgrades/templates/upgrades-new-release.html:22
|
||||
@ -8368,16 +8360,12 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade.html:50
|
||||
#, fuzzy
|
||||
#| msgid "Frequent feature updates are activated."
|
||||
msgid "Automatic updates are disabled."
|
||||
msgstr "Честото обновяване на пакети е включено."
|
||||
msgstr "Автоматичното обновяване е изключено."
|
||||
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade.html:54
|
||||
#, fuzzy
|
||||
#| msgid "Distribution update started"
|
||||
msgid "Distribution upgrades are disabled."
|
||||
msgstr "Започнато е обновяване на дистрибуцията"
|
||||
msgstr "Обновяванията на дистрибуцията са изключени."
|
||||
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade.html:58
|
||||
msgid ""
|
||||
@ -8390,10 +8378,8 @@ msgid "Your current distribution is mixed or not understood."
|
||||
msgstr ""
|
||||
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade.html:72
|
||||
#, fuzzy
|
||||
#| msgid "Test Distribution Upgrade"
|
||||
msgid "Current Distribution:"
|
||||
msgstr "Надграждане на дистрибуцията до тестова"
|
||||
msgstr "Текуща дистрибуция:"
|
||||
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade.html:74
|
||||
msgid "Unknown or mixed"
|
||||
@ -8409,10 +8395,8 @@ msgid "Released: %(date)s."
|
||||
msgstr ""
|
||||
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade.html:91
|
||||
#, fuzzy
|
||||
#| msgid "Test Distribution Upgrade"
|
||||
msgid "Next Stable Distribution:"
|
||||
msgstr "Надграждане на дистрибуцията до тестова"
|
||||
msgstr "Следваща стабилна дистрибуция:"
|
||||
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade.html:93
|
||||
msgid "Unknown"
|
||||
@ -8467,22 +8451,16 @@ msgstr ""
|
||||
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade.html:157
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade.html:172
|
||||
#, fuzzy
|
||||
#| msgid "Test Distribution Upgrade"
|
||||
msgid "Start Distribution Update"
|
||||
msgstr "Надграждане на дистрибуцията до тестова"
|
||||
msgstr "Начало на обновяване на дистрибуцията"
|
||||
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade.html:162
|
||||
#, fuzzy
|
||||
#| msgid "Test Distribution Upgrade"
|
||||
msgid "Continue Distribution Update"
|
||||
msgstr "Надграждане на дистрибуцията до тестова"
|
||||
msgstr "Продължаване обновяването на дистрибуцията"
|
||||
|
||||
#: modules/upgrades/templates/upgrades-dist-upgrade.html:167
|
||||
#, fuzzy
|
||||
#| msgid "Starting distribution upgrade test."
|
||||
msgid "Start Distribution Update (for testing)"
|
||||
msgstr "Начало на опит за обновяване на дистрибуцията."
|
||||
msgstr "Начало на обновяване на дистрибуцията (за проба)"
|
||||
|
||||
#: modules/upgrades/templates/upgrades-new-release.html:9
|
||||
#, python-format
|
||||
@ -8562,10 +8540,8 @@ msgid "Error when configuring unattended-upgrades"
|
||||
msgstr "Грешка при настройка на unattended-upgrades"
|
||||
|
||||
#: modules/upgrades/views.py:117
|
||||
#, fuzzy
|
||||
#| msgid "Starting distribution upgrade test."
|
||||
msgid "Started distribution update."
|
||||
msgstr "Начало на опит за обновяване на дистрибуцията."
|
||||
msgstr "Обновяване на дистрибуцията е започнато."
|
||||
|
||||
#: modules/upgrades/views.py:153
|
||||
msgid "Upgrade process started."
|
||||
|
||||
@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-04-21 20:08-0400\n"
|
||||
"PO-Revision-Date: 2025-06-19 22:01+0000\n"
|
||||
"Last-Translator: Priit Jõerüüt <hwlate@joeruut.com>\n"
|
||||
"PO-Revision-Date: 2025-07-20 18:01+0000\n"
|
||||
"Last-Translator: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>\n"
|
||||
"Language-Team: Estonian <https://hosted.weblate.org/projects/freedombox/"
|
||||
"freedombox/et/>\n"
|
||||
"Language: et\n"
|
||||
@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.12.1\n"
|
||||
"X-Generator: Weblate 5.13-dev\n"
|
||||
|
||||
#: config.py:103
|
||||
#, python-brace-format
|
||||
@ -921,7 +921,7 @@ msgstr ""
|
||||
#: modules/miniflux/forms.py:14 modules/networks/forms.py:282
|
||||
#: modules/shadowsocks/forms.py:32 modules/shadowsocksserver/forms.py:37
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
msgstr "Salasõna"
|
||||
|
||||
#: modules/bepasty/views.py:19
|
||||
msgid "admin"
|
||||
|
||||
@ -20,7 +20,7 @@ from plinth.privileged import service as service_privileged
|
||||
from plinth.signals import domain_added, domain_removed
|
||||
from plinth.utils import format_lazy, gettext_noop
|
||||
|
||||
from . import aliases, manifest, privileged
|
||||
from . import aliases, dovecot, manifest, privileged
|
||||
|
||||
_description = [
|
||||
_('This is a complete email server solution using Postfix, Dovecot, '
|
||||
@ -52,7 +52,7 @@ class EmailApp(plinth.app.App):
|
||||
|
||||
app_id = 'email'
|
||||
|
||||
_version = 6
|
||||
_version = 7
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the email app."""
|
||||
@ -95,21 +95,12 @@ class EmailApp(plinth.app.App):
|
||||
'dovecot-lmtpd', 'dovecot-managesieved', 'dovecot-ldap',
|
||||
'rspamd', 'redis-server', 'openssl'
|
||||
], conflicts=['exim4-base', 'exim4-config', 'exim4-daemon-light'],
|
||||
conflicts_action=Packages.ConflictsAction.REMOVE)
|
||||
conflicts_action=Packages.ConflictsAction.REMOVE,
|
||||
rerun_setup_on_upgrade=True)
|
||||
self.add(packages)
|
||||
|
||||
dropin_configs = DropinConfigs('dropin-configs-email', [
|
||||
'/etc/apache2/conf-available/email-freedombox.conf',
|
||||
'/etc/dovecot/conf.d/05-freedombox-passdb.conf',
|
||||
'/etc/dovecot/conf.d/05-freedombox-userdb.conf',
|
||||
'/etc/dovecot/conf.d/15-freedombox-auth.conf',
|
||||
'/etc/dovecot/conf.d/15-freedombox-mail.conf',
|
||||
'/etc/dovecot/conf.d/90-freedombox-imap.conf',
|
||||
'/etc/dovecot/conf.d/90-freedombox-lmtp.conf',
|
||||
'/etc/dovecot/conf.d/90-freedombox-mailboxes.conf',
|
||||
'/etc/dovecot/conf.d/90-freedombox-master.conf',
|
||||
'/etc/dovecot/conf.d/90-freedombox-tls.conf',
|
||||
'/etc/dovecot/conf.d/freedombox-ldap.conf.ext',
|
||||
'/etc/fail2ban/jail.d/dovecot-freedombox.conf',
|
||||
'/etc/postfix/freedombox-aliases.cf',
|
||||
'/etc/rspamd/local.d/freedombox-logging.inc',
|
||||
@ -121,10 +112,24 @@ class EmailApp(plinth.app.App):
|
||||
dropin_configs_sieve = DropinConfigs('dropin-configs-email-sieve', [
|
||||
'/etc/dovecot/freedombox-sieve/learn-ham.sieve',
|
||||
'/etc/dovecot/freedombox-sieve/learn-spam.sieve',
|
||||
'/etc/dovecot/freedombox-sieve-after/sort-spam.sieve',
|
||||
'/etc/dovecot/conf.d/95-freedombox-sieve.conf'
|
||||
'/etc/dovecot/freedombox-sieve-after/sort-spam.sieve'
|
||||
])
|
||||
self.add(dropin_configs_sieve)
|
||||
dropin_configs_dovecot = DovecotDropinConfigs(
|
||||
'dropin-configs-email-dovecot', [
|
||||
'/etc/dovecot/conf.d/05-freedombox-passdb.conf',
|
||||
'/etc/dovecot/conf.d/05-freedombox-userdb.conf',
|
||||
'/etc/dovecot/conf.d/15-freedombox-auth.conf',
|
||||
'/etc/dovecot/conf.d/15-freedombox-mail.conf',
|
||||
'/etc/dovecot/conf.d/90-freedombox-imap.conf',
|
||||
'/etc/dovecot/conf.d/90-freedombox-lmtp.conf',
|
||||
'/etc/dovecot/conf.d/90-freedombox-mailboxes.conf',
|
||||
'/etc/dovecot/conf.d/90-freedombox-master.conf',
|
||||
'/etc/dovecot/conf.d/90-freedombox-tls.conf',
|
||||
'/etc/dovecot/conf.d/95-freedombox-sieve.conf',
|
||||
'/etc/dovecot/conf.d/freedombox-ldap.conf.ext'
|
||||
])
|
||||
self.add(dropin_configs_dovecot)
|
||||
|
||||
listen_ports = [(25, 'tcp4'), (25, 'tcp6'), (465, 'tcp4'),
|
||||
(465, 'tcp6'), (587, 'tcp4'), (587, 'tcp6')]
|
||||
@ -212,13 +217,15 @@ class EmailApp(plinth.app.App):
|
||||
# Enable drop-in configuration files component for sieve (temporarily)
|
||||
# to ensure that sievec can compile.
|
||||
self.get_component('dropin-configs-email-sieve').enable()
|
||||
self.get_component('dropin-configs-email-dovecot').enable()
|
||||
service_privileged.try_restart('dovecot')
|
||||
privileged.setup_spam()
|
||||
|
||||
# Restart daemons
|
||||
service_privileged.try_restart('postfix')
|
||||
service_privileged.try_restart('dovecot')
|
||||
service_privileged.try_restart('rspamd')
|
||||
if self.is_enabled():
|
||||
service_privileged.restart('postfix')
|
||||
service_privileged.restart('dovecot')
|
||||
service_privileged.restart('rspamd')
|
||||
|
||||
# Expose to public internet
|
||||
if old_version == 0:
|
||||
@ -228,6 +235,20 @@ class EmailApp(plinth.app.App):
|
||||
service_privileged.try_restart('rspamd')
|
||||
|
||||
|
||||
class DovecotDropinConfigs(DropinConfigs):
|
||||
"""Configure dovecot based on its package version."""
|
||||
|
||||
def get_target_path(self, path):
|
||||
"""Return Path object for a target path."""
|
||||
version = '2.3'
|
||||
if dovecot.is_version_24():
|
||||
version = '2.4'
|
||||
|
||||
target_path = super().get_target_path(path)
|
||||
target_path = target_path.parent / version / target_path.name
|
||||
return target_path
|
||||
|
||||
|
||||
def _get_first_admin():
|
||||
"""Return an admin user in the system or None if non exist."""
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
# Do not edit this file. Manage your settings on FreedomBox.
|
||||
|
||||
# See:
|
||||
# https://doc.dovecot.org/main/core/config/auth/passdb.html
|
||||
# https://doc.dovecot.org/main/howto/active_directory.html
|
||||
#
|
||||
# For passdb, the passwd driver looks up using NSS. In FreedomBox, NSS is
|
||||
# configured to lookup LDAP with the help of libnss-ldapd. Lookup using passdb
|
||||
# would have been sufficient if FreedomBox allowed all its users to login using
|
||||
# pam. However, by default, FreedomBox disallows all users but 'admin' group to
|
||||
# login. Hence, the need for LDAP lookup.
|
||||
#
|
||||
passdb freedombox-ldap {
|
||||
driver = ldap
|
||||
ldap_uris = ldapi:///
|
||||
ldap_base = dc=thisbox
|
||||
ldap_bind = yes
|
||||
ldap_bind_userdn = uid=%{user},ou=users,dc=thisbox
|
||||
ldap_filter = (&(objectClass=posixAccount)(uid=%{user}))
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
# Do not edit this file. Manage your settings on FreedomBox.
|
||||
|
||||
# See:
|
||||
# https://doc.dovecot.org/main/core/config/auth/userdb.html
|
||||
#
|
||||
# Users in FreedomBox are not expected to access mail by logging into the
|
||||
# system. Storing the mail in single location instead of home directories and
|
||||
# with single UID/GID simplifies security reasoning and backup/restore
|
||||
# operations.
|
||||
#
|
||||
# When FreedomBox has multiple domains a user is expected to get a mailbox that
|
||||
# is same across the domains. Changing an domain name is not uncommon in
|
||||
# FreedomBox. So, authenticate and store mails based on username only instead of
|
||||
# including domain names in storage path.
|
||||
#
|
||||
# Directories are created under /var/mail as necessary by dovecot. Permissions
|
||||
# for newly created directories are inherited from parent directory. FreedomBox
|
||||
# will remove all permissions for 'others' from /var/mail to ensure that mail is
|
||||
# not read by non-root users.
|
||||
#
|
||||
# userdb provides lookup for three parameters after authentication of a user.
|
||||
# These parameters are uid, gid, and home directory of the user. If these do not
|
||||
# change from user to user, a 'static' database type with fixed values is
|
||||
# sufficient as userdb.
|
||||
userdb freedombox-static {
|
||||
driver = static
|
||||
fields {
|
||||
uid=mail
|
||||
gid=mail
|
||||
home=/var/mail/%{user | username | lower}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
# Do not edit this file. Manage your settings on FreedomBox.
|
||||
|
||||
# See:
|
||||
# https://doc.dovecot.org/main/core/config/auth/basic.html
|
||||
# https://doc.dovecot.org/main/core/config/auth/databases/ldap.html#username
|
||||
|
||||
# Outlook and Windows Mail work only with LOGIN mechanism, not the standard PLAIN
|
||||
auth_mechanisms = plain login
|
||||
|
||||
auth_username_format = %{user | lower}
|
||||
@ -0,0 +1,18 @@
|
||||
# Do not edit this file. Manage your settings on FreedomBox.
|
||||
|
||||
# See: https://doc.dovecot.org/main/core/config/mail_location.html
|
||||
|
||||
# Use sdbox, a format specific to dovecot, for storing mails. The format allows
|
||||
# better performance with some IMAP queries. When this is combined with Full
|
||||
# Text Search (FTS), users will get optimal web and desktop mail experience.
|
||||
# Don't pick mdbox format because is requires regular expunge maintenance. We
|
||||
# have enabled btrfs filesystem compression by default.
|
||||
mail_driver = sdbox
|
||||
mail_path = ~/mail
|
||||
|
||||
# We try to deliver all mail using a single UID 'mail' and a single GID 'mail'.
|
||||
# In Debian, UID of mail user is 8 and GID of mail user is 8 as set in
|
||||
# /usr/share/base-passwd/{passwd|group}.master. By default first valid UID in
|
||||
# dovecot is 500.
|
||||
first_valid_uid = 8
|
||||
last_valid_uid = 8
|
||||
@ -0,0 +1,11 @@
|
||||
# Do not edit this file. Manage your settings on FreedomBox.
|
||||
|
||||
# Make rspamd learn spam/ham when the user marks mails as junk or not junk.
|
||||
# https://doc.dovecot.org/main/core/config/sieve/overview.html
|
||||
# https://doc.dovecot.org/main/core/plugins/sieve.html
|
||||
|
||||
protocol imap {
|
||||
mail_plugins {
|
||||
imap_sieve = yes
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
# Do not edit this file. Manage your settings on FreedomBox.
|
||||
|
||||
# See:
|
||||
# https://doc.dovecot.org/main/core/config/sieve/overview.html
|
||||
# https://doc.dovecot.org/main/core/plugins/sieve.html
|
||||
|
||||
# Enable the sieve plugin to sort mail during delivery using sieve scripts.
|
||||
protocol lmtp {
|
||||
mail_plugins {
|
||||
sieve = yes
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
# Do not edit this file. Manage your settings on FreedomBox.
|
||||
|
||||
# Mark various mailboxes with special use flags (RFC 6154). Various names used
|
||||
# in mail clients for mailboxes: https://www.imapwiki.org/SpecialUse
|
||||
# See:
|
||||
# https://doc.dovecot.org/main/core/config/mail_location.html#custom-namespace-location
|
||||
|
||||
namespace inbox {
|
||||
# Archive
|
||||
mailbox Archive {
|
||||
auto = subscribe
|
||||
special_use = \Archive
|
||||
}
|
||||
mailbox Archives { # Thunderbird
|
||||
auto = no
|
||||
special_use = \Archive
|
||||
}
|
||||
|
||||
# Drafts
|
||||
mailbox Drafts {
|
||||
auto = subscribe
|
||||
special_use = \Drafts
|
||||
}
|
||||
|
||||
# Sent
|
||||
mailbox Sent {
|
||||
auto = subscribe
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Sent Items" { # Outlook 2010/2013
|
||||
auto = no
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Sent Messages" { # iOS
|
||||
auto = no
|
||||
special_use = \Sent
|
||||
}
|
||||
|
||||
# Junk
|
||||
mailbox Junk {
|
||||
auto = subscribe
|
||||
autoexpunge = 60d
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox Spam { # KMail, K-9 Mail
|
||||
auto = no
|
||||
autoexpunge = 60d
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "Junk E-mail" { # Outlook 2010
|
||||
auto = no
|
||||
autoexpunge = 60d
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox INBOX.Junk {
|
||||
auto = no
|
||||
autoexpunge = 60d
|
||||
special_use = \Junk
|
||||
}
|
||||
|
||||
# Trash
|
||||
mailbox Trash {
|
||||
auto = subscribe
|
||||
autoexpunge = 60d
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox INBOX.Trash {
|
||||
auto = no
|
||||
autoexpunge = 60d
|
||||
special_use = \Trash
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
# Do not edit this file. Manage your settings on FreedomBox.
|
||||
|
||||
# Listen on Unix domain sockets for postfix to use dovecot SASL authentication
|
||||
# and for postfix to deliver mail using dovecot to local mailboxes. See:
|
||||
# https://doc.dovecot.org/main/howto/sasl/postfix.html#postfix-and-dovecot-sasl
|
||||
|
||||
service auth {
|
||||
unix_listener /var/spool/postfix/private/auth {
|
||||
mode = 0600
|
||||
user = postfix
|
||||
group = postfix
|
||||
}
|
||||
}
|
||||
|
||||
service lmtp {
|
||||
unix_listener /var/spool/postfix/private/dovecot-lmtp {
|
||||
mode = 0600
|
||||
user = postfix
|
||||
group = postfix
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
# Do not edit this file. Manage your settings on FreedomBox.
|
||||
|
||||
# Mozilla Guideline v5.7, Dovecot 2.3.21, OpenSSL 3.4.0, intermediate.
|
||||
# Generated 2025-07-16: https://ssl-config.mozilla.org/
|
||||
# See: https://doc.dovecot.org/main/core/config/ssl.html
|
||||
ssl = required
|
||||
|
||||
ssl_min_protocol = TLSv1.2
|
||||
ssl_server_prefer_ciphers = client
|
||||
|
||||
ssl_curve_list = X25519:prime256v1:secp384r1
|
||||
ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305
|
||||
@ -0,0 +1,43 @@
|
||||
# Do not edit this file. Manage your settings on FreedomBox.
|
||||
|
||||
# Default sieve scripts applied for delivery to all users. To move mail to Junk
|
||||
# folder based on classification headers set by rspamd. See:
|
||||
# https://doc.dovecot.org/main/core/plugins/sieve.html
|
||||
|
||||
sieve_script freedombox-after {
|
||||
type = after
|
||||
driver = file
|
||||
path = /etc/dovecot/freedombox-sieve-after
|
||||
}
|
||||
|
||||
sieve_plugins {
|
||||
sieve_imapsieve = yes
|
||||
sieve_extprograms = yes
|
||||
}
|
||||
|
||||
sieve_global_extensions {
|
||||
vnd.dovecot.pipe = yes
|
||||
vnd.dovecot.environment = yes
|
||||
}
|
||||
|
||||
# Make rspamd learn spam/ham when the user marks mails as junk or not junk.
|
||||
# https://doc.dovecot.org/main/core/config/spam_reporting.html
|
||||
sieve_pipe_bin_dir = /usr/bin
|
||||
|
||||
# When moving a mail from to Junk folder from elsewhere
|
||||
mailbox Junk {
|
||||
sieve_script learn-spam {
|
||||
type = before
|
||||
cause = copy
|
||||
path = /etc/dovecot/freedombox-sieve/learn-spam.sieve
|
||||
}
|
||||
}
|
||||
|
||||
# When moving a mail from from Junk folder to elsewhere
|
||||
imapsieve_from Junk {
|
||||
sieve_script learn-ham {
|
||||
type = before
|
||||
cause = copy
|
||||
path = /etc/dovecot/freedombox-sieve/learn-ham.sieve
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
# Do not edit this file. Manage your settings on FreedomBox.
|
||||
|
||||
# This file is not needed for Dovecot >= 2.4. It is only needed for simplifying
|
||||
# compatibility with Dovecot 2.3.
|
||||
17
plinth/modules/email/dovecot.py
Normal file
17
plinth/modules/email/dovecot.py
Normal file
@ -0,0 +1,17 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Utilities to configure Dovecot."""
|
||||
|
||||
import apt
|
||||
|
||||
from plinth.utils import Version
|
||||
|
||||
|
||||
def is_version_24():
|
||||
"""Return the currently installed version of Dovecot."""
|
||||
cache = apt.Cache()
|
||||
try:
|
||||
version = cache['dovecot-core'].installed.version
|
||||
except KeyError:
|
||||
return True
|
||||
|
||||
return Version(version) >= Version('1:2.4')
|
||||
@ -2,8 +2,7 @@
|
||||
"""Provides privileged actions that run as root."""
|
||||
|
||||
from .aliases import setup_aliases
|
||||
from .dkim import (get_dkim_public_key, setup_dkim,
|
||||
fix_incorrect_key_ownership)
|
||||
from .dkim import fix_incorrect_key_ownership, get_dkim_public_key, setup_dkim
|
||||
from .domain import set_domains
|
||||
from .home import setup_home
|
||||
from .postfix import setup_postfix
|
||||
|
||||
@ -10,6 +10,7 @@ See: https://doc.dovecot.org/configuration_manual/dovecot_ssl_configuration/
|
||||
import pathlib
|
||||
|
||||
from .. import postfix
|
||||
from ..dovecot import is_version_24
|
||||
|
||||
# Mozilla Guideline v5.6, Postfix 1.17.7, OpenSSL 1.1.1d, intermediate
|
||||
# Generated 2021-08
|
||||
@ -68,15 +69,27 @@ def set_postfix_config(primary_domain, all_domains):
|
||||
|
||||
def set_dovecot_config(primary_domain, all_domains):
|
||||
"""Set dovecot configuration for TLS certificates."""
|
||||
is_new_version = is_version_24()
|
||||
|
||||
# Determine whether to prefix file paths with '<' based on version
|
||||
prefix = ''
|
||||
cert_naming = 'ssl_server_cert_file'
|
||||
key_naming = 'ssl_server_key_file'
|
||||
if not is_new_version:
|
||||
prefix = '<'
|
||||
cert_naming = 'ssl_cert'
|
||||
key_naming = 'ssl_key'
|
||||
|
||||
content = f'''# This file is managed by FreedomBox
|
||||
ssl_cert = </etc/dovecot/letsencrypt/{primary_domain}/cert.pem
|
||||
ssl_key = </etc/dovecot/letsencrypt/{primary_domain}/privkey.pem
|
||||
{cert_naming} = {prefix}/etc/dovecot/letsencrypt/{primary_domain}/cert.pem
|
||||
{key_naming} = {prefix}/etc/dovecot/letsencrypt/{primary_domain}/privkey.pem
|
||||
'''
|
||||
|
||||
for domain in all_domains:
|
||||
content += f'''
|
||||
local_name {domain} {{
|
||||
ssl_cert = </etc/dovecot/letsencrypt/{domain}/cert.pem
|
||||
ssl_key = </etc/dovecot/letsencrypt/{domain}/privkey.pem
|
||||
{cert_naming} = {prefix}/etc/dovecot/letsencrypt/{domain}/cert.pem
|
||||
{key_naming} = {prefix}/etc/dovecot/letsencrypt/{domain}/privkey.pem
|
||||
}}
|
||||
'''
|
||||
cert_config = pathlib.Path('/etc/dovecot/conf.d/91-freedombox-tls.conf')
|
||||
|
||||
@ -10,7 +10,7 @@ from plinth import module_loader
|
||||
from plinth.actions import privileged
|
||||
|
||||
|
||||
def _assert_managed_dropin_config(app_id: str, path: str):
|
||||
def _get_managed_dropin_config(app_id: str, path: str):
|
||||
"""Check that this is a path managed by the specified app."""
|
||||
module_path = module_loader.get_module_import_path(app_id)
|
||||
module = importlib.import_module(module_path)
|
||||
@ -25,7 +25,7 @@ def _assert_managed_dropin_config(app_id: str, path: str):
|
||||
components = app.get_components_of_type(DropinConfigs)
|
||||
for component in components:
|
||||
if path in component.etc_paths:
|
||||
return
|
||||
return component
|
||||
|
||||
raise AssertionError('Not a managed drop-in config')
|
||||
|
||||
@ -37,10 +37,9 @@ def dropin_is_valid(app_id: str, path: str, copy_only: bool,
|
||||
|
||||
Optionally, drop the link if it is invalid.
|
||||
"""
|
||||
_assert_managed_dropin_config(app_id, path)
|
||||
from plinth.config import DropinConfigs
|
||||
etc_path = DropinConfigs.get_etc_path(path)
|
||||
target = DropinConfigs.get_target_path(path)
|
||||
component = _get_managed_dropin_config(app_id, path)
|
||||
etc_path = component.get_etc_path(path)
|
||||
target = component.get_target_path(path)
|
||||
if etc_path.exists() or etc_path.is_symlink():
|
||||
if (not copy_only and etc_path.is_symlink()
|
||||
and etc_path.readlink() == target):
|
||||
@ -59,10 +58,9 @@ def dropin_is_valid(app_id: str, path: str, copy_only: bool,
|
||||
@privileged
|
||||
def dropin_link(app_id: str, path: str, copy_only: bool):
|
||||
"""Create a symlink from /etc/ to /usr/share/freedombox/etc."""
|
||||
_assert_managed_dropin_config(app_id, path)
|
||||
from plinth.config import DropinConfigs
|
||||
target = DropinConfigs.get_target_path(path)
|
||||
etc_path = DropinConfigs.get_etc_path(path)
|
||||
component = _get_managed_dropin_config(app_id, path)
|
||||
target = component.get_target_path(path)
|
||||
etc_path = component.get_etc_path(path)
|
||||
etc_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if copy_only:
|
||||
shutil.copyfile(target, etc_path)
|
||||
@ -73,7 +71,6 @@ def dropin_link(app_id: str, path: str, copy_only: bool):
|
||||
@privileged
|
||||
def dropin_unlink(app_id: str, path: str, missing_ok: bool = False):
|
||||
"""Remove a symlink in /etc/."""
|
||||
_assert_managed_dropin_config(app_id, path)
|
||||
from plinth.config import DropinConfigs
|
||||
etc_path = DropinConfigs.get_etc_path(path)
|
||||
component = _get_managed_dropin_config(app_id, path)
|
||||
etc_path = component.get_etc_path(path)
|
||||
etc_path.unlink(missing_ok=missing_ok)
|
||||
|
||||
@ -31,9 +31,10 @@ def fixture_dropin_configs():
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def fixture_assert_dropin_config():
|
||||
def fixture_assert_dropin_config(dropin_configs):
|
||||
"""Mock asserting dropin config path."""
|
||||
with patch('plinth.privileged.config._assert_managed_dropin_config'):
|
||||
with patch('plinth.privileged.config._get_managed_dropin_config') as mock:
|
||||
mock.return_value = dropin_configs
|
||||
yield
|
||||
|
||||
|
||||
@ -95,7 +96,7 @@ def test_dropin_configs_enable_disable_symlinks(dropin_configs, tmp_path):
|
||||
|
||||
# Enable when a file already exists
|
||||
dropin_configs.disable()
|
||||
etc_path = DropinConfigs.get_etc_path('/etc/test/path1')
|
||||
etc_path = dropin_configs.get_etc_path('/etc/test/path1')
|
||||
etc_path.touch()
|
||||
dropin_configs.enable()
|
||||
_assert_symlinks(dropin_configs, tmp_path, should_exist=True)
|
||||
@ -108,7 +109,7 @@ def test_dropin_configs_enable_disable_symlinks(dropin_configs, tmp_path):
|
||||
|
||||
# When symlink already exists to correct location
|
||||
dropin_configs.disable()
|
||||
target_path = DropinConfigs.get_target_path('/etc/test/path1')
|
||||
target_path = dropin_configs.get_target_path('/etc/test/path1')
|
||||
etc_path.symlink_to(target_path)
|
||||
dropin_configs.enable()
|
||||
_assert_symlinks(dropin_configs, tmp_path, should_exist=True)
|
||||
@ -119,7 +120,7 @@ def test_dropin_configs_enable_disable_copy_only(dropin_configs, tmp_path):
|
||||
with patch('plinth.config.DropinConfigs.ROOT', new=tmp_path):
|
||||
dropin_configs.copy_only = True
|
||||
for path in ['/etc/test/path1', '/etc/path2']:
|
||||
target = DropinConfigs.get_target_path(path)
|
||||
target = dropin_configs.get_target_path(path)
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text('test-config-content')
|
||||
|
||||
@ -135,7 +136,7 @@ def test_dropin_configs_enable_disable_copy_only(dropin_configs, tmp_path):
|
||||
|
||||
# Enable when a file already exists with wrong content
|
||||
dropin_configs.disable()
|
||||
etc_path = DropinConfigs.get_etc_path('/etc/test/path1')
|
||||
etc_path = dropin_configs.get_etc_path('/etc/test/path1')
|
||||
etc_path.write_text('x-invalid-content')
|
||||
dropin_configs.enable()
|
||||
_assert_symlinks(dropin_configs, tmp_path, should_exist=True,
|
||||
@ -182,7 +183,7 @@ def test_dropin_config_diagnose_symlinks(dropin_configs, tmp_path):
|
||||
|
||||
# A file exists instead of symlink
|
||||
dropin_configs.disable()
|
||||
etc_path = DropinConfigs.get_etc_path('/etc/test/path1')
|
||||
etc_path = dropin_configs.get_etc_path('/etc/test/path1')
|
||||
etc_path.touch()
|
||||
results = dropin_configs.diagnose()
|
||||
assert results[0].result == 'failed'
|
||||
@ -204,7 +205,7 @@ def test_dropin_config_diagnose_copy_only(dropin_configs, tmp_path):
|
||||
with patch('plinth.config.DropinConfigs.ROOT', new=tmp_path):
|
||||
dropin_configs.copy_only = True
|
||||
for path in ['/etc/test/path1', '/etc/path2']:
|
||||
target = DropinConfigs.get_target_path(path)
|
||||
target = dropin_configs.get_target_path(path)
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text('test-config-content')
|
||||
|
||||
@ -221,7 +222,7 @@ def test_dropin_config_diagnose_copy_only(dropin_configs, tmp_path):
|
||||
|
||||
# A symlink exists instead of a copied file
|
||||
dropin_configs.disable()
|
||||
etc_path = DropinConfigs.get_etc_path('/etc/test/path1')
|
||||
etc_path = dropin_configs.get_etc_path('/etc/test/path1')
|
||||
etc_path.symlink_to('/blah')
|
||||
results = dropin_configs.diagnose()
|
||||
assert results[0].result == 'failed'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user