Merge options virtualAccountAliases and virtualDomainAliases into virtualAliases

This commit is contained in:
Thomas Preisner 2025-02-22 19:52:13 +01:00
parent faf6f549b0
commit a1e87f70fa
3 changed files with 49 additions and 57 deletions

View file

@ -126,43 +126,43 @@ in {
default = {};
};
virtualDomainAliases = lib.mkOption {
type = with lib.types; attrsOf str;
example = {
"aliasdomain.com" = "domain.com";
};
description = ''
Virtual aliasing of domains. A virtual alias `"aliasdomain.com" = "domain.com"`
means that all mail directed at `@aliasdomain.com` are forwarded to `@domain.com`.
This also entails, that any account or alias of `domain.com` is partially valid
for `aliasdomain.com`. For example, `user@domain.com` can receive mails at
`user@aliasdomain.com`. However, if `user@domain.com` shall be able to dispatch
mails using `user@aliasdomain.com`, an explicit alias needs to be configured.
'';
default = {};
};
virtualAccountAliases = lib.mkOption {
virtualAliases = lib.mkOption {
type = let
isAccount = value: builtins.elem value (builtins.attrNames cfg.accounts);
isDomain = value: !(lib.hasInfix "@" value) && (builtins.elem value cfg.domains);
account = lib.mkOptionType {
name = "Mail Account";
check = account: builtins.elem account (builtins.attrNames cfg.accounts);
check = isAccount;
};
accountOrDomain = lib.mkOptionType {
name = "Mail Account or Domain";
check = value: (isAccount value) || (isDomain value);
};
in
with lib.types; attrsOf (either account (nonEmptyListOf account));
with lib.types; attrsOf (either (nonEmptyListOf account) accountOrDomain);
example = {
"info@example.com" = "user1@example.com";
"postmaster@example.com" = "user1@example.com";
"abuse@example.com" = "user1@example.com";
"multi@example.com" = ["user1@example.com" "user2@example.com"];
"aliasdomain.com" = "domain.com";
};
description = ''
Virtual account aliases. A virtual alias `"info@example.com" = "user1@example.com"`
means that all mail to `info@example.com` is forwarded to `user1@example.com`.
Furthermore, it also allows the user `user1@example.com` to send emails as
`info@example.com`. It is 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`.
Virtual account and domain aliases. A virtual alias means, that all mail directed
at a given target are forwarded to the specified other destinations, too.
For account aliases, this means that, e.g., `"user1@example.com"` receives all mail
sent to `"info@example.com"`. In addition, `"user1@example.com"` is also able to
impersonate `"info@example.com"` when sending mails. It is 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"`.
For domain aliases, this means that all mails directed at an aliased domain, e.g.,
`"aliasdomain.com"` are forwarded to `"domain.com"` This also entails, that any
account or alias of `"domain.com"` receives mails directed at `"aliasdomain.com"`.
However, if `"user@domain.com"` shall be able to send mails using
`"user@aliasdomain.com"`, an explicit alias needs to be configured.
'';
default = {};
};

View file

@ -15,33 +15,27 @@ with (import ./common.nix {inherit config;}); let
mergeLookupTables lookupTables;
lookupTableToString = attrs: let
valueToString = value: lib.concatStringsSep ", " value;
isDomain = value: !(lib.hasInfix "@" value);
valueToString = value:
if (isDomain value)
then "@${value}"
else value;
listToString = list: lib.concatStringsSep ", " (map valueToString list);
in
lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name} ${valueToString value}") attrs);
lib.concatStringsSep "\n" (lib.mapAttrsToList (name: list: "${valueToString name} ${listToString list}") attrs);
mergeLookupTables = tables: lib.zipAttrsWith (n: v: lib.flatten v) tables;
virtual_accounts = mergeLookupTables (lib.map (name: {"${name}" = name;}) (lib.attrNames cfg.accounts));
virtual_account_aliases = attrsToLookupTable cfg.virtualAccountAliases;
virtual_domain_aliases = let
alias_domains = lib.mapAttrs' (src: dst: lib.nameValuePair "@${src}" "@${dst}") cfg.virtualDomainAliases;
in
attrsToLookupTable alias_domains;
all_virtual_aliases = mergeLookupTables [virtual_accounts virtual_account_aliases virtual_domain_aliases];
virtual_aliases = attrsToLookupTable cfg.virtualAliases;
all_virtual_aliases = mergeLookupTables [virtual_accounts virtual_aliases];
# File containing all mappings of aliases/authenticated accounts and their sender mail addresses.
aliases_file = let
content = lookupTableToString all_virtual_aliases;
in
builtins.toFile "virtual_aliases" content;
# File containing all mappings of authenticated accounts and their sender mail addresses.
virtual_accounts_file = let
content = lookupTableToString all_virtual_aliases;
in
builtins.toFile "virtual_accounts" content;
virtual_domains_file = builtins.toFile "virtual_domains" (lib.concatStringsSep "\n" cfg.domains);
denied_recipients = map (account: "${account.name} REJECT ${account.rejectMessage}") (lib.filter (account: account.isSystemUser) (lib.attrValues cfg.accounts));
@ -67,14 +61,15 @@ with (import ./common.nix {inherit config;}); let
tls_exclude_ciphers = "MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL";
in {
config = lib.mkIf cfg.enable {
assertions =
lib.mapAttrsToList (
src: dst: {
assertion = (builtins.elem src cfg.domains) && (builtins.elem dst cfg.domains);
message = "Both aliased domain (${src}) and actual domain (${dst}) need to be managed by the mailserver.";
}
)
cfg.virtualDomainAliases;
assertions = let
isDomain = value: !lib.hasInfix "@" value;
aliasedDomains = builtins.filter isDomain (builtins.attrNames cfg.virtualAliases);
in
map (domain: {
assertion = builtins.elem domain cfg.domains;
message = "The domain to be aliased (${domain}) must be managed by the mailserver.";
})
aliasedDomains;
services.postfix = {
enable = true;
@ -86,9 +81,7 @@ in {
enableSubmissions = true;
# TODO: create function to simplify this?
mapFiles."virtual_aliases" = aliases_file;
mapFiles."virtual_accounts" = virtual_accounts_file;
mapFiles."denied_recipients" = denied_recipients_file;
virtual = lookupTableToString all_virtual_aliases;
@ -100,8 +93,7 @@ in {
smtpd_sasl_security_options = "noanonymous";
smtpd_sasl_local_domain = "$myhostname";
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
# use mappedFile -> different path?
smtpd_sender_login_maps = "hash:/etc/postfix/virtual_accounts";
smtpd_sender_login_maps = mappedFile "virtual_aliases";
smtpd_sender_restrictions = "reject_sender_login_mismatch";
smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
cleanup_service_name = "submission-header-cleanup";

View file

@ -42,13 +42,13 @@ in
fqdn = "mail.example.com";
domains = ["example.com" "aliased.com" "otherdomain.com"];
accounts = mkAccounts accounts;
virtualDomainAliases = {
virtualAliases = {
# domain aliases
"aliased.com" = "example.com";
};
virtualAccountAliases = {
# account aliases
"alias@example.com" = accounts."alias".address;
"multi-alias@example.com" = lib.map (x: accounts.${x}.address) ["multi-alias1" "multi-alias2"];
"@example.com" = accounts."catchall".address;
"example.com" = accounts."catchall".address;
"user@otherdomain.com" = accounts."otherdomain".address;
};
};