361 Commits

Author SHA1 Message Date
Spitap
8045f41718 Added rspamd redis db backup 2025-11-01 18:13:52 +01:00
Spitap
24ca07f762 Fixed copytree complains 2025-11-01 17:47:52 +01:00
Spitap
39e78649aa Default gid & uid for folder creation 2025-11-01 17:41:02 +01:00
Spitap
a81cd4196c Alway create the backup directory 2025-11-01 17:33:54 +01:00
Spitap
d6c70fba1d Use only the basename 2025-11-01 17:27:12 +01:00
Spitap
9c0052c274 Fixed file path for rspamd 2025-11-01 17:23:53 +01:00
Spitap
3e5b9ab310 Improved backups 2025-11-01 17:18:22 +01:00
Adrien P
30b9393877 Do not initiate backup if the section is not enabled 2025-10-20 22:34:05 +02:00
Adrien P
5887274ba8 Enable rspamd backup 2025-10-20 22:28:18 +02:00
github-actions[bot]
0e64b92199 [GitHub Action] Updated version file 2025-10-13 07:27:25 +00:00
Antoine Nguyen
1d701353d9 Merge pull request #604 from CooperDActor-bytes/patch-1
Fix Grammar
2025-10-13 09:26:14 +02:00
Cooper D'Andilly
3acec87ab5 Fix Grammar 2025-10-12 17:05:05 +11:00
github-actions[bot]
dd9c7457c8 [GitHub Action] Updated version file 2025-09-26 15:02:26 +00:00
Antoine Nguyen
73feb967fe cover mysql use case for dict definition in dovecot 2.4
see #602
2025-09-26 17:00:29 +02:00
github-actions[bot]
ad3f9f8cef [GitHub Action] Updated version file 2025-09-25 11:59:29 +00:00
Antoine Nguyen
765b56d48a Do not install radicale-storage-by-index radicale plugin anymore 2025-09-25 13:58:05 +02:00
github-actions[bot]
a9a8f8888c [GitHub Action] Updated version file 2025-09-24 09:24:28 +00:00
Antoine Nguyen
355e8d9b98 Complete MariaDB fix on debian 12
fix #598
2025-09-24 11:22:51 +02:00
github-actions[bot]
212fa9b5c1 [GitHub Action] Updated version file 2025-09-24 09:06:37 +00:00
Antoine Nguyen
65961209c8 Fixed issue with MariaDB and Debian 13
see #598
2025-09-24 11:05:09 +02:00
github-actions[bot]
5a6f5e7d71 [GitHub Action] Updated version file 2025-09-23 16:38:15 +00:00
Antoine Nguyen
eed7603ba1 Complete fix for #601 2025-09-23 18:36:41 +02:00
github-actions[bot]
731df935e5 [GitHub Action] Updated version file 2025-09-23 14:28:39 +00:00
Antoine Nguyen
6f1e717fc4 Fixed issue with autoconfig setup
see #601
2025-09-23 16:27:05 +02:00
github-actions[bot]
c7f24218aa [GitHub Action] Updated version file 2025-09-23 09:28:12 +00:00
Antoine Nguyen
3d80bc3131 Merge pull request #597 from modoboa/automx-replacement
Replaced automx by Modoboa autoconfig service
2025-09-23 11:27:06 +02:00
github-actions[bot]
67971ee981 [GitHub Action] Updated version file 2025-09-23 08:14:31 +00:00
Antoine Nguyen
34bf98452d Merge pull request #600 from FranMercedesG/master
Fix amavis installation on Debian 13 (LZ4)
2025-09-23 10:14:02 +02:00
github-actions[bot]
6e9fc2e7c2 [GitHub Action] Updated version file 2025-09-23 08:13:44 +00:00
Antoine Nguyen
90d4aac001 Merge pull request #599 from bergerc/master
Add missing dependancy on unzip for postwhite.
2025-09-23 10:12:37 +02:00
FranMercedesG
22cbc2e278 fix amavis installation on debian 13 2025-09-23 01:02:15 -04:00
Chris
2dbec9d2a5 Add missing dependancy on unzip for postwhite. 2025-09-22 22:13:09 +02:00
github-actions[bot]
9d91a25293 [GitHub Action] Updated version file 2025-09-20 09:48:04 +00:00
Antoine Nguyen
246419407a Fixed wrong path 2025-09-20 11:46:45 +02:00
Antoine Nguyen
fc81c04220 Replaced automx by Modoboa autoconfig service 2025-09-16 18:12:11 +02:00
github-actions[bot]
ff485f0d25 [GitHub Action] Updated version file 2025-09-16 13:43:42 +00:00
Antoine Nguyen
56be1be372 Merge pull request #596 from modoboa/dovecot-24-support
Dovecot 2.4 support
2025-09-16 15:42:31 +02:00
Antoine Nguyen
b95bf58488 Configuration fixes 2025-09-16 15:40:27 +02:00
Antoine Nguyen
9e12fa3dda Removed temp. change 2025-09-16 09:44:54 +02:00
Antoine Nguyen
4b0c9e4376 WIP: dovecot 2.4 support 2025-09-16 09:43:18 +02:00
github-actions[bot]
e56868072f [GitHub Action] Updated version file 2025-09-08 10:31:45 +00:00
Antoine Nguyen
506b59cda7 Make sure to install modoboa apps according to selected content filter 2025-09-08 12:30:09 +02:00
github-actions[bot]
327238d917 [GitHub Action] Updated version file 2025-08-19 15:41:04 +00:00
Antoine Nguyen
af1fb15953 Merge pull request #493 from modoboa/rspamd
Added Rspamd installation
2025-08-19 17:40:00 +02:00
Antoine Nguyen
2c6c3a7573 Do not alter global variable 2025-08-19 17:38:25 +02:00
Antoine Nguyen
21a6f85786 Also install opendkim if antispam is amavis 2025-08-19 17:34:55 +02:00
Antoine Nguyen
4d49f182ec Make sure amavis can still be installed 2025-08-19 17:26:33 +02:00
Antoine Nguyen
a9ae8c50ad Removed wrong constructor argument 2025-08-19 15:34:54 +02:00
Antoine Nguyen
95e2010957 Few fixes 2025-08-19 15:34:54 +02:00
Antoine Nguyen
97b98c9d09 Fixed unit tests 2025-08-19 15:34:54 +02:00
Spitfireap
757c1dd48b Hide sender client IP v2 2025-08-19 15:34:54 +02:00
Spitfireap
0056ef20aa Hide sender client IP 2025-08-19 15:34:54 +02:00
Spitfireap
b9539fa33c Updated rspamd config 2025-08-19 15:34:54 +02:00
Antoine Nguyen
7ae6196793 Added missing method parameter 2025-08-19 15:34:54 +02:00
Spitfireap
fd50d62f97 Updated ARC 2025-08-19 15:34:54 +02:00
Spitap
cd280f054b Added Arc signing 2025-08-19 15:34:54 +02:00
Spitap
f980d4e86f Added rspamd dashboard info 2025-08-19 15:34:54 +02:00
Spitap
9b7489ea58 Fixed dovecot #2 2025-08-19 15:34:54 +02:00
Spitap
5c7f230647 Fixed dovecot 2025-08-19 15:34:52 +02:00
Spitap
06d65f7921 imported arguments 2025-08-19 15:33:58 +02:00
Spitap
84e82199ef fix import 2025-08-19 15:33:58 +02:00
Spitap
f0a84c81b9 imported checks 2025-08-19 15:33:58 +02:00
Spitfireap
9a582fb1d0 Update after rebase 2025-08-19 15:33:56 +02:00
Antoine Nguyen
b7106bb15a Fixed file copy issue 2025-08-19 15:33:25 +02:00
Spitfireap
a8b2f9f015 create sieve dir if needed 2025-08-19 15:33:25 +02:00
Spitfireap
86481417cf Made junk sieve optional 2025-08-19 15:33:25 +02:00
Spitfireap
e73d318e14 Made 90-sieve a template 2025-08-19 15:33:24 +02:00
Spitfireap
38eae741bf added sieve rule to move spam to junk folder 2025-08-19 15:32:55 +02:00
Spitap
5156ad0468 small fix part 4 2025-08-19 15:32:31 +02:00
Spitap
183bfd2742 small fix part 3 2025-08-19 15:32:31 +02:00
Spitap
e7e5dce778 Bug fix 2025-08-19 15:32:31 +02:00
Spitap
6771ea0028 small fix part 2 2025-08-19 15:32:31 +02:00
Spitap
a92c92c06c small fix 2025-08-19 15:32:31 +02:00
Spitap
e4d68498dd Fixed capped default choice, removed old py2 code 2025-08-19 15:32:30 +02:00
Spitap
bd91c85888 Fixed new source bug, removed bionic, added dynamic defaults 2025-08-19 15:31:53 +02:00
Spitap
b667636dcb Added possibility of if directive in each entry 2025-08-19 15:31:53 +02:00
Spitap
c0ca901353 Fixed config 2025-08-19 15:31:53 +02:00
Spitap
eb1a8ece55 Updated config and interactive mode 2025-08-19 15:31:53 +02:00
Antoine Nguyen
9f5542f07e Better custom repo installation 2025-08-19 15:31:53 +02:00
Antoine Nguyen
b4b5fa288f Fixed wrong call to mkdir_safe 2025-08-19 15:31:53 +02:00
Antoine Nguyen
9ab1b5f18e Convert codename to str 2025-08-19 15:31:53 +02:00
Antoine Nguyen
daf5338ee1 Make rspamd installation work 2025-08-19 15:31:53 +02:00
Antoine Nguyen
576c696472 Fixed tests 2025-08-19 15:31:53 +02:00
Antoine Nguyen
dea95ee1ba Fixed wrong access to config option 2025-08-19 15:31:53 +02:00
Antoine Nguyen
fb42636df0 Escape % character in config file 2025-08-19 15:31:53 +02:00
Antoine Nguyen
ec82b346a3 Fixed wrong setting names! 2025-08-19 15:31:53 +02:00
Antoine Nguyen
d44faf96b1 Consistency for variable names 2025-08-19 15:31:53 +02:00
Antoine Nguyen
2564f856bd Fixed wrong setting names 2025-08-19 15:31:53 +02:00
Antoine Nguyen
92864aa288 Fixed issues in rspamd script 2025-08-19 15:31:52 +02:00
Antoine Nguyen
0b85e2c7ef Fixed wrong settings initialization 2025-08-19 15:31:13 +02:00
Antoine Nguyen
35e9ea4bde Few fixes 2025-08-19 15:31:13 +02:00
Spitap
077e84349a import fix 2025-08-19 15:31:13 +02:00
Spitap
70e9cffd87 App incompatibility detection, updated for 2.2.0 2025-08-19 15:31:12 +02:00
Spitap
df23f4e181 fix 2025-08-19 15:30:29 +02:00
Spitap
46bbb1039b updated rspamd config 2025-08-19 15:30:29 +02:00
Spitap
69a8f08246 fixed test 2025-08-19 15:30:29 +02:00
Spitap
f7c03e8632 Removed installer.cfg 2025-08-19 15:30:29 +02:00
Spitap
1423fe0e6e Better configuration 2025-08-19 15:30:29 +02:00
Spitap
45870e20ef Fixed dict, few fixes 2025-08-19 15:30:05 +02:00
Spitap
4082d5790d Added Rspamd installation 2025-08-19 15:27:22 +02:00
github-actions[bot]
fbedc6a051 [GitHub Action] Updated version file 2025-07-03 08:44:23 +00:00
Antoine Nguyen
53669b48de Compat with Modoboa 2.4.0 2025-07-03 10:43:07 +02:00
github-actions[bot]
5fe3e49b9a [GitHub Action] Updated version file 2025-06-06 06:57:44 +00:00
Antoine Nguyen
c571462485 Merge pull request #592 from phizev/ubuntu-24.04-mysql-fix
Update MySQL (MariaDB) install to account for Ubuntu 24.04.
2025-06-06 08:56:40 +02:00
phizev
daf3ec2d42 Update MySQL (MariaDB) install to account for Ubuntu 24.04. 2025-06-01 17:41:39 +02:00
github-actions[bot]
1e4ba06764 [GitHub Action] Updated version file 2025-03-26 16:37:04 +00:00
Antoine Nguyen
0bc3a8367c Merge pull request #587 from modoboa/update/install-reqs-process
Use extras instead of requirements file
2025-03-26 17:36:00 +01:00
Adrien P
eee2c76a16 Use extras instead of requirements file 2025-03-07 12:37:06 +01:00
github-actions[bot]
24c9599ca5 [GitHub Action] Updated version file 2025-01-29 08:42:20 +00:00
Antoine Nguyen
78092509c7 Merge pull request #581 from seb4itik/master
Fix #561 and #576
2025-01-29 09:40:58 +01:00
S. Nameche
5fbf373dc2 Fix #561 and #576 2025-01-28 21:04:44 +03:00
github-actions[bot]
5313abf42b [GitHub Action] Updated version file 2025-01-28 11:47:18 +00:00
Antoine Nguyen
8f41ebd15c Merge branch 'master' of github.com:modoboa/modoboa-installer 2025-01-28 12:46:40 +01:00
Antoine Nguyen
707d44d819 Updated codecov badge 2025-01-28 12:46:08 +01:00
github-actions[bot]
6b359898a9 [GitHub Action] Updated version file 2025-01-28 11:43:29 +00:00
Antoine Nguyen
ce728b0669 Merge pull request #580 from modoboa/fix/radicale_config
Updated Radicale config
2025-01-28 12:42:17 +01:00
Antoine Nguyen
2c862e3179 Update workflow config 2025-01-28 12:40:49 +01:00
Antoine Nguyen
5efc3a4aa6 Updated test matrix 2025-01-28 12:32:03 +01:00
Antoine Nguyen
114a15b407 Updated Radicale config
dovecot auth is now part of Radicale
2025-01-28 11:49:37 +01:00
github-actions[bot]
50f632ee9a [GitHub Action] Updated version file 2024-10-19 07:48:12 +00:00
Antoine Nguyen
20b6ede211 Merge pull request #572 from FranMercedesG/fix-tls-and-ciphers-version
feature: improve security on postfix
2024-10-19 09:47:42 +02:00
github-actions[bot]
97c81a8eaf [GitHub Action] Updated version file 2024-10-19 07:46:49 +00:00
Antoine Nguyen
a35780fe4f Merge branch 'master' of github.com:modoboa/modoboa-installer 2024-10-19 09:45:49 +02:00
Antoine Nguyen
feba5ca406 Force index update after enabling backports (debian) 2024-10-19 09:45:17 +02:00
github-actions[bot]
a46b3e18ff [GitHub Action] Updated version file 2024-10-16 11:38:31 +00:00
github-actions[bot]
32a16b6ea3 [GitHub Action] Updated version file 2024-10-16 10:04:23 +00:00
Antoine Nguyen
33cad9b29b Install dovecot from backports if Debian 12 2024-10-16 12:03:19 +02:00
FranMercedesG
1bb108c62c feature: improve security on postfix 2024-09-18 15:00:55 -04:00
github-actions[bot]
69b966a030 [GitHub Action] Updated version file 2024-08-21 15:30:32 +00:00
Antoine Nguyen
336677cf8c Merge pull request #570 from modoboa/fix/updated-comp-matric
Update compatibility_matrix.py
2024-08-21 17:29:18 +02:00
Spitap
29153f8d48 Update compatibility_matrix.py 2024-08-21 16:30:34 +02:00
github-actions[bot]
79d09f2eb9 [GitHub Action] Updated version file 2024-08-02 09:31:10 +00:00
Antoine Nguyen
cb06459ea3 Merge pull request #563 from modoboa/feature/dovecot_oauth2_setup
Added setup instructions for Dovecot oauth2 support
2024-08-02 11:29:57 +02:00
Antoine Nguyen
81f1332e84 Merge branch 'feature/dovecot_oauth2_setup' of github.com:modoboa/modoboa-installer into feature/dovecot_oauth2_setup 2024-08-02 11:28:37 +02:00
Antoine Nguyen
7dbe1ea093 Few fixes 2024-08-02 11:27:57 +02:00
Spitfireap
997478704d Added uwsgi buffer-size 2024-07-22 16:02:47 +02:00
Antoine Nguyen
1e7b8209cf Fixed createapplication call 2024-07-12 18:43:50 +02:00
Antoine Nguyen
2572dd64d1 Added setup instructions for Dovecot oauth2 support 2024-07-07 10:48:05 +02:00
github-actions[bot]
d05618e53d [GitHub Action] Updated version file 2024-06-14 14:32:45 +00:00
Antoine Nguyen
e9fc8efeca Merge pull request #559 from modoboa/fix/sorbs
Removed SORBS dnsbl
2024-06-14 16:31:36 +02:00
Antoine Nguyen
290c50326e Removed SORBS dnsbl 2024-06-13 09:29:29 +02:00
github-actions[bot]
10b2e71aa4 [GitHub Action] Updated version file 2024-06-10 08:13:20 +00:00
Antoine Nguyen
c1abbe9792 Merge pull request #557 from modoboa/compat/ubuntu2404
Compat. with Ubuntu 24.04
2024-06-10 10:12:07 +02:00
Antoine Nguyen
2f93a1eddb Compat. with Ubuntu 24.04
see #556
2024-06-05 12:03:59 +02:00
github-actions[bot]
ec12104f44 [GitHub Action] Updated version file 2024-05-16 12:23:46 +00:00
Antoine Nguyen
917bd7382b Removed option.
see #554
2024-05-16 14:21:54 +02:00
github-actions[bot]
271add9b6e [GitHub Action] Updated version file 2024-05-16 08:27:33 +00:00
Antoine Nguyen
c39cd568e4 Fix #553 2024-05-16 10:26:04 +02:00
github-actions[bot]
e45a07f8cf [GitHub Action] Updated version file 2024-04-26 14:44:53 +00:00
Antoine Nguyen
b099337d24 Merge pull request #552 from modoboa/fix/le_renewal_hook
Added renewal deploy hook to reload services.
2024-04-26 16:43:42 +02:00
Antoine Nguyen
015a535d0f Safer script syntax 2024-04-26 15:04:15 +02:00
Antoine Nguyen
c0bdc22c4c Added renewal deploy hook to reload services.
see #522
2024-04-26 14:55:48 +02:00
github-actions[bot]
a187e08fe4 [GitHub Action] Updated version file 2024-04-26 11:54:06 +00:00
Antoine Nguyen
47468d3f72 Merge branch 'master' of github.com:modoboa/modoboa-installer 2024-04-26 13:52:46 +02:00
Antoine Nguyen
90daf2fd3f Removed useless workflow trigger 2024-04-26 13:52:25 +02:00
github-actions[bot]
462422af81 [GitHub Action] Updated version file 2024-04-26 11:50:07 +00:00
Antoine Nguyen
87a5a24947 Merge branch 'master' of github.com:modoboa/modoboa-installer 2024-04-26 13:48:52 +02:00
Antoine Nguyen
8eeb88dd34 Run versioning workflow after tests on master branch 2024-04-26 13:47:56 +02:00
github-actions[bot]
032e043321 [GitHub Action] Updated version file 2024-04-26 11:46:37 +00:00
Antoine Nguyen
46a19d08e5 Fix #515 2024-04-26 13:46:08 +02:00
github-actions[bot]
4f4915983e [GitHub Action] Updated version file 2024-04-26 10:08:16 +00:00
Antoine Nguyen
9dc05691b0 Merge pull request #551 from modoboa/feature/backup-wo-mail
Added skipping mail backup in backup process
2024-04-26 12:08:05 +02:00
Spitfireap
da4cddf198 Actually skip mail backup when needed 2024-04-26 12:06:21 +02:00
Spitfireap
6fe80b5ea3 Added skipping mail backup in backup process 2024-04-26 11:21:36 +02:00
github-actions[bot]
7066c2b86c [GitHub Action] Updated version file 2024-04-24 06:30:55 +00:00
Antoine Nguyen
eb8bb2138f Merge pull request #504 from modoboa/custom-tls-fix
Added custom tls cert support
2024-04-24 08:30:43 +02:00
Antoine Nguyen
18369e238c Few updates 2024-04-24 08:28:56 +02:00
Spitap
68ecf77045 Fixed for upgrade 2024-04-24 08:12:46 +02:00
Spitap
469005b528 Fixed README 2024-04-24 08:12:46 +02:00
Spitap
bc88110be6 Fixed template 2024-04-24 08:12:46 +02:00
Spitap
e900e6258f Revert generate removing 2024-04-24 08:12:46 +02:00
Spitap
4759146d99 Added custom tls cert support 2024-04-24 08:12:44 +02:00
github-actions[bot]
ccae88bb77 [GitHub Action] Updated version file 2024-04-24 06:08:44 +00:00
Antoine Nguyen
367c8a31eb Merge pull request #530 from gsloop/patch-1
Update master.cf.tpl - add missing headers
2024-04-24 08:08:30 +02:00
github-actions[bot]
5559368a3d [GitHub Action] Updated version file 2024-04-24 06:05:05 +00:00
Antoine Nguyen
ea26a6d38a Merge pull request #533 from modoboa/fix-prevent-old-ext-install
Prevent installation of incompatible extensions
2024-04-24 08:04:54 +02:00
Antoine Nguyen
c069f7e6eb updated doc 2024-04-24 08:03:30 +02:00
Antoine Nguyen
d0d19b920b Moved version conversion upper 2024-04-24 07:53:38 +02:00
Antoine Nguyen
2df9fcfd86 Moved code 2024-04-24 07:52:02 +02:00
github-actions[bot]
3a498b7c1c [GitHub Action] Updated version file 2024-04-24 05:26:37 +00:00
Antoine Nguyen
2f692e6557 Merge pull request #546 from modoboa/feature/versioning
Added version check
2024-04-24 07:26:23 +02:00
Antoine Nguyen
8fec73ebba Updated version numbers in workflow 2024-04-24 07:23:34 +02:00
Antoine Nguyen
0d6507e2dc Quote version numbers 2024-04-24 07:21:45 +02:00
Antoine Nguyen
fb961f9339 Updated worflow 2024-04-24 07:18:56 +02:00
Antoine Nguyen
0f7a63697a Merge pull request #548 from aleczdr/patch-1
LMDE compatability
2024-04-18 09:33:55 +02:00
aleczdr
237bad9078 Update package.py 2024-04-16 10:40:32 -04:00
aleczdr
40f94fa816 Update package.py
updated debian distro compatability to include linuxmint, LMDE

***untested***
but should work, or at least not break anything currently working
2024-04-16 10:31:53 -04:00
Spitfireap
c9a2f260da Merge branch 'master' into feature/versioning 2024-04-12 16:42:04 +02:00
Spitfireap
65a2802aba added version.txt 2024-04-12 16:40:33 +02:00
Spitfireap
a64c12bf9b fixed typo 2024-04-12 16:36:52 +02:00
Spitfireap
64ba5eb543 Added checks on install 2024-04-12 16:12:36 +02:00
github-actions[bot]
ef1d7670dd [GitHub Action] Updated version file 2024-04-12 12:20:19 +00:00
Spitfireap
d75f500cd8 Merge branch 'feature/versioning' of https://github.com/modoboa/modoboa-installer into feature/versioning 2024-04-12 14:19:58 +02:00
Spitfireap
ece8c30979 fixed version.txt content 2024-04-12 14:19:03 +02:00
github-actions[bot]
938629eb97 [GitHub Action] Updated version file 2024-04-12 12:11:48 +00:00
Spitfireap
6da31945d6 fixed workflow 2024-04-12 14:11:35 +02:00
Spitfireap
14f0da5c1f Updated versioning 2024-04-12 14:10:20 +02:00
Spitfireap
382a2d5a12 Added github action 2024-04-12 14:01:19 +02:00
Antoine Nguyen
748ac2087f Added sponsoring disclaimer. 2024-04-09 13:21:29 +02:00
Antoine Nguyen
e76d7d5c28 Merge pull request #539 from softwarecreations/master
Fixed frozen apt install during Modoboa upgrade when package already installed with old config file
2024-04-09 13:07:40 +02:00
Antoine Nguyen
9eda3b81be Safer way to detect python package version 2024-04-09 13:01:43 +02:00
softwarecreations
7dcf69bc36 Update package.py Fixed frozen apt install
Fixes apt-get install when package already exists with an old config file. The updated code automatically installs the package-maintainer's version of any config files that come with the package. Resolves #538
2024-02-29 14:40:51 +02:00
Antoine Nguyen
0ccd81c92b Make sure to use int vars 2024-01-22 13:46:27 +01:00
Antoine Nguyen
715a5e3c8f Make redis available before we deploy modoboa 2024-01-12 17:33:29 +01:00
Spitap
6f2ed24c1a Prevent installation of incompatible extensions 2024-01-07 11:46:54 +01:00
Antoine Nguyen
e7995ada3f Quickfix against SMTP smuggling
https://www.postfix.org/smtp-smuggling.html
2023-12-22 16:50:26 +01:00
Antoine Nguyen
7097e15ae9 Deploy supervisor config for new RQ worker 2023-12-22 16:43:33 +01:00
Antoine Nguyen
b7f378fc63 Disable all required lines when dovecot is not installed 2023-12-15 11:59:03 +01:00
Antoine Nguyen
8942836cfc Merge pull request #531 from modoboa/fix/postfix_dhe_group
Replace EDH key generation by DHE group file
2023-12-15 11:57:13 +01:00
Antoine Nguyen
7b990c9ff6 Replace EDH key generation by DHE group file 2023-12-15 11:55:11 +01:00
as_you_wish
24e334c06f Update master.cf.tpl - add missing headers
Add missing headers for mail from sasl auth'd users and mynetworks.
2023-11-22 12:12:11 -08:00
Antoine Nguyen
4a2e9f2ec6 Merge pull request #523 from modoboa/dynamic-requirements
Fetch requirements dynamically
2023-10-20 16:52:12 +02:00
Spitap
6f528c94c6 Moved block to _setup_venv() 2023-10-20 10:13:44 +02:00
Spitap
f77d6f07da Fetch requirements dynamically 2023-10-19 18:10:30 +02:00
Antoine Nguyen
960d1ad23d Merge pull request #516 from xBiei/patch-1
Typo~
2023-08-31 09:17:11 +02:00
_xB
821f72a989 Typo~ 2023-08-31 00:16:24 +03:00
Antoine Nguyen
d1e036b7b0 Merge pull request #514 from modoboa/rq
Updated for 2.2
2023-08-30 18:31:12 +02:00
Spitap
f658e5e85e Fixed escape character on dovecot config tpl 2023-08-30 17:13:59 +02:00
Spitap
9715fcc86e few fixes 2023-08-30 16:57:49 +02:00
Antoine Nguyen
23aabbfffc Updated exec_cmd to allow capturing while in debug mode 2023-08-30 14:17:04 +02:00
Spitap
4782000791 few fixes 2023-08-30 10:13:49 +02:00
Spitap
23a6101b7a fix 2023-08-30 09:58:18 +02:00
Spitap
8a0b3cda9e Added python module to base.py 2023-08-30 09:53:26 +02:00
Spitap
1a528282ce Removed duplicates 2023-08-30 09:47:10 +02:00
Spitap
b1da76cfbd Fixed venvpath 2023-08-30 09:05:42 +02:00
Spitap
941142f5f5 Fixed dkim user 2023-08-30 08:48:58 +02:00
Spitap
ef1bace29e Cleaning code the 2nd 2023-08-29 20:42:44 +02:00
Spitap
35fa19e47d Cleaning code 2023-08-29 20:41:01 +02:00
Spitap
0b0e2a4e6a Updated for 2.2 2023-08-29 20:07:26 +02:00
Antoine Nguyen
e537794af2 Merge pull request #508 from florealcab/master
Add support of Debian 12
2023-07-21 09:56:38 +02:00
Floréal Cabanettes
2cc34e9033 Merge branch 'modoboa:master' into master 2023-07-13 11:21:29 +02:00
Antoine Nguyen
393c433e9a Merge pull request #507 from samuraikid0/master
Fix http2 wrong port
2023-07-11 18:18:19 +02:00
Floréal Cabanettes
5704a0a236 Add amavis 2.13.X as a copy of 2.12.X for debian12, for postgresql too 2023-07-10 23:47:32 +02:00
Floréal Cabanettes
4b6ffa1630 Debian 12 is like debian 11 2023-07-08 07:57:47 +02:00
Floréal Cabanettes
987b43d9e9 Fix for debian12 2023-07-07 23:55:34 +02:00
Floréal Cabanettes
187790149d Add amavis 2.13.X as a copy of 2.12.X for debian12 2023-07-07 23:52:44 +02:00
Zzzz
9ad6c4db68 Fix http2 wrong port 2023-07-07 09:54:05 -11:00
Antoine Nguyen
dd668aca70 Merge pull request #500 from modoboa/http2
Added http2 for nginx
2023-06-12 16:35:12 +02:00
Spitap
6e3a232e83 Added http2 for nginx 2023-05-25 11:16:32 +02:00
Antoine Nguyen
ffb3356b46 Merge pull request #498 from modoboa/bug-fix
Fixed installation issue, Updated automx conf
2023-05-10 09:03:23 +02:00
Spitap
2873a5ae69 Updated automx config 2023-05-09 19:34:22 +02:00
Spitap
4e0b025477 added missing packages
Thanks to @ruslaan7
2023-05-09 18:30:16 +02:00
Antoine Nguyen
21435d885b Merge pull request #497 from modoboa/fix-pdf-storage
Fixed db query for pdf storage
2023-05-02 17:40:27 +02:00
Spitap
c8484406d2 Fixed db query for pdf storage 2023-05-02 08:42:02 +02:00
Antoine Nguyen
60bb5eadea Merge pull request #494 from modoboa/update-for-2.1
Updated for 2.1
2023-04-26 09:05:31 +02:00
Spitap
a6b1d9e5d8 Updated for 2.1 2023-04-26 08:21:33 +02:00
Antoine Nguyen
7752b860fa Merge pull request #492 from modoboa/fix-mysql-db-ubuntu
Added workaround for ubuntu 20 and 22
2023-04-25 17:30:17 +02:00
Antoine Nguyen
0040277380 Improved code 2023-04-25 17:28:04 +02:00
Antoine Nguyen
3767c056ca Create FUNDING.yml 2023-04-20 09:03:42 +02:00
Spitap
ff214ab8f9 Added workaround for ubuntu 20 and 22 2023-03-31 14:53:58 +02:00
Antoine Nguyen
58fc991722 Merge pull request #485 from modoboa/upgrade-config-file
Added ability to update configfile
2023-03-14 08:35:26 +01:00
Spitap
602405833c Better test 2023-03-13 14:59:30 +01:00
Spitap
85652320b6 Simplified return 2023-03-13 12:09:11 +01:00
Spitap
52bccf3393 Refactoring 2023-03-12 10:22:40 +01:00
Spitap
4cd3937fdd Updated tests 2023-03-12 00:50:34 +01:00
Spitap
6261066ccd Formating, force outdated config check 2023-03-12 00:30:04 +01:00
Spitfireap
0b29f74e08 typo, review fix 2023-03-11 12:41:16 +00:00
Spitfireap
29ff6d1933 Merge pull request #489 from softwarecreations/master
Fixed permissions of /etc/dovecot/conf.d/10-ssl-keys.try to resolve issue #2570
2023-03-10 15:19:53 +00:00
softwarecreations
9d24f17632 Fixed permissions of /etc/dovecot/conf.d/10-ssl-keys.try to resolve issue 2570
Resolves modoboa/modoboa#2570

When dovecot first starts up, root reads the conf and is able to read and load the keys in /etc/dovecot/conf.d/10-ssl-keys.try Inside that file, it can read the private key (that only root has permissions to read)

However when we try delete a user, doveconf tries to read the config (to find the user's mailbox) doveconf MUST fail to open 10-ssl-keys.try, which is fine, because 10-ssl.conf says

!include_try /etc/dovecot/conf.d/10-ssl-keys.try

So if doveconf can't open 10-ssl-keys.try it will will keep going. However if doveconf can read 10-ssl-keys.try then doveconf crashes saying something like:

Failed to retrieve mailbox location (b doveconf: Fatal: Error in configuration file /etc/dovecot/conf.d/10-ssl-keys.try line 11: ssl_key: Can't open file /etc/ssl/example.com/privkey.pem: Permission denied

And then the attempt to delete the user's mailbox fails.

According to @gsloop, "the API calls doveadm to return the directory that holds the users mailbox"

I did a new installation, the file /etc/dovecot/conf.d/10-ssl-keys.try was already owned by root:root but it had 644 permissions. So the line that I added corrects that.
2023-03-10 13:03:43 +02:00
Antoine Nguyen
b26905a97b Merge pull request #479 from n-tdi/patch-1
Make DNS understaind easier for users
2023-03-06 16:58:15 +01:00
Antoine Nguyen
a3f7b98104 Merge pull request #487 from mbaechtold/patch-1
Fix automx
2023-03-06 13:19:26 +01:00
Martin Bächtold
d547d37ece Fix automx
Relates to https://github.com/modoboa/modoboa-installer/issues/475
2023-03-05 09:27:40 +01:00
Spitap
dbfede6df1 Fixed typo, updated test 2023-03-03 09:33:32 +01:00
Spitap
335a676a1e Added ability to update configfile 2023-03-02 20:54:31 +01:00
Antoine Nguyen
06a81c7a80 Fix #481 2023-02-17 10:12:56 +01:00
Ntdi
cac6c1e7f7 Make DNS understaind easier for users
This simple change makes it easier for new users to add the DNS records they need for there Modoboa installation.
2023-02-12 17:13:35 -05:00
Antoine Nguyen
63d92b73f3 Merge pull request #474 from modoboa/security/fail2ban
Added fail2ban setup
2023-01-31 09:09:51 +01:00
Antoine Nguyen
76ec16cd45 Added missing files 2023-01-31 09:08:34 +01:00
Antoine Nguyen
5f02e1b8ed Added fail2ban setup 2023-01-30 18:02:09 +01:00
Antoine Nguyen
960f1429fd Removed temp. fix for django-webpack-loader. 2023-01-30 15:51:43 +01:00
Antoine Nguyen
8b376b0f69 Fixed typo
see #472
2023-01-24 13:02:13 +01:00
Antoine Nguyen
4fc540ddd8 Merge pull request #471 from Spitfireap/fix-dovecot-ownership
Fix dovecot ownership
2023-01-24 10:53:36 +01:00
Spitap
81129d2875 Removed globally set mail_uid and mail_gid
Co-Authored-By: Antoine Nguyen <tonio@ngyn.org>
2023-01-24 09:29:51 +01:00
Spitap
a6935bba89 Simplifeid setup_user
Co-Authored-By: Antoine Nguyen <tonio@ngyn.org>
2023-01-24 09:25:18 +01:00
Spitap
7cae12b32e Fix multiple hard-coded vmail 2023-01-23 19:24:28 +01:00
Antoine Nguyen
0fc15fc024 updated regexp
fix #312
2023-01-13 12:12:48 +01:00
Antoine Nguyen
7877de1abc Removed call to deprecated discover command
fix #403
2023-01-13 12:05:03 +01:00
Spitap
6144f7967c make use of mailbox_owner 2023-01-12 11:22:26 +01:00
Antoine Nguyen
a647edf5a5 Merge pull request #460 from Spitfireap/fix-dkim-perm
fixed dkim permissions
2023-01-10 14:07:22 +01:00
Antoine Nguyen
9f08964c59 Merge pull request #470 from Spitfireap/Fix-webmail-folder
Create subfolder on modoboas extensions install
2023-01-10 14:06:20 +01:00
Antoine Nguyen
99d229a693 Merge pull request #464 from Spitfireap/postwhite-conf-fix
Postwhite conf file not being copied
2023-01-10 14:05:23 +01:00
Spitap
cf6f34b257 Be sure to create webmail subfolder 2023-01-10 13:19:09 +01:00
Spitap
a94b5ac4b7 Refactoring 2022-12-27 20:27:28 +01:00
Spitap
4f9f433008 PEP 2022-12-27 19:56:12 +01:00
Spitap
2665e18c0a Fixed config file not copied on new install 2022-12-27 19:45:38 +01:00
Antoine Nguyen
5c22600d98 Merge pull request #462 from Spitfireap/randomize-api-call-time
randomize api call time
2022-11-29 16:54:28 +01:00
Spitap
bcdbb4a2ce fix typo 2022-11-29 14:53:05 +01:00
Spitap
bd1ddcef21 randomize api call time 2022-11-29 13:45:31 +01:00
Spitap
24f231bf1d fixed dkim permsissions 2022-11-27 13:57:35 +01:00
Antoine Nguyen
bc12ca7327 Merge pull request #458 from Spitfireap/fix-include_try
fix typo in dovecot configuration file
2022-11-14 15:49:41 +01:00
Spitap
bd0ecd0949 fix typo in dovecot configuration file 2022-11-10 14:57:43 +01:00
Antoine Nguyen
d364239348 Merge pull request #456 from modoboa/feature/improved_backup_restore
WIP: Improved backup/restore system.
2022-11-09 10:51:30 +01:00
Antoine Nguyen
37633008cb Fixed restore mode 2022-11-09 10:30:44 +01:00
Antoine Nguyen
d6f9a5b913 Few fixes. 2022-11-08 17:20:25 +01:00
Antoine Nguyen
8b1d60ee59 Few fixes 2022-11-08 17:19:23 +01:00
Antoine Nguyen
2b5edae5d5 WIP: Improved backup/restore system. 2022-11-06 10:30:24 +01:00
Antoine Nguyen
61838dbe4d Check if restore is defined before doing anything else.
fix #453
2022-11-05 09:30:50 +01:00
Antoine Nguyen
962cac3ad9 Merge pull request #450 from Spitfireap/fixed-super-call
fixed super call in modoboa's script
2022-11-04 09:41:20 +01:00
Spitap
ef2359a2a8 fixed super call 2022-11-03 23:10:21 +01:00
Antoine Nguyen
1b192c5fd5 Merge pull request #449 from Spitfireap/fixed-import-typo
fixed constants import
2022-11-03 15:34:48 +01:00
Spitap
b0b01465d9 fixed constants import 2022-11-03 15:00:07 +01:00
Antoine Nguyen
754d652fc2 Few fixes 2022-11-03 12:27:04 +01:00
Antoine Nguyen
cb5fa75693 Merge pull request #444 from Spitfireap/tighter-config-file-perm
tighter config file permission
2022-11-03 12:20:25 +01:00
Antoine Nguyen
1afb8e61fc Merge pull request #424 from Spitfireap/restore
Backup & restore system
2022-11-03 12:17:16 +01:00
Spitap
8dd0b7d497 Last camelCase 2022-11-03 10:57:03 +01:00
Spitap
554611b366 review fix 2022-11-03 10:54:06 +01:00
Antoine Nguyen
15c17796f2 Merge pull request #446 from Spitfireap/fix-ssl-min-protocol
fixed ssl_min_protocol setting
2022-10-28 09:43:30 +02:00
Spitap
84d13633a1 fixed ssl_min_protocol setting 2022-10-27 22:37:47 +02:00
Antoine Nguyen
ce8e7e6027 Merge pull request #445 from Spitfireap/dovecot-fixes
Fixes ssl permission error, updated ssl_protocol parameter
2022-10-27 17:56:37 +02:00
Spitap
e01265a4ee Merge branch 'tighter-config-file-perm' of https://github.com/Spitfireap/modoboa-installer into tighter-config-file-perm 2022-10-27 17:44:37 +02:00
Spitap
a5fba03264 tighter config file permission 2022-10-27 17:44:29 +02:00
Spitap
fe7df276fc Check dovecot version greater 2022-10-27 17:25:39 +02:00
Spitap
8f34f0af6f Fixes ssl permission error, updated ssl_protocol parameter 2022-10-27 17:00:58 +02:00
Antoine Nguyen
8e8ae5fb9c Merge pull request #439 from stefaweb/master
Update config_dict_template.py for default max_servers value
2022-10-27 16:49:20 +02:00
Spitap
235ef3befb thighter config file permission 2022-10-27 11:14:06 +02:00
Antoine Nguyen
67f6cee8ea Merge pull request #442 from Spitfireap/patch-1
Set $max_server to 2 to avoid amavis crash
2022-10-25 19:32:37 +02:00
Spitap
5c9d5c9a03 DKIM keys restore, Radicale backup/restore, fixes 2022-10-25 16:58:57 +02:00
Spitap
4c1f8710b5 Added dkim key backup 2022-10-25 16:04:55 +02:00
Spitap
e34eb4b337 fix database path 2022-10-25 13:59:28 +02:00
Spitfireap
53f7f8ef9d Update config_dict_template.py 2022-10-19 08:19:40 +00:00
Spitfireap
35778cd614 Merge branch 'modoboa:master' into restore 2022-10-18 17:17:48 +02:00
Stephane Leclerc
fefbf549a4 Update config_dict_template.py for default max_server value 2022-10-06 13:36:13 +02:00
Spitap
6726f5b1a2 Improved path generation, path mistake proofing 2022-09-26 13:39:28 +02:00
Spitap
a192cbcbd0 Updated doc, default path on conf file 2022-09-19 16:40:25 +02:00
Spitap
5bed9655ea fixed typo 2022-09-19 15:53:19 +02:00
Spitap
6b096a7470 Simplified db dumps restore 2022-09-19 15:50:03 +02:00
Spitap
e30add03fd Update from master 2022-09-19 15:39:05 +02:00
Spitap
d75d83f202 more refactoring 2022-09-19 15:13:44 +02:00
Spitap
f3811b4b39 refactoring 2022-09-19 15:00:26 +02:00
Spitap
b0d56b3989 PEP formating 2022-09-15 11:32:57 +02:00
Spitap
53e3e3ec58 Better UX, use of os to concatenate path 2022-08-05 15:20:11 +02:00
Spitap
e546d2cb23 Better UX 2022-07-27 16:32:59 +02:00
Spitap
70faa1c5cb Fixed backupdir index 2022-07-27 15:58:41 +02:00
Spitap
563979a7dd fixed mail backup/restore 2022-07-27 15:51:22 +02:00
Spitap
ee2ccf0647 Fixed postfix install, added restore to readme 2022-07-27 14:35:48 +02:00
Spitap
2077c94b52 Fix amavis config file not copied to right location 2022-07-26 17:05:00 +02:00
Spitap
4a7222bd24 Fixed nginx call to uwsgi 2022-07-26 16:53:24 +02:00
Spitap
e7b6104195 fixed install within class 2022-07-26 16:39:41 +02:00
Spitap
4a00590354 fixed restore disclamer 2022-07-26 16:20:03 +02:00
Spitap
15768c429e Restore workflow done 2022-07-26 12:07:42 +02:00
Spitap
439ffb94c4 initial commit 2022-07-26 10:37:38 +02:00
Spitap
37bc21dfd3 Backup postewhite.conf instead of custom whitelist
Postwhite.conf contains a custom host list
2022-07-26 10:36:08 +02:00
Spitap
26204143af Merge branch 'master' into backup 2022-07-25 22:10:26 +02:00
Spitap
20970557de Allow to disable mail backup 2022-07-25 22:05:35 +02:00
Spitap
632c26596e Update backup readme 2022-07-25 21:52:15 +02:00
Spitap
9e1c18cd6b Fix argument passed as list instead of string 2022-07-21 19:09:53 +02:00
Spitap
db6457c5f5 better path handling 2022-07-21 19:07:18 +02:00
Spitap
579faccfa5 added an automatic bash option (no path provided) or a path provided bash (for cron job) 2022-07-21 19:00:32 +02:00
Spitap
5318fa279b bash option 2022-07-21 18:00:50 +02:00
Spitap
74de6a9bb1 Reset pgpass before trying to backup secondary dbs 2022-07-21 17:31:56 +02:00
Spitap
54185a7c5a Fix database backup logic issue 2022-07-21 17:26:40 +02:00
Spitap
1f9d69c37c Fix copy issue 2022-07-21 17:21:59 +02:00
Spitap
8d02d2a9fb added safe mkdir in utils, use utils.mkdir_safe() in backup 2022-07-21 17:09:23 +02:00
Spitap
6f604a5fec Fix loop logic 2022-07-21 16:53:56 +02:00
Spitap
568c4a65a0 fix none-type passed to os.path 2022-07-21 16:51:32 +02:00
Spitap
dc84a79528 Note : capitalize affects only first letter 2022-07-21 14:12:35 +02:00
Spitap
304e25fa3c Fix getattr 2022-07-21 14:10:57 +02:00
Spitap
070efd61c4 Fix import 2022-07-21 14:08:39 +02:00
Spitap
9917d8023e Edited README, fix backup run process 2022-07-21 14:02:41 +02:00
Spitap
27b9de6755 database backup 2022-07-21 13:48:44 +02:00
Spitap
56ed214fb5 Starting work on backup system 2022-07-19 19:06:53 +02:00
110 changed files with 4215 additions and 1157 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [modoboa]

View File

@@ -1,7 +1,7 @@
# Impacted versions # Impacted versions
* Distribution: Debian / Ubuntu / Centos * Distribution: Debian / Ubuntu / Centos
* Codename: Jessie / Trusty / Centos 9 Stream / ... * Codename: Jessie / Trusty / Centos 7 / ...
* Arch: 32 Bits / 64 Bits * Arch: 32 Bits / 64 Bits
* Database: PostgreSQL / MySQL * Database: PostgreSQL / MySQL

View File

@@ -11,47 +11,48 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: [3.7, 3.8, 3.9] python-version: [3.9, '3.10', '3.11', '3.12']
fail-fast: false fail-fast: false
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install dependencies - name: Install dependencies
run: | run: |
pip install -r test-requirements.txt pip install -r test-requirements.txt
- name: Run tests - name: Run tests
if: ${{ matrix.python-version != '3.9' }} if: ${{ matrix.python-version != '3.12' }}
run: | run: |
python tests.py python tests.py
- name: Run tests and coverage - name: Run tests and coverage
if: ${{ matrix.python-version == '3.9' }} if: ${{ matrix.python-version == '3.12' }}
run: | run: |
coverage run tests.py coverage run tests.py
- name: Upload coverage result - name: Upload coverage result
if: ${{ matrix.python-version == '3.9' }} if: ${{ matrix.python-version == '3.12' }}
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: coverage-results name: coverage-results
path: .coverage path: .coverage
include-hidden-files: true
coverage: coverage:
needs: test needs: test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v2 uses: actions/setup-python@v5
with: with:
python-version: '3.9' python-version: '3.12'
- name: Install dependencies - name: Install dependencies
run: | run: |
pip install codecov pip install codecov
- name: Download coverage results - name: Download coverage results
uses: actions/download-artifact@v2 uses: actions/download-artifact@v4
with: with:
name: coverage-results name: coverage-results
- name: Report coverage - name: Report coverage

32
.github/workflows/versioning.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Update version file
on:
workflow_run:
branches: [ master ]
workflows: [Modoboa installer]
types:
- completed
jobs:
update-version:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository.
ref: ${{ github.head_ref }}
- name: Overwrite file
uses: "DamianReeves/write-file-action@master"
with:
path: version.txt
write-mode: overwrite
contents: ${{ github.sha }}
- name: Commit & Push
uses: Andro999b/push@v1.3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref_name }}
force: true
message: '[GitHub Action] Updated version file'

5
.gitignore vendored
View File

@@ -58,3 +58,8 @@ target/
# PyCharm # PyCharm
.idea/ .idea/
#KDE
*.kdev4
installer.cfg

View File

@@ -1,22 +1,20 @@
modoboa-installer **modoboa-installer**
================= =====================
|workflow| |codecov| |workflow| |codecov|
An installer which deploy a complete mail server based on Modoboa. An installer which deploys a complete mail server based on Modoboa.
.. warning:: .. warning::
This tool is still in beta stage, it has been tested on: This tool is still in beta, it has been tested on:
* Debian Buster (10) / Bullseye (11) * Debian 12 and upper
* Ubuntu Bionic Beaver (18.04) and upper * Ubuntu Focal Fossa (20.04) and upper
* CentOS 9 Stream
.. warning:: .. warning::
``/tmp`` partition must be mounted without the ``noexec`` option. ``/tmp`` partition must be mounted without the ``noexec`` option.
Centos 7 support has been depreceated since modoboa requires python 3.7>=.
.. note:: .. note::
@@ -45,8 +43,7 @@ The following components are installed by the installer:
* Nginx and uWSGI * Nginx and uWSGI
* Postfix * Postfix
* Dovecot * Dovecot
* Amavis (with SpamAssassin and ClamAV) * Amavis (with SpamAssassin and ClamAV) or Rspamd
* automx (autoconfiguration service)
* OpenDKIM * OpenDKIM
* Radicale (CalDAV and CardDAV server) * Radicale (CalDAV and CardDAV server)
@@ -78,7 +75,7 @@ If you want more information about the installation process, add the
``--debug`` option to your command line. ``--debug`` option to your command line.
Upgrade mode Upgrade mode
------------ ============
An experimental upgrade mode is available. An experimental upgrade mode is available.
@@ -94,7 +91,7 @@ You can activate it as follows::
It will automatically install latest versions of modoboa and its plugins. It will automatically install latest versions of modoboa and its plugins.
Backup mode Backup mode
------------ ===========
An experimental backup mode is available. An experimental backup mode is available.
@@ -109,7 +106,19 @@ You can start the process as follows::
Then follow the step on the console. Then follow the step on the console.
There is also a non-interactive mode: There is also a non-interactive mode::
$ sudo ./run.py --silent-backup <your domain>
You can also add a path, else it will be saved in ./modoboa_backup/Backup_M_Y_d_H_M::
$ sudo ./run.py --silent-backup --backup-path "/My_Backup_Path" <your domain>
if you want to disable mail backup::
$ sudo ./run.py --backup --no-mail <your domain>
This can be useful for larger instance
1. Silent mode 1. Silent mode
@@ -131,7 +140,7 @@ configuration file (set enabled to False).
This can be useful for larger instance. This can be useful for larger instance.
Restore mode Restore mode
------------ ============
An experimental restore mode is available. An experimental restore mode is available.
@@ -142,7 +151,7 @@ You can start the process as follows::
Then wait for the process to finish. Then wait for the process to finish.
Change the generated hostname Change the generated hostname
----------------------------- =============================
By default, the installer will setup your email server using the By default, the installer will setup your email server using the
following hostname: ``mail.<your domain>``. If you want a different following hostname: ``mail.<your domain>``. If you want a different
@@ -161,12 +170,24 @@ modifications.
Finally, run the installer without the Finally, run the installer without the
``--stop-after-configfile-check`` option. ``--stop-after-configfile-check`` option.
Let's Encrypt certificate Certificate
------------------------- ===========
Self-signed
-----------
It is the default type of certificate the installer will generate, it
is however not recommended for production use.
Letsencrypt
-----------
.. warning:: .. warning::
Please note this option requires the hostname you're using to be Please note that by using this option, you agree to the `ToS
<https://community.letsencrypt.org/tos>`_ of
letsencrypt and that your IP will be logged (see ToS).
Please also note this option requires the hostname you're using to be
valid (ie. it can be resolved with a DNS query) and to match the valid (ie. it can be resolved with a DNS query) and to match the
server you're installing Modoboa on. server you're installing Modoboa on.
@@ -177,6 +198,8 @@ modify the following settings::
[certificate] [certificate]
generate = true generate = true
type = letsencrypt type = letsencrypt
tls_cert_file_path =
tls_key_file_path =
[letsencrypt] [letsencrypt]
email = admin@example.com email = admin@example.com
@@ -184,6 +207,43 @@ modify the following settings::
Change the ``email`` setting to a valid value since it will be used Change the ``email`` setting to a valid value since it will be used
for account recovery. for account recovery.
Manual
------
.. warning::
It is not possible to configure manual certs interactively, so
you'll have to do it in 2 steps. Please run ``run.py`` with
`--stop-after-configfile-check` first, configure your file as
desired and apply the configuration as written bellow. Then run
``run.py`` again but without `--stop-after-configfile-check` or
`--interactive`.
If you want to use already generated certs, simply edit the
``installer.cfg`` file and modify the following settings::
[certificate]
generate = true
type = manual
tls_cert_file_path = *path to tls fullchain file*
tls_key_file_path = *path to tls key file*
Antispam
========
You have 3 options regarding antispam : disabled, Amavis, Rspamd
Amavis
------
Amavis
Rspamd
------
Rspamd
.. |workflow| image:: https://github.com/modoboa/modoboa-installer/workflows/Modoboa%20installer/badge.svg .. |workflow| image:: https://github.com/modoboa/modoboa-installer/workflows/Modoboa%20installer/badge.svg
.. |codecov| image:: http://codecov.io/github/modoboa/modoboa-installer/coverage.svg?branch=master .. |codecov| image:: https://codecov.io/gh/modoboa/modoboa-installer/graph/badge.svg?token=Fo2o1GdHZq
:target: http://codecov.io/github/modoboa/modoboa-installer?branch=master :target: https://codecov.io/gh/modoboa/modoboa-installer

View File

@@ -0,0 +1,37 @@
"""Checks to be performed before any install or upgrade"""
import sys
from urllib.request import urlopen
from modoboa_installer import utils
def check_version():
local_version = ""
with open("version.txt", "r") as version:
local_version = version.readline()
remote_version = ""
with urlopen("https://raw.githubusercontent.com/modoboa/modoboa-installer/master/version.txt") as r_version:
remote_version = r_version.read().decode()
if local_version == "" or remote_version == "":
utils.printcolor(
"Could not check that your installer is up-to-date: "
f"local version: {local_version}, "
f"remote version: {remote_version}",
utils.YELLOW
)
if remote_version != local_version:
utils.error(
"Your installer seems outdated.\n"
"Check README file for instructions about how to update.\n"
"No support will be provided without an up-to-date installer!"
)
answer = utils.user_input("Continue anyway? (y/N) ")
if not answer.lower().startswith("y"):
sys.exit(0)
else:
utils.success("Installer seems up to date!")
def handle():
check_version()

View File

@@ -20,9 +20,27 @@ COMPATIBILITY_MATRIX = {
"modoboa-pdfcredentials": ">=1.1.1", "modoboa-pdfcredentials": ">=1.1.1",
"modoboa-sievefilters": ">=1.1.1", "modoboa-sievefilters": ">=1.1.1",
"modoboa-webmail": ">=1.2.0", "modoboa-webmail": ">=1.2.0",
} },
} }
EXTENSIONS_AVAILABILITY = { EXTENSIONS_AVAILABILITY = {
"modoboa-contacts": "1.7.4", "modoboa-contacts": "1.7.4",
} }
REMOVED_EXTENSIONS = {
"modoboa-pdfcredentials": "2.1.0",
"modoboa-dmarc": "2.1.0",
"modoboa-imap-migration": "2.1.0",
"modoboa-sievefilters": "2.3.0",
"modoboa-postfix-autoreply": "2.3.0",
"modoboa-contacts": "2.4.0",
"modoboa-radicale": "2.4.0",
"modoboa-webmail": "2.4.0",
}
APP_INCOMPATIBILITY = {
"opendkim": ["rspamd"],
"amavis": ["rspamd"],
"postwhite": ["rspamd"],
"spamassassin": ["rspamd"]
}

View File

@@ -28,24 +28,49 @@ ConfigDictTemplate = [
] ]
}, },
{ {
"name": "certificate", "name": "antispam",
"values": [ "values": [
{ {
"option": "generate", "option": "enabled",
"default": "true", "default": "true",
"customizable": True,
"values": ["true", "false"],
"question": "Do you want to setup an antispam utility?"
}, },
{
"option": "type",
"default": "amavis",
"customizable": True,
"question": "Please select your antispam utility",
"values": ["rspamd", "amavis"],
"if": ["antispam.enabled=true"]
}
]
},
{
"name": "certificate",
"values": [
{ {
"option": "type", "option": "type",
"default": "self-signed", "default": "self-signed",
"customizable": True, "customizable": True,
"question": "Please choose your certificate type", "question": "Please choose your certificate type",
"values": ["self-signed", "letsencrypt"], "values": ["self-signed", "letsencrypt", "manual"],
"non_interactive_values": ["manual"],
},
{
"option": "tls_cert_file_path",
"default": ""
},
{
"option": "tls_key_file_path",
"default": ""
} }
], ],
}, },
{ {
"name": "letsencrypt", "name": "letsencrypt",
"if": "certificate.type=letsencrypt", "if": ["certificate.type=letsencrypt"],
"values": [ "values": [
{ {
"option": "email", "option": "email",
@@ -80,7 +105,7 @@ ConfigDictTemplate = [
}, },
{ {
"name": "postgres", "name": "postgres",
"if": "database.engine=postgres", "if": ["database.engine=postgres"],
"values": [ "values": [
{ {
"option": "user", "option": "user",
@@ -96,7 +121,7 @@ ConfigDictTemplate = [
}, },
{ {
"name": "mysql", "name": "mysql",
"if": "database.engine=mysql", "if": ["database.engine=mysql"],
"values": [ "values": [
{ {
"option": "user", "option": "user",
@@ -118,6 +143,31 @@ ConfigDictTemplate = [
} }
] ]
}, },
{
"name": "fail2ban",
"values": [
{
"option": "enabled",
"default": "true",
},
{
"option": "config_dir",
"default": "/etc/fail2ban"
},
{
"option": "max_retry",
"default": "20"
},
{
"option": "ban_time",
"default": "3600"
},
{
"option": "find_time",
"default": "30"
},
]
},
{ {
"name": "modoboa", "name": "modoboa",
"values": [ "values": [
@@ -155,14 +205,16 @@ ConfigDictTemplate = [
"customizable": True, "customizable": True,
"question": "Please enter Modoboa db password", "question": "Please enter Modoboa db password",
}, },
{
"option": "cron_error_recipient",
"default": "root",
"customizable": True,
"question":
"Please enter a mail recipient for cron error reports"
},
{ {
"option": "extensions", "option": "extensions",
"default": ( "default": ""
"modoboa-amavis modoboa-pdfcredentials "
"modoboa-postfix-autoreply modoboa-sievefilters "
"modoboa-webmail modoboa-contacts "
"modoboa-radicale"
),
}, },
{ {
"option": "devmode", "option": "devmode",
@@ -171,40 +223,59 @@ ConfigDictTemplate = [
] ]
}, },
{ {
"name": "automx", "name": "rspamd",
"if": ["antispam.enabled=true", "antispam.type=rspamd"],
"values": [ "values": [
{ {
"option": "enabled", "option": "enabled",
"default": "true", "default": ["antispam.enabled=true", "antispam.type=rspamd"],
}, },
{ {
"option": "user", "option": "user",
"default": "automx", "default": "_rspamd",
}, },
{ {
"option": "config_dir", "option": "password",
"default": "/etc", "default": make_password,
"customizable": True,
"question": "Please enter Rspamd interface password",
}, },
{ {
"option": "home_dir", "option": "dnsbl",
"default": "/srv/automx", "default": "true",
}, },
{ {
"option": "venv_path", "option": "dkim_keys_storage_dir",
"default": "%(home_dir)s/env", "default": "/var/lib/dkim"
}, },
{ {
"option": "instance_path", "option": "key_map_path",
"default": "%(home_dir)s/instance", "default": "/var/lib/dkim/keys.path.map"
}, },
] {
"option": "selector_map_path",
"default": "/var/lib/dkim/selectors.path.map"
},
{
"option": "greylisting",
"default": "true"
},
{
"option": "whitelist_auth",
"default": "true"
},
{
"option": "whitelist_auth_weigth",
"default": "-5"
}
],
}, },
{ {
"name": "amavis", "name": "amavis",
"values": [ "values": [
{ {
"option": "enabled", "option": "enabled",
"default": "true", "default": ["antispam.enabled=true", "antispam.type=amavis"],
}, },
{ {
"option": "user", "option": "user",
@@ -225,8 +296,6 @@ ConfigDictTemplate = [
{ {
"option": "dbpassword", "option": "dbpassword",
"default": make_password, "default": make_password,
"customizable": True,
"question": "Please enter amavis db password"
}, },
], ],
}, },
@@ -256,7 +325,7 @@ ConfigDictTemplate = [
}, },
{ {
"option": "user", "option": "user",
"default": "vmail", "default": "dovecot",
}, },
{ {
"option": "home_dir", "option": "home_dir",
@@ -276,7 +345,11 @@ ConfigDictTemplate = [
}, },
{ {
"option": "radicale_auth_socket_path", "option": "radicale_auth_socket_path",
"default": "/var/run/dovecot/auth-radicale" "default": "/var/run/dovecot/auth-radicale",
},
{
"option": "move_spam_to_junk",
"default": "true",
}, },
] ]
}, },
@@ -298,7 +371,7 @@ ConfigDictTemplate = [
"values": [ "values": [
{ {
"option": "enabled", "option": "enabled",
"default": "true", "default": "false",
}, },
{ {
"option": "config_dir", "option": "config_dir",
@@ -321,6 +394,10 @@ ConfigDictTemplate = [
"option": "message_size_limit", "option": "message_size_limit",
"default": "11534336", "default": "11534336",
}, },
{
"option": "dhe_group",
"default": "4096"
}
] ]
}, },
{ {
@@ -328,7 +405,7 @@ ConfigDictTemplate = [
"values": [ "values": [
{ {
"option": "enabled", "option": "enabled",
"default": "true", "default": ["antispam.enabled=true", "antispam.type=amavis"],
}, },
{ {
"option": "config_dir", "option": "config_dir",
@@ -338,10 +415,11 @@ ConfigDictTemplate = [
}, },
{ {
"name": "spamassassin", "name": "spamassassin",
"if": ["antispam.enabled=true", "antispam.type=amavis"],
"values": [ "values": [
{ {
"option": "enabled", "option": "enabled",
"default": "true", "default": ["antispam.enabled=true", "antispam.type=amavis"],
}, },
{ {
"option": "config_dir", "option": "config_dir",
@@ -376,7 +454,7 @@ ConfigDictTemplate = [
}, },
{ {
"option": "nb_processes", "option": "nb_processes",
"default": "2", "default": "4",
}, },
] ]
}, },
@@ -407,10 +485,11 @@ ConfigDictTemplate = [
}, },
{ {
"name": "opendkim", "name": "opendkim",
"if": ["antispam.enabled=true", "antispam.type=amavis"],
"values": [ "values": [
{ {
"option": "enabled", "option": "enabled",
"default": "true", "default": ["antispam.enabled=true", "antispam.type=amavis"],
}, },
{ {
"option": "user", "option": "user",

View File

@@ -3,19 +3,19 @@
import os import os
import pwd import pwd
import stat import stat
from typing import Optional
from . import package from . import package
from . import system from . import system
from . import utils from . import utils
class Database(object): class Database:
"""Common database backend.""" """Common database backend."""
default_port = None default_port: Optional[int] = None
packages = None packages: Optional[dict[str, list[str]]] = None
service = None service: Optional[str] = None
def __init__(self, config): def __init__(self, config):
"""Install if necessary.""" """Install if necessary."""
@@ -36,13 +36,12 @@ class Database(object):
class PostgreSQL(Database): class PostgreSQL(Database):
"""Postgres.""" """Postgres."""
default_port = 5432 default_port = 5432
packages = { packages = {
"deb": ["postgresql", "postgresql-server-dev-all"], "deb": ["postgresql", "postgresql-server-dev-all"],
"rpm": ["postgresql-server", "postgresql-server-devel", "postgresql"] "rpm": ["postgresql-server", "postgresql-devel"]
} }
service = "postgresql" service = "postgresql"
@@ -54,7 +53,19 @@ class PostgreSQL(Database):
"""Install database if required.""" """Install database if required."""
name, version = utils.dist_info() name, version = utils.dist_info()
if "CentOS" in name: if "CentOS" in name:
initdb_cmd = "postgresql-setup --initdb" if version.startswith("7"):
# Install newer version of postgres in this case
package.backend.install(
"https://download.postgresql.org/pub/repos/yum/"
"reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm"
)
self.packages["rpm"] = [
"postgresql10-server", "postgresql10-devel"]
self.service = "postgresql-10"
initdb_cmd = "/usr/pgsql-10/bin/postgresql-10-setup initdb"
cfgfile = "/var/lib/pgsql/10/data/pg_hba.conf"
else:
initdb_cmd = "postgresql-setup initdb"
cfgfile = "/var/lib/pgsql/data/pg_hba.conf" cfgfile = "/var/lib/pgsql/data/pg_hba.conf"
package.backend.install_many(self.packages[package.backend.FORMAT]) package.backend.install_many(self.packages[package.backend.FORMAT])
utils.exec_cmd(initdb_cmd) utils.exec_cmd(initdb_cmd)
@@ -91,7 +102,7 @@ class PostgreSQL(Database):
def create_database(self, name, owner): def create_database(self, name, owner):
"""Create a database.""" """Create a database."""
code, output = utils.exec_cmd( code, output = utils.exec_cmd(
"psql -lqt | cut -d \| -f 1 | grep -w {} | wc -l" "psql -lqt | cut -d \\| -f 1 | grep -w {} | wc -l"
.format(name), sudo_user=self.dbuser) .format(name), sudo_user=self.dbuser)
if code: if code:
return return
@@ -145,7 +156,6 @@ class PostgreSQL(Database):
class MySQL(Database): class MySQL(Database):
"""MySQL backend.""" """MySQL backend."""
default_port = 3306 default_port = 3306
@@ -166,13 +176,17 @@ class MySQL(Database):
if name.startswith("debian"): if name.startswith("debian"):
if version.startswith("8"): if version.startswith("8"):
self.packages["deb"].append("libmysqlclient-dev") self.packages["deb"].append("libmysqlclient-dev")
elif version.startswith("11"): elif int(version[:2]) >= 11:
self.packages["deb"].append("libmariadb-dev") self.packages["deb"].append("libmariadb-dev")
else: else:
self.packages["deb"].append("libmariadbclient-dev") self.packages["deb"].append("libmariadbclient-dev")
elif name == "ubuntu": elif name == "ubuntu":
if version.startswith("2"):
# Works for Ubuntu 20, 22, and 24.
self.packages["deb"].append("libmariadb-dev")
else:
self.packages["deb"].append("libmysqlclient-dev") self.packages["deb"].append("libmysqlclient-dev")
super(MySQL, self).install_package() super().install_package()
queries = [] queries = []
if name.startswith("debian"): if name.startswith("debian"):
if version.startswith("8"): if version.startswith("8"):
@@ -183,7 +197,10 @@ class MySQL(Database):
"mariadb-server", "root_password_again", "password", "mariadb-server", "root_password_again", "password",
self.dbpassword) self.dbpassword)
return return
if version.startswith("11"): if (
(name.startswith("debian") and int(version[:2]) >= 11) or
(name.startswith("ubuntu") and int(version[:2]) >= 22)
):
queries = [ queries = [
"SET PASSWORD FOR 'root'@'localhost' = PASSWORD('{}')" "SET PASSWORD FOR 'root'@'localhost' = PASSWORD('{}')"
.format(self.dbpassword), .format(self.dbpassword),

View File

@@ -0,0 +1,51 @@
from . import utils
def installation_disclaimer(args, config):
"""Display installation disclaimer."""
hostname = config.get("general", "hostname")
utils.printcolor(
"Notice:\n"
"It is recommanded to run this installer on a FRESHLY installed server.\n"
"(ie. with nothing special already installed on it)\n",
utils.CYAN
)
utils.printcolor(
"Warning:\n"
"Before you start the installation, please make sure the following "
"DNS records exist for domain '{}':\n"
" {} IN A <IP ADDRESS OF YOUR SERVER>\n"
" @ IN MX {}.\n".format(
args.domain,
hostname.replace(".{}".format(args.domain), ""),
hostname
),
utils.YELLOW
)
utils.printcolor(
"Your mail server will be installed with the following components:",
utils.BLUE)
def upgrade_disclaimer(config):
"""Display upgrade disclaimer."""
utils.printcolor(
"Your mail server is about to be upgraded and the following components"
" will be impacted:", utils.BLUE
)
def backup_disclaimer():
"""Display backup disclamer. """
utils.printcolor(
"Your mail server will be backed up locally.\n"
" !! You should really transfer the backup somewhere else...\n"
" !! Custom configuration (like for postfix) won't be saved.", utils.BLUE)
def restore_disclaimer():
"""Display restore disclamer. """
utils.printcolor(
"You are about to restore a previous installation of Modoboa.\n"
"If a new version has been released in between, please update your database!",
utils.BLUE)

View File

@@ -2,10 +2,12 @@
import re import re
from os.path import isfile as file_exists
from . import utils from . import utils
class Package(object): class Package:
"""Base classe.""" """Base classe."""
def __init__(self, dist_name): def __init__(self, dist_name):
@@ -29,10 +31,17 @@ class DEBPackage(Package):
FORMAT = "deb" FORMAT = "deb"
def __init__(self, dist_name): def __init__(self, dist_name):
super(DEBPackage, self).__init__(dist_name) super().__init__(dist_name)
self.index_updated = False self.index_updated = False
self.policy_file = "/usr/sbin/policy-rc.d" self.policy_file = "/usr/sbin/policy-rc.d"
def enable_backports(self, codename):
code, output = utils.exec_cmd(f"grep {codename}-backports /etc/apt/sources.list")
if code:
with open(f"/etc/apt/sources.list.d/backports.list", "w") as fp:
fp.write(f"deb http://deb.debian.org/debian {codename}-backports main\n")
self.update(force=True)
def prepare_system(self): def prepare_system(self):
"""Make sure services don't start at installation.""" """Make sure services don't start at installation."""
with open(self.policy_file, "w") as fp: with open(self.policy_file, "w") as fp:
@@ -42,11 +51,34 @@ class DEBPackage(Package):
def restore_system(self): def restore_system(self):
utils.exec_cmd("rm -f {}".format(self.policy_file)) utils.exec_cmd("rm -f {}".format(self.policy_file))
def update(self): def add_custom_repository(self,
name: str,
url: str,
key_url: str,
codename: str,
with_source: bool = True):
key_file = f"/etc/apt/keyrings/{name}.gpg"
utils.exec_cmd(
f"wget -O - {key_url} | gpg --dearmor | tee {key_file} > /dev/null"
)
line_types = ["deb"]
if with_source:
line_types.append("deb-src")
for line_type in line_types:
line = (
f"{line_type} [arch=amd64 signed-by={key_file}] "
f"{url} {codename} main"
)
target_file = f"/etc/apt/sources.list.d/{name}.list"
tee_option = "-a" if file_exists(target_file) else ""
utils.exec_cmd(f'echo "{line}" | tee {tee_option} {target_file}')
self.index_updated = False
def update(self, force=False):
"""Update local cache.""" """Update local cache."""
if self.index_updated: if self.index_updated and not force:
return return
utils.exec_cmd("apt-get update --quiet") utils.exec_cmd("apt-get -o Dpkg::Progress-Fancy=0 update --quiet")
self.index_updated = True self.index_updated = True
def preconfigure(self, name, question, qtype, answer): def preconfigure(self, name, question, qtype, answer):
@@ -57,18 +89,18 @@ class DEBPackage(Package):
def install(self, name): def install(self, name):
"""Install a package.""" """Install a package."""
self.update() self.update()
utils.exec_cmd("apt-get install --quiet --assume-yes {}".format(name)) utils.exec_cmd("apt-get -o Dpkg::Progress-Fancy=0 install --quiet --assume-yes -o DPkg::options::=--force-confold {}".format(name))
def install_many(self, names): def install_many(self, names):
"""Install many packages.""" """Install many packages."""
self.update() self.update()
return utils.exec_cmd("apt-get install --quiet --assume-yes {}".format( return utils.exec_cmd("apt-get -o Dpkg::Progress-Fancy=0 install --quiet --assume-yes -o DPkg::options::=--force-confold {}".format(
" ".join(names))) " ".join(names)))
def get_installed_version(self, name): def get_installed_version(self, name):
"""Get installed package version.""" """Get installed package version."""
code, output = utils.exec_cmd( code, output = utils.exec_cmd(
"dpkg -s {} | grep Version".format(name), capture_output=True) "dpkg -s {} | grep Version".format(name))
match = re.match(r"Version: (\d:)?(.+)-\d", output.decode()) match = re.match(r"Version: (\d:)?(.+)-\d", output.decode())
if match: if match:
return match.group(2) return match.group(2)
@@ -82,32 +114,22 @@ class RPMPackage(Package):
def __init__(self, dist_name): def __init__(self, dist_name):
"""Initialize backend.""" """Initialize backend."""
self.dist_name = dist_name super().__init__(dist_name)
super(RPMPackage, self).__init__(dist_name) if "centos" in dist_name:
def prepare_system(self):
if "centos" in self.dist_name:
utils.exec_cmd("dnf config-manager --set-enabled crb")
self.install("epel-release") self.install("epel-release")
self.update()
def update(self):
"""Update the database repo."""
utils.exec_cmd("dnf update -y --quiet")
def install(self, name): def install(self, name):
"""Install a package.""" """Install a package."""
"""Need to add check for rrdtool, sendmail-milter, libmemcached and --enablerepo=crb""" utils.exec_cmd("yum install -y --quiet {}".format(name))
utils.exec_cmd("dnf install -y --quiet {}".format(name))
def install_many(self, names): def install_many(self, names):
"""Install many packages.""" """Install many packages."""
return utils.exec_cmd("dnf install -y --quiet {}".format(" ".join(names))) return utils.exec_cmd("yum install -y --quiet {}".format(" ".join(names)))
def get_installed_version(self, name): def get_installed_version(self, name):
"""Get installed package version.""" """Get installed package version."""
code, output = utils.exec_cmd( code, output = utils.exec_cmd(
"rpm -qi {} | grep Version".format(name), capture_output=True) "rpm -qi {} | grep Version".format(name))
match = re.match(r"Version\s+: (.+)", output.decode()) match = re.match(r"Version\s+: (.+)", output.decode())
if match: if match:
return match.group(1) return match.group(1)
@@ -118,7 +140,7 @@ def get_backend():
"""Return the appropriate package backend.""" """Return the appropriate package backend."""
distname = utils.dist_name() distname = utils.dist_name()
backend = None backend = None
if distname in ["debian", "debian gnu/linux", "ubuntu"]: if distname in ["debian", "debian gnu/linux", "ubuntu", "linuxmint"]:
backend = DEBPackage backend = DEBPackage
elif "centos" in distname: elif "centos" in distname:
backend = RPMPackage backend = RPMPackage

View File

@@ -1,6 +1,7 @@
"""Python related tools.""" """Python related tools."""
import os import os
import sys
from . import package from . import package
from . import utils from . import utils
@@ -45,6 +46,40 @@ def install_packages(names, venv=None, upgrade=False, **kwargs):
utils.exec_cmd(cmd, **kwargs) utils.exec_cmd(cmd, **kwargs)
def get_package_version(name, venv=None, **kwargs):
"""Returns the version of an installed package."""
cmd = "{} show {}".format(
get_pip_path(venv),
name
)
exit_code, output = utils.exec_cmd(cmd, **kwargs)
if exit_code != 0:
utils.error(f"Failed to get version of {name}. "
f"Output is: {output}")
sys.exit(1)
version_list_clean = []
for line in output.decode().split("\n"):
if not line.startswith("Version:"):
continue
version_item_list = line.split(":")
version_list = version_item_list[1].split(".")
for element in version_list:
try:
version_list_clean.append(int(element))
except ValueError:
utils.printcolor(
f"Failed to decode some part of the version of {name}",
utils.YELLOW)
version_list_clean.append(element)
if len(version_list_clean) == 0:
utils.printcolor(
f"Failed to find the version of {name}",
utils.RED)
sys.exit(1)
return version_list_clean
def install_package_from_repository(name, url, vcs="git", venv=None, **kwargs): def install_package_from_repository(name, url, vcs="git", venv=None, **kwargs):
"""Install a Python package from its repository.""" """Install a Python package from its repository."""
if vcs == "git": if vcs == "git":
@@ -54,16 +89,10 @@ def install_package_from_repository(name, url, vcs="git", venv=None, **kwargs):
utils.exec_cmd(cmd, **kwargs) utils.exec_cmd(cmd, **kwargs)
def setup_virtualenv(path, sudo_user=None, python_version=2): def setup_virtualenv(path, sudo_user=None):
"""Install a virtualenv if needed.""" """Install a virtualenv if needed."""
if os.path.exists(path): if os.path.exists(path):
return return
if python_version == 2:
python_binary = "python"
packages = ["python-virtualenv"]
if utils.dist_name() == "debian":
packages.append("virtualenv")
else:
if utils.dist_name().startswith("centos"): if utils.dist_name().startswith("centos"):
python_binary = "python3" python_binary = "python3"
packages = ["python3"] packages = ["python3"]
@@ -72,8 +101,5 @@ def setup_virtualenv(path, sudo_user=None, python_version=2):
packages = ["python3-venv"] packages = ["python3-venv"]
package.backend.install_many(packages) package.backend.install_many(packages)
with utils.settings(sudo_user=sudo_user): with utils.settings(sudo_user=sudo_user):
if python_version == 2:
utils.exec_cmd("virtualenv {}".format(path))
else:
utils.exec_cmd("{} -m venv {}".format(python_binary, path)) utils.exec_cmd("{} -m venv {}".format(python_binary, path))
install_packages(["pip", "setuptools\<58.0.0"], venv=path, upgrade=True) install_packages(["pip", "setuptools"], venv=path, upgrade=True)

View File

@@ -9,8 +9,7 @@ from .. import utils
def load_app_script(appname): def load_app_script(appname):
"""Load module corresponding to the given appname.""" """Load module corresponding to the given appname."""
try: try:
script = importlib.import_module( script = importlib.import_module("modoboa_installer.scripts.{}".format(appname))
"modoboa_installer.scripts.{}".format(appname))
except ImportError: except ImportError:
print("Unknown application {}".format(appname)) print("Unknown application {}".format(appname))
sys.exit(1) sys.exit(1)
@@ -19,8 +18,9 @@ def load_app_script(appname):
def install(appname: str, config, upgrade: bool, archive_path: str): def install(appname: str, config, upgrade: bool, archive_path: str):
"""Install an application.""" """Install an application."""
if (config.has_option(appname, "enabled") and if config.has_option(appname, "enabled") and not config.getboolean(
not config.getboolean(appname, "enabled")): appname, "enabled"
):
return return
utils.printcolor("Installing {}".format(appname), utils.MAGENTA) utils.printcolor("Installing {}".format(appname), utils.MAGENTA)
@@ -34,8 +34,9 @@ def install(appname: str, config, upgrade: bool, archive_path: str):
def backup(appname, config, path): def backup(appname, config, path):
"""Backup an application.""" """Backup an application."""
if (config.has_option(appname, "enabled") and if config.has_option(appname, "enabled") and not config.getboolean(
not config.getboolean(appname, "enabled")): appname, "enabled"
):
return return
utils.printcolor("Backing up {}".format(appname), utils.MAGENTA) utils.printcolor("Backing up {}".format(appname), utils.MAGENTA)
@@ -49,6 +50,5 @@ def backup(appname, config, path):
def restore_prep(restore): def restore_prep(restore):
"""Restore instance""" """Restore instance"""
script = importlib.import_module( script = importlib.import_module("modoboa_installer.scripts.restore")
"modoboa_installer.scripts.restore")
getattr(script, "Restore")(restore) getattr(script, "Restore")(restore)

View File

@@ -10,18 +10,29 @@ from . import backup, install
class Amavis(base.Installer): class Amavis(base.Installer):
"""Amavis installer.""" """Amavis installer."""
appname = "amavis" appname = "amavis"
packages = { packages = {
"deb": [ "deb": [
"libdbi-perl", "amavisd-new", "arc", "arj", "cabextract", "libdbi-perl",
"liblz4-tool", "lrzip", "lzop", "p7zip-full", "rpm2cpio", "amavisd-new",
"arc",
"arj",
"cabextract",
"liblz4-tool",
"lrzip",
"lzop",
"p7zip-full",
"rpm2cpio",
"unrar-free", "unrar-free",
], ],
"rpm": [ "rpm": [
"amavis", "arj", "lz4", "lzop", "p7zip", "amavisd-new",
"arj",
"lz4",
"lzop",
"p7zip",
], ],
} }
with_db = True with_db = True
@@ -43,8 +54,10 @@ class Amavis(base.Installer):
"""Return appropriate config files.""" """Return appropriate config files."""
if package.backend.FORMAT == "deb": if package.backend.FORMAT == "deb":
return [ return [
"conf.d/05-node_id", "conf.d/15-content_filter_mode", "conf.d/05-node_id",
"conf.d/50-user"] "conf.d/15-content_filter_mode",
"conf.d/50-user",
]
return ["amavisd.conf"] return ["amavisd.conf"]
def get_packages(self): def get_packages(self):
@@ -52,7 +65,17 @@ class Amavis(base.Installer):
packages = super(Amavis, self).get_packages() packages = super(Amavis, self).get_packages()
if package.backend.FORMAT == "deb": if package.backend.FORMAT == "deb":
db_driver = "pg" if self.db_driver == "pgsql" else self.db_driver db_driver = "pg" if self.db_driver == "pgsql" else self.db_driver
return packages + ["libdbd-{}-perl".format(db_driver)] packages += ["libdbd-{}-perl".format(db_driver)]
name, version = utils.dist_info()
try:
major_version = int(version.split(".")[0])
except ValueError:
major_version = 0
if major_version >= 13:
packages = [p if p != "liblz4-tool" else "lz4" for p in packages]
return packages
if self.db_driver == "pgsql": if self.db_driver == "pgsql":
db_driver = "Pg" db_driver = "Pg"
elif self.db_driver == "mysql": elif self.db_driver == "mysql":
@@ -61,9 +84,9 @@ class Amavis(base.Installer):
raise NotImplementedError("DB driver not supported") raise NotImplementedError("DB driver not supported")
packages += ["perl-DBD-{}".format(db_driver)] packages += ["perl-DBD-{}".format(db_driver)]
name, version = utils.dist_info() name, version = utils.dist_info()
if version.startswith('7'): if version.startswith("7"):
packages += ["cabextract", "lrzip", "unar", "unzoo"] packages += ["cabextract", "lrzip", "unar", "unzoo"]
elif version.startswith('8'): elif version.startswith("8"):
packages += ["perl-IO-stringy"] packages += ["perl-IO-stringy"]
return packages return packages
@@ -75,12 +98,10 @@ class Amavis(base.Installer):
version = package.backend.get_installed_version("amavis") version = package.backend.get_installed_version("amavis")
if version is None: if version is None:
raise utils.FatalError("Amavis is not installed") raise utils.FatalError("Amavis is not installed")
path = self.get_file_path( path = self.get_file_path("amavis_{}_{}.sql".format(self.dbengine, version))
"amavis_{}_{}.sql".format(self.dbengine, version))
if not os.path.exists(path): if not os.path.exists(path):
version = ".".join(version.split(".")[:-1]) + ".X" version = ".".join(version.split(".")[:-1]) + ".X"
path = self.get_file_path( path = self.get_file_path("amavis_{}_{}.sql".format(self.dbengine, version))
"amavis_{}_{}.sql".format(self.dbengine, version))
if not os.path.exists(path): if not os.path.exists(path):
raise utils.FatalError("Failed to find amavis database schema") raise utils.FatalError("Failed to find amavis database schema")
return path return path
@@ -97,20 +118,21 @@ class Amavis(base.Installer):
def custom_backup(self, path): def custom_backup(self, path):
"""Backup custom configuration if any.""" """Backup custom configuration if any."""
if package.backend.FORMAT == "deb":
amavis_custom = f"{self.config_dir}/conf.d/99-custom" amavis_custom = f"{self.config_dir}/conf.d/99-custom"
if os.path.isfile(amavis_custom): if os.path.isfile(amavis_custom):
utils.copy_file(amavis_custom, path) utils.copy_file(amavis_custom, path)
utils.success("Amavis custom configuration saved!") utils.success("Amavis custom configuration saved!")
backup("spamassassin", self.config, os.path.dirname(path)) backup("spamassassin", self.config, self.base_backup_path)
def restore(self): def restore(self):
"""Restore custom config files.""" """Restore custom config files."""
if package.backend.FORMAT != "deb": if package.backend.FORMAT != "deb":
return return
amavis_custom_configuration = os.path.join( amavis_custom_configuration = os.path.join(
self.archive_path, "custom/99-custom") self.archive_path, "custom/amavis/99-custom"
)
if os.path.isfile(amavis_custom_configuration): if os.path.isfile(amavis_custom_configuration):
utils.copy_file(amavis_custom_configuration, os.path.join( utils.copy_file(
self.config_dir, "conf.d")) amavis_custom_configuration, os.path.join(self.config_dir, "conf.d")
)
utils.success("Custom amavis configuration restored.") utils.success("Custom amavis configuration restored.")

View File

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

View File

@@ -1,231 +0,0 @@
"""Backup script for pre-installed instance."""
import os
import pwd
import shutil
import stat
import sys
import datetime
from .. import database
from .. import utils
from ..constants import DEFAULT_BACKUP_DIRECTORY
class Backup:
"""
Backup structure ( {optional} ):
{{backup_directory}}
||
||--> installer.cfg
||--> custom
|--> { (copy of) /etc/amavis/conf.d/99-custom }
|--> { (copy of) /etc/postfix/custom_whitelist.cidr }
|--> { (copy of) dkim directory }
|--> {dkim.pem}...
|--> { (copy of) radicale home_dir }
||--> databases
|--> modoboa.sql
|--> { amavis.sql }
|--> { spamassassin.sql }
||--> mails
|--> vmails
"""
def __init__(self, config, silent_backup, backup_path, nomail):
self.config = config
self.backup_path = backup_path
self.nomail = nomail
self.silent_backup = silent_backup
def validate_path(self, path):
"""Check basic condition for backup directory."""
path_exists = os.path.exists(path)
if path_exists and os.path.isfile(path):
utils.printcolor(
"Error, you provided a file instead of a directory!", utils.RED)
return False
if not path_exists:
if not self.silent_backup:
create_dir = input(
f"\"{path}\" doesn't exist, would you like to create it? [Y/n]\n").lower()
if self.silent_backup or (not self.silent_backup and create_dir.startswith("y")):
pw = pwd.getpwnam("root")
utils.mkdir_safe(path, stat.S_IRWXU |
stat.S_IRWXG, pw[2], pw[3])
else:
utils.printcolor(
"Error, backup directory not present.", utils.RED
)
return False
if len(os.listdir(path)) != 0:
if not self.silent_backup:
delete_dir = input(
"Warning: backup directory is not empty, it will be purged if you continue... [Y/n]\n").lower()
if self.silent_backup or (not self.silent_backup and delete_dir.startswith("y")):
try:
os.remove(os.path.join(path, "installer.cfg"))
except FileNotFoundError:
pass
shutil.rmtree(os.path.join(path, "custom"),
ignore_errors=False)
shutil.rmtree(os.path.join(path, "mails"), ignore_errors=False)
shutil.rmtree(os.path.join(path, "databases"),
ignore_errors=False)
else:
utils.printcolor(
"Error: backup directory not clean.", utils.RED
)
return False
self.backup_path = path
pw = pwd.getpwnam("root")
for dir in ["custom/", "databases/"]:
utils.mkdir_safe(os.path.join(self.backup_path, dir),
stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3])
return True
def set_path(self):
"""Setup backup directory."""
if self.silent_backup:
if self.backup_path is None:
if self.config.has_option("backup", "default_path"):
path = self.config.get("backup", "default_path")
else:
path = DEFAULT_BACKUP_DIRECTORY
date = datetime.datetime.now().strftime("%m_%d_%Y_%H_%M")
path = os.path.join(path, f"backup_{date}")
self.validate_path(path)
else:
if not self.validate_path(self.backup_path):
utils.printcolor(
f"Path provided: {self.backup_path}", utils.BLUE)
sys.exit(1)
else:
user_value = None
while user_value == "" or user_value is None or not self.validate_path(user_value):
utils.printcolor(
"Enter backup path (it must be an empty directory)", utils.MAGENTA)
utils.printcolor("CTRL+C to cancel", utils.MAGENTA)
user_value = utils.user_input("-> ")
def config_file_backup(self):
utils.copy_file("installer.cfg", self.backup_path)
def mail_backup(self):
if self.nomail:
utils.printcolor(
"Skipping mail backup, no-mail argument provided", utils.MAGENTA)
return
utils.printcolor("Backing up mails", utils.MAGENTA)
home_path = self.config.get("dovecot", "home_dir")
if not os.path.exists(home_path) or os.path.isfile(home_path):
utils.printcolor("Error backing up Email, provided path "
f" ({home_path}) seems not right...", utils.RED)
else:
dst = os.path.join(self.backup_path, "mails/")
if os.path.exists(dst):
shutil.rmtree(dst)
shutil.copytree(home_path, dst)
utils.printcolor("Mail backup complete!", utils.GREEN)
def custom_config_backup(self):
"""
Custom config :
- DKIM keys: {{keys_storage_dir}}
- Radicale collection (calendat, contacts): {{home_dir}}
- Amavis : /etc/amavis/conf.d/99-custom
- Postwhite : /etc/postwhite.conf
Feel free to suggest to add others!
"""
utils.printcolor(
"Backing up some custom configuration...", utils.MAGENTA)
custom_path = os.path.join(
self.backup_path, "custom")
# DKIM Key
if (self.config.has_option("opendkim", "enabled") and
self.config.getboolean("opendkim", "enabled")):
dkim_keys = self.config.get(
"opendkim", "keys_storage_dir", fallback="/var/lib/dkim")
if os.path.isdir(dkim_keys):
shutil.copytree(dkim_keys, os.path.join(custom_path, "dkim"))
utils.printcolor(
"DKIM keys saved!", utils.GREEN)
# Radicale Collections
if (self.config.has_option("radicale", "enabled") and
self.config.getboolean("radicale", "enabled")):
radicale_backup = os.path.join(self.config.get(
"radicale", "home_dir", fallback="/srv/radicale"), "collections")
if os.path.isdir(radicale_backup):
shutil.copytree(radicale_backup, os.path.join(
custom_path, "radicale"))
utils.printcolor("Radicale files saved", utils.GREEN)
# AMAVIS
if (self.config.has_option("amavis", "enabled") and
self.config.getboolean("amavis", "enabled")):
amavis_custom = "/etc/amavis/conf.d/99-custom"
if os.path.isfile(amavis_custom):
utils.copy_file(amavis_custom, custom_path)
utils.printcolor(
"Amavis custom configuration saved!", utils.GREEN)
# POSTWHITE
if (self.config.has_option("postwhite", "enabled") and
self.config.getboolean("postwhite", "enabled")):
postswhite_custom = "/etc/postwhite.conf"
if os.path.isfile(postswhite_custom):
utils.copy_file(postswhite_custom, custom_path)
utils.printcolor(
"Postwhite configuration saved!", utils.GREEN)
def database_backup(self):
"""Backing up databases"""
utils.printcolor("Backing up databases...", utils.MAGENTA)
self.database_dump("modoboa")
self.database_dump("amavis")
self.database_dump("spamassassin")
def database_dump(self, app_name):
dump_path = os.path.join(self.backup_path, "databases")
backend = database.get_backend(self.config)
if app_name == "modoboa" or (self.config.has_option(app_name, "enabled") and
self.config.getboolean(app_name, "enabled")):
dbname = self.config.get(app_name, "dbname")
dbuser = self.config.get(app_name, "dbuser")
dbpasswd = self.config.get(app_name, "dbpassword")
backend.dump_database(dbname, dbuser, dbpasswd,
os.path.join(dump_path, f"{app_name}.sql"))
def backup_completed(self):
utils.printcolor("Backup process done, your backup is available here:"
f"--> {self.backup_path}", utils.GREEN)
def run(self):
self.set_path()
self.config_file_backup()
self.mail_backup()
self.custom_config_backup()
self.database_backup()
self.backup_completed()

View File

@@ -2,25 +2,27 @@
import os import os
import sys import sys
from typing import Optional
from .. import database from .. import database
from .. import package from .. import package
from .. import python
from .. import system from .. import system
from .. import utils from .. import utils
class Installer(object): class Installer:
"""Simple installer for one application.""" """Simple installer for one application."""
appname = None appname: str
no_daemon = False no_daemon: bool = False
daemon_name = None daemon_name: Optional[str] = None
packages = {} packages: dict[str, list[str]] = {}
with_user = False with_user: bool = False
with_db = False with_db: bool = False
config_files = [] config_files: list[str] = []
def __init__(self, config, upgrade: bool, archive_path: str): def __init__(self, config, upgrade: bool, archive_path: str) -> None:
"""Get configuration.""" """Get configuration."""
self.config = config self.config = config
self.upgrade = upgrade self.upgrade = upgrade
@@ -29,12 +31,12 @@ class Installer(object):
self.app_config = dict(self.config.items(self.appname)) self.app_config = dict(self.config.items(self.appname))
self.dbengine = self.config.get("database", "engine") self.dbengine = self.config.get("database", "engine")
# Used to install system packages # Used to install system packages
self.db_driver = ( self.db_driver = "pgsql" if self.dbengine == "postgres" else self.dbengine
"pgsql" if self.dbengine == "postgres" else self.dbengine)
self.backend = database.get_backend(self.config) self.backend = database.get_backend(self.config)
self.dbhost = self.config.get("database", "host") self.dbhost = self.config.get("database", "host")
self.dbport = self.config.get( self.dbport = self.config.get(
"database", "port", fallback=self.backend.default_port) "database", "port", fallback=self.backend.default_port
)
self._config_dir = None self._config_dir = None
if not self.with_db: if not self.with_db:
return return
@@ -42,11 +44,25 @@ class Installer(object):
self.dbuser = self.config.get(self.appname, "dbuser") self.dbuser = self.config.get(self.appname, "dbuser")
self.dbpasswd = self.config.get(self.appname, "dbpassword") self.dbpasswd = self.config.get(self.appname, "dbpassword")
@property
def modoboa_2_2_or_greater(self) -> bool:
# Check if modoboa version > 2.2
modoboa_version = python.get_package_version(
"modoboa",
self.config.get("modoboa", "venv_path"),
sudo_user=self.config.get("modoboa", "user"),
)
condition = (
int(modoboa_version[0]) == 2 and int(modoboa_version[1]) >= 2
) or int(modoboa_version[0]) > 2
return condition
@property @property
def config_dir(self): def config_dir(self):
"""Return main configuration directory.""" """Return main configuration directory."""
if self._config_dir is None and self.config.has_option( if self._config_dir is None and self.config.has_option(
self.appname, "config_dir"): self.appname, "config_dir"
):
self._config_dir = self.config.get(self.appname, "config_dir") self._config_dir = self.config.get(self.appname, "config_dir")
return self._config_dir return self._config_dir
@@ -57,11 +73,11 @@ class Installer(object):
def get_sql_schema_from_backup(self): def get_sql_schema_from_backup(self):
"""Retrieve a dump path from a previous backup.""" """Retrieve a dump path from a previous backup."""
utils.printcolor( utils.printcolor(
f"Trying to restore {self.appname} database from backup.", f"Trying to restore {self.appname} database from backup.", utils.MAGENTA
utils.MAGENTA
) )
database_backup_path = os.path.join( database_backup_path = os.path.join(
self.archive_path, f"databases/{self.appname}.sql") self.archive_path, f"databases/{self.appname}.sql"
)
if os.path.isfile(database_backup_path): if os.path.isfile(database_backup_path):
utils.success(f"SQL dump found in backup for {self.appname}!") utils.success(f"SQL dump found in backup for {self.appname}!")
return database_backup_path return database_backup_path
@@ -70,8 +86,7 @@ class Installer(object):
def get_file_path(self, fname): def get_file_path(self, fname):
"""Return the absolute path of this file.""" """Return the absolute path of this file."""
return os.path.abspath( return os.path.abspath(
os.path.join( os.path.join(os.path.dirname(__file__), "files", self.appname, fname)
os.path.dirname(__file__), "files", self.appname, fname)
) )
def setup_database(self): def setup_database(self):
@@ -86,8 +101,7 @@ class Installer(object):
if not schema: if not schema:
schema = self.get_sql_schema_path() schema = self.get_sql_schema_path()
if schema: if schema:
self.backend.load_sql_file( self.backend.load_sql_file(self.dbname, self.dbuser, self.dbpasswd, schema)
self.dbname, self.dbuser, self.dbpasswd, schema)
def setup_user(self): def setup_user(self):
"""Setup a system user.""" """Setup a system user."""
@@ -103,8 +117,7 @@ class Installer(object):
def get_template_context(self): def get_template_context(self):
"""Return context used for template rendering.""" """Return context used for template rendering."""
context = { context = {
"dbengine": ( "dbengine": ("Pg" if self.dbengine == "postgres" else self.dbengine),
"Pg" if self.dbengine == "postgres" else self.dbengine),
"dbhost": self.dbhost, "dbhost": self.dbhost,
"dbport": self.dbport, "dbport": self.dbport,
} }
@@ -131,7 +144,7 @@ class Installer(object):
return return
exitcode, output = package.backend.install_many(packages) exitcode, output = package.backend.install_many(packages)
if exitcode: if exitcode:
utils.printcolor("Failed to install dependencies", utils.RED) utils.error("Failed to install dependencies")
sys.exit(1) sys.exit(1)
def get_config_files(self): def get_config_files(self):
@@ -156,9 +169,11 @@ class Installer(object):
utils.copy_from_template(src, dst, context) utils.copy_from_template(src, dst, context)
def backup(self, path): def backup(self, path):
self.base_backup_path = path
if self.with_db: if self.with_db:
self._dump_database(path) self._dump_database(path)
custom_backup_path = os.path.join(path, "custom") custom_backup_path = os.path.join(path, "custom", self.appname)
utils.mkdir_safe(custom_backup_path)
self.custom_backup(custom_backup_path) self.custom_backup(custom_backup_path)
def custom_backup(self, path): def custom_backup(self, path):
@@ -197,8 +212,7 @@ class Installer(object):
"""Create a new database dump for this app.""" """Create a new database dump for this app."""
target_dir = os.path.join(backup_path, "databases") target_dir = os.path.join(backup_path, "databases")
target_file = os.path.join(target_dir, f"{self.appname}.sql") target_file = os.path.join(target_dir, f"{self.appname}.sql")
self.backend.dump_database( self.backend.dump_database(self.dbname, self.dbuser, self.dbpasswd, target_file)
self.dbname, self.dbuser, self.dbpasswd, target_file)
def pre_run(self): def pre_run(self):
"""Tasks to execute before the installer starts.""" """Tasks to execute before the installer starts."""

View File

@@ -15,7 +15,7 @@ class Clamav(base.Installer):
packages = { packages = {
"deb": ["clamav-daemon"], "deb": ["clamav-daemon"],
"rpm": [ "rpm": [
"clamav", "clamav-update", "clamd" "clamav", "clamav-update", "clamav-server", "clamav-server-systemd"
], ],
} }
@@ -42,6 +42,7 @@ class Clamav(base.Installer):
"""Additional tasks.""" """Additional tasks."""
if package.backend.FORMAT == "deb": if package.backend.FORMAT == "deb":
user = self.config.get(self.appname, "user") user = self.config.get(self.appname, "user")
if self.config.getboolean("amavis", "enabled"):
system.add_user_to_group( system.add_user_to_group(
user, self.config.get("amavis", "user") user, self.config.get("amavis", "user")
) )
@@ -57,7 +58,7 @@ class Clamav(base.Installer):
# Check if not present before # Check if not present before
path = "/usr/lib/systemd/system/clamd@.service" path = "/usr/lib/systemd/system/clamd@.service"
code, output = utils.exec_cmd( code, output = utils.exec_cmd(
"grep 'WantedBy=multi-user.target' {}".format(path)) r"grep 'WantedBy\s*=\s*multi-user.target' {}".format(path))
if code: if code:
utils.exec_cmd( utils.exec_cmd(
"""cat <<EOM >> {} """cat <<EOM >> {}

View File

@@ -4,6 +4,7 @@ import glob
import os import os
import pwd import pwd
import shutil import shutil
import stat
from .. import database from .. import database
from .. import package from .. import package
@@ -14,32 +15,87 @@ from . import base
class Dovecot(base.Installer): class Dovecot(base.Installer):
"""Dovecot installer.""" """Dovecot installer."""
appname = "dovecot" appname = "dovecot"
packages = { packages = {
"deb": [ "deb": [
"dovecot-imapd", "dovecot-lmtpd", "dovecot-managesieved", "dovecot-imapd",
"dovecot-sieve"], "dovecot-lmtpd",
"rpm": [ "dovecot-managesieved",
"dovecot", "dovecot-pigeonhole"] "dovecot-sieve",
],
"rpm": ["dovecot", "dovecot-pigeonhole"],
}
per_version_config_files = {
"2.3": [
"dovecot.conf",
"dovecot-dict-sql.conf.ext",
"conf.d/10-ssl.conf",
"conf.d/10-master.conf",
"conf.d/20-lmtp.conf",
"conf.d/10-ssl-keys.try",
"conf.d/dovecot-oauth2.conf.ext",
],
"2.4": [
"dovecot.conf",
"conf.d/10-mail.conf",
"conf.d/10-master.conf",
"conf.d/10-ssl.conf",
"conf.d/10-ssl-keys.try",
"conf.d/20-lmtp.conf",
"conf.d/auth-oauth2.conf.ext",
],
} }
config_files = [
"dovecot.conf", "dovecot-dict-sql.conf.ext", "conf.d/10-ssl.conf",
"conf.d/10-master.conf", "conf.d/20-lmtp.conf", "conf.d/10-ssl-keys.try"]
with_user = True with_user = True
def get_config_files(self): @property
"""Additional config files.""" def version(self) -> str:
return self.config_files + [ if not hasattr(self, "_version"):
"dovecot-sql-{}.conf.ext=dovecot-sql.conf.ext" self._version = package.backend.get_installed_version("dovecot-core")[:3]
.format(self.dbengine), return self._version
"dovecot-sql-master-{}.conf.ext=dovecot-sql-master.conf.ext"
.format(self.dbengine), def setup_user(self):
"postlogin-{}.sh=/usr/local/bin/postlogin.sh" """Setup mailbox user."""
.format(self.dbengine), super().setup_user()
self.mailboxes_owner = self.app_config["mailboxes_owner"]
system.create_user(self.mailboxes_owner, self.home_dir)
def _get_config_files_for_version(self, version: str) -> list[str]:
files = self.per_version_config_files[version]
if version == "2.4":
files += [
f"conf.d/auth-sql-{self.dbengine}.conf.ext=conf.d/auth-sql.conf.ext",
f"conf.d/auth-master-{self.dbengine}.conf.ext=conf.d/auth-master.conf.ext",
f"conf.d/30-dict-server-{self.dbengine}.conf=conf.d/30-dict-server.conf",
] ]
else:
files += [
f"dovecot-sql-{self.dbengine}.conf.ext=dovecot-sql.conf.ext",
f"dovecot-sql-master-{self.dbengine}.conf.ext=dovecot-sql-master.conf.ext",
]
result = []
for path in files:
if "=" not in path:
result.append(f"{version}/{path}={path}")
else:
src, dst = path.split("=")
result.append(f"{version}/{src}={dst}")
return result
def get_config_files(self) -> list[str]:
"""Additional config files."""
_config_files = self._get_config_files_for_version(self.version)
_config_files.append(
f"postlogin-{self.dbengine}.sh=/usr/local/bin/postlogin.sh"
)
if self.app_config["move_spam_to_junk"]:
_config_files += [
"custom_after_sieve/spam-to-junk.sieve=conf.d/custom_after_sieve/spam-to-junk.sieve",
f"{self.version}/conf.d/90-sieve.conf=conf.d/90-sieve.conf",
]
return _config_files
def get_packages(self): def get_packages(self):
"""Additional packages.""" """Additional packages."""
@@ -47,28 +103,46 @@ class Dovecot(base.Installer):
if package.backend.FORMAT == "deb": if package.backend.FORMAT == "deb":
if "pop3" in self.config.get("dovecot", "extra_protocols"): if "pop3" in self.config.get("dovecot", "extra_protocols"):
packages += ["dovecot-pop3d"] packages += ["dovecot-pop3d"]
return super(Dovecot, self).get_packages() + packages packages += super().get_packages()
backports_codename = getattr(self, "backports_codename", None)
if backports_codename:
packages = [
f"{package}/{backports_codename}-backports" for package in packages
]
return packages
def install_packages(self): def install_packages(self):
"""Preconfigure Dovecot if needed.""" """Preconfigure Dovecot if needed."""
name, version = utils.dist_info()
name = name.lower()
if name.startswith("debian") and version.startswith("12"):
package.backend.enable_backports("bookworm")
self.backports_codename = "bookworm"
package.backend.preconfigure( package.backend.preconfigure(
"dovecot-core", "create-ssl-cert", "boolean", "false") "dovecot-core", "create-ssl-cert", "boolean", "false"
super(Dovecot, self).install_packages() )
super().install_packages()
def get_template_context(self): def get_template_context(self):
"""Additional variables.""" """Additional variables."""
context = super(Dovecot, self).get_template_context() context = super().get_template_context()
pw = pwd.getpwnam(self.user) pw_mailbox = pwd.getpwnam(self.mailboxes_owner)
dovecot_package = {"deb": "dovecot-core", "rpm": "dovecot"} dovecot_package = {"deb": "dovecot-core", "rpm": "dovecot"}
ssl_protocol_parameter = "ssl_protocols" ssl_protocol_parameter = "ssl_protocols"
if package.backend.get_installed_version(dovecot_package[package.backend.FORMAT]) > "2.3": if (
package.backend.get_installed_version(
dovecot_package[package.backend.FORMAT]
)
> "2.3"
):
ssl_protocol_parameter = "ssl_min_protocol" ssl_protocol_parameter = "ssl_min_protocol"
ssl_protocols = "!SSLv2 !SSLv3" ssl_protocols = "!SSLv2 !SSLv3"
if package.backend.get_installed_version("openssl").startswith("1.1") \ if package.backend.get_installed_version("openssl").startswith(
or package.backend.get_installed_version("openssl").startswith("3"): "1.1"
) or package.backend.get_installed_version("openssl").startswith("3"):
ssl_protocols = "!SSLv3" ssl_protocols = "!SSLv3"
if ssl_protocol_parameter == "ssl_min_protocol": if ssl_protocol_parameter == "ssl_min_protocol":
ssl_protocols = "TLSv1" ssl_protocols = "TLSv1.2"
if "centos" in utils.dist_name(): if "centos" in utils.dist_name():
protocols = "protocols = imap lmtp sieve" protocols = "protocols = imap lmtp sieve"
extra_protocols = self.config.get("dovecot", "extra_protocols") extra_protocols = self.config.get("dovecot", "extra_protocols")
@@ -77,10 +151,22 @@ class Dovecot(base.Installer):
else: else:
# Protocols are automatically guessed on debian/ubuntu # Protocols are automatically guessed on debian/ubuntu
protocols = "" protocols = ""
context.update({
oauth2_client_id, oauth2_client_secret = utils.create_oauth2_app(
"Dovecot", "dovecot", self.config
)
hostname = self.config.get("general", "hostname")
oauth2_introspection_url = (
f"https://{oauth2_client_id}:{oauth2_client_secret}"
f"@{hostname}/api/o/introspect/"
)
context.update(
{
"db_driver": self.db_driver, "db_driver": self.db_driver,
"mailboxes_owner_uid": pw[2], "mailboxes_owner_uid": pw_mailbox[2],
"mailboxes_owner_gid": pw[3], "mailboxes_owner_gid": pw_mailbox[3],
"mailbox_owner": self.mailboxes_owner,
"modoboa_user": self.config.get("modoboa", "user"), "modoboa_user": self.config.get("modoboa", "user"),
"modoboa_dbname": self.config.get("modoboa", "dbname"), "modoboa_dbname": self.config.get("modoboa", "dbname"),
"modoboa_dbuser": self.config.get("modoboa", "dbuser"), "modoboa_dbuser": self.config.get("modoboa", "dbuser"),
@@ -88,37 +174,70 @@ class Dovecot(base.Installer):
"protocols": protocols, "protocols": protocols,
"ssl_protocols": ssl_protocols, "ssl_protocols": ssl_protocols,
"ssl_protocol_parameter": ssl_protocol_parameter, "ssl_protocol_parameter": ssl_protocol_parameter,
"modoboa_2_2_or_greater": "" if self.modoboa_2_2_or_greater else "#",
"not_modoboa_2_2_or_greater": (
"" if not self.modoboa_2_2_or_greater else "#"
),
"do_move_spam_to_junk": (
"" if self.app_config["move_spam_to_junk"] else "#"
),
"oauth2_introspection_url": oauth2_introspection_url,
"radicale_user": self.config.get("radicale", "user"), "radicale_user": self.config.get("radicale", "user"),
"radicale_auth_socket_path": os.path.basename( }
self.config.get("dovecot", "radicale_auth_socket_path")) )
})
return context return context
def install_config_files(self):
"""Create sieve dir if needed."""
if self.app_config["move_spam_to_junk"]:
utils.mkdir_safe(
f"{self.config_dir}/conf.d/custom_after_sieve",
stat.S_IRWXU
| stat.S_IRGRP
| stat.S_IXGRP
| stat.S_IROTH
| stat.S_IXOTH,
0,
0,
)
super().install_config_files()
def post_run(self): def post_run(self):
"""Additional tasks.""" """Additional tasks."""
if self.dbengine == "postgres": if self.version == "2.3" and self.dbengine == "postgres":
dbname = self.config.get("modoboa", "dbname") dbname = self.config.get("modoboa", "dbname")
dbuser = self.config.get("modoboa", "dbuser") dbuser = self.config.get("modoboa", "dbuser")
dbpassword = self.config.get("modoboa", "dbpassword") dbpassword = self.config.get("modoboa", "dbpassword")
backend = database.get_backend(self.config) backend = database.get_backend(self.config)
backend.load_sql_file( backend.load_sql_file(
dbname, dbuser, dbpassword, dbname,
self.get_file_path("install_modoboa_postgres_trigger.sql") dbuser,
dbpassword,
self.get_file_path("install_modoboa_postgres_trigger.sql"),
) )
backend.load_sql_file( backend.load_sql_file(
dbname, dbuser, dbpassword, dbname,
self.get_file_path("fix_modoboa_postgres_schema.sql") dbuser,
dbpassword,
self.get_file_path("fix_modoboa_postgres_schema.sql"),
) )
for f in glob.glob("{}/*".format(self.get_file_path("conf.d"))): for f in glob.glob(f"{self.get_file_path(f'{self.version}/conf.d')}/*"):
if os.path.isfile(f):
utils.copy_file(f, "{}/conf.d".format(self.config_dir)) utils.copy_file(f, "{}/conf.d".format(self.config_dir))
# Make postlogin script executable # Make postlogin script executable
utils.exec_cmd("chmod +x /usr/local/bin/postlogin.sh") utils.exec_cmd("chmod +x /usr/local/bin/postlogin.sh")
# Only root should have read access to the 10-ssl-keys.try
# See https://github.com/modoboa/modoboa/issues/2570
utils.exec_cmd("chmod 600 /etc/dovecot/conf.d/10-ssl-keys.try")
# Add mailboxes user to dovecot group for modoboa mailbox commands. # Add mailboxes user to dovecot group for modoboa mailbox commands.
# See https://github.com/modoboa/modoboa/issues/2157. # See https://github.com/modoboa/modoboa/issues/2157.
system.add_user_to_group( if self.app_config["move_spam_to_junk"]:
self.config.get("dovecot", "mailboxes_owner"), # Compile sieve script
'dovecot' sieve_file = (
f"{self.config_dir}/conf.d/custom_after_sieve/spam-to-junk.sieve"
) )
utils.exec_cmd(f"/usr/bin/sievec {sieve_file}")
system.add_user_to_group(self.mailboxes_owner, "dovecot")
def restart_daemon(self): def restart_daemon(self):
"""Restart daemon process. """Restart daemon process.
@@ -132,7 +251,8 @@ class Dovecot(base.Installer):
action = "start" if code else "restart" action = "start" if code else "restart"
utils.exec_cmd( utils.exec_cmd(
"service {} {} > /dev/null 2>&1".format(self.appname, action), "service {} {} > /dev/null 2>&1".format(self.appname, action),
capture_output=False) capture_output=False,
)
system.enable_service(self.get_daemon_name()) system.enable_service(self.get_daemon_name())
def backup(self, path): def backup(self, path):
@@ -140,14 +260,16 @@ class Dovecot(base.Installer):
home_dir = self.config.get("dovecot", "home_dir") home_dir = self.config.get("dovecot", "home_dir")
utils.printcolor("Backing up mails", utils.MAGENTA) utils.printcolor("Backing up mails", utils.MAGENTA)
if not os.path.exists(home_dir) or os.path.isfile(home_dir): if not os.path.exists(home_dir) or os.path.isfile(home_dir):
utils.error("Error backing up emails, provided path " utils.error(
f" ({home_dir}) seems not right...") "Error backing up emails, provided path "
f" ({home_dir}) seems not right..."
)
return return
dst = os.path.join(path, "mails/") dst = os.path.join(path, "mails/")
if os.path.exists(dst): if os.path.exists(dst):
shutil.rmtree(dst) shutil.rmtree(dst)
shutil.copytree(home_dir, dst) shutil.copytree(home_dir, dst, dirs_exist_ok=True)
utils.success("Mail backup complete!") utils.success("Mail backup complete!")
def restore(self): def restore(self):
@@ -161,12 +283,15 @@ class Dovecot(base.Installer):
shutil.copytree(mail_dir, home_dir) shutil.copytree(mail_dir, home_dir)
# Resetting permission for vmail # Resetting permission for vmail
for dirpath, dirnames, filenames in os.walk(home_dir): for dirpath, dirnames, filenames in os.walk(home_dir):
shutil.chown(dirpath, self.user, self.user) shutil.chown(dirpath, self.mailboxes_owner, self.mailboxes_owner)
for filename in filenames: for filename in filenames:
shutil.chown(os.path.join(dirpath, filename), shutil.chown(
self.user, self.user) os.path.join(dirpath, filename),
self.mailboxes_owner,
self.mailboxes_owner,
)
else: else:
utils.printcolor( utils.printcolor(
"It seems that emails were not backed up, skipping restoration.", "It seems that emails were not backed up, skipping restoration.",
utils.MAGENTA utils.MAGENTA,
) )

View File

@@ -0,0 +1,17 @@
"""fail2ban related functions."""
from . import base
class Fail2ban(base.Installer):
"""Fail2ban installer."""
appname = "fail2ban"
packages = {
"deb": ["fail2ban"],
"rpm": ["fail2ban"]
}
config_files = [
"jail.d/modoboa.conf",
"filter.d/modoboa-auth.conf",
]

View File

@@ -0,0 +1,213 @@
-- Amavis 2.11.0 MySQL schema
-- Provided by Modoboa
-- Warning: foreign key creations are enabled
-- local users
CREATE TABLE users (
id int unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, -- unique id
priority integer NOT NULL DEFAULT '7', -- sort field, 0 is low prior.
policy_id integer unsigned NOT NULL DEFAULT '1', -- JOINs with policy.id
email varbinary(255) NOT NULL UNIQUE,
fullname varchar(255) DEFAULT NULL -- not used by amavisd-new
-- local char(1) -- Y/N (optional field, see note further down)
);
-- any e-mail address (non- rfc2822-quoted), external or local,
-- used as senders in wblist
CREATE TABLE mailaddr (
id int unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
priority integer NOT NULL DEFAULT '7', -- 0 is low priority
email varbinary(255) NOT NULL UNIQUE
);
-- per-recipient whitelist and/or blacklist,
-- puts sender and recipient in relation wb (white or blacklisted sender)
CREATE TABLE wblist (
rid integer unsigned NOT NULL, -- recipient: users.id
sid integer unsigned NOT NULL, -- sender: mailaddr.id
wb varchar(10) NOT NULL, -- W or Y / B or N / space=neutral / score
PRIMARY KEY (rid,sid)
);
CREATE TABLE policy (
id int unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
-- 'id' this is the _only_ required field
policy_name varchar(32), -- not used by amavisd-new, a comment
virus_lover char(1) default NULL, -- Y/N
spam_lover char(1) default NULL, -- Y/N
unchecked_lover char(1) default NULL, -- Y/N
banned_files_lover char(1) default NULL, -- Y/N
bad_header_lover char(1) default NULL, -- Y/N
bypass_virus_checks char(1) default NULL, -- Y/N
bypass_spam_checks char(1) default NULL, -- Y/N
bypass_banned_checks char(1) default NULL, -- Y/N
bypass_header_checks char(1) default NULL, -- Y/N
virus_quarantine_to varchar(64) default NULL,
spam_quarantine_to varchar(64) default NULL,
banned_quarantine_to varchar(64) default NULL,
unchecked_quarantine_to varchar(64) default NULL,
bad_header_quarantine_to varchar(64) default NULL,
clean_quarantine_to varchar(64) default NULL,
archive_quarantine_to varchar(64) default NULL,
spam_tag_level float default NULL, -- higher score inserts spam info headers
spam_tag2_level float default NULL, -- inserts 'declared spam' header fields
spam_tag3_level float default NULL, -- inserts 'blatant spam' header fields
spam_kill_level float default NULL, -- higher score triggers evasive actions
-- e.g. reject/drop, quarantine, ...
-- (subject to final_spam_destiny setting)
spam_dsn_cutoff_level float default NULL,
spam_quarantine_cutoff_level float default NULL,
addr_extension_virus varchar(64) default NULL,
addr_extension_spam varchar(64) default NULL,
addr_extension_banned varchar(64) default NULL,
addr_extension_bad_header varchar(64) default NULL,
warnvirusrecip char(1) default NULL, -- Y/N
warnbannedrecip char(1) default NULL, -- Y/N
warnbadhrecip char(1) default NULL, -- Y/N
newvirus_admin varchar(64) default NULL,
virus_admin varchar(64) default NULL,
banned_admin varchar(64) default NULL,
bad_header_admin varchar(64) default NULL,
spam_admin varchar(64) default NULL,
spam_subject_tag varchar(64) default NULL,
spam_subject_tag2 varchar(64) default NULL,
spam_subject_tag3 varchar(64) default NULL,
message_size_limit integer default NULL, -- max size in bytes, 0 disable
banned_rulenames varchar(64) default NULL, -- comma-separated list of ...
-- names mapped through %banned_rules to actual banned_filename tables
disclaimer_options varchar(64) default NULL,
forward_method varchar(64) default NULL,
sa_userconf varchar(64) default NULL,
sa_username varchar(64) default NULL
);
-- R/W part of the dataset (optional)
-- May reside in the same or in a separate database as lookups database;
-- REQUIRES SUPPORT FOR TRANSACTIONS; specified in @storage_sql_dsn
--
-- MySQL note ( http://dev.mysql.com/doc/mysql/en/storage-engines.html ):
-- ENGINE is the preferred term, but cannot be used before MySQL 4.0.18.
-- TYPE is available beginning with MySQL 3.23.0, the first version of
-- MySQL for which multiple storage engines were available. If you omit
-- the ENGINE or TYPE option, the default storage engine is used.
-- By default this is MyISAM.
--
-- Please create additional indexes on keys when needed, or drop suggested
-- ones as appropriate to optimize queries needed by a management application.
-- See your database documentation for further optimization hints. With MySQL
-- see Chapter 15 of the reference manual. For example the chapter 15.17 says:
-- InnoDB does not keep an internal count of rows in a table. To process a
-- SELECT COUNT(*) FROM T statement, InnoDB must scan an index of the table,
-- which takes some time if the index is not entirely in the buffer pool.
--
-- Wayne Smith adds: When using MySQL with InnoDB one might want to
-- increase buffer size for both pool and log, and might also want
-- to change flush settings for a little better performance. Example:
-- innodb_buffer_pool_size = 384M
-- innodb_log_buffer_size = 8M
-- innodb_flush_log_at_trx_commit = 0
-- The big performance increase is the first two, the third just helps with
-- lowering disk activity. Consider also adjusting the key_buffer_size.
-- provide unique id for each e-mail address, avoids storing copies
CREATE TABLE maddr (
partition_tag integer DEFAULT 0, -- see $partition_tag
id bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
email varbinary(255) NOT NULL, -- full mail address
domain varchar(255) NOT NULL, -- only domain part of the email address
-- with subdomain fields in reverse
CONSTRAINT part_email UNIQUE (partition_tag,email)
) ENGINE=InnoDB;
-- information pertaining to each processed message as a whole;
-- NOTE: records with NULL msgs.content should be ignored by utilities,
-- as such records correspond to messages just being processes, or were lost
-- NOTE: instead of a character field time_iso, one might prefer:
-- time_iso TIMESTAMP NOT NULL DEFAULT 0,
-- but the following MUST then be set in amavisd.conf: $timestamp_fmt_mysql=1
CREATE TABLE msgs (
partition_tag integer DEFAULT 0, -- see $partition_tag
mail_id varbinary(16) NOT NULL, -- long-term unique mail id, dflt 12 ch
secret_id varbinary(16) DEFAULT '', -- authorizes release of mail_id, 12 ch
am_id varchar(20) NOT NULL, -- id used in the log
time_num integer unsigned NOT NULL, -- rx_time: seconds since Unix epoch
time_iso char(16) NOT NULL, -- rx_time: ISO8601 UTC ascii time
sid bigint unsigned NOT NULL, -- sender: maddr.id
policy varchar(255) DEFAULT '', -- policy bank path (like macro %p)
client_addr varchar(255) DEFAULT '', -- SMTP client IP address (IPv4 or v6)
size integer unsigned NOT NULL, -- message size in bytes
originating char(1) DEFAULT ' ' NOT NULL, -- sender from inside or auth'd
content char(1), -- content type: V/B/U/S/Y/M/H/O/T/C
-- virus/banned/unchecked/spam(kill)/spammy(tag2)/
-- /bad-mime/bad-header/oversized/mta-err/clean
-- is NULL on partially processed mail
-- (prior to 2.7.0 the CC_SPAMMY was logged as 's', now 'Y' is used;
-- to avoid a need for case-insenstivity in queries)
quar_type char(1), -- quarantined as: ' '/F/Z/B/Q/M/L
-- none/file/zipfile/bsmtp/sql/
-- /mailbox(smtp)/mailbox(lmtp)
quar_loc varbinary(255) DEFAULT '', -- quarantine location (e.g. file)
dsn_sent char(1), -- was DSN sent? Y/N/q (q=quenched)
spam_level float, -- SA spam level (no boosts)
message_id varchar(255) DEFAULT '', -- mail Message-ID header field
from_addr varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT '',
-- mail From header field, UTF8
subject varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT '',
-- mail Subject header field, UTF8
host varchar(255) NOT NULL, -- hostname where amavisd is running
PRIMARY KEY (partition_tag,mail_id),
FOREIGN KEY (sid) REFERENCES maddr(id) ON DELETE RESTRICT
) ENGINE=InnoDB;
CREATE INDEX msgs_idx_sid ON msgs (sid);
CREATE INDEX msgs_idx_mess_id ON msgs (message_id); -- useful with pen pals
CREATE INDEX msgs_idx_time_num ON msgs (time_num);
-- alternatively when purging based on time_iso (instead of msgs_idx_time_num):
CREATE INDEX msgs_idx_time_iso ON msgs (time_iso);
-- When using FOREIGN KEY contraints, InnoDB requires index on a field
-- (an the field must be the first field in the index). Hence create it:
CREATE INDEX msgs_idx_mail_id ON msgs (mail_id);
-- per-recipient information related to each processed message;
-- NOTE: records in msgrcpt without corresponding msgs.mail_id record are
-- orphaned and should be ignored and eventually deleted by external utilities
CREATE TABLE msgrcpt (
partition_tag integer DEFAULT 0, -- see $partition_tag
mail_id varbinary(16) NOT NULL, -- (must allow duplicates)
rseqnum integer DEFAULT 0 NOT NULL, -- recip's enumeration within msg
rid bigint unsigned NOT NULL, -- recipient: maddr.id (dupl. allowed)
is_local char(1) DEFAULT ' ' NOT NULL, -- recip is: Y=local, N=foreign
content char(1) DEFAULT ' ' NOT NULL, -- content type V/B/U/S/Y/M/H/O/T/C
ds char(1) NOT NULL, -- delivery status: P/R/B/D/T
-- pass/reject/bounce/discard/tempfail
rs char(1) NOT NULL, -- release status: initialized to ' '
bl char(1) DEFAULT ' ', -- sender blacklisted by this recip
wl char(1) DEFAULT ' ', -- sender whitelisted by this recip
bspam_level float, -- per-recipient (total) spam level
smtp_resp varchar(255) DEFAULT '', -- SMTP response given to MTA
PRIMARY KEY (partition_tag,mail_id,rseqnum),
FOREIGN KEY (rid) REFERENCES maddr(id) ON DELETE RESTRICT,
FOREIGN KEY (mail_id) REFERENCES msgs(mail_id) ON DELETE CASCADE
) ENGINE=InnoDB;
CREATE INDEX msgrcpt_idx_mail_id ON msgrcpt (mail_id);
CREATE INDEX msgrcpt_idx_rid ON msgrcpt (rid);
-- Additional index on rs since Modoboa uses it to filter its quarantine
CREATE INDEX msgrcpt_idx_rs ON msgrcpt (rs);
-- mail quarantine in SQL, enabled by $*_quarantine_method='sql:'
-- NOTE: records in quarantine without corresponding msgs.mail_id record are
-- orphaned and should be ignored and eventually deleted by external utilities
CREATE TABLE quarantine (
partition_tag integer DEFAULT 0, -- see $partition_tag
mail_id varbinary(16) NOT NULL, -- long-term unique mail id
chunk_ind integer unsigned NOT NULL, -- chunk number, starting with 1
mail_text blob NOT NULL, -- store mail as chunks of octets
PRIMARY KEY (partition_tag,mail_id,chunk_ind),
FOREIGN KEY (mail_id) REFERENCES msgs(mail_id) ON DELETE CASCADE
) ENGINE=InnoDB;

View File

@@ -0,0 +1,189 @@
CREATE TABLE policy (
id serial PRIMARY KEY, -- 'id' is the _only_ required field
policy_name varchar(32), -- not used by amavisd-new, a comment
virus_lover char(1) default NULL, -- Y/N
spam_lover char(1) default NULL, -- Y/N
unchecked_lover char(1) default NULL, -- Y/N
banned_files_lover char(1) default NULL, -- Y/N
bad_header_lover char(1) default NULL, -- Y/N
bypass_virus_checks char(1) default NULL, -- Y/N
bypass_spam_checks char(1) default NULL, -- Y/N
bypass_banned_checks char(1) default NULL, -- Y/N
bypass_header_checks char(1) default NULL, -- Y/N
virus_quarantine_to varchar(64) default NULL,
spam_quarantine_to varchar(64) default NULL,
banned_quarantine_to varchar(64) default NULL,
unchecked_quarantine_to varchar(64) default NULL,
bad_header_quarantine_to varchar(64) default NULL,
clean_quarantine_to varchar(64) default NULL,
archive_quarantine_to varchar(64) default NULL,
spam_tag_level real default NULL, -- higher score inserts spam info headers
spam_tag2_level real default NULL, -- inserts 'declared spam' header fields
spam_tag3_level real default NULL, -- inserts 'blatant spam' header fields
spam_kill_level real default NULL, -- higher score triggers evasive actions
-- e.g. reject/drop, quarantine, ...
-- (subject to final_spam_destiny setting)
spam_dsn_cutoff_level real default NULL,
spam_quarantine_cutoff_level real default NULL,
addr_extension_virus varchar(64) default NULL,
addr_extension_spam varchar(64) default NULL,
addr_extension_banned varchar(64) default NULL,
addr_extension_bad_header varchar(64) default NULL,
warnvirusrecip char(1) default NULL, -- Y/N
warnbannedrecip char(1) default NULL, -- Y/N
warnbadhrecip char(1) default NULL, -- Y/N
newvirus_admin varchar(64) default NULL,
virus_admin varchar(64) default NULL,
banned_admin varchar(64) default NULL,
bad_header_admin varchar(64) default NULL,
spam_admin varchar(64) default NULL,
spam_subject_tag varchar(64) default NULL,
spam_subject_tag2 varchar(64) default NULL,
spam_subject_tag3 varchar(64) default NULL,
message_size_limit integer default NULL, -- max size in bytes, 0 disable
banned_rulenames varchar(64) default NULL, -- comma-separated list of ...
-- names mapped through %banned_rules to actual banned_filename tables
disclaimer_options varchar(64) default NULL,
forward_method varchar(64) default NULL,
sa_userconf varchar(64) default NULL,
sa_username varchar(64) default NULL
);
-- local users
CREATE TABLE users (
id serial PRIMARY KEY, -- unique id
priority integer NOT NULL DEFAULT 7, -- sort field, 0 is low prior.
policy_id integer NOT NULL DEFAULT 1 CHECK (policy_id >= 0) REFERENCES policy(id),
email bytea NOT NULL UNIQUE, -- email address, non-rfc2822-quoted
fullname varchar(255) DEFAULT NULL -- not used by amavisd-new
-- local char(1) -- Y/N (optional, see SQL section in README.lookups)
);
-- any e-mail address (non- rfc2822-quoted), external or local,
-- used as senders in wblist
CREATE TABLE mailaddr (
id serial PRIMARY KEY,
priority integer NOT NULL DEFAULT 9, -- 0 is low priority
email bytea NOT NULL UNIQUE
);
-- per-recipient whitelist and/or blacklist,
-- puts sender and recipient in relation wb (white or blacklisted sender)
CREATE TABLE wblist (
rid integer NOT NULL CHECK (rid >= 0) REFERENCES users(id),
sid integer NOT NULL CHECK (sid >= 0) REFERENCES mailaddr(id),
wb varchar(10) NOT NULL, -- W or Y / B or N / space=neutral / score
PRIMARY KEY (rid,sid)
);
-- grant usage rights:
GRANT select ON policy TO amavis;
GRANT select ON users TO amavis;
GRANT select ON mailaddr TO amavis;
GRANT select ON wblist TO amavis;
-- R/W part of the dataset (optional)
-- May reside in the same or in a separate database as lookups database;
-- REQUIRES SUPPORT FOR TRANSACTIONS; specified in @storage_sql_dsn
--
-- Please create additional indexes on keys when needed, or drop suggested
-- ones as appropriate to optimize queries needed by a management application.
-- See your database documentation for further optimization hints.
-- provide unique id for each e-mail address, avoids storing copies
CREATE TABLE maddr (
id serial PRIMARY KEY,
partition_tag integer DEFAULT 0, -- see $partition_tag
email bytea NOT NULL, -- full e-mail address
domain varchar(255) NOT NULL, -- only domain part of the email address
-- with subdomain fields in reverse
CONSTRAINT part_email UNIQUE (partition_tag,email)
);
-- information pertaining to each processed message as a whole;
-- NOTE: records with a NULL msgs.content should be ignored by utilities,
-- as such records correspond to messages just being processed, or were lost
CREATE TABLE msgs (
partition_tag integer DEFAULT 0, -- see $partition_tag
mail_id bytea NOT NULL, -- long-term unique mail id, dflt 12 ch
secret_id bytea DEFAULT '', -- authorizes release of mail_id, 12 ch
am_id varchar(20) NOT NULL, -- id used in the log
time_num integer NOT NULL CHECK (time_num >= 0),
-- rx_time: seconds since Unix epoch
time_iso timestamp WITH TIME ZONE NOT NULL,-- rx_time: ISO8601 UTC ascii time
sid integer NOT NULL CHECK (sid >= 0), -- sender: maddr.id
policy varchar(255) DEFAULT '', -- policy bank path (like macro %p)
client_addr varchar(255) DEFAULT '', -- SMTP client IP address (IPv4 or v6)
size integer NOT NULL CHECK (size >= 0), -- message size in bytes
originating char(1) DEFAULT ' ' NOT NULL, -- sender from inside or auth'd
content char(1), -- content type: V/B/U/S/Y/M/H/O/T/C
-- virus/banned/unchecked/spam(kill)/spammy(tag2)/
-- /bad-mime/bad-header/oversized/mta-err/clean
-- is NULL on partially processed mail
-- (prior to 2.7.0 the CC_SPAMMY was logged as 's', now 'Y' is used;
--- to avoid a need for case-insenstivity in queries)
quar_type char(1), -- quarantined as: ' '/F/Z/B/Q/M/L
-- none/file/zipfile/bsmtp/sql/
-- /mailbox(smtp)/mailbox(lmtp)
quar_loc varchar(255) DEFAULT '', -- quarantine location (e.g. file)
dsn_sent char(1), -- was DSN sent? Y/N/q (q=quenched)
spam_level real, -- SA spam level (no boosts)
message_id varchar(255) DEFAULT '', -- mail Message-ID header field
from_addr varchar(255) DEFAULT '', -- mail From header field, UTF8
subject varchar(255) DEFAULT '', -- mail Subject header field, UTF8
host varchar(255) NOT NULL, -- hostname where amavisd is running
CONSTRAINT msgs_partition_mail UNIQUE (partition_tag,mail_id),
PRIMARY KEY (partition_tag,mail_id)
--FOREIGN KEY (sid) REFERENCES maddr(id) ON DELETE RESTRICT
);
CREATE INDEX msgs_idx_sid ON msgs (sid);
CREATE INDEX msgs_idx_mess_id ON msgs (message_id); -- useful with pen pals
CREATE INDEX msgs_idx_time_iso ON msgs (time_iso);
CREATE INDEX msgs_idx_time_num ON msgs (time_num); -- optional
-- per-recipient information related to each processed message;
-- NOTE: records in msgrcpt without corresponding msgs.mail_id record are
-- orphaned and should be ignored and eventually deleted by external utilities
CREATE TABLE msgrcpt (
partition_tag integer DEFAULT 0, -- see $partition_tag
mail_id bytea NOT NULL, -- (must allow duplicates)
rseqnum integer DEFAULT 0 NOT NULL, -- recip's enumeration within msg
rid integer NOT NULL, -- recipient: maddr.id (duplicates allowed)
is_local char(1) DEFAULT ' ' NOT NULL, -- recip is: Y=local, N=foreign
content char(1) DEFAULT ' ' NOT NULL, -- content type V/B/U/S/Y/M/H/O/T/C
ds char(1) NOT NULL, -- delivery status: P/R/B/D/T
-- pass/reject/bounce/discard/tempfail
rs char(1) NOT NULL, -- release status: initialized to ' '
bl char(1) DEFAULT ' ', -- sender blacklisted by this recip
wl char(1) DEFAULT ' ', -- sender whitelisted by this recip
bspam_level real, -- per-recipient (total) spam level
smtp_resp varchar(255) DEFAULT '', -- SMTP response given to MTA
CONSTRAINT msgrcpt_partition_mail_rseq UNIQUE (partition_tag,mail_id,rseqnum),
PRIMARY KEY (partition_tag,mail_id,rseqnum)
--FOREIGN KEY (rid) REFERENCES maddr(id) ON DELETE RESTRICT,
--FOREIGN KEY (mail_id) REFERENCES msgs(mail_id) ON DELETE CASCADE
);
CREATE INDEX msgrcpt_idx_mail_id ON msgrcpt (mail_id);
CREATE INDEX msgrcpt_idx_rid ON msgrcpt (rid);
-- Additional index on rs since Modoboa uses it to filter its quarantine
CREATE INDEX msgrcpt_idx_rs ON msgrcpt (rs);
-- mail quarantine in SQL, enabled by $*_quarantine_method='sql:'
-- NOTE: records in quarantine without corresponding msgs.mail_id record are
-- orphaned and should be ignored and eventually deleted by external utilities
CREATE TABLE quarantine (
partition_tag integer DEFAULT 0, -- see $partition_tag
mail_id bytea NOT NULL, -- long-term unique mail id
chunk_ind integer NOT NULL CHECK (chunk_ind >= 0), -- chunk number, 1..
mail_text bytea NOT NULL, -- store mail as chunks of octects
PRIMARY KEY (partition_tag,mail_id,chunk_ind)
--FOREIGN KEY (mail_id) REFERENCES msgs(mail_id) ON DELETE CASCADE
);

View File

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

View File

@@ -96,7 +96,7 @@ auth_master_user_separator = *
# plain login digest-md5 cram-md5 ntlm rpa apop anonymous gssapi otp skey # plain login digest-md5 cram-md5 ntlm rpa apop anonymous gssapi otp skey
# gss-spnego # gss-spnego
# NOTE: See also disable_plaintext_auth setting. # NOTE: See also disable_plaintext_auth setting.
auth_mechanisms = plain login auth_mechanisms = plain login oauthbearer xoauth2
## ##
## Password and user databases ## Password and user databases
@@ -120,6 +120,7 @@ auth_mechanisms = plain login
#!include auth-system.conf.ext #!include auth-system.conf.ext
!include auth-sql.conf.ext !include auth-sql.conf.ext
!include auth-oauth2.conf.ext
#!include auth-ldap.conf.ext #!include auth-ldap.conf.ext
#!include auth-passwdfile.conf.ext #!include auth-passwdfile.conf.ext
#!include auth-checkpassword.conf.ext #!include auth-checkpassword.conf.ext

View File

@@ -92,14 +92,14 @@ service postlogin {
service stats { service stats {
# To allow modoboa to access available cipher list. # To allow modoboa to access available cipher list.
unix_listener stats-reader { unix_listener stats-reader {
user = vmail user = %{mailboxes_owner}
group = vmail group = %{mailboxes_owner}
mode = 0660 mode = 0660
} }
unix_listener stats-writer { unix_listener stats-writer {
user = vmail user = %{mailboxes_owner}
group = vmail group = %{mailboxes_owner}
mode = 0660 mode = 0660
} }
} }
@@ -120,7 +120,7 @@ service auth {
# permissions (e.g. 0777 allows everyone full permissions). # permissions (e.g. 0777 allows everyone full permissions).
unix_listener auth-userdb { unix_listener auth-userdb {
#mode = 0666 #mode = 0666
user = vmail user = %{mailboxes_owner}
#group = #group =
} }
@@ -131,13 +131,6 @@ service auth {
group = postfix group = postfix
} }
# Radicale auth
%{radicale_enabled}unix_listener %{radicale_auth_socket_path} {
%{radicale_enabled} mode = 0666
%{radicale_enabled} user = %{radicale_user}
%{radicale_enabled} group = %{radicale_user}
%{radicale_enabled}}
# Auth process is run as this user. # Auth process is run as this user.
#user = $default_internal_user #user = $default_internal_user
} }
@@ -154,7 +147,7 @@ service dict {
# For example: mode=0660, group=vmail and global mail_access_groups=vmail # For example: mode=0660, group=vmail and global mail_access_groups=vmail
unix_listener dict { unix_listener dict {
mode = 0600 mode = 0600
user = vmail user = %{mailboxes_owner}
#group = #group =
} }
} }

View File

@@ -38,7 +38,7 @@ plugin {
# Identical to sieve_before, only the specified scripts are executed after the # Identical to sieve_before, only the specified scripts are executed after the
# user's script (only when keep is still in effect!). Multiple script file or # user's script (only when keep is still in effect!). Multiple script file or
# directory paths can be specified by appending an increasing number. # directory paths can be specified by appending an increasing number.
#sieve_after = %{do_move_spam_to_junk}sieve_after = /etc/dovecot/conf.d/custom_after_sieve
#sieve_after2 = #sieve_after2 =
#sieve_after2 = (etc...) #sieve_after2 = (etc...)

View File

@@ -0,0 +1,5 @@
passdb {
driver = oauth2
mechanisms = xoauth2 oauthbearer
args = /etc/dovecot/conf.d/dovecot-oauth2.conf.ext
}

View File

@@ -0,0 +1,6 @@
introspection_mode = post
introspection_url = %{oauth2_introspection_url}
username_attribute = username
tls_ca_cert_file = /etc/ssl/certs/ca-certificates.crt
active_attribute = active
active_value = true

View File

@@ -123,7 +123,8 @@ connect = host=%dbhost port=%dbport dbname=%modoboa_dbname user=%modoboa_dbuser
#user_query = \ #user_query = \
# SELECT home, uid, gid \ # SELECT home, uid, gid \
# FROM users WHERE username = '%%n' AND domain = '%%d' # FROM users WHERE username = '%%n' AND domain = '%%d'
user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, CONCAT('*:bytes=', mb.quota, 'M') AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE mb.address='%%n' AND dom.name='%%d' %{not_modoboa_2_2_or_greater}user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, CONCAT('*:bytes=', mb.quota, 'M') AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE mb.address='%%n' AND dom.name='%%d'
%{modoboa_2_2_or_greater}user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, CONCAT('*:bytes=', mb.quota, 'M') AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE (mb.is_send_only=0 OR '%%s' NOT IN ('imap', 'pop3', 'lmtp')) AND mb.address='%%n' AND dom.name='%%d'
# If you wish to avoid two SQL lookups (passdb + userdb), you can use # If you wish to avoid two SQL lookups (passdb + userdb), you can use
# userdb prefetch instead of userdb sql in dovecot.conf. In that case you'll # userdb prefetch instead of userdb sql in dovecot.conf. In that case you'll
@@ -133,7 +134,8 @@ user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid,
# SELECT userid AS user, password, \ # SELECT userid AS user, password, \
# home AS userdb_home, uid AS userdb_uid, gid AS userdb_gid \ # home AS userdb_home, uid AS userdb_uid, gid AS userdb_gid \
# FROM users WHERE userid = '%%u' # FROM users WHERE userid = '%%u'
password_query = SELECT email AS user, password, '%{home_dir}/%%d/%%n' AS userdb_home, %mailboxes_owner_uid AS userdb_uid, %mailboxes_owner_gid AS userdb_gid, CONCAT('*:bytes=', mb.quota, 'M') AS userdb_quota_rule FROM core_user u INNER JOIN admin_mailbox mb ON u.id=mb.user_id INNER JOIN admin_domain dom ON mb.domain_id=dom.id WHERE u.email='%%u' AND u.is_active=1 AND dom.enabled=1 %{not_modoboa_2_2_or_greater}password_query = SELECT email AS user, password, '%{home_dir}/%%d/%%n' AS userdb_home, %mailboxes_owner_uid AS userdb_uid, %mailboxes_owner_gid AS userdb_gid, CONCAT('*:bytes=', mb.quota, 'M') AS userdb_quota_rule FROM core_user u INNER JOIN admin_mailbox mb ON u.id=mb.user_id INNER JOIN admin_domain dom ON mb.domain_id=dom.id WHERE u.email='%%u' AND u.is_active=1 AND dom.enabled=1
%{modoboa_2_2_or_greater}password_query = SELECT email AS user, password, '%{home_dir}/%%d/%%n' AS userdb_home, %mailboxes_owner_uid AS userdb_uid, %mailboxes_owner_gid AS userdb_gid, CONCAT('*:bytes=', mb.quota, 'M') AS userdb_quota_rule FROM core_user u INNER JOIN admin_mailbox mb ON u.id=mb.user_id INNER JOIN admin_domain dom ON mb.domain_id=dom.id WHERE (mb.is_send_only=0 OR '%%s' NOT IN ('imap', 'pop3')) AND u.email='%%u' AND u.is_active=1 AND dom.enabled=1
# Query to get a list of all usernames. # Query to get a list of all usernames.
#iterate_query = SELECT username AS user FROM users #iterate_query = SELECT username AS user FROM users

View File

@@ -123,7 +123,8 @@ connect = host=%dbhost port=%dbport dbname=%modoboa_dbname user=%modoboa_dbuser
#user_query = \ #user_query = \
# SELECT home, uid, gid \ # SELECT home, uid, gid \
# FROM users WHERE username = '%%n' AND domain = '%%d' # FROM users WHERE username = '%%n' AND domain = '%%d'
user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, '*:bytes=' || mb.quota || 'M' AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE mb.address='%%n' AND dom.name='%%d' %{not_modoboa_2_2_or_greater}user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, '*:bytes=' || mb.quota || 'M' AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE mb.address='%%n' AND dom.name='%%d'
%{modoboa_2_2_or_greater}user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid, %mailboxes_owner_gid as gid, '*:bytes=' || mb.quota || 'M' AS quota_rule FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON u.id=mb.user_id WHERE (mb.is_send_only IS NOT TRUE OR '%%s' NOT IN ('imap', 'pop3', 'lmtp')) AND mb.address='%%n' AND dom.name='%%d'
# If you wish to avoid two SQL lookups (passdb + userdb), you can use # If you wish to avoid two SQL lookups (passdb + userdb), you can use
# userdb prefetch instead of userdb sql in dovecot.conf. In that case you'll # userdb prefetch instead of userdb sql in dovecot.conf. In that case you'll
@@ -133,7 +134,8 @@ user_query = SELECT '%{home_dir}/%%d/%%n' AS home, %mailboxes_owner_uid as uid,
# SELECT userid AS user, password, \ # SELECT userid AS user, password, \
# home AS userdb_home, uid AS userdb_uid, gid AS userdb_gid \ # home AS userdb_home, uid AS userdb_uid, gid AS userdb_gid \
# FROM users WHERE userid = '%%u' # FROM users WHERE userid = '%%u'
password_query = SELECT email AS user, password, '%{home_dir}/%%d/%%n' AS userdb_home, %mailboxes_owner_uid AS userdb_uid, %mailboxes_owner_gid AS userdb_gid, CONCAT('*:bytes=', mb.quota, 'M') AS userdb_quota_rule FROM core_user u INNER JOIN admin_mailbox mb ON u.id=mb.user_id INNER JOIN admin_domain dom ON mb.domain_id=dom.id WHERE email='%%u' AND is_active AND dom.enabled %{not_modoboa_2_2_or_greater}password_query = SELECT email AS user, password, '%{home_dir}/%%d/%%n' AS userdb_home, %mailboxes_owner_uid AS userdb_uid, %mailboxes_owner_gid AS userdb_gid, CONCAT('*:bytes=', mb.quota, 'M') AS userdb_quota_rule FROM core_user u INNER JOIN admin_mailbox mb ON u.id=mb.user_id INNER JOIN admin_domain dom ON mb.domain_id=dom.id WHERE email='%%u' AND is_active AND dom.enabled
%{modoboa_2_2_or_greater}password_query = SELECT email AS user, password, '%{home_dir}/%%d/%%n' AS userdb_home, %mailboxes_owner_uid AS userdb_uid, %mailboxes_owner_gid AS userdb_gid, CONCAT('*:bytes=', mb.quota, 'M') AS userdb_quota_rule FROM core_user u INNER JOIN admin_mailbox mb ON u.id=mb.user_id INNER JOIN admin_domain dom ON mb.domain_id=dom.id WHERE (mb.is_send_only IS NOT TRUE OR '%%s' NOT IN ('imap', 'pop3')) AND email='%%u' AND is_active AND dom.enabled
# Query to get a list of all usernames. # Query to get a list of all usernames.
#iterate_query = SELECT username AS user FROM users #iterate_query = SELECT username AS user FROM users

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
require "fileinto";
if header :contains "X-Spam-Status" "Yes" {
fileinto "Junk";
}

View File

@@ -0,0 +1,9 @@
# Fail2Ban filter Modoboa authentication
[INCLUDES]
before = common.conf
[Definition]
failregex = modoboa\.auth: WARNING Failed connection attempt from \'<HOST>\' as user \'.*?\'$

View File

@@ -0,0 +1,9 @@
[modoboa]
enabled = true
port = http,https
protocol = tcp
filter = modoboa-auth
maxretry = %max_retry
bantime = %ban_time
findtime = %find_time
logpath = /var/log/auth.log

View File

@@ -3,6 +3,7 @@
# #
PYTHON=%{venv_path}/bin/python PYTHON=%{venv_path}/bin/python
INSTANCE=%{instance_path} INSTANCE=%{instance_path}
MAILTO=%{cron_error_recipient}
# Operations on mailboxes # Operations on mailboxes
%{dovecot_enabled}* * * * * %{dovecot_mailboxes_owner} $PYTHON $INSTANCE/manage.py handle_mailbox_operations %{dovecot_enabled}* * * * * %{dovecot_mailboxes_owner} $PYTHON $INSTANCE/manage.py handle_mailbox_operations
@@ -33,4 +34,4 @@ INSTANCE=%{instance_path}
%{minutes} %{hours} * * * root $PYTHON $INSTANCE/manage.py communicate_with_public_api %{minutes} %{hours} * * * root $PYTHON $INSTANCE/manage.py communicate_with_public_api
# Generate DKIM keys (they will belong to the user running this job) # Generate DKIM keys (they will belong to the user running this job)
%{opendkim_enabled}* * * * * %{opendkim_user} umask 077 && $PYTHON $INSTANCE/manage.py modo manage_dkim_keys %{dkim_cron_enabled}* * * * * %{opendkim_user} umask 077 && $PYTHON $INSTANCE/manage.py modo manage_dkim_keys

View File

@@ -0,0 +1,9 @@
[program:modoboa-base-worker]
autostart=true
autorestart=true
command=%{venv_path}/bin/python %{home_dir}/instance/manage.py rqworker modoboa
directory=%{home_dir}
user=%{user}
redirect_stderr=true
numprocs=1
stopsignal=TERM

View File

@@ -0,0 +1,9 @@
[program:modoboa-dkim-worker]
autostart=true
autorestart=true
command=%{venv_path}/bin/python %{home_dir}/instance/manage.py rqworker dkim
directory=%{home_dir}
user=%{dkim_user}
redirect_stderr=true
numprocs=1
stopsignal=TERM

View File

@@ -6,3 +6,4 @@ directory=%{home_dir}
redirect_stderr=true redirect_stderr=true
user=%{user} user=%{user}
numprocs=1 numprocs=1

View File

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

View File

@@ -10,8 +10,8 @@ server {
} }
server { server {
listen 443 ssl; listen 443 ssl http2;
listen [::]:443 ssl; listen [::]:443 ssl http2;
server_name %hostname; server_name %hostname;
root %app_instance_path; root %app_instance_path;
@@ -37,7 +37,20 @@ server {
try_files $uri $uri/ =404; try_files $uri $uri/ =404;
} }
location ^~ /new-admin { %{rspamd_enabled} location /rspamd/ {
%{rspamd_enabled} proxy_pass http://localhost:11334/;
%{rspamd_enabled}
%{rspamd_enabled} proxy_set_header Host $host;
%{rspamd_enabled} proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
%{rspamd_enabled} }
location ~ ^/(api|accounts) {
include uwsgi_params;
uwsgi_param UWSGI_SCRIPT instance.wsgi:application;
uwsgi_pass modoboa;
}
location / {
alias %{app_instance_path}/frontend/; alias %{app_instance_path}/frontend/;
index index.html; index index.html;
@@ -48,10 +61,5 @@ server {
try_files $uri $uri/ /index.html = 404; try_files $uri $uri/ /index.html = 404;
} }
location / {
include uwsgi_params;
uwsgi_param UWSGI_SCRIPT instance.wsgi:application;
uwsgi_pass modoboa;
}
%{extra_config} %{extra_config}
} }

View File

@@ -0,0 +1,11 @@
if /^\s*Received:.*Authenticated sender.*\(Postfix\)/
/^Received: from .*? \([\w\-.]* \[.*?\]\)(.*|\n.*)\(Authenticated sender: (.+)\)\s+by.+\(Postfix\) with (.*)/
REPLACE Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with $3
endif
if /^\s*Received: from .*rspamd.localhost .*\(Postfix\)/
/^Received: from.* (.*|\n.*)\((.+) (.+)\)\s+by (.+) \(Postfix\) with (.*)/
REPLACE Received: from rspamd (rspamd $3) by $4 (Postfix) with $5
endif
/^\s*X-Enigmail/ IGNORE
/^\s*X-Originating-IP/ IGNORE
/^\s*X-Forward/ IGNORE

View File

@@ -41,22 +41,29 @@ smtpd_tls_auth_only = no
smtpd_tls_CApath = /etc/ssl/certs smtpd_tls_CApath = /etc/ssl/certs
smtpd_tls_key_file = %tls_key_file smtpd_tls_key_file = %tls_key_file
smtpd_tls_cert_file = %tls_cert_file smtpd_tls_cert_file = %tls_cert_file
smtpd_tls_dh1024_param_file = ${config_directory}/dh2048.pem smtpd_tls_dh1024_param_file = ${config_directory}/ffdhe%{dhe_group}.pem
smtpd_tls_loglevel = 1 smtpd_tls_loglevel = 1
smtpd_tls_session_cache_database = btree:$data_directory/smtpd_tls_session_cache smtpd_tls_session_cache_database = btree:$data_directory/smtpd_tls_session_cache
smtpd_tls_security_level = may smtpd_tls_security_level = may
smtpd_tls_received_header = yes smtpd_tls_received_header = yes
# Disallow SSLv2 and SSLv3, only accept secure ciphers # Disallow SSLv2 and SSLv3, only accept secure ciphers
smtpd_tls_protocols = !SSLv2, !SSLv3 smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3 smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_mandatory_ciphers = high smtpd_tls_mandatory_ciphers = high
smtpd_tls_mandatory_exclude_ciphers = aNULL, MD5 , DES, ADH, RC4, PSD, SRP, 3DES, eNULL smtpd_tls_mandatory_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA, KRB5-DES, CBC3-SHA, CAMELLIA, SEED-SHA, AES256-SHA, AES256-SHA256, AES256-GCM-SHA384, AES128-SHA, AES128-SHA256, AES128-GCM-SHA256, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA, DHE-RSA-AES256-SHA256, DHE-RSA-CHACHA20-POLY1305, ECDHE-RSA-AES128-SHA, ECDHE-RSA-AES256-SHA
smtpd_tls_exclude_ciphers = aNULL, MD5 , DES, ADH, RC4, PSD, SRP, 3DES, eNULL smtpd_tls_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA, KRB5-DES, CBC3-SHA, CAMELLIA, SEED-SHA, AES256-SHA, AES256-SHA256, AES256-GCM-SHA384, AES128-SHA, AES128-SHA256, AES128-GCM-SHA256, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES128-SHA, DHE-RSA-AES128-SHA256, DHE-RSA-AES256-GCM-SHA384, DHE-RSA-AES256-SHA, DHE-RSA-AES256-SHA256, DHE-RSA-CHACHA20-POLY1305, ECDHE-RSA-AES128-SHA, ECDHE-RSA-AES256-SHA
tls_preempt_cipherlist = yes
tls_ssl_options = NO_COMPRESSION
# Enable elliptic curve cryptography # Enable elliptic curve cryptography
smtpd_tls_eecdh_grade = strong smtpd_tls_eecdh_grade = strong
# SMTP Smuggling prevention
# See https://www.postfix.org/smtp-smuggling.html
smtpd_data_restrictions = reject_unauth_pipelining
smtpd_forbid_unauth_pipelining = yes
# Use TLS if this is supported by the remote SMTP server, otherwise use plaintext. # Use TLS if this is supported by the remote SMTP server, otherwise use plaintext.
smtp_tls_CApath = /etc/ssl/certs smtp_tls_CApath = /etc/ssl/certs
smtp_tls_security_level = may smtp_tls_security_level = may
@@ -67,10 +74,10 @@ smtp_tls_exclude_ciphers = EXPORT, LOW
# #
%{dovecot_enabled}virtual_transport = lmtp:unix:private/dovecot-lmtp %{dovecot_enabled}virtual_transport = lmtp:unix:private/dovecot-lmtp
virtual_mailbox_domains = proxy:%{db_driver}:/etc/postfix/sql-domains.cf %{dovecot_enabled}virtual_mailbox_domains = proxy:%{db_driver}:/etc/postfix/sql-domains.cf
virtual_alias_domains = proxy:%{db_driver}:/etc/postfix/sql-domain-aliases.cf %{dovecot_enabled}virtual_alias_domains = proxy:%{db_driver}:/etc/postfix/sql-domain-aliases.cf
virtual_alias_maps = %{dovecot_enabled}virtual_alias_maps =
proxy:%{db_driver}:/etc/postfix/sql-aliases.cf %{dovecot_enabled} proxy:%{db_driver}:/etc/postfix/sql-aliases.cf
## Relay domains ## Relay domains
# #
@@ -115,10 +122,19 @@ strict_rfc821_envelopes = yes
%{opendkim_enabled}milter_default_action = accept %{opendkim_enabled}milter_default_action = accept
%{opendkim_enabled}milter_content_timeout = 30s %{opendkim_enabled}milter_content_timeout = 30s
# Rspamd setup
%{rspamd_enabled}smtpd_milters = inet:localhost:11332
%{rspamd_enabled}non_smtpd_milters = inet:localhost:11332
%{rspamd_enabled}milter_default_action = accept
%{rspamd_enabled}milter_protocol = 6
# List of authorized senders # List of authorized senders
smtpd_sender_login_maps = smtpd_sender_login_maps =
proxy:%{db_driver}:/etc/postfix/sql-sender-login-map.cf proxy:%{db_driver}:/etc/postfix/sql-sender-login-map.cf
# Add authenticated header to hide public client IP
smtpd_sasl_authenticated_header = yes
# Recipient restriction rules # Recipient restriction rules
smtpd_recipient_restrictions = smtpd_recipient_restrictions =
check_policy_service inet:127.0.0.1:9999 check_policy_service inet:127.0.0.1:9999
@@ -135,28 +151,27 @@ smtpd_recipient_restrictions =
## Postcreen settings ## Postcreen settings
# #
postscreen_access_list = %{rspamd_disabled}postscreen_access_list =
permit_mynetworks %{rspamd_disabled} permit_mynetworks
cidr:/etc/postfix/postscreen_spf_whitelist.cidr %{rspamd_disabled} cidr:/etc/postfix/postscreen_spf_whitelist.cidr
postscreen_blacklist_action = enforce %{rspamd_disabled}postscreen_blacklist_action = enforce
# Use some DNSBL # Use some DNSBL
postscreen_dnsbl_sites = %{rspamd_disabled}postscreen_dnsbl_sites =
zen.spamhaus.org=127.0.0.[2..11]*3 %{rspamd_disabled} zen.spamhaus.org=127.0.0.[2..11]*3
bl.spameatingmonkey.net=127.0.0.2*2 %{rspamd_disabled} bl.spameatingmonkey.net=127.0.0.2*2
bl.spamcop.net=127.0.0.2 %{rspamd_disabled} bl.spamcop.net=127.0.0.2
dnsbl.sorbs.net=127.0.0.[2..15] %{rspamd_disabled}postscreen_dnsbl_threshold = 3
postscreen_dnsbl_threshold = 3 %{rspamd_disabled}postscreen_dnsbl_action = enforce
postscreen_dnsbl_action = enforce
postscreen_greet_banner = Welcome, please wait... %{rspamd_disabled}postscreen_greet_banner = Welcome, please wait...
postscreen_greet_action = enforce %{rspamd_disabled}postscreen_greet_action = enforce
postscreen_pipelining_enable = yes %{rspamd_disabled}postscreen_pipelining_enable = yes
postscreen_pipelining_action = enforce %{rspamd_disabled}postscreen_pipelining_action = enforce
postscreen_non_smtp_command_enable = yes %{rspamd_disabled}postscreen_non_smtp_command_enable = yes
postscreen_non_smtp_command_action = enforce %{rspamd_disabled}postscreen_non_smtp_command_action = enforce
postscreen_bare_newline_enable = yes %{rspamd_disabled}postscreen_bare_newline_enable = yes
postscreen_bare_newline_action = enforce %{rspamd_disabled}postscreen_bare_newline_action = enforce

View File

@@ -9,7 +9,8 @@
# service type private unpriv chroot wakeup maxproc command + args # service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (yes) (never) (100) # (yes) (yes) (yes) (never) (100)
# ========================================================================== # ==========================================================================
smtp inet n - - - 1 postscreen %{rspamd_disabled}smtp inet n - - - 1 postscreen
%{rspamd_enabled}smtp inet n - - - - smtpd
smtpd pass - - - - - smtpd smtpd pass - - - - - smtpd
%{amavis_enabled} -o smtpd_proxy_filter=inet:[127.0.0.1]:10024 %{amavis_enabled} -o smtpd_proxy_filter=inet:[127.0.0.1]:10024
%{amavis_enabled} -o smtpd_proxy_options=speed_adjust %{amavis_enabled} -o smtpd_proxy_options=speed_adjust
@@ -26,6 +27,7 @@ submission inet n - - - - smtpd
-o smtpd_helo_restrictions= -o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=reject_sender_login_mismatch -o smtpd_sender_restrictions=reject_sender_login_mismatch
-o milter_macro_daemon_name=ORIGINATING -o milter_macro_daemon_name=ORIGINATING
-o cleanup_service_name=ascleanup
%{amavis_enabled} -o smtpd_proxy_filter=inet:[127.0.0.1]:10026 %{amavis_enabled} -o smtpd_proxy_filter=inet:[127.0.0.1]:10026
#smtps inet n - - - - smtpd #smtps inet n - - - - smtpd
# -o syslog_name=postfix/smtps # -o syslog_name=postfix/smtps
@@ -41,6 +43,8 @@ submission inet n - - - - smtpd
#628 inet n - - - - qmqpd #628 inet n - - - - qmqpd
pickup unix n - - 60 1 pickup pickup unix n - - 60 1 pickup
cleanup unix n - - - 0 cleanup cleanup unix n - - - 0 cleanup
ascleanup unix n - - - 0 cleanup
-o header_checks=pcre:/etc/postfix/anonymize_headers.pcre
qmgr unix n - n 300 1 qmgr qmgr unix n - n 300 1 qmgr
#qmgr unix n - n 300 1 oqmgr #qmgr unix n - n 300 1 oqmgr
tlsmgr unix - - - 1000? 1 tlsmgr tlsmgr unix - - - 1000? 1 tlsmgr
@@ -78,7 +82,7 @@ scache unix - - - - 1 scache
# Also specify in main.cf: maildrop_destination_recipient_limit=1 # Also specify in main.cf: maildrop_destination_recipient_limit=1
# #
maildrop unix - n n - - pipe maildrop unix - n n - - pipe
flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient} flags=DRhu user=%{dovecot_mailboxes_owner} argv=/usr/bin/maildrop -d ${recipient}
# #
# ==================================================================== # ====================================================================
# #
@@ -124,11 +128,6 @@ mailman unix - n n - - pipe
flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
${nexthop} ${user} ${nexthop} ${user}
# Modoboa autoreply service
#
autoreply unix - n n - - pipe
flags= user=%{dovecot_mailboxes_owner}:%{dovecot_mailboxes_owner} argv=%{modoboa_venv_path}/bin/python %{modoboa_instance_path}/manage.py autoreply $sender $mailbox
# Amavis return path # Amavis return path
# #
%{amavis_enabled}127.0.0.1:10025 inet n - n - - smtpd %{amavis_enabled}127.0.0.1:10025 inet n - n - - smtpd
@@ -149,4 +148,4 @@ autoreply unix - n n - - pipe
%{amavis_enabled} -o smtpd_client_connection_count_limit=0 %{amavis_enabled} -o smtpd_client_connection_count_limit=0
%{amavis_enabled} -o smtpd_client_connection_rate_limit=0 %{amavis_enabled} -o smtpd_client_connection_rate_limit=0
%{amavis_enabled} -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks %{amavis_enabled} -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks
%{amavis_enabled} -o local_header_rewrite_clients= %{amavis_enabled} -o local_header_rewrite_clients=permit_mynetworks,permit_sasl_authenticated

View File

@@ -71,7 +71,7 @@
# Authentication method # Authentication method
# Value: none | htpasswd | remote_user | http_x_remote_user # Value: none | htpasswd | remote_user | http_x_remote_user
type = radicale_dovecot_auth type = radicale_modoboa_auth_oauth2
# Htpasswd filename # Htpasswd filename
# htpasswd_filename = users # htpasswd_filename = users
@@ -85,7 +85,7 @@ type = radicale_dovecot_auth
# Incorrect authentication delay (seconds) # Incorrect authentication delay (seconds)
#delay = 1 #delay = 1
auth_socket = %{auth_socket_path} oauth2_introspection_endpoint = %{oauth2_introspection_url}
[rights] [rights]
@@ -102,8 +102,6 @@ file = %{config_dir}/rights
# Storage backend # Storage backend
# Value: multifilesystem # Value: multifilesystem
type = radicale_storage_by_index
radicale_storage_by_index_fields = dtstart, dtend, uid, summary
# Folder for storing local collections, created if not present # Folder for storing local collections, created if not present
filesystem_folder = %{home_dir}/collections filesystem_folder = %{home_dir}/collections

View File

@@ -0,0 +1,14 @@
clamav {
scan_mime_parts = true;
scan_text_mime = true;
scan_image_mime = true;
retransmits = 2;
timeout = 30;
symbol = "CLAM_VIRUS";
type = "clamav";
servers = "127.0.0.1:3310"
patterns {
# symbol_name = "pattern";
JUST_EICAR = "Test.EICAR";
}
}

View File

@@ -0,0 +1,3 @@
try_fallback = false;
selector_map = "%selector_map_path";
path_map = "%key_map_path";

View File

@@ -0,0 +1,3 @@
try_fallback = false;
selector_map = "%selector_map_path";
path_map = "%key_map_path";

View File

@@ -0,0 +1,21 @@
reporting {
# Required attributes
enabled = true; # Enable reports in general
email = 'postmaster@%hostname'; # Source of DMARC reports
domain = '%hostname'; # Domain to serve
org_name = '%hostname'; # Organisation
# Optional parameters
#bcc_addrs = ["postmaster@example.com"]; # additional addresses to copy on reports
report_local_controller = false; # Store reports for local/controller scans (for testing only)
#helo = 'rspamd.localhost'; # Helo used in SMTP dialog
#smtp = '127.0.0.1'; # SMTP server IP
#smtp_port = 25; # SMTP server port
from_name = '%hostname DMARC REPORT'; # SMTP FROM
msgid_from = 'rspamd'; # Msgid format
#max_entries = 1k; # Maxiumum amount of entries per domain
#keys_expire = 2d; # Expire date for Redis keys
#only_domains = '/path/to/map'; # Only store reports from domains or eSLDs listed in this map
# Available from 3.3
#exclude_domains = '/path/to/map'; # Exclude reports from domains or eSLDs listed in this map
#exclude_domains = ["example.com", "another.com"]; # Alternative, use array to exclude reports from domains or eSLDs
}

View File

@@ -0,0 +1,5 @@
rules {
DMARC_POLICY_QUARANTINE {
action = "add header";
}
}

View File

@@ -0,0 +1,2 @@
%{greylisting_disabled}enabled = false;
servers = "127.0.0.1:6379";

View File

@@ -0,0 +1,5 @@
symbols {
"WHITELIST_AUTHENTICATED" {
weight = %whitelist_auth_weigth;
}
}

View File

@@ -0,0 +1,20 @@
actions {
reject = 15; # normal value is 15, 150 so it will never be rejected
add_header = 6; # set to 0.1 for testing, 6 for normal operation.
rewrite_subject = 8; # Default: 8
greylist = 4; # Default: 4
}
group "antivirus" {
symbol "JUST_EICAR" {
weight = 10;
description = "Eicar test signature";
}
symbol "CLAM_VIRUS_FAIL" {
weight = 0;
}
symbol "CLAM_VIRUS" {
weight = 10;
description = "ClamAV found a Virus";
}
}

View File

@@ -0,0 +1,16 @@
use = ["x-spam-status","x-virus","authentication-results" ];
extended_spam_headers = false;
skip_local = false;
skip_authenticated = false;
routines {
x-virus {
header = "X-Virus";
remove = 1;
symbols = ["CLAM_VIRUS", "JUST_EICAR"];
}
}

View File

@@ -0,0 +1 @@
enabled = true;

View File

@@ -0,0 +1,6 @@
# to disable all predefined rules if the user doesn't want dnsbl
url_whitelist = [];
rbls {
}

View File

@@ -0,0 +1,2 @@
write_servers = "localhost";
read_servers = "localhost";

View File

@@ -0,0 +1,8 @@
authenticated {
priority = high;
authenticated = yes;
apply {
groups_disabled = ["rbl", "spf"];
}
%{whitelist_auth_enabled} symbols ["WHITELIST_AUTHENTICATED"];
}

View File

@@ -0,0 +1,6 @@
spf_cache_size = 1k;
spf_cache_expire = 1d;
max_dns_nesting = 10;
max_dns_requests = 30;
min_cache_ttl = 5m;
disable_ipv6 = false;

View File

@@ -0,0 +1 @@
enable_password = %controller_password

View File

@@ -0,0 +1 @@
enabled = false;

View File

@@ -0,0 +1,3 @@
upstream "local" {
self_scan = yes;
}

View File

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

View File

@@ -13,3 +13,5 @@ socket = %uwsgi_socket_path
chmod-socket = 660 chmod-socket = 660
vacuum = true vacuum = true
single-interpreter = True single-interpreter = True
max-requests = 5000
buffer-size = 8192

View File

@@ -26,7 +26,8 @@ class Modoboa(base.Installer):
"deb": [ "deb": [
"build-essential", "python3-dev", "libxml2-dev", "libxslt-dev", "build-essential", "python3-dev", "libxml2-dev", "libxslt-dev",
"libjpeg-dev", "librrd-dev", "rrdtool", "libffi-dev", "cron", "libjpeg-dev", "librrd-dev", "rrdtool", "libffi-dev", "cron",
"libssl-dev", "redis-server", "supervisor" "libssl-dev", "redis-server", "supervisor", "pkg-config",
"libcairo2-dev", "libmagic-dev"
], ],
"rpm": [ "rpm": [
"gcc", "gcc-c++", "python3-devel", "libxml2-devel", "libxslt-devel", "gcc", "gcc-c++", "python3-devel", "libxml2-devel", "libxslt-devel",
@@ -43,80 +44,74 @@ class Modoboa(base.Installer):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Get configuration.""" """Get configuration."""
super(Modoboa, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.venv_path = self.config.get("modoboa", "venv_path") self.venv_path = self.config.get("modoboa", "venv_path")
self.instance_path = self.config.get("modoboa", "instance_path") self.instance_path = self.config.get("modoboa", "instance_path")
self.extensions = self.config.get("modoboa", "extensions").split() self.extensions = self.config.get("modoboa", "extensions").split()
self.devmode = self.config.getboolean("modoboa", "devmode") self.devmode = self.config.getboolean("modoboa", "devmode")
# Sanity check for amavis self.amavis_enabled = self.config.getboolean("amavis", "enabled")
self.amavis_enabled = False self.rspamd_enabled = self.config.getboolean("rspamd", "enabled")
if "modoboa-amavis" in self.extensions:
if self.config.getboolean("amavis", "enabled"):
self.amavis_enabled = True
else:
self.extensions.remove("modoboa-amavis")
if "modoboa-radicale" in self.extensions:
if not self.config.getboolean("radicale", "enabled"):
self.extensions.remove("modoboa-radicale")
self.dovecot_enabled = self.config.getboolean("dovecot", "enabled") self.dovecot_enabled = self.config.getboolean("dovecot", "enabled")
self.opendkim_enabled = self.config.getboolean("opendkim", "enabled") self.opendkim_enabled = self.config.getboolean("opendkim", "enabled")
self.dkim_cron_enabled = False
def is_extension_ok_for_version(self, extension, version): def is_extension_ok_for_version(self, extension, version):
"""Check if extension can be installed with this modo version.""" """Check if extension can be installed with this modo version."""
if extension not in compatibility_matrix.EXTENSIONS_AVAILABILITY:
return True
version = utils.convert_version_to_int(version) version = utils.convert_version_to_int(version)
if extension in compatibility_matrix.EXTENSIONS_AVAILABILITY:
min_version = compatibility_matrix.EXTENSIONS_AVAILABILITY[extension] min_version = compatibility_matrix.EXTENSIONS_AVAILABILITY[extension]
min_version = utils.convert_version_to_int(min_version) min_version = utils.convert_version_to_int(min_version)
return version >= min_version return version >= min_version
if extension in compatibility_matrix.REMOVED_EXTENSIONS:
max_version = compatibility_matrix.REMOVED_EXTENSIONS[extension]
max_version = utils.convert_version_to_int(max_version)
return version < max_version
return True
def _setup_venv(self): def _setup_venv(self):
"""Prepare a dedicated virtualenv.""" """Prepare a dedicated virtualenv."""
python.setup_virtualenv( python.setup_virtualenv(self.venv_path, sudo_user=self.user)
self.venv_path, sudo_user=self.user, python_version=3) packages = []
packages = ["rrdtool"]
version = self.config.get("modoboa", "version") version = self.config.get("modoboa", "version")
extras = "postgresql"
if self.dbengine != "postgres":
extras = "mysql"
if self.devmode:
extras += ",dev"
if version == "latest": if version == "latest":
packages += ["modoboa"] + self.extensions packages += [f"modoboa[{extras}]"] + self.extensions
for extension in list(self.extensions):
if extension in compatibility_matrix.REMOVED_EXTENSIONS.keys():
self.extensions.remove(extension)
self.extensions = [
extension for extension in self.extensions
if extension not in compatibility_matrix.REMOVED_EXTENSIONS
]
else: else:
matrix = compatibility_matrix.COMPATIBILITY_MATRIX[version] matrix = compatibility_matrix.COMPATIBILITY_MATRIX[version]
packages.append("modoboa=={}".format(version)) packages.append(f"modoboa[{extras}]=={version}")
for extension in list(self.extensions): for extension in list(self.extensions):
if not self.is_extension_ok_for_version(extension, version): if not self.is_extension_ok_for_version(extension, version):
self.extensions.remove(extension) self.extensions.remove(extension)
continue continue
if extension in matrix: if extension in matrix:
req_version = matrix[extension] req_version = matrix[extension]
req_version = req_version.replace("<", "\<") if req_version is None:
req_version = req_version.replace(">", "\>") continue
req_version = req_version.replace("<", "\\<")
req_version = req_version.replace(">", "\\>")
packages.append("{}{}".format(extension, req_version)) packages.append("{}{}".format(extension, req_version))
else: else:
packages.append(extension) packages.append(extension)
# Temp fix for django-braces
python.install_package(
"django-braces", self.venv_path, upgrade=self.upgrade,
sudo_user=self.user
)
if self.dbengine == "postgres":
packages.append("psycopg2-binary\<2.9")
else:
packages.append("mysqlclient")
if sys.version_info.major == 2 and sys.version_info.micro < 9: if sys.version_info.major == 2 and sys.version_info.micro < 9:
# Add extra packages to fix the SNI issue # Add extra packages to fix the SNI issue
packages += ["pyOpenSSL"] packages += ["pyOpenSSL"]
# Temp fix for https://github.com/modoboa/modoboa/issues/2247
packages.append("django-webpack-loader==0.7.0")
python.install_packages( python.install_packages(
packages, self.venv_path, packages, self.venv_path,
upgrade=self.upgrade, upgrade=self.upgrade,
sudo_user=self.user, sudo_user=self.user,
beta=self.config.getboolean("modoboa", "install_beta") beta=self.config.getboolean("modoboa", "install_beta")
) )
if self.devmode:
# FIXME: use dev-requirements instead
python.install_packages(
["django-bower", "django-debug-toolbar"], self.venv_path,
upgrade=self.upgrade, sudo_user=self.user)
def _deploy_instance(self): def _deploy_instance(self):
"""Deploy Modoboa.""" """Deploy Modoboa."""
@@ -140,6 +135,10 @@ class Modoboa(base.Installer):
prefix = ". {}; ".format( prefix = ". {}; ".format(
os.path.join(self.venv_path, "bin", "activate")) os.path.join(self.venv_path, "bin", "activate"))
if self.amavis_enabled:
self.extensions += ["modoboa.amavis"]
if self.rspamd_enabled:
self.extensions += ["modoboa.rspamd"]
args = [ args = [
"--collectstatic", "--collectstatic",
"--timezone", self.config.get("modoboa", "timezone"), "--timezone", self.config.get("modoboa", "timezone"),
@@ -177,7 +176,7 @@ class Modoboa(base.Installer):
if self.upgrade and self.opendkim_enabled and self.dbengine == "postgres": if self.upgrade and self.opendkim_enabled and self.dbengine == "postgres":
# Restore view previously deleted # Restore view previously deleted
self.backend.load_sql_file( self.backend.load_sql_file(
self.dbname, self.dbuser, self.dbpassword, self.dbname, self.dbuser, self.dbpasswd,
self.get_file_path("dkim_view_{}.sql".format(self.dbengine)) self.get_file_path("dkim_view_{}.sql".format(self.dbengine))
) )
self.backend.grant_right_on_table( self.backend.grant_right_on_table(
@@ -187,7 +186,7 @@ class Modoboa(base.Installer):
def setup_database(self): def setup_database(self):
"""Additional config.""" """Additional config."""
super(Modoboa, self).setup_database() super().setup_database()
if not self.amavis_enabled: if not self.amavis_enabled:
return return
self.backend.grant_access( self.backend.grant_access(
@@ -195,7 +194,7 @@ class Modoboa(base.Installer):
def get_packages(self): def get_packages(self):
"""Include extra packages if needed.""" """Include extra packages if needed."""
packages = super(Modoboa, self).get_packages() packages = super().get_packages()
condition = ( condition = (
package.backend.FORMAT == "rpm" and package.backend.FORMAT == "rpm" and
sys.version_info.major == 2 and sys.version_info.major == 2 and
@@ -205,6 +204,10 @@ class Modoboa(base.Installer):
packages += ["openssl-devel"] packages += ["openssl-devel"]
return packages return packages
def setup_user(self):
super().setup_user()
self._setup_venv()
def get_config_files(self): def get_config_files(self):
"""Return appropriate path.""" """Return appropriate path."""
config_files = super().get_config_files() config_files = super().get_config_files()
@@ -213,6 +216,13 @@ class Modoboa(base.Installer):
else: else:
path = "supervisor=/etc/supervisord.d/policyd.ini" path = "supervisor=/etc/supervisord.d/policyd.ini"
config_files.append(path) config_files.append(path)
# Add worker for dkim if needed
if self.modoboa_2_2_or_greater:
config_files.append(
"supervisor-rq-dkim=/etc/supervisor/conf.d/modoboa-dkim-worker.conf")
config_files.append(
"supervisor-rq-base=/etc/supervisor/conf.d/modoboa-base-worker.conf")
return config_files return config_files
def get_template_context(self): def get_template_context(self):
@@ -221,17 +231,20 @@ class Modoboa(base.Installer):
extensions = self.config.get("modoboa", "extensions") extensions = self.config.get("modoboa", "extensions")
extensions = extensions.split() extensions = extensions.split()
random_hour = random.randint(0, 6) random_hour = random.randint(0, 6)
self.dkim_cron_enabled = (not self.modoboa_2_2_or_greater and
self.opendkim_enabled)
context.update({ context.update({
"sudo_user": ( "sudo_user": (
"uwsgi" if package.backend.FORMAT == "rpm" else context["user"] "uwsgi" if package.backend.FORMAT == "rpm" else context["user"]
), ),
"dovecot_mailboxes_owner": ( "dovecot_mailboxes_owner": (
self.config.get("dovecot", "mailboxes_owner")), self.config.get("dovecot", "mailboxes_owner")),
"radicale_enabled": (
"" if "modoboa-radicale" in extensions else "#"),
"opendkim_user": self.config.get("opendkim", "user"), "opendkim_user": self.config.get("opendkim", "user"),
"dkim_user": "_rspamd" if self.rspamd_enabled else self.config.get("opendkim", "user"),
"minutes": random.randint(1, 59), "minutes": random.randint(1, 59),
"hours" : f"{random_hour},{random_hour+12}" "hours": f"{random_hour},{random_hour+12}",
"modoboa_2_2_or_greater": "" if self.modoboa_2_2_or_greater else "#",
"dkim_cron_enabled": "" if self.dkim_cron_enabled else "#"
}) })
return context return context
@@ -243,22 +256,19 @@ class Modoboa(base.Installer):
self.instance_path, "media", "webmail") self.instance_path, "media", "webmail")
pw = pwd.getpwnam(self.user) pw = pwd.getpwnam(self.user)
for d in [rrd_root_dir, pdf_storage_dir, webmail_media_dir]: for d in [rrd_root_dir, pdf_storage_dir, webmail_media_dir]:
utils.mkdir(d, stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3]) utils.mkdir_safe(d, stat.S_IRWXU | stat.S_IRWXG, pw[2], pw[3])
settings = { settings = {
"admin": { "admin": {
"handle_mailboxes": True, "handle_mailboxes": True,
"account_auto_removal": True "account_auto_removal": True
}, },
"modoboa_amavis": {
"am_pdp_mode": "inet",
},
"maillog": { "maillog": {
"rrd_rootdir": rrd_root_dir, "rrd_rootdir": rrd_root_dir,
}, },
"modoboa_pdfcredentials": { "pdfcredentials": {
"storage_dir": pdf_storage_dir "storage_dir": pdf_storage_dir
}, },
"modoboa_radicale": { "calendars": {
"server_location": "https://{}/radicale/".format( "server_location": "https://{}/radicale/".format(
self.config.get("general", "hostname")), self.config.get("general", "hostname")),
"rights_file_path": "{}/rights".format( "rights_file_path": "{}/rights".format(
@@ -271,6 +281,19 @@ class Modoboa(base.Installer):
if self.config.getboolean("opendkim", "enabled"): if self.config.getboolean("opendkim", "enabled"):
settings["admin"]["dkim_keys_storage_dir"] = ( settings["admin"]["dkim_keys_storage_dir"] = (
self.config.get("opendkim", "keys_storage_dir")) self.config.get("opendkim", "keys_storage_dir"))
if self.rspamd_enabled:
settings["admin"]["dkim_keys_storage_dir"] = (
self.config.get("rspamd", "dkim_keys_storage_dir"))
settings["rspamd"] = {
"key_map_path": self.config.get("rspamd", "key_map_path"),
"selector_map_path": self.config.get("rspamd", "selector_map_path")
}
if self.config.getboolean("amavis", "enabled"):
settings["amavis"] = {
"am_pdp_mode": "inet",
}
settings = json.dumps(settings) settings = json.dumps(settings)
query = ( query = (
"UPDATE core_localconfig SET _parameters='{}'" "UPDATE core_localconfig SET _parameters='{}'"
@@ -281,17 +304,18 @@ class Modoboa(base.Installer):
def post_run(self): def post_run(self):
"""Additional tasks.""" """Additional tasks."""
self._setup_venv() if 'centos' in utils.dist_name():
system.enable_and_start_service("redis")
else:
system.enable_and_start_service("redis-server")
self._deploy_instance() self._deploy_instance()
if not self.upgrade: if not self.upgrade:
self.apply_settings() self.apply_settings()
if 'centos' in utils.dist_name(): if 'centos' in utils.dist_name():
supervisor = "supervisord" supervisor = "supervisord"
system.enable_and_start_service("redis")
else: else:
supervisor = "supervisor" supervisor = "supervisor"
system.enable_and_start_service("redis-server")
# Restart supervisor # Restart supervisor
system.enable_service(supervisor) system.enable_service(supervisor)
utils.exec_cmd("service {} stop".format(supervisor)) utils.exec_cmd("service {} stop".format(supervisor))

View File

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

View File

@@ -16,10 +16,7 @@ class Opendkim(base.Installer):
"""OpenDKIM installer.""" """OpenDKIM installer."""
appname = "opendkim" appname = "opendkim"
packages = { packages = {"deb": ["opendkim"], "rpm": ["opendkim"]}
"deb": ["opendkim"],
"rpm": ["opendkim"]
}
config_files = ["opendkim.conf", "opendkim.hosts"] config_files = ["opendkim.conf", "opendkim.hosts"]
def get_packages(self): def get_packages(self):
@@ -36,30 +33,34 @@ class Opendkim(base.Installer):
"""Make sure config directory exists.""" """Make sure config directory exists."""
user = self.config.get("opendkim", "user") user = self.config.get("opendkim", "user")
pw = pwd.getpwnam(user) pw = pwd.getpwnam(user)
targets = [ targets = [[self.app_config["keys_storage_dir"], pw[2], pw[3]]]
[self.app_config["keys_storage_dir"], pw[2], pw[3]]
]
for target in targets: for target in targets:
if not os.path.exists(target[0]): if not os.path.exists(target[0]):
utils.mkdir( utils.mkdir(
target[0], target[0],
stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IRWXU
stat.S_IROTH | stat.S_IXOTH, | stat.S_IRGRP
target[1], target[2] | stat.S_IXGRP
| stat.S_IROTH
| stat.S_IXOTH,
target[1],
target[2],
) )
super().install_config_files() super().install_config_files()
def get_template_context(self): def get_template_context(self):
"""Additional variables.""" """Additional variables."""
context = super(Opendkim, self).get_template_context() context = super(Opendkim, self).get_template_context()
context.update({ context.update(
{
"db_driver": self.db_driver, "db_driver": self.db_driver,
"db_name": self.config.get("modoboa", "dbname"), "db_name": self.config.get("modoboa", "dbname"),
"db_user": self.app_config["dbuser"], "db_user": self.app_config["dbuser"],
"db_password": self.app_config["dbpassword"], "db_password": self.app_config["dbpassword"],
"port": self.app_config["port"], "port": self.app_config["port"],
"user": self.app_config["user"] "user": self.app_config["user"],
}) }
)
return context return context
def setup_database(self): def setup_database(self):
@@ -72,60 +73,69 @@ class Opendkim(base.Installer):
dbuser = self.config.get("modoboa", "dbuser") dbuser = self.config.get("modoboa", "dbuser")
dbpassword = self.config.get("modoboa", "dbpassword") dbpassword = self.config.get("modoboa", "dbpassword")
self.backend.load_sql_file( self.backend.load_sql_file(
dbname, dbuser, dbpassword, dbname,
self.get_file_path("dkim_view_{}.sql".format(self.dbengine)) dbuser,
dbpassword,
self.get_file_path("dkim_view_{}.sql".format(self.dbengine)),
) )
self.backend.grant_right_on_table( self.backend.grant_right_on_table(
dbname, "dkim", self.app_config["dbuser"], "SELECT") dbname, "dkim", self.app_config["dbuser"], "SELECT"
)
def post_run(self): def post_run(self):
"""Additional tasks. """Additional tasks.
Check linux distribution (package deb, rpm), to adapt Check linux distribution (package deb, rpm), to adapt
to config file location and syntax. to config file location and syntax.
- update opendkim isocket port config for Debian based distro - update opendkim isocket port config
- make sure opendkim starts after db service started - make sure opendkim starts after db service started
""" """
if package.backend.FORMAT == "deb": if package.backend.FORMAT == "deb":
params_file = "/etc/default/opendkim" params_file = "/etc/default/opendkim"
else:
params_file = "/etc/opendkim.conf"
pattern = r"s/^(SOCKET=.*)/#\1/" pattern = r"s/^(SOCKET=.*)/#\1/"
utils.exec_cmd( utils.exec_cmd("perl -pi -e '{}' {}".format(pattern, params_file))
"perl -pi -e '{}' {}".format(pattern, params_file))
with open(params_file, "a") as f: with open(params_file, "a") as f:
f.write('\n'.join([ f.write(
"\n".join(
[
"", "",
'SOCKET="inet:12345@localhost"', 'SOCKET="inet:12345@localhost"',
])) ]
)
)
# Make sure opendkim is started after postgresql and mysql, # Make sure opendkim is started after postgresql and mysql,
# respectively. # respectively.
if (self.dbengine != "postgres" and package.backend.FORMAT == "deb"): if self.dbengine != "postgres" and package.backend.FORMAT == "deb":
dbservice = "mysql.service" dbservice = "mysql.service"
elif (self.dbengine != "postgres" and package.backend.FORMAT != "deb"): elif self.dbengine != "postgres" and package.backend.FORMAT != "deb":
dbservice = "mysqld.service" dbservice = "mysqld.service"
else: else:
dbservice = "postgresql.service" dbservice = "postgresql.service"
pattern = ( pattern = "s/^After=(.*)$/After=$1 {}/".format(dbservice)
"s/^After=(.*)$/After=$1 {}/".format(dbservice))
utils.exec_cmd( utils.exec_cmd(
"perl -pi -e '{}' /lib/systemd/system/opendkim.service".format(pattern)) "perl -pi -e '{}' /lib/systemd/system/opendkim.service".format(pattern)
)
def restore(self): def restore(self):
"""Restore keys.""" """Restore keys."""
dkim_keys_backup = os.path.join( dkim_keys_backup = os.path.join(self.archive_path, "custom/opendkim")
self.archive_path, "custom/dkim") keys_storage_dir = self.app_config["keys_storage_dir"]
if os.path.isdir(dkim_keys_backup): if os.path.isdir(dkim_keys_backup):
for file in os.listdir(dkim_keys_backup): for file in os.listdir(dkim_keys_backup):
file_path = os.path.join(dkim_keys_backup, file) file_path = os.path.join(dkim_keys_backup, file)
if os.path.isfile(file_path): if os.path.isfile(file_path):
utils.copy_file(file_path, self.config.get( utils.copy_file(file_path, keys_storage_dir)
"opendkim", "keys_storage_dir", fallback="/var/lib/dkim"))
utils.success("DKIM keys restored from backup") utils.success("DKIM keys restored from backup")
# Setup permissions
user = self.config.get("opendkim", "user")
utils.exec_cmd(f"chown -R {user}:{user} {keys_storage_dir}")
def custom_backup(self, path): def custom_backup(self, path):
"""Backup DKIM keys.""" """Backup DKIM keys."""
storage_dir = self.config.get( if os.path.isdir(self.app_config["keys_storage_dir"]):
"opendkim", "keys_storage_dir", fallback="/var/lib/dkim") shutil.copytree(
if os.path.isdir(storage_dir): self.app_config["keys_storage_dir"], path, dirs_exist_ok=True
shutil.copytree(storage_dir, os.path.join(path, "dkim")) )
utils.printcolor( utils.printcolor("DKIM keys saved!", utils.GREEN)
"DKIM keys saved!", utils.GREEN)

View File

@@ -14,15 +14,13 @@ from . import backup, install
class Postfix(base.Installer): class Postfix(base.Installer):
"""Postfix installer.""" """Postfix installer."""
appname = "postfix" appname = "postfix"
packages = { packages = {
"deb": ["postfix"], "deb": ["postfix", "postfix-pcre"],
"rpm": ["postfix"],
} }
config_files = ["main.cf", "master.cf"] config_files = ["main.cf", "master.cf", "anonymize_headers.pcre"]
def get_packages(self): def get_packages(self):
"""Additional packages.""" """Additional packages."""
@@ -30,17 +28,28 @@ class Postfix(base.Installer):
packages = ["postfix-{}".format(self.db_driver)] packages = ["postfix-{}".format(self.db_driver)]
else: else:
packages = [] packages = []
return super(Postfix, self).get_packages() + packages return super().get_packages() + packages
def install_packages(self): def install_packages(self):
"""Preconfigure postfix package installation.""" """Preconfigure postfix package installation."""
if "centos" in utils.dist_name():
config = configparser.ConfigParser()
with open("/etc/yum.repos.d/CentOS-Base.repo") as fp:
config.read_file(fp)
config.set("centosplus", "enabled", "1")
config.set("centosplus", "includepkgs", "postfix-*")
config.set("base", "exclude", "postfix-*")
config.set("updates", "exclude", "postfix-*")
with open("/etc/yum.repos.d/CentOS-Base.repo", "w") as fp:
config.write(fp)
package.backend.preconfigure( package.backend.preconfigure(
"postfix", "main_mailer_type", "select", "No configuration") "postfix", "main_mailer_type", "select", "No configuration")
super(Postfix, self).install_packages() super().install_packages()
def get_template_context(self): def get_template_context(self):
"""Additional variables.""" """Additional variables."""
context = super(Postfix, self).get_template_context() context = super().get_template_context()
context.update({ context.update({
"db_driver": self.db_driver, "db_driver": self.db_driver,
"dovecot_mailboxes_owner": self.config.get( "dovecot_mailboxes_owner": self.config.get(
@@ -50,10 +59,19 @@ class Postfix(base.Installer):
"modoboa_instance_path": self.config.get( "modoboa_instance_path": self.config.get(
"modoboa", "instance_path"), "modoboa", "instance_path"),
"opendkim_port": self.config.get( "opendkim_port": self.config.get(
"opendkim", "port") "opendkim", "port"),
"rspamd_disabled": "" if not self.config.getboolean(
"rspamd", "enabled") else "#"
}) })
return context return context
def check_dhe_group_file(self):
group = self.config.get(self.appname, "dhe_group")
file_name = f"ffdhe{group}.pem"
if not os.path.exists(f"{self.config_dir}/{file_name}"):
url = f"https://raw.githubusercontent.com/internetstandards/dhe_groups/main/{file_name}"
utils.exec_cmd(f"wget {url}", cwd=self.config_dir)
def post_run(self): def post_run(self):
"""Additional tasks.""" """Additional tasks."""
venv_path = self.config.get("modoboa", "venv_path") venv_path = self.config.get("modoboa", "venv_path")
@@ -75,10 +93,8 @@ class Postfix(base.Installer):
if not os.path.exists(path): if not os.path.exists(path):
utils.copy_file(os.path.join("/etc", f), path) utils.copy_file(os.path.join("/etc", f), path)
# Generate EDH parameters # Generate DHE group
if not os.path.exists("{}/dh2048.pem".format(self.config_dir)): self.check_dhe_group_file()
cmd = "openssl dhparam -dsaparam -out dh2048.pem 2048"
utils.exec_cmd(cmd, cwd=self.config_dir)
# Generate /etc/aliases.db file to avoid warnings # Generate /etc/aliases.db file to avoid warnings
aliases_file = "/etc/aliases" aliases_file = "/etc/aliases"
@@ -86,8 +102,18 @@ class Postfix(base.Installer):
utils.exec_cmd("postalias {}".format(aliases_file)) utils.exec_cmd("postalias {}".format(aliases_file))
# Postwhite # Postwhite
condition = (
not self.config.getboolean("rspamd", "enabled") and
self.config.getboolean("postwhite", "enabled")
)
if condition:
install("postwhite", self.config, self.upgrade, self.archive_path) install("postwhite", self.config, self.upgrade, self.archive_path)
def backup(self, path): def backup(self, path):
"""Launch postwhite backup.""" """Launch postwhite backup."""
condition = (
not self.config.getboolean("rspamd", "enabled") and
self.config.getboolean("postwhite", "enabled")
)
if condition:
backup("postwhite", self.config, path) backup("postwhite", self.config, path)

View File

@@ -19,10 +19,7 @@ class Postwhite(base.Installer):
"crontab=/etc/cron.d/postwhite", "crontab=/etc/cron.d/postwhite",
] ]
no_daemon = True no_daemon = True
packages = { packages = {"deb": ["bind9-host", "unzip"], "rpm": ["bind-utils", "unzip"]}
"deb": ["bind9-host"],
"rpm": ["bind-utils"]
}
def install_from_archive(self, repository, target_dir): def install_from_archive(self, repository, target_dir):
"""Install from an archive.""" """Install from an archive."""
@@ -36,8 +33,7 @@ class Postwhite(base.Installer):
if os.path.exists(archive_dir): if os.path.exists(archive_dir):
shutil.rmtree(archive_dir) shutil.rmtree(archive_dir)
utils.exec_cmd("unzip master.zip", cwd=target_dir) utils.exec_cmd("unzip master.zip", cwd=target_dir)
utils.exec_cmd( utils.exec_cmd("mv {name}-master {name}".format(name=app_name), cwd=target_dir)
"mv {name}-master {name}".format(name=app_name), cwd=target_dir)
os.unlink(target) os.unlink(target)
return archive_dir return archive_dir
@@ -45,26 +41,23 @@ class Postwhite(base.Installer):
"""Additionnal tasks.""" """Additionnal tasks."""
install_dir = "/usr/local/bin" install_dir = "/usr/local/bin"
self.install_from_archive(SPF_TOOLS_REPOSITORY, install_dir) self.install_from_archive(SPF_TOOLS_REPOSITORY, install_dir)
self.postw_dir = self.install_from_archive( self.postw_dir = self.install_from_archive(POSTWHITE_REPOSITORY, install_dir)
POSTWHITE_REPOSITORY, install_dir) utils.copy_file(os.path.join(self.postw_dir, "postwhite.conf"), self.config_dir)
postw_bin = os.path.join(self.postw_dir, "postwhite") self.postw_bin = os.path.join(self.postw_dir, "postwhite")
utils.exec_cmd("{} /etc/postwhite.conf".format(postw_bin)) utils.exec_cmd("{} /etc/postwhite.conf".format(self.postw_bin))
def custom_backup(self, path): def custom_backup(self, path):
"""Backup custom configuration if any.""" """Backup custom configuration if any."""
postswhite_custom = "/etc/postwhite.conf" postswhite_custom = "/etc/postwhite.conf"
if os.path.isfile(postswhite_custom): if os.path.isfile(postswhite_custom):
utils.copy_file(postswhite_custom, path) utils.copy_file(postswhite_custom, path)
utils.printcolor( utils.printcolor("Postwhite configuration saved!", utils.GREEN)
"Postwhite configuration saved!", utils.GREEN)
def restore(self): def restore(self):
"""Restore config files.""" """Restore config files."""
postwhite_backup_configuration = os.path.join( postwhite_backup_configuration = os.path.join(
self.archive_path, "custom/postwhite.conf") self.archive_path, "custom/postwhite/postwhite.conf"
)
if os.path.isfile(postwhite_backup_configuration): if os.path.isfile(postwhite_backup_configuration):
utils.copy_file(postwhite_backup_configuration, self.config_dir) utils.copy_file(postwhite_backup_configuration, self.config_dir)
utils.success("postwhite.conf restored from backup") utils.success("postwhite.conf restored from backup")
else:
utils.copy_file(
os.path.join(self.postw_dir, "postwhite.conf"), self.config_dir)

View File

@@ -18,10 +18,7 @@ class Radicale(base.Installer):
appname = "radicale" appname = "radicale"
config_files = ["config"] config_files = ["config"]
no_daemon = True no_daemon = True
packages = { packages = {"deb": ["supervisor"], "rpm": ["supervisor"]}
"deb": ["supervisor"],
"rpm": ["supervisor"]
}
with_user = True with_user = True
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -31,30 +28,31 @@ class Radicale(base.Installer):
def _setup_venv(self): def _setup_venv(self):
"""Prepare a dedicated virtualenv.""" """Prepare a dedicated virtualenv."""
python.setup_virtualenv( python.setup_virtualenv(self.venv_path, sudo_user=self.user)
self.venv_path, sudo_user=self.user, python_version=3) packages = ["Radicale", "pytz", "radicale-modoboa-auth-oauth2"]
packages = [
"Radicale", "radicale-dovecot-auth", "pytz"
]
python.install_packages(packages, self.venv_path, sudo_user=self.user) python.install_packages(packages, self.venv_path, sudo_user=self.user)
python.install_package_from_repository(
"radicale-storage-by-index",
"https://github.com/tonioo/RadicaleStorageByIndex",
venv=self.venv_path, sudo_user=self.user)
def get_template_context(self): def get_template_context(self):
"""Additional variables.""" """Additional variables."""
context = super(Radicale, self).get_template_context() context = super().get_template_context()
radicale_auth_socket_path = self.config.get( oauth2_client_id, oauth2_client_secret = utils.create_oauth2_app(
"dovecot", "radicale_auth_socket_path") "Radicale", "radicale", self.config
context.update({ )
"auth_socket_path": radicale_auth_socket_path hostname = self.config.get("general", "hostname")
}) oauth2_introspection_url = (
f"https://{oauth2_client_id}:{oauth2_client_secret}"
f"@{hostname}/api/o/introspect/"
)
context.update(
{
"oauth2_introspection_url": oauth2_introspection_url,
}
)
return context return context
def get_config_files(self): def get_config_files(self):
"""Return appropriate path.""" """Return appropriate path."""
config_files = super(Radicale, self).get_config_files() config_files = super().get_config_files()
if package.backend.FORMAT == "deb": if package.backend.FORMAT == "deb":
path = "supervisor=/etc/supervisor/conf.d/radicale.conf" path = "supervisor=/etc/supervisor/conf.d/radicale.conf"
else: else:
@@ -67,16 +65,19 @@ class Radicale(base.Installer):
if not os.path.exists(self.config_dir): if not os.path.exists(self.config_dir):
utils.mkdir( utils.mkdir(
self.config_dir, self.config_dir,
stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IRWXU
stat.S_IROTH | stat.S_IXOTH, | stat.S_IRGRP
0, 0 | stat.S_IXGRP
| stat.S_IROTH
| stat.S_IXOTH,
0,
0,
) )
super().install_config_files() super().install_config_files()
def restore(self): def restore(self):
"""Restore collections.""" """Restore collections."""
radicale_backup = os.path.join( radicale_backup = os.path.join(self.archive_path, "custom/radicale")
self.archive_path, "custom/radicale")
if os.path.isdir(radicale_backup): if os.path.isdir(radicale_backup):
restore_target = os.path.join(self.home_dir, "collections") restore_target = os.path.join(self.home_dir, "collections")
if os.path.isdir(restore_target): if os.path.isdir(restore_target):
@@ -87,18 +88,17 @@ class Radicale(base.Installer):
def post_run(self): def post_run(self):
"""Additional tasks.""" """Additional tasks."""
self._setup_venv() self._setup_venv()
daemon_name = ( daemon_name = "supervisor" if package.backend.FORMAT == "deb" else "supervisord"
"supervisor" if package.backend.FORMAT == "deb" else "supervisord"
)
system.enable_service(daemon_name) system.enable_service(daemon_name)
utils.exec_cmd("service {} stop".format(daemon_name)) utils.exec_cmd("service {} stop".format(daemon_name))
utils.exec_cmd("service {} start".format(daemon_name)) utils.exec_cmd("service {} start".format(daemon_name))
def custom_backup(self, path): def custom_backup(self, path):
"""Backup collections.""" """Backup collections."""
radicale_backup = os.path.join(self.config.get( radicale_backup = os.path.join(
"radicale", "home_dir", fallback="/srv/radicale"), "collections") self.config.get("radicale", "home_dir", fallback="/srv/radicale"),
"collections",
)
if os.path.isdir(radicale_backup): if os.path.isdir(radicale_backup):
shutil.copytree(radicale_backup, os.path.join( shutil.copytree(radicale_backup, path, dirs_exist_ok=True)
path, "radicale"))
utils.printcolor("Radicale files saved", utils.GREEN) utils.printcolor("Radicale files saved", utils.GREEN)

View File

@@ -13,14 +13,14 @@ class Restore:
""" """
if not os.path.isdir(restore): if not os.path.isdir(restore):
utils.printcolor( utils.error(
"Provided path is not a directory !", utils.RED) "Provided path is not a directory !")
sys.exit(1) sys.exit(1)
modoba_sql_file = os.path.join(restore, "databases/modoboa.sql") modoba_sql_file = os.path.join(restore, "databases/modoboa.sql")
if not os.path.isfile(modoba_sql_file): if not os.path.isfile(modoba_sql_file):
utils.printcolor( utils.error(
modoba_sql_file + " not found, please check your backup", utils.RED) modoba_sql_file + " not found, please check your backup")
sys.exit(1) sys.exit(1)
# Everything seems alright here, proceeding... # Everything seems alright here, proceeding...

Some files were not shown because too many files have changed in this diff Show More