diff --git a/README.md b/README.md index e8367d4..16ad813 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,6 @@ SNM branch corresponding to your NixOS version. * [ ] [Mobileconfig](https://support.apple.com/guide/profile-manager/distribute-profiles-manually-pmdbd71ebc9/mac) * Improve the Forwarding Experience * [ ] Support [ARC](https://en.wikipedia.org/wiki/Authenticated_Received_Chain) signing with [Rspamd](https://rspamd.com/doc/modules/arc.html) -* User management - * [ ] Allow local and LDAP user to coexist * OpenID Connect * Depends on relevant clients adding support, e.g. [Thunderbird](https://bugzilla.mozilla.org/show_bug.cgi?id=1602166) diff --git a/default.nix b/default.nix index 0aa69fb..8461eb1 100644 --- a/default.nix +++ b/default.nix @@ -649,7 +649,7 @@ in ''; }; - extraVirtualAliases = mkOption { + aliases = mkOption { type = let loginAccount = mkOptionType { @@ -660,7 +660,6 @@ in with types; attrsOf (either loginAccount (nonEmptyListOf loginAccount)); example = { - "info@example.com" = "user1@example.com"; "postmaster@example.com" = "user1@example.com"; "abuse@example.com" = "user1@example.com"; "multi@example.com" = [ @@ -669,15 +668,14 @@ in ]; }; description = '' - Virtual Aliases. A virtual alias `"info@example.com" = "user1@example.com"` means that - all mail to `info@example.com` is forwarded to `user1@example.com`. Note - that it is expected that `postmaster@example.com` and `abuse@example.com` is - forwarded to some valid email address. (Alternatively you can create login - accounts for `postmaster` and (or) `abuse`). Furthermore, it also allows - the user `user1@example.com` to send emails as `info@example.com`. - It's also possible to create an alias for multiple accounts. In this - example all mails for `multi@example.com` will be forwarded to both - `user1@example.com` and `user2@example.com`. + Aliases are additional mail addresses routed to one or more existing local accounts. + + The target accounts are allowed to use the alias as the sender address. + + :::{note} + This feature is limited to local accounts and does not support LDAP or + other external accounts. + ::: ''; default = { }; }; @@ -685,16 +683,18 @@ in forwards = mkOption { type = with types; attrsOf (either (listOf str) str); example = { - "user@example.com" = "user@elsewhere.com"; + "user@example.com" = "user@example.edu"; + "gamenight@example.com" = [ + "bob@example.com" + "frank@example.org" + "wendy@example.net" + ]; }; description = '' - To forward mails to an external address. For instance, - the value `{ "user@example.com" = "user@elsewhere.com"; }` - means that mails to `user@example.com` are forwarded to - `user@elsewhere.com`. The difference with the - {option}`mailserver.extraVirtualAliases` option is that `user@elsewhere.com` - can't send mail as `user@example.com`. Also, this option - allows to forward mails to external addresses. + Forwards route mail from local addresses to one or more local or external addresses. + + Unlike {option}`mailserver.aliases`, the target addresses cannot send + mail using the forward address. ''; default = { }; }; @@ -1681,5 +1681,6 @@ in [ "mailserver" "ldap" "postfix" "mailAttribute" ] [ "mailserver" "ldap" "attributes" "mail" ] ) + (mkRenamedOptionModule [ "mailserver" "extraVirtualAliases" ] [ "mailserver" "aliases" ]) ]; } diff --git a/docs/ldap.rst b/docs/ldap.rst index 4750e31..41b61ba 100644 --- a/docs/ldap.rst +++ b/docs/ldap.rst @@ -40,15 +40,25 @@ follow best practices to simplify maintenance. Limitations ~~~~~~~~~~~ -We have various assertions in place, that prevent using LDAP together with -other features. Most of them are not technical limitations per se, but instead -lack configuration or validation. +Design choices +^^^^^^^^^^^^^^ -- Local users (:option:`mailserver.loginAccounts`) and aliases - (:option:`mailserver.extraVirtualAliases`) are not currently allowed with - :option:`mailserver.ldap.enable` enabled -- Aliases based on LDAP attributes are currently not implemented -- Quotas based on LDAP attributes are currently not implemented +These are intentional choices in how the mail server operates that affect the +LDAP integration. + +- For mail address routing local accounts always take priority over LDAP accounts. + +Planned +^^^^^^^ + +These are features we are interested in but require implementation, +documentation and tests. + +- Aliases based on LDAP attributes +- Quotas based on LDAP attributes + +Avoided +^^^^^^^ The following features will likely never be implemented, since they would complicate the setup significantly. @@ -58,7 +68,9 @@ complicate the setup significantly. - Use of ``homeDirectory``, ``uid``, ``gid`` LDAP attributes (we are committed to a virtual setup with one vmail user/uid/gid and UUID based home directories) - +- Declarative aliases through :option:`mailserver.aliases`. These are limited + to local accounts, because Postfix enforces sender ownership based on login + identity and does not consult virtual aliases for authorization. Enabling LDAP support ~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/release-notes.rst b/docs/release-notes.rst index e3122d4..9ee6ccc 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -30,6 +30,8 @@ NixOS 26.05 rather than their email address, which is more convenient and consistent with typical LDAP practices. The exact attribute can be customized through :option:`mailserver.ldap.attributes.username`. +- Local and LDAP accounts can now co-exist. For overlapping names and addresses + the local account will always win. - The following integrations are deprecated and will be removed before the next release: diff --git a/mail-server/assertions.nix b/mail-server/assertions.nix index 8a346ad..a3c2428 100644 --- a/mail-server/assertions.nix +++ b/mail-server/assertions.nix @@ -98,16 +98,6 @@ in ) config.mailserver.dkim.domains ) ) - ++ lib.optionals config.mailserver.ldap.enable [ - { - assertion = config.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"; - } - ] ++ lib.optionals (config.mailserver.ldap.enable && config.mailserver.mailDirectory != "/var/vmail") [ diff --git a/mail-server/postfix.nix b/mail-server/postfix.nix index 05794ac..70ae68e 100644 --- a/mail-server/postfix.nix +++ b/mail-server/postfix.nix @@ -94,7 +94,7 @@ let mergeLookupTables lookupTables; # extra_valiases_postfix :: Map String [String] - extra_valiases_postfix = attrsToLookupTable cfg.extraVirtualAliases; + extra_valiases_postfix = attrsToLookupTable cfg.aliases; # forwards :: Map String [String] forwards = attrsToLookupTable cfg.forwards; diff --git a/tests/external.nix b/tests/external.nix index fd2c267..a149144 100644 --- a/tests/external.nix +++ b/tests/external.nix @@ -85,7 +85,7 @@ }; }; - extraVirtualAliases = { + aliases = { "single-alias@example.com" = "user1@example.com"; "multi-alias@example.com" = [ "user1@example.com" @@ -494,7 +494,7 @@ # if this succeeds, it means that user1 received the mail that was intended for chuck. client.fail("fetchmail --nosslcertck -v") - with subtest("extraVirtualAliases"): + with subtest("Test sending from alias address (mailserver.aliases)"): client.execute("rm ~/mail/*") # send email from single-alias to user1 client.succeed( diff --git a/tests/ldap.nix b/tests/ldap.nix index a67362b..40b1390 100644 --- a/tests/ldap.nix +++ b/tests/ldap.nix @@ -1,7 +1,25 @@ +{ + pkgs, + ... +}: let + hashPassword = + password: + pkgs.runCommand "password-${password}-hashed" + { + buildInputs = [ pkgs.mkpasswd ]; + inherit password; + } + '' + mkpasswd -s <<<"$password" > $out + ''; + bindPassword = "unsafegibberish"; alicePassword = "testalice"; bobPassword = "testbob"; + carolPassword = "testcarol"; + frankPassword = "testfrank"; + malloryPassword = "testmallory"; in { name = "ldap"; @@ -83,6 +101,22 @@ in mail: bob@example.com homeDirectory: /home/bob userPassword: ${bobPassword} + + dn: cn=carol,ou=users,dc=example + entryUUID: 41240499-27e2-4fa2-be4f-4113a77661b1 + objectClass: inetOrgPerson + uid: carol + sn: Baz + mail: carol@example.com + userPassword: ${carolPassword} + + dn: cn=frank,ou=users,dc=example + entryUUID: ca16f594-f6b2-418f-87d3-0d02d746461f + objectClass: inetOrgPerson + uid: frank + sn: Moo + mail: frank@example.com + userPassword: ${frankPassword} ''; }; @@ -93,6 +127,24 @@ in localDnsResolver = false; indexDir = "/var/lib/dovecot/indices"; + aliases = { + # Steal frank@example.com from LDAP user frank + "frank@example.com" = "mallory@example.com"; + }; + + loginAccounts = { + # Colliding local account takes precedence over LDAP account with + # same address. + "carol@example.com" = { + hashedPasswordFile = hashPassword carolPassword; + }; + # Another account used as a virtual alias target to steal + # frank@example.com from the LDAP user frank + "mallory@example.com" = { + hashedPasswordFile = hashPassword malloryPassword; + }; + }; + ldap = { enable = true; uris = [ @@ -237,5 +289,37 @@ in ])) machine.succeed("journalctl -u postfix | grep -q 'Sender address rejected: not owned by user bob'") + with subtest("Local addresses take priority over those learnt from LDAP"): + # carol@example.com is routed to the local user account + machine.succeed(" ".join([ + "mail-check send-and-read", + "--smtp-port 465", + "--smtp-ssl", + "--smtp-host localhost", + "--smtp-username alice", # LDAP user + "--imap-host localhost", + "--imap-username carol@example.com", # Local user + "--from-addr alice@example.com", + "--to-addr carol@example.com", + "--src-password-file <(echo '${alicePassword}')", + "--dst-password-file <(echo '${carolPassword}')", + "--ignore-dkim-spf" + ])) + + # frank@example.com gets routed to mallory@example.com due to a virtual alias + machine.succeed(" ".join([ + "mail-check send-and-read", + "--smtp-port 465", + "--smtp-ssl", + "--smtp-host localhost", + "--smtp-username alice", # LDAP user + "--imap-host localhost", + "--imap-username mallory@example.com", # Local user + "--from-addr alice@example.com", + "--to-addr frank@example.com", + "--src-password-file <(echo '${alicePassword}')", + "--dst-password-file <(echo '${malloryPassword}')", + "--ignore-dkim-spf" + ])) ''; }