Compare commits
40 commits
ldap-suppo
...
master
Author | SHA1 | Date | |
---|---|---|---|
a98a93cf22 | |||
806a4cfd21 | |||
|
059b50b2e7 | ||
|
290a995de5 | ||
|
54cbacb6eb | ||
|
29916981e7 | ||
|
0d51a32e47 | ||
|
ed80b589d3 | ||
|
46a0829aa8 | ||
|
41059fc548 | ||
|
ef4756bcfc | ||
|
9f6635a035 | ||
|
79c8cfcd58 | ||
|
799fe34c12 | ||
|
d507bd9c95 | ||
|
fe6d325397 | ||
|
572c1b4d69 | ||
|
9e36323ae3 | ||
|
e47f3719f1 | ||
|
b5023b36a1 | ||
|
3f526c08e8 | ||
|
008d78cc21 | ||
|
84783b661e | ||
|
93221e4b25 | ||
|
c63f6e7b05 | ||
|
a3b03d1b5a | ||
|
69a4b7ad67 | ||
|
71b4c62d85 | ||
|
6775502be3 | ||
|
7695c856f1 | ||
|
fb3210b932 | ||
|
33554e57ce | ||
|
8b03ae5701 | ||
|
42e245b069 | ||
|
08f077c5ca | ||
|
d460e9ff62 | ||
|
0c1801b489 | ||
|
24128c3052 | ||
|
c4ec122aac | ||
|
131c48de9b |
23 changed files with 292 additions and 198 deletions
17
.forgejo/workflows/build.yaml
Normal file
17
.forgejo/workflows/build.yaml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# deploy it upstream
|
||||||
|
deploy:
|
||||||
|
runs-on: docker
|
||||||
|
steps:
|
||||||
|
- name: "Deploy to Skynet"
|
||||||
|
uses: https://forgejo.skynet.ie/Skynet/actions-deploy-to-skynet@v2
|
||||||
|
with:
|
||||||
|
input: 'simple-nixos-mailserver'
|
||||||
|
token: ${{ secrets.API_TOKEN_FORGEJO }}
|
|
@ -32,8 +32,8 @@ let
|
||||||
|
|
||||||
desc = prJobsets // {
|
desc = prJobsets // {
|
||||||
"master" = mkFlakeJobset "master";
|
"master" = mkFlakeJobset "master";
|
||||||
"nixos-22.05" = mkFlakeJobset "nixos-22.05";
|
"nixos-23.11" = mkFlakeJobset "nixos-23.11";
|
||||||
"nixos-22.11" = mkFlakeJobset "nixos-22.11";
|
"nixos-24.05" = mkFlakeJobset "nixos-24.05";
|
||||||
};
|
};
|
||||||
|
|
||||||
log = {
|
log = {
|
||||||
|
|
87
README.md
87
README.md
|
@ -8,26 +8,21 @@
|
||||||
For each NixOS release, we publish a branch. You then have to use the
|
For each NixOS release, we publish a branch. You then have to use the
|
||||||
SNM branch corresponding to your NixOS version.
|
SNM branch corresponding to your NixOS version.
|
||||||
|
|
||||||
* For NixOS 22.11
|
* For NixOS 24.05
|
||||||
- Use the [SNM branch `nixos-22.11`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/nixos-22.11)
|
- Use the [SNM branch `nixos-24.05`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/nixos-24.05)
|
||||||
- [Documentation](https://nixos-mailserver.readthedocs.io/en/nixos-22.11/)
|
- [Documentation](https://nixos-mailserver.readthedocs.io/en/nixos-24.05/)
|
||||||
- [Release notes](https://nixos-mailserver.readthedocs.io/en/nixos-22.11/release-notes.html#nixos-22-11)
|
- [Release notes](https://nixos-mailserver.readthedocs.io/en/nixos-24.05/release-notes.html#nixos-24-05)
|
||||||
* For NixOS 22.05
|
* For NixOS 23.11
|
||||||
- Use the [SNM branch `nixos-22.05`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/nixos-22.05)
|
- Use the [SNM branch `nixos-23.11`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/nixos-23.11)
|
||||||
- [Documentation](https://nixos-mailserver.readthedocs.io/en/nixos-22.05/)
|
- [Documentation](https://nixos-mailserver.readthedocs.io/en/nixos-23.11/)
|
||||||
- [Release notes](https://nixos-mailserver.readthedocs.io/en/nixos-22.05/release-notes.html#nixos-22-05)
|
- [Release notes](https://nixos-mailserver.readthedocs.io/en/nixos-23.11/release-notes.html#nixos-23-11)
|
||||||
* For NixOS unstable
|
* For NixOS unstable
|
||||||
- Use the [SNM branch `master`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/master)
|
- Use the [SNM branch `master`](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/tree/master)
|
||||||
- [Documentation](https://nixos-mailserver.readthedocs.io/en/latest/)
|
- [Documentation](https://nixos-mailserver.readthedocs.io/en/latest/)
|
||||||
|
|
||||||
[Subscribe to SNM Announcement List](https://www.freelists.org/list/snm)
|
[Subscribe to SNM Announcement List](https://www.freelists.org/list/snm)
|
||||||
This is a very low volume list where new releases of SNM are announced, so you
|
This is a very low volume list where new releases of SNM are announced, so you
|
||||||
can stay up to date with bug fixes and updates. All announcements are signed by
|
can stay up to date with bug fixes and updates.
|
||||||
the gpg key with fingerprint
|
|
||||||
|
|
||||||
```
|
|
||||||
D9FE 4119 F082 6F15 93BD BD36 6162 DBA5 635E A16A
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
@ -76,71 +71,15 @@ D9FE 4119 F082 6F15 93BD BD36 6162 DBA5 635E A16A
|
||||||
- Subscribe to the [mailing list](https://www.freelists.org/archive/snm/)
|
- Subscribe to the [mailing list](https://www.freelists.org/archive/snm/)
|
||||||
- Join the Libera Chat IRC channel `#nixos-mailserver`
|
- Join the Libera Chat IRC channel `#nixos-mailserver`
|
||||||
|
|
||||||
### Quick Start
|
|
||||||
|
|
||||||
```nix
|
|
||||||
{ config, pkgs, ... }:
|
|
||||||
let release = "nixos-21.11";
|
|
||||||
in {
|
|
||||||
imports = [
|
|
||||||
(builtins.fetchTarball {
|
|
||||||
url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/${release}/nixos-mailserver-${release}.tar.gz";
|
|
||||||
# This hash needs to be updated
|
|
||||||
sha256 = "0000000000000000000000000000000000000000000000000000";
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
mailserver = {
|
|
||||||
enable = true;
|
|
||||||
fqdn = "mail.example.com";
|
|
||||||
domains = [ "example.com" "example2.com" ];
|
|
||||||
loginAccounts = {
|
|
||||||
"user1@example.com" = {
|
|
||||||
# nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt' > /hashed/password/file/location
|
|
||||||
hashedPasswordFile = "/hashed/password/file/location";
|
|
||||||
|
|
||||||
aliases = [
|
|
||||||
"info@example.com"
|
|
||||||
"postmaster@example.com"
|
|
||||||
"postmaster@example2.com"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For a complete list of options, see `default.nix`.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## How to Set Up a 10/10 Mail Server Guide
|
## How to Set Up a 10/10 Mail Server Guide
|
||||||
Check out the [Complete Setup Guide](https://nixos-mailserver.readthedocs.io/en/latest/setup-guide.html) in the project's documentation.
|
|
||||||
|
|
||||||
## How to Backup
|
Check out the [Setup Guide](https://nixos-mailserver.readthedocs.io/en/latest/setup-guide.html) in the project's documentation.
|
||||||
|
|
||||||
Checkout the [Complete Backup Guide](https://nixos-mailserver.readthedocs.io/en/latest/backup-guide.html). Backups are easy with `SNM`.
|
For a complete list of options, [see in readthedocs](https://nixos-mailserver.readthedocs.io/en/latest/options.html).
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
See the [How to Develop SNM](https://nixos-mailserver.readthedocs.io/en/latest/howto-develop.html) wiki page.
|
See the [How to Develop SNM](https://nixos-mailserver.readthedocs.io/en/latest/howto-develop.html) documentation page.
|
||||||
|
|
||||||
## Release notes
|
|
||||||
|
|
||||||
### nixos-20.03
|
|
||||||
|
|
||||||
- Rspamd is upgraded to 2.0 which deprecates the SQLite Bayes
|
|
||||||
backend. We then moved to the Redis backend (the default since
|
|
||||||
Rspamd 2.0). If you don't want to relearn the Redis backend from the
|
|
||||||
scratch, we could manually run
|
|
||||||
|
|
||||||
rspamadm statconvert --spam-db /var/lib/rspamd/bayes.spam.sqlite --ham-db /var/lib/rspamd/bayes.ham.sqlite -h 127.0.0.1:6379 --symbol-ham BAYES_HAM --symbol-spam BAYES_SPAM
|
|
||||||
|
|
||||||
See the [Rspamd migration
|
|
||||||
notes](https://rspamd.com/doc/migration.html#migration-to-rspamd-20)
|
|
||||||
and [this SNM Merge
|
|
||||||
Request](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/merge_requests/164)
|
|
||||||
for details.
|
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
See the [contributor tab](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/graphs/master)
|
See the [contributor tab](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/graphs/master)
|
||||||
|
@ -155,6 +94,4 @@ See the [contributor tab](https://gitlab.com/simple-nixos-mailserver/nixos-mails
|
||||||
* Logo made with [Logomakr.com](https://logomakr.com)
|
* Logo made with [Logomakr.com](https://logomakr.com)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[logo]: docs/logo.png
|
[logo]: docs/logo.png
|
||||||
|
|
48
default.nix
48
default.nix
|
@ -111,6 +111,15 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
aliasesRegexp = mkOption {
|
||||||
|
type = with types; listOf types.str;
|
||||||
|
example = [''/^tom\..*@domain\.com$/''];
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
Same as {option}`mailserver.aliases` but using PCRE (Perl compatible regex).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
catchAll = mkOption {
|
catchAll = mkOption {
|
||||||
type = with types; listOf (enum cfg.domains);
|
type = with types; listOf (enum cfg.domains);
|
||||||
example = ["example.com" "example2.com"];
|
example = ["example.com" "example2.com"];
|
||||||
|
@ -268,7 +277,7 @@ in
|
||||||
|
|
||||||
dovecot = {
|
dovecot = {
|
||||||
userAttrs = mkOption {
|
userAttrs = mkOption {
|
||||||
type = types.str;
|
type = types.nullOr types.str;
|
||||||
default = "";
|
default = "";
|
||||||
description = ''
|
description = ''
|
||||||
LDAP attributes to be retrieved during userdb lookups.
|
LDAP attributes to be retrieved during userdb lookups.
|
||||||
|
@ -455,7 +464,6 @@ in
|
||||||
type = let
|
type = let
|
||||||
loginAccount = mkOptionType {
|
loginAccount = mkOptionType {
|
||||||
name = "Login Account";
|
name = "Login Account";
|
||||||
check = (account: builtins.elem account (builtins.attrNames cfg.loginAccounts));
|
|
||||||
};
|
};
|
||||||
in with types; attrsOf (either loginAccount (nonEmptyListOf loginAccount));
|
in with types; attrsOf (either loginAccount (nonEmptyListOf loginAccount));
|
||||||
example = {
|
example = {
|
||||||
|
@ -565,6 +573,14 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useUTF8FolderNames = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Store mailbox names on disk using UTF-8 instead of modified UTF-7 (mUTF-7).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
hierarchySeparator = mkOption {
|
hierarchySeparator = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default = ".";
|
default = ".";
|
||||||
|
@ -658,6 +674,19 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
acmeCertificateName = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = cfg.fqdn;
|
||||||
|
example = "example.com";
|
||||||
|
description = ''
|
||||||
|
({option}`mailserver.certificateScheme` == `acme`)
|
||||||
|
|
||||||
|
When the `acme` `certificateScheme` is selected, you can use this option
|
||||||
|
to override the default certificate name. This is useful if you've
|
||||||
|
generated a wildcard certificate, for example.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
enableImap = mkOption {
|
enableImap = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = true;
|
default = true;
|
||||||
|
@ -938,6 +967,21 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
smtpdForbidBareNewline = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
With "smtpd_forbid_bare_newline = yes", the Postfix SMTP server
|
||||||
|
disconnects a remote SMTP client that sends a line ending in a 'bare
|
||||||
|
newline'.
|
||||||
|
|
||||||
|
This feature was added in Postfix 3.8.4 against SMTP Smuggling and will
|
||||||
|
default to "yes" in Postfix 3.9.
|
||||||
|
|
||||||
|
https://www.postfix.org/smtp-smuggling.html
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
sendingFqdn = mkOption {
|
sendingFqdn = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default = cfg.fqdn;
|
default = cfg.fqdn;
|
||||||
|
|
|
@ -24,12 +24,13 @@ have to be used. These can still be generated using `mkpasswd -m bcrypt`.
|
||||||
in {
|
in {
|
||||||
services.radicale = {
|
services.radicale = {
|
||||||
enable = true;
|
enable = true;
|
||||||
config = ''
|
settings = {
|
||||||
[auth]
|
auth = {
|
||||||
type = htpasswd
|
type = "htpasswd";
|
||||||
htpasswd_filename = ${htpasswd}
|
htpasswd_filename = "${htpasswd}";
|
||||||
htpasswd_encryption = bcrypt
|
htpasswd_encryption = "bcrypt";
|
||||||
'';
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
|
|
|
@ -20,7 +20,7 @@ servers may require more work.
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
# starttls needed for authentication, so the fqdn required to match
|
# starttls needed for authentication, so the fqdn required to match
|
||||||
# the certificate
|
# the certificate
|
||||||
$config['smtp_server'] = "tls://${config.mailserver.fqdn}";
|
$config['smtp_host'] = "tls://${config.mailserver.fqdn}";
|
||||||
$config['smtp_user'] = "%u";
|
$config['smtp_user'] = "%u";
|
||||||
$config['smtp_pass'] = "%p";
|
$config['smtp_pass'] = "%p";
|
||||||
'';
|
'';
|
||||||
|
|
|
@ -30,6 +30,7 @@ Welcome to NixOS Mailserver's documentation!
|
||||||
fts
|
fts
|
||||||
flakes
|
flakes
|
||||||
autodiscovery
|
autodiscovery
|
||||||
|
ldap
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
==================
|
==================
|
||||||
|
|
14
docs/ldap.rst
Normal file
14
docs/ldap.rst
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
LDAP Support
|
||||||
|
============
|
||||||
|
|
||||||
|
It is possible to manage mail user accounts with LDAP rather than with
|
||||||
|
the option `loginAccounts <options.html#mailserver-loginaccounts>`_.
|
||||||
|
|
||||||
|
All related LDAP options are described in the `LDAP options section
|
||||||
|
<options.html#mailserver-ldap>`_ and the `LDAP test
|
||||||
|
<https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/blob/master/tests/ldap.nix>`_
|
||||||
|
provides a getting started example.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The LDAP support can not be enabled if some accounts are also defined with ``mailserver.loginAccounts``.
|
||||||
|
|
|
@ -1,10 +1,28 @@
|
||||||
Release Notes
|
Release Notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
NixOS 24.05
|
||||||
|
-----------
|
||||||
|
|
||||||
|
- Add new option ``acmeCertificateName`` which can be used to support
|
||||||
|
wildcard certificates
|
||||||
|
|
||||||
|
NixOS 23.11
|
||||||
|
-----------
|
||||||
|
|
||||||
|
- Add basic support for LDAP users
|
||||||
|
- Add support for regex (PCRE) aliases
|
||||||
|
|
||||||
|
NixOS 23.05
|
||||||
|
-----------
|
||||||
|
|
||||||
|
- Existing ACME certificates can be reused without configuring NGINX
|
||||||
|
- Certificate scheme is no longer a number, but a meaningful string instead
|
||||||
|
|
||||||
NixOS 22.11
|
NixOS 22.11
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
- Allow Rspamd to send dmarc reporting
|
- Allow Rspamd to send DMARC reporting
|
||||||
(`merge request <https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/merge_requests/244>`__)
|
(`merge request <https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/merge_requests/244>`__)
|
||||||
|
|
||||||
NixOS 22.05
|
NixOS 22.05
|
||||||
|
|
|
@ -48,18 +48,19 @@ Setup the server
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
The following describes a server setup that is fairly complete. Even
|
The following describes a server setup that is fairly complete. Even
|
||||||
though there are more possible options (see the ``default.nix`` file),
|
though there are more possible options (see the `NixOS Mailserver
|
||||||
these should be the most common ones.
|
options documentation <options.html>`_), these should be the most
|
||||||
|
common ones.
|
||||||
|
|
||||||
.. code:: nix
|
.. code:: nix
|
||||||
|
|
||||||
{ config, pkgs, ... }:
|
{ config, pkgs, ... }: {
|
||||||
{
|
|
||||||
imports = [
|
imports = [
|
||||||
(builtins.fetchTarball {
|
(builtins.fetchTarball {
|
||||||
# Pick a commit from the branch you are interested in
|
# Pick a release version you are interested in and set its hash, e.g.
|
||||||
url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/A-COMMIT-ID/nixos-mailserver-A-COMMIT-ID.tar.gz";
|
url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/nixos-23.05/nixos-mailserver-nixos-23.05.tar.gz";
|
||||||
# And set its hash
|
# To get the sha256 of the nixos-mailserver tarball, we can use the nix-prefetch-url command:
|
||||||
|
# release="nixos-23.05"; nix-prefetch-url "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/${release}/nixos-mailserver-${release}.tar.gz" --unpack
|
||||||
sha256 = "0000000000000000000000000000000000000000000000000000";
|
sha256 = "0000000000000000000000000000000000000000000000000000";
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
@ -72,17 +73,19 @@ these should be the most common ones.
|
||||||
# A list of all login accounts. To create the password hashes, use
|
# A list of all login accounts. To create the password hashes, use
|
||||||
# nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
|
# nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
|
||||||
loginAccounts = {
|
loginAccounts = {
|
||||||
"user1@example.com" = {
|
"user1@example.com" = {
|
||||||
hashedPasswordFile = "/a/file/containing/a/hashed/password";
|
hashedPasswordFile = "/a/file/containing/a/hashed/password";
|
||||||
aliases = ["postmaster@example.com"];
|
aliases = ["postmaster@example.com"];
|
||||||
};
|
};
|
||||||
"user2@example.com" = { ... };
|
"user2@example.com" = { ... };
|
||||||
};
|
};
|
||||||
|
|
||||||
# Use Let's Encrypt certificates. Note that this needs to set up a stripped
|
# Use Let's Encrypt certificates. Note that this needs to set up a stripped
|
||||||
# down nginx and opens port 80.
|
# down nginx and opens port 80.
|
||||||
certificateScheme = "acme-nginx";
|
certificateScheme = "acme-nginx";
|
||||||
};
|
};
|
||||||
|
security.acme.acceptTerms = true;
|
||||||
|
security.acme.defaults.email = "security@example.com";
|
||||||
}
|
}
|
||||||
|
|
||||||
After a ``nixos-rebuild switch`` your server should be running all
|
After a ``nixos-rebuild switch`` your server should be running all
|
||||||
|
|
40
flake.lock
40
flake.lock
|
@ -19,11 +19,11 @@
|
||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1668681692,
|
"lastModified": 1696426674,
|
||||||
"narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=",
|
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||||
"owner": "edolstra",
|
"owner": "edolstra",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"rev": "009399224d5e398d03b22badca40a37ac85412a1",
|
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -34,11 +34,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1670751203,
|
"lastModified": 1717602782,
|
||||||
"narHash": "sha256-XdoH1v3shKDGlrwjgrNX/EN8s3c+kQV7xY6cLCE8vcI=",
|
"narHash": "sha256-pL9jeus5QpX5R+9rsp3hhZ+uplVHscNJh8n8VpqscM0=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "64e0bf055f9d25928c31fb12924e59ff8ce71e60",
|
"rev": "e8057b67ebf307f01bdcc8fba94d94f75039d1f6",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -47,18 +47,18 @@
|
||||||
"type": "indirect"
|
"type": "indirect"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-22_11": {
|
"nixpkgs-24_05": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1669558522,
|
"lastModified": 1717144377,
|
||||||
"narHash": "sha256-yqxn+wOiPqe6cxzOo4leeJOp1bXE/fjPEi/3F/bBHv8=",
|
"narHash": "sha256-F/TKWETwB5RaR8owkPPi+SPJh83AQsm6KrQAlJ8v/uA=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "ce5fe99df1f15a09a91a86be9738d68fadfbad82",
|
"rev": "805a384895c696f802a9bf5bf4720f37385df547",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"id": "nixpkgs",
|
"id": "nixpkgs",
|
||||||
"ref": "nixos-22.11",
|
"ref": "nixos-24.05",
|
||||||
"type": "indirect"
|
"type": "indirect"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -67,23 +67,7 @@
|
||||||
"blobs": "blobs",
|
"blobs": "blobs",
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"nixpkgs-22_11": "nixpkgs-22_11",
|
"nixpkgs-24_05": "nixpkgs-24_05"
|
||||||
"utils": "utils"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"utils": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1605370193,
|
|
||||||
"narHash": "sha256-YyMTf3URDL/otKdKgtoMChu4vfVL3vCMkRqpGifhUn0=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "5021eac20303a61fafe17224c087f5519baed54d",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
10
flake.nix
10
flake.nix
|
@ -6,16 +6,15 @@
|
||||||
url = "github:edolstra/flake-compat";
|
url = "github:edolstra/flake-compat";
|
||||||
flake = false;
|
flake = false;
|
||||||
};
|
};
|
||||||
utils.url = "github:numtide/flake-utils";
|
|
||||||
nixpkgs.url = "flake:nixpkgs/nixos-unstable";
|
nixpkgs.url = "flake:nixpkgs/nixos-unstable";
|
||||||
nixpkgs-22_11.url = "flake:nixpkgs/nixos-22.11";
|
nixpkgs-24_05.url = "flake:nixpkgs/nixos-24.05";
|
||||||
blobs = {
|
blobs = {
|
||||||
url = "gitlab:simple-nixos-mailserver/blobs";
|
url = "gitlab:simple-nixos-mailserver/blobs";
|
||||||
flake = false;
|
flake = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, utils, blobs, nixpkgs, nixpkgs-22_11, ... }: let
|
outputs = { self, blobs, nixpkgs, nixpkgs-24_05, ... }: let
|
||||||
lib = nixpkgs.lib;
|
lib = nixpkgs.lib;
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
@ -24,6 +23,10 @@
|
||||||
name = "unstable";
|
name = "unstable";
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
name = "24.05";
|
||||||
|
pkgs = nixpkgs-24_05.legacyPackages.${system};
|
||||||
|
}
|
||||||
];
|
];
|
||||||
testNames = [
|
testNames = [
|
||||||
"internal"
|
"internal"
|
||||||
|
@ -86,6 +89,7 @@
|
||||||
sphinx
|
sphinx
|
||||||
sphinx_rtd_theme
|
sphinx_rtd_theme
|
||||||
myst-parser
|
myst-parser
|
||||||
|
linkify-it-py
|
||||||
])
|
])
|
||||||
)];
|
)];
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
|
|
|
@ -5,13 +5,14 @@
|
||||||
assertion = config.mailserver.loginAccounts == {};
|
assertion = config.mailserver.loginAccounts == {};
|
||||||
message = "When the LDAP support is enable (mailserver.ldap.enable = true), it is not possible to define mailserver.loginAccounts";
|
message = "When the LDAP support is enable (mailserver.ldap.enable = true), it is not possible to define mailserver.loginAccounts";
|
||||||
}
|
}
|
||||||
{
|
|
||||||
assertion = config.mailserver.extraVirtualAliases == {};
|
|
||||||
message = "When the LDAP support is enable (mailserver.ldap.enable = true), it is not possible to define mailserver.extraVirtualAliases";
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
assertion = config.mailserver.forwards == {};
|
assertion = config.mailserver.forwards == {};
|
||||||
message = "When the LDAP support is enable (mailserver.ldap.enable = true), it is not possible to define mailserver.forwards";
|
message = "When the LDAP support is enable (mailserver.ldap.enable = true), it is not possible to define mailserver.forwards";
|
||||||
}
|
}
|
||||||
|
] ++ lib.optionals (config.mailserver.enable && config.mailserver.certificateScheme != "acme") [
|
||||||
|
{
|
||||||
|
assertion = config.mailserver.acmeCertificateName == config.mailserver.fqdn;
|
||||||
|
message = "When the certificate scheme is not 'acme' (mailserver.certificateScheme != \"acme\"), it is not possible to define mailserver.acmeCertificateName";
|
||||||
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ in
|
||||||
else if cfg.certificateScheme == "selfsigned"
|
else if cfg.certificateScheme == "selfsigned"
|
||||||
then "${cfg.certificateDirectory}/cert-${cfg.fqdn}.pem"
|
then "${cfg.certificateDirectory}/cert-${cfg.fqdn}.pem"
|
||||||
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx"
|
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx"
|
||||||
then "${config.security.acme.certs.${cfg.fqdn}.directory}/fullchain.pem"
|
then "${config.security.acme.certs.${cfg.acmeCertificateName}.directory}/fullchain.pem"
|
||||||
else throw "unknown certificate scheme";
|
else throw "unknown certificate scheme";
|
||||||
|
|
||||||
# key :: PATH
|
# key :: PATH
|
||||||
|
@ -35,7 +35,7 @@ in
|
||||||
else if cfg.certificateScheme == "selfsigned"
|
else if cfg.certificateScheme == "selfsigned"
|
||||||
then "${cfg.certificateDirectory}/key-${cfg.fqdn}.pem"
|
then "${cfg.certificateDirectory}/key-${cfg.fqdn}.pem"
|
||||||
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx"
|
else if cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx"
|
||||||
then "${config.security.acme.certs.${cfg.fqdn}.directory}/key.pem"
|
then "${config.security.acme.certs.${cfg.acmeCertificateName}.directory}/key.pem"
|
||||||
else throw "unknown certificate scheme";
|
else throw "unknown certificate scheme";
|
||||||
|
|
||||||
passwordFiles = let
|
passwordFiles = let
|
||||||
|
@ -49,7 +49,7 @@ in
|
||||||
# Appends the LDAP bind password to files to avoid writing this
|
# Appends the LDAP bind password to files to avoid writing this
|
||||||
# password into the Nix store.
|
# password into the Nix store.
|
||||||
appendLdapBindPwd = {
|
appendLdapBindPwd = {
|
||||||
name, file, prefix, passwordFile, destination
|
name, file, prefix, suffix ? "", passwordFile, destination
|
||||||
}: pkgs.writeScript "append-ldap-bind-pwd-in-${name}" ''
|
}: pkgs.writeScript "append-ldap-bind-pwd-in-${name}" ''
|
||||||
#!${pkgs.stdenv.shell}
|
#!${pkgs.stdenv.shell}
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
@ -61,8 +61,9 @@ in
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cat ${file} > ${destination}
|
cat ${file} > ${destination}
|
||||||
echo -n "${prefix}" >> ${destination}
|
echo -n '${prefix}' >> ${destination}
|
||||||
cat ${passwordFile} >> ${destination}
|
cat ${passwordFile} >> ${destination}
|
||||||
|
echo -n '${suffix}' >> ${destination}
|
||||||
chmod 600 ${destination}
|
chmod 600 ${destination}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,11 @@ let
|
||||||
bool2int = x: if x then "1" else "0";
|
bool2int = x: if x then "1" else "0";
|
||||||
|
|
||||||
maildirLayoutAppendix = lib.optionalString cfg.useFsLayout ":LAYOUT=fs";
|
maildirLayoutAppendix = lib.optionalString cfg.useFsLayout ":LAYOUT=fs";
|
||||||
|
maildirUTF8FolderNames = lib.optionalString cfg.useUTF8FolderNames ":UTF-8";
|
||||||
|
|
||||||
# maildir in format "/${domain}/${user}"
|
# maildir in format "/${domain}/${user}"
|
||||||
dovecotMaildir =
|
dovecotMaildir =
|
||||||
"maildir:${cfg.mailDirectory}/%d/%n${maildirLayoutAppendix}"
|
"maildir:${cfg.mailDirectory}/%d/%n${maildirLayoutAppendix}${maildirUTF8FolderNames}"
|
||||||
+ (lib.optionalString (cfg.indexDir != null)
|
+ (lib.optionalString (cfg.indexDir != null)
|
||||||
":INDEX=${cfg.indexDir}/%d/%n"
|
":INDEX=${cfg.indexDir}/%d/%n"
|
||||||
);
|
);
|
||||||
|
@ -75,7 +76,7 @@ let
|
||||||
auth_bind = yes
|
auth_bind = yes
|
||||||
base = ${cfg.ldap.searchBase}
|
base = ${cfg.ldap.searchBase}
|
||||||
scope = ${mkLdapSearchScope cfg.ldap.searchScope}
|
scope = ${mkLdapSearchScope cfg.ldap.searchScope}
|
||||||
${lib.optionalString (cfg.ldap.dovecot.userAttrs != "") ''
|
${lib.optionalString (cfg.ldap.dovecot.userAttrs != null) ''
|
||||||
user_attrs = ${cfg.ldap.dovecot.userAttrs}
|
user_attrs = ${cfg.ldap.dovecot.userAttrs}
|
||||||
''}
|
''}
|
||||||
user_filter = ${cfg.ldap.dovecot.userFilter}
|
user_filter = ${cfg.ldap.dovecot.userFilter}
|
||||||
|
@ -89,7 +90,8 @@ let
|
||||||
setPwdInLdapConfFile = appendLdapBindPwd {
|
setPwdInLdapConfFile = appendLdapBindPwd {
|
||||||
name = "ldap-conf-file";
|
name = "ldap-conf-file";
|
||||||
file = ldapConfig;
|
file = ldapConfig;
|
||||||
prefix = "dnpass = ";
|
prefix = ''dnpass = "'';
|
||||||
|
suffix = ''"'';
|
||||||
passwordFile = cfg.ldap.bind.passwordFile;
|
passwordFile = cfg.ldap.bind.passwordFile;
|
||||||
destination = ldapConfFile;
|
destination = ldapConfFile;
|
||||||
};
|
};
|
||||||
|
@ -104,6 +106,9 @@ let
|
||||||
chmod 755 "${passwdDir}"
|
chmod 755 "${passwdDir}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Prevent world-readable password files, even temporarily.
|
||||||
|
umask 077
|
||||||
|
|
||||||
for f in ${builtins.toString (lib.mapAttrsToList (name: value: passwordFiles."${name}") cfg.loginAccounts)}; do
|
for f in ${builtins.toString (lib.mapAttrsToList (name: value: passwordFiles."${name}") cfg.loginAccounts)}; do
|
||||||
if [ ! -f "$f" ]; then
|
if [ ! -f "$f" ]; then
|
||||||
echo "Expected password hash file $f does not exist!"
|
echo "Expected password hash file $f does not exist!"
|
||||||
|
@ -125,9 +130,6 @@ let
|
||||||
else "")
|
else "")
|
||||||
) cfg.loginAccounts)}
|
) cfg.loginAccounts)}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
chmod 600 ${passwdFile}
|
|
||||||
chmod 600 ${userdbFile}
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
junkMailboxes = builtins.attrNames (lib.filterAttrs (n: v: v ? "specialUse" && v.specialUse == "Junk") cfg.mailboxes);
|
junkMailboxes = builtins.attrNames (lib.filterAttrs (n: v: v ? "specialUse" && v.specialUse == "Junk") cfg.mailboxes);
|
||||||
|
@ -151,6 +153,13 @@ in
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# for sieve-test. Shelling it in on demand usually doesnt' work, as it reads
|
||||||
|
# the global config and tries to open shared libraries configured in there,
|
||||||
|
# which are usually not compatible.
|
||||||
|
environment.systemPackages = [
|
||||||
|
pkgs.dovecot_pigeonhole
|
||||||
|
];
|
||||||
|
|
||||||
services.dovecot2 = {
|
services.dovecot2 = {
|
||||||
enable = true;
|
enable = true;
|
||||||
enableImap = enableImap || enableImapSsl;
|
enableImap = enableImap || enableImapSsl;
|
||||||
|
@ -167,8 +176,18 @@ in
|
||||||
mailPlugins.globally.enable = lib.optionals cfg.fullTextSearch.enable [ "fts" "fts_xapian" ];
|
mailPlugins.globally.enable = lib.optionals cfg.fullTextSearch.enable [ "fts" "fts_xapian" ];
|
||||||
protocols = lib.optional cfg.enableManageSieve "sieve";
|
protocols = lib.optional cfg.enableManageSieve "sieve";
|
||||||
|
|
||||||
sieveScripts = {
|
pluginSettings = {
|
||||||
after = builtins.toFile "spam.sieve" ''
|
sieve = "file:${cfg.sieveDirectory}/%u/scripts;active=${cfg.sieveDirectory}/%u/active.sieve";
|
||||||
|
sieve_default = "file:${cfg.sieveDirectory}/%u/default.sieve";
|
||||||
|
sieve_default_name = "default";
|
||||||
|
};
|
||||||
|
|
||||||
|
sieve = {
|
||||||
|
extensions = [
|
||||||
|
"fileinto"
|
||||||
|
];
|
||||||
|
|
||||||
|
scripts.after = builtins.toFile "spam.sieve" ''
|
||||||
require "fileinto";
|
require "fileinto";
|
||||||
|
|
||||||
if header :is "X-Spam" "Yes" {
|
if header :is "X-Spam" "Yes" {
|
||||||
|
@ -176,8 +195,29 @@ in
|
||||||
stop;
|
stop;
|
||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
pipeBins = map lib.getExe [
|
||||||
|
(pkgs.writeShellScriptBin "sa-learn-ham.sh"
|
||||||
|
"exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_ham")
|
||||||
|
(pkgs.writeShellScriptBin "sa-learn-spam.sh"
|
||||||
|
"exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_spam")
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
imapsieve.mailbox = [
|
||||||
|
{
|
||||||
|
name = junkMailboxName;
|
||||||
|
causes = [ "COPY" "APPEND" ];
|
||||||
|
before = ./dovecot/imap_sieve/report-spam.sieve;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "*";
|
||||||
|
from = junkMailboxName;
|
||||||
|
causes = [ "COPY" ];
|
||||||
|
before = ./dovecot/imap_sieve/report-ham.sieve;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
mailboxes = cfg.mailboxes;
|
mailboxes = cfg.mailboxes;
|
||||||
|
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
|
@ -299,28 +339,6 @@ in
|
||||||
inbox = yes
|
inbox = yes
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin {
|
|
||||||
sieve_plugins = sieve_imapsieve sieve_extprograms
|
|
||||||
sieve = file:${cfg.sieveDirectory}/%u/scripts;active=${cfg.sieveDirectory}/%u/active.sieve
|
|
||||||
sieve_default = file:${cfg.sieveDirectory}/%u/default.sieve
|
|
||||||
sieve_default_name = default
|
|
||||||
|
|
||||||
# From elsewhere to Spam folder
|
|
||||||
imapsieve_mailbox1_name = ${junkMailboxName}
|
|
||||||
imapsieve_mailbox1_causes = COPY,APPEND
|
|
||||||
imapsieve_mailbox1_before = file:${stateDir}/imap_sieve/report-spam.sieve
|
|
||||||
|
|
||||||
# From Spam folder to elsewhere
|
|
||||||
imapsieve_mailbox2_name = *
|
|
||||||
imapsieve_mailbox2_from = ${junkMailboxName}
|
|
||||||
imapsieve_mailbox2_causes = COPY
|
|
||||||
imapsieve_mailbox2_before = file:${stateDir}/imap_sieve/report-ham.sieve
|
|
||||||
|
|
||||||
sieve_pipe_bin_dir = ${pipeBin}/pipe/bin
|
|
||||||
|
|
||||||
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
|
|
||||||
}
|
|
||||||
|
|
||||||
${lib.optionalString cfg.fullTextSearch.enable ''
|
${lib.optionalString cfg.fullTextSearch.enable ''
|
||||||
plugin {
|
plugin {
|
||||||
plugin = fts fts_xapian
|
plugin = fts fts_xapian
|
||||||
|
@ -349,13 +367,6 @@ in
|
||||||
systemd.services.dovecot2 = {
|
systemd.services.dovecot2 = {
|
||||||
preStart = ''
|
preStart = ''
|
||||||
${genPasswdScript}
|
${genPasswdScript}
|
||||||
rm -rf '${stateDir}/imap_sieve'
|
|
||||||
mkdir '${stateDir}/imap_sieve'
|
|
||||||
cp -p "${./dovecot/imap_sieve}"/*.sieve '${stateDir}/imap_sieve/'
|
|
||||||
for k in "${stateDir}/imap_sieve"/*.sieve ; do
|
|
||||||
${pkgs.dovecot_pigeonhole}/bin/sievec "$k"
|
|
||||||
done
|
|
||||||
chown -R '${dovecot2Cfg.mailUser}:${dovecot2Cfg.mailGroup}' '${stateDir}/imap_sieve'
|
|
||||||
'' + (lib.optionalString cfg.ldap.enable setPwdInLdapConfFile);
|
'' + (lib.optionalString cfg.ldap.enable setPwdInLdapConfFile);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,10 @@
|
||||||
|
|
||||||
{ config, pkgs, lib, ... }:
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
with (import ./common.nix { inherit config; });
|
with (import ./common.nix { inherit config lib pkgs; });
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.mailserver;
|
cfg = config.mailserver;
|
||||||
acmeRoot = "/var/lib/acme/acme-challenge";
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = lib.mkIf (cfg.enable && (cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx")) {
|
config = lib.mkIf (cfg.enable && (cfg.certificateScheme == "acme" || cfg.certificateScheme == "acme-nginx")) {
|
||||||
|
@ -32,11 +31,10 @@ in
|
||||||
serverAliases = cfg.certificateDomains;
|
serverAliases = cfg.certificateDomains;
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
acmeRoot = acmeRoot;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
security.acme.certs."${cfg.fqdn}".reloadServices = [
|
security.acme.certs."${cfg.acmeCertificateName}".reloadServices = [
|
||||||
"postfix.service"
|
"postfix.service"
|
||||||
"dovecot2.service"
|
"dovecot2.service"
|
||||||
];
|
];
|
||||||
|
|
|
@ -33,6 +33,11 @@ let
|
||||||
let to = name;
|
let to = name;
|
||||||
in map (from: {"${from}" = to;}) (value.aliases ++ lib.singleton name))
|
in map (from: {"${from}" = to;}) (value.aliases ++ lib.singleton name))
|
||||||
cfg.loginAccounts));
|
cfg.loginAccounts));
|
||||||
|
regex_valiases_postfix = mergeLookupTables (lib.flatten (lib.mapAttrsToList
|
||||||
|
(name: value:
|
||||||
|
let to = name;
|
||||||
|
in map (from: {"${from}" = to;}) value.aliasesRegexp)
|
||||||
|
cfg.loginAccounts));
|
||||||
|
|
||||||
# catchAllPostfix :: Map String [String]
|
# catchAllPostfix :: Map String [String]
|
||||||
catchAllPostfix = mergeLookupTables (lib.flatten (lib.mapAttrsToList
|
catchAllPostfix = mergeLookupTables (lib.flatten (lib.mapAttrsToList
|
||||||
|
@ -65,6 +70,10 @@ let
|
||||||
content = lookupTableToString (mergeLookupTables [all_valiases_postfix catchAllPostfix]);
|
content = lookupTableToString (mergeLookupTables [all_valiases_postfix catchAllPostfix]);
|
||||||
in builtins.toFile "valias" content;
|
in builtins.toFile "valias" content;
|
||||||
|
|
||||||
|
regex_valiases_file = let
|
||||||
|
content = lookupTableToString regex_valiases_postfix;
|
||||||
|
in builtins.toFile "regex_valias" content;
|
||||||
|
|
||||||
# denied_recipients_postfix :: [ String ]
|
# denied_recipients_postfix :: [ String ]
|
||||||
denied_recipients_postfix = (map
|
denied_recipients_postfix = (map
|
||||||
(acct: "${acct.name} REJECT ${acct.sendOnlyRejectMessage}")
|
(acct: "${acct.name} REJECT ${acct.sendOnlyRejectMessage}")
|
||||||
|
@ -94,6 +103,7 @@ let
|
||||||
# every alias is owned (uniquely) by its user.
|
# every alias is owned (uniquely) by its user.
|
||||||
# The user's own address is already in all_valiases_postfix.
|
# The user's own address is already in all_valiases_postfix.
|
||||||
vaccounts_file = builtins.toFile "vaccounts" (lookupTableToString all_valiases_postfix);
|
vaccounts_file = builtins.toFile "vaccounts" (lookupTableToString all_valiases_postfix);
|
||||||
|
regex_vaccounts_file = builtins.toFile "regex_vaccounts" (lookupTableToString regex_valiases_postfix);
|
||||||
|
|
||||||
submissionHeaderCleanupRules = pkgs.writeText "submission_header_cleanup_rules" (''
|
submissionHeaderCleanupRules = pkgs.writeText "submission_header_cleanup_rules" (''
|
||||||
# Removes sensitive headers from mails handed in via the submission port.
|
# Removes sensitive headers from mails handed in via the submission port.
|
||||||
|
@ -123,6 +133,7 @@ let
|
||||||
policyd-spf = pkgs.writeText "policyd-spf.conf" cfg.policydSPFExtraConfig;
|
policyd-spf = pkgs.writeText "policyd-spf.conf" cfg.policydSPFExtraConfig;
|
||||||
|
|
||||||
mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
|
mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
|
||||||
|
mappedRegexFile = name: "pcre:/var/lib/postfix/conf/${name}";
|
||||||
|
|
||||||
submissionOptions =
|
submissionOptions =
|
||||||
{
|
{
|
||||||
|
@ -133,7 +144,7 @@ let
|
||||||
smtpd_sasl_security_options = "noanonymous";
|
smtpd_sasl_security_options = "noanonymous";
|
||||||
smtpd_sasl_local_domain = "$myhostname";
|
smtpd_sasl_local_domain = "$myhostname";
|
||||||
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
|
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
|
||||||
smtpd_sender_login_maps = "hash:/etc/postfix/vaccounts${lib.optionalString cfg.ldap.enable ",ldap:${ldapSenderLoginMapFile}"}";
|
smtpd_sender_login_maps = "hash:/etc/postfix/vaccounts${lib.optionalString cfg.ldap.enable ",ldap:${ldapSenderLoginMapFile}"}${lib.optionalString (regex_valiases_postfix != {}) ",pcre:/etc/postfix/regex_vaccounts"}";
|
||||||
smtpd_sender_restrictions = "reject_sender_login_mismatch";
|
smtpd_sender_restrictions = "reject_sender_login_mismatch";
|
||||||
smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
|
smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
|
||||||
cleanup_service_name = "submission-header-cleanup";
|
cleanup_service_name = "submission-header-cleanup";
|
||||||
|
@ -197,7 +208,9 @@ in
|
||||||
hostname = "${sendingFqdn}";
|
hostname = "${sendingFqdn}";
|
||||||
networksStyle = "host";
|
networksStyle = "host";
|
||||||
mapFiles."valias" = valiases_file;
|
mapFiles."valias" = valiases_file;
|
||||||
|
mapFiles."regex_valias" = regex_valiases_file;
|
||||||
mapFiles."vaccounts" = vaccounts_file;
|
mapFiles."vaccounts" = vaccounts_file;
|
||||||
|
mapFiles."regex_vaccounts" = regex_vaccounts_file;
|
||||||
mapFiles."denied_recipients" = denied_recipients_file;
|
mapFiles."denied_recipients" = denied_recipients_file;
|
||||||
mapFiles."reject_senders" = reject_senders_file;
|
mapFiles."reject_senders" = reject_senders_file;
|
||||||
mapFiles."reject_recipients" = reject_recipients_file;
|
mapFiles."reject_recipients" = reject_recipients_file;
|
||||||
|
@ -224,7 +237,12 @@ in
|
||||||
(mappedFile "valias")
|
(mappedFile "valias")
|
||||||
] ++ lib.optionals (cfg.ldap.enable) [
|
] ++ lib.optionals (cfg.ldap.enable) [
|
||||||
"ldap:${ldapVirtualMailboxMapFile}"
|
"ldap:${ldapVirtualMailboxMapFile}"
|
||||||
|
] ++ lib.optionals (regex_valiases_postfix != {}) [
|
||||||
|
(mappedRegexFile "regex_valias")
|
||||||
];
|
];
|
||||||
|
virtual_alias_maps = lib.mkAfter (lib.optionals (regex_valiases_postfix != {}) [
|
||||||
|
(mappedRegexFile "regex_valias")
|
||||||
|
]);
|
||||||
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
|
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
|
||||||
# Avoid leakage of X-Original-To, X-Delivered-To headers between recipients
|
# Avoid leakage of X-Original-To, X-Delivered-To headers between recipients
|
||||||
lmtp_destination_recipient_limit = "1";
|
lmtp_destination_recipient_limit = "1";
|
||||||
|
@ -256,9 +274,6 @@ in
|
||||||
# Submission by mail clients is handled in submissionOptions
|
# Submission by mail clients is handled in submissionOptions
|
||||||
smtpd_tls_security_level = "may";
|
smtpd_tls_security_level = "may";
|
||||||
|
|
||||||
# strong might suffice and is computationally less expensive
|
|
||||||
smtpd_tls_eecdh_grade = "ultra";
|
|
||||||
|
|
||||||
# Disable obselete protocols
|
# Disable obselete protocols
|
||||||
smtpd_tls_protocols = "TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
|
smtpd_tls_protocols = "TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
|
||||||
smtp_tls_protocols = "TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
|
smtp_tls_protocols = "TLSv1.3, TLSv1.2, TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
|
||||||
|
@ -291,6 +306,9 @@ in
|
||||||
milter_protocol = "6";
|
milter_protocol = "6";
|
||||||
milter_mail_macros = "i {mail_addr} {client_addr} {client_name} {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}";
|
milter_mail_macros = "i {mail_addr} {client_addr} {client_name} {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}";
|
||||||
|
|
||||||
|
# Fix for https://www.postfix.org/smtp-smuggling.html
|
||||||
|
smtpd_forbid_bare_newline = cfg.smtpdForbidBareNewline;
|
||||||
|
smtpd_forbid_bare_newline_exclusions = "$mynetworks";
|
||||||
};
|
};
|
||||||
|
|
||||||
submissionOptions = submissionOptions;
|
submissionOptions = submissionOptions;
|
||||||
|
@ -307,7 +325,7 @@ in
|
||||||
privileged = true;
|
privileged = true;
|
||||||
chroot = false;
|
chroot = false;
|
||||||
command = "spawn";
|
command = "spawn";
|
||||||
args = [ "user=nobody" "argv=${pkgs.pypolicyd-spf}/bin/policyd-spf" "${policyd-spf}"];
|
args = [ "user=nobody" "argv=${pkgs.spf-engine}/bin/policyd-spf" "${policyd-spf}"];
|
||||||
};
|
};
|
||||||
"submission-header-cleanup" = {
|
"submission-header-cleanup" = {
|
||||||
type = "unix";
|
type = "unix";
|
||||||
|
|
|
@ -30,7 +30,7 @@ in
|
||||||
inherit debug;
|
inherit debug;
|
||||||
locals = {
|
locals = {
|
||||||
"milter_headers.conf" = { text = ''
|
"milter_headers.conf" = { text = ''
|
||||||
extended_spam_headers = yes;
|
extended_spam_headers = true;
|
||||||
''; };
|
''; };
|
||||||
"redis.conf" = { text = ''
|
"redis.conf" = { text = ''
|
||||||
servers = "${cfg.redis.address}:${toString cfg.redis.port}";
|
servers = "${cfg.redis.address}:${toString cfg.redis.port}";
|
||||||
|
@ -69,14 +69,6 @@ in
|
||||||
''; };
|
''; };
|
||||||
};
|
};
|
||||||
|
|
||||||
overrides = {
|
|
||||||
"milter_headers.conf" = {
|
|
||||||
text = ''
|
|
||||||
extended_spam_headers = true;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
workers.rspamd_proxy = {
|
workers.rspamd_proxy = {
|
||||||
type = "rspamd_proxy";
|
type = "rspamd_proxy";
|
||||||
bindSockets = [{
|
bindSockets = [{
|
||||||
|
|
|
@ -64,6 +64,8 @@ in
|
||||||
in ''
|
in ''
|
||||||
# Create mail directory and set permissions. See
|
# Create mail directory and set permissions. See
|
||||||
# <http://wiki2.dovecot.org/SharedMailboxes/Permissions>.
|
# <http://wiki2.dovecot.org/SharedMailboxes/Permissions>.
|
||||||
|
# Prevent world-readable paths, even temporarily.
|
||||||
|
umask 007
|
||||||
mkdir -p ${directories}
|
mkdir -p ${directories}
|
||||||
chgrp "${vmailGroupName}" ${directories}
|
chgrp "${vmailGroupName}" ${directories}
|
||||||
chmod 02770 ${directories}
|
chmod 02770 ${directories}
|
||||||
|
|
|
@ -34,6 +34,9 @@ let
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Prevent world-readable paths, even temporarily.
|
||||||
|
umask 007
|
||||||
|
|
||||||
# Create directory to store user sieve scripts if it doesn't exist
|
# Create directory to store user sieve scripts if it doesn't exist
|
||||||
if (! test -d "${sieveDirectory}"); then
|
if (! test -d "${sieveDirectory}"); then
|
||||||
mkdir "${sieveDirectory}"
|
mkdir "${sieveDirectory}"
|
||||||
|
|
|
@ -501,7 +501,6 @@ pkgs.nixosTest {
|
||||||
|
|
||||||
with subtest("dmarc reporting"):
|
with subtest("dmarc reporting"):
|
||||||
server.systemctl("start rspamd-dmarc-reporter.service")
|
server.systemctl("start rspamd-dmarc-reporter.service")
|
||||||
server.wait_until_succeeds("journalctl -eu rspamd-dmarc-reporter.service -o cat | grep -q 'No reports for '")
|
|
||||||
|
|
||||||
with subtest("no warnings or errors"):
|
with subtest("no warnings or errors"):
|
||||||
server.fail("journalctl -u postfix | grep -i error >&2")
|
server.fail("journalctl -u postfix | grep -i error >&2")
|
||||||
|
@ -509,7 +508,7 @@ pkgs.nixosTest {
|
||||||
server.fail("journalctl -u dovecot2 | grep -i error >&2")
|
server.fail("journalctl -u dovecot2 | grep -i error >&2")
|
||||||
# harmless ? https://dovecot.org/pipermail/dovecot/2020-August/119575.html
|
# harmless ? https://dovecot.org/pipermail/dovecot/2020-August/119575.html
|
||||||
server.fail(
|
server.fail(
|
||||||
"journalctl -u dovecot2 |grep -v 'Expunged message reappeared, giving a new UID'| grep -i warning >&2"
|
"journalctl -u dovecot2 |grep -v 'Expunged message reappeared, giving a new UID'| grep -v 'FTS Xapian: Box is empty' | grep -i warning >&2"
|
||||||
)
|
)
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ pkgs.nixosTest {
|
||||||
mailserver = {
|
mailserver = {
|
||||||
enable = true;
|
enable = true;
|
||||||
fqdn = "mail.example.com";
|
fqdn = "mail.example.com";
|
||||||
domains = [ "example.com" ];
|
domains = [ "example.com" "domain.com" ];
|
||||||
localDnsResolver = false;
|
localDnsResolver = false;
|
||||||
|
|
||||||
loginAccounts = {
|
loginAccounts = {
|
||||||
|
@ -64,6 +64,7 @@ pkgs.nixosTest {
|
||||||
};
|
};
|
||||||
"user2@example.com" = {
|
"user2@example.com" = {
|
||||||
hashedPasswordFile = hashedPasswordFile;
|
hashedPasswordFile = hashedPasswordFile;
|
||||||
|
aliasesRegexp = [''/^user2.*@domain\.com$/''];
|
||||||
};
|
};
|
||||||
"send-only@example.com" = {
|
"send-only@example.com" = {
|
||||||
hashedPasswordFile = hashPassword "send-only";
|
hashedPasswordFile = hashPassword "send-only";
|
||||||
|
@ -126,6 +127,46 @@ pkgs.nixosTest {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
with subtest("regex email alias are received"):
|
||||||
|
# A mail sent to user2-regex-alias@domain.com is in the user2@example.com mailbox
|
||||||
|
machine.succeed(
|
||||||
|
" ".join(
|
||||||
|
[
|
||||||
|
"mail-check send-and-read",
|
||||||
|
"--smtp-port 587",
|
||||||
|
"--smtp-starttls",
|
||||||
|
"--smtp-host localhost",
|
||||||
|
"--imap-host localhost",
|
||||||
|
"--imap-username user2@example.com",
|
||||||
|
"--from-addr user1@example.com",
|
||||||
|
"--to-addr user2-regex-alias@domain.com",
|
||||||
|
"--src-password-file ${passwordFile}",
|
||||||
|
"--dst-password-file ${passwordFile}",
|
||||||
|
"--ignore-dkim-spf",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
with subtest("user can send from regex email alias"):
|
||||||
|
# A mail sent from user2-regex-alias@domain.com, using user2@example.com credentials is received
|
||||||
|
machine.succeed(
|
||||||
|
" ".join(
|
||||||
|
[
|
||||||
|
"mail-check send-and-read",
|
||||||
|
"--smtp-port 587",
|
||||||
|
"--smtp-starttls",
|
||||||
|
"--smtp-host localhost",
|
||||||
|
"--imap-host localhost",
|
||||||
|
"--smtp-username user2@example.com",
|
||||||
|
"--from-addr user2-regex-alias@domain.com",
|
||||||
|
"--to-addr user1@example.com",
|
||||||
|
"--src-password-file ${passwordFile}",
|
||||||
|
"--dst-password-file ${passwordFile}",
|
||||||
|
"--ignore-dkim-spf",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
with subtest("vmail gid is set correctly"):
|
with subtest("vmail gid is set correctly"):
|
||||||
machine.succeed("getent group vmail | grep 5000")
|
machine.succeed("getent group vmail | grep 5000")
|
||||||
|
|
||||||
|
@ -136,7 +177,7 @@ pkgs.nixosTest {
|
||||||
"set +e; timeout 1 ${pkgs.netcat}/bin/nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]"
|
"set +e; timeout 1 ${pkgs.netcat}/bin/nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]"
|
||||||
)
|
)
|
||||||
machine.succeed(
|
machine.succeed(
|
||||||
"cat ${sendMail} | ${pkgs.netcat-gnu}/bin/nc localhost 25 | grep -q 'This account cannot receive emails'"
|
"cat ${sendMail} | ${pkgs.netcat-gnu}/bin/nc localhost 25 | grep -q '554 5.5.0 Error'"
|
||||||
)
|
)
|
||||||
|
|
||||||
with subtest("rspamd controller serves web ui"):
|
with subtest("rspamd controller serves web ui"):
|
||||||
|
|
|
@ -30,7 +30,12 @@ let
|
||||||
};
|
};
|
||||||
services.dnsmasq = {
|
services.dnsmasq = {
|
||||||
enable = true;
|
enable = true;
|
||||||
settings.mx-host = [ "domain1.com,domain1,10" "domain2.com,domain2,10" ];
|
# Fixme: once nixos-22.11 has been removed, could be replaced by
|
||||||
|
# settings.mx-host = [ "domain1.com,domain1,10" "domain2.com,domain2,10" ];
|
||||||
|
extraConfig = ''
|
||||||
|
mx-host=domain1.com,domain1,10
|
||||||
|
mx-host=domain2.com,domain2,10
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue