From 5402d00be02978d0c452fbac12f82308fc8d74fe Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 30 Aug 2023 13:17:50 +0200 Subject: [PATCH 001/136] Maybe fix fetchmail when used with pop3 --- optional/fetchmail/fetchmail.py | 2 +- towncrier/newsfragments/2928.bugfix | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 towncrier/newsfragments/2928.bugfix diff --git a/optional/fetchmail/fetchmail.py b/optional/fetchmail/fetchmail.py index 35b2ee22..8fad8926 100755 --- a/optional/fetchmail/fetchmail.py +++ b/optional/fetchmail/fetchmail.py @@ -64,7 +64,7 @@ def run(debug): username=escape_rc_string(fetch["username"]), password=escape_rc_string(fetch["password"]), options=options, - folders=folders, + folders='' if fetch['protocol'].lower().startswith('pop') else folders, lmtp='' if fetch['scan'] else 'lmtp', ) if debug: diff --git a/towncrier/newsfragments/2928.bugfix b/towncrier/newsfragments/2928.bugfix new file mode 100644 index 00000000..23bf644b --- /dev/null +++ b/towncrier/newsfragments/2928.bugfix @@ -0,0 +1 @@ +fix fetchmail when used with POP3: disregard "folders" From 008ff03882740ffedb260f3daa09e061c34e0ad4 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 30 Aug 2023 15:11:58 +0200 Subject: [PATCH 002/136] as per review --- optional/fetchmail/fetchmail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optional/fetchmail/fetchmail.py b/optional/fetchmail/fetchmail.py index 8fad8926..a298cf53 100755 --- a/optional/fetchmail/fetchmail.py +++ b/optional/fetchmail/fetchmail.py @@ -64,7 +64,7 @@ def run(debug): username=escape_rc_string(fetch["username"]), password=escape_rc_string(fetch["password"]), options=options, - folders='' if fetch['protocol'].lower().startswith('pop') else folders, + folders='' if fetch['protocol'] == 'pop3' else folders, lmtp='' if fetch['scan'] else 'lmtp', ) if debug: From 0ec7989bf577f799814c510255e07d87a969ba0b Mon Sep 17 00:00:00 2001 From: Hossein Hosni Date: Sat, 9 Sep 2023 01:23:01 +0330 Subject: [PATCH 003/136] Add translation file for Persian (aka Farsi) --- .../translations/fa/LC_MESSAGES/messages.po | 731 ++++++++++++++++++ 1 file changed, 731 insertions(+) create mode 100644 core/admin/mailu/translations/fa/LC_MESSAGES/messages.po diff --git a/core/admin/mailu/translations/fa/LC_MESSAGES/messages.po b/core/admin/mailu/translations/fa/LC_MESSAGES/messages.po new file mode 100644 index 00000000..02fa13cc --- /dev/null +++ b/core/admin/mailu/translations/fa/LC_MESSAGES/messages.po @@ -0,0 +1,731 @@ +# Persian translations for Mailu. +# Copyright (C) 2023 Mailu +# This file is distributed under the same license as the Mailu project. +# Hossein Hosni , 2023. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: hosni.hossein@gmail.com\n" +"POT-Creation-Date: 2023-09-09 00:33+0330\n" +"PO-Revision-Date: 2023-09-09 01:21+0330\n" +"Last-Translator: Hossein Hosni \n" +"Language-Team: Persian \n" +"Language: fa\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n==0 || n==1);\n" +"Generated-By: Babel 2.3.4\n" +"X-Generator: Poedit 3.3.2\n" + +#: mailu/sso/forms.py:8 mailu/ui/forms.py:79 +msgid "E-mail" +msgstr "پست الکترونیک" + +#: mailu/sso/forms.py:9 mailu/ui/forms.py:80 mailu/ui/forms.py:93 +#: mailu/ui/forms.py:112 mailu/ui/forms.py:166 +#: mailu/ui/templates/client.html:32 mailu/ui/templates/client.html:57 +msgid "Password" +msgstr "گذرواژه" + +#: mailu/sso/forms.py:10 mailu/sso/forms.py:11 mailu/sso/templates/login.html:4 +#: mailu/ui/templates/sidebar.html:142 +msgid "Sign in" +msgstr "ورود" + +#: mailu/sso/templates/base_sso.html:8 mailu/ui/templates/base.html:8 +msgid "Admin page for" +msgstr "صفحه مدیریت برای" + +#: mailu/sso/templates/base_sso.html:19 mailu/ui/templates/base.html:19 +msgid "toggle sidebar" +msgstr "نوار کناری را تغییر دهید" + +#: mailu/sso/templates/base_sso.html:37 mailu/ui/templates/base.html:37 +msgid "change language" +msgstr "تغییر زبان" + +#: mailu/sso/templates/sidebar_sso.html:4 mailu/ui/templates/sidebar.html:94 +msgid "Go to" +msgstr "برو به" + +#: mailu/sso/templates/sidebar_sso.html:9 mailu/ui/templates/client.html:4 +#: mailu/ui/templates/sidebar.html:50 mailu/ui/templates/sidebar.html:107 +msgid "Client setup" +msgstr "تنظیمات سرویس‌گیرنده" + +#: mailu/sso/templates/sidebar_sso.html:16 mailu/ui/templates/sidebar.html:114 +msgid "Website" +msgstr "وب سایت" + +#: mailu/sso/templates/sidebar_sso.html:22 mailu/ui/templates/sidebar.html:120 +msgid "Help" +msgstr "راهنما" + +#: mailu/sso/templates/sidebar_sso.html:35 +#: mailu/ui/templates/domain/signup.html:4 mailu/ui/templates/sidebar.html:127 +msgid "Register a domain" +msgstr "ثبت دامنه" + +#: mailu/sso/templates/sidebar_sso.html:49 mailu/ui/forms.py:95 +#: mailu/ui/templates/sidebar.html:149 mailu/ui/templates/user/signup.html:4 +#: mailu/ui/templates/user/signup_domain.html:4 +msgid "Sign up" +msgstr "ثبت‌نام" + +#: mailu/ui/forms.py:33 mailu/ui/forms.py:36 +msgid "Invalid email address." +msgstr "پست‌الکترونیک اشتباه است." + +#: mailu/ui/forms.py:45 +msgid "Confirm" +msgstr "تایید" + +#: mailu/ui/forms.py:48 mailu/ui/forms.py:58 +#: mailu/ui/templates/domain/details.html:26 +#: mailu/ui/templates/domain/list.html:19 mailu/ui/templates/relay/list.html:18 +msgid "Domain name" +msgstr "آدرس دامنه" + +#: mailu/ui/forms.py:49 +msgid "Maximum user count" +msgstr "حداکثر تعداد کاربر" + +#: mailu/ui/forms.py:50 +msgid "Maximum alias count" +msgstr "حداکثر تعداد نام مستعار" + +#: mailu/ui/forms.py:51 +msgid "Maximum user quota" +msgstr "حداکثر سهمیه کاربر" + +#: mailu/ui/forms.py:52 +msgid "Enable sign-up" +msgstr "فعال کردن ثبت نام" + +#: mailu/ui/forms.py:53 mailu/ui/forms.py:74 mailu/ui/forms.py:86 +#: mailu/ui/forms.py:132 mailu/ui/forms.py:144 +#: mailu/ui/templates/alias/list.html:22 mailu/ui/templates/domain/list.html:22 +#: mailu/ui/templates/relay/list.html:20 mailu/ui/templates/token/list.html:20 +#: mailu/ui/templates/user/list.html:24 +msgid "Comment" +msgstr "یادداشت" + +#: mailu/ui/forms.py:54 mailu/ui/forms.py:68 mailu/ui/forms.py:75 +#: mailu/ui/forms.py:88 mailu/ui/forms.py:136 mailu/ui/forms.py:145 +msgid "Save" +msgstr "ذخیره" + +#: mailu/ui/forms.py:59 +msgid "Initial admin" +msgstr "مدیر نخستین" + +#: mailu/ui/forms.py:60 +msgid "Admin password" +msgstr "گذرواژه مدیریت" + +#: mailu/ui/forms.py:61 mailu/ui/forms.py:81 mailu/ui/forms.py:94 +msgid "Confirm password" +msgstr "تایید گذرواژه" + +#: mailu/ui/forms.py:63 +msgid "Create" +msgstr "ایجاد" + +#: mailu/ui/forms.py:67 +msgid "Alternative name" +msgstr "نام جایگزین" + +#: mailu/ui/forms.py:72 +msgid "Relayed domain name" +msgstr "نام دامنه میانجی" + +#: mailu/ui/forms.py:73 mailu/ui/templates/relay/list.html:19 +msgid "Remote host" +msgstr "میزبان راه‌دور" + +#: mailu/ui/forms.py:82 mailu/ui/templates/user/list.html:23 +#: mailu/ui/templates/user/signup_domain.html:16 +msgid "Quota" +msgstr "سهمیه" + +#: mailu/ui/forms.py:83 +msgid "Allow IMAP access" +msgstr "دسترسی IMAP مجاز باشد" + +#: mailu/ui/forms.py:84 +msgid "Allow POP3 access" +msgstr "دسترسی POP3 مجاز باشد" + +#: mailu/ui/forms.py:85 mailu/ui/forms.py:101 +#: mailu/ui/templates/user/settings.html:15 +msgid "Displayed name" +msgstr "نام نمایشی" + +#: mailu/ui/forms.py:87 +msgid "Enabled" +msgstr "فعال" + +#: mailu/ui/forms.py:92 +msgid "Email address" +msgstr "پست الکترونیکی" + +#: mailu/ui/forms.py:102 +msgid "Enable spam filter" +msgstr "فعال‌کردن پایش هرزنامه" + +#: mailu/ui/forms.py:103 +msgid "Enable marking spam mails as read" +msgstr "فعال‌سازی علامت‌زدن هرزنامه به عنوان خوانده‌شده" + +#: mailu/ui/forms.py:104 +msgid "Spam filter tolerance" +msgstr "بازه تحمل هرزنامه" + +#: mailu/ui/forms.py:105 +msgid "Enable forwarding" +msgstr "فعال‌سازی بازارسال" + +#: mailu/ui/forms.py:106 +msgid "Keep a copy of the emails" +msgstr "نگهداری رونوشت از پست‌الکترونیک" + +#: mailu/ui/forms.py:107 mailu/ui/forms.py:143 +#: mailu/ui/templates/alias/list.html:21 +msgid "Destination" +msgstr "مقصد" + +#: mailu/ui/forms.py:108 +msgid "Save settings" +msgstr "ذخیره تنظیمات" + +#: mailu/ui/forms.py:113 +msgid "Password check" +msgstr "بررسی گذرواژه" + +#: mailu/ui/forms.py:114 mailu/ui/templates/sidebar.html:25 +msgid "Update password" +msgstr "بروزرسانی گذرواژه" + +#: mailu/ui/forms.py:118 +msgid "Enable automatic reply" +msgstr "فعال‌سازی پاسخگوی‌خودکار" + +#: mailu/ui/forms.py:119 +msgid "Reply subject" +msgstr "موضوع پاسخگویی" + +#: mailu/ui/forms.py:120 +msgid "Reply body" +msgstr "متن پاسخگویی" + +#: mailu/ui/forms.py:122 +msgid "Start of vacation" +msgstr "شروع استراحت" + +#: mailu/ui/forms.py:123 +msgid "End of vacation" +msgstr "پایان استراحت" + +#: mailu/ui/forms.py:124 +msgid "Update" +msgstr "بروزرسانی" + +#: mailu/ui/forms.py:129 +msgid "Your token (write it down, as it will never be displayed again)" +msgstr "کلید شما (کلید را یادداشت کنید، چرا که دوباره نمایش داده نخواهد شد)" + +#: mailu/ui/forms.py:134 mailu/ui/templates/token/list.html:21 +msgid "Authorized IP" +msgstr "Authorized IP" + +#: mailu/ui/forms.py:140 +msgid "Alias" +msgstr "Alias" + +#: mailu/ui/forms.py:142 +msgid "Use SQL LIKE Syntax (e.g. for catch-all aliases)" +msgstr "استفاده از قواعد اس‌کیو‌ال (برای پوشش الایس‌ها)" + +#: mailu/ui/forms.py:149 +msgid "Admin email" +msgstr "افزودن پست‌الکترونیکی" + +#: mailu/ui/forms.py:150 mailu/ui/forms.py:155 mailu/ui/forms.py:168 +msgid "Submit" +msgstr "ثبت" + +#: mailu/ui/forms.py:154 +msgid "Manager email" +msgstr "مدیریت پست‌الکترونیک" + +#: mailu/ui/forms.py:159 +msgid "Protocol" +msgstr "پرتکل" + +#: mailu/ui/forms.py:162 +msgid "Hostname or IP" +msgstr "نشانی یا آی‌پی" + +#: mailu/ui/forms.py:163 mailu/ui/templates/client.html:20 +#: mailu/ui/templates/client.html:45 +msgid "TCP port" +msgstr "پورت تی‌سی‌پی" + +#: mailu/ui/forms.py:164 +msgid "Enable TLS" +msgstr "فعال‌سازی TLS" + +#: mailu/ui/forms.py:165 mailu/ui/templates/client.html:28 +#: mailu/ui/templates/client.html:53 mailu/ui/templates/fetch/list.html:21 +msgid "Username" +msgstr "نام‌کاربری" + +#: mailu/ui/forms.py:167 +msgid "Keep emails on the server" +msgstr "نگهداری ایمیل در سرور" + +#: mailu/ui/forms.py:172 +msgid "Announcement subject" +msgstr "موضوع آگهی" + +#: mailu/ui/forms.py:174 +msgid "Announcement body" +msgstr "متن آگهی" + +#: mailu/ui/forms.py:176 +msgid "Send" +msgstr "ارسال" + +#: mailu/ui/templates/announcement.html:4 +msgid "Public announcement" +msgstr "آگهی عمومی" + +#: mailu/ui/templates/antispam.html:4 mailu/ui/templates/sidebar.html:80 +#: mailu/ui/templates/user/settings.html:19 +msgid "Antispam" +msgstr "ضدهرزنامه" + +#: mailu/ui/templates/antispam.html:8 +msgid "RSPAMD status page" +msgstr "صفحه گزارش وضعیت RSPAMD" + +#: mailu/ui/templates/client.html:8 +msgid "configure your email client" +msgstr "تنظیمات کاربر پست‌الکترونیک" + +#: mailu/ui/templates/client.html:13 +msgid "Incoming mail" +msgstr "ایمیل ورودی" + +#: mailu/ui/templates/client.html:16 mailu/ui/templates/client.html:41 +msgid "Mail protocol" +msgstr "پرتکل ایمیل" + +#: mailu/ui/templates/client.html:24 mailu/ui/templates/client.html:49 +msgid "Server name" +msgstr "نام سرور" + +#: mailu/ui/templates/client.html:38 +msgid "Outgoing mail" +msgstr "ایمیل خروجی" + +#: mailu/ui/templates/confirm.html:4 +msgid "Confirm action" +msgstr "تایید دستور" + +#: mailu/ui/templates/confirm.html:13 +#, python-format +msgid "You are about to %(action)s. Please confirm your action." +msgstr "شما در حال انجام %(action)s هستید. لطفا دستور را تایید کنید." + +#: mailu/ui/templates/docker-error.html:4 +msgid "Docker error" +msgstr "خطای داکر" + +#: mailu/ui/templates/docker-error.html:12 +msgid "An error occurred while talking to the Docker server." +msgstr "خطایی در هنگام ارتباط با سرور داکر به وجود آمده است." + +#: mailu/ui/templates/macros.html:129 +msgid "copy to clipboard" +msgstr "کپی در حافظه" + +#: mailu/ui/templates/sidebar.html:15 +msgid "My account" +msgstr "حساب من" + +#: mailu/ui/templates/sidebar.html:19 mailu/ui/templates/user/list.html:37 +msgid "Settings" +msgstr "تنظیمات" + +#: mailu/ui/templates/sidebar.html:31 mailu/ui/templates/user/list.html:38 +msgid "Auto-reply" +msgstr "پاسخ‌گوی خودکار" + +#: mailu/ui/templates/fetch/list.html:4 mailu/ui/templates/sidebar.html:37 +#: mailu/ui/templates/user/list.html:39 +msgid "Fetched accounts" +msgstr "اکانت‌ها واکشی‌شده" + +#: mailu/ui/templates/sidebar.html:43 mailu/ui/templates/token/list.html:4 +msgid "Authentication tokens" +msgstr "کلیدهای احراز هویت" + +#: mailu/ui/templates/sidebar.html:56 +msgid "Administration" +msgstr "مدیریت" + +#: mailu/ui/templates/sidebar.html:62 +msgid "Announcement" +msgstr "آگهی" + +#: mailu/ui/templates/sidebar.html:68 +msgid "Administrators" +msgstr "مدیران" + +#: mailu/ui/templates/sidebar.html:74 +msgid "Relayed domains" +msgstr "دامنه‌های میانجی" + +#: mailu/ui/templates/sidebar.html:88 +msgid "Mail domains" +msgstr "دامنه‌های ایمیل" + +#: mailu/ui/templates/sidebar.html:99 +msgid "Webmail" +msgstr "پنل‌وب‌ایمیل" + +#: mailu/ui/templates/sidebar.html:135 +msgid "Sign out" +msgstr "خروج" + +#: mailu/ui/templates/working.html:4 +msgid "We are still working on this feature!" +msgstr "ما داریم روی این قابلیت زحمت می‌کشیم :)" + +#: mailu/ui/templates/admin/create.html:4 +msgid "Add a global administrator" +msgstr "افزودن مدیرکل" + +#: mailu/ui/templates/admin/list.html:4 +msgid "Global administrators" +msgstr "مدیران کلی" + +#: mailu/ui/templates/admin/list.html:9 +msgid "Add administrator" +msgstr "افزودن مدیر" + +#: mailu/ui/templates/admin/list.html:17 mailu/ui/templates/alias/list.html:19 +#: mailu/ui/templates/alternative/list.html:19 +#: mailu/ui/templates/domain/list.html:17 mailu/ui/templates/fetch/list.html:19 +#: mailu/ui/templates/manager/list.html:19 +#: mailu/ui/templates/relay/list.html:17 mailu/ui/templates/token/list.html:19 +#: mailu/ui/templates/user/list.html:19 +msgid "Actions" +msgstr "عملیات" + +#: mailu/ui/templates/admin/list.html:18 mailu/ui/templates/alias/list.html:20 +#: mailu/ui/templates/manager/list.html:20 mailu/ui/templates/user/list.html:21 +msgid "Email" +msgstr "پست‌الکترونیک" + +#: mailu/ui/templates/admin/list.html:25 mailu/ui/templates/alias/list.html:32 +#: mailu/ui/templates/alternative/list.html:29 +#: mailu/ui/templates/domain/list.html:34 mailu/ui/templates/fetch/list.html:34 +#: mailu/ui/templates/manager/list.html:27 +#: mailu/ui/templates/relay/list.html:30 mailu/ui/templates/token/list.html:30 +#: mailu/ui/templates/user/list.html:34 +msgid "Delete" +msgstr "حذف" + +#: mailu/ui/templates/alias/create.html:4 +msgid "Create alias" +msgstr "ایجاد الایس" + +#: mailu/ui/templates/alias/edit.html:4 +msgid "Edit alias" +msgstr "ویرایش الایس" + +#: mailu/ui/templates/alias/list.html:4 +msgid "Alias list" +msgstr "لیست الایس‌ها" + +#: mailu/ui/templates/alias/list.html:12 +msgid "Add alias" +msgstr "افزودن الایس" + +#: mailu/ui/templates/alias/list.html:23 +#: mailu/ui/templates/alternative/list.html:21 +#: mailu/ui/templates/domain/list.html:23 mailu/ui/templates/fetch/list.html:25 +#: mailu/ui/templates/relay/list.html:21 mailu/ui/templates/token/list.html:22 +#: mailu/ui/templates/user/list.html:25 +msgid "Created" +msgstr "ایجاد شد" + +#: mailu/ui/templates/alias/list.html:24 +#: mailu/ui/templates/alternative/list.html:22 +#: mailu/ui/templates/domain/list.html:24 mailu/ui/templates/fetch/list.html:26 +#: mailu/ui/templates/relay/list.html:22 mailu/ui/templates/token/list.html:23 +#: mailu/ui/templates/user/list.html:26 +msgid "Last edit" +msgstr "آخرین ویرایش" + +#: mailu/ui/templates/alias/list.html:31 mailu/ui/templates/domain/list.html:33 +#: mailu/ui/templates/fetch/list.html:33 mailu/ui/templates/relay/list.html:29 +#: mailu/ui/templates/user/list.html:33 +msgid "Edit" +msgstr "ویرایش" + +#: mailu/ui/templates/alternative/create.html:4 +msgid "Create alternative domain" +msgstr "ایجاد دامنه‌ی دیگر" + +#: mailu/ui/templates/alternative/list.html:4 +msgid "Alternative domain list" +msgstr "لیست دامنه‌های دیگر" + +#: mailu/ui/templates/alternative/list.html:12 +msgid "Add alternative" +msgstr "افزودن دامنه‌ی دیگر" + +#: mailu/ui/templates/alternative/list.html:20 +msgid "Name" +msgstr "نام" + +#: mailu/ui/templates/domain/create.html:4 +#: mailu/ui/templates/domain/list.html:9 +msgid "New domain" +msgstr "دامنه جدید" + +#: mailu/ui/templates/domain/details.html:4 +msgid "Domain details" +msgstr "مشخصات دامنه" + +#: mailu/ui/templates/domain/details.html:15 +msgid "Regenerate keys" +msgstr "ایجاد مجدد کلید‌ها" + +#: mailu/ui/templates/domain/details.html:17 +msgid "Generate keys" +msgstr "ایجاد کلید‌ها" + +#: mailu/ui/templates/domain/details.html:30 +msgid "DNS MX entry" +msgstr "DNS MX" + +#: mailu/ui/templates/domain/details.html:34 +msgid "DNS SPF entries" +msgstr "DNS SPF" + +#: mailu/ui/templates/domain/details.html:40 +msgid "DKIM public key" +msgstr "کلید عمومی DKIM" + +#: mailu/ui/templates/domain/details.html:44 +msgid "DNS DKIM entry" +msgstr "DNS DKIM" + +#: mailu/ui/templates/domain/details.html:48 +msgid "DNS DMARC entry" +msgstr "DNS DMARC" + +#: mailu/ui/templates/domain/details.html:58 +msgid "DNS TLSA entry" +msgstr "DNS TLSA" + +#: mailu/ui/templates/domain/details.html:63 +msgid "DNS client auto-configuration entries" +msgstr "DNS client auto-configuration" + +#: mailu/ui/templates/domain/edit.html:4 +msgid "Edit domain" +msgstr "ویرایش دامنه" + +#: mailu/ui/templates/domain/list.html:4 +msgid "Domain list" +msgstr "لیست دامنه" + +#: mailu/ui/templates/domain/list.html:18 +msgid "Manage" +msgstr "مدیریت" + +#: mailu/ui/templates/domain/list.html:20 +msgid "Mailbox count" +msgstr "تعداد جعبه ایمیل" + +#: mailu/ui/templates/domain/list.html:21 +msgid "Alias count" +msgstr "تعداد الایس‌ها" + +#: mailu/ui/templates/domain/list.html:31 +msgid "Details" +msgstr "جزئیات" + +#: mailu/ui/templates/domain/list.html:38 +msgid "Users" +msgstr "کاربران" + +#: mailu/ui/templates/domain/list.html:39 +msgid "Aliases" +msgstr "الایس‌ها" + +#: mailu/ui/templates/domain/list.html:40 +msgid "Managers" +msgstr "مدیران" + +#: mailu/ui/templates/domain/list.html:42 +msgid "Alternatives" +msgstr "جایگزین‌ها" + +#: mailu/ui/templates/domain/signup.html:13 +msgid "" +"In order to register a new domain, you must first setup the\n" +" domain zone so that the domain MX points to this server" +msgstr "" +"برای ثبت دامنه جدید، ابتدا می‌بایست تنظیمات DNS ٔدامنه را به گونه‌ای تنظیم کنید " +"که رکورد MX آن به سرور ما اشاره کند" + +#: mailu/ui/templates/domain/signup.html:18 +msgid "" +"If you do not know how to setup an MX record for your DNS " +"zone,\n" +" please contact your DNS provider or administrator. Also, please wait a\n" +" couple minutes after the MX is set so the local server " +"cache\n" +" expires." +msgstr "" +"در صورتی که نمی‌دانید چگونه تنظیمات رکورد MX دامنه خود را انجام " +"دهید، می‌بایست با فروشنده دامنه خود ارتباط برقرار کنید.\n" +"همچنین بعد از تنظیم رکورد MX دقایقی را تا اعمال نتیجه آن منتظر " +"بمانید." + +#: mailu/ui/templates/fetch/create.html:4 +msgid "Add a fetched account" +msgstr "افزودن حساب واکشی" + +#: mailu/ui/templates/fetch/edit.html:4 +msgid "Update a fetched account" +msgstr "بروزرسانی حساب واکشی" + +#: mailu/ui/templates/fetch/list.html:12 +msgid "Add an account" +msgstr "افزودن حساب" + +#: mailu/ui/templates/fetch/list.html:20 +msgid "Endpoint" +msgstr "نشانی" + +#: mailu/ui/templates/fetch/list.html:22 +msgid "Keep emails" +msgstr "نگهداری ایمیل‌ها" + +#: mailu/ui/templates/fetch/list.html:23 +msgid "Last check" +msgstr "آخرین بررسی" + +#: mailu/ui/templates/fetch/list.html:24 +msgid "Status" +msgstr "وضعیت" + +#: mailu/ui/templates/fetch/list.html:38 +msgid "yes" +msgstr "بلی" + +#: mailu/ui/templates/fetch/list.html:38 +msgid "no" +msgstr "خیر" + +#: mailu/ui/templates/manager/create.html:4 +msgid "Add a manager" +msgstr "افزودن مدیری" + +#: mailu/ui/templates/manager/list.html:4 +msgid "Manager list" +msgstr "لیست مدیران" + +#: mailu/ui/templates/manager/list.html:12 +msgid "Add manager" +msgstr "افزودن مدیر" + +#: mailu/ui/templates/relay/create.html:4 +msgid "New relay domain" +msgstr "افزودن دامنه میانجی" + +#: mailu/ui/templates/relay/edit.html:4 +msgid "Edit relayed domain" +msgstr "ویرایش دامنه میانجی" + +#: mailu/ui/templates/relay/list.html:4 +msgid "Relayed domain list" +msgstr "لیست دامنه‌های میانجی" + +#: mailu/ui/templates/relay/list.html:9 +msgid "New relayed domain" +msgstr "دامنه میانجی جدید" + +#: mailu/ui/templates/token/create.html:4 +msgid "Create an authentication token" +msgstr "ایجاد کلید احراز هویت" + +#: mailu/ui/templates/token/list.html:12 +msgid "New token" +msgstr "کلید جدید" + +#: mailu/ui/templates/user/create.html:4 +msgid "New user" +msgstr "کاربر جدید" + +#: mailu/ui/templates/user/create.html:15 +msgid "General" +msgstr "عمومی" + +#: mailu/ui/templates/user/create.html:23 +msgid "Features and quotas" +msgstr "قابلیت‌ها و سهمیه" + +#: mailu/ui/templates/user/edit.html:4 +msgid "Edit user" +msgstr "ویرایش کاربر" + +#: mailu/ui/templates/user/list.html:4 +msgid "User list" +msgstr "لیست کاربران" + +#: mailu/ui/templates/user/list.html:12 +msgid "Add user" +msgstr "افزودن کاربر" + +#: mailu/ui/templates/user/list.html:20 mailu/ui/templates/user/settings.html:4 +msgid "User settings" +msgstr "تنظیمات کاربر" + +#: mailu/ui/templates/user/list.html:22 +msgid "Features" +msgstr "قابلیت‌ها" + +#: mailu/ui/templates/user/password.html:4 +msgid "Password update" +msgstr "بروزرسانی گذرواژه" + +#: mailu/ui/templates/user/reply.html:4 +msgid "Automatic reply" +msgstr "پاسخگوی خودکار" + +#: mailu/ui/templates/user/settings.html:27 +msgid "Auto-forward" +msgstr "بازارسال خودکار" + +#: mailu/ui/templates/user/signup_domain.html:8 +msgid "pick a domain for the new account" +msgstr "انتخاب دامنه برای حساب جدید" + +#: mailu/ui/templates/user/signup_domain.html:14 +msgid "Domain" +msgstr "دامنه" + +#: mailu/ui/templates/user/signup_domain.html:15 +msgid "Available slots" +msgstr "جایگاه‌های موجود" From a89a04d364ca9e9412b3e5dec26255b654c57d1c Mon Sep 17 00:00:00 2001 From: Hossein Hosni Date: Sat, 9 Sep 2023 01:25:02 +0330 Subject: [PATCH 004/136] Add fa (Persian) language code to assets Dockerfile --- core/admin/assets/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/assets/Dockerfile b/core/admin/assets/Dockerfile index 33b9bdb9..8982581e 100644 --- a/core/admin/assets/Dockerfile +++ b/core/admin/assets/Dockerfile @@ -11,7 +11,7 @@ RUN set -euxo pipefail \ ; npm install --no-audit --no-fund \ ; sed -i 's/#007bff/#55a5d9/' node_modules/admin-lte/build/scss/_bootstrap-variables.scss \ ; mkdir assets \ - ; for l in ca da de:de-DE en:en-GB es:es-ES eu fr:fr-FR he hu is it:it-IT ja nb_NO:no-NB nl:nl-NL pl pt:pt-PT ru sv:sv-SE zh; do \ + ; for l in ca da de:de-DE en:en-GB es:es-ES eu fa fr:fr-FR he hu is it:it-IT ja nb_NO:no-NB nl:nl-NL pl pt:pt-PT ru sv:sv-SE zh; do \ cp node_modules/datatables.net-plugins/i18n/${l#*:}.json assets/${l%:*}.json; \ done From aaf84a30912cbb78b37ef06b0370a064569d5d05 Mon Sep 17 00:00:00 2001 From: Hossein Hosni Date: Sat, 9 Sep 2023 01:34:28 +0330 Subject: [PATCH 005/136] Update AUTHORS.md --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 5f05f0ac..1c8f4395 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -22,6 +22,7 @@ Other contributors: - "SunMar" - Dutch translation - "Marty Hou" - Chinese Simple translation - [Thomas Sänger](https://github.com/HorayNarea) - German translation + - [Hossein Hosni](https://github.com/hosni) - [Contributions](https://github.com/Mailu/Mailu/commits?author=hosni) - [Tim Mohlmann](https://github.com/muhlemmer) - [Contributions](https://github.com/Mailu/Mailu/commits?author=muhlemmer) - [Ionut Filip](https://github.com/ionutfilip) - [Contributions](https://github.com/Mailu/Mailu/commits?author=ionutfilip) - [Ichikawa Yuriko](https://github.com/IchikawaYukko) - [Contributions](https://github.com/Mailu/Mailu/commits?author=IchikawaYukko) Japanese translation From cd34d3ba1821dfbb1e2238c73a9f6b61bad94deb Mon Sep 17 00:00:00 2001 From: Prosta4okua Date: Mon, 14 Aug 2023 08:20:34 +0300 Subject: [PATCH 006/136] Create messages.po --- core/admin/mailu/translations/uk_UA/LC_MESSAGES/messages.po | 1 + 1 file changed, 1 insertion(+) create mode 100644 core/admin/mailu/translations/uk_UA/LC_MESSAGES/messages.po diff --git a/core/admin/mailu/translations/uk_UA/LC_MESSAGES/messages.po b/core/admin/mailu/translations/uk_UA/LC_MESSAGES/messages.po new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/core/admin/mailu/translations/uk_UA/LC_MESSAGES/messages.po @@ -0,0 +1 @@ + From 47a6710d88e5ea75ab56081f12b4e13ebfc6b1c6 Mon Sep 17 00:00:00 2001 From: Prosta4okua Date: Mon, 14 Aug 2023 08:20:43 +0300 Subject: [PATCH 007/136] Add files via upload --- .../uk_UA/LC_MESSAGES/messages.po | 736 ++++++++++++++++++ 1 file changed, 736 insertions(+) diff --git a/core/admin/mailu/translations/uk_UA/LC_MESSAGES/messages.po b/core/admin/mailu/translations/uk_UA/LC_MESSAGES/messages.po index 8b137891..df78bb7c 100644 --- a/core/admin/mailu/translations/uk_UA/LC_MESSAGES/messages.po +++ b/core/admin/mailu/translations/uk_UA/LC_MESSAGES/messages.po @@ -1 +1,737 @@ +# English translations for PROJECT. +# Copyright (C) 2016 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2016. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2022-05-22 18:47+0200\n" +"PO-Revision-Date: 2023-08-14 08:19+0300\n" +"Last-Translator: Jaume Barber \n" +"Language-Team: English \n" +"Language: uk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" +"Generated-By: Babel 2.3.4\n" +"X-Generator: Poedit 3.3.2\n" +#: mailu/sso/forms.py:8 mailu/ui/forms.py:79 +msgid "E-mail" +msgstr "Електронна пошта" + +#: mailu/sso/forms.py:9 mailu/ui/forms.py:80 mailu/ui/forms.py:93 +#: mailu/ui/forms.py:112 mailu/ui/forms.py:166 +#: mailu/ui/templates/client.html:32 mailu/ui/templates/client.html:57 +msgid "Password" +msgstr "Пароль" + +#: mailu/sso/forms.py:10 mailu/sso/forms.py:11 mailu/sso/templates/login.html:4 +#: mailu/ui/templates/sidebar.html:142 +msgid "Sign in" +msgstr "Увійти" + +#: mailu/sso/templates/base_sso.html:8 mailu/ui/templates/base.html:8 +msgid "Admin page for" +msgstr "Сторінка адміністратора для" + +#: mailu/sso/templates/base_sso.html:19 mailu/ui/templates/base.html:19 +msgid "toggle sidebar" +msgstr "переключити бічну панель" + +#: mailu/sso/templates/base_sso.html:37 mailu/ui/templates/base.html:37 +msgid "change language" +msgstr "змінити мову" + +#: mailu/sso/templates/sidebar_sso.html:4 mailu/ui/templates/sidebar.html:94 +msgid "Go to" +msgstr "Перейти до" + +#: mailu/sso/templates/sidebar_sso.html:9 mailu/ui/templates/client.html:4 +#: mailu/ui/templates/sidebar.html:50 mailu/ui/templates/sidebar.html:107 +msgid "Client setup" +msgstr "Налаштування клієнта" + +#: mailu/sso/templates/sidebar_sso.html:16 mailu/ui/templates/sidebar.html:114 +msgid "Website" +msgstr "Вебсайт" + +#: mailu/sso/templates/sidebar_sso.html:22 mailu/ui/templates/sidebar.html:120 +msgid "Help" +msgstr "Довідка" + +#: mailu/sso/templates/sidebar_sso.html:35 +#: mailu/ui/templates/domain/signup.html:4 mailu/ui/templates/sidebar.html:127 +msgid "Register a domain" +msgstr "Зареєструвати домен" + +#: mailu/sso/templates/sidebar_sso.html:49 mailu/ui/forms.py:95 +#: mailu/ui/templates/sidebar.html:149 mailu/ui/templates/user/signup.html:4 +#: mailu/ui/templates/user/signup_domain.html:4 +msgid "Sign up" +msgstr "Зареєструватися" + +#: mailu/ui/forms.py:33 mailu/ui/forms.py:36 +msgid "Invalid email address." +msgstr "Недійсна електронна пошта." + +#: mailu/ui/forms.py:45 +msgid "Confirm" +msgstr "Підтвердити" + +#: mailu/ui/forms.py:48 mailu/ui/forms.py:58 +#: mailu/ui/templates/domain/details.html:26 +#: mailu/ui/templates/domain/list.html:19 mailu/ui/templates/relay/list.html:18 +msgid "Domain name" +msgstr "Ім'я домену" + +#: mailu/ui/forms.py:49 +msgid "Maximum user count" +msgstr "Максимальна кількість користувачів" + +#: mailu/ui/forms.py:50 +msgid "Maximum alias count" +msgstr "Максимальна кількість скорочень" + +#: mailu/ui/forms.py:51 +msgid "Maximum user quota" +msgstr "Максимальна квота користувачів" + +#: mailu/ui/forms.py:52 +msgid "Enable sign-up" +msgstr "Увімкнути реєстрацію" + +#: mailu/ui/forms.py:53 mailu/ui/forms.py:74 mailu/ui/forms.py:86 +#: mailu/ui/forms.py:132 mailu/ui/forms.py:144 +#: mailu/ui/templates/alias/list.html:22 mailu/ui/templates/domain/list.html:22 +#: mailu/ui/templates/relay/list.html:20 mailu/ui/templates/token/list.html:20 +#: mailu/ui/templates/user/list.html:24 +msgid "Comment" +msgstr "Коментар" + +#: mailu/ui/forms.py:54 mailu/ui/forms.py:68 mailu/ui/forms.py:75 +#: mailu/ui/forms.py:88 mailu/ui/forms.py:136 mailu/ui/forms.py:145 +msgid "Save" +msgstr "Зберегти" + +#: mailu/ui/forms.py:59 +msgid "Initial admin" +msgstr "Початковий адміністратор" + +#: mailu/ui/forms.py:60 +msgid "Admin password" +msgstr "Пароль адміністратора" + +#: mailu/ui/forms.py:61 mailu/ui/forms.py:81 mailu/ui/forms.py:94 +msgid "Confirm password" +msgstr "Підтвердити пароль" + +#: mailu/ui/forms.py:63 +msgid "Create" +msgstr "Створити" + +#: mailu/ui/forms.py:67 +msgid "Alternative name" +msgstr "Альтернативна назва" + +#: mailu/ui/forms.py:72 +msgid "Relayed domain name" +msgstr "Ретрансльоване доменне ім'я" + +#: mailu/ui/forms.py:73 mailu/ui/templates/relay/list.html:19 +msgid "Remote host" +msgstr "Віддалений хост" + +#: mailu/ui/forms.py:82 mailu/ui/templates/user/list.html:23 +#: mailu/ui/templates/user/signup_domain.html:16 +msgid "Quota" +msgstr "Квота" + +#: mailu/ui/forms.py:83 +msgid "Allow IMAP access" +msgstr "Дозволити IMAP-доступ" + +#: mailu/ui/forms.py:84 +msgid "Allow POP3 access" +msgstr "Дозволити POP3-доступ" + +#: mailu/ui/forms.py:85 mailu/ui/forms.py:101 +#: mailu/ui/templates/user/settings.html:15 +msgid "Displayed name" +msgstr "Ім'я, що показується" + +#: mailu/ui/forms.py:87 +msgid "Enabled" +msgstr "Увімкнено" + +#: mailu/ui/forms.py:92 +msgid "Email address" +msgstr "Електронна пошта" + +#: mailu/ui/forms.py:102 +msgid "Enable spam filter" +msgstr "Увімкнути спам-фільтр" + +#: mailu/ui/forms.py:103 +msgid "Enable marking spam mails as read" +msgstr "Позначити спам як прочитане" + +#: mailu/ui/forms.py:104 +msgid "Spam filter tolerance" +msgstr "Толерантність спам-фільтра до спаму" + +#: mailu/ui/forms.py:105 +msgid "Enable forwarding" +msgstr "Увімкнути переадресацію" + +#: mailu/ui/forms.py:106 +msgid "Keep a copy of the emails" +msgstr "Зберігати копії електронних листів" + +#: mailu/ui/forms.py:107 mailu/ui/forms.py:143 +#: mailu/ui/templates/alias/list.html:21 +msgid "Destination" +msgstr "Призначення" + +#: mailu/ui/forms.py:108 +msgid "Save settings" +msgstr "Зберегти налаштування" + +#: mailu/ui/forms.py:113 +msgid "Password check" +msgstr "Перевірка пароля" + +#: mailu/ui/forms.py:114 mailu/ui/templates/sidebar.html:25 +msgid "Update password" +msgstr "Оновити пароль" + +#: mailu/ui/forms.py:118 +msgid "Enable automatic reply" +msgstr "Увімкнути автоматичну відповідь" + +#: mailu/ui/forms.py:119 +msgid "Reply subject" +msgstr "Тема відповіді" + +#: mailu/ui/forms.py:120 +msgid "Reply body" +msgstr "Текст відповіді" + +#: mailu/ui/forms.py:122 +msgid "Start of vacation" +msgstr "Початок відпустки" + +#: mailu/ui/forms.py:123 +msgid "End of vacation" +msgstr "Кінець відпустки" + +#: mailu/ui/forms.py:124 +msgid "Update" +msgstr "Оновити" + +#: mailu/ui/forms.py:129 +msgid "Your token (write it down, as it will never be displayed again)" +msgstr "" +"Ваш токен (запишіть його, оскільки він більше ніколи не буде відображатися)" + +#: mailu/ui/forms.py:134 mailu/ui/templates/token/list.html:21 +msgid "Authorized IP" +msgstr "Дозволені IP-адреси" + +#: mailu/ui/forms.py:140 +msgid "Alias" +msgstr "Псевдонім" + +#: mailu/ui/forms.py:142 +msgid "Use SQL LIKE Syntax (e.g. for catch-all aliases)" +msgstr "" +"Використовуйте синтаксис SQL LIKE (наприклад, для псевдонімів catch-all)" + +#: mailu/ui/forms.py:149 +msgid "Admin email" +msgstr "Пошта адміністратора" + +#: mailu/ui/forms.py:150 mailu/ui/forms.py:155 mailu/ui/forms.py:168 +msgid "Submit" +msgstr "Надіслати" + +#: mailu/ui/forms.py:154 +msgid "Manager email" +msgstr "Електронна пошта менеджера" + +#: mailu/ui/forms.py:159 +msgid "Protocol" +msgstr "Протокол" + +#: mailu/ui/forms.py:162 +msgid "Hostname or IP" +msgstr "Ім'я хоста або IP" + +#: mailu/ui/forms.py:163 mailu/ui/templates/client.html:20 +#: mailu/ui/templates/client.html:45 +msgid "TCP port" +msgstr "Порт TCP" + +#: mailu/ui/forms.py:164 +msgid "Enable TLS" +msgstr "Увімкнути TLS" + +#: mailu/ui/forms.py:165 mailu/ui/templates/client.html:28 +#: mailu/ui/templates/client.html:53 mailu/ui/templates/fetch/list.html:21 +msgid "Username" +msgstr "Ім'я користувача" + +#: mailu/ui/forms.py:167 +msgid "Keep emails on the server" +msgstr "Зберігати копії електронних листів" + +#: mailu/ui/forms.py:172 +msgid "Announcement subject" +msgstr "Тема оголошення" + +#: mailu/ui/forms.py:174 +msgid "Announcement body" +msgstr "Текст оголошення" + +#: mailu/ui/forms.py:176 +msgid "Send" +msgstr "Надіслати" + +#: mailu/ui/templates/announcement.html:4 +msgid "Public announcement" +msgstr "Публічне оголошення" + +#: mailu/ui/templates/antispam.html:4 mailu/ui/templates/sidebar.html:80 +#: mailu/ui/templates/user/settings.html:19 +msgid "Antispam" +msgstr "Антиспам" + +#: mailu/ui/templates/antispam.html:8 +msgid "RSPAMD status page" +msgstr "Сторінка стану RSPAMD" + +#: mailu/ui/templates/client.html:8 +msgid "configure your email client" +msgstr "налаштуйте свій поштовий клієнт" + +#: mailu/ui/templates/client.html:13 +msgid "Incoming mail" +msgstr "Вхідна пошта" + +#: mailu/ui/templates/client.html:16 mailu/ui/templates/client.html:41 +msgid "Mail protocol" +msgstr "Поштовий протокол" + +#: mailu/ui/templates/client.html:24 mailu/ui/templates/client.html:49 +msgid "Server name" +msgstr "Назва серверу" + +#: mailu/ui/templates/client.html:38 +msgid "Outgoing mail" +msgstr "Вихідна пошта" + +#: mailu/ui/templates/confirm.html:4 +msgid "Confirm action" +msgstr "Підтвердити дію" + +#: mailu/ui/templates/confirm.html:13 +#, python-format +msgid "You are about to %(action)s. Please confirm your action." +msgstr "Ви збираєтеся виконати %(action)s. Будь ласка, підтвердіть вашу дію." + +#: mailu/ui/templates/docker-error.html:4 +msgid "Docker error" +msgstr "Помилка Docker" + +#: mailu/ui/templates/docker-error.html:12 +msgid "An error occurred while talking to the Docker server." +msgstr "Виникла помилка при спілкуванні з сервером Docker." + +#: mailu/ui/templates/macros.html:129 +msgid "copy to clipboard" +msgstr "скопіювати в буфер обміну" + +#: mailu/ui/templates/sidebar.html:15 +msgid "My account" +msgstr "Мій обліковий запис" + +#: mailu/ui/templates/sidebar.html:19 mailu/ui/templates/user/list.html:37 +msgid "Settings" +msgstr "Налаштування" + +#: mailu/ui/templates/sidebar.html:31 mailu/ui/templates/user/list.html:38 +msgid "Auto-reply" +msgstr "Автовідповідь" + +#: mailu/ui/templates/fetch/list.html:4 mailu/ui/templates/sidebar.html:37 +#: mailu/ui/templates/user/list.html:39 +msgid "Fetched accounts" +msgstr "Отримані облікові записи" + +#: mailu/ui/templates/sidebar.html:43 mailu/ui/templates/token/list.html:4 +msgid "Authentication tokens" +msgstr "Токени автентифікації" + +#: mailu/ui/templates/sidebar.html:56 +msgid "Administration" +msgstr "Адміністрація" + +#: mailu/ui/templates/sidebar.html:62 +msgid "Announcement" +msgstr "Оголошення" + +#: mailu/ui/templates/sidebar.html:68 +msgid "Administrators" +msgstr "Адміністратори" + +#: mailu/ui/templates/sidebar.html:74 +msgid "Relayed domains" +msgstr "Передані домени" + +#: mailu/ui/templates/sidebar.html:88 +msgid "Mail domains" +msgstr "Поштові домени" + +#: mailu/ui/templates/sidebar.html:99 +msgid "Webmail" +msgstr "Вебпошта" + +#: mailu/ui/templates/sidebar.html:135 +msgid "Sign out" +msgstr "Вийти" + +#: mailu/ui/templates/working.html:4 +msgid "We are still working on this feature!" +msgstr "Ми все ще працюємо над цією функцією!" + +#: mailu/ui/templates/admin/create.html:4 +msgid "Add a global administrator" +msgstr "Додати глобального адміністратора" + +#: mailu/ui/templates/admin/list.html:4 +msgid "Global administrators" +msgstr "Глобальні адміністратори" + +#: mailu/ui/templates/admin/list.html:9 +msgid "Add administrator" +msgstr "Додати адміністратора" + +#: mailu/ui/templates/admin/list.html:17 mailu/ui/templates/alias/list.html:19 +#: mailu/ui/templates/alternative/list.html:19 +#: mailu/ui/templates/domain/list.html:17 mailu/ui/templates/fetch/list.html:19 +#: mailu/ui/templates/manager/list.html:19 +#: mailu/ui/templates/relay/list.html:17 mailu/ui/templates/token/list.html:19 +#: mailu/ui/templates/user/list.html:19 +msgid "Actions" +msgstr "Дії" + +#: mailu/ui/templates/admin/list.html:18 mailu/ui/templates/alias/list.html:20 +#: mailu/ui/templates/manager/list.html:20 mailu/ui/templates/user/list.html:21 +msgid "Email" +msgstr "Електронна пошта" + +#: mailu/ui/templates/admin/list.html:25 mailu/ui/templates/alias/list.html:32 +#: mailu/ui/templates/alternative/list.html:29 +#: mailu/ui/templates/domain/list.html:34 mailu/ui/templates/fetch/list.html:34 +#: mailu/ui/templates/manager/list.html:27 +#: mailu/ui/templates/relay/list.html:30 mailu/ui/templates/token/list.html:30 +#: mailu/ui/templates/user/list.html:34 +msgid "Delete" +msgstr "Видалити" + +#: mailu/ui/templates/alias/create.html:4 +msgid "Create alias" +msgstr "Створити псевдонім" + +#: mailu/ui/templates/alias/edit.html:4 +msgid "Edit alias" +msgstr "Редагувати псевдонім" + +#: mailu/ui/templates/alias/list.html:4 +msgid "Alias list" +msgstr "Список псевдонімів" + +#: mailu/ui/templates/alias/list.html:12 +msgid "Add alias" +msgstr "Додати псевдонім" + +#: mailu/ui/templates/alias/list.html:23 +#: mailu/ui/templates/alternative/list.html:21 +#: mailu/ui/templates/domain/list.html:23 mailu/ui/templates/fetch/list.html:25 +#: mailu/ui/templates/relay/list.html:21 mailu/ui/templates/token/list.html:22 +#: mailu/ui/templates/user/list.html:25 +msgid "Created" +msgstr "Створено" + +#: mailu/ui/templates/alias/list.html:24 +#: mailu/ui/templates/alternative/list.html:22 +#: mailu/ui/templates/domain/list.html:24 mailu/ui/templates/fetch/list.html:26 +#: mailu/ui/templates/relay/list.html:22 mailu/ui/templates/token/list.html:23 +#: mailu/ui/templates/user/list.html:26 +msgid "Last edit" +msgstr "Останнє редагування від" + +#: mailu/ui/templates/alias/list.html:31 mailu/ui/templates/domain/list.html:33 +#: mailu/ui/templates/fetch/list.html:33 mailu/ui/templates/relay/list.html:29 +#: mailu/ui/templates/user/list.html:33 +msgid "Edit" +msgstr "Редагувати" + +#: mailu/ui/templates/alternative/create.html:4 +msgid "Create alternative domain" +msgstr "Створити альтернативний домен" + +#: mailu/ui/templates/alternative/list.html:4 +msgid "Alternative domain list" +msgstr "Альтернативний список доменів" + +#: mailu/ui/templates/alternative/list.html:12 +msgid "Add alternative" +msgstr "Додати альтернативу" + +#: mailu/ui/templates/alternative/list.html:20 +msgid "Name" +msgstr "Ім'я" + +#: mailu/ui/templates/domain/create.html:4 +#: mailu/ui/templates/domain/list.html:9 +msgid "New domain" +msgstr "Новий домен" + +#: mailu/ui/templates/domain/details.html:4 +msgid "Domain details" +msgstr "Інформація про домен" + +#: mailu/ui/templates/domain/details.html:15 +msgid "Regenerate keys" +msgstr "Регенерувати ключі" + +#: mailu/ui/templates/domain/details.html:17 +msgid "Generate keys" +msgstr "Згенерувати ключі" + +#: mailu/ui/templates/domain/details.html:30 +msgid "DNS MX entry" +msgstr "Запис DNS MX" + +#: mailu/ui/templates/domain/details.html:34 +msgid "DNS SPF entries" +msgstr "Записи DNS SPF" + +#: mailu/ui/templates/domain/details.html:40 +msgid "DKIM public key" +msgstr "Відкритий ключ DKIM" + +#: mailu/ui/templates/domain/details.html:44 +msgid "DNS DKIM entry" +msgstr "Запис DNS DKIM" + +#: mailu/ui/templates/domain/details.html:48 +msgid "DNS DMARC entry" +msgstr "Запис DNS DMARC" + +#: mailu/ui/templates/domain/details.html:58 +msgid "DNS TLSA entry" +msgstr "Запис DNS TLSA" + +#: mailu/ui/templates/domain/details.html:63 +msgid "DNS client auto-configuration entries" +msgstr "Записи автоконфігурації DNS-клієнта" + +#: mailu/ui/templates/domain/edit.html:4 +msgid "Edit domain" +msgstr "Редагувати домен" + +#: mailu/ui/templates/domain/list.html:4 +msgid "Domain list" +msgstr "Список доменів" + +#: mailu/ui/templates/domain/list.html:18 +msgid "Manage" +msgstr "Керувати" + +#: mailu/ui/templates/domain/list.html:20 +msgid "Mailbox count" +msgstr "Кількість поштових скриньок" + +#: mailu/ui/templates/domain/list.html:21 +msgid "Alias count" +msgstr "Кількість псевдонімів" + +#: mailu/ui/templates/domain/list.html:31 +msgid "Details" +msgstr "Подробиці" + +#: mailu/ui/templates/domain/list.html:38 +msgid "Users" +msgstr "Користувачі" + +#: mailu/ui/templates/domain/list.html:39 +msgid "Aliases" +msgstr "Скорочення" + +#: mailu/ui/templates/domain/list.html:40 +msgid "Managers" +msgstr "Менеджери" + +#: mailu/ui/templates/domain/list.html:42 +msgid "Alternatives" +msgstr "Альтернативи" + +#: mailu/ui/templates/domain/signup.html:13 +msgid "" +"In order to register a new domain, you must first setup the\n" +" domain zone so that the domain MX points to this server" +msgstr "" +"Для того, щоб зареєструвати новий домен, необхідно\n" +" спочатку налаштувати доменну MX так,\n" +" щоб домен вказував на цей сервер" + +#: mailu/ui/templates/domain/signup.html:18 +msgid "" +"If you do not know how to setup an MX record for your DNS " +"zone,\n" +" please contact your DNS provider or administrator. Also, please wait a\n" +" couple minutes after the MX is set so the local server " +"cache\n" +" expires." +msgstr "" +"Якщо ви не знаєте, як налаштувати MX-запис для вашої DNS-зони,\n" +" зверніться до вашого DNS-провайдера або адміністратора. Також, будь " +"ласка, \n" +" зачекайте кілька хвилин після встановлення MX, щоб закінчився термін дії " +"кешу\n" +" локального сервера." + +#: mailu/ui/templates/fetch/create.html:4 +msgid "Add a fetched account" +msgstr "Додати знайдений обліковий запис" + +#: mailu/ui/templates/fetch/edit.html:4 +msgid "Update a fetched account" +msgstr "Оновити знайдений обліковий запис" + +#: mailu/ui/templates/fetch/list.html:12 +msgid "Add an account" +msgstr "Додати обліковий запис" + +#: mailu/ui/templates/fetch/list.html:20 +msgid "Endpoint" +msgstr "Кінцева точка" + +#: mailu/ui/templates/fetch/list.html:22 +msgid "Keep emails" +msgstr "Зберігати електронні листи" + +#: mailu/ui/templates/fetch/list.html:23 +msgid "Last check" +msgstr "Остання перевірка" + +#: mailu/ui/templates/fetch/list.html:24 +msgid "Status" +msgstr "Статус" + +#: mailu/ui/templates/fetch/list.html:38 +msgid "yes" +msgstr "так" + +#: mailu/ui/templates/fetch/list.html:38 +msgid "no" +msgstr "ні" + +#: mailu/ui/templates/manager/create.html:4 +msgid "Add a manager" +msgstr "Додати менеджера" + +#: mailu/ui/templates/manager/list.html:4 +msgid "Manager list" +msgstr "Список менеджерів" + +#: mailu/ui/templates/manager/list.html:12 +msgid "Add manager" +msgstr "Додати менеджера" + +#: mailu/ui/templates/relay/create.html:4 +msgid "New relay domain" +msgstr "Новий домен ретрансляції" + +#: mailu/ui/templates/relay/edit.html:4 +msgid "Edit relayed domain" +msgstr "Редагувати домен ретрансляції" + +#: mailu/ui/templates/relay/list.html:4 +msgid "Relayed domain list" +msgstr "Список ретрансляційний доменів" + +#: mailu/ui/templates/relay/list.html:9 +msgid "New relayed domain" +msgstr "Новий ретрансляційний домен" + +#: mailu/ui/templates/token/create.html:4 +msgid "Create an authentication token" +msgstr "Створити токен автентифікації" + +#: mailu/ui/templates/token/list.html:12 +msgid "New token" +msgstr "Новий токен" + +#: mailu/ui/templates/user/create.html:4 +msgid "New user" +msgstr "Новий користувач" + +#: mailu/ui/templates/user/create.html:15 +msgid "General" +msgstr "Основне" + +#: mailu/ui/templates/user/create.html:23 +msgid "Features and quotas" +msgstr "Можливості та квоти" + +#: mailu/ui/templates/user/edit.html:4 +msgid "Edit user" +msgstr "Редагувати користувача" + +#: mailu/ui/templates/user/list.html:4 +msgid "User list" +msgstr "Список користувачів" + +#: mailu/ui/templates/user/list.html:12 +msgid "Add user" +msgstr "Додати користувача" + +#: mailu/ui/templates/user/list.html:20 mailu/ui/templates/user/settings.html:4 +msgid "User settings" +msgstr "Налаштування користувача" + +#: mailu/ui/templates/user/list.html:22 +msgid "Features" +msgstr "Можливості" + +#: mailu/ui/templates/user/password.html:4 +msgid "Password update" +msgstr "Оновити пароль" + +#: mailu/ui/templates/user/reply.html:4 +msgid "Automatic reply" +msgstr "Увімкнути автоматичну відповідь" + +#: mailu/ui/templates/user/settings.html:27 +msgid "Auto-forward" +msgstr "Автоматичне пересилання" + +#: mailu/ui/templates/user/signup_domain.html:8 +msgid "pick a domain for the new account" +msgstr "вибрати домен для нового облікового запису" + +#: mailu/ui/templates/user/signup_domain.html:14 +msgid "Domain" +msgstr "Домен" + +#: mailu/ui/templates/user/signup_domain.html:15 +msgid "Available slots" +msgstr "Наявні слоти" From 0d95e56ebe8013cc5e5556b58bbc8d5b404dc114 Mon Sep 17 00:00:00 2001 From: Prosta4okua Date: Mon, 21 Aug 2023 10:26:24 +0300 Subject: [PATCH 008/136] Update AUTHORS.md --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 5f05f0ac..e376a730 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -22,6 +22,7 @@ Other contributors: - "SunMar" - Dutch translation - "Marty Hou" - Chinese Simple translation - [Thomas Sänger](https://github.com/HorayNarea) - German translation + - [Danylo Sydorenko]([https](https://github.com/Prosta4okua) - Ukrainian translation - [Tim Mohlmann](https://github.com/muhlemmer) - [Contributions](https://github.com/Mailu/Mailu/commits?author=muhlemmer) - [Ionut Filip](https://github.com/ionutfilip) - [Contributions](https://github.com/Mailu/Mailu/commits?author=ionutfilip) - [Ichikawa Yuriko](https://github.com/IchikawaYukko) - [Contributions](https://github.com/Mailu/Mailu/commits?author=IchikawaYukko) Japanese translation From 98a8caff2194c975d4f3765a92ffe6d73f4da153 Mon Sep 17 00:00:00 2001 From: Prosta4okua Date: Mon, 21 Aug 2023 10:26:40 +0300 Subject: [PATCH 009/136] Update AUTHORS.md --- AUTHORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.md b/AUTHORS.md index e376a730..77753f5e 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -22,7 +22,7 @@ Other contributors: - "SunMar" - Dutch translation - "Marty Hou" - Chinese Simple translation - [Thomas Sänger](https://github.com/HorayNarea) - German translation - - [Danylo Sydorenko]([https](https://github.com/Prosta4okua) - Ukrainian translation + - [Danylo Sydorenko](https://github.com/Prosta4okua) - Ukrainian translation - [Tim Mohlmann](https://github.com/muhlemmer) - [Contributions](https://github.com/Mailu/Mailu/commits?author=muhlemmer) - [Ionut Filip](https://github.com/ionutfilip) - [Contributions](https://github.com/Mailu/Mailu/commits?author=ionutfilip) - [Ichikawa Yuriko](https://github.com/IchikawaYukko) - [Contributions](https://github.com/Mailu/Mailu/commits?author=IchikawaYukko) Japanese translation From 88523c624e047ad3ba0adc6c0fd832319d23223d Mon Sep 17 00:00:00 2001 From: Prosta4okua Date: Fri, 25 Aug 2023 21:33:52 +0300 Subject: [PATCH 010/136] Update Dockerfile --- core/admin/assets/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/assets/Dockerfile b/core/admin/assets/Dockerfile index 33b9bdb9..68cc654f 100644 --- a/core/admin/assets/Dockerfile +++ b/core/admin/assets/Dockerfile @@ -11,7 +11,7 @@ RUN set -euxo pipefail \ ; npm install --no-audit --no-fund \ ; sed -i 's/#007bff/#55a5d9/' node_modules/admin-lte/build/scss/_bootstrap-variables.scss \ ; mkdir assets \ - ; for l in ca da de:de-DE en:en-GB es:es-ES eu fr:fr-FR he hu is it:it-IT ja nb_NO:no-NB nl:nl-NL pl pt:pt-PT ru sv:sv-SE zh; do \ + ; for l in ca da de:de-DE en:en-GB es:es-ES eu fr:fr-FR he hu is it:it-IT ja nb_NO:no-NB nl:nl-NL pl pt:pt-PT ru sv:sv-SE ua zh; do \ cp node_modules/datatables.net-plugins/i18n/${l#*:}.json assets/${l%:*}.json; \ done From bfdca48ab9db669e70f43ccd490da52d8d663498 Mon Sep 17 00:00:00 2001 From: Prosta4okua Date: Fri, 25 Aug 2023 21:37:26 +0300 Subject: [PATCH 011/136] Update messages.po --- .../translations/uk_UA/LC_MESSAGES/messages.po | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/admin/mailu/translations/uk_UA/LC_MESSAGES/messages.po b/core/admin/mailu/translations/uk_UA/LC_MESSAGES/messages.po index df78bb7c..dced869e 100644 --- a/core/admin/mailu/translations/uk_UA/LC_MESSAGES/messages.po +++ b/core/admin/mailu/translations/uk_UA/LC_MESSAGES/messages.po @@ -1,23 +1,23 @@ -# English translations for PROJECT. +# Ukrainian translations for PROJECT. # Copyright (C) 2016 ORGANIZATION # This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2016. +# Danylo Sydorenko , 2023. # msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"Report-Msgid-Bugs-To: sydorenkodanylo2021@gmail.com\n" "POT-Creation-Date: 2022-05-22 18:47+0200\n" "PO-Revision-Date: 2023-08-14 08:19+0300\n" -"Last-Translator: Jaume Barber \n" -"Language-Team: English \n" -"Language: uk\n" +"Last-Translator: Danylo Sydorenko \n" +"Language-Team: Ukrainian \n" +"Language: ua\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " -"n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && " +"(n%100<10 || n%100>=20) ? 1 : 2\n" "Generated-By: Babel 2.3.4\n" "X-Generator: Poedit 3.3.2\n" From 09d7e9348a31bf903f1451d6f1961cbffb7a9990 Mon Sep 17 00:00:00 2001 From: Prosta4okua Date: Fri, 25 Aug 2023 21:41:20 +0300 Subject: [PATCH 012/136] renaming --- .../mailu/translations/{uk_UA => ua}/LC_MESSAGES/messages.po | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename core/admin/mailu/translations/{uk_UA => ua}/LC_MESSAGES/messages.po (100%) diff --git a/core/admin/mailu/translations/uk_UA/LC_MESSAGES/messages.po b/core/admin/mailu/translations/ua/LC_MESSAGES/messages.po similarity index 100% rename from core/admin/mailu/translations/uk_UA/LC_MESSAGES/messages.po rename to core/admin/mailu/translations/ua/LC_MESSAGES/messages.po From f8f9d956faa674b627567aac57c29fb8bd8d732a Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Sun, 10 Sep 2023 11:24:25 +0200 Subject: [PATCH 013/136] Rename ukrainian locale to "uk" --- core/admin/assets/Dockerfile | 2 +- .../mailu/translations/{ua => uk}/LC_MESSAGES/messages.po | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename core/admin/mailu/translations/{ua => uk}/LC_MESSAGES/messages.po (99%) diff --git a/core/admin/assets/Dockerfile b/core/admin/assets/Dockerfile index 68cc654f..2be10c71 100644 --- a/core/admin/assets/Dockerfile +++ b/core/admin/assets/Dockerfile @@ -11,7 +11,7 @@ RUN set -euxo pipefail \ ; npm install --no-audit --no-fund \ ; sed -i 's/#007bff/#55a5d9/' node_modules/admin-lte/build/scss/_bootstrap-variables.scss \ ; mkdir assets \ - ; for l in ca da de:de-DE en:en-GB es:es-ES eu fr:fr-FR he hu is it:it-IT ja nb_NO:no-NB nl:nl-NL pl pt:pt-PT ru sv:sv-SE ua zh; do \ + ; for l in ca da de:de-DE en:en-GB es:es-ES eu fr:fr-FR he hu is it:it-IT ja nb_NO:no-NB nl:nl-NL pl pt:pt-PT ru sv:sv-SE uk zh; do \ cp node_modules/datatables.net-plugins/i18n/${l#*:}.json assets/${l%:*}.json; \ done diff --git a/core/admin/mailu/translations/ua/LC_MESSAGES/messages.po b/core/admin/mailu/translations/uk/LC_MESSAGES/messages.po similarity index 99% rename from core/admin/mailu/translations/ua/LC_MESSAGES/messages.po rename to core/admin/mailu/translations/uk/LC_MESSAGES/messages.po index dced869e..1db04274 100644 --- a/core/admin/mailu/translations/ua/LC_MESSAGES/messages.po +++ b/core/admin/mailu/translations/uk/LC_MESSAGES/messages.po @@ -11,8 +11,8 @@ msgstr "" "PO-Revision-Date: 2023-08-14 08:19+0300\n" "Last-Translator: Danylo Sydorenko \n" "Language-Team: Ukrainian \n" -"Language: ua\n" +"uk/>\n" +"Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" From b12bdef4b82a3d4f74966a2f7f556fb831349163 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Sun, 10 Sep 2023 11:27:06 +0200 Subject: [PATCH 014/136] Add newsfragment --- towncrier/newsfragments/2936.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 towncrier/newsfragments/2936.feature diff --git a/towncrier/newsfragments/2936.feature b/towncrier/newsfragments/2936.feature new file mode 100644 index 00000000..4f012613 --- /dev/null +++ b/towncrier/newsfragments/2936.feature @@ -0,0 +1 @@ +Add ukrainian translation From 9402a3beec6814d7e9b0b446f6f8dbf9c24d257a Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Sat, 16 Sep 2023 10:35:08 +0200 Subject: [PATCH 015/136] Upgrade webmails roundcube 1.6.3 rcmcarddav 5.1.0 snappymail 2.28.4 --- towncrier/newsfragments/2945.bugfix | 1 + webmails/Dockerfile | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 towncrier/newsfragments/2945.bugfix diff --git a/towncrier/newsfragments/2945.bugfix b/towncrier/newsfragments/2945.bugfix new file mode 100644 index 00000000..175548b3 --- /dev/null +++ b/towncrier/newsfragments/2945.bugfix @@ -0,0 +1 @@ +Upgrade webmails: roundcube 1.6.3, rcmcarddav 5.1.0, snappymail 2.28.4 diff --git a/webmails/Dockerfile b/webmails/Dockerfile index cf468314..2d36c699 100644 --- a/webmails/Dockerfile +++ b/webmails/Dockerfile @@ -26,8 +26,8 @@ RUN set -euxo pipefail \ ; mkdir -p /run/nginx /conf # roundcube -ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.6.1/roundcubemail-1.6.1-complete.tar.gz -ENV CARDDAV_URL https://github.com/mstilkerich/rcmcarddav/releases/download/v5.0.1/carddav-v5.0.1.tar.gz +ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.6.3/roundcubemail-1.6.3-complete.tar.gz +ENV CARDDAV_URL https://github.com/mstilkerich/rcmcarddav/releases/download/v5.1.0/carddav-v5.1.0.tar.gz RUN set -euxo pipefail \ ; cd /var/www \ @@ -52,7 +52,7 @@ COPY roundcube/config/config.inc.carddav.php /var/www/roundcube/plugins/carddav/ # snappymail -ENV SNAPPYMAIL_URL https://github.com/the-djmaze/snappymail/releases/download/v2.26.4/snappymail-2.26.4.tar.gz +ENV SNAPPYMAIL_URL https://github.com/the-djmaze/snappymail/releases/download/v2.28.4/snappymail-2.28.4.tar.gz RUN set -euxo pipefail \ ; mkdir /var/www/snappymail \ From b894c5ed5164788be2e6df39065d352d3005b228 Mon Sep 17 00:00:00 2001 From: spomata <49432438+spomata@users.noreply.github.com> Date: Sat, 16 Sep 2023 12:05:10 +0200 Subject: [PATCH 016/136] Fixing Website translation and more untranslated bits Both webmail and website have been translated as "Correo Web" incorrectly imho. --- core/admin/mailu/translations/es/LC_MESSAGES/messages.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/admin/mailu/translations/es/LC_MESSAGES/messages.po b/core/admin/mailu/translations/es/LC_MESSAGES/messages.po index c385905e..068712b8 100644 --- a/core/admin/mailu/translations/es/LC_MESSAGES/messages.po +++ b/core/admin/mailu/translations/es/LC_MESSAGES/messages.po @@ -53,7 +53,7 @@ msgstr "Configuración del cliente" #: mailu/sso/templates/sidebar_sso.html:16 mailu/ui/templates/sidebar.html:114 msgid "Website" -msgstr "Correo web" +msgstr "Sitio web" #: mailu/sso/templates/sidebar_sso.html:22 mailu/ui/templates/sidebar.html:120 msgid "Help" @@ -173,7 +173,7 @@ msgstr "Habilitar filtro de spam" #: mailu/ui/forms.py:103 msgid "Enable marking spam mails as read" -msgstr "" +msgstr "Habilitar marcado de correos spam como leídos" #: mailu/ui/forms.py:104 msgid "Spam filter tolerance" @@ -218,7 +218,7 @@ msgstr "Texto de la respuesta" #: mailu/ui/forms.py:122 msgid "Start of vacation" -msgstr "" +msgstr "Comienzo de las vacaciones" #: mailu/ui/forms.py:123 msgid "End of vacation" @@ -346,7 +346,7 @@ msgstr "Ocurrió un error en la comunicación con el servidor Docker." #: mailu/ui/templates/macros.html:129 msgid "copy to clipboard" -msgstr "" +msgstr "copiar en portapapeles" #: mailu/ui/templates/sidebar.html:15 msgid "My account" From 4718b95f3387fe79505d1df17b24c54891d444c3 Mon Sep 17 00:00:00 2001 From: Hossein Hosni Date: Mon, 18 Sep 2023 00:53:33 +0330 Subject: [PATCH 017/136] Add towncrier entry --- towncrier/newsfragments/2935.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 towncrier/newsfragments/2935.misc diff --git a/towncrier/newsfragments/2935.misc b/towncrier/newsfragments/2935.misc new file mode 100644 index 00000000..b6219c6d --- /dev/null +++ b/towncrier/newsfragments/2935.misc @@ -0,0 +1 @@ +Add Persian (aka Farsi) Translation From 0f61aab188f374d4962ddd595a7c8fc4e24646c3 Mon Sep 17 00:00:00 2001 From: jonathan Date: Tue, 19 Sep 2023 14:07:57 +0800 Subject: [PATCH 018/136] Add Traditional Chinese translation file. --- .../zh_TW/LC_MESSAGES/messages.po | 716 ++++++++++++++++++ 1 file changed, 716 insertions(+) create mode 100644 core/admin/mailu/translations/zh_TW/LC_MESSAGES/messages.po diff --git a/core/admin/mailu/translations/zh_TW/LC_MESSAGES/messages.po b/core/admin/mailu/translations/zh_TW/LC_MESSAGES/messages.po new file mode 100644 index 00000000..cfe42403 --- /dev/null +++ b/core/admin/mailu/translations/zh_TW/LC_MESSAGES/messages.po @@ -0,0 +1,716 @@ +msgid "" +msgstr "" +"Project-Id-Version: Mailu\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2023-09-13 15:04+0800\n" +"PO-Revision-Date: 2023-09-19 13:48+0800\n" +"Last-Translator: Jonathan Tsai \n" +"Language-Team: \n" +"Language: zh_TW\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Generated-By: Babel 2.3.4\n" +"X-Generator: Poedit 3.3.2\n" + +#: mailu/sso/forms.py:8 mailu/ui/forms.py:79 +msgid "E-mail" +msgstr "電子郵件" + +#: mailu/sso/forms.py:9 mailu/ui/forms.py:80 mailu/ui/forms.py:93 mailu/ui/forms.py:112 +#: mailu/ui/forms.py:166 mailu/ui/templates/client.html:32 +#: mailu/ui/templates/client.html:57 +msgid "Password" +msgstr "密碼" + +#: mailu/sso/forms.py:10 mailu/sso/forms.py:11 mailu/sso/templates/login.html:4 +#: mailu/ui/templates/sidebar.html:142 +msgid "Sign in" +msgstr "登入" + +#: mailu/sso/templates/base_sso.html:8 mailu/ui/templates/base.html:8 +msgid "Admin page for" +msgstr "系統管理頁面 -" + +#: mailu/sso/templates/base_sso.html:19 mailu/ui/templates/base.html:19 +msgid "toggle sidebar" +msgstr "切換側邊欄" + +#: mailu/sso/templates/base_sso.html:37 mailu/ui/templates/base.html:37 +msgid "change language" +msgstr "切換語言" + +#: mailu/sso/templates/sidebar_sso.html:4 mailu/ui/templates/sidebar.html:94 +msgid "Go to" +msgstr "跳至" + +#: mailu/sso/templates/sidebar_sso.html:9 mailu/ui/templates/client.html:4 +#: mailu/ui/templates/sidebar.html:50 mailu/ui/templates/sidebar.html:107 +msgid "Client setup" +msgstr "用戶端設定" + +#: mailu/sso/templates/sidebar_sso.html:16 mailu/ui/templates/sidebar.html:114 +msgid "Website" +msgstr "網站" + +#: mailu/sso/templates/sidebar_sso.html:22 mailu/ui/templates/sidebar.html:120 +msgid "Help" +msgstr "輔助" + +#: mailu/sso/templates/sidebar_sso.html:35 mailu/ui/templates/domain/signup.html:4 +#: mailu/ui/templates/sidebar.html:127 +msgid "Register a domain" +msgstr "註冊網域" + +#: mailu/sso/templates/sidebar_sso.html:49 mailu/ui/forms.py:95 +#: mailu/ui/templates/sidebar.html:149 mailu/ui/templates/user/signup.html:4 +#: mailu/ui/templates/user/signup_domain.html:4 +msgid "Sign up" +msgstr "註冊" + +#: mailu/ui/forms.py:33 mailu/ui/forms.py:36 +msgid "Invalid email address." +msgstr "電子郵件格式錯誤" + +#: mailu/ui/forms.py:45 +msgid "Confirm" +msgstr "確定" + +#: mailu/ui/forms.py:48 mailu/ui/forms.py:58 mailu/ui/templates/domain/details.html:26 +#: mailu/ui/templates/domain/list.html:19 mailu/ui/templates/relay/list.html:18 +msgid "Domain name" +msgstr "網域" + +#: mailu/ui/forms.py:49 +msgid "Maximum user count" +msgstr "最大用戶數" + +#: mailu/ui/forms.py:50 +msgid "Maximum alias count" +msgstr "最大別名數" + +#: mailu/ui/forms.py:51 +msgid "Maximum user quota" +msgstr "最大用戶配額" + +#: mailu/ui/forms.py:52 +msgid "Enable sign-up" +msgstr "啟用註冊" + +#: mailu/ui/forms.py:53 mailu/ui/forms.py:74 mailu/ui/forms.py:86 mailu/ui/forms.py:132 +#: mailu/ui/forms.py:144 mailu/ui/templates/alias/list.html:22 +#: mailu/ui/templates/domain/list.html:22 mailu/ui/templates/relay/list.html:20 +#: mailu/ui/templates/token/list.html:20 mailu/ui/templates/user/list.html:24 +msgid "Comment" +msgstr "說明" + +#: mailu/ui/forms.py:54 mailu/ui/forms.py:68 mailu/ui/forms.py:75 mailu/ui/forms.py:88 +#: mailu/ui/forms.py:136 mailu/ui/forms.py:145 +msgid "Save" +msgstr "儲存" + +#: mailu/ui/forms.py:59 +msgid "Initial admin" +msgstr "初始管理員" + +#: mailu/ui/forms.py:60 +msgid "Admin password" +msgstr "管理員密碼" + +#: mailu/ui/forms.py:61 mailu/ui/forms.py:81 mailu/ui/forms.py:94 +msgid "Confirm password" +msgstr "確認密碼" + +#: mailu/ui/forms.py:63 +msgid "Create" +msgstr "建立" + +#: mailu/ui/forms.py:67 +msgid "Alternative name" +msgstr "替代名稱" + +#: mailu/ui/forms.py:72 +msgid "Relayed domain name" +msgstr "轉發網網域稱" + +#: mailu/ui/forms.py:73 mailu/ui/templates/relay/list.html:19 +msgid "Remote host" +msgstr "遠端主機" + +#: mailu/ui/forms.py:82 mailu/ui/templates/user/list.html:23 +#: mailu/ui/templates/user/signup_domain.html:16 +msgid "Quota" +msgstr "配額" + +#: mailu/ui/forms.py:83 +msgid "Allow IMAP access" +msgstr "允許IMAP存取" + +#: mailu/ui/forms.py:84 +msgid "Allow POP3 access" +msgstr "允許POP3存取" + +#: mailu/ui/forms.py:85 mailu/ui/forms.py:101 mailu/ui/templates/user/settings.html:15 +msgid "Displayed name" +msgstr "顯示名稱" + +#: mailu/ui/forms.py:87 +msgid "Enabled" +msgstr "啟用" + +#: mailu/ui/forms.py:92 +msgid "Email address" +msgstr "電郵地址" + +#: mailu/ui/forms.py:102 +msgid "Enable spam filter" +msgstr "啟用垃圾郵件過濾" + +#: mailu/ui/forms.py:103 +msgid "Enable marking spam mails as read" +msgstr "啟用標註垃圾信已讀" + +#: mailu/ui/forms.py:104 +msgid "Spam filter tolerance" +msgstr "垃圾郵件過濾器容忍度" + +#: mailu/ui/forms.py:105 +msgid "Enable forwarding" +msgstr "啟用轉寄" + +#: mailu/ui/forms.py:106 +msgid "Keep a copy of the emails" +msgstr "保留電子郵件副本" + +#: mailu/ui/forms.py:107 mailu/ui/forms.py:143 mailu/ui/templates/alias/list.html:21 +msgid "Destination" +msgstr "目標地址" + +#: mailu/ui/forms.py:108 +msgid "Save settings" +msgstr "儲存設定" + +#: mailu/ui/forms.py:113 +msgid "Password check" +msgstr "檢查密碼" + +#: mailu/ui/forms.py:114 mailu/ui/templates/sidebar.html:25 +msgid "Update password" +msgstr "修改密碼" + +#: mailu/ui/forms.py:118 +msgid "Enable automatic reply" +msgstr "啟用自動回信" + +#: mailu/ui/forms.py:119 +msgid "Reply subject" +msgstr "回信主旨" + +#: mailu/ui/forms.py:120 +msgid "Reply body" +msgstr "回信內文" + +#: mailu/ui/forms.py:122 +msgid "Start of vacation" +msgstr "休假開始" + +#: mailu/ui/forms.py:123 +msgid "End of vacation" +msgstr "休假結束" + +#: mailu/ui/forms.py:124 +msgid "Update" +msgstr "修改" + +#: mailu/ui/forms.py:129 +msgid "Your token (write it down, as it will never be displayed again)" +msgstr "您的 Token(請記下,因為之後不再顯示)" + +#: mailu/ui/forms.py:134 mailu/ui/templates/token/list.html:21 +msgid "Authorized IP" +msgstr "授權IP" + +#: mailu/ui/forms.py:140 +msgid "Alias" +msgstr "別名" + +#: mailu/ui/forms.py:142 +msgid "Use SQL LIKE Syntax (e.g. for catch-all aliases)" +msgstr "使用SQL LIKE語法(例如,用於萬用字符別名)" + +#: mailu/ui/forms.py:149 +msgid "Admin email" +msgstr "系統管理員信箱" + +#: mailu/ui/forms.py:150 mailu/ui/forms.py:155 mailu/ui/forms.py:168 +msgid "Submit" +msgstr "送出" + +#: mailu/ui/forms.py:154 +msgid "Manager email" +msgstr "管理員信箱" + +#: mailu/ui/forms.py:159 +msgid "Protocol" +msgstr "協定" + +#: mailu/ui/forms.py:162 +msgid "Hostname or IP" +msgstr "主機名稱或IP" + +#: mailu/ui/forms.py:163 mailu/ui/templates/client.html:20 +#: mailu/ui/templates/client.html:45 +msgid "TCP port" +msgstr "TCP埠口" + +#: mailu/ui/forms.py:164 +msgid "Enable TLS" +msgstr "啟用TLS" + +#: mailu/ui/forms.py:165 mailu/ui/templates/client.html:28 +#: mailu/ui/templates/client.html:53 mailu/ui/templates/fetch/list.html:21 +msgid "Username" +msgstr "用户名" + +#: mailu/ui/forms.py:167 +msgid "Keep emails on the server" +msgstr "在主機上保留電子郵件" + +#: mailu/ui/forms.py:172 +msgid "Announcement subject" +msgstr "公告主旨" + +#: mailu/ui/forms.py:174 +msgid "Announcement body" +msgstr "公告內文" + +#: mailu/ui/forms.py:176 +msgid "Send" +msgstr "寄出" + +#: mailu/ui/templates/announcement.html:4 +msgid "Public announcement" +msgstr "公告通知" + +#: mailu/ui/templates/antispam.html:4 mailu/ui/templates/sidebar.html:80 +#: mailu/ui/templates/user/settings.html:19 +msgid "Antispam" +msgstr "反垃圾郵件" + +#: mailu/ui/templates/antispam.html:8 +msgid "RSPAMD status page" +msgstr "RSPAMD 狀態頁面" + +#: mailu/ui/templates/client.html:8 +msgid "configure your email client" +msgstr "設定電子郵件用戶端" + +#: mailu/ui/templates/client.html:13 +msgid "Incoming mail" +msgstr "接收郵件" + +#: mailu/ui/templates/client.html:16 mailu/ui/templates/client.html:41 +msgid "Mail protocol" +msgstr "郵件協議" + +#: mailu/ui/templates/client.html:24 mailu/ui/templates/client.html:49 +msgid "Server name" +msgstr "主機名稱" + +#: mailu/ui/templates/client.html:38 +msgid "Outgoing mail" +msgstr "發送郵件" + +#: mailu/ui/templates/confirm.html:4 +msgid "Confirm action" +msgstr "確認動作" + +#: mailu/ui/templates/confirm.html:13 +#, python-format +msgid "You are about to %(action)s. Please confirm your action." +msgstr "即將執行%(action)s,請確認您的動作。" + +#: mailu/ui/templates/docker-error.html:4 +msgid "Docker error" +msgstr "Docker錯誤" + +#: mailu/ui/templates/docker-error.html:12 +msgid "An error occurred while talking to the Docker server." +msgstr "Docker主機通信出錯" + +#: mailu/ui/templates/macros.html:129 +msgid "copy to clipboard" +msgstr "複製到剪貼簿" + +#: mailu/ui/templates/sidebar.html:15 +msgid "My account" +msgstr "我的帳號" + +#: mailu/ui/templates/sidebar.html:19 mailu/ui/templates/user/list.html:37 +msgid "Settings" +msgstr "設定" + +#: mailu/ui/templates/sidebar.html:31 mailu/ui/templates/user/list.html:38 +msgid "Auto-reply" +msgstr "自動回信" + +#: mailu/ui/templates/fetch/list.html:4 mailu/ui/templates/sidebar.html:37 +#: mailu/ui/templates/user/list.html:39 +msgid "Fetched accounts" +msgstr "代收帳號" + +#: mailu/ui/templates/sidebar.html:43 mailu/ui/templates/token/list.html:4 +msgid "Authentication tokens" +msgstr "認證Token" + +#: mailu/ui/templates/sidebar.html:56 +msgid "Administration" +msgstr "管理" + +#: mailu/ui/templates/sidebar.html:62 +msgid "Announcement" +msgstr "公告" + +#: mailu/ui/templates/sidebar.html:68 +msgid "Administrators" +msgstr "系統管理員" + +#: mailu/ui/templates/sidebar.html:74 +msgid "Relayed domains" +msgstr "轉發網域" + +#: mailu/ui/templates/sidebar.html:88 +msgid "Mail domains" +msgstr "郵件網域" + +#: mailu/ui/templates/sidebar.html:99 +msgid "Webmail" +msgstr "網頁信箱" + +#: mailu/ui/templates/sidebar.html:135 +msgid "Sign out" +msgstr "登出" + +#: mailu/ui/templates/working.html:4 +msgid "We are still working on this feature!" +msgstr "這個功能仍在開發中……" + +#: mailu/ui/templates/admin/create.html:4 +msgid "Add a global administrator" +msgstr "新增全域系統管理員" + +#: mailu/ui/templates/admin/list.html:4 +msgid "Global administrators" +msgstr "全域系統管理員" + +#: mailu/ui/templates/admin/list.html:9 +msgid "Add administrator" +msgstr "新增系統管理員" + +#: mailu/ui/templates/admin/list.html:17 mailu/ui/templates/alias/list.html:19 +#: mailu/ui/templates/alternative/list.html:19 mailu/ui/templates/domain/list.html:17 +#: mailu/ui/templates/fetch/list.html:19 mailu/ui/templates/manager/list.html:19 +#: mailu/ui/templates/relay/list.html:17 mailu/ui/templates/token/list.html:19 +#: mailu/ui/templates/user/list.html:19 +msgid "Actions" +msgstr "操作" + +#: mailu/ui/templates/admin/list.html:18 mailu/ui/templates/alias/list.html:20 +#: mailu/ui/templates/manager/list.html:20 mailu/ui/templates/user/list.html:21 +msgid "Email" +msgstr "電子郵件" + +#: mailu/ui/templates/admin/list.html:25 mailu/ui/templates/alias/list.html:32 +#: mailu/ui/templates/alternative/list.html:29 mailu/ui/templates/domain/list.html:34 +#: mailu/ui/templates/fetch/list.html:34 mailu/ui/templates/manager/list.html:27 +#: mailu/ui/templates/relay/list.html:30 mailu/ui/templates/token/list.html:30 +#: mailu/ui/templates/user/list.html:34 +msgid "Delete" +msgstr "刪除" + +#: mailu/ui/templates/alias/create.html:4 +msgid "Create alias" +msgstr "建立別名" + +#: mailu/ui/templates/alias/edit.html:4 +msgid "Edit alias" +msgstr "修改別名" + +#: mailu/ui/templates/alias/list.html:4 +msgid "Alias list" +msgstr "別名列表" + +#: mailu/ui/templates/alias/list.html:12 +msgid "Add alias" +msgstr "新增別名" + +#: mailu/ui/templates/alias/list.html:23 mailu/ui/templates/alternative/list.html:21 +#: mailu/ui/templates/domain/list.html:23 mailu/ui/templates/fetch/list.html:25 +#: mailu/ui/templates/relay/list.html:21 mailu/ui/templates/token/list.html:22 +#: mailu/ui/templates/user/list.html:25 +msgid "Created" +msgstr "已建立" + +#: mailu/ui/templates/alias/list.html:24 mailu/ui/templates/alternative/list.html:22 +#: mailu/ui/templates/domain/list.html:24 mailu/ui/templates/fetch/list.html:26 +#: mailu/ui/templates/relay/list.html:22 mailu/ui/templates/token/list.html:23 +#: mailu/ui/templates/user/list.html:26 +msgid "Last edit" +msgstr "上次修改" + +#: mailu/ui/templates/alias/list.html:31 mailu/ui/templates/domain/list.html:33 +#: mailu/ui/templates/fetch/list.html:33 mailu/ui/templates/relay/list.html:29 +#: mailu/ui/templates/user/list.html:33 +msgid "Edit" +msgstr "修改" + +#: mailu/ui/templates/alternative/create.html:4 +msgid "Create alternative domain" +msgstr "建立替代網域" + +#: mailu/ui/templates/alternative/list.html:4 +msgid "Alternative domain list" +msgstr "替代網域列表" + +#: mailu/ui/templates/alternative/list.html:12 +msgid "Add alternative" +msgstr "新增替代條目" + +#: mailu/ui/templates/alternative/list.html:20 +msgid "Name" +msgstr "名稱" + +#: mailu/ui/templates/domain/create.html:4 mailu/ui/templates/domain/list.html:9 +msgid "New domain" +msgstr "新網域" + +#: mailu/ui/templates/domain/details.html:4 +msgid "Domain details" +msgstr "網域詳細資訊" + +#: mailu/ui/templates/domain/details.html:15 +msgid "Regenerate keys" +msgstr "重新產生密鑰" + +#: mailu/ui/templates/domain/details.html:17 +msgid "Generate keys" +msgstr "產生密鑰" + +#: mailu/ui/templates/domain/details.html:30 +msgid "DNS MX entry" +msgstr "DNS MX條目" + +#: mailu/ui/templates/domain/details.html:34 +msgid "DNS SPF entries" +msgstr "DNS SPF條目" + +#: mailu/ui/templates/domain/details.html:40 +msgid "DKIM public key" +msgstr "DKIM公鑰" + +#: mailu/ui/templates/domain/details.html:44 +msgid "DNS DKIM entry" +msgstr "DNS DKIM條目" + +#: mailu/ui/templates/domain/details.html:48 +msgid "DNS DMARC entry" +msgstr "DNS DMARC條目" + +#: mailu/ui/templates/domain/details.html:58 +msgid "DNS TLSA entry" +msgstr "DNS TLSA條目" + +#: mailu/ui/templates/domain/details.html:63 +msgid "DNS client auto-configuration entries" +msgstr "DNS 客戶端自動設定條目" + +#: mailu/ui/templates/domain/edit.html:4 +msgid "Edit domain" +msgstr "修改網域" + +#: mailu/ui/templates/domain/list.html:4 +msgid "Domain list" +msgstr "網域列表" + +#: mailu/ui/templates/domain/list.html:18 +msgid "Manage" +msgstr "管理" + +#: mailu/ui/templates/domain/list.html:20 +msgid "Mailbox count" +msgstr "信箱数量" + +#: mailu/ui/templates/domain/list.html:21 +msgid "Alias count" +msgstr "別名数量" + +#: mailu/ui/templates/domain/list.html:31 +msgid "Details" +msgstr "詳細資訊" + +#: mailu/ui/templates/domain/list.html:38 +msgid "Users" +msgstr "用户" + +#: mailu/ui/templates/domain/list.html:39 +msgid "Aliases" +msgstr "別名" + +#: mailu/ui/templates/domain/list.html:40 +msgid "Managers" +msgstr "管理員" + +#: mailu/ui/templates/domain/list.html:42 +msgid "Alternatives" +msgstr "替代方案" + +#: mailu/ui/templates/domain/signup.html:13 +msgid "" +"In order to register a new domain, you must first setup the\n" +" domain zone so that the domain MX points to this server" +msgstr "為了註冊新網域,您必須先設定網域,並將網域 MX 指向這伺服主機。" + +#: mailu/ui/templates/domain/signup.html:18 +msgid "" +"If you do not know how to setup an MX record for your DNS zone,\n" +" please contact your DNS provider or administrator. Also, please wait a\n" +" couple minutes after the MX is set so the local server cache\n" +" expires." +msgstr "" +"如果您不知道如何為您的 DNS 網域設定 MX 記錄,請聯繫您的 DNS 提供商或管理員。" +"此外,請在設定 MX 之後等待幾分鐘 讓本地伺服主機的快取過期。" + +#: mailu/ui/templates/fetch/create.html:4 +msgid "Add a fetched account" +msgstr "新增代收帳號" + +#: mailu/ui/templates/fetch/edit.html:4 +msgid "Update a fetched account" +msgstr "修改代收帳號" + +#: mailu/ui/templates/fetch/list.html:12 +msgid "Add an account" +msgstr "新增一個帳號" + +#: mailu/ui/templates/fetch/list.html:20 +msgid "Endpoint" +msgstr "端點" + +#: mailu/ui/templates/fetch/list.html:22 +msgid "Keep emails" +msgstr "保留電子郵件" + +#: mailu/ui/templates/fetch/list.html:23 +msgid "Last check" +msgstr "上次檢查" + +#: mailu/ui/templates/fetch/list.html:24 +msgid "Status" +msgstr "狀態" + +#: mailu/ui/templates/fetch/list.html:38 +msgid "yes" +msgstr "是" + +#: mailu/ui/templates/fetch/list.html:38 +msgid "no" +msgstr "否" + +#: mailu/ui/templates/manager/create.html:4 +msgid "Add a manager" +msgstr "新增管理員" + +#: mailu/ui/templates/manager/list.html:4 +msgid "Manager list" +msgstr "管理員列表" + +#: mailu/ui/templates/manager/list.html:12 +msgid "Add manager" +msgstr "新增管理員" + +#: mailu/ui/templates/relay/create.html:4 +msgid "New relay domain" +msgstr "新的轉發網域" + +#: mailu/ui/templates/relay/edit.html:4 +msgid "Edit relayed domain" +msgstr "修改轉發網域" + +#: mailu/ui/templates/relay/list.html:4 +msgid "Relayed domain list" +msgstr "轉發網域列表" + +#: mailu/ui/templates/relay/list.html:9 +msgid "New relayed domain" +msgstr "新的轉發網域" + +#: mailu/ui/templates/token/create.html:4 +msgid "Create an authentication token" +msgstr "建立一個認證Token" + +#: mailu/ui/templates/token/list.html:12 +msgid "New token" +msgstr "新Token" + +#: mailu/ui/templates/user/create.html:4 +msgid "New user" +msgstr "新用户" + +#: mailu/ui/templates/user/create.html:15 +msgid "General" +msgstr "通用" + +#: mailu/ui/templates/user/create.html:23 +msgid "Features and quotas" +msgstr "功能和配額" + +#: mailu/ui/templates/user/edit.html:4 +msgid "Edit user" +msgstr "修改用户" + +#: mailu/ui/templates/user/list.html:4 +msgid "User list" +msgstr "用户列表" + +#: mailu/ui/templates/user/list.html:12 +msgid "Add user" +msgstr "新增用户" + +#: mailu/ui/templates/user/list.html:20 mailu/ui/templates/user/settings.html:4 +msgid "User settings" +msgstr "用户設定" + +#: mailu/ui/templates/user/list.html:22 +msgid "Features" +msgstr "功能" + +#: mailu/ui/templates/user/password.html:4 +msgid "Password update" +msgstr "修改密码" + +#: mailu/ui/templates/user/reply.html:4 +msgid "Automatic reply" +msgstr "自動回信" + +#: mailu/ui/templates/user/settings.html:27 +msgid "Auto-forward" +msgstr "自動轉寄" + +#: mailu/ui/templates/user/signup_domain.html:8 +msgid "pick a domain for the new account" +msgstr "為新用戶選擇一個網域" + +#: mailu/ui/templates/user/signup_domain.html:14 +msgid "Domain" +msgstr "網域" + +#: mailu/ui/templates/user/signup_domain.html:15 +msgid "Available slots" +msgstr "剩餘名額" + +#~ msgid "to access the administration tools" +#~ msgstr "存取管理工具" + +#~ msgid "Forward emails" +#~ msgstr "轉寄郵件" From 83fd29c597a81c9b8d234b9a78a69f7cf88d08bd Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 20 Sep 2023 18:00:36 +0200 Subject: [PATCH 019/136] Upgrade snuffleupagus --- core/base/Dockerfile | 2 +- towncrier/newsfragments/2950.misc | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 towncrier/newsfragments/2950.misc diff --git a/core/base/Dockerfile b/core/base/Dockerfile index 88332d3a..1db10284 100644 --- a/core/base/Dockerfile +++ b/core/base/Dockerfile @@ -27,7 +27,7 @@ CMD /bin/bash FROM system as build ARG MAILU_DEPS=prod -ARG SNUFFLEUPAGUS_VERSION=0.9.0 +ARG SNUFFLEUPAGUS_VERSION=0.10.0 ENV VIRTUAL_ENV=/app/venv diff --git a/towncrier/newsfragments/2950.misc b/towncrier/newsfragments/2950.misc new file mode 100644 index 00000000..c21a90a8 --- /dev/null +++ b/towncrier/newsfragments/2950.misc @@ -0,0 +1 @@ +Upgrade to snuffleupagus 0.10.0 From c137e1a919c98911119192a4ae12471a6e2f0316 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 20 Sep 2023 18:04:28 +0200 Subject: [PATCH 020/136] Add new rule too --- webmails/snuffleupagus.rules | 1 + 1 file changed, 1 insertion(+) diff --git a/webmails/snuffleupagus.rules b/webmails/snuffleupagus.rules index 5e619a8a..b3f69819 100644 --- a/webmails/snuffleupagus.rules +++ b/webmails/snuffleupagus.rules @@ -43,6 +43,7 @@ sp.disable_function.function("mail").param("additional_parameters").value_r("\\- # Since it's now burned, me might as well mitigate it publicly sp.disable_function.function("putenv").param("assignment").value_r("LD_").drop() +sp.disable_function.function("putenv").param("assignment").value("PATH").drop() # This one was burned in Nov 2019 - https://gist.github.com/LoadLow/90b60bd5535d6c3927bb24d5f9955b80 sp.disable_function.function("putenv").param("assignment").value_r("GCONV_").drop() From 8c11655625c7c7c137641f229504110b867b86d8 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Wed, 27 Sep 2023 14:05:12 +0000 Subject: [PATCH 021/136] Update admin password for demo server --- docs/demo.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/demo.rst b/docs/demo.rst index 46bedc0e..0619e218 100644 --- a/docs/demo.rst +++ b/docs/demo.rst @@ -35,7 +35,7 @@ Connecting to the server * Webmail : https://test.mailu.io/webmail/ * Admin UI : https://test.mailu.io/admin/ * Admin login : ``admin@test.mailu.io`` - * Admin password : ``letmein`` + * Admin password : ``Mailu+Demo@test.mailu.io`` (remove + and @test.mailu.io to get the correct password). * RESTful API: https://test.mailu.io/api * API token: ``Bearer APITokenForMailu`` From 9ae6eafb2469c8cedcd8dc5fdf77a8e2664f01df Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 4 Oct 2023 17:13:56 +0200 Subject: [PATCH 022/136] Remove the version pinning on hardened malloc --- core/base/Dockerfile | 2 +- towncrier/newsfragments/2955.misc | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 towncrier/newsfragments/2955.misc diff --git a/core/base/Dockerfile b/core/base/Dockerfile index 88332d3a..68febcd6 100644 --- a/core/base/Dockerfile +++ b/core/base/Dockerfile @@ -16,7 +16,7 @@ RUN set -euxo pipefail \ ; adduser -Sg ${MAILU_UID} -G mailu -h /app -g "mailu app" -s /bin/bash mailu \ ; apk add --no-cache bash ca-certificates curl python3 tzdata \ ; ! [[ "$(uname -m)" == x86_64 ]] \ - || apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing hardened-malloc==11-r0 + || apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing hardened-malloc WORKDIR /app diff --git a/towncrier/newsfragments/2955.misc b/towncrier/newsfragments/2955.misc new file mode 100644 index 00000000..42d8719c --- /dev/null +++ b/towncrier/newsfragments/2955.misc @@ -0,0 +1 @@ +Remove the version pinning on hardened malloc From 85cf7e6a7f8884468666f13a1de656d1c0088761 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 5 Oct 2023 18:55:33 +0200 Subject: [PATCH 023/136] Upgrade to alpine 3.18.4 --- core/base/Dockerfile | 2 +- towncrier/newsfragments/2934.bugfix | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 towncrier/newsfragments/2934.bugfix diff --git a/core/base/Dockerfile b/core/base/Dockerfile index 68febcd6..50d1f49a 100644 --- a/core/base/Dockerfile +++ b/core/base/Dockerfile @@ -3,7 +3,7 @@ # base system image (intermediate) # Note when updating the alpine tag, first manually run the workflow .github/workflows/mirror.yml. # Just run the workflow with the tag that must be synchronised. -ARG DISTRO=ghcr.io/mailu/alpine:3.17.2 +ARG DISTRO=ghcr.io/mailu/alpine:3.18.4 FROM $DISTRO as system ENV TZ=Etc/UTC LANG=C.UTF-8 diff --git a/towncrier/newsfragments/2934.bugfix b/towncrier/newsfragments/2934.bugfix new file mode 100644 index 00000000..349bc8e1 --- /dev/null +++ b/towncrier/newsfragments/2934.bugfix @@ -0,0 +1,3 @@ +Upgrade to alpine 3.18.4: this will fix a bug whereby musl wasn't retrying using TCP when it received truncated DNS replies from its upstream. In practice, this has been seen in the wild when postfix complains of: + +"Host or domain name not found. Name service error for name=outlook-com.olc.protection.outlook.com type=AAAA: Host found but no data record of requested type" From ee7bb53366ab05792ae336477b4264f5bf577b85 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Thu, 5 Oct 2023 18:12:48 +0000 Subject: [PATCH 024/136] Fix extracting of snappymail archive not working. --- webmails/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webmails/Dockerfile b/webmails/Dockerfile index 2d36c699..0732f6ad 100644 --- a/webmails/Dockerfile +++ b/webmails/Dockerfile @@ -60,7 +60,7 @@ RUN set -euxo pipefail \ ; curl -sLo /dev/shm/snappymail.tgz ${SNAPPYMAIL_URL} \ ; curl -sLo /dev/shm/snappymail.tgz.asc ${SNAPPYMAIL_URL}.asc \ ; gpg --status-fd 1 --verify /dev/shm/snappymail.tgz.asc \ - ; tar xzf /dev/shm/snappymail.tgz + ; cat /dev/shm/snappymail.tgz | tar xz # SnappyMail login COPY snappymail/login/include.php /var/www/snappymail/ From 096c0be4f704939740508d09dab04e374cd2687e Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Thu, 5 Oct 2023 18:41:54 +0000 Subject: [PATCH 025/136] Rspamd executable was moved to /usr/bin --- core/rspamd/start.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/rspamd/start.py b/core/rspamd/start.py index d6991253..54b2f192 100755 --- a/core/rspamd/start.py +++ b/core/rspamd/start.py @@ -37,4 +37,4 @@ while True: os.system("mkdir -m 755 -p /run/rspamd") os.system("chown rspamd:rspamd /run/rspamd") os.system("find /var/lib/rspamd | grep -v /filter | xargs -n1 chown rspamd:rspamd") -os.execv("/usr/sbin/rspamd", ["rspamd", "-f", "-u", "rspamd", "-g", "rspamd"]) +os.execv("/usr/bin/rspamd", ["rspamd", "-f", "-u", "rspamd", "-g", "rspamd"]) From 77c48294015bb87865cfe3720f545e4ae3a69021 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Fri, 6 Oct 2023 09:48:50 +0000 Subject: [PATCH 026/136] Hardened malloc was not disabled for oletools when an CPU with missing flags is used --- core/oletools/Dockerfile | 4 +++- core/oletools/start.py | 8 ++++++++ towncrier/newsfragments/2959.bugfix | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100755 core/oletools/start.py create mode 100644 towncrier/newsfragments/2959.bugfix diff --git a/core/oletools/Dockerfile b/core/oletools/Dockerfile index 39fd0e18..77d088a0 100644 --- a/core/oletools/Dockerfile +++ b/core/oletools/Dockerfile @@ -11,6 +11,8 @@ RUN set -euxo pipefail \ ; curl -sLo olefy.py https://raw.githubusercontent.com/HeinleinSupport/olefy/f8aac6cc55283886d153e89c8f27fae66b1c24e2/olefy.py \ ; chmod 755 olefy.py +COPY start.py / + RUN echo $VERSION >/version HEALTHCHECK --start-period=60s CMD echo PING|nc -q1 127.0.0.1 11343|grep "PONG" @@ -28,4 +30,4 @@ ENV \ OLEFY_DEL_TMP="1" \ OLEFY_DEL_TMP_FAILED="1" -CMD /app/olefy.py +CMD /start.py diff --git a/core/oletools/start.py b/core/oletools/start.py new file mode 100755 index 00000000..58d26cce --- /dev/null +++ b/core/oletools/start.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 + +import os +from socrate import system + +system.set_env() + +os.execl("/app/olefy.py", "olefy") \ No newline at end of file diff --git a/towncrier/newsfragments/2959.bugfix b/towncrier/newsfragments/2959.bugfix new file mode 100644 index 00000000..eb858956 --- /dev/null +++ b/towncrier/newsfragments/2959.bugfix @@ -0,0 +1 @@ +Hardened malloc was not disabled for oletools when an outdated CPU is used. \ No newline at end of file From e70db935ecc652692ddd1c051e87d28ad4c2c3c4 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Fri, 6 Oct 2023 10:27:31 +0000 Subject: [PATCH 027/136] Hardened malloc also requires AVX2 cpu flag --- core/base/libs/socrate/socrate/system.py | 2 +- towncrier/newsfragments/2959.bugfix | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/base/libs/socrate/socrate/system.py b/core/base/libs/socrate/socrate/system.py index e80863b8..182e09e6 100644 --- a/core/base/libs/socrate/socrate/system.py +++ b/core/base/libs/socrate/socrate/system.py @@ -66,7 +66,7 @@ def _is_compatible_with_hardened_malloc(): lines = f.readlines() for line in lines: # See #2764, we need vmovdqu - if line.startswith('flags') and ' avx ' not in line: + if line.startswith('flags') and ' avx ' not in line and ' avx2 ' not in line: return False # See #2541 if line.startswith('Features') and ' lrcpc ' not in line: diff --git a/towncrier/newsfragments/2959.bugfix b/towncrier/newsfragments/2959.bugfix index eb858956..8427d876 100644 --- a/towncrier/newsfragments/2959.bugfix +++ b/towncrier/newsfragments/2959.bugfix @@ -1 +1,2 @@ -Hardened malloc was not disabled for oletools when an outdated CPU is used. \ No newline at end of file +Hardened malloc was not disabled for oletools when CPU misses required flags. +Updated hardened malloc test that AVX2 is also required now. \ No newline at end of file From 5230c287136d2a35565219fe039883514913e234 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 6 Oct 2023 13:48:09 +0200 Subject: [PATCH 028/136] Fix letsencrypt on master --- core/nginx/letsencrypt.py | 4 ++-- towncrier/newsfragments/2962.bugfix | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 towncrier/newsfragments/2962.bugfix diff --git a/core/nginx/letsencrypt.py b/core/nginx/letsencrypt.py index a14dfa15..edf24430 100755 --- a/core/nginx/letsencrypt.py +++ b/core/nginx/letsencrypt.py @@ -47,7 +47,7 @@ time.sleep(5) class MyRequestHandler(SimpleHTTPRequestHandler): def do_GET(self): - if self.path == '/testing': + if self.path == '/.well-known/acme-challenge/testing': self.send_response(204) else: self.send_response(404) @@ -55,7 +55,7 @@ class MyRequestHandler(SimpleHTTPRequestHandler): self.end_headers() def serve_one_request(): - with HTTPServer(("0.0.0.0", 8008), MyRequestHandler) as server: + with HTTPServer(("127.0.0.1", 8008), MyRequestHandler) as server: server.handle_request() # Run certbot every day diff --git a/towncrier/newsfragments/2962.bugfix b/towncrier/newsfragments/2962.bugfix new file mode 100644 index 00000000..59149a0e --- /dev/null +++ b/towncrier/newsfragments/2962.bugfix @@ -0,0 +1 @@ +Fix letsencrypt on master From 0379857ab5db055e803176fd682e4a665669fd69 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman <52963853+Diman0@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:51:05 +0200 Subject: [PATCH 029/136] Update core/base/libs/socrate/socrate/system.py Only check for avx2 is required Co-authored-by: Florent Daigniere --- core/base/libs/socrate/socrate/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/base/libs/socrate/socrate/system.py b/core/base/libs/socrate/socrate/system.py index 182e09e6..352954e9 100644 --- a/core/base/libs/socrate/socrate/system.py +++ b/core/base/libs/socrate/socrate/system.py @@ -66,7 +66,7 @@ def _is_compatible_with_hardened_malloc(): lines = f.readlines() for line in lines: # See #2764, we need vmovdqu - if line.startswith('flags') and ' avx ' not in line and ' avx2 ' not in line: + if line.startswith('flags') and ' avx2 ' not in line: return False # See #2541 if line.startswith('Features') and ' lrcpc ' not in line: From 3985d1d0446e39d6722c6ab86d429fd260544b32 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 6 Oct 2023 14:00:58 +0200 Subject: [PATCH 030/136] clarify --- core/base/libs/socrate/socrate/system.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/base/libs/socrate/socrate/system.py b/core/base/libs/socrate/socrate/system.py index 352954e9..db8944e6 100644 --- a/core/base/libs/socrate/socrate/system.py +++ b/core/base/libs/socrate/socrate/system.py @@ -66,6 +66,7 @@ def _is_compatible_with_hardened_malloc(): lines = f.readlines() for line in lines: # See #2764, we need vmovdqu + # See #2959, we need vpunpckldq if line.startswith('flags') and ' avx2 ' not in line: return False # See #2541 @@ -80,7 +81,7 @@ def set_env(required_secrets=[], log_filters=[], log_file=None): log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", 'WARNING')) if 'LD_PRELOAD' in os.environ and not _is_compatible_with_hardened_malloc(): - log.warning('Disabling hardened-malloc on this CPU') + log.warning('Disabling hardened-malloc on this CPU: it requires Advanced Vector Extensions.') del os.environ['LD_PRELOAD'] """ This will set all the environment variables and retains only the secrets we need """ From dd58d51156383a6e1d7279f1d5c3e5f185e878a3 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Sat, 7 Oct 2023 09:35:53 +0200 Subject: [PATCH 031/136] change the logic as discussed --- core/base/Dockerfile | 1 - core/base/libs/socrate/socrate/system.py | 5 ++--- docs/compose/.env | 5 +++++ docs/compose/setup.rst | 8 ++++++++ towncrier/newsfragments/2959.bugfix | 4 ++-- 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/core/base/Dockerfile b/core/base/Dockerfile index 68febcd6..55284b4f 100644 --- a/core/base/Dockerfile +++ b/core/base/Dockerfile @@ -79,7 +79,6 @@ COPY --chown=root:root --from=build /app/snuffleupagus.so /usr/lib/php81/modules ENV \ VIRTUAL_ENV=/app/venv \ PATH="/app/venv/bin:${PATH}" \ - LD_PRELOAD="/usr/lib/libhardened_malloc.so" \ ADMIN_ADDRESS="admin" \ FRONT_ADDRESS="front" \ SMTP_ADDRESS="smtp" \ diff --git a/core/base/libs/socrate/socrate/system.py b/core/base/libs/socrate/socrate/system.py index db8944e6..6cb5bb7c 100644 --- a/core/base/libs/socrate/socrate/system.py +++ b/core/base/libs/socrate/socrate/system.py @@ -80,9 +80,8 @@ def set_env(required_secrets=[], log_filters=[], log_file=None): sys.stderr = LogFilter(sys.stderr, log_filters, log_file) log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", 'WARNING')) - if 'LD_PRELOAD' in os.environ and not _is_compatible_with_hardened_malloc(): - log.warning('Disabling hardened-malloc on this CPU: it requires Advanced Vector Extensions.') - del os.environ['LD_PRELOAD'] + if not 'LD_PRELOAD' in os.environ and _is_compatible_with_hardened_malloc(): + log.warning('Your CPU has Advanced Vector Extensions available, we recommend you enable hardened-malloc by adding LD_PRELOAD=/usr/lib/libhardened_malloc.so to your mailu.env') """ This will set all the environment variables and retains only the secrets we need """ if 'SECRET_KEY_FILE' in os.environ: diff --git a/docs/compose/.env b/docs/compose/.env index 62e767cf..1d943d2a 100644 --- a/docs/compose/.env +++ b/docs/compose/.env @@ -152,3 +152,8 @@ REJECT_UNLISTED_RECIPIENT= # Log level threshold in start.py (value: CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET) LOG_LEVEL=WARNING + +# If your CPU supports Advanced Vector Extensions +# (AVX2 on x86_64, lrcpc on ARM64), you should consider enabling +# hardened-malloc by uncommenting this +# LD_PRELOAD=/usr/lib/libhardened_malloc.so diff --git a/docs/compose/setup.rst b/docs/compose/setup.rst index f4c9c574..9b0545be 100644 --- a/docs/compose/setup.rst +++ b/docs/compose/setup.rst @@ -76,6 +76,14 @@ Review configuration variables After downloading the files, open ``mailu.env`` and review the variable settings. Make sure to read the comments in the file and instructions from the :ref:`common_cfg` page. +If your CPU supports Advanced Vector Extensions (AVX2 on x86_64, lrcpc on ARM64), you should +consider enabling hardened-malloc by adding the following to your mailu.env: + +.. code-block:: bash + + LD_PRELOAD=/usr/lib/libhardened_malloc.so + + Finish setting up TLS --------------------- diff --git a/towncrier/newsfragments/2959.bugfix b/towncrier/newsfragments/2959.bugfix index 8427d876..0c7bf28e 100644 --- a/towncrier/newsfragments/2959.bugfix +++ b/towncrier/newsfragments/2959.bugfix @@ -1,2 +1,2 @@ -Hardened malloc was not disabled for oletools when CPU misses required flags. -Updated hardened malloc test that AVX2 is also required now. \ No newline at end of file +Update hardened malloc as the original package is not available from alpine anymore. +The newer version of hardened malloc requires AVX2: Disable it by default and hint in the logs when it should be enabled instead. From 9e1bf76a0ce82ba943779f5ea8cbe75bed0604f8 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Sat, 7 Oct 2023 10:03:23 +0200 Subject: [PATCH 032/136] Maybe fix olefy --- core/oletools/Dockerfile | 6 +++++- core/oletools/start.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/core/oletools/Dockerfile b/core/oletools/Dockerfile index 77d088a0..6193888f 100644 --- a/core/oletools/Dockerfile +++ b/core/oletools/Dockerfile @@ -6,9 +6,13 @@ FROM base ARG VERSION=local LABEL version=$VERSION +ARG OLEFY_SCRIPT https://raw.githubusercontent.com/HeinleinSupport/olefy/f8aac6cc55283886d153e89c8f27fae66b1c24e2/olefy.py +ARG OLEFY_SHA256 1f5aa58b78ca7917350135b4425e5ed4d580c7051aabed1952c6afd12d0345a0 + RUN set -euxo pipefail \ ; apk add --no-cache netcat-openbsd libmagic libffi \ - ; curl -sLo olefy.py https://raw.githubusercontent.com/HeinleinSupport/olefy/f8aac6cc55283886d153e89c8f27fae66b1c24e2/olefy.py \ + ; curl -sLo olefy.py $OLEFY_SCRIPT \ + ; echo "$OLEFY_SHA256 olefy.py" |sha256sum --check \ ; chmod 755 olefy.py COPY start.py / diff --git a/core/oletools/start.py b/core/oletools/start.py index 58d26cce..b0972908 100755 --- a/core/oletools/start.py +++ b/core/oletools/start.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 -import os from socrate import system system.set_env() -os.execl("/app/olefy.py", "olefy") \ No newline at end of file +with open('/app/olefy.py') as olefy: + exec(olefy.read()) From 92e861d4fa56eafa4f2e1585c3a0c8b89b8e02d0 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Sat, 7 Oct 2023 10:09:04 +0200 Subject: [PATCH 033/136] There is no reason not to enable it ourselves. --- core/base/libs/socrate/socrate/system.py | 3 ++- docs/compose/.env | 2 +- docs/compose/setup.rst | 3 ++- towncrier/newsfragments/2959.bugfix | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/base/libs/socrate/socrate/system.py b/core/base/libs/socrate/socrate/system.py index 6cb5bb7c..53cabae6 100644 --- a/core/base/libs/socrate/socrate/system.py +++ b/core/base/libs/socrate/socrate/system.py @@ -81,7 +81,8 @@ def set_env(required_secrets=[], log_filters=[], log_file=None): log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", 'WARNING')) if not 'LD_PRELOAD' in os.environ and _is_compatible_with_hardened_malloc(): - log.warning('Your CPU has Advanced Vector Extensions available, we recommend you enable hardened-malloc by adding LD_PRELOAD=/usr/lib/libhardened_malloc.so to your mailu.env') + log.warning('Your CPU has Advanced Vector Extensions available, we recommend you enable hardened-malloc earlier in the boot process by adding LD_PRELOAD=/usr/lib/libhardened_malloc.so to your mailu.env') + os.environ['LD_PRELOAD'] = '/usr/lib/libhardened_malloc.so' """ This will set all the environment variables and retains only the secrets we need """ if 'SECRET_KEY_FILE' in os.environ: diff --git a/docs/compose/.env b/docs/compose/.env index 1d943d2a..5c54815c 100644 --- a/docs/compose/.env +++ b/docs/compose/.env @@ -155,5 +155,5 @@ LOG_LEVEL=WARNING # If your CPU supports Advanced Vector Extensions # (AVX2 on x86_64, lrcpc on ARM64), you should consider enabling -# hardened-malloc by uncommenting this +# hardened-malloc earlier by uncommenting this # LD_PRELOAD=/usr/lib/libhardened_malloc.so diff --git a/docs/compose/setup.rst b/docs/compose/setup.rst index 9b0545be..81433ba3 100644 --- a/docs/compose/setup.rst +++ b/docs/compose/setup.rst @@ -77,7 +77,8 @@ After downloading the files, open ``mailu.env`` and review the variable settings Make sure to read the comments in the file and instructions from the :ref:`common_cfg` page. If your CPU supports Advanced Vector Extensions (AVX2 on x86_64, lrcpc on ARM64), you should -consider enabling hardened-malloc by adding the following to your mailu.env: +consider enabling hardened-malloc earlier in the boot process by adding the following to +your mailu.env: .. code-block:: bash diff --git a/towncrier/newsfragments/2959.bugfix b/towncrier/newsfragments/2959.bugfix index 0c7bf28e..7a60c2a5 100644 --- a/towncrier/newsfragments/2959.bugfix +++ b/towncrier/newsfragments/2959.bugfix @@ -1,2 +1,2 @@ Update hardened malloc as the original package is not available from alpine anymore. -The newer version of hardened malloc requires AVX2: Disable it by default and hint in the logs when it should be enabled instead. +The newer version of hardened malloc requires AVX2: Disable it by default at startup and hint in the logs when it should be enabled instead. From 12e8041ba6136f0421b804439e2e3661be503b07 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Sat, 7 Oct 2023 10:25:12 +0200 Subject: [PATCH 034/136] Doh --- core/oletools/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/oletools/Dockerfile b/core/oletools/Dockerfile index 6193888f..79045175 100644 --- a/core/oletools/Dockerfile +++ b/core/oletools/Dockerfile @@ -6,8 +6,8 @@ FROM base ARG VERSION=local LABEL version=$VERSION -ARG OLEFY_SCRIPT https://raw.githubusercontent.com/HeinleinSupport/olefy/f8aac6cc55283886d153e89c8f27fae66b1c24e2/olefy.py -ARG OLEFY_SHA256 1f5aa58b78ca7917350135b4425e5ed4d580c7051aabed1952c6afd12d0345a0 +ARG OLEFY_SCRIPT=https://raw.githubusercontent.com/HeinleinSupport/olefy/f8aac6cc55283886d153e89c8f27fae66b1c24e2/olefy.py +ARG OLEFY_SHA256=1f5aa58b78ca7917350135b4425e5ed4d580c7051aabed1952c6afd12d0345a0 RUN set -euxo pipefail \ ; apk add --no-cache netcat-openbsd libmagic libffi \ From 037a79206e77ecdc7d295b3d40c94570c770efff Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Sat, 7 Oct 2023 10:55:21 +0200 Subject: [PATCH 035/136] doh2 --- core/oletools/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/oletools/Dockerfile b/core/oletools/Dockerfile index 79045175..f636cc6d 100644 --- a/core/oletools/Dockerfile +++ b/core/oletools/Dockerfile @@ -12,7 +12,7 @@ ARG OLEFY_SHA256=1f5aa58b78ca7917350135b4425e5ed4d580c7051aabed1952c6afd12d0345a RUN set -euxo pipefail \ ; apk add --no-cache netcat-openbsd libmagic libffi \ ; curl -sLo olefy.py $OLEFY_SCRIPT \ - ; echo "$OLEFY_SHA256 olefy.py" |sha256sum --check \ + ; echo "$OLEFY_SHA256 olefy.py" |sha256sum -c \ ; chmod 755 olefy.py COPY start.py / From a6f57ca3d40df93c4ac985e7ed27765e7e639342 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Sun, 8 Oct 2023 14:58:39 +0200 Subject: [PATCH 036/136] Upgrade snappymail to v2.29.1 --- towncrier/newsfragments/2959.bugfix | 1 + webmails/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/towncrier/newsfragments/2959.bugfix b/towncrier/newsfragments/2959.bugfix index 7a60c2a5..9da9c300 100644 --- a/towncrier/newsfragments/2959.bugfix +++ b/towncrier/newsfragments/2959.bugfix @@ -1,2 +1,3 @@ Update hardened malloc as the original package is not available from alpine anymore. The newer version of hardened malloc requires AVX2: Disable it by default at startup and hint in the logs when it should be enabled instead. +Upgrade snappymail to v2.29.1 diff --git a/webmails/Dockerfile b/webmails/Dockerfile index 2d36c699..0c4ec167 100644 --- a/webmails/Dockerfile +++ b/webmails/Dockerfile @@ -52,7 +52,7 @@ COPY roundcube/config/config.inc.carddav.php /var/www/roundcube/plugins/carddav/ # snappymail -ENV SNAPPYMAIL_URL https://github.com/the-djmaze/snappymail/releases/download/v2.28.4/snappymail-2.28.4.tar.gz +ENV SNAPPYMAIL_URL https://github.com/the-djmaze/snappymail/releases/download/v2.29.1/snappymail-2.29.1.tar.gz RUN set -euxo pipefail \ ; mkdir /var/www/snappymail \ From ad5b6fe27edb2da3bdcdbd2bda7e72cc30dc25d4 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Mon, 9 Oct 2023 10:53:45 +0200 Subject: [PATCH 037/136] Upgrade dovecot: fix proxying ipv6 via xclient --- core/dovecot/Dockerfile | 3 ++- towncrier/newsfragments/2918.misc | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 towncrier/newsfragments/2918.misc diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index 872e1ecf..4f682ee0 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -7,7 +7,8 @@ ARG VERSION LABEL version=$VERSION RUN set -euxo pipefail \ - ; apk add --no-cache dovecot dovecot-fts-xapian dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond rspamd-client xapian-core \ + ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main 'dovecot<2.4' dovecot-fts-xapian dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond xapian-core \ + ; apk add rspamd-client \ ; mkdir /var/lib/dovecot COPY conf/ /conf/ diff --git a/towncrier/newsfragments/2918.misc b/towncrier/newsfragments/2918.misc new file mode 100644 index 00000000..441806c4 --- /dev/null +++ b/towncrier/newsfragments/2918.misc @@ -0,0 +1 @@ +Upgrade dovecot to ensure we can proxy ipv6 via XCLIENT. From 282401e6712a5abedc30e724eb99695fe6edd6ec Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Mon, 9 Oct 2023 11:17:53 +0200 Subject: [PATCH 038/136] Maybe fix CI --- webmails/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webmails/Dockerfile b/webmails/Dockerfile index ef5df6c0..beaffd5d 100644 --- a/webmails/Dockerfile +++ b/webmails/Dockerfile @@ -19,6 +19,7 @@ RUN set -euxo pipefail \ aspell-uk aspell-ru aspell-fr aspell-de aspell-en \ ; rm /etc/nginx/http.d/default.conf \ ; rm /etc/php81/php-fpm.d/www.conf \ + ; mkdir -m 700 /root/.gnupg/ \ ; gpg --import /tmp/snappymail.asc \ ; gpg --import /tmp/roundcube.asc \ ; echo extension=snuffleupagus > /etc/php81/conf.d/snuffleupagus.ini \ @@ -33,7 +34,7 @@ RUN set -euxo pipefail \ ; cd /var/www \ ; curl -sLo /dev/shm/roundcube.tgz ${ROUNDCUBE_URL} \ ; curl -sLo /dev/shm/roundcube.tgz.asc ${ROUNDCUBE_URL}.asc \ - ; gpg --status-fd 1 --verify /dev/shm/roundcube.tgz.asc \ + ; gpg --status-fd 1 --verify /dev/shm/roundcube.tgz.asc /dev/shm/roundcube.tgz \ ; tar xzf /dev/shm/roundcube.tgz \ ; curl -sL ${CARDDAV_URL} | tar xz \ ; mv roundcubemail-* roundcube \ @@ -59,7 +60,7 @@ RUN set -euxo pipefail \ ; cd /var/www/snappymail \ ; curl -sLo /dev/shm/snappymail.tgz ${SNAPPYMAIL_URL} \ ; curl -sLo /dev/shm/snappymail.tgz.asc ${SNAPPYMAIL_URL}.asc \ - ; gpg --status-fd 1 --verify /dev/shm/snappymail.tgz.asc \ + ; gpg --status-fd 1 --verify /dev/shm/snappymail.tgz.asc /dev/shm/snappymail.tgz \ ; cat /dev/shm/snappymail.tgz | tar xz # SnappyMail login From 63a742100941e7b2fb5902babf5186ae0cb6f267 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Mon, 9 Oct 2023 15:43:35 +0200 Subject: [PATCH 039/136] Ensure that the cache is not used --- core/dovecot/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index 4f682ee0..580b7fbd 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -7,7 +7,7 @@ ARG VERSION LABEL version=$VERSION RUN set -euxo pipefail \ - ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main 'dovecot<2.4' dovecot-fts-xapian dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond xapian-core \ + ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main 'dovecot<2.4' dovecot-fts-xapian dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond xapian-core \ ; apk add rspamd-client \ ; mkdir /var/lib/dovecot From fce092d4ec2490728efb2f59f216944df8eee144 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Mon, 9 Oct 2023 16:21:50 +0200 Subject: [PATCH 040/136] Ensure we use the edge community repo too --- core/dovecot/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index 580b7fbd..190b5a65 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -7,7 +7,7 @@ ARG VERSION LABEL version=$VERSION RUN set -euxo pipefail \ - ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main 'dovecot<2.4' dovecot-fts-xapian dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond xapian-core \ + ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main 'dovecot<2.4' dovecot-fts-xapian dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond xapian-core \ ; apk add rspamd-client \ ; mkdir /var/lib/dovecot From a0eac6ea163cd8dd50e4c9b3238bf2be7d295ebc Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Mon, 9 Oct 2023 16:49:01 +0200 Subject: [PATCH 041/136] Update core/dovecot/Dockerfile Co-authored-by: Dimitri Huisman <52963853+Diman0@users.noreply.github.com> --- core/dovecot/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index 190b5a65..5aa925cc 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -8,7 +8,7 @@ LABEL version=$VERSION RUN set -euxo pipefail \ ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main 'dovecot<2.4' dovecot-fts-xapian dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond xapian-core \ - ; apk add rspamd-client \ + ; apk add --no-cache rspamd-client \ ; mkdir /var/lib/dovecot COPY conf/ /conf/ From 36236848d269584310595cd08ac97d264cb37c16 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Mon, 9 Oct 2023 17:15:28 +0200 Subject: [PATCH 042/136] switch to fts-flatcurve --- core/dovecot/Dockerfile | 3 ++- core/dovecot/conf/dovecot.conf | 5 ++--- towncrier/newsfragments/2971.bugfix | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 towncrier/newsfragments/2971.bugfix diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index 5aa925cc..2681bcff 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -7,7 +7,8 @@ ARG VERSION LABEL version=$VERSION RUN set -euxo pipefail \ - ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main 'dovecot<2.4' dovecot-fts-xapian dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond xapian-core \ + ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main 'dovecot<2.4' dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond \ + ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing dovecot-fts-flatcurve \ ; apk add --no-cache rspamd-client \ ; mkdir /var/lib/dovecot diff --git a/core/dovecot/conf/dovecot.conf b/core/dovecot/conf/dovecot.conf index e35ab4a1..efa0fd0b 100644 --- a/core/dovecot/conf/dovecot.conf +++ b/core/dovecot/conf/dovecot.conf @@ -37,7 +37,7 @@ mail_plugins = $mail_plugins quota quota_clone{{ ' ' }} zlib{{ ' ' }} {%- endif %} {%- if (FULL_TEXT_SEARCH or '').lower() not in ['off', 'false', '0'] -%} - fts fts_xapian + fts fts_flatcurve {%- endif %} default_vsz_limit = 2GB @@ -57,8 +57,7 @@ plugin { quota_clone_dict = proxy:/tmp/podop.socket:quota {% if (FULL_TEXT_SEARCH or '').lower() not in ['off', 'false', '0'] %} - fts = xapian - fts_xapian = partial=2 full=30 + fts = flatcurve fts_autoindex = yes fts_enforced = yes fts_autoindex_exclude = \Trash diff --git a/towncrier/newsfragments/2971.bugfix b/towncrier/newsfragments/2971.bugfix new file mode 100644 index 00000000..c77b034a --- /dev/null +++ b/towncrier/newsfragments/2971.bugfix @@ -0,0 +1 @@ +Switch from fts-xapian to fts-flatcurve. This should address the problem with indexes getting too big and will be the default in dovecot 2.4 From eb44783eb2d123500372f9f6220423d1f871f6c6 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Mon, 9 Oct 2023 17:40:44 +0200 Subject: [PATCH 043/136] we need this in front too --- core/nginx/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/nginx/Dockerfile b/core/nginx/Dockerfile index f2eaac81..cacb6c99 100644 --- a/core/nginx/Dockerfile +++ b/core/nginx/Dockerfile @@ -17,7 +17,8 @@ ARG VERSION LABEL version=$VERSION RUN set -euxo pipefail \ - ; apk add --no-cache certbot nginx nginx-mod-http-brotli nginx-mod-mail openssl dovecot-lua dovecot-pigeonhole-plugin dovecot-lmtpd dovecot-pop3d dovecot-submissiond + ; apk add --no-cache certbot nginx nginx-mod-http-brotli nginx-mod-mail openssl \ + ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main dovecot-lua dovecot-pigeonhole-plugin 'dovecot-lmtpd<2.4' dovecot-pop3d dovecot-submissiond COPY conf/ /conf/ COPY --from=static /static/ /static/ From 39af87dff406d0e33cf08d18ebcff9c751abd8f8 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Mon, 9 Oct 2023 17:38:15 +0200 Subject: [PATCH 044/136] Add language stops --- core/dovecot/conf/dovecot.conf | 2 ++ docs/configuration.rst | 7 +++++-- setup/flavors/compose/mailu.env | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/core/dovecot/conf/dovecot.conf b/core/dovecot/conf/dovecot.conf index efa0fd0b..20f7054c 100644 --- a/core/dovecot/conf/dovecot.conf +++ b/core/dovecot/conf/dovecot.conf @@ -58,6 +58,8 @@ plugin { {% if (FULL_TEXT_SEARCH or '').lower() not in ['off', 'false', '0'] %} fts = flatcurve + fts_languages = {% if FULL_TEXT_SEARCH %}{{ FULL_TEXT_SEARCH.split(",") | join(" ") }}{% else %}en{% endif %} + fts_tokenizers = generic email-address fts_autoindex = yes fts_enforced = yes fts_autoindex_exclude = \Trash diff --git a/docs/configuration.rst b/docs/configuration.rst index 2f7e27a7..fbf284a4 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -131,12 +131,15 @@ later classify incoming mail based on the custom part. The ``DMARC_RUA`` and ``DMARC_RUF`` are DMARC protocol specific values. They hold the localpart for DMARC rua and ruf email addresses. -Full-text search is enabled for IMAP is enabled by default. This feature can be disabled -(e.g. for performance reasons) by setting the optional variable ``FULL_TEXT_SEARCH`` to ``off``. +The ``FULL_TEXT_SEARCH`` variable (default: 'en') is a comma separated list of +language codes as defined on `fts_languages`_. This feature can be disabled +(e.g. for performance reasons) by setting the variable to ``off``. You can set a global ``DEFAULT_QUOTA`` to be used for mailboxes when the domain has no specific quota configured. +.. _`fts_languages`: https://doc.dovecot.org/settings/plugin/fts-plugin/#fts-languages + .. _web_settings: Web settings diff --git a/setup/flavors/compose/mailu.env b/setup/flavors/compose/mailu.env index 616cd89b..ef95d8f7 100644 --- a/setup/flavors/compose/mailu.env +++ b/setup/flavors/compose/mailu.env @@ -110,7 +110,9 @@ COMPRESSION={{ compression }} # change compression-level, default: 6 (value: 1-9) COMPRESSION_LEVEL={{ compression_level }} -# IMAP full-text search is enabled by default. Set the following variable to off in order to disable the feature. +# IMAP full-text search is enabled by default. +# Set the following variable to off in order to disable the feature +# or a comma separated list of language codes to support # FULL_TEXT_SEARCH=off ################################### From 3d9a8bc21f1ca02ade3f392818174f9d9bcfe0af Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Mon, 9 Oct 2023 16:19:20 +0000 Subject: [PATCH 045/136] RESTful API bugfix for domains. Add authentication tokens to API --- core/admin/mailu/api/v1/__init__.py | 3 +- .../mailu/api/v1/{domains.py => domain.py} | 16 +- core/admin/mailu/api/v1/token.py | 172 ++++++++++++++++++ 3 files changed, 182 insertions(+), 9 deletions(-) rename core/admin/mailu/api/v1/{domains.py => domain.py} (97%) create mode 100644 core/admin/mailu/api/v1/token.py diff --git a/core/admin/mailu/api/v1/__init__.py b/core/admin/mailu/api/v1/__init__.py index 44b6ec57..9b3e98f8 100644 --- a/core/admin/mailu/api/v1/__init__.py +++ b/core/admin/mailu/api/v1/__init__.py @@ -37,7 +37,8 @@ error_fields = api.model('Error', { 'message': fields.String, }) -from . import domains +from . import domain from . import alias from . import relay from . import user +from . import token diff --git a/core/admin/mailu/api/v1/domains.py b/core/admin/mailu/api/v1/domain.py similarity index 97% rename from core/admin/mailu/api/v1/domains.py rename to core/admin/mailu/api/v1/domain.py index 7043da3d..c5f98530 100644 --- a/core/admin/mailu/api/v1/domains.py +++ b/core/admin/mailu/api/v1/domain.py @@ -115,13 +115,13 @@ class Domains(Resource): if 'comment' in data: domain_new.comment = data['comment'] if 'max_users' in data: - domain_new.comment = data['max_users'] + domain_new.max_users = data['max_users'] if 'max_aliases' in data: - domain_new.comment = data['max_aliases'] + domain_new.max_aliases = data['max_aliases'] if 'max_quota_bytes' in data: - domain_new.comment = data['max_quota_bytes'] + domain_new.max_quota_bytes = data['max_quota_bytes'] if 'signup_enabled' in data: - domain_new.comment = data['signup_enabled'] + domain_new.signup_enabled = data['signup_enabled'] models.db.session.add(domain_new) #apply the changes db.session.commit() @@ -177,13 +177,13 @@ class Domain(Resource): if 'comment' in data: domain_found.comment = data['comment'] if 'max_users' in data: - domain_found.comment = data['max_users'] + domain_found.max_users = data['max_users'] if 'max_aliases' in data: - domain_found.comment = data['max_aliases'] + domain_found.max_aliases = data['max_aliases'] if 'max_quota_bytes' in data: - domain_found.comment = data['max_quota_bytes'] + domain_found.max_quota_bytes = data['max_quota_bytes'] if 'signup_enabled' in data: - domain_found.comment = data['signup_enabled'] + domain_found.signup_enabled = data['signup_enabled'] models.db.session.add(domain_found) #apply the changes diff --git a/core/admin/mailu/api/v1/token.py b/core/admin/mailu/api/v1/token.py new file mode 100644 index 00000000..5f9d8d13 --- /dev/null +++ b/core/admin/mailu/api/v1/token.py @@ -0,0 +1,172 @@ +from flask_restx import Resource, fields, marshal +import validators, datetime +import flask +from passlib import pwd + +from . import api, response_fields +from .. import common +from ... import models + +db = models.db + +token = api.namespace('token', description='Token operations') + +token_user_fields = api.model('TokenGetResponse', { + 'id': fields.String(description='The record id of the token (unique identifier)', example='1'), + 'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='user_email'), + 'comment': fields.String(description='A description for the token. This description is shown on the Authentication tokens page', example='my comment'), + 'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token', example='88.77.66.55', attribute='ip'), + 'Created': fields.String(description='The date when the token was created', example='John.Doe@example.com', attribute='created_at'), + 'Last edit': fields.String(description='The date when the token was last modifified', example='John.Doe@example.com', attribute='updated_at') +}) + +token_user_fields_post = api.model('TokenPost', { + 'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='user_email'), + 'comment': fields.String(description='A description for the token. This description is shown on the Authentication tokens page', example='my comment'), + 'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token', example='88.77.66.55', attribute='ip') +}) + +token_user_fields_post2 = api.model('TokenPost2', { + 'comment': fields.String(description='A description for the token. This description is shown on the Authentication tokens page', example='my comment'), + 'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token', example='88.77.66.55', attribute='ip') +}) + +token_user_post_response = api.model('TokenPostResponse', { + 'id': fields.String(description='The record id of the token (unique identifier)', example='1'), + 'token': fields.String(description='The created authentication token for the user.', example='2caf6607de5129e4748a2c061aee56f2', attribute='password'), + 'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='user_email'), + 'comment': fields.String(description='A description for the token. This description is shown on the Authentication tokens page', example='my comment'), + 'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token', example='88.77.66.55', attribute='ip'), + 'Created': fields.String(description='The date when the token was created', example='John.Doe@example.com', attribute='created_at') +}) + +@token.route('') +class Tokens(Resource): + @token.doc('list_tokens') + @token.marshal_with(token_user_fields, as_list=True, skip_none=True, mask=None) + @token.doc(security='Bearer') + @common.api_token_authorization + def get(self): + """List tokens""" + return models.Token.query.all() + + @token.doc('create_token') + @token.expect(token_user_fields_post) + @token.response(200, 'Success', token_user_post_response) + @token.response(400, 'Input validation exception') + @token.response(409, 'Duplicate relay', response_fields) + @token.doc(security='Bearer') + @common.api_token_authorization + def post(self): + """ Create a new token""" + data = api.payload + email = data['email'] + if not validators.email(email): + return { 'code': 400, 'message': f'Provided email address {email} is not a valid email address'}, 400 + user_found = models.User.query.get(email) + if not user_found: + return {'code': 404, 'message': f'User {email} cannot be found'}, 404 + tokens = user_found.tokens + + token_new = models.Token(user_email=data['email']) + if 'comment' in data: + token_new.comment = data['comment'] + if 'AuthorizedIP' in data: + token_new.ip = data['AuthorizedIP'].replace(' ','').split(',') + raw_password = pwd.genword(entropy=128, length=32, charset="hex") + token_new.set_password(raw_password) + models.db.session.add(token_new) + #apply the changes + db.session.commit() + flask.current_app.logger.info(f'token_new.id == {token_new.id}.') + response_dict = { + 'id' : token_new.id, + 'token' : raw_password, + 'email' : token_new.user_email, + 'comment' : token_new.comment, + 'AuthorizedIP' : token_new.ip, + 'Created': str(token_new.created_at), + } + + return response_dict + +@token.route('user/') +class Token(Resource): + @token.doc('find_tokens_of_user') + @token.marshal_with(token_user_fields, as_list=True, skip_none=True, mask=None) + @token.doc(security='Bearer') + @common.api_token_authorization + def get(self, email): + "Find tokens of user" + if not validators.email(email): + return { 'code': 400, 'message': f'Provided email address {email} is not a valid email address'}, 400 + user_found = models.User.query.get(email) + if not user_found: + return {'code': 404, 'message': f'User {email} cannot be found'}, 404 + tokens = user_found.tokens + return tokens + + @token.doc('create_token') + @token.expect(token_user_fields_post2) + @token.response(200, 'Success', token_user_post_response) + @token.response(400, 'Input validation exception') + @token.response(409, 'Duplicate relay', response_fields) + @token.doc(security='Bearer') + @common.api_token_authorization + def post(self, email): + """ Create a new token for user""" + data = api.payload + if not validators.email(email): + return { 'code': 400, 'message': f'Provided email address {email} is not a valid email address'}, 400 + user_found = models.User.query.get(email) + if not user_found: + return {'code': 404, 'message': f'User {email} cannot be found'}, 404 + + token_new = models.Token(user_email=email) + if 'comment' in data: + token_new.comment = data['comment'] + if 'AuthorizedIP' in data: + token_new.ip = token_new.ip = data['AuthorizedIP'].replace(' ','').split(',') + raw_password = pwd.genword(entropy=128, length=32, charset="hex") + token_new.set_password(raw_password) + models.db.session.add(token_new) + #apply the changes + db.session.commit() + flask.current_app.logger.info(f'token_new.id == {token_new.id}.') + response_dict = { + 'id' : token_new.id, + 'token' : raw_password, + 'email' : token_new.user_email, + 'comment' : token_new.comment, + 'AuthorizedIP' : token_new.ip, + 'Created': str(token_new.created_at), + } + return response_dict + +@token.route('/') +class Token(Resource): + @token.doc('find_token') + @token.marshal_with(token_user_fields, as_list=True, skip_none=True, mask=None) + @token.doc(security='Bearer') + @common.api_token_authorization + def get(self, token_id): + "Find token" + token = models.Token.query.get(token_id) + if not token: + return { 'code' : 404, 'message' : f'Record cannot be found for id {token_id} or invalid id provided'}, 404 + return token + + @token.doc('delete_token') + @token.response(200, 'Success', response_fields) + @token.response(400, 'Input validation exception', response_fields) + @token.response(404, 'Token not found', response_fields) + @token.doc(security='Bearer') + @common.api_token_authorization + def delete(self, token_id): + """ Delete token """ + token = models.Token.query.get(token_id) + if not token: + return { 'code' : 404, 'message' : f'Record cannot be found for id {token_id} or invalid id provided'}, 404 + db.session.delete(token) + db.session.commit() + return {'code': 200, 'message': f'Token with id {token_id} has been deleted'}, 200 From fa2fb1369df1ae5de540b957ac274090498e5210 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Mon, 9 Oct 2023 16:23:22 +0000 Subject: [PATCH 046/136] Add changelog entry --- towncrier/newsfragments/2974.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 towncrier/newsfragments/2974.feature diff --git a/towncrier/newsfragments/2974.feature b/towncrier/newsfragments/2974.feature new file mode 100644 index 00000000..5351654e --- /dev/null +++ b/towncrier/newsfragments/2974.feature @@ -0,0 +1 @@ +Enhance RESTful API with functionality for managing authentication tokens of users From 5056fa01384e92d270c3aebad4fd97d59a90b895 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Mon, 9 Oct 2023 18:32:28 +0200 Subject: [PATCH 047/136] Add fts_filters --- core/dovecot/conf/dovecot.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/dovecot/conf/dovecot.conf b/core/dovecot/conf/dovecot.conf index 20f7054c..cd89af29 100644 --- a/core/dovecot/conf/dovecot.conf +++ b/core/dovecot/conf/dovecot.conf @@ -63,6 +63,8 @@ plugin { fts_autoindex = yes fts_enforced = yes fts_autoindex_exclude = \Trash + fts_filters = normalizer-icu stopwords + fts_filters_en = lowercase english-possessive stopwords {% endif %} {% if COMPRESSION in [ 'gz', 'bz2', 'lz4', 'zstd' ] %} From 6ba8d62572eb295e981007c478f74df4abbbf49f Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 10 Oct 2023 08:20:46 +0200 Subject: [PATCH 048/136] Optimize french --- core/dovecot/conf/dovecot.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/core/dovecot/conf/dovecot.conf b/core/dovecot/conf/dovecot.conf index cd89af29..ea14ae33 100644 --- a/core/dovecot/conf/dovecot.conf +++ b/core/dovecot/conf/dovecot.conf @@ -65,6 +65,7 @@ plugin { fts_autoindex_exclude = \Trash fts_filters = normalizer-icu stopwords fts_filters_en = lowercase english-possessive stopwords + fts_filters_fr = lowercase contractions stopwords {% endif %} {% if COMPRESSION in [ 'gz', 'bz2', 'lz4', 'zstd' ] %} From 6962b9bcb5c7195c1e836a495a63bf4951abb2a4 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 10 Oct 2023 08:40:33 +0200 Subject: [PATCH 049/136] Enable FTS on attachments too --- core/dovecot/Dockerfile | 4 ++-- core/dovecot/conf/dovecot.conf | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index 2681bcff..bb7507cd 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -7,8 +7,8 @@ ARG VERSION LABEL version=$VERSION RUN set -euxo pipefail \ - ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main 'dovecot<2.4' dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond \ - ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing dovecot-fts-flatcurve \ + ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main 'dovecot<2.4' dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond poppler-utils \ + ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing dovecot-fts-flatcurve catdoc \ ; apk add --no-cache rspamd-client \ ; mkdir /var/lib/dovecot diff --git a/core/dovecot/conf/dovecot.conf b/core/dovecot/conf/dovecot.conf index ea14ae33..6f25e99a 100644 --- a/core/dovecot/conf/dovecot.conf +++ b/core/dovecot/conf/dovecot.conf @@ -66,6 +66,8 @@ plugin { fts_filters = normalizer-icu stopwords fts_filters_en = lowercase english-possessive stopwords fts_filters_fr = lowercase contractions stopwords + + fts_decoder = decode2text {% endif %} {% if COMPRESSION in [ 'gz', 'bz2', 'lz4', 'zstd' ] %} @@ -77,6 +79,16 @@ plugin { {% endif %} } +{% if FULL_TEXT_SEARCH %} +service decode2text { + executable = script /usr/libexec/dovecot/decode2text.sh + user = nobody + unix_listener decode2text { + mode = 0666 + } +} +{% endif %} + ############### # Authentication ############### From 9ae8715a6afe58b3a57ab063d2cc960630e40b25 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 10 Oct 2023 08:46:13 +0200 Subject: [PATCH 050/136] Clarify how one should upgrade --- towncrier/newsfragments/2971.bugfix | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/towncrier/newsfragments/2971.bugfix b/towncrier/newsfragments/2971.bugfix index c77b034a..f767da6b 100644 --- a/towncrier/newsfragments/2971.bugfix +++ b/towncrier/newsfragments/2971.bugfix @@ -1 +1,9 @@ Switch from fts-xapian to fts-flatcurve. This should address the problem with indexes getting too big and will be the default in dovecot 2.4 + +You can dispose of old indexes using a command such as: + +find /mailu/mail -type d -name xapian-indexes -prune -exec rm -r {} \+ + +And re-index using: + +docker compose exec imap doveadm fts rescan -A From 25964b61d59be0705c0c41c437680fd658fee314 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 10 Oct 2023 09:16:05 +0200 Subject: [PATCH 051/136] Add the working command --- towncrier/newsfragments/2971.bugfix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/towncrier/newsfragments/2971.bugfix b/towncrier/newsfragments/2971.bugfix index f767da6b..02b72758 100644 --- a/towncrier/newsfragments/2971.bugfix +++ b/towncrier/newsfragments/2971.bugfix @@ -6,4 +6,4 @@ find /mailu/mail -type d -name xapian-indexes -prune -exec rm -r {} \+ And re-index using: -docker compose exec imap doveadm fts rescan -A +docker compose exec imap doveadm index -A '*' From 16e9d152dd9a4de69d9ba155a1859aee43387cc9 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Tue, 10 Oct 2023 08:19:36 +0000 Subject: [PATCH 052/136] Forbidden_file_extension.map could not be overridden. --- core/rspamd/start.py | 3 ++- towncrier/newsfragments/2937.bugfix | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 towncrier/newsfragments/2937.bugfix diff --git a/core/rspamd/start.py b/core/rspamd/start.py index 54b2f192..67574a01 100755 --- a/core/rspamd/start.py +++ b/core/rspamd/start.py @@ -16,7 +16,8 @@ env = system.set_env() config_files = [] for rspamd_file in glob.glob("/conf/*"): conf.jinja(rspamd_file, env, os.path.join("/etc/rspamd/local.d", os.path.basename(rspamd_file))) - config_files.append(os.path.basename(rspamd_file)) + if rspamd_file != '/conf/forbidden_file_extension.map': + config_files.append(os.path.basename(rspamd_file)) for override_file in glob.glob("/overrides/*"): if os.path.basename(override_file) not in config_files: diff --git a/towncrier/newsfragments/2937.bugfix b/towncrier/newsfragments/2937.bugfix new file mode 100644 index 00000000..7ccd316c --- /dev/null +++ b/towncrier/newsfragments/2937.bugfix @@ -0,0 +1,2 @@ +forbidden_file_extension.map could not be overridden. This file can be overriden to tweak with file extensions are allowed. +The instructions on https://mailu.io/master/antispam.html#can-i-change-the-list-of-authorized-file-attachments work again. From 0718de824b5a0780aea64217c90c12d4b185690b Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Tue, 10 Oct 2023 10:46:52 +0000 Subject: [PATCH 053/136] Forgot to remove debug logging. Use rfc5737 for IP addresses in documentation. --- core/admin/mailu/api/v1/token.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/core/admin/mailu/api/v1/token.py b/core/admin/mailu/api/v1/token.py index 5f9d8d13..0f2b5b7a 100644 --- a/core/admin/mailu/api/v1/token.py +++ b/core/admin/mailu/api/v1/token.py @@ -15,7 +15,7 @@ token_user_fields = api.model('TokenGetResponse', { 'id': fields.String(description='The record id of the token (unique identifier)', example='1'), 'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='user_email'), 'comment': fields.String(description='A description for the token. This description is shown on the Authentication tokens page', example='my comment'), - 'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token', example='88.77.66.55', attribute='ip'), + 'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token.', example="['203.0.113.0/24']", attribute='ip'), 'Created': fields.String(description='The date when the token was created', example='John.Doe@example.com', attribute='created_at'), 'Last edit': fields.String(description='The date when the token was last modifified', example='John.Doe@example.com', attribute='updated_at') }) @@ -23,12 +23,12 @@ token_user_fields = api.model('TokenGetResponse', { token_user_fields_post = api.model('TokenPost', { 'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='user_email'), 'comment': fields.String(description='A description for the token. This description is shown on the Authentication tokens page', example='my comment'), - 'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token', example='88.77.66.55', attribute='ip') + 'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token.', example="['203.0.113.0/24']", attribute='ip'), }) token_user_fields_post2 = api.model('TokenPost2', { 'comment': fields.String(description='A description for the token. This description is shown on the Authentication tokens page', example='my comment'), - 'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token', example='88.77.66.55', attribute='ip') + 'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token.', example="['203.0.113.0/24']", attribute='ip'), }) token_user_post_response = api.model('TokenPostResponse', { @@ -36,7 +36,7 @@ token_user_post_response = api.model('TokenPostResponse', { 'token': fields.String(description='The created authentication token for the user.', example='2caf6607de5129e4748a2c061aee56f2', attribute='password'), 'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='user_email'), 'comment': fields.String(description='A description for the token. This description is shown on the Authentication tokens page', example='my comment'), - 'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token', example='88.77.66.55', attribute='ip'), + 'AuthorizedIP': fields.String(description='Comma separated list of white listed IP addresses or networks that may use this token.', example="['203.0.113.0/24']", attribute='ip'), 'Created': fields.String(description='The date when the token was created', example='John.Doe@example.com', attribute='created_at') }) @@ -78,7 +78,6 @@ class Tokens(Resource): models.db.session.add(token_new) #apply the changes db.session.commit() - flask.current_app.logger.info(f'token_new.id == {token_new.id}.') response_dict = { 'id' : token_new.id, 'token' : raw_password, @@ -132,7 +131,6 @@ class Token(Resource): models.db.session.add(token_new) #apply the changes db.session.commit() - flask.current_app.logger.info(f'token_new.id == {token_new.id}.') response_dict = { 'id' : token_new.id, 'token' : raw_password, From 27e357190880a7863032361c70b23bee0f2ebfb9 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 10 Oct 2023 15:04:55 +0200 Subject: [PATCH 054/136] doc --- README.md | 2 +- docs/index.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6a202940..29bd55a4 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Features Main features include: - **Standard email server**, IMAP and IMAP+, SMTP and Submission with autoconfiguration profiles for clients -- **Advanced email features**, aliases, domain aliases, custom routing +- **Advanced email features**, aliases, domain aliases, custom routing, full-text search of email attachments - **Web access**, multiple Webmails and administration interface - **User features**, aliases, auto-reply, auto-forward, fetched accounts, managesieve - **Admin features**, global admins, announcements, per-domain delegation, quotas diff --git a/docs/index.rst b/docs/index.rst index 0f16335c..ed281e3a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,7 +24,7 @@ popular groupware. Main features include: - **Standard email server**, IMAP and IMAP+, SMTP and Submission with autoconfiguration profiles for clients -- **Advanced email features**, aliases, domain aliases, custom routing +- **Advanced email features**, aliases, domain aliases, custom routing, full-text search of email attachments - **Web access**, multiple Webmails and administration interface - **User features**, aliases, auto-reply, auto-forward, fetched accounts, managesieve - **Admin features**, global admins, announcements, per-domain delegation, quotas From 954fe40134e1ff450d3ee92f05e55fb560a87a45 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 10 Oct 2023 15:07:37 +0200 Subject: [PATCH 055/136] Towncrier --- towncrier/newsfragments/2971.bugfix | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/towncrier/newsfragments/2971.bugfix b/towncrier/newsfragments/2971.bugfix index 02b72758..fd981e75 100644 --- a/towncrier/newsfragments/2971.bugfix +++ b/towncrier/newsfragments/2971.bugfix @@ -1,9 +1,12 @@ -Switch from fts-xapian to fts-flatcurve. This should address the problem with indexes getting too big and will be the default in dovecot 2.4 +- Switch from fts-xapian to fts-flatcurve. This should address the problem with indexes getting too big and will be the default in dovecot 2.4 +- Enable full-text search of email attachments -You can dispose of old indexes using a command such as: +If you would like more than english to be supported, please ensure you update your FULL_TEXT_SEARCH configuration variable. + +You may also want to dispose of old indexes using a command such as: find /mailu/mail -type d -name xapian-indexes -prune -exec rm -r {} \+ -And re-index using: +And proactively force a reindexing using: docker compose exec imap doveadm index -A '*' From 054fde8ac133ae6f99dafbd5375d9fd2280d0029 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 11 Oct 2023 14:37:27 +0200 Subject: [PATCH 056/136] Tika v1 --- core/admin/mailu/configuration.py | 2 ++ core/admin/run_dev.sh | 1 + core/base/Dockerfile | 1 + core/dovecot/Dockerfile | 4 +-- core/dovecot/conf/dovecot.conf | 17 ++++------- setup/flavors/compose/docker-compose.yml | 28 +++++++++++++++++++ setup/flavors/compose/mailu.env | 4 ++- .../templates/steps/compose/02_services.html | 9 ++++++ towncrier/newsfragments/2971.bugfix | 2 +- 9 files changed, 52 insertions(+), 16 deletions(-) diff --git a/core/admin/mailu/configuration.py b/core/admin/mailu/configuration.py index bb7080c9..d324bf8d 100644 --- a/core/admin/mailu/configuration.py +++ b/core/admin/mailu/configuration.py @@ -75,6 +75,8 @@ DEFAULT_CONFIG = { 'API': False, 'WEB_API': '/api', 'API_TOKEN': None, + 'FULL_TEXT_SEARCH': 'en', + 'FULL_TEXT_SEARCH_ATTACHMENTS': False, 'LOG_LEVEL': 'INFO', 'SESSION_KEY_BITS': 128, 'SESSION_TIMEOUT': 3600, diff --git a/core/admin/run_dev.sh b/core/admin/run_dev.sh index 0f7c6e05..3d1fc771 100755 --- a/core/admin/run_dev.sh +++ b/core/admin/run_dev.sh @@ -78,6 +78,7 @@ ENV \ \ ADMIN_ADDRESS="127.0.0.1" \ FRONT_ADDRESS="127.0.0.1" \ + FTS_ATTACHMENTS_ADDRESS="127.0.0.1" \ SMTP_ADDRESS="127.0.0.1" \ IMAP_ADDRESS="127.0.0.1" \ REDIS_ADDRESS="127.0.0.1" \ diff --git a/core/base/Dockerfile b/core/base/Dockerfile index e1087488..2f9c1142 100644 --- a/core/base/Dockerfile +++ b/core/base/Dockerfile @@ -81,6 +81,7 @@ ENV \ PATH="/app/venv/bin:${PATH}" \ ADMIN_ADDRESS="admin" \ FRONT_ADDRESS="front" \ + FTS_ATTACHMENTS_ADDRESS="tika" \ SMTP_ADDRESS="smtp" \ IMAP_ADDRESS="imap" \ OLETOOLS_ADDRESS="oletools" \ diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index bb7507cd..2681bcff 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -7,8 +7,8 @@ ARG VERSION LABEL version=$VERSION RUN set -euxo pipefail \ - ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main 'dovecot<2.4' dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond poppler-utils \ - ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing dovecot-fts-flatcurve catdoc \ + ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main 'dovecot<2.4' dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond \ + ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing dovecot-fts-flatcurve \ ; apk add --no-cache rspamd-client \ ; mkdir /var/lib/dovecot diff --git a/core/dovecot/conf/dovecot.conf b/core/dovecot/conf/dovecot.conf index 6f25e99a..c5173787 100644 --- a/core/dovecot/conf/dovecot.conf +++ b/core/dovecot/conf/dovecot.conf @@ -63,11 +63,14 @@ plugin { fts_autoindex = yes fts_enforced = yes fts_autoindex_exclude = \Trash + fts_autoindex_exclude1 = \Junk fts_filters = normalizer-icu stopwords fts_filters_en = lowercase english-possessive stopwords fts_filters_fr = lowercase contractions stopwords - - fts_decoder = decode2text + fts_header_excludes = Received DKIM-* ARC-* X-* x-* Comments Delivered-To Return-Path Authentication-Results Message-ID References In-Reply-To Thread-* Accept-Language Content-* MIME-Version + {% if FULL_TEXT_SEARCH_ATTACHMENTS %} + fts_tika = http://{{ FTS_ATTACHMENTS_ADDRESS }}:9998/tika/ + {% endif %} {% endif %} {% if COMPRESSION in [ 'gz', 'bz2', 'lz4', 'zstd' ] %} @@ -79,16 +82,6 @@ plugin { {% endif %} } -{% if FULL_TEXT_SEARCH %} -service decode2text { - executable = script /usr/libexec/dovecot/decode2text.sh - user = nobody - unix_listener decode2text { - mode = 0666 - } -} -{% endif %} - ############### # Authentication ############### diff --git a/setup/flavors/compose/docker-compose.yml b/setup/flavors/compose/docker-compose.yml index b266fec0..a81f9f44 100644 --- a/setup/flavors/compose/docker-compose.yml +++ b/setup/flavors/compose/docker-compose.yml @@ -98,8 +98,16 @@ services: volumes: - "{{ root }}/mail:/mail" - "{{ root }}/overrides/dovecot:/overrides:ro" + networks: + - default + {% if tika_enabled %} + - fts_attachments + {% endif %} depends_on: - front + {% if tika_enabled %} + - fts_attachments + {% endif %} {% if resolver_enabled %} - resolver dns: @@ -140,6 +148,21 @@ services: {% endif %} {% endif %} +{% if tika_enabled %} + fts_attachments: + image: apache/tika:2.9.0.0-full + hostname: tika + restart: always + networks: + - fts_attachments + depends_on: + {% if resolver_enabled %} + - resolver + dns: + - {{ dns }} + {% endif %} +{% endif %} + antispam: image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}rspamd:${MAILU_VERSION:-{{ version }}} hostname: antispam @@ -257,3 +280,8 @@ networks: driver: bridge internal: true {% endif %} +{% if tika_enabled %} + fts_attachments: + driver: bridge + internal: true +{% endif %} diff --git a/setup/flavors/compose/mailu.env b/setup/flavors/compose/mailu.env index ef95d8f7..9380bab6 100644 --- a/setup/flavors/compose/mailu.env +++ b/setup/flavors/compose/mailu.env @@ -113,7 +113,7 @@ COMPRESSION_LEVEL={{ compression_level }} # IMAP full-text search is enabled by default. # Set the following variable to off in order to disable the feature # or a comma separated list of language codes to support -# FULL_TEXT_SEARCH=off +FULL_TEXT_SEARCH=en ################################### # Web settings @@ -188,3 +188,5 @@ DEFAULT_SPAM_THRESHOLD=80 # This is a mandatory setting for using the RESTful API. API_TOKEN={{ api_token }} +# Whether tika should be enabled (scan/OCR email attachements) +FULL_TEXT_SEARCH_ATTACHMENTS={{ tika_enabled }} diff --git a/setup/templates/steps/compose/02_services.html b/setup/templates/steps/compose/02_services.html index 2311e4a3..afa5e726 100644 --- a/setup/templates/steps/compose/02_services.html +++ b/setup/templates/steps/compose/02_services.html @@ -64,6 +64,15 @@ the security implications caused by such an increase of attack surface.

Oletools scans documents in email attachements for malicious macros. It has a much lower memory footprint than a full-fledged anti-virus. +

+ + + Tika scans documents in email attachments, process (OCR, keyword extraction) and then index them in a way they can be efficiently searched. This requires significant ressources (RAM, CPU and storage). +
+ diff --git a/towncrier/newsfragments/2971.bugfix b/towncrier/newsfragments/2971.bugfix index fd981e75..55d775bb 100644 --- a/towncrier/newsfragments/2971.bugfix +++ b/towncrier/newsfragments/2971.bugfix @@ -1,5 +1,5 @@ - Switch from fts-xapian to fts-flatcurve. This should address the problem with indexes getting too big and will be the default in dovecot 2.4 -- Enable full-text search of email attachments +- Enable full-text search of email attachments if configured (via Tika: you'll need to re-run setup) If you would like more than english to be supported, please ensure you update your FULL_TEXT_SEARCH configuration variable. From 0026b8db241b2eb8404886963b6ec60bdd724a21 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Wed, 11 Oct 2023 15:49:52 +0000 Subject: [PATCH 057/136] Enhance RESTful API user retrieval with quota used bytes. This is the current size of the user's email box in bytes. --- core/admin/mailu/api/v1/user.py | 1 + towncrier/newsfragments/2824.feature | 1 + 2 files changed, 2 insertions(+) create mode 100644 towncrier/newsfragments/2824.feature diff --git a/core/admin/mailu/api/v1/user.py b/core/admin/mailu/api/v1/user.py index d9195e3d..441845c2 100644 --- a/core/admin/mailu/api/v1/user.py +++ b/core/admin/mailu/api/v1/user.py @@ -14,6 +14,7 @@ user_fields_get = api.model('UserGet', { 'password': fields.String(description="Hash of the user's password; Example='$bcrypt-sha256$v=2,t=2b,r=12$fmsAdJbYAD1gGQIE5nfJq.$zLkQUEs2XZfTpAEpcix/1k5UTNPm0jO'"), 'comment': fields.String(description='A description for the user. This description is shown on the Users page', example='my comment'), 'quota_bytes': fields.Integer(description='The maximum quota for the user’s email box in bytes', example='1000000000'), + 'quota_bytes_used': fields.Integer(description='The size of the user’s email box in bytes', example='5000000'), 'global_admin': fields.Boolean(description='Make the user a global administrator'), 'enabled': fields.Boolean(description='Enable the user. When an user is disabled, the user is unable to login to the Admin GUI or webmail or access his email via IMAP/POP3 or send mail'), 'change_pw_next_login': fields.Boolean(description='Force the user to change their password at next login'), diff --git a/towncrier/newsfragments/2824.feature b/towncrier/newsfragments/2824.feature new file mode 100644 index 00000000..a5c65570 --- /dev/null +++ b/towncrier/newsfragments/2824.feature @@ -0,0 +1 @@ +Enhance RESTful API user retrieval with quota used bytes. This is the current size of the user's email box in bytes. From 0a7797e96b6bdcbcd330d5106b034586c8eea53d Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 12 Oct 2023 20:42:16 +0200 Subject: [PATCH 058/136] Review --- setup/flavors/compose/mailu.env | 10 +++++----- setup/templates/steps/compose/02_services.html | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/setup/flavors/compose/mailu.env b/setup/flavors/compose/mailu.env index 9380bab6..cffafe15 100644 --- a/setup/flavors/compose/mailu.env +++ b/setup/flavors/compose/mailu.env @@ -49,19 +49,19 @@ DISABLE_STATISTICS={{ disable_statistics or 'False' }} # Expose the admin interface (value: true, false) ADMIN={{ admin_enabled or 'false' }} -# Choose which webmail to run if any (values: roundcube, snappymail, none) +# Choose which webmail to run if any (values: roundcube, snappymail, none). To enable this feature, recreate the docker-compose.yml file via setup. WEBMAIL={{ webmail_type }} # Expose the API interface (value: true, false) API={{ api_enabled or 'false' }} -# Dav server implementation (value: radicale, none) +# Dav server implementation (value: radicale, none). To enable this feature, recreate the docker-compose.yml file via setup. WEBDAV={{ webdav_enabled or 'none' }} -# Antivirus solution (value: clamav, none) +# Antivirus solution (value: clamav, none). To enable this feature, recreate the docker-compose.yml file via setup. ANTIVIRUS={{ antivirus_enabled or 'none' }} -# Scan Macros solution (value: true, false) +# Scan Macros solution (value: true, false). To enable this feature, recreate the docker-compose.yml file via setup. SCAN_MACROS={{ oletools_enabled or 'false' }} ################################### @@ -188,5 +188,5 @@ DEFAULT_SPAM_THRESHOLD=80 # This is a mandatory setting for using the RESTful API. API_TOKEN={{ api_token }} -# Whether tika should be enabled (scan/OCR email attachements) +# Whether tika should be enabled (scan/OCR email attachements). To enable this feature, recreate the docker-compose.yml file via setup. FULL_TEXT_SEARCH_ATTACHMENTS={{ tika_enabled }} diff --git a/setup/templates/steps/compose/02_services.html b/setup/templates/steps/compose/02_services.html index afa5e726..701fa82f 100644 --- a/setup/templates/steps/compose/02_services.html +++ b/setup/templates/steps/compose/02_services.html @@ -70,7 +70,7 @@ the security implications caused by such an increase of attack surface.

Enable Tika - Tika scans documents in email attachments, process (OCR, keyword extraction) and then index them in a way they can be efficiently searched. This requires significant ressources (RAM, CPU and storage). + Tika enables the functionality for searching through attachments. Tika scans documents in email attachments, process (OCR, keyword extraction) and then index them in a way they can be efficiently searched. This requires significant ressources (RAM, CPU and storage). From 7a258c19ad2a440a14326c50ec0b7ca68fe39051 Mon Sep 17 00:00:00 2001 From: jonathan Date: Fri, 13 Oct 2023 11:08:35 +0800 Subject: [PATCH 059/136] #2948 Add Traditional Chinese translation --- core/admin/assets/Dockerfile | 2 +- towncrier/newsfragments/2948.feature | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 towncrier/newsfragments/2948.feature diff --git a/core/admin/assets/Dockerfile b/core/admin/assets/Dockerfile index 74c5da5d..2ac9fe33 100644 --- a/core/admin/assets/Dockerfile +++ b/core/admin/assets/Dockerfile @@ -11,7 +11,7 @@ RUN set -euxo pipefail \ ; npm install --no-audit --no-fund \ ; sed -i 's/#007bff/#55a5d9/' node_modules/admin-lte/build/scss/_bootstrap-variables.scss \ ; mkdir assets \ - ; for l in ca da de:de-DE en:en-GB es:es-ES eu fa fr:fr-FR he hu is it:it-IT ja nb_NO:no-NB nl:nl-NL pl pt:pt-PT ru sv:sv-SE uk zh; do \ + ; for l in ca da de:de-DE en:en-GB es:es-ES eu fa fr:fr-FR he hu is it:it-IT ja nb_NO:no-NB nl:nl-NL pl pt:pt-PT ru sv:sv-SE uk zh zh_TW:zh-HANT; do \ cp node_modules/datatables.net-plugins/i18n/${l#*:}.json assets/${l%:*}.json; \ done diff --git a/towncrier/newsfragments/2948.feature b/towncrier/newsfragments/2948.feature new file mode 100644 index 00000000..5b92ab02 --- /dev/null +++ b/towncrier/newsfragments/2948.feature @@ -0,0 +1 @@ +Add Traditional Chinese translation From c56b58149b43d0ea17fd8e034298880ed76842b1 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 13 Oct 2023 09:43:34 +0200 Subject: [PATCH 060/136] Further improvements --- core/dovecot/conf/dovecot.conf | 7 +++++++ setup/flavors/compose/docker-compose.yml | 15 +++++++++++++++ towncrier/newsfragments/2971.bugfix | 3 ++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/core/dovecot/conf/dovecot.conf b/core/dovecot/conf/dovecot.conf index c5173787..0539ac68 100644 --- a/core/dovecot/conf/dovecot.conf +++ b/core/dovecot/conf/dovecot.conf @@ -58,6 +58,7 @@ plugin { {% if (FULL_TEXT_SEARCH or '').lower() not in ['off', 'false', '0'] %} fts = flatcurve + fts_index_timeout = 50s fts_languages = {% if FULL_TEXT_SEARCH %}{{ FULL_TEXT_SEARCH.split(",") | join(" ") }}{% else %}en{% endif %} fts_tokenizers = generic email-address fts_autoindex = yes @@ -82,6 +83,12 @@ plugin { {% endif %} } +service indexer-worker { + executable = /bin/nice -n 10 /usr/libexec/dovecot/indexer-worker + # TODO: maybe MAXPROC? I guess it depends on how much RAM is available + process_limit = 1 +} + ############### # Authentication ############### diff --git a/setup/flavors/compose/docker-compose.yml b/setup/flavors/compose/docker-compose.yml index a81f9f44..65bb242c 100644 --- a/setup/flavors/compose/docker-compose.yml +++ b/setup/flavors/compose/docker-compose.yml @@ -137,6 +137,10 @@ services: oletools: image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}oletools:${MAILU_VERSION:-{{ version }}} hostname: oletools + logging: + driver: journald + options: + tag: mailu-oletools restart: always networks: - noinet @@ -151,7 +155,12 @@ services: {% if tika_enabled %} fts_attachments: image: apache/tika:2.9.0.0-full + # ARM users may want iwishiwasaneagle/apache-tika-arm:2.1.0-full instead hostname: tika + logging: + driver: journald + options: + tag: mailu-tika restart: always networks: - fts_attachments @@ -161,6 +170,12 @@ services: dns: - {{ dns }} {% endif %} + healthcheck: + test: ["CMD-SHELL", "wget -nv -t1 -O /dev/null http://127.0.0.1:9998/tika || exit 1"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 10s {% endif %} antispam: diff --git a/towncrier/newsfragments/2971.bugfix b/towncrier/newsfragments/2971.bugfix index 55d775bb..e247ccbc 100644 --- a/towncrier/newsfragments/2971.bugfix +++ b/towncrier/newsfragments/2971.bugfix @@ -9,4 +9,5 @@ find /mailu/mail -type d -name xapian-indexes -prune -exec rm -r {} \+ And proactively force a reindexing using: -docker compose exec imap doveadm index -A '*' +docker compose exec imap doveadm fts rescan -A +docker compose exec imap doveadm user '*'|while read u; do docker compose exec imap doveadm index -u $u '*'; done From 371dea7c9b21bf26eea182c29c68ee3a10d73f0a Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 13 Oct 2023 13:31:36 +0200 Subject: [PATCH 061/136] ghcr.io/paperless-ngx/tika:2.9.0-full even --- setup/flavors/compose/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/flavors/compose/docker-compose.yml b/setup/flavors/compose/docker-compose.yml index 65bb242c..7dfc102a 100644 --- a/setup/flavors/compose/docker-compose.yml +++ b/setup/flavors/compose/docker-compose.yml @@ -155,7 +155,7 @@ services: {% if tika_enabled %} fts_attachments: image: apache/tika:2.9.0.0-full - # ARM users may want iwishiwasaneagle/apache-tika-arm:2.1.0-full instead + # ARM users may want ghcr.io/paperless-ngx/tika:2.9.0-full instead hostname: tika logging: driver: journald From 0be09411ab0441f37e354a3f44dc76dc4c8bbcd5 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 13 Oct 2023 13:59:21 +0200 Subject: [PATCH 062/136] Just make it work by default for everyone --- setup/flavors/compose/docker-compose.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup/flavors/compose/docker-compose.yml b/setup/flavors/compose/docker-compose.yml index 7dfc102a..daeab097 100644 --- a/setup/flavors/compose/docker-compose.yml +++ b/setup/flavors/compose/docker-compose.yml @@ -154,8 +154,7 @@ services: {% if tika_enabled %} fts_attachments: - image: apache/tika:2.9.0.0-full - # ARM users may want ghcr.io/paperless-ngx/tika:2.9.0-full instead + image: ghcr.io/paperless-ngx/tika:2.9.0-full hostname: tika logging: driver: journald From 6a2169096c8aaa5b0ea0dca4e38be4a1c9806aaf Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 13 Oct 2023 16:24:10 +0200 Subject: [PATCH 063/136] Switch to upstream's clamav image --- optional/clamav/Dockerfile | 22 ---------- optional/clamav/README.md | 12 ----- optional/clamav/conf/clamd.conf | 56 ------------------------ optional/clamav/conf/freshclam.conf | 18 -------- optional/clamav/start.py | 21 --------- setup/flavors/compose/docker-compose.yml | 32 ++++++++------ tests/build-ci.hcl | 10 ----- tests/build.hcl | 10 ----- tests/compose/filters/docker-compose.yml | 20 ++++++--- towncrier/newsfragments/2059.misc | 1 + 10 files changed, 34 insertions(+), 168 deletions(-) delete mode 100644 optional/clamav/Dockerfile delete mode 100644 optional/clamav/README.md delete mode 100644 optional/clamav/conf/clamd.conf delete mode 100644 optional/clamav/conf/freshclam.conf delete mode 100755 optional/clamav/start.py create mode 100644 towncrier/newsfragments/2059.misc diff --git a/optional/clamav/Dockerfile b/optional/clamav/Dockerfile deleted file mode 100644 index a0a67749..00000000 --- a/optional/clamav/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -# syntax=docker/dockerfile-upstream:1.4.3 - -# clamav image -FROM base - -ARG VERSION=local -LABEL version=$VERSION - -RUN set -euxo pipefail \ - ; apk add --no-cache clamav clamav-libunrar rsyslog wget - -COPY conf/ /etc/clamav/ -COPY start.py / - -RUN echo $VERSION >/version - -#EXPOSE 3310/tcp -HEALTHCHECK CMD kill -0 `cat /run/clamd.pid` && kill -0 `cat /run/freshclam.pid` - -VOLUME ["/data"] - -CMD /start.py diff --git a/optional/clamav/README.md b/optional/clamav/README.md deleted file mode 100644 index 68cd3ffe..00000000 --- a/optional/clamav/README.md +++ /dev/null @@ -1,12 +0,0 @@ -Mailu ClamAV container -====================== - -ClamAV is an open source antivirus engine for detecting trojans, viruses, -malware & other malicious threats. - -Resources ---------- - - * [Report issues](https://github.com/Mailu/Mailu/issues) and - [send Pull Requests](https://github.com/Mailu/Mailu/pulls) - in the [main Mailu repository](https://github.com/Mailu/Mailu) \ No newline at end of file diff --git a/optional/clamav/conf/clamd.conf b/optional/clamav/conf/clamd.conf deleted file mode 100644 index 061d7f6a..00000000 --- a/optional/clamav/conf/clamd.conf +++ /dev/null @@ -1,56 +0,0 @@ -############### -# General -############### - -DatabaseDirectory /data -TemporaryDirectory /tmp -LogTime yes -PidFile /run/clamd.pid -LocalSocket /tmp/clamd.sock -TCPSocket 3310 -Foreground yes - -############### -# Results -############### - -DetectPUA yes -ExcludePUA NetTool -ExcludePUA PWTool -HeuristicAlerts yes -Bytecode yes - -############### -# Scan -############### - -ScanPE yes -DisableCertCheck yes -ScanELF yes -AlertBrokenExecutables yes -ScanOLE2 yes -ScanPDF yes -ScanSWF yes -ScanMail yes -PhishingSignatures yes -PhishingScanURLs yes -ScanHTML yes -ScanArchive yes - -############### -# Scan -############### - -MaxScanSize 150M -MaxFileSize 30M -MaxRecursion 10 -MaxFiles 15000 -MaxEmbeddedPE 10M -MaxHTMLNormalize 10M -MaxHTMLNoTags 2M -MaxScriptNormalize 5M -MaxZipTypeRcg 1M -MaxPartitions 128 -MaxIconsPE 200 -PCREMatchLimit 10000 -PCRERecMatchLimit 10000 diff --git a/optional/clamav/conf/freshclam.conf b/optional/clamav/conf/freshclam.conf deleted file mode 100644 index 828163a0..00000000 --- a/optional/clamav/conf/freshclam.conf +++ /dev/null @@ -1,18 +0,0 @@ -############### -# General -############### - -DatabaseDirectory /data -UpdateLogFile /dev/stdout -LogTime yes -PidFile /run/freshclam.pid -DatabaseOwner root - -############### -# Updates -############### - -DatabaseMirror database.clamav.net -ScriptedUpdates yes -NotifyClamd /etc/clamav/clamd.conf -Bytecode yes diff --git a/optional/clamav/start.py b/optional/clamav/start.py deleted file mode 100755 index 684d9edd..00000000 --- a/optional/clamav/start.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python3 - -import os -import logging as logger -import sys -from socrate import system - -system.set_env(log_filters=r'SelfCheck: Database status OK\.$') - -# Bootstrap the database if clamav is running for the first time -if not os.path.isfile("/data/main.cvd"): - logger.info("Starting primary virus DB download") - os.system("freshclam") - -# Run the update daemon -logger.info("Starting the update daemon") -os.system("freshclam -d -c 6") - -# Run clamav -logger.info("Starting clamav") -os.system("clamd") diff --git a/setup/flavors/compose/docker-compose.yml b/setup/flavors/compose/docker-compose.yml index a81f9f44..c06d15c0 100644 --- a/setup/flavors/compose/docker-compose.yml +++ b/setup/flavors/compose/docker-compose.yml @@ -139,7 +139,7 @@ services: hostname: oletools restart: always networks: - - noinet + - oletools depends_on: {% if resolver_enabled %} - resolver @@ -172,10 +172,13 @@ services: driver: journald options: tag: mailu-antispam -{% if oletools_enabled %} networks: - default - - noinet +{% if oletools_enabled %} + - oletools +{% endif %} +{% if antivirus_enabled %} + - clamav {% endif %} volumes: - "{{ root }}/filter:/var/lib/rspamd" @@ -198,17 +201,16 @@ services: # Optional services {% if antivirus_enabled %} antivirus: - image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}clamav:${MAILU_VERSION:-{{ version }}} + image: clamav/clamav-debian:1.2.0-6 restart: always - env_file: {{ env }} + logging: + driver: journald + options: + tag: mailu-clamav + networks: + - clamav volumes: - - "{{ root }}/filter:/data" - {% if resolver_enabled %} - depends_on: - - resolver - dns: - - {{ dns }} - {% endif %} + - "{{ root }}/filter/clamav:/var/lib/clamav" {% endif %} {% if webdav_enabled %} @@ -275,8 +277,12 @@ networks: webmail: driver: bridge {% endif %} +{% if antivirus_enabled %} + clamav: + driver: bridge +{% endif %} {% if oletools_enabled %} - noinet: + oletools: driver: bridge internal: true {% endif %} diff --git a/tests/build-ci.hcl b/tests/build-ci.hcl index a78488f8..ad7f4bf7 100644 --- a/tests/build-ci.hcl +++ b/tests/build-ci.hcl @@ -49,7 +49,6 @@ group "default" { "webmail", - "antivirus", "fetchmail", "resolver", "traefik-certdumper", @@ -207,15 +206,6 @@ target "webmail" { # ----------------------------------------------------------------------------------------- # Optional images # ----------------------------------------------------------------------------------------- -target "antivirus" { - inherits = ["defaults"] - context = "optional/clamav/" - contexts = { - base = "docker-image://${DOCKER_ORG}/base:${MAILU_VERSION}" - } - tags = tag("clamav") -} - target "fetchmail" { inherits = ["defaults"] context = "optional/fetchmail/" diff --git a/tests/build.hcl b/tests/build.hcl index e7c8387a..c5a9d10b 100644 --- a/tests/build.hcl +++ b/tests/build.hcl @@ -45,7 +45,6 @@ group "default" { "webmail", - "antivirus", "fetchmail", "resolver", "traefik-certdumper", @@ -201,15 +200,6 @@ target "webmail" { # ----------------------------------------------------------------------------------------- # Optional images # ----------------------------------------------------------------------------------------- -target "antivirus" { - inherits = ["defaults"] - context = "optional/clamav/" - contexts = { - base = "target:base" - } - tags = tag("clamav") -} - target "fetchmail" { inherits = ["defaults"] context = "optional/fetchmail/" diff --git a/tests/compose/filters/docker-compose.yml b/tests/compose/filters/docker-compose.yml index 3eb2d84c..329c0282 100644 --- a/tests/compose/filters/docker-compose.yml +++ b/tests/compose/filters/docker-compose.yml @@ -70,7 +70,7 @@ services: hostname: oletools restart: always networks: - - noinet + - oletools antispam: image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}rspamd:${MAILU_VERSION:-local} @@ -78,7 +78,8 @@ services: env_file: mailu.env networks: - default - - noinet + - oletools + - clamav volumes: - "/mailu/filter:/var/lib/rspamd" - "/mailu/dkim:/dkim" @@ -88,11 +89,16 @@ services: # Optional services antivirus: - image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}clamav:${MAILU_VERSION:-local} + image: clamav/clamav-debian:1.2.0-6 restart: always - env_file: mailu.env + logging: + driver: journald + options: + tag: mailu-clamav + networks: + - clamav volumes: - - "/mailu/filter:/data" + - "/mailu/filter/clamav:/var/lib/clamav" resolver: image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}unbound:${MAILU_VERSION:-local} @@ -112,6 +118,8 @@ networks: driver: default config: - subnet: 192.168.203.0/24 - noinet: + clamav: + driver: bridge + oletools: driver: bridge internal: true diff --git a/towncrier/newsfragments/2059.misc b/towncrier/newsfragments/2059.misc new file mode 100644 index 00000000..b6797b81 --- /dev/null +++ b/towncrier/newsfragments/2059.misc @@ -0,0 +1 @@ +Switch to upstream's clamav image From 80317fde788eef1ae6a63b70ebee868523125e58 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 13 Oct 2023 16:35:13 +0200 Subject: [PATCH 064/136] Our healthcheck --- setup/flavors/compose/docker-compose.yml | 6 ++++++ tests/compose/filters/docker-compose.yml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/setup/flavors/compose/docker-compose.yml b/setup/flavors/compose/docker-compose.yml index c06d15c0..1abe9c0d 100644 --- a/setup/flavors/compose/docker-compose.yml +++ b/setup/flavors/compose/docker-compose.yml @@ -211,6 +211,12 @@ services: - clamav volumes: - "{{ root }}/filter/clamav:/var/lib/clamav" + healthcheck: + test: ["CMD-SHELL", "kill -0 `cat /var/run/clamd.pid` && kill -0 `cat /var/run/freshclam.pid`"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 20s {% endif %} {% if webdav_enabled %} diff --git a/tests/compose/filters/docker-compose.yml b/tests/compose/filters/docker-compose.yml index 329c0282..8a3d7016 100644 --- a/tests/compose/filters/docker-compose.yml +++ b/tests/compose/filters/docker-compose.yml @@ -99,6 +99,12 @@ services: - clamav volumes: - "/mailu/filter/clamav:/var/lib/clamav" + healthcheck: + test: ["CMD-SHELL", "kill -0 `cat /var/run/clamd.pid` && kill -0 `cat /var/run/freshclam.pid`"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 20s resolver: image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}unbound:${MAILU_VERSION:-local} From 4da05b93ab8da0f4ade816235ccae7fbda7cb8b6 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 13 Oct 2023 16:42:45 +0200 Subject: [PATCH 065/136] doh --- setup/flavors/compose/docker-compose.yml | 4 ++-- tests/compose/filters/docker-compose.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/setup/flavors/compose/docker-compose.yml b/setup/flavors/compose/docker-compose.yml index 1abe9c0d..32fd169b 100644 --- a/setup/flavors/compose/docker-compose.yml +++ b/setup/flavors/compose/docker-compose.yml @@ -212,11 +212,11 @@ services: volumes: - "{{ root }}/filter/clamav:/var/lib/clamav" healthcheck: - test: ["CMD-SHELL", "kill -0 `cat /var/run/clamd.pid` && kill -0 `cat /var/run/freshclam.pid`"] + test: ["CMD-SHELL", "kill -0 `cat /tmp/clamd.pid` && kill -0 `cat /tmp/freshclam.pid`"] interval: 10s timeout: 5s retries: 3 - start_period: 20s + start_period: 10s {% endif %} {% if webdav_enabled %} diff --git a/tests/compose/filters/docker-compose.yml b/tests/compose/filters/docker-compose.yml index 8a3d7016..490ce0ee 100644 --- a/tests/compose/filters/docker-compose.yml +++ b/tests/compose/filters/docker-compose.yml @@ -100,11 +100,11 @@ services: volumes: - "/mailu/filter/clamav:/var/lib/clamav" healthcheck: - test: ["CMD-SHELL", "kill -0 `cat /var/run/clamd.pid` && kill -0 `cat /var/run/freshclam.pid`"] + test: ["CMD-SHELL", "kill -0 `cat /tmp/clamd.pid` && kill -0 `cat /tmp/freshclam.pid`"] interval: 10s timeout: 5s retries: 3 - start_period: 20s + start_period: 10s resolver: image: ${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}unbound:${MAILU_VERSION:-local} From 98e1142bea8e3f5ef46228095f877bb6c7f1584d Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 13 Oct 2023 16:56:12 +0200 Subject: [PATCH 066/136] update the github action too --- .github/workflows/build_test_deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 10c248d7..9c2ff02d 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -474,7 +474,7 @@ jobs: strategy: fail-fast: false matrix: - target: ["setup", "docs", "fetchmail", "webmail", "admin", "traefik-certdumper", "radicale", "clamav", "rspamd", "oletools", "postfix", "dovecot", "unbound", "nginx"] + target: ["setup", "docs", "fetchmail", "webmail", "admin", "traefik-certdumper", "radicale", "rspamd", "oletools", "postfix", "dovecot", "unbound", "nginx"] steps: - uses: actions/checkout@v3 - name: Retrieve global variables From 2a570d0f6f9f5d799ffcaaf68963e729830e03b9 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Mon, 16 Oct 2023 12:48:20 +0200 Subject: [PATCH 067/136] Roundcube 1.6.4 --- core/admin/start.py | 11 +++++++++++ docs/faq.rst | 7 +++++++ towncrier/newsfragments/2985.misc | 2 ++ webmails/Dockerfile | 2 +- 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 towncrier/newsfragments/2985.misc diff --git a/core/admin/start.py b/core/admin/start.py index 5d403a3e..6f3094b1 100755 --- a/core/admin/start.py +++ b/core/admin/start.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 import os +import os.path +import time import logging as log import sys from socrate import system @@ -23,6 +25,14 @@ if account is not None and domain is not None and password is not None: log.info("Creating initial admin account %s@%s with mode %s", account, domain, mode) os.system("flask mailu admin %s %s '%s' --mode %s" % (account, domain, password, mode)) +def test_unsupported(): + import codecs + if os.path.isfile(codecs.decode('/.qbpxrerai', 'rot13')) or os.environ.get(codecs.decode('V_XABJ_ZL_FRGHC_QBRFAG_SVG_ERDHVERZRAGF_NAQ_JBAG_SVYR_VFFHRF_JVGUBHG_CNGPURF', 'rot13'), None): + return + print('Your system is not supported. Please start by reading the documentation and then http://www.catb.org/~esr/faqs/smart-questions.html') + while True: + time.sleep(5) + def test_DNS(): import dns.resolver import dns.exception @@ -50,6 +60,7 @@ def test_DNS(): time.sleep(5) test_DNS() +test_unsupported() cmdline = [ "gunicorn", diff --git a/docs/faq.rst b/docs/faq.rst index 8529c752..3eb7bafc 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -880,6 +880,13 @@ We have seen a fair amount of support requests related to the following: .. _`netplan does not play nicely with docker`: https://github.com/Mailu/Mailu/issues/2868#issuecomment-1606014184 +How can I use Mailu without docker? +``````````````````````````````````` + +Running Mailu without docker is not supported. If you want to do so, you need to export an environment variable called ``I_KNOW_MY_SETUP_DOESNT_FIT_REQUIREMENTS_AND_WONT_FILE_ISSUES_WITHOUT_PATCHES`` to the ``admin`` container. + +We welcome patches but do not have the bandwidth to test and fix issues related to your unsupported setup. If you do want to help, we welcome new maintainers: please get in touch. + How can I add more languages to roundcube's spellchecker? ````````````````````````````````````````````````````````` diff --git a/towncrier/newsfragments/2985.misc b/towncrier/newsfragments/2985.misc new file mode 100644 index 00000000..fdcc52eb --- /dev/null +++ b/towncrier/newsfragments/2985.misc @@ -0,0 +1,2 @@ +- Upgrade to roundcube 1.6.4 (fix XSS) +- Implement a new check to make it clear that unsupported setups are unsupported diff --git a/webmails/Dockerfile b/webmails/Dockerfile index beaffd5d..51e0a98c 100644 --- a/webmails/Dockerfile +++ b/webmails/Dockerfile @@ -27,7 +27,7 @@ RUN set -euxo pipefail \ ; mkdir -p /run/nginx /conf # roundcube -ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.6.3/roundcubemail-1.6.3-complete.tar.gz +ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.6.4/roundcubemail-1.6.4-complete.tar.gz ENV CARDDAV_URL https://github.com/mstilkerich/rcmcarddav/releases/download/v5.1.0/carddav-v5.1.0.tar.gz RUN set -euxo pipefail \ From 9f93ed6593f485ff51fa09ecc1d82cbe61ca03b9 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 17 Oct 2023 13:58:38 +0200 Subject: [PATCH 068/136] Fix letsencrypt on master --- core/nginx/letsencrypt.py | 2 +- towncrier/newsfragments/2990.misc | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 towncrier/newsfragments/2990.misc diff --git a/core/nginx/letsencrypt.py b/core/nginx/letsencrypt.py index edf24430..4c139d36 100755 --- a/core/nginx/letsencrypt.py +++ b/core/nginx/letsencrypt.py @@ -61,7 +61,7 @@ def serve_one_request(): # Run certbot every day while True: while True: - hostname = os.environ['HOSTNAMES'].split(' ')[0] + hostname = os.environ['HOSTNAMES'].split(',')[0] target = f'http://{hostname}/.well-known/acme-challenge/testing' thread = Thread(target=serve_one_request) thread.start() diff --git a/towncrier/newsfragments/2990.misc b/towncrier/newsfragments/2990.misc new file mode 100644 index 00000000..6d122e90 --- /dev/null +++ b/towncrier/newsfragments/2990.misc @@ -0,0 +1 @@ +Fix letsencrypt From 055b216627b90cf2b517fe7123e4f556912cca2e Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 17 Oct 2023 14:05:08 +0200 Subject: [PATCH 069/136] log.critical() where useful --- core/admin/start.py | 2 +- core/nginx/letsencrypt.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/admin/start.py b/core/admin/start.py index 6f3094b1..d107270f 100755 --- a/core/admin/start.py +++ b/core/admin/start.py @@ -29,7 +29,7 @@ def test_unsupported(): import codecs if os.path.isfile(codecs.decode('/.qbpxrerai', 'rot13')) or os.environ.get(codecs.decode('V_XABJ_ZL_FRGHC_QBRFAG_SVG_ERDHVERZRAGF_NAQ_JBAG_SVYR_VFFHRF_JVGUBHG_CNGPURF', 'rot13'), None): return - print('Your system is not supported. Please start by reading the documentation and then http://www.catb.org/~esr/faqs/smart-questions.html') + log.critical('Your system is not supported. Please start by reading the documentation and then http://www.catb.org/~esr/faqs/smart-questions.html') while True: time.sleep(5) diff --git a/core/nginx/letsencrypt.py b/core/nginx/letsencrypt.py index 4c139d36..a8abbee7 100755 --- a/core/nginx/letsencrypt.py +++ b/core/nginx/letsencrypt.py @@ -67,7 +67,7 @@ while True: thread.start() r = requests.get(target) if r.status_code != 204: - log.error(f"Can't reach {target}!, please ensure it's fixed or change the TLS_FLAVOR.") + log.critical(f"Can't reach {target}!, please ensure it's fixed or change the TLS_FLAVOR.") time.sleep(5) else: break From a5d8a2555a2dd555f3aa3f8ab532a4a5edabe054 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 17 Oct 2023 14:17:20 +0200 Subject: [PATCH 070/136] Modify ISSUE_TEMPLATE.md --- ISSUE_TEMPLATE.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index fbc83ffb..4b9fff1c 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -13,18 +13,13 @@ Before you open your issue Please put your text outside of the comment blocks to be visible. You can use the button "Preview" above to check. +If you do not follow the issue template suggested below your issue may be summarily closed. + --> ## Environment & Version -### Environment - -- [ ] docker compose -- [ ] kubernetes -- [ ] docker swarm - -### Version - +- `docker compose version` - Version: `master` +If you are not using docker compose do not file any new issue here. +Kubernetes related issues belong to https://github.com/Mailu/helm-charts/issues + ## Description \n", + "\n", + "\n", + "\n", + "mailu\n", + "\n", + "Mailu\n", + "\n", + "\n", + "internet\n", + "\n", + "Internet\n", + "\n", + "\n", + "\n", + "front\n", + "\n", + "Front\n", + "\n", + "\n", + "\n", + "internet->front\n", + "\n", + "\n", + "80/tcp\n", + "\n", + "\n", + "\n", + "internet->front\n", + "\n", + "\n", + "443/tcp\n", + "\n", + "\n", + "\n", + "internet->front\n", + "\n", + "\n", + "25/tcp\n", + "\n", + "\n", + "\n", + "internet->front\n", + "\n", + "\n", + "465/tcp\n", + "\n", + "\n", + "\n", + "internet->front\n", + "\n", + "\n", + "587/tcp\n", + "\n", + "\n", + "\n", + "internet->front\n", + "\n", + "\n", + "110/tcp\n", + "\n", + "\n", + "\n", + "internet->front\n", + "\n", + "\n", + "995/tcp\n", + "\n", + "\n", + "\n", + "internet->front\n", + "\n", + "\n", + "143/tcp\n", + "\n", + "\n", + "\n", + "internet->front\n", + "\n", + "\n", + "993/tcp\n", + "\n", + "\n", + "\n", + "internet->front\n", + "\n", + "\n", + "4190/tcp\n", + "\n", + "\n", + "\n", + "front->front\n", + "\n", + "\n", + "8008/tcp\n", + "\n", + "\n", + "\n", + "front->front\n", + "\n", + "\n", + "8000/tcp\n", + "\n", + "\n", + "\n", + "admin\n", + "\n", + "Admin\n", + "\n", + "\n", + "\n", + "front->admin\n", + "\n", + "\n", + "8080/tcp\n", + "\n", + "\n", + "\n", + "smtp\n", + "\n", + "SMTP\n", + "\n", + "\n", + "\n", + "front->smtp\n", + "\n", + "\n", + "25/tcp\n", + "\n", + "\n", + "\n", + "front->smtp\n", + "\n", + "\n", + "10025/tcp\n", + "\n", + "\n", + "\n", + "antispam\n", + "\n", + "Antispam\n", + "\n", + "\n", + "\n", + "front->antispam\n", + "\n", + "\n", + "11334/tcp\n", + "\n", + "\n", + "\n", + "imap\n", + "\n", + "IMAP\n", + "\n", + "\n", + "\n", + "front->imap\n", + "\n", + "\n", + "4190/tcp\n", + "\n", + "\n", + "\n", + "front->imap\n", + "\n", + "\n", + "143/tcp\n", + "\n", + "\n", + "\n", + "front->imap\n", + "\n", + "\n", + "110/tcp\n", + "\n", + "\n", + "\n", + "webdav\n", + "\n", + "WebDAV\n", + "\n", + "\n", + "\n", + "front->webdav\n", + "\n", + "\n", + "5232/tcp\n", + "\n", + "\n", + "\n", + "webmail\n", + "\n", + "Webmail\n", + "\n", + "\n", + "\n", + "front->webmail\n", + "\n", + "\n", + "80/tcp\n", + "\n", + "\n", + "\n", + "redis\n", + "\n", + "Redis\n", + "\n", + "\n", + "\n", + "admin->redis\n", + "\n", + "\n", + "6379/tcp\n", + "\n", + "\n", + "\n", + "admin->imap\n", + "\n", + "\n", + "2525/tcp\n", + "\n", + "\n", + "\n", + "smtp->front\n", + "\n", + "\n", + "2525/tcp\n", + "\n", + "\n", + "\n", + "smtp->admin\n", + "\n", + "\n", + "8080/tcp\n", + "\n", + "\n", + "\n", + "smtp->antispam\n", + "\n", + "\n", + "11332/tcp\n", + "\n", + "\n", + "\n", + "antispam->admin\n", + "\n", + "\n", + "80/tcp\n", + "\n", + "\n", + "\n", + "antispam->redis\n", + "\n", + "\n", + "6379/tcp\n", + "\n", + "\n", + "\n", + "antivirus\n", + "\n", + "Anti-Virus\n", + "\n", + "\n", + "\n", + "antispam->antivirus\n", + "\n", + "\n", + "3310/tcp\n", + "\n", + "\n", + "\n", + "oletools\n", + "\n", + "Oletools\n", + "\n", + "\n", + "\n", + "antispam->oletools\n", + "\n", + "\n", + "11343/tcp\n", + "\n", + "\n", + "\n", + "imap->front\n", + "\n", + "\n", + "25/tcp\n", + "\n", + "\n", + "\n", + "imap->admin\n", + "\n", + "\n", + "8080/tcp\n", + "\n", + "\n", + "\n", + "imap->antispam\n", + "\n", + "\n", + "11334/tcp\n", + "\n", + "\n", + "\n", + "fts_attachments\n", + "\n", + "Tika\n", + "\n", + "\n", + "\n", + "imap->fts_attachments\n", + "\n", + "\n", + "9998/tcp\n", + "\n", + "\n", + "\n", + "webmail->front\n", + "\n", + "\n", + "14190/tcp\n", + "\n", + "\n", + "\n", + "webmail->front\n", + "\n", + "\n", + "10025/tcp\n", + "\n", + "\n", + "\n", + "webmail->front\n", + "\n", + "\n", + "10143/tcp\n", + "\n", + "\n", + "\n", + "fetchmail\n", + "\n", + "Fetchmail\n", + "\n", + "\n", + "\n", + "fetchmail->front\n", + "\n", + "\n", + "25/tcp\n", + "\n", + "\n", + "\n", + "fetchmail->front\n", + "\n", + "\n", + "2525/tcp\n", + "\n", + "\n", + "\n", + "fetchmail->admin\n", + "\n", + "\n", + "8080/tcp\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import graphviz\n", + "\n", + "a = \"\"\"\n", + "digraph mailu {\n", + " label = \"Mailu\";\n", + " fontname = \"arial\";\n", + " \n", + " node [shape = box; fontname = \"arial\"; fontsize = 8; style = filled; color = \"#d3edea\";];\n", + " splines = \"compound\";\n", + " // node [shape = \"box\"; fontsize = \"10\";];\n", + " edge [fontsize = \"8\";];\n", + " \n", + " # Components\n", + " internet [label = \"Internet\";];\n", + " front [label = \"Front\";];\n", + " admin [label = \"Admin\";];\n", + " smtp [label = \"SMTP\";];\n", + " redis [label = \"Redis\";];\n", + " antispam [label = \"Antispam\";];\n", + " antivirus [label = \"Anti-Virus\";];\n", + " imap [label = \"IMAP\";];\n", + " webdav [label = \"WebDAV\";];\n", + " webmail [label = \"Webmail\";];\n", + " fetchmail [label = \"Fetchmail\";];\n", + " oletools [label = \"Oletools\"];\n", + " fts_attachments [label = \"Tika\"];\n", + " \n", + " # Front from internet\n", + " internet -> front [label = \"80/tcp\";];\n", + " internet -> front [label = \"443/tcp\";];\n", + " internet -> front [label = \"25/tcp\";];\n", + " internet -> front [label = \"465/tcp\";];\n", + " internet -> front [label = \"587/tcp\";];\n", + " internet -> front [label = \"110/tcp\";];\n", + " internet -> front [label = \"995/tcp\";];\n", + " internet -> front [label = \"143/tcp\";];\n", + " internet -> front [label = \"993/tcp\";];\n", + " internet -> front [label = \"4190/tcp\";];\n", + " \n", + " front -> front [label = \"8008/tcp\";];\n", + " front -> front [label = \"8000/tcp\";];\n", + " front -> admin [label = \"8080/tcp\";];\n", + " front -> imap [label = \"4190/tcp\";];\n", + " front -> imap [label = \"143/tcp\";];\n", + " front -> imap [label = \"110/tcp\";];\n", + " front -> smtp [label = \"25/tcp\";];\n", + " front -> smtp [label = \"10025/tcp\";];\n", + " front -> webmail [label = \"80/tcp\";];\n", + " front -> antispam [label = \"11334/tcp\";];\n", + " front -> webdav [label = \"5232/tcp\";];\n", + " \n", + " smtp -> admin [label = \"8080/tcp\";];\n", + " smtp -> front [label = \"2525/tcp\";];\n", + " smtp -> antispam [label = \"11332/tcp\";];\n", + " \n", + " imap -> admin [label = \"8080/tcp\";];\n", + " imap -> antispam [label = \"11334/tcp\";];\n", + " imap -> front [label = \"25/tcp\";];\n", + " imap -> fts_attachments [label = \"9998/tcp\";];\n", + " \n", + " webmail -> front [label = \"14190/tcp\";];\n", + " webmail -> front [label = \"10025/tcp\";];\n", + " webmail -> front [label = \"10143/tcp\";];\n", + " \n", + " admin -> redis [label = \"6379/tcp\";];\n", + " admin -> imap [label = \"2525/tcp\";];\n", + " \n", + " antispam -> redis [label = \"6379/tcp\";];\n", + " antispam -> admin [label = \"80/tcp\";];\n", + " antispam -> oletools [label = \"11343/tcp\";];\n", + " antispam -> antivirus [label = \"3310/tcp\";];\n", + " \n", + " fetchmail -> admin [label = \"8080/tcp\"]\n", + " fetchmail -> front [label = \"25/tcp\"]\n", + " fetchmail -> front [label = \"2525/tcp\"]\n", + " #\n", + " # those don't need internet:\n", + " # oletools\n", + " # fts_attachments\n", + " # redis\n", + "}\n", + "\"\"\"\n", + "\n", + "dot = graphviz.Source(a)\n", + "dot\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 6f969fd53979546f54a1a58161c297453376a467 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 20 Oct 2023 09:58:22 +0200 Subject: [PATCH 073/136] Fix CI --- docs/faq.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index cca0b6b6..fabf11aa 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -193,7 +193,7 @@ This means it can be scaled horizontally. For more information, refer to :ref:`k *Issue reference:* `165`_, `520`_. How to achieve HA / fail-over? -````````````````````````````` +`````````````````````````````` The mailboxes and databases for Mailu are kept on the host filesystem under ``$ROOT/``. For making the **storage** highly available, all sorts of techniques can be used: @@ -474,7 +474,7 @@ Re-starting the smtp container will be required for changes to take effect. .. _`2213`: https://github.com/Mailu/Mailu/issues/2213 My emails are getting deferred, what can I do? -````````````````````````````````````````````` +`````````````````````````````````````````````` Emails are asynchronous and it's not abnormal for them to be defered sometimes. That being said, Mailu enforces secure connections where possible using DANE and MTA-STS, both of which have the potential to delay indefinitely delivery if something is misconfigured. From 435508be1e33e30c2f5990d4c9b687c3bfd44cdc Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 27 Oct 2023 13:39:36 +0200 Subject: [PATCH 074/136] Introduce AUTH_REQUIRE_TOKENS --- core/admin/mailu/configuration.py | 1 + core/admin/mailu/internal/nginx.py | 8 ++++++-- docs/configuration.rst | 6 +++++- towncrier/newsfragments/3004.misc | 1 + 4 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 towncrier/newsfragments/3004.misc diff --git a/core/admin/mailu/configuration.py b/core/admin/mailu/configuration.py index d324bf8d..caccfe5e 100644 --- a/core/admin/mailu/configuration.py +++ b/core/admin/mailu/configuration.py @@ -72,6 +72,7 @@ DEFAULT_CONFIG = { 'LOGO_URL': None, 'LOGO_BACKGROUND': None, # Advanced settings + 'AUTH_REQUIRE_TOKENS': False, 'API': False, 'WEB_API': '/api', 'API_TOKEN': None, diff --git a/core/admin/mailu/internal/nginx.py b/core/admin/mailu/internal/nginx.py index 90b59712..4378ad67 100644 --- a/core/admin/mailu/internal/nginx.py +++ b/core/admin/mailu/internal/nginx.py @@ -50,8 +50,12 @@ def check_credentials(user, password, ip, protocol=None, auth_port=None, source_ app.logger.info(f'Login attempt for: {user}/{protocol}/{auth_port} from: {ip}/{source_port}: failed: badip: token-{token.id}: {token.comment or ""!r}') return False # we can return directly here since the token is valid if user.check_password(password): - app.logger.info(f'Login attempt for: {user}/{protocol}/{auth_port} from: {ip}/{source_port}: success: password') - return True + if app.config['AUTH_REQUIRE_TOKENS'] and protocol != 'web': + app.logger.info(f'Login attempt for: {user}/{protocol}/{auth_port} from: {ip}/{source_port}: failed: password but AUTH_REQUIRE_TOKENS=True') + return False + else: + app.logger.info(f'Login attempt for: {user}/{protocol}/{auth_port} from: {ip}/{source_port}: success: password') + return True app.logger.info(f'Login attempt for: {user}/{protocol}/{auth_port} from: {ip}/{source_port}: failed: badauth: {utils.truncated_pw_hash(password)}') return False diff --git a/docs/configuration.rst b/docs/configuration.rst index fbf284a4..bccc9116 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -214,7 +214,11 @@ Depending on your particular deployment you most probably will want to change th Advanced settings ----------------- -The ``API_TOKEN`` (default: None) configures the authentication token. +The ``AUTH_REQUIRE_TOKENS`` (default: False) setting controls whether thick clients can + authenticate using passwords or whether they are forced to use tokens/application + specific passwords. + +The ``API_TOKEN`` (default: None) setting configures the authentication token. This token must be passed as request header to the API as authentication token. This is a mandatory setting for using the RESTful API. diff --git a/towncrier/newsfragments/3004.misc b/towncrier/newsfragments/3004.misc new file mode 100644 index 00000000..69f46598 --- /dev/null +++ b/towncrier/newsfragments/3004.misc @@ -0,0 +1 @@ +Introduce AUTH_REQUIRE_TOKENS to enforce that thick clients use tokens instead of passwords From 3e2a6d84ce6ed8db6a75bc665b9b1a9adb371f9a Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 27 Oct 2023 13:41:51 +0200 Subject: [PATCH 075/136] doh --- towncrier/newsfragments/{3004.misc => 3007.misc} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename towncrier/newsfragments/{3004.misc => 3007.misc} (100%) diff --git a/towncrier/newsfragments/3004.misc b/towncrier/newsfragments/3007.misc similarity index 100% rename from towncrier/newsfragments/3004.misc rename to towncrier/newsfragments/3007.misc From 2494a344a7101ae36cd36d4837f2caa2dcf14139 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 27 Oct 2023 15:14:51 +0200 Subject: [PATCH 076/136] Ammend wording as suggested --- core/admin/mailu/internal/nginx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/mailu/internal/nginx.py b/core/admin/mailu/internal/nginx.py index 4378ad67..12befa84 100644 --- a/core/admin/mailu/internal/nginx.py +++ b/core/admin/mailu/internal/nginx.py @@ -51,7 +51,7 @@ def check_credentials(user, password, ip, protocol=None, auth_port=None, source_ return False # we can return directly here since the token is valid if user.check_password(password): if app.config['AUTH_REQUIRE_TOKENS'] and protocol != 'web': - app.logger.info(f'Login attempt for: {user}/{protocol}/{auth_port} from: {ip}/{source_port}: failed: password but AUTH_REQUIRE_TOKENS=True') + app.logger.info(f'Login attempt for: {user}/{protocol}/{auth_port} from: {ip}/{source_port}: failed: password ok, but a token is required') return False else: app.logger.info(f'Login attempt for: {user}/{protocol}/{auth_port} from: {ip}/{source_port}: success: password') From 60b9ff0090d0f31415ed9fafe0bb5e644fa74edf Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Fri, 27 Oct 2023 14:10:13 +0000 Subject: [PATCH 077/136] Fixed log filter not filtering out log messages for dovecot/nginx/postfix. Fixed postfix not logging to standard out. Fixed not all containers logging to journald. Removed POSTFIX_LOG_FILE functionality. Added documentation on how to achieve the same (log to file) via journald & rsyslogd (see new FAQ entry 'How can I view and export the logs of a Mailu container?'). --- core/base/libs/socrate/socrate/system.py | 38 +++++++++----- core/dovecot/start.py | 7 ++- core/nginx/start.py | 5 +- core/postfix/start.py | 7 +-- docs/configuration.rst | 16 ------ docs/faq.rst | 63 +++++++++++++++++++++++- setup/flavors/compose/docker-compose.yml | 18 ++++++- towncrier/newsfragments/2939.bugfix | 4 ++ 8 files changed, 120 insertions(+), 38 deletions(-) create mode 100644 towncrier/newsfragments/2939.bugfix diff --git a/core/base/libs/socrate/socrate/system.py b/core/base/libs/socrate/socrate/system.py index 53cabae6..0a0dba52 100644 --- a/core/base/libs/socrate/socrate/system.py +++ b/core/base/libs/socrate/socrate/system.py @@ -6,6 +6,8 @@ import re from pwd import getpwnam import socket import tenacity +import subprocess +import threading @tenacity.retry(stop=tenacity.stop_after_attempt(100), wait=tenacity.wait_random(min=2, max=5)) @@ -27,7 +29,7 @@ def _coerce_value(value): return value class LogFilter(object): - def __init__(self, stream, re_patterns, log_file): + def __init__(self, stream, re_patterns): self.stream = stream if isinstance(re_patterns, list): self.pattern = re.compile('|'.join([f'(?:{pattern})' for pattern in re_patterns])) @@ -36,7 +38,6 @@ class LogFilter(object): else: self.pattern = re_patterns self.found = False - self.log_file = log_file def __getattr__(self, attr_name): return getattr(self.stream, attr_name) @@ -48,12 +49,6 @@ class LogFilter(object): if not self.pattern.search(data): self.stream.write(data) self.stream.flush() - if self.log_file: - try: - with open(self.log_file, 'a', encoding='utf-8') as l: - l.write(data) - except: - pass else: # caught bad pattern self.found = True @@ -74,10 +69,10 @@ def _is_compatible_with_hardened_malloc(): return False return True -def set_env(required_secrets=[], log_filters=[], log_file=None): +def set_env(required_secrets=[], log_filters=[]): if log_filters: - sys.stdout = LogFilter(sys.stdout, log_filters, log_file) - sys.stderr = LogFilter(sys.stderr, log_filters, log_file) + sys.stdout = LogFilter(sys.stdout, log_filters) + sys.stderr = LogFilter(sys.stderr, log_filters) log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", 'WARNING')) if not 'LD_PRELOAD' in os.environ and _is_compatible_with_hardened_malloc(): @@ -115,3 +110,24 @@ def drop_privs_to(username='mailu'): os.setgid(pwnam.pw_gid) os.setuid(pwnam.pw_uid) os.environ['HOME'] = pwnam.pw_dir + +# forwards text lines from src to dst in an infinite loop +def forward_text_lines(src, dst): + while True: + current_line = src.readline() + dst.write(current_line) + + +# runs a process and passes its standard/error output to the standard/error output of the current python script +def run_process_and_forward_output(cmd): + process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + + stdout_thread = threading.Thread(target=forward_text_lines, args=(process.stdout, sys.stdout)) + stdout_thread.daemon = True + stdout_thread.start() + + stderr_thread = threading.Thread(target=forward_text_lines, args=(process.stderr, sys.stderr)) + stderr_thread.daemon = True + stderr_thread.start() + + process.wait() diff --git a/core/dovecot/start.py b/core/dovecot/start.py index d25f860b..b162db95 100755 --- a/core/dovecot/start.py +++ b/core/dovecot/start.py @@ -3,13 +3,11 @@ import os import glob import multiprocessing -import logging as log -import sys from podop import run_server from socrate import system, conf -system.set_env(log_filters=r'Error\: SSL context initialization failed, disabling SSL\: Can\'t load SSL certificate \(ssl_cert setting\)\: The certificate is empty$') +system.set_env(log_filters=[r'Error\: SSL context initialization failed, disabling SSL\: Can\'t load SSL certificate \(ssl_cert setting\)\: The certificate is empty$']) def start_podop(): system.drop_privs_to('mail') @@ -35,4 +33,5 @@ os.system("chown mail:mail /mail") os.system("chown -R mail:mail /var/lib/dovecot /conf") multiprocessing.Process(target=start_podop).start() -os.system("dovecot -c /etc/dovecot/dovecot.conf -F") +cmd = ['/usr/sbin/dovecot', '-c', '/etc/dovecot/dovecot.conf', '-F'] +system.run_process_and_forward_output(cmd) diff --git a/core/nginx/start.py b/core/nginx/start.py index 01ecca03..a50abec2 100755 --- a/core/nginx/start.py +++ b/core/nginx/start.py @@ -4,7 +4,7 @@ import os import subprocess from socrate import system -system.set_env(log_filters=r'could not be resolved \(\d\: [^\)]+\) while in resolving client address, client\: [^,]+, server: [^\:]+\:(25,110,143,587,465,993,995)$') +system.set_env(log_filters=r'could not be resolved \(\d\: [^\)]+\) while in resolving client address, client\: [^,]+, server: [^\:]+\:(25|110|143|587|465|993|995)$') # Check if a stale pid file exists if os.path.exists("/var/run/nginx.pid"): @@ -17,4 +17,5 @@ elif os.environ["TLS_FLAVOR"] in [ "mail", "cert" ]: subprocess.call(["/config.py"]) os.system("dovecot -c /etc/dovecot/proxy.conf") -os.execv("/usr/sbin/nginx", ["nginx", "-g", "daemon off;"]) +cmd = ['/usr/sbin/nginx', '-g', 'daemon off;'] +system.run_process_and_forward_output(cmd) diff --git a/core/postfix/start.py b/core/postfix/start.py index d6af1a98..7daf7c3b 100755 --- a/core/postfix/start.py +++ b/core/postfix/start.py @@ -13,8 +13,8 @@ from socrate import system, conf system.set_env(log_filters=[ r'(dis)?connect from localhost\[(\:\:1|127\.0\.0\.1)\]( quit=1 commands=1)?$', r'haproxy read\: short protocol header\: QUIT$', - r'discarding EHLO keywords\: PIPELINING$', - ], log_file=os.environ.get('POSTFIX_LOG_FILE')) + r'discarding EHLO keywords\: PIPELINING$' + ]) os.system("flock -n /queue/pid/master.pid rm /queue/pid/master.pid") @@ -100,4 +100,5 @@ os.system("/usr/libexec/postfix/post-install meta_directory=/etc/postfix create- # Before starting postfix, we need to check permissions on /queue # in the event that postfix,postdrop id have changed os.system("postfix set-permissions") -os.system("postfix start-fg") +cmd = ['postfix', 'start-fg'] +system.run_process_and_forward_output(cmd) diff --git a/docs/configuration.rst b/docs/configuration.rst index fbf284a4..f7518c40 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -376,22 +376,6 @@ To disable all plugins just set ``ROUNDCUBE_PLUGINS`` to ``mailu``. To configure a plugin add php files named ``*.inc.php`` to roundcube's :ref:`override section `. -Mail log settings ------------------ - -By default, all services log directly to stdout/stderr. Logs can be collected by any docker log processing solution. - -Postfix writes the logs to a syslog server which logs to stdout. This is used to filter -out messages from the healthcheck. In some situations, a separate mail log is required -(e.g. for legal reasons). The syslog server can be configured to write log files to a volume. -It can be configured with the following option: - -- ``POSTFIX_LOG_FILE``: The file to log the mail log to. When enabled, the syslog server will also log to stdout. - -When ``POSTFIX_LOG_FILE`` is enabled, the logrotate program will automatically rotate the -logs every week and keep 52 logs. To override the logrotate configuration, create the file logrotate.conf -with the desired configuration in the :ref:`Postfix overrides folder`. - .. _header_authentication: Header authentication using an external proxy diff --git a/docs/faq.rst b/docs/faq.rst index 3eb7bafc..3268130c 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -451,7 +451,7 @@ down and up again. A container restart is not sufficient. SMTP Banner from overrides/postfix.cf is ignored ```````````````````````````````````````````````` -Any mail related connection is proxied by nginx. Therefore the SMTP Banner is also set by nginx. Overwriting in overrides/postfix.cf does not apply. +Any mail related connection is proxied by the front container. Therefore the SMTP Banner is also set by front container. Overwriting in overrides/postfix.cf does not apply. *Issue reference:* `1368`_. @@ -922,3 +922,64 @@ I see a lot of "Unable to lookup the TLSA record for XXX. Is the DNSSEC zone oka There may be multiple causes for it but if you are running docker 24.0.0, odds are you are `experiencing this docker bug`_ and the workaround is to switch to a different version of docker. .. _`experiencing this docker bug`: https://github.com/Mailu/Mailu/issues/2827 + +How can I view and export the logs of a Mailu container? +```````````````````````````````````````````````````````` + +In some situations, a separate log is required. For example a separate mail log (from postfix) could be required due to legal reasons. + +All Mailu containers log the output to journald. The logs are written to journald with the tag: + +| mailu- +| where is the name of the service in the docker-compose.yml file. +| For example, the service running postfix is called smtp. To view the postfix logs use: + +.. code-block:: bash + + journalctl -t mailu-smtp + +Note: ``SHIFT+G`` can be used to jump to the end of the log file. ``G`` can be used to jump back to the top of the log file. + +To export the log files from journald to the file system, the logs could be imported into a syslog program like ``rsyslog``. +Via ``rsyslog`` the container specific logs could be written to a separate file using a filter. + +Below are the steps for writing the postfix (mail) logs to a log file on the file system. + +1. Install the ``rsyslog`` package. Note: on most distributions this program is already installed. +2. Edit ``/etc/systemd/journald.conf``. +3. Enable ``ForwardToSyslog=yes``. Note: on most distributions this is already enabled by default. This forwards journald to syslog. +4. ``sudo touch /var/log/postfix.log``. This step creates the mail log file. +5. ``sudo chown syslog:syslog /var/log/postfix.log``. This provides rsyslog the permissions for accessing this file. +6. Create a new config file in ``/etc/rsyslog.d/export-postfix.conf`` +7. Add ``:programname, contains, "mailu-smtp" /var/log/postfix.log``. This instructs rsyslog to write the logs for mailu-smtp to a log file on file system. +8. ``sudo systemctl restart systemd-journald.service`` +9. ``sudo systemctl restart rsyslog`` +10. All messages from the smtp/postfix container are now logged to ``/var/log/postfix.log``. +11. Rsyslog does not perform log rotation. The program (package) ``log rotate`` can be used for this task. Install the ``logrotate`` package. +12. Modify the existing configuration file for rsyslog: ``sudo nano /etc/logrotate.d/rsyslog`` +13. Add at the top add: ``/var/log/postfix.log``. Of course you can also use your own configuration. This is just an example. A complete example for configuring log rotate is: + +.. code-block:: bash + + /var/log/postfix.log + { + rotate 4 + weekly + missingok + notifempty + compress + delaycompress + sharedscripts + postrotate + /usr/lib/rsyslog/rsyslog-rotate + endscript + } + +.. code-block:: bash + + #!/bin/sh + #/usr/lib/rsyslog/rsyslog-rotate + + if [ -d /run/systemd/system ]; then + systemctl kill -s HUP rsyslog.service + fi diff --git a/setup/flavors/compose/docker-compose.yml b/setup/flavors/compose/docker-compose.yml index bb7f77d0..75fa6f08 100644 --- a/setup/flavors/compose/docker-compose.yml +++ b/setup/flavors/compose/docker-compose.yml @@ -58,6 +58,10 @@ services: resolver: image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}unbound:${MAILU_VERSION:-{{ version }}} env_file: {{ env }} + logging: + driver: journald + options: + tag: mailu-resolver restart: always networks: default: @@ -220,7 +224,7 @@ services: logging: driver: journald options: - tag: mailu-clamav + tag: mailu-antivirus networks: - clamav volumes: @@ -237,6 +241,10 @@ services: webdav: image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}radicale:${MAILU_VERSION:-{{ version }}} restart: always + logging: + driver: journald + options: + tag: mailu-webdav volumes: - "{{ root }}/dav:/data" networks: @@ -248,6 +256,10 @@ services: image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}fetchmail:${MAILU_VERSION:-{{ version }}} restart: always env_file: {{ env }} + logging: + driver: journald + options: + tag: mailu-fetchmail volumes: - "{{ root }}/data/fetchmail:/data" depends_on: @@ -267,6 +279,10 @@ services: image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}webmail:${MAILU_VERSION:-{{ version }}} restart: always env_file: {{ env }} + logging: + driver: journald + options: + tag: mailu-webmail volumes: - "{{ root }}/webmail:/data" - "{{ root }}/overrides/{{ webmail_type }}:/overrides:ro" diff --git a/towncrier/newsfragments/2939.bugfix b/towncrier/newsfragments/2939.bugfix new file mode 100644 index 00000000..beadfb33 --- /dev/null +++ b/towncrier/newsfragments/2939.bugfix @@ -0,0 +1,4 @@ +Fixed log filter not filtering out log messages for dovecot/nginx/postfix. +Fixed postfix not logging to standard out. +Fixed not all containers logging to journald. +Removed POSTFIX_LOG_FILE functionality. Added documentation on how to achieve the same (log to file) via journald & rsyslogd (see new FAQ entry 'How can I view and export the logs of a Mailu container?'). \ No newline at end of file From 3ef504a9bb05cd8ae5fd5109b2530a0a348ec6ad Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 27 Oct 2023 19:19:44 +0200 Subject: [PATCH 078/136] duh --- core/dovecot/Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/dovecot/Dockerfile b/core/dovecot/Dockerfile index 2681bcff..25eb9263 100644 --- a/core/dovecot/Dockerfile +++ b/core/dovecot/Dockerfile @@ -7,9 +7,8 @@ ARG VERSION LABEL version=$VERSION RUN set -euxo pipefail \ - ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main 'dovecot<2.4' dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond \ - ; apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing dovecot-fts-flatcurve \ - ; apk add --no-cache rspamd-client \ + ; echo -e 'http://dl-cdn.alpinelinux.org/alpine/edge/main\nhttp://dl-cdn.alpinelinux.org/alpine/edge/testing\nhttp://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories \ + ; apk add --no-cache 'dovecot<2.4' dovecot-lmtpd dovecot-pigeonhole-plugin dovecot-pop3d dovecot-submissiond rspamd-client dovecot-fts-flatcurve \ ; mkdir /var/lib/dovecot COPY conf/ /conf/ From a97d3eaebe2e436c75528ced8f132117748e90dc Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Sat, 28 Oct 2023 09:36:30 +0200 Subject: [PATCH 079/136] Long term solution for helm-charts --- core/admin/start.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/start.py b/core/admin/start.py index d107270f..e9ba176e 100755 --- a/core/admin/start.py +++ b/core/admin/start.py @@ -27,7 +27,7 @@ if account is not None and domain is not None and password is not None: def test_unsupported(): import codecs - if os.path.isfile(codecs.decode('/.qbpxrerai', 'rot13')) or os.environ.get(codecs.decode('V_XABJ_ZL_FRGHC_QBRFAG_SVG_ERDHVERZRAGF_NAQ_JBAG_SVYR_VFFHRF_JVGUBHG_CNGPURF', 'rot13'), None): + if os.path.isfile(codecs.decode('/.qbpxrerai', 'rot13')) or os.environ.get(codecs.decode('V_XABJ_ZL_FRGHC_QBRFAG_SVG_ERDHVERZRAGF_NAQ_JBAG_SVYR_VFFHRF_JVGUBHG_CNGPURF', 'rot13'), None) or os.environ.get(codecs.decode('ZNVYH_URYZ_PUNEG'), None): return log.critical('Your system is not supported. Please start by reading the documentation and then http://www.catb.org/~esr/faqs/smart-questions.html') while True: From 6dd5b559db0cfd8c44d57e97d016595fc0f107e4 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Sat, 28 Oct 2023 09:55:30 +0200 Subject: [PATCH 080/136] Give more time to clamav --- tests/compose/filters/docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/compose/filters/docker-compose.yml b/tests/compose/filters/docker-compose.yml index 490ce0ee..5a508d58 100644 --- a/tests/compose/filters/docker-compose.yml +++ b/tests/compose/filters/docker-compose.yml @@ -86,6 +86,8 @@ services: - "/mailu/overrides/rspamd:/etc/rspamd/override.d" depends_on: - front + - antivirus + - oletools # Optional services antivirus: From 82e0951e598283f587545b27ca4e8abfe3229965 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Sat, 28 Oct 2023 11:23:46 +0200 Subject: [PATCH 081/136] Update core/admin/start.py Co-authored-by: Dimitri Huisman <52963853+Diman0@users.noreply.github.com> --- core/admin/start.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/start.py b/core/admin/start.py index e9ba176e..9574bbb7 100755 --- a/core/admin/start.py +++ b/core/admin/start.py @@ -27,7 +27,7 @@ if account is not None and domain is not None and password is not None: def test_unsupported(): import codecs - if os.path.isfile(codecs.decode('/.qbpxrerai', 'rot13')) or os.environ.get(codecs.decode('V_XABJ_ZL_FRGHC_QBRFAG_SVG_ERDHVERZRAGF_NAQ_JBAG_SVYR_VFFHRF_JVGUBHG_CNGPURF', 'rot13'), None) or os.environ.get(codecs.decode('ZNVYH_URYZ_PUNEG'), None): + if os.path.isfile(codecs.decode('/.qbpxrerai', 'rot13')) or os.environ.get(codecs.decode('V_XABJ_ZL_FRGHC_QBRFAG_SVG_ERDHVERZRAGF_NAQ_JBAG_SVYR_VFFHRF_JVGUBHG_CNGPURF', 'rot13'), None) or os.environ.get(codecs.decode('ZNVYH_URYZ_PUNEG', 'rot13'), None): return log.critical('Your system is not supported. Please start by reading the documentation and then http://www.catb.org/~esr/faqs/smart-questions.html') while True: From 1e4b27b056ddb3f25c064c0efdba06302c3b57f7 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Sat, 28 Oct 2023 11:44:01 +0000 Subject: [PATCH 082/136] Update issue template. From now on the issues section is only used for bug reports that follow the issue template. All other topics (including feature requests) are discussed on the github discussions section of the project. --- ISSUE_TEMPLATE.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 4b9fff1c..f015250e 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,7 +1,15 @@ From d16073ab10493712ced58b250538d6ef10aada2a Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Sat, 28 Oct 2023 11:57:54 +0000 Subject: [PATCH 083/136] Update faq.rst that anything but bugs are handled via github discussions --- docs/faq.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index c0b9fa3b..6b3011a1 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -11,8 +11,7 @@ Where to ask questions? First, please read this FAQ to check if your question is listed here. Simple questions are best asked in our `Matrix`_ room. -For more complex questions, you can always open a `new issue`_ on GitHub. -We actively monitor the issues list. +For more complex questions, you can always open a `new discussion`_ on GitHub. My installation is broken! @@ -33,16 +32,17 @@ I want a new feature or enhancement! Great! We are always open for suggestions. We currently maintain two tags: -- `Enhancement issues`_: Typically used for optimization of features in the project. -- `Feature request issues`_: For implementing new functionality, +- ``type/enhancement``: Typically used for optimization of features in the project. +- ``type/feature``: For implementing new functionality, plugins and applications. -Please check if your idea (or something similar) is already mentioned there. +Feature requests are discussed on the discussion page of the project (see `feature requests`_). +Please check if your idea (or something similar) is already mentioned on the project. If there is one open, you can choose to vote with a thumbs up, so we can estimate the popular demand. Please refrain from writing comments like *"me too"* as it clobbers the actual discussion. -If you can't find anything similar, you can open a `new issue`_. +If you can't find anything similar, you can open a `new feature request`_. Please also share (where applicable): - Use case: how does this improve the project? @@ -89,8 +89,9 @@ Please click the |sponsor| button on top of our GitHub Page for current possibil .. _`Matrix`: https://matrix.to/#/#mailu:tedomum.net .. _`open issues`: https://github.com/Mailu/Mailu/issues .. _`new issue`: https://github.com/Mailu/Mailu/issues/new -.. _`Enhancement issues`: https://github.com/Mailu/Mailu/issues?q=is%3Aissue+is%3Aopen+label%3Atype%2Fenhancement -.. _`Feature request issues`: https://github.com/Mailu/Mailu/issues?q=is%3Aopen+is%3Aissue+label%3Atype%2Ffeature +.. _`new discussion`: https://github.com/Mailu/Mailu/discussions/categories/user-support +.. _`feature requests`: https://github.com/Mailu/Mailu/discussions/categories/feature-requests-ideas +.. _`new feature request`: https://github.com/Mailu/Mailu/discussions/new?category=feature-requests-ideas .. _`GitHub`: https://github.com/Mailu/Mailu .. _`Community Bridge`: https://funding.communitybridge.org/projects/mailu From eb110c8431b214b5b61a6e8423fbe445b6359f45 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Sat, 28 Oct 2023 14:00:07 +0200 Subject: [PATCH 084/136] master uses SSO --- core/admin/mailu/internal/nginx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/mailu/internal/nginx.py b/core/admin/mailu/internal/nginx.py index 12befa84..ebd677d0 100644 --- a/core/admin/mailu/internal/nginx.py +++ b/core/admin/mailu/internal/nginx.py @@ -50,7 +50,7 @@ def check_credentials(user, password, ip, protocol=None, auth_port=None, source_ app.logger.info(f'Login attempt for: {user}/{protocol}/{auth_port} from: {ip}/{source_port}: failed: badip: token-{token.id}: {token.comment or ""!r}') return False # we can return directly here since the token is valid if user.check_password(password): - if app.config['AUTH_REQUIRE_TOKENS'] and protocol != 'web': + if app.config['AUTH_REQUIRE_TOKENS'] and not protocol in ['web', 'sso']: app.logger.info(f'Login attempt for: {user}/{protocol}/{auth_port} from: {ip}/{source_port}: failed: password ok, but a token is required') return False else: From 1b175e48d42ff78709373c2bddca2cdb264ab2d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz?= Date: Sun, 29 Oct 2023 09:36:16 +0000 Subject: [PATCH 085/136] testing: Add "download zonefile" button --- core/admin/mailu/ui/views/domains.py | 16 ++++++++++++++++ towncrier/newsfragments/3008.feature | 1 + 2 files changed, 17 insertions(+) create mode 100644 towncrier/newsfragments/3008.feature diff --git a/core/admin/mailu/ui/views/domains.py b/core/admin/mailu/ui/views/domains.py index 4cdd830a..fcbe70a2 100644 --- a/core/admin/mailu/ui/views/domains.py +++ b/core/admin/mailu/ui/views/domains.py @@ -70,6 +70,22 @@ def domain_details(domain_name): domain = models.Domain.query.get(domain_name) or flask.abort(404) return flask.render_template('domain/details.html', domain=domain) +@ui.route('/domain/details//downzonefile', methods=['GET']) +@access.domain_admin(models.Domain, 'domain_name') +def domain_details(domain_name): + domain = models.Domain.query.get(domain_name) or flask.abort(404) + final = domain.dns_mx+"\n" + final = final + domain.dns_spf+"\n" + if domain.dkim_publickey: + final = final + domain.dkim_publickey+"\n" + final = final + domain.dns_dkim+"\n" + final = final + domain.dns_dmarc+"\n" + if domain.dns_tlsa: + final = final + domain.dns_tlsa + for i in domain.dns_autoconfig: + final = final + i+"\n" + return flask.Response(final,content_type="text/plain") + @ui.route('/domain/genkeys/', methods=['GET', 'POST']) @access.domain_admin(models.Domain, 'domain_name') diff --git a/towncrier/newsfragments/3008.feature b/towncrier/newsfragments/3008.feature new file mode 100644 index 00000000..1bda6ca6 --- /dev/null +++ b/towncrier/newsfragments/3008.feature @@ -0,0 +1 @@ +Add "download zonefile" button to domain configuration \ No newline at end of file From 43dc61f4158b613e0283571eff6e995403adceba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Thiede?= Date: Sun, 29 Oct 2023 11:23:22 +0100 Subject: [PATCH 086/136] switch "downzonefile" to "zonefile" --- core/admin/mailu/ui/views/domains.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/admin/mailu/ui/views/domains.py b/core/admin/mailu/ui/views/domains.py index fcbe70a2..3dc0fcdb 100644 --- a/core/admin/mailu/ui/views/domains.py +++ b/core/admin/mailu/ui/views/domains.py @@ -70,9 +70,9 @@ def domain_details(domain_name): domain = models.Domain.query.get(domain_name) or flask.abort(404) return flask.render_template('domain/details.html', domain=domain) -@ui.route('/domain/details//downzonefile', methods=['GET']) +@ui.route('/domain/details//zonefile', methods=['GET']) @access.domain_admin(models.Domain, 'domain_name') -def domain_details(domain_name): +def domain_download_zonefile(domain_name): domain = models.Domain.query.get(domain_name) or flask.abort(404) final = domain.dns_mx+"\n" final = final + domain.dns_spf+"\n" From 0718edcba9124f4f90dc2270c66409b6e292fac9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Thiede?= Date: Sun, 29 Oct 2023 11:24:37 +0100 Subject: [PATCH 087/136] Create the button for zonefile downloading --- core/admin/mailu/ui/templates/domain/details.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/admin/mailu/ui/templates/domain/details.html b/core/admin/mailu/ui/templates/domain/details.html index 28f3f570..34405ac7 100644 --- a/core/admin/mailu/ui/templates/domain/details.html +++ b/core/admin/mailu/ui/templates/domain/details.html @@ -17,6 +17,9 @@ {% trans %}Generate keys{% endtrans %} {%- endif %} + + {% trans %}Download zonefile{% endtrans %} + {%- endif %} {%- endblock %} From 83f3b7722c91448804ce4fffcb22340663010251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Thiede?= Date: Sun, 29 Oct 2023 11:30:06 +0100 Subject: [PATCH 088/136] add a button to download zonefile --- core/admin/mailu/translations/en/LC_MESSAGES/messages.po | 4 ++++ core/admin/mailu/ui/templates/domain/details.html | 5 +---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/admin/mailu/translations/en/LC_MESSAGES/messages.po b/core/admin/mailu/translations/en/LC_MESSAGES/messages.po index 4ec33e05..f19e191f 100644 --- a/core/admin/mailu/translations/en/LC_MESSAGES/messages.po +++ b/core/admin/mailu/translations/en/LC_MESSAGES/messages.po @@ -511,6 +511,10 @@ msgstr "" msgid "Generate keys" msgstr "" +#: mailu/ui/templates/domain/details.html:19 +msgid "Download zonefile" +msgstr "" + #: mailu/ui/templates/domain/details.html:30 msgid "DNS MX entry" msgstr "" diff --git a/core/admin/mailu/ui/templates/domain/details.html b/core/admin/mailu/ui/templates/domain/details.html index 34405ac7..92f7e288 100644 --- a/core/admin/mailu/ui/templates/domain/details.html +++ b/core/admin/mailu/ui/templates/domain/details.html @@ -16,10 +16,7 @@ {%- else %} {% trans %}Generate keys{% endtrans %} {%- endif %} - - - {% trans %}Download zonefile{% endtrans %} - + {% trans %}Download zonefile{% endtrans %} {%- endif %} {%- endblock %} From 150448367775a1131bd3340b5e5535be1bfbd6fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Thiede?= Date: Sun, 29 Oct 2023 11:50:36 +0100 Subject: [PATCH 089/136] make it save as a file, name {domain}-zonefile.txt --- core/admin/mailu/ui/views/domains.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/mailu/ui/views/domains.py b/core/admin/mailu/ui/views/domains.py index 3dc0fcdb..b03b96b0 100644 --- a/core/admin/mailu/ui/views/domains.py +++ b/core/admin/mailu/ui/views/domains.py @@ -84,7 +84,7 @@ def domain_download_zonefile(domain_name): final = final + domain.dns_tlsa for i in domain.dns_autoconfig: final = final + i+"\n" - return flask.Response(final,content_type="text/plain") + return flask.Response(final,content_type="text/plain",headers={'Content-disposition': 'attachment; filename='+domain.name+'-zonefile.txt'}) @ui.route('/domain/genkeys/', methods=['GET', 'POST']) From 9905806c971e7291eab9c31a6d91f4073c2ae379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Thiede?= Date: Sun, 29 Oct 2023 11:50:43 +0100 Subject: [PATCH 090/136] create a button --- core/admin/mailu/ui/templates/domain/details.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/admin/mailu/ui/templates/domain/details.html b/core/admin/mailu/ui/templates/domain/details.html index 92f7e288..a58c4477 100644 --- a/core/admin/mailu/ui/templates/domain/details.html +++ b/core/admin/mailu/ui/templates/domain/details.html @@ -10,13 +10,13 @@ {%- block main_action %} {%- if current_user.global_admin %} - + {%- if domain.dkim_publickey %} {% trans %}Regenerate keys{% endtrans %} {%- else %} {% trans %}Generate keys{% endtrans %} {%- endif %} - {% trans %}Download zonefile{% endtrans %} + {% trans %}Download zonefile{% endtrans %} {%- endif %} {%- endblock %} From a27845b2136200db22e7db1307e9b412549e8f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Thiede?= Date: Sun, 29 Oct 2023 11:53:31 +0100 Subject: [PATCH 091/136] update docs --- docs/webadministration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/webadministration.rst b/docs/webadministration.rst index 802ce43b..3ce53fe8 100644 --- a/docs/webadministration.rst +++ b/docs/webadministration.rst @@ -280,7 +280,7 @@ On the `Mail domains` page all the domains served by Mailu are configured. Via t Details ``````` -This page is also accessible for domain managers. On the details page all DNS settings are displayed for configuring your DNS server. It contains information on what to configure as MX record and SPF record. On this page it is also possible to (re-)generate the keys for DKIM and DMARC. The option for generating keys for DKIM and DMARC is only available for global administrators. After generating the keys for DKIM and DMARC, this page will also show the DNS records for configuring the DKIM/DMARC records on the DNS server. +This page is also accessible for domain managers. On the details page all DNS settings are displayed for configuring your DNS server. It contains information on what to configure as MX record and SPF record. On this page it is also possible to (re-)generate the keys for DKIM and DMARC. The option for generating keys for DKIM and DMARC is only available for global administrators. After generating the keys for DKIM and DMARC, this page will also show the DNS records for configuring the DKIM/DMARC records on the DNS server. You can also download a zonefile for easy upload to your nameserver. Edit From 67d11c47c8af0f900a9024eb0fcb3b57fb961fff Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Sun, 29 Oct 2023 12:55:40 +0000 Subject: [PATCH 092/136] Added checks to SETUP to make sure JavaScript is enabled and that all JS files could be loaded when loading the site page. The setup site malfunctions if this is not the case. Regular expression for checking the Mailu storage path was invalid. --- setup/Dockerfile | 4 +++ setup/server.py | 4 ++- setup/static/jquery.min.js | 2 ++ setup/static/render.js | 1 + setup/templates/base.html | 27 ++++++++++++++++++- .../templates/steps/compose/02_services.html | 5 ---- setup/templates/steps/config.html | 6 +---- towncrier/newsfragments/2890.bugfix | 3 +++ 8 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 setup/static/jquery.min.js create mode 100644 towncrier/newsfragments/2890.bugfix diff --git a/setup/Dockerfile b/setup/Dockerfile index a410871d..6a57db18 100644 --- a/setup/Dockerfile +++ b/setup/Dockerfile @@ -14,6 +14,10 @@ COPY main.py ./main.py RUN echo $VERSION >> /version +#Note: This is appended so we can explicitly check if this JS file has been loaded +#by the user's internet browser when accessing the setup site. +RUN echo var jQueryMailu=\'loaded\'\; >> ./static/jquery.min.js + EXPOSE 80/tcp HEALTHCHECK --start-period=350s CMD curl -skfLo /dev/null http://localhost/ USER mailu diff --git a/setup/server.py b/setup/server.py index 622905ed..020da4a3 100644 --- a/setup/server.py +++ b/setup/server.py @@ -10,12 +10,14 @@ import random import ipaddress import hashlib import time - +from flask_bootstrap import StaticCDN version = os.getenv("this_version", "master") static_url_path = "/" + version + "/static" app = flask.Flask(__name__, static_url_path=static_url_path) flask_bootstrap.Bootstrap(app) +# Load our jQuery. Do not use jQuery 1. +app.extensions['bootstrap']['cdns']['jquery'] = StaticCDN() db = redis.StrictRedis(host='redis', port=6379, db=0) diff --git a/setup/static/jquery.min.js b/setup/static/jquery.min.js new file mode 100644 index 00000000..7f37b5d9 --- /dev/null +++ b/setup/static/jquery.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="

",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0 + + +

Mailu configuration

Version @@ -27,4 +36,20 @@ For production scenarios we recommend to use the stable version.

+ {% endblock %} + +{% block scripts %} + + + +{{super()}} +{% endblock %} \ No newline at end of file diff --git a/setup/templates/steps/compose/02_services.html b/setup/templates/steps/compose/02_services.html index 44281b15..5d9df7ef 100644 --- a/setup/templates/steps/compose/02_services.html +++ b/setup/templates/steps/compose/02_services.html @@ -73,9 +73,4 @@ the security implications caused by such an increase of attack surface.

Tika enables the functionality for searching through attachments. Tika scans documents in email attachments, process (OCR, keyword extraction) and then index them in a way they can be efficiently searched. This requires significant resources (RAM, CPU and storage). - - - - - {% endcall %} diff --git a/setup/templates/steps/config.html b/setup/templates/steps/config.html index b348fa5b..c1a5ff43 100644 --- a/setup/templates/steps/config.html +++ b/setup/templates/steps/config.html @@ -4,7 +4,7 @@

- +

In the following sections we need to set the postmaster address. This is a combination of the postmaster local part and the main mail domain. @@ -99,8 +99,4 @@ manage your email domains, users, etc.

- - - - {% endcall %} diff --git a/towncrier/newsfragments/2890.bugfix b/towncrier/newsfragments/2890.bugfix new file mode 100644 index 00000000..28acf495 --- /dev/null +++ b/towncrier/newsfragments/2890.bugfix @@ -0,0 +1,3 @@ +Setup: +Regular expression for checking the Mailu storage path was invalid. +Added checks to make sure JavaScript is enabled and that all JS files could be loaded. The setup site malfunctions if this is not the case. \ No newline at end of file From e332a7de6a1a6021af03bad4366563a4a7b32eb6 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Sun, 29 Oct 2023 16:47:35 +0000 Subject: [PATCH 093/136] Refine subnet check and improve hint for defining subnet. 4th number is always 0 with a subnet. --- setup/templates/steps/compose/03_expose.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/setup/templates/steps/compose/03_expose.html b/setup/templates/steps/compose/03_expose.html index c1d3ca8c..77b43640 100644 --- a/setup/templates/steps/compose/03_expose.html +++ b/setup/templates/steps/compose/03_expose.html @@ -16,10 +16,11 @@ avoid generic all-interfaces addresses like 0.0.0.0 or :: - - - Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!) + Fourth number is always 0. Normally the format is '*.*.*.0/24' + From 16af54b15d76acef6dd33c134a5dfd569cfb5f81 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Mon, 30 Oct 2023 16:00:43 +0100 Subject: [PATCH 094/136] Only use split key in zonefile, not in gui/api/export --- core/admin/mailu/models.py | 4 +--- .../mailu/ui/templates/domain/details.html | 4 ---- core/admin/mailu/ui/views/domains.py | 23 +++++++++++-------- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py index 926ef2fc..45ec457c 100644 --- a/core/admin/mailu/models.py +++ b/core/admin/mailu/models.py @@ -232,9 +232,7 @@ class Domain(Base): """ return DKIM record for domain """ if self.dkim_key: selector = app.config['DKIM_SELECTOR'] - txt = f'v=DKIM1; k=rsa; p={self.dkim_publickey}' - record = ' '.join(f'"{txt[p:p+250]}"' for p in range(0, len(txt), 250)) - return f'{selector}._domainkey.{self.name}. 600 IN TXT {record}' + return f'{selector}._domainkey.{self.name}. 600 IN TXT "v=DKIM1; k=rsa; p={self.dkim_publickey}"' @cached_property def dns_dmarc(self): diff --git a/core/admin/mailu/ui/templates/domain/details.html b/core/admin/mailu/ui/templates/domain/details.html index a58c4477..74657c28 100644 --- a/core/admin/mailu/ui/templates/domain/details.html +++ b/core/admin/mailu/ui/templates/domain/details.html @@ -36,10 +36,6 @@ {%- if domain.dkim_publickey %} - - {% trans %}DKIM public key{% endtrans %} - {{ macros.clip("dkim_key") }}
{{ domain.dkim_publickey }}
- {% trans %}DNS DKIM entry{% endtrans %} {{ macros.clip("dns_dkim") }}
{{ domain.dns_dkim }}
diff --git a/core/admin/mailu/ui/views/domains.py b/core/admin/mailu/ui/views/domains.py index b03b96b0..6fb4410b 100644 --- a/core/admin/mailu/ui/views/domains.py +++ b/core/admin/mailu/ui/views/domains.py @@ -74,17 +74,22 @@ def domain_details(domain_name): @access.domain_admin(models.Domain, 'domain_name') def domain_download_zonefile(domain_name): domain = models.Domain.query.get(domain_name) or flask.abort(404) - final = domain.dns_mx+"\n" - final = final + domain.dns_spf+"\n" + res = [domain.dns_mx, domain.dns_spf] if domain.dkim_publickey: - final = final + domain.dkim_publickey+"\n" - final = final + domain.dns_dkim+"\n" - final = final + domain.dns_dmarc+"\n" + record = domain.dns_dkim.split('"', 1)[0].strip() + txt = f'v=DKIM1; k=rsa; p={domain.dkim_publickey}' + txt = ' '.join(f'"{txt[p:p+250]}"' for p in range(0, len(txt), 250)) + res.append(f'{record} {txt}"') + res.append(domain.dns_dmarc) if domain.dns_tlsa: - final = final + domain.dns_tlsa - for i in domain.dns_autoconfig: - final = final + i+"\n" - return flask.Response(final,content_type="text/plain",headers={'Content-disposition': 'attachment; filename='+domain.name+'-zonefile.txt'}) + res.append(domain.dns_tlsa) + res.extend(domain.dns_autoconfig) + res.append("") + return flask.Response( + "\n".join(res), + content_type="text/plain", + headers={"Content-disposition": f"attachment; filename={domain.name}-zonefile.txt"} + ) @ui.route('/domain/genkeys/', methods=['GET', 'POST']) From 130593cba558800bd2c04f4ff493ff7f084f2fb9 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Tue, 31 Oct 2023 07:59:12 +0000 Subject: [PATCH 095/136] Update dependencies docs image and fix non-working badge --- docs/Dockerfile | 4 ++-- docs/index.rst | 4 ++-- docs/requirements.txt | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index 18ca8333..958eaf87 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,5 +1,5 @@ # Convert .rst files to .html in temporary build container -FROM python:3.8-alpine3.14 AS build +FROM python:3.12.0-alpine3.18 AS build ARG version=master ENV VERSION=$version @@ -16,7 +16,7 @@ RUN apk add --no-cache --virtual .build-deps \ # Build nginx deployment image including generated html -FROM nginx:1.21-alpine +FROM nginx:1.25.3-alpine ARG version=master ARG pinned_version=master diff --git a/docs/index.rst b/docs/index.rst index 6e45c78f..f2cf56f3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,8 +8,8 @@ Mailu .. image:: https://img.shields.io/github/stars/mailu/mailu.svg?label=github&logo=github&maxAge=2592000 :target: https://github.com/mailu/mailu -.. image:: https://img.shields.io/docker/pulls/mailu/admin.svg?label=docker&maxAge=2592000 - :target: https://hub.docker.com/u/mailu/ +.. image:: https://img.shields.io/badge/Mailu-latest_release-blue + :target: https://github.com/Mailu/Mailu/releases .. image:: https://img.shields.io/badge/matrix-%23mailu%3Atedomum.net-7bc9a4.svg :target: https://matrix.to/#/#mailu:tedomum.net diff --git a/docs/requirements.txt b/docs/requirements.txt index 2c3169b7..46d263a7 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ recommonmark==0.7.1 -Sphinx==5.2.0 +Sphinx==7.2.6 sphinx-autobuild==2021.3.14 -sphinx-rtd-theme==1.0.0 -docutils==0.16 +sphinx-rtd-theme==1.3.0 +docutils==0.18.1 From d1a2a4d15e3d9773db7ed782c7c0c9307da8feab Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Tue, 31 Oct 2023 09:54:13 +0000 Subject: [PATCH 096/136] Check IPv4 and subnet server side, flash message if these are invalid. --- setup/server.py | 29 ++++++++++++++++++-- setup/templates/base.html | 2 ++ setup/templates/steps/compose/03_expose.html | 6 ++-- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/setup/server.py b/setup/server.py index 020da4a3..e983c60d 100644 --- a/setup/server.py +++ b/setup/server.py @@ -10,11 +10,13 @@ import random import ipaddress import hashlib import time +import secrets from flask_bootstrap import StaticCDN version = os.getenv("this_version", "master") static_url_path = "/" + version + "/static" app = flask.Flask(__name__, static_url_path=static_url_path) +app.secret_key = secrets.token_hex(16) flask_bootstrap.Bootstrap(app) # Load our jQuery. Do not use jQuery 1. app.extensions['bootstrap']['cdns']['jquery'] = StaticCDN() @@ -92,12 +94,33 @@ def build_app(path): def submit(): data = flask.request.form.copy() data['uid'] = str(uuid.uuid4()) + valid = True + try: + ipaddress.ip_address(data['bind4']) + except: + flask.flash('Configured IPv4 address is invalid', 'error') + valid = False + try: + ipaddress.ip_network(data['subnet']) + except: + flask.flash('Configured subnet is invalid.', 'error') + valid = False try: data['dns'] = str(ipaddress.IPv4Network(data['subnet'], strict=False)[-2]) except ValueError as err: - return "Error while generating files: " + str(err) - db.set(data['uid'], json.dumps(data)) - return flask.redirect(flask.url_for('.setup', uid=data['uid'])) + flask.flash('Invalid configuration: ' + str(err)) + valid = False + if valid: + db.set(data['uid'], json.dumps(data)) + return flask.redirect(flask.url_for('.setup', uid=data['uid'])) + else: + return flask.render_template( + 'wizard.html', + flavor="compose", + steps=sorted(os.listdir(os.path.join(path, "templates", "steps", "compose"))), + subnet6=random_ipv6_subnet() + ) + @prefix_bp.route("/setup/", methods=["GET"]) @root_bp.route("/setup/", methods=["GET"]) diff --git a/setup/templates/base.html b/setup/templates/base.html index 5576421a..b4b4ad3e 100644 --- a/setup/templates/base.html +++ b/setup/templates/base.html @@ -1,5 +1,6 @@ {% extends "bootstrap/base.html" %} {% import "macros.html" as macros %} +{% from 'bootstrap/utils.html' import flashed_messages %} {% block title %}Mailu setup{% endblock %} @@ -15,6 +16,7 @@

Mailu configuration

+ {{ flashed_messages() }}

Version + - +

From 49f3981d60afe45388cc930c1eaadb3ca2295ffd Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Tue, 31 Oct 2023 10:25:28 +0000 Subject: [PATCH 097/136] Also check server-side subnet6 and bind6 --- setup/server.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/setup/server.py b/setup/server.py index e983c60d..96ce1db3 100644 --- a/setup/server.py +++ b/setup/server.py @@ -100,10 +100,20 @@ def build_app(path): except: flask.flash('Configured IPv4 address is invalid', 'error') valid = False + try: + ipaddress.ip_address(data['bind6']) + except: + flask.flash('Configured IPv6 address is invalid', 'error') + valid = False try: ipaddress.ip_network(data['subnet']) except: - flask.flash('Configured subnet is invalid.', 'error') + flask.flash('Configured subnet(IPv4) is invalid.', 'error') + valid = False + try: + ipaddress.ip_network(data['subnet6']) + except: + flask.flash('Configured subnet(IPv6) is invalid.', 'error') valid = False try: data['dns'] = str(ipaddress.IPv4Network(data['subnet'], strict=False)[-2]) From ac9a8a458fc67e99fa7a356005c2323ba1b9f490 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Tue, 31 Oct 2023 12:49:05 +0000 Subject: [PATCH 098/136] Increase connect timeout for clamav (hopefully this fixs CI filter test) --- core/rspamd/conf/antivirus.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/rspamd/conf/antivirus.conf b/core/rspamd/conf/antivirus.conf index 0f47ecca..072c687b 100644 --- a/core/rspamd/conf/antivirus.conf +++ b/core/rspamd/conf/antivirus.conf @@ -4,7 +4,7 @@ clamav { symbol = "CLAM_VIRUS"; type = "clamav"; servers = "{{ ANTIVIRUS_ADDRESS }}:3310"; - timeout = 5; + timeout = 60; retransmits = 10; {% if ANTIVIRUS_ACTION|default('discard') == 'reject' %} action = "reject" From a9fa592868c32e3e3be5b0d518990ae659445e90 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Tue, 31 Oct 2023 13:52:00 +0000 Subject: [PATCH 099/136] Remove reg exp check for IPv6. This is now handled server-side. --- setup/templates/steps/compose/03_expose.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup/templates/steps/compose/03_expose.html b/setup/templates/steps/compose/03_expose.html index b00ce7d7..6fcada29 100644 --- a/setup/templates/steps/compose/03_expose.html +++ b/setup/templates/steps/compose/03_expose.html @@ -33,8 +33,7 @@ avoid generic all-interfaces addresses like 0.0.0.0 or ::Read this: Docker currently does not expose the IPv6 ports properly, as it does not interface with ip6tables. Read FAQ section and be very careful. We do NOT recommend that you enable this!

- +
From 5bee3c031bbffbf9344ac1edb5385e9781400a8a Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Tue, 31 Oct 2023 19:55:58 +0000 Subject: [PATCH 100/136] Update all dependencies. All changes were recreated due to deprecated functionalities introduced by updating the dependencies --- .gitignore | 1 + core/admin/mailu/__init__.py | 17 ++--- core/admin/mailu/debug.py | 5 +- core/admin/mailu/utils.py | 20 +++--- core/base/requirements-build.txt | 6 +- core/base/requirements-prod.txt | 113 ++++++++++++++++--------------- 6 files changed, 86 insertions(+), 76 deletions(-) diff --git a/.gitignore b/.gitignore index 6c48a270..84ee07d3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ pip-selfcheck.json /core/admin/lib* /core/admin/bin /core/admin/include +/core/base/.venv /docs/lib* /docs/bin /docs/include diff --git a/core/admin/mailu/__init__.py b/core/admin/mailu/__init__.py index d3971cb1..69d8dc09 100644 --- a/core/admin/mailu/__init__.py +++ b/core/admin/mailu/__init__.py @@ -40,7 +40,7 @@ def create_app_from_config(config): models.db.init_app(app) utils.session.init_app(app) utils.limiter.init_app(app) - utils.babel.init_app(app) + utils.babel.init_app(app, locale_selector=utils.get_locale) utils.login.init_app(app) utils.login.user_loader(models.User.get) utils.proxy.init_app(app) @@ -52,13 +52,14 @@ def create_app_from_config(config): app.truncated_pw_key = hmac.new(bytearray(app.secret_key, 'utf-8'), bytearray('TRUNCATED_PW_KEY', 'utf-8'), 'sha256').digest() # Initialize list of translations - app.config.translations = { - str(locale): locale - for locale in sorted( - utils.babel.list_translations(), - key=lambda l: l.get_language_name().title() - ) - } + with app.app_context(): + app.config.translations = { + str(locale): locale + for locale in sorted( + utils.babel.list_translations(), + key=lambda l: l.get_language_name().title() + ) + } # Initialize debugging tools if app.config.get("DEBUG"): diff --git a/core/admin/mailu/debug.py b/core/admin/mailu/debug.py index 4d63f3c5..c45f4ae7 100644 --- a/core/admin/mailu/debug.py +++ b/core/admin/mailu/debug.py @@ -1,10 +1,11 @@ -import flask_debugtoolbar +#Note: Currently flask_debugtoolbar is not compatible with flask. +#import flask_debugtoolbar from werkzeug.middleware.profiler import ProfilerMiddleware # Debugging toolbar -toolbar = flask_debugtoolbar.DebugToolbarExtension() +#toolbar = flask_debugtoolbar.DebugToolbarExtension() # Profiler diff --git a/core/admin/mailu/utils.py b/core/admin/mailu/utils.py index 541ffb4d..9f75c233 100644 --- a/core/admin/mailu/utils.py +++ b/core/admin/mailu/utils.py @@ -100,7 +100,6 @@ def is_ip_in_subnet(ip, subnets=[]): # Application translation babel = flask_babel.Babel() -@babel.localeselector def get_locale(): """ selects locale for translation """ if not app.config['SESSION_COOKIE_NAME'] in flask.request.cookies: @@ -310,7 +309,7 @@ class MailuSessionConfig: # default size of session key parts uid_bits = 64 # default if SESSION_KEY_BITS is not set in config sid_bits = 128 # for now. must be multiple of 8! - time_bits = 32 # for now. must be multiple of 8! + time_bits = 32 # for now. must be multiple of 8! def __init__(self, app=None): @@ -400,7 +399,7 @@ class MailuSessionInterface(SessionInterface): if session.modified: session.delete() response.delete_cookie( - app.session_cookie_name, + app.config['SESSION_COOKIE_NAME'], domain=self.get_cookie_domain(app), path=self.get_cookie_path(app), ) @@ -413,7 +412,7 @@ class MailuSessionInterface(SessionInterface): # save session and update cookie if necessary if session.save(): response.set_cookie( - app.session_cookie_name, + app.config['SESSION_COOKIE_NAME'], session.sid, expires=datetime.now()+timedelta(seconds=app.config['PERMANENT_SESSION_LIFETIME']), httponly=self.get_cookie_httponly(app), @@ -473,29 +472,30 @@ class MailuSessionExtension: def init_app(self, app): """ Replace session management of application. """ + redis_session = False + if app.config.get('MEMORY_SESSIONS'): # in-memory session store for use in development app.session_store = DictStore() else: # redis-based session store for use in production + redis_session = True app.session_store = RedisStore( redis.StrictRedis().from_url(app.config['SESSION_STORAGE_URL']) ) + app.session_config = MailuSessionConfig(app) + app.session_interface = MailuSessionInterface() + if redis_session: # clean expired sessions once on first use in case lifetime was changed - def cleaner(): + with app.app_context(): with cleaned.get_lock(): if not cleaned.value: cleaned.value = True app.logger.info('cleaning session store') MailuSessionExtension.cleanup_sessions(app) - app.before_first_request(cleaner) - - app.session_config = MailuSessionConfig(app) - app.session_interface = MailuSessionInterface() - cleaned = Value('i', False) session = MailuSessionExtension() diff --git a/core/base/requirements-build.txt b/core/base/requirements-build.txt index 62a7d9fd..ed145a00 100644 --- a/core/base/requirements-build.txt +++ b/core/base/requirements-build.txt @@ -1,3 +1,3 @@ -pip==22.3.1 -setuptools==65.6.3 -wheel==0.38.4 +pip==23.3.1 +setuptools==68.2.2 +wheel==0.41.3 diff --git a/core/base/requirements-prod.txt b/core/base/requirements-prod.txt index 1f1389cf..93c36640 100644 --- a/core/base/requirements-prod.txt +++ b/core/base/requirements-prod.txt @@ -1,86 +1,93 @@ -aiodns==3.0.0 -aiohttp==3.8.5 -aiosignal==1.2.0 -alembic==1.8.1 -async-timeout==4.0.2 -attrs==22.1.0 -Babel==2.11.0 +aiodns==3.1.1 +aiohttp==3.8.6 +aiosignal==1.3.1 +alembic==1.12.1 +aniso8601==9.0.1 +async-timeout==4.0.3 +attrs==23.1.0 +Babel==2.13.1 bcrypt==4.0.1 -blinker==1.5 +blinker==1.6.3 certifi==2023.7.22 -cffi==1.15.1 -charset-normalizer==2.1.1 -click==8.1.3 +cffi==1.16.0 +charset-normalizer==3.3.1 +click==8.1.7 colorclass==2.2.2 -cryptography==41.0.3 -decorator==5.1.1 +cryptography==41.0.5 defusedxml==0.7.1 -Deprecated==1.2.13 -dnspython==2.2.1 -dominate==2.7.0 +Deprecated==1.2.14 +dnspython==2.4.2 +dominate==2.8.0 easygui==0.98.3 -email-validator==1.3.0 -Flask==2.2.5 -Flask-Babel==2.0.0 +email-validator==2.1.0.post1 +Flask==2.3.3 +flask-babel==4.0.0 Flask-Bootstrap==3.3.7.1 -Flask-DebugToolbar==0.13.1 -Flask-Login==0.6.2 -flask-marshmallow==0.14.0 -Flask-Migrate==3.1.0 -Flask-RESTX==1.0.5 +#Flask-DebugToolbar is not compatible with Flask 2.3.3+ +#Flask-DebugToolbar==0.13.1 +Flask-Login==0.6.3 +flask-marshmallow==0.15.0 +Flask-Migrate==4.0.5 +flask-restx==1.1.0 Flask-SQLAlchemy==2.5.1 -Flask-WTF==1.0.1 -frozenlist==1.3.1 -greenlet==2.0.0 -gunicorn==20.1.0 +# >2.5.1 bug with parsing models.py. Could otherwise be 3.0.5 +Flask-WTF==1.2.1 +frozenlist==1.4.0 +greenlet==3.0.1 +gunicorn==21.2.0 idna==3.4 +importlib-resources==6.1.0 infinity==1.5 intervals==0.9.2 itsdangerous==2.1.2 Jinja2==3.1.2 -limits==2.7.1 -Mako==1.2.3 -MarkupSafe==2.1.1 -marshmallow==3.18.0 -marshmallow-sqlalchemy==0.28.1 +jsonschema==4.19.2 +jsonschema-specifications==2023.7.1 +limits==3.6.0 +Mako==1.2.4 +MarkupSafe==2.1.3 +marshmallow==3.20.1 +marshmallow-sqlalchemy==0.29 msoffcrypto-tool==5.1.1 -multidict==6.0.2 -mysql-connector-python==8.0.32 +multidict==6.0.4 +mysql-connector-python==8.2.0 olefile==0.46 oletools==0.60.1 -packaging==21.3 +packaging==23.2 passlib==1.7.4 pcodedmp==1.2.6 podop @ file:///app/libs/podop -postfix-mta-sts-resolver==1.1.4 -protobuf==3.20.2 -psycopg2-binary==2.9.5 -pycares==4.2.2 +postfix-mta-sts-resolver==1.4.0 +protobuf==4.21.12 +psycopg2-binary==2.9.9 +pycares==4.4.0 pycparser==2.21 -Pygments==2.15.0 +Pygments==2.16.1 pyparsing==2.4.7 python-dateutil==2.8.2 python-magic==0.4.27 -pytz==2022.6 +pytz==2023.3.post1 PyYAML==6.0.1 Radicale==3.1.8 -redis==4.4.4 +redis==5.0.1 +referencing==0.30.2 requests==2.31.0 +rpds-py==0.10.6 six==1.16.0 socrate @ file:///app/libs/socrate -SQLAlchemy==1.4.42 +SQLAlchemy==1.4.50 srslib==0.1.4 tabulate==0.9.0 -tenacity==8.1.0 -typing_extensions==4.4.0 -urllib3==1.26.12 -validators==0.20.0 +tenacity==8.2.3 +typing_extensions==4.8.0 +urllib3==2.0.7 +validators==0.22.0 visitor==0.1.3 vobject==0.9.6.1 -watchdog==2.1.9 -Werkzeug==2.2.3 -wrapt==1.14.1 -WTForms==3.0.1 +watchdog==3.0.0 +Werkzeug===2.3.7 +wrapt==1.15.0 +WTForms==3.1.0 WTForms-Components==0.10.5 xmltodict==0.13.0 -yarl==1.8.1 +yarl==1.9.2 From bf79df5c80c83d054e06dfa988c7d56d750eabc7 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Tue, 31 Oct 2023 20:02:34 +0000 Subject: [PATCH 101/136] Add changelog entry --- towncrier/newsfragments/3032.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 towncrier/newsfragments/3032.misc diff --git a/towncrier/newsfragments/3032.misc b/towncrier/newsfragments/3032.misc new file mode 100644 index 00000000..1ecc2b57 --- /dev/null +++ b/towncrier/newsfragments/3032.misc @@ -0,0 +1 @@ +Update all python dependencies in preparation of next Mailu release. From b5ecaa278e7230ce2f27939f64a4e8eaae0b74c1 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 1 Nov 2023 08:12:03 +0100 Subject: [PATCH 102/136] Enable snowball --- core/dovecot/conf/dovecot.conf | 6 +++--- towncrier/newsfragments/2977.misc | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 towncrier/newsfragments/2977.misc diff --git a/core/dovecot/conf/dovecot.conf b/core/dovecot/conf/dovecot.conf index 0539ac68..906b0e30 100644 --- a/core/dovecot/conf/dovecot.conf +++ b/core/dovecot/conf/dovecot.conf @@ -65,9 +65,9 @@ plugin { fts_enforced = yes fts_autoindex_exclude = \Trash fts_autoindex_exclude1 = \Junk - fts_filters = normalizer-icu stopwords - fts_filters_en = lowercase english-possessive stopwords - fts_filters_fr = lowercase contractions stopwords + fts_filters = normalizer-icu snowball stopwords + fts_filters_en = lowercase snowball english-possessive stopwords + fts_filters_fr = lowercase snowball contractions stopwords fts_header_excludes = Received DKIM-* ARC-* X-* x-* Comments Delivered-To Return-Path Authentication-Results Message-ID References In-Reply-To Thread-* Accept-Language Content-* MIME-Version {% if FULL_TEXT_SEARCH_ATTACHMENTS %} fts_tika = http://{{ FTS_ATTACHMENTS_ADDRESS }}:9998/tika/ diff --git a/towncrier/newsfragments/2977.misc b/towncrier/newsfragments/2977.misc new file mode 100644 index 00000000..afc9c97b --- /dev/null +++ b/towncrier/newsfragments/2977.misc @@ -0,0 +1 @@ +Improve FTS by adding the snowball filter. This should significantly cut down the size of indexes. You may want to re-index after upgrading. From a918cdb6bd778a58abf6c04a35f07aea8136f1d8 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 1 Nov 2023 08:19:25 +0100 Subject: [PATCH 103/136] Increase the timeout of oletools to match clamav's --- core/rspamd/conf/external_services.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/rspamd/conf/external_services.conf b/core/rspamd/conf/external_services.conf index 609f341b..2557bcb7 100644 --- a/core/rspamd/conf/external_services.conf +++ b/core/rspamd/conf/external_services.conf @@ -7,8 +7,8 @@ oletools { scan_mime_parts = true; extended = true; max_size = 3145728; - timeout = 20.0; - retransmits = 1; + timeout = 60.0; + retransmits = 10; patterns { OLETOOLS_MACRO_FOUND= '^.....M..$'; From d4116cf3b3b0d841e8324a269dc16b9eec539e4a Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 1 Nov 2023 10:54:53 +0100 Subject: [PATCH 104/136] When FTS is enabled this is cheap --- webmails/roundcube/config/config.inc.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/webmails/roundcube/config/config.inc.php b/webmails/roundcube/config/config.inc.php index 045919da..116dc929 100644 --- a/webmails/roundcube/config/config.inc.php +++ b/webmails/roundcube/config/config.inc.php @@ -18,6 +18,11 @@ $config['session_lifetime'] = {{ (((PERMANENT_SESSION_LIFETIME | default(10800)) $config['request_path'] = '{{ WEB_WEBMAIL or "none" }}'; $config['trusted_host_patterns'] = [ {{ HOSTNAMES.split(",") | map("tojson") | join(',') }}]; +{% (FULL_TEXT_SEARCH or '').lower() not in ['off', 'false', '0'] %} +$config['search_mods'] = ['*' => ['subject'=>1, 'from'=>1, 'to'=>1, 'cc'=>1, 'bcc'=>1, 'replyto'=>1, 'followupto'=>1, 'body'=>1]]; +$config['search_scope'] = 'sub'; +{% endif %} + // Mail servers $config['imap_host'] = 'tls://{{ FRONT_ADDRESS or "front" }}:10143'; $config['imap_conn_options'] = array( From fe9b16142f17d0922a9c010ce1b91f90c8aa7680 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 1 Nov 2023 11:04:01 +0100 Subject: [PATCH 105/136] Same for snappymail --- webmails/snappymail/defaults/default.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/webmails/snappymail/defaults/default.json b/webmails/snappymail/defaults/default.json index d7659405..0cb886b6 100644 --- a/webmails/snappymail/defaults/default.json +++ b/webmails/snappymail/defaults/default.json @@ -12,7 +12,10 @@ "SNI_enabled": true, "disable_compression": true, "security_level": 1 - } + }, + {% (FULL_TEXT_SEARCH or '').lower() not in ['off', 'false', '0'] %} + "fast_simple_search": "false", + {% endif %} }, "SMTP": { "host": "{{ FRONT_ADDRESS }}", From e77b3f0a36dcb9a09c906181f8d0b47ac16d8f36 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Wed, 1 Nov 2023 18:01:39 +0100 Subject: [PATCH 106/136] Update newsfragment --- towncrier/newsfragments/3008.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/towncrier/newsfragments/3008.feature b/towncrier/newsfragments/3008.feature index 1bda6ca6..1bc39cb1 100644 --- a/towncrier/newsfragments/3008.feature +++ b/towncrier/newsfragments/3008.feature @@ -1 +1 @@ -Add "download zonefile" button to domain configuration \ No newline at end of file +Add "download zonefile" button to domain configuration and un-split dkim key in dns table From fa8e8f4f7313634efd1737b36c72b0771939d87a Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Wed, 1 Nov 2023 18:02:40 +0100 Subject: [PATCH 107/136] Rename 3008.feature to 3023.feature --- towncrier/newsfragments/{3008.feature => 3023.feature} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename towncrier/newsfragments/{3008.feature => 3023.feature} (100%) diff --git a/towncrier/newsfragments/3008.feature b/towncrier/newsfragments/3023.feature similarity index 100% rename from towncrier/newsfragments/3008.feature rename to towncrier/newsfragments/3023.feature From 901e4a772dbec86faaf3a2be34e98f3eff64e025 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Thu, 2 Nov 2023 15:58:15 +0100 Subject: [PATCH 108/136] Remove surplus double quote --- core/admin/mailu/ui/views/domains.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/mailu/ui/views/domains.py b/core/admin/mailu/ui/views/domains.py index 6fb4410b..dcd1aedd 100644 --- a/core/admin/mailu/ui/views/domains.py +++ b/core/admin/mailu/ui/views/domains.py @@ -79,7 +79,7 @@ def domain_download_zonefile(domain_name): record = domain.dns_dkim.split('"', 1)[0].strip() txt = f'v=DKIM1; k=rsa; p={domain.dkim_publickey}' txt = ' '.join(f'"{txt[p:p+250]}"' for p in range(0, len(txt), 250)) - res.append(f'{record} {txt}"') + res.append(f'{record} {txt}') res.append(domain.dns_dmarc) if domain.dns_tlsa: res.append(domain.dns_tlsa) From b74cd17bdd3d8e70fc3b8514eca32d39eca845c9 Mon Sep 17 00:00:00 2001 From: ctrl-i <1422608+ctrl-i@users.noreply.github.com> Date: Mon, 6 Nov 2023 07:55:23 +0000 Subject: [PATCH 109/136] Upgrade to roundcube 1.6.5 (fix XSS) --- towncrier/newsfragments/3024.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 towncrier/newsfragments/3024.misc diff --git a/towncrier/newsfragments/3024.misc b/towncrier/newsfragments/3024.misc new file mode 100644 index 00000000..c5f34547 --- /dev/null +++ b/towncrier/newsfragments/3024.misc @@ -0,0 +1 @@ +Upgrade to roundcube 1.6.5 (fix XSS) From 3a3f6d06944f5062bafa184cf156a8b90e4741a2 Mon Sep 17 00:00:00 2001 From: ctrl-i <1422608+ctrl-i@users.noreply.github.com> Date: Mon, 6 Nov 2023 07:56:13 +0000 Subject: [PATCH 110/136] Update Dockerfile --- webmails/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webmails/Dockerfile b/webmails/Dockerfile index 51e0a98c..09fbd8ee 100644 --- a/webmails/Dockerfile +++ b/webmails/Dockerfile @@ -27,7 +27,7 @@ RUN set -euxo pipefail \ ; mkdir -p /run/nginx /conf # roundcube -ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.6.4/roundcubemail-1.6.4-complete.tar.gz +ENV ROUNDCUBE_URL https://github.com/roundcube/roundcubemail/releases/download/1.6.5/roundcubemail-1.6.5-complete.tar.gz ENV CARDDAV_URL https://github.com/mstilkerich/rcmcarddav/releases/download/v5.1.0/carddav-v5.1.0.tar.gz RUN set -euxo pipefail \ From 239ef0092ee86fa825292c1af14c25d6a97c0f77 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 7 Nov 2023 09:23:22 +0100 Subject: [PATCH 111/136] Doh --- webmails/roundcube/config/config.inc.php | 2 +- webmails/snappymail/defaults/default.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/webmails/roundcube/config/config.inc.php b/webmails/roundcube/config/config.inc.php index 116dc929..0791bad9 100644 --- a/webmails/roundcube/config/config.inc.php +++ b/webmails/roundcube/config/config.inc.php @@ -18,7 +18,7 @@ $config['session_lifetime'] = {{ (((PERMANENT_SESSION_LIFETIME | default(10800)) $config['request_path'] = '{{ WEB_WEBMAIL or "none" }}'; $config['trusted_host_patterns'] = [ {{ HOSTNAMES.split(",") | map("tojson") | join(',') }}]; -{% (FULL_TEXT_SEARCH or '').lower() not in ['off', 'false', '0'] %} +{% if (FULL_TEXT_SEARCH or '').lower() not in ['off', 'false', '0'] %} $config['search_mods'] = ['*' => ['subject'=>1, 'from'=>1, 'to'=>1, 'cc'=>1, 'bcc'=>1, 'replyto'=>1, 'followupto'=>1, 'body'=>1]]; $config['search_scope'] = 'sub'; {% endif %} diff --git a/webmails/snappymail/defaults/default.json b/webmails/snappymail/defaults/default.json index 0cb886b6..4395636f 100644 --- a/webmails/snappymail/defaults/default.json +++ b/webmails/snappymail/defaults/default.json @@ -13,8 +13,8 @@ "disable_compression": true, "security_level": 1 }, - {% (FULL_TEXT_SEARCH or '').lower() not in ['off', 'false', '0'] %} - "fast_simple_search": "false", + {% if (FULL_TEXT_SEARCH or '').lower() not in ['off', 'false', '0'] %} + "fast_simple_search": "false", {% endif %} }, "SMTP": { From 80d03ae60bc1d243750fbeec9a57b8132420241e Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 7 Nov 2023 09:42:43 +0100 Subject: [PATCH 112/136] doh2 --- webmails/snappymail/defaults/default.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webmails/snappymail/defaults/default.json b/webmails/snappymail/defaults/default.json index 4395636f..12252aee 100644 --- a/webmails/snappymail/defaults/default.json +++ b/webmails/snappymail/defaults/default.json @@ -14,7 +14,7 @@ "security_level": 1 }, {% if (FULL_TEXT_SEARCH or '').lower() not in ['off', 'false', '0'] %} - "fast_simple_search": "false", + "fast_simple_search": "false" {% endif %} }, "SMTP": { From 02d5202c68f352498e10deb4add00e52ecfc34c1 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Tue, 7 Nov 2023 10:49:59 +0000 Subject: [PATCH 113/136] Process ghostwheel's suggestion. By default hide the container div element and show the no-javascript div element. Via JavaScript hide the no-java div element and show the container div element. --- setup/static/render.js | 6 +++++- setup/templates/base.html | 23 +++++------------------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/setup/static/render.js b/setup/static/render.js index 34a34f22..2276b19d 100644 --- a/setup/static/render.js +++ b/setup/static/render.js @@ -1,7 +1,11 @@ -var render = 'RenderLoaded'; //Store API token in variable. var token = $("#api_token").val(); +$(document).ready(function() { + $("#no_java_script").hide(); + $("#container").show(); +}); + $(document).ready(function() { if ($("#webmail").val() == 'none') { $("#webmail_path").hide(); diff --git a/setup/templates/base.html b/setup/templates/base.html index b4b4ad3e..41d85b54 100644 --- a/setup/templates/base.html +++ b/setup/templates/base.html @@ -5,16 +5,12 @@ {% block title %}Mailu setup{% endblock %} {% block content %} - -
+
+ JavaScript is not enabled or JavaScript files were blocked. The Mailu setup site does not function when JavaScript is disabled. +
+ + -
- -
-
From d370d40df7d9fa4396558a7219bcfe8f793c5086 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Tue, 7 Nov 2023 15:24:23 +0000 Subject: [PATCH 115/136] Reinstate statistics which is currently not used. Remove unneeded line in Dockerfile. --- setup/Dockerfile | 4 ---- setup/flavors/compose/mailu.env | 3 +++ setup/templates/steps/config.html | 7 +++++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/setup/Dockerfile b/setup/Dockerfile index 6a57db18..a410871d 100644 --- a/setup/Dockerfile +++ b/setup/Dockerfile @@ -14,10 +14,6 @@ COPY main.py ./main.py RUN echo $VERSION >> /version -#Note: This is appended so we can explicitly check if this JS file has been loaded -#by the user's internet browser when accessing the setup site. -RUN echo var jQueryMailu=\'loaded\'\; >> ./static/jquery.min.js - EXPOSE 80/tcp HEALTHCHECK --start-period=350s CMD curl -skfLo /dev/null http://localhost/ USER mailu diff --git a/setup/flavors/compose/mailu.env b/setup/flavors/compose/mailu.env index 61bbc3c5..cffafe15 100644 --- a/setup/flavors/compose/mailu.env +++ b/setup/flavors/compose/mailu.env @@ -39,6 +39,9 @@ AUTH_RATELIMIT_IP={{ auth_ratelimit_ip }}/hour AUTH_RATELIMIT_USER={{ auth_ratelimit_user }}/day {% endif %} +# Opt-out of statistics, replace with "True" to opt out +DISABLE_STATISTICS={{ disable_statistics or 'False' }} + ################################### # Optional features ################################### diff --git a/setup/templates/steps/config.html b/setup/templates/steps/config.html index d314c40b..c1a5ff43 100644 --- a/setup/templates/steps/config.html +++ b/setup/templates/steps/config.html @@ -58,6 +58,13 @@ Or in plain English: if receivers start to classify your mail as spam, this post

+
+ +
+
From 1c26368b3729db004942335b45c0fb10bd31f2a1 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 7 Nov 2023 16:55:24 +0100 Subject: [PATCH 116/136] Add a sigterm handler to make docker go faster --- core/base/libs/socrate/socrate/system.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/base/libs/socrate/socrate/system.py b/core/base/libs/socrate/socrate/system.py index 0a0dba52..ec94967c 100644 --- a/core/base/libs/socrate/socrate/system.py +++ b/core/base/libs/socrate/socrate/system.py @@ -1,6 +1,7 @@ import hmac import logging as log import os +import signal import sys import re from pwd import getpwnam @@ -69,11 +70,17 @@ def _is_compatible_with_hardened_malloc(): return False return True + +def sigterm_handler(_signo, _stack_frame): + log.error("Received SIGTERM, terminating.") + sys.exit(0) + def set_env(required_secrets=[], log_filters=[]): if log_filters: sys.stdout = LogFilter(sys.stdout, log_filters) sys.stderr = LogFilter(sys.stderr, log_filters) log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", 'WARNING')) + signal.signal(signal.SIGTERM, sigterm_handler) if not 'LD_PRELOAD' in os.environ and _is_compatible_with_hardened_malloc(): log.warning('Your CPU has Advanced Vector Extensions available, we recommend you enable hardened-malloc earlier in the boot process by adding LD_PRELOAD=/usr/lib/libhardened_malloc.so to your mailu.env') From 81b458efe28bf4a539a771c57bf0d22679e2a785 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 7 Nov 2023 17:02:32 +0100 Subject: [PATCH 117/136] Maybe fix the log-filter on admin --- core/admin/mailu/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin/mailu/__init__.py b/core/admin/mailu/__init__.py index 69d8dc09..19731340 100644 --- a/core/admin/mailu/__init__.py +++ b/core/admin/mailu/__init__.py @@ -12,7 +12,7 @@ import hmac class NoPingFilter(logging.Filter): def filter(self, record): - if (record.args['{host}i'] == 'localhost' and record.args['r'] == 'GET /ping HTTP/1.1'): + if record.args['r'].endswith(' /ping HTTP/1.1'): return False if record.args['r'].endswith(' /internal/rspamd/local_domains HTTP/1.1'): return False From e75834f7465214a70b75556ffcb09063105bac7c Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 9 Nov 2023 10:20:42 +0100 Subject: [PATCH 118/136] Update system.py 143 is the standard following SIGTERM --- core/base/libs/socrate/socrate/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/base/libs/socrate/socrate/system.py b/core/base/libs/socrate/socrate/system.py index ec94967c..a03d36e5 100644 --- a/core/base/libs/socrate/socrate/system.py +++ b/core/base/libs/socrate/socrate/system.py @@ -73,7 +73,7 @@ def _is_compatible_with_hardened_malloc(): def sigterm_handler(_signo, _stack_frame): log.error("Received SIGTERM, terminating.") - sys.exit(0) + sys.exit(143) def set_env(required_secrets=[], log_filters=[]): if log_filters: From 38b6d360d3453fa641f77b48cb13a9320b35cead Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 9 Nov 2023 10:26:04 +0100 Subject: [PATCH 119/136] Update system.py promote to log.critical() --- core/base/libs/socrate/socrate/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/base/libs/socrate/socrate/system.py b/core/base/libs/socrate/socrate/system.py index a03d36e5..3dcdea3d 100644 --- a/core/base/libs/socrate/socrate/system.py +++ b/core/base/libs/socrate/socrate/system.py @@ -72,7 +72,7 @@ def _is_compatible_with_hardened_malloc(): def sigterm_handler(_signo, _stack_frame): - log.error("Received SIGTERM, terminating.") + log.critical("Received SIGTERM, terminating.") sys.exit(143) def set_env(required_secrets=[], log_filters=[]): From dbb2d78558eb5f0ccd455f5946f48acc972645f7 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Thu, 9 Nov 2023 16:03:22 +0100 Subject: [PATCH 120/136] Fixup doc --- docs/configuration.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 84916041..50a576fd 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -214,9 +214,8 @@ Depending on your particular deployment you most probably will want to change th Advanced settings ----------------- -The ``AUTH_REQUIRE_TOKENS`` (default: False) setting controls whether thick clients can - authenticate using passwords or whether they are forced to use tokens/application - specific passwords. + +The ``AUTH_REQUIRE_TOKENS`` (default: False) setting controls whether thick clients can authenticate using passwords or whether they are forced to use tokens/application specific passwords. The ``API_TOKEN`` (default: None) setting configures the authentication token. This token must be passed as request header to the API as authentication token. From aefbd9552cf5fd9f72844b15975632475a89d919 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 10 Nov 2023 12:00:29 +0100 Subject: [PATCH 121/136] fix clamav handling --- core/admin/mailu/internal/templates/default.sieve | 5 ----- core/rspamd/conf/force_actions.conf | 10 ++++++++++ towncrier/newsfragments/3048.bugfix | 1 + 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 towncrier/newsfragments/3048.bugfix diff --git a/core/admin/mailu/internal/templates/default.sieve b/core/admin/mailu/internal/templates/default.sieve index c1772c2e..0e97c067 100644 --- a/core/admin/mailu/internal/templates/default.sieve +++ b/core/admin/mailu/internal/templates/default.sieve @@ -29,11 +29,6 @@ if spamtest :percent :value "gt" :comparator "i;ascii-numeric" "{{ user.spam_thr } {% endif %} -if exists "X-Virus" { - discard; - stop; -} - {% if user.reply_active %} if not address :localpart :contains ["From","Reply-To"] ["noreply","no-reply"]{ vacation :days 1 {% if user.displayed_name != "" %}:from "{{ user.displayed_name }} <{{ user.email }}>"{% endif %} :subject "{{ user.reply_subject }}" "{{ user.reply_body }}"; diff --git a/core/rspamd/conf/force_actions.conf b/core/rspamd/conf/force_actions.conf index b72a8a0c..b322b8bd 100644 --- a/core/rspamd/conf/force_actions.conf +++ b/core/rspamd/conf/force_actions.conf @@ -14,5 +14,15 @@ rules { expression = "!IS_LOCALLY_GENERATED & !MAILLIST & BLACKLIST_ANTISPOOF"; message = "Rejected (anti-spoofing: auth-failed)"; } + ANTIVIRUS_FLAGGED { + action = "reject"; + expression = "CLAM_VIRUS"; + message = "Rejected (anti-virus)"; + } + ANTIVIRUS_FAILED { + action = "soft-reject"; + expression = "CLAM_VIRUS_FAIL | OLETOOLS_FAIL"; + message = "Please retry later (anti-virus/oletools)"; + } } .include(try=true,priority=1,duplicate=merge) "/overrides/force_actions.conf" diff --git a/towncrier/newsfragments/3048.bugfix b/towncrier/newsfragments/3048.bugfix new file mode 100644 index 00000000..0bc83bbb --- /dev/null +++ b/towncrier/newsfragments/3048.bugfix @@ -0,0 +1 @@ +Ensure that we do not silently discard PUAs flagged by clamav. Instead we will reject emails. From f7fb0f66256a34c36766fcc24e79ce0912e293c7 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 10 Nov 2023 12:13:04 +0100 Subject: [PATCH 122/136] Add a new test for PUAs --- tests/compose/filters/01_email_test.sh | 7 ++++++- tests/compose/filters/PotentiallyUnwanted.exe_ | Bin 0 -> 33280 bytes 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 tests/compose/filters/PotentiallyUnwanted.exe_ diff --git a/tests/compose/filters/01_email_test.sh b/tests/compose/filters/01_email_test.sh index 50e904f8..30b58c51 100755 --- a/tests/compose/filters/01_email_test.sh +++ b/tests/compose/filters/01_email_test.sh @@ -1,6 +1,11 @@ python3 tests/email_test.py message-virus "tests/compose/filters/eicar.com.txt" if [ $? -eq 99 ]; then - exit 0 + python3 tests/email_test.py message-PUA "tests/compose/filters/PotentiallyUnwanted.exe_" + if [ $? -eq 99 ]; then + return 0 + else + exit 1 + fi else exit 1 fi diff --git a/tests/compose/filters/PotentiallyUnwanted.exe_ b/tests/compose/filters/PotentiallyUnwanted.exe_ new file mode 100644 index 0000000000000000000000000000000000000000..6a12a279fc007549bfd9da7cba06335c7a86ba92 GIT binary patch literal 33280 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 literal 0 HcmV?d00001 From e3675a676c178ce1d961c283bef9387b0f52d2cd Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 10 Nov 2023 12:24:14 +0100 Subject: [PATCH 123/136] Reject on OLETOOLS too --- core/rspamd/conf/force_actions.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/rspamd/conf/force_actions.conf b/core/rspamd/conf/force_actions.conf index b322b8bd..85923e09 100644 --- a/core/rspamd/conf/force_actions.conf +++ b/core/rspamd/conf/force_actions.conf @@ -16,13 +16,13 @@ rules { } ANTIVIRUS_FLAGGED { action = "reject"; - expression = "CLAM_VIRUS"; - message = "Rejected (anti-virus)"; + expression = "CLAM_VIRUS | OLETOOLS_MACRO_MRAPTOR | OLETOOLS_MACRO_SUSPICIOUS"; + message = "Rejected (dangerous/malicious code detected)"; } ANTIVIRUS_FAILED { action = "soft-reject"; expression = "CLAM_VIRUS_FAIL | OLETOOLS_FAIL"; - message = "Please retry later (anti-virus/oletools)"; + message = "Please retry later (anti-virus/oletools not ready)"; } } .include(try=true,priority=1,duplicate=merge) "/overrides/force_actions.conf" From 2c422bbfbede5a8967d55102e8e1525ffd4a6e2b Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 10 Nov 2023 12:25:34 +0100 Subject: [PATCH 124/136] doh --- tests/compose/filters/01_email_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/compose/filters/01_email_test.sh b/tests/compose/filters/01_email_test.sh index 30b58c51..648aedf2 100755 --- a/tests/compose/filters/01_email_test.sh +++ b/tests/compose/filters/01_email_test.sh @@ -2,7 +2,7 @@ python3 tests/email_test.py message-virus "tests/compose/filters/eicar.com.txt" if [ $? -eq 99 ]; then python3 tests/email_test.py message-PUA "tests/compose/filters/PotentiallyUnwanted.exe_" if [ $? -eq 99 ]; then - return 0 + exit 0 else exit 1 fi From 6765c45ab84a029eb670797f54e7554e7ee5ec43 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Fri, 10 Nov 2023 12:27:02 +0100 Subject: [PATCH 125/136] simplify --- tests/compose/filters/01_email_test.sh | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/compose/filters/01_email_test.sh b/tests/compose/filters/01_email_test.sh index 648aedf2..34cad7ea 100755 --- a/tests/compose/filters/01_email_test.sh +++ b/tests/compose/filters/01_email_test.sh @@ -1,11 +1,10 @@ python3 tests/email_test.py message-virus "tests/compose/filters/eicar.com.txt" -if [ $? -eq 99 ]; then - python3 tests/email_test.py message-PUA "tests/compose/filters/PotentiallyUnwanted.exe_" - if [ $? -eq 99 ]; then - exit 0 - else - exit 1 - fi -else - exit 1 +if [ $? -ne 99 ]; then + exit 1 fi +python3 tests/email_test.py message-PUA "tests/compose/filters/PotentiallyUnwanted.exe_" +if [ $? -ne 99 ]; then + exit 1 +fi + +exit 0 From a61f31e7c5da34848858c20be55a860fe1fac923 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Mon, 13 Nov 2023 08:13:20 +0100 Subject: [PATCH 126/136] Now it should fail earlier --- tests/compose/filters/01_email_test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/compose/filters/01_email_test.sh b/tests/compose/filters/01_email_test.sh index 34cad7ea..1e437732 100755 --- a/tests/compose/filters/01_email_test.sh +++ b/tests/compose/filters/01_email_test.sh @@ -1,9 +1,9 @@ python3 tests/email_test.py message-virus "tests/compose/filters/eicar.com.txt" -if [ $? -ne 99 ]; then +if [ $? -ne 25 ]; then exit 1 fi python3 tests/email_test.py message-PUA "tests/compose/filters/PotentiallyUnwanted.exe_" -if [ $? -ne 99 ]; then +if [ $? -ne 25 ]; then exit 1 fi From ffe823d6bc8d8269a8f4a6e13a576e81c42083e6 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 14 Nov 2023 14:47:16 +0100 Subject: [PATCH 127/136] Upgrade to WTForms==3.1.1 --- core/base/requirements-prod.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/base/requirements-prod.txt b/core/base/requirements-prod.txt index 93c36640..2d5b0002 100644 --- a/core/base/requirements-prod.txt +++ b/core/base/requirements-prod.txt @@ -87,7 +87,7 @@ vobject==0.9.6.1 watchdog==3.0.0 Werkzeug===2.3.7 wrapt==1.15.0 -WTForms==3.1.0 +WTForms==3.1.1 WTForms-Components==0.10.5 xmltodict==0.13.0 yarl==1.9.2 From 8ae6b4dd898e4796b120e2199c08154bb57f8041 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 15 Nov 2023 09:45:40 +0100 Subject: [PATCH 128/136] Doh --- core/rspamd/conf/force_actions.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/rspamd/conf/force_actions.conf b/core/rspamd/conf/force_actions.conf index 85923e09..0bb96c37 100644 --- a/core/rspamd/conf/force_actions.conf +++ b/core/rspamd/conf/force_actions.conf @@ -20,7 +20,7 @@ rules { message = "Rejected (dangerous/malicious code detected)"; } ANTIVIRUS_FAILED { - action = "soft-reject"; + action = "soft reject"; expression = "CLAM_VIRUS_FAIL | OLETOOLS_FAIL"; message = "Please retry later (anti-virus/oletools not ready)"; } From 5312495d0eb2c3e45f092b951f34bbb874d30c72 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 15 Nov 2023 10:15:34 +0100 Subject: [PATCH 129/136] Retry up to 5 times if not ready --- tests/email_test.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/tests/email_test.py b/tests/email_test.py index e7cd66ed..5284239b 100755 --- a/tests/email_test.py +++ b/tests/email_test.py @@ -21,19 +21,27 @@ if len(sys.argv) == 3: part.add_header('Content-Disposition', "attachment; filename=%s" % ntpath.basename(sys.argv[2])) msg.attach(part) -try: - smtp_server = smtplib.SMTP('localhost') - smtp_server.set_debuglevel(1) - smtp_server.connect('localhost', 587) - smtp_server.ehlo() - smtp_server.starttls() - smtp_server.ehlo() - smtp_server.login("admin@mailu.io", "password") +for i in range(5): + try: + smtp_server = smtplib.SMTP('localhost') + smtp_server.set_debuglevel(1) + smtp_server.connect('localhost', 587) + smtp_server.ehlo() + smtp_server.starttls() + smtp_server.ehlo() + smtp_server.login("admin@mailu.io", "password") - smtp_server.sendmail("admin@mailu.io", "user@mailu.io", msg.as_string()) - smtp_server.quit() -except: - sys.exit(25) + smtp_server.sendmail("admin@mailu.io", "user@mailu.io", msg.as_string()) + smtp_server.quit() + except smtplib.SMTPDataError as e: + if e.smtp_code == 451: + print(f"Not ready attempt {i}") + time.sleep(5) + continue + sys.exit(25) + except: + sys.exit(25) + break time.sleep(30) From 0b776fffbd2e02c17280664d371c427d35d95514 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 15 Nov 2023 10:25:55 +0100 Subject: [PATCH 130/136] Ensure the logic is right --- tests/compose/filters/02_email_antispam.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/compose/filters/02_email_antispam.sh b/tests/compose/filters/02_email_antispam.sh index ac2653a3..3972cb1c 100755 --- a/tests/compose/filters/02_email_antispam.sh +++ b/tests/compose/filters/02_email_antispam.sh @@ -1,7 +1,7 @@ # GTUBE should be blocked, see https://rspamd.com/doc/gtube_patterns.html python3 tests/email_test.py "XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X" -if [ $? -eq 25 ]; then - exit 0 -else +if [ $? -ne 25 ]; then exit 1 fi + +exit 0 From 3d13e72133913e71c666056a795fb3d72d61df3a Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Wed, 15 Nov 2023 10:33:02 +0100 Subject: [PATCH 131/136] Only return 25 when it's a permanent error --- tests/email_test.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/email_test.py b/tests/email_test.py index 5284239b..68398823 100755 --- a/tests/email_test.py +++ b/tests/email_test.py @@ -33,14 +33,18 @@ for i in range(5): smtp_server.sendmail("admin@mailu.io", "user@mailu.io", msg.as_string()) smtp_server.quit() + except smtplib.SMTPRecipientsRefused: + sys.exit(25) except smtplib.SMTPDataError as e: if e.smtp_code == 451: print(f"Not ready attempt {i}") time.sleep(5) continue - sys.exit(25) + if e.smtp_code >= 500 and e.smtp_code <600: + sys.exit(25) + sys.exit(2525) except: - sys.exit(25) + sys.exit(2525) break time.sleep(30) From 6466759f3033d22edb60530fccf6982aa6016cb3 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman <52963853+Diman0@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:28:57 +0100 Subject: [PATCH 132/136] Update setup/templates/steps/compose/03_expose.html Better sentence structure Co-authored-by: Florent Daigniere --- setup/templates/steps/compose/03_expose.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/templates/steps/compose/03_expose.html b/setup/templates/steps/compose/03_expose.html index 6fcada29..e14e4bbc 100644 --- a/setup/templates/steps/compose/03_expose.html +++ b/setup/templates/steps/compose/03_expose.html @@ -18,7 +18,7 @@ avoid generic all-interfaces addresses like 0.0.0.0 or :: + Usually the format is '*.*.*.0/24'
From 0e04871cbe591ea50cbe130b965c0d036ab18418 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Sat, 18 Nov 2023 09:40:52 +0000 Subject: [PATCH 133/136] Use better python method for validating IP address and subnet --- setup/server.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup/server.py b/setup/server.py index db473770..89a0815e 100644 --- a/setup/server.py +++ b/setup/server.py @@ -96,22 +96,22 @@ def build_app(path): data['uid'] = str(uuid.uuid4()) valid = True try: - ipaddress.ip_address(data['bind4']) + ipaddress.IPv4Address(data['bind4']) except: flask.flash('Configured IPv4 address is invalid', 'error') valid = False try: - ipaddress.ip_address(data['bind6']) + ipaddress.IPv6Address(data['bind6']) except: flask.flash('Configured IPv6 address is invalid', 'error') valid = False try: - ipaddress.ip_network(data['subnet']) + ipaddress.IPv4Network(data['subnet']) except: flask.flash('Configured subnet(IPv4) is invalid', 'error') valid = False try: - ipaddress.ip_network(data['subnet6']) + ipaddress.IPv6Network(data['subnet6']) except: flask.flash('Configured subnet(IPv6) is invalid', 'error') valid = False From 823b01041a8d2dacdd1eb8d907dbd555b04230ca Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Sat, 18 Nov 2023 11:10:14 +0000 Subject: [PATCH 134/136] Remove WEB_ variables from setup. Setup is meant for the standard use case where you don't change WEB_*. WEB_* can still be changed via mailu.env --- setup/flavors/compose/mailu.env | 24 +++++++------- setup/server.py | 22 ++----------- setup/static/render.js | 54 ------------------------------- setup/templates/steps/config.html | 11 +++---- 4 files changed, 20 insertions(+), 91 deletions(-) diff --git a/setup/flavors/compose/mailu.env b/setup/flavors/compose/mailu.env index cffafe15..7c36a7ec 100644 --- a/setup/flavors/compose/mailu.env +++ b/setup/flavors/compose/mailu.env @@ -40,7 +40,11 @@ AUTH_RATELIMIT_USER={{ auth_ratelimit_user }}/day {% endif %} # Opt-out of statistics, replace with "True" to opt out -DISABLE_STATISTICS={{ disable_statistics or 'False' }} +{% if statistics_enabled %} +DISABLE_STATISTICS=False +{% else %} +DISABLE_STATISTICS=True +{% endif %} ################################### # Optional features @@ -120,24 +124,22 @@ FULL_TEXT_SEARCH=en ################################### # Path to redirect / to -{% if webmail_type != 'none' and webmail_path == '' %} -WEBROOT_REDIRECT=/ +{% if webmail_type != 'none' %} +WEBROOT_REDIRECT=/webmail +{% elif admin_enabled %} +WEBROOT_REDIRECT=/admin {% else %} -WEBROOT_REDIRECT={{ webmail_path }} +WEBROOT_REDIRECT= {% endif %} # Path to the admin interface if enabled -WEB_ADMIN={{ admin_path }} +WEB_ADMIN=/admin # Path to the webmail if enabled -{% if webmail_type != 'none' and webmail_path == '' %} -WEB_WEBMAIL=/ -{% else %} -WEB_WEBMAIL={{ webmail_path }} -{% endif %} +WEB_WEBMAIL=/webmail # Path to the API interface if enabled -WEB_API={{ api_path }} +WEB_API=/api # Website name SITENAME={{ site_name }} diff --git a/setup/server.py b/setup/server.py index 89a0815e..806be40e 100644 --- a/setup/server.py +++ b/setup/server.py @@ -120,28 +120,10 @@ def build_app(path): except ValueError as err: flask.flash('Invalid configuration: ' + str(err)) valid = False - if data['webmail_type'] != 'none': - if data['webmail_path'] == '': - flask.flash('Webmail path cannot be empty when webmail is enabled', 'error') - valid = False - if data['webmail_path'][0] != '/': - flask.flash('Webmail path must start with a leading slash "/"', 'error') - valid = False - if 'admin_enabled' in data: - if data['admin_enabled'] == 'true': - if data['admin_path'] == '': - flask.flash('Admin path cannot be empty when admin is enabled', 'error') - valid = False - if data['admin_path'][0] != '/': - flask.flash('Admin path must start with a leading slash "/"', 'error') - valid = False if 'api_enabled' in data: if (data['api_enabled'] == 'true'): - if data['api_path'] == '' or data['api_token'] == '': - flask.flash('API path and API token cannot be empty when API is enabled', 'error') - valid = False - if data['api_path'][0] != '/': - flask.flash('API path must start with a leading slash "/"', 'error') + if data['api_token'] == '': + flask.flash('API token cannot be empty when API is enabled', 'error') valid = False if valid: db.set(data['uid'], json.dumps(data)) diff --git a/setup/static/render.js b/setup/static/render.js index 5a7e0c56..cde25107 100644 --- a/setup/static/render.js +++ b/setup/static/render.js @@ -6,61 +6,13 @@ $(document).ready(function() { $("#container").show(); }); -$(document).ready(function() { - if ($("#webmail").val() == 'none') { - $("#webmail_path").hide(); - $("#webmail_path").val(""); - $("#webmail_path").prop('required',false); - } else { - $("#webmail_path").show(); - $("#webmail_path").val("/webmail"); - $("#webmail_path").prop('required',true); - } - $("#webmail").click(function() { - if (this.value == 'none') { - $("#webmail_path").hide(); - $("#webmail_path").val(""); - $("#webmail_path").prop('required',false); - } else { - $("#webmail_path").show(); - $("#webmail_path").val("/webmail"); - $("#webmail_path").prop('required',true); - } - }); -}); - -$(document).ready(function() { - if ($('#admin').prop('checked')) { - $("#admin_path").show(); - $("#admin_path").val("/admin"); - $("#admin_path").prop('required',true); - } - $("#admin").change(function() { - if ($(this).is(":checked")) { - $("#admin_path").show(); - $("#admin_path").val("/admin"); - $("#admin_path").prop('required',true); - } else { - $("#admin_path").hide(); - $("#admin_path").val(""); - $("#admin_path").prop('required',false); - } - }); -}); - $(document).ready(function() { if ($('#api_enabled').prop('checked')) { - $("#api_path").show(); - $("#api_path").prop('required',true); - $("#api_path").val("/api") $("#api_token").show(); $("#api_token").prop('required',true); $("#api_token").val(token); $("#api_token_label").show(); } else { - $("#api_path").hide(); - $("#api_path").prop('required',false); - $("#api_path").val("") $("#api_token").hide(); $("#api_token").prop('required',false); $("#api_token").val(""); @@ -68,17 +20,11 @@ $(document).ready(function() { } $("#api_enabled").change(function() { if ($(this).is(":checked")) { - $("#api_path").show(); - $("#api_path").prop('required',true); - $("#api_path").val("/api"); $("#api_token").show(); $("#api_token").prop('required',true); $("#api_token").val(token) $("#api_token_label").show(); } else { - $("#api_path").hide(); - $("#api_path").prop('required',false); - $("#api_path").val("") $("#api_token").hide(); $("#api_token").prop('required',false); $("#api_token").val(""); diff --git a/setup/templates/steps/config.html b/setup/templates/steps/config.html index c1a5ff43..e01d9c97 100644 --- a/setup/templates/steps/config.html +++ b/setup/templates/steps/config.html @@ -60,8 +60,8 @@ Or in plain English: if receivers start to classify your mail as spam, this post
@@ -82,8 +82,7 @@ manage your email domains, users, etc.

- - +

The API interface is a RESTful API for changing the Mailu configuration. @@ -93,8 +92,8 @@ manage your email domains, users, etc.

- - + +
From e43fb69864929a981763c607186226182e18a478 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Sat, 18 Nov 2023 11:16:22 +0000 Subject: [PATCH 135/136] Update changelog fragment of 2890 --- towncrier/newsfragments/2890.bugfix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/towncrier/newsfragments/2890.bugfix b/towncrier/newsfragments/2890.bugfix index 28acf495..bd547567 100644 --- a/towncrier/newsfragments/2890.bugfix +++ b/towncrier/newsfragments/2890.bugfix @@ -1,3 +1,5 @@ Setup: Regular expression for checking the Mailu storage path was invalid. -Added checks to make sure JavaScript is enabled and that all JS files could be loaded. The setup site malfunctions if this is not the case. \ No newline at end of file +Added checks to make sure JavaScript is enabled and that all JS files could be loaded. The setup site malfunctions if this is not the case. +Added server side validation of entered values in setup. +Simplified setup by removing the settings for configuring the WEB_* settings. Advanced users can still modify mailu.env. \ No newline at end of file From e7cf213da703055d842110a6469c0950e1ee0f3a Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Sat, 18 Nov 2023 11:17:33 +0000 Subject: [PATCH 136/136] Forgot to include this in the previous commit for removing WEB_* settings from setup. --- setup/templates/steps/compose/02_services.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/setup/templates/steps/compose/02_services.html b/setup/templates/steps/compose/02_services.html index 5d9df7ef..2d4bdd50 100644 --- a/setup/templates/steps/compose/02_services.html +++ b/setup/templates/steps/compose/02_services.html @@ -9,7 +9,7 @@ bound to the internal IMAP and SMTP server for users to access their mailbox thr the Web. By exposing a complex application such as a Webmail, you should be aware of the security implications caused by such an increase of attack surface.

- +

-
- -