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.
This commit is contained in:
Martin Weinelt
2026-03-12 02:33:06 +01:00
parent a87d01ea79
commit 762f553643
4 changed files with 36 additions and 21 deletions
+13 -5
View File
@@ -445,8 +445,11 @@ in
dovecot = { dovecot = {
userFilter = mkOption { userFilter = mkOption {
type = types.str; type = types.str;
default = "mail=%{user}"; default = with cfg.ldap.attributes; "(|(${mail}=%{user})(${username}=%{user}))";
example = "(&(objectClass=inetOrgPerson)(mail=%{user}))"; defaultText = literalExpression ''
with config.mailserver.ldap.attributes; "(|(''${mail}=%{user})(''${username}=%{user}))";
'';
example = "(|(mail=%{user})(uid=%{user}))";
description = '' description = ''
LDAP filter used for LMTP delivery from Postfix and post-login LDAP filter used for LMTP delivery from Postfix and post-login
information construction, like the home directory. information construction, like the home directory.
@@ -459,8 +462,13 @@ in
passFilter = mkOption { passFilter = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = "mail=%{user}"; default = with cfg.ldap.attributes; "${username}=%{user}";
example = "(&(objectClass=inetOrgPerson)(mail=%{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 = '' description = ''
LDAP filter used to restrict which users are eligible to LDAP filter used to restrict which users are eligible to
authenticate against Dovecot. authenticate against Dovecot.
@@ -479,7 +487,7 @@ in
defaultText = lib.literalExpression '' defaultText = lib.literalExpression ''
with config.mailserver.ldap.attributes; "''${mail}=%s"; with config.mailserver.ldap.attributes; "''${mail}=%s";
''; '';
example = "(&(objectClass=inetOrgPerson)(mail=%s))"; example = "(mail=%s)";
description = '' description = ''
LDAP filter used to search for an account by mail, where `%s` is a LDAP filter used to search for an account by mail, where `%s` is a
substitute for the address in question. substitute for the address in question.
+5
View File
@@ -22,6 +22,11 @@ NixOS 26.05
established by `agenix`_/`sops-nix`_ that instead rely on encryption. This established by `agenix`_/`sops-nix`_ that instead rely on encryption. This
option prevents files from leaking in to the Nix store. option prevents files from leaking in to the Nix store.
See :option:`mailserver.loginAccounts.<name>.passwordFile`. See :option:`mailserver.loginAccounts.<name>.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 - The following integrations are deprecated and will be removed before the next
release: release:
+1 -1
View File
@@ -222,7 +222,7 @@ let
ldapSenderLoginMap = pkgs.writeText "ldap-sender-login-map.cf" '' ldapSenderLoginMap = pkgs.writeText "ldap-sender-login-map.cf" ''
${commonLdapConfig} ${commonLdapConfig}
query_filter = ${cfg.ldap.postfix.filter} 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"; ldapSenderLoginMapFile = "/run/postfix/ldap-sender-login-map.cf";
appendPwdInSenderLoginMap = appendLdapBindPwd { appendPwdInSenderLoginMap = appendLdapBindPwd {
+17 -15
View File
@@ -74,7 +74,7 @@ in
dn: cn=alice,ou=users,dc=example dn: cn=alice,ou=users,dc=example
entryUUID: c52f777b-a6e8-4507-80f9-c4de47e8520d entryUUID: c52f777b-a6e8-4507-80f9-c4de47e8520d
objectClass: inetOrgPerson objectClass: inetOrgPerson
cn: alice uid: alice
sn: Foo sn: Foo
mail: alice@example.com mail: alice@example.com
userPassword: ${alicePassword} userPassword: ${alicePassword}
@@ -83,7 +83,6 @@ in
entryUUID: f3b4e8ea-087f-42cc-95f0-cbfd99386092 entryUUID: f3b4e8ea-087f-42cc-95f0-cbfd99386092
objectClass: inetOrgPerson objectClass: inetOrgPerson
objectClass: posixAccount objectClass: posixAccount
cn: bob
uid: bob uid: bob
uidNumber: 9999 uidNumber: 9999
gidNumber: 9999 gidNumber: 9999
@@ -161,16 +160,19 @@ in
raise raise
with subtest("Test postmap lookups"): with subtest("Test postmap lookups"):
test_lookup("postconf virtual_mailbox_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@example.com") 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 virtual_mailbox_maps", "bob@example.com", "bob")
test_lookup("postconf -P submission/inet/smtpd_sender_login_maps", "bob@example.com", "bob@example.com") test_lookup("postconf -P submission/inet/smtpd_sender_login_maps", "bob@example.com", "bob")
with subtest("Test doveadm lookups"): with subtest("Test doveadm lookups"):
machine.succeed("doveadm user -u alice@example.com") machine.succeed("doveadm user -u alice@example.com")
machine.succeed("doveadm user -u bob@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 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}") machine.succeed("doveadm user -f gid bob@example.com | grep ${toString nodes.machine.mailserver.vmailUID}")
@@ -187,16 +189,16 @@ in
"--smtp-port 587", "--smtp-port 587",
"--smtp-starttls", "--smtp-starttls",
"--smtp-host localhost", "--smtp-host localhost",
"--smtp-username alice@example.com", "--smtp-username alice",
"--imap-host localhost", "--imap-host localhost",
"--imap-username bob@example.com", "--imap-username bob",
"--from-addr bob@example.com", "--from-addr bob@example.com",
"--to-addr aliceb@example.com", "--to-addr aliceb@example.com",
"--src-password-file <(echo '${alicePassword}')", "--src-password-file <(echo '${alicePassword}')",
"--dst-password-file <(echo '${bobPassword}')", "--dst-password-file <(echo '${bobPassword}')",
"--ignore-dkim-spf" "--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"): with subtest("Test mail delivery via implicit TLS"):
machine.succeed(" ".join([ machine.succeed(" ".join([
@@ -204,9 +206,9 @@ in
"--smtp-port 465", "--smtp-port 465",
"--smtp-ssl", "--smtp-ssl",
"--smtp-host localhost", "--smtp-host localhost",
"--smtp-username alice@example.com", "--smtp-username alice",
"--imap-host localhost", "--imap-host localhost",
"--imap-username bob@example.com", "--imap-username bob",
"--from-addr alice@example.com", "--from-addr alice@example.com",
"--to-addr bob@example.com", "--to-addr bob@example.com",
"--src-password-file <(echo '${alicePassword}')", "--src-password-file <(echo '${alicePassword}')",
@@ -220,9 +222,9 @@ in
"--smtp-port 587", "--smtp-port 587",
"--smtp-starttls", "--smtp-starttls",
"--smtp-host localhost", "--smtp-host localhost",
"--smtp-username alice@example.com", "--smtp-username alice",
"--imap-host localhost", "--imap-host localhost",
"--imap-username bob@example.com", "--imap-username bob",
"--from-addr alice@example.com", "--from-addr alice@example.com",
"--to-addr bob_fw@example.com", "--to-addr bob_fw@example.com",
"--src-password-file <(echo '${alicePassword}')", "--src-password-file <(echo '${alicePassword}')",
@@ -236,7 +238,7 @@ in
"--smtp-port 465", "--smtp-port 465",
"--smtp-ssl", "--smtp-ssl",
"--smtp-host localhost", "--smtp-host localhost",
"--smtp-username bob@example.com", "--smtp-username bob",
"--imap-host localhost", "--imap-host localhost",
"--imap-username alice@example.com", "--imap-username alice@example.com",
"--from-addr bob_fw@example.com", "--from-addr bob_fw@example.com",
@@ -245,7 +247,7 @@ in
"--dst-password-file <(echo '${alicePassword}')", "--dst-password-file <(echo '${alicePassword}')",
"--ignore-dkim-spf" "--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'")
''; '';
} }