From 762f553643e7db2d610238848fc55c3e104cf5f7 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Thu, 12 Mar 2026 02:33:06 +0100 Subject: [PATCH] ldap: make uid the default account name I fail to understand how mail became the uidAttribute way back when LDAP support was introduced, but it was unintentional and clearly a mistake. The uid attribute is the standard system login name per RFC4519 2.39 and what we default to going forward. --- default.nix | 18 +++++++++++++----- docs/release-notes.rst | 5 +++++ mail-server/postfix.nix | 2 +- tests/ldap.nix | 32 +++++++++++++++++--------------- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/default.nix b/default.nix index d8c39d0..0aa69fb 100644 --- a/default.nix +++ b/default.nix @@ -445,8 +445,11 @@ in dovecot = { userFilter = mkOption { type = types.str; - default = "mail=%{user}"; - example = "(&(objectClass=inetOrgPerson)(mail=%{user}))"; + default = with cfg.ldap.attributes; "(|(${mail}=%{user})(${username}=%{user}))"; + defaultText = literalExpression '' + with config.mailserver.ldap.attributes; "(|(''${mail}=%{user})(''${username}=%{user}))"; + ''; + example = "(|(mail=%{user})(uid=%{user}))"; description = '' LDAP filter used for LMTP delivery from Postfix and post-login information construction, like the home directory. @@ -459,8 +462,13 @@ in passFilter = mkOption { type = types.nullOr types.str; - default = "mail=%{user}"; - example = "(&(objectClass=inetOrgPerson)(mail=%{user}))"; + default = with cfg.ldap.attributes; "${username}=%{user}"; + defaultText = lib.literalExpression '' + with config.mailserver.ldap.attributes; "''${username}=%{user}"; + ''; + example = + with cfg.ldap.attributes; + "(&(memberOf=cn=mail_users,ou=groups,dc=example,dc=com)(${username}=%{user}))"; description = '' LDAP filter used to restrict which users are eligible to authenticate against Dovecot. @@ -479,7 +487,7 @@ in defaultText = lib.literalExpression '' with config.mailserver.ldap.attributes; "''${mail}=%s"; ''; - example = "(&(objectClass=inetOrgPerson)(mail=%s))"; + example = "(mail=%s)"; description = '' LDAP filter used to search for an account by mail, where `%s` is a substitute for the address in question. diff --git a/docs/release-notes.rst b/docs/release-notes.rst index c4c0fb9..d1094a4 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -22,6 +22,11 @@ NixOS 26.05 established by `agenix`_/`sops-nix`_ that instead rely on encryption. This option prevents files from leaking in to the Nix store. See :option:`mailserver.loginAccounts..passwordFile`. +- The default login username for LDAP users has changed from the ``mail`` to + the ``uid`` attribute. This allows users to login with their account name + 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`. - The following integrations are deprecated and will be removed before the next release: diff --git a/mail-server/postfix.nix b/mail-server/postfix.nix index bf51b63..d131fba 100644 --- a/mail-server/postfix.nix +++ b/mail-server/postfix.nix @@ -222,7 +222,7 @@ let ldapSenderLoginMap = pkgs.writeText "ldap-sender-login-map.cf" '' ${commonLdapConfig} query_filter = ${cfg.ldap.postfix.filter} - result_attribute = ${cfg.ldap.attributes.mail} + result_attribute = ${cfg.ldap.attributes.username} ''; ldapSenderLoginMapFile = "/run/postfix/ldap-sender-login-map.cf"; appendPwdInSenderLoginMap = appendLdapBindPwd { diff --git a/tests/ldap.nix b/tests/ldap.nix index 7402a67..8349d70 100644 --- a/tests/ldap.nix +++ b/tests/ldap.nix @@ -74,7 +74,7 @@ in dn: cn=alice,ou=users,dc=example entryUUID: c52f777b-a6e8-4507-80f9-c4de47e8520d objectClass: inetOrgPerson - cn: alice + uid: alice sn: Foo mail: alice@example.com userPassword: ${alicePassword} @@ -83,7 +83,6 @@ in entryUUID: f3b4e8ea-087f-42cc-95f0-cbfd99386092 objectClass: inetOrgPerson objectClass: posixAccount - cn: bob uid: bob uidNumber: 9999 gidNumber: 9999 @@ -161,16 +160,19 @@ in raise with subtest("Test postmap lookups"): - test_lookup("postconf virtual_mailbox_maps", "alice@example.com", "alice@example.com") - test_lookup("postconf -P submission/inet/smtpd_sender_login_maps", "alice@example.com", "alice@example.com") + test_lookup("postconf virtual_mailbox_maps", "alice@example.com", "alice") + test_lookup("postconf -P submission/inet/smtpd_sender_login_maps", "alice@example.com", "alice") - test_lookup("postconf virtual_mailbox_maps", "bob@example.com", "bob@example.com") - test_lookup("postconf -P submission/inet/smtpd_sender_login_maps", "bob@example.com", "bob@example.com") + test_lookup("postconf virtual_mailbox_maps", "bob@example.com", "bob") + test_lookup("postconf -P submission/inet/smtpd_sender_login_maps", "bob@example.com", "bob") with subtest("Test doveadm lookups"): machine.succeed("doveadm user -u alice@example.com") machine.succeed("doveadm user -u bob@example.com") + machine.succeed("doveadm user -u alice") + machine.log(machine.succeed("doveadm user -u bob")) + machine.succeed("doveadm user -f uid bob@example.com | grep ${toString nodes.machine.mailserver.vmailUID}") machine.succeed("doveadm user -f gid bob@example.com | grep ${toString nodes.machine.mailserver.vmailUID}") @@ -187,16 +189,16 @@ in "--smtp-port 587", "--smtp-starttls", "--smtp-host localhost", - "--smtp-username alice@example.com", + "--smtp-username alice", "--imap-host localhost", - "--imap-username bob@example.com", + "--imap-username bob", "--from-addr bob@example.com", "--to-addr aliceb@example.com", "--src-password-file <(echo '${alicePassword}')", "--dst-password-file <(echo '${bobPassword}')", "--ignore-dkim-spf" ])) - machine.succeed("journalctl -u postfix | grep -q 'Sender address rejected: not owned by user alice@example.com'") + machine.succeed("journalctl -u postfix | grep -q 'Sender address rejected: not owned by user alice'") with subtest("Test mail delivery via implicit TLS"): machine.succeed(" ".join([ @@ -204,9 +206,9 @@ in "--smtp-port 465", "--smtp-ssl", "--smtp-host localhost", - "--smtp-username alice@example.com", + "--smtp-username alice", "--imap-host localhost", - "--imap-username bob@example.com", + "--imap-username bob", "--from-addr alice@example.com", "--to-addr bob@example.com", "--src-password-file <(echo '${alicePassword}')", @@ -220,9 +222,9 @@ in "--smtp-port 587", "--smtp-starttls", "--smtp-host localhost", - "--smtp-username alice@example.com", + "--smtp-username alice", "--imap-host localhost", - "--imap-username bob@example.com", + "--imap-username bob", "--from-addr alice@example.com", "--to-addr bob_fw@example.com", "--src-password-file <(echo '${alicePassword}')", @@ -236,7 +238,7 @@ in "--smtp-port 465", "--smtp-ssl", "--smtp-host localhost", - "--smtp-username bob@example.com", + "--smtp-username bob", "--imap-host localhost", "--imap-username alice@example.com", "--from-addr bob_fw@example.com", @@ -245,7 +247,7 @@ in "--dst-password-file <(echo '${alicePassword}')", "--ignore-dkim-spf" ])) - machine.succeed("journalctl -u postfix | grep -q 'Sender address rejected: not owned by user bob@example.com'") + machine.succeed("journalctl -u postfix | grep -q 'Sender address rejected: not owned by user bob'") ''; }