diff --git a/default.nix b/default.nix index 36d0848..ebeb782 100644 --- a/default.nix +++ b/default.nix @@ -733,6 +733,7 @@ in ./mail-server/networking.nix ./mail-server/systemd.nix ./mail-server/dovecot.nix + ./mail-server/opendkim.nix ./mail-server/postfix.nix ./mail-server/rmilter.nix ./mail-server/nginx.nix diff --git a/mail-server/opendkim.nix b/mail-server/opendkim.nix new file mode 100644 index 0000000..d060323 --- /dev/null +++ b/mail-server/opendkim.nix @@ -0,0 +1,90 @@ +# nixos-mailserver: a simple mail server +# Copyright (C) 2017 Brian Olsen +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.mailserver; + + dkimUser = config.services.opendkim.user; + dkimGroup = config.services.opendkim.group; + + createDomainDkimCert = dom: + let + dkim_key = "${cfg.dkimKeyDirectory}/${dom}.${cfg.dkimSelector}.key"; + dkim_txt = "${cfg.dkimKeyDirectory}/${dom}.${cfg.dkimSelector}.txt"; + in + '' + if [ ! -f "${dkim_key}" ] || [ ! -f "${dkim_txt}" ] + then + ${pkgs.opendkim}/bin/opendkim-genkey -s "${cfg.dkimSelector}" \ + -d "${dom}" \ + --directory="${cfg.dkimKeyDirectory}" + mv "${cfg.dkimKeyDirectory}/${cfg.dkimSelector}.private" "${dkim_key}" + mv "${cfg.dkimKeyDirectory}/${cfg.dkimSelector}.txt" "${dkim_txt}" + echo "Generated key for domain ${dom} selector ${cfg.dkimSelector}" + fi + ''; + createAllCerts = lib.concatStringsSep "\n" (map createDomainDkimCert cfg.domains); + create_dkim_cert = + '' + # Create dkim dir + mkdir -p "${cfg.dkimKeyDirectory}" + chown ${dkimUser}:${dkimGroup} "${cfg.dkimKeyDirectory}" + + ${createAllCerts} + + chown -R ${dkimUser}:${dkimGroup} "${cfg.dkimKeyDirectory}" + ''; + + keyTable = pkgs.writeText "opendkim-KeyTable" + (lib.concatStringsSep "\n" (lib.flip map cfg.domains + (dom: "${dom} ${dom}:${cfg.dkimSelector}:${cfg.dkimKeyDirectory}/${dom}.${cfg.dkimSelector}.key"))); + signingTable = pkgs.writeText "opendkim-SigningTable" + (lib.concatStringsSep "\n" (lib.flip map cfg.domains (dom: "${dom} ${dom}"))); + + dkim = config.services.opendkim; + args = [ "-f" "-l" ] ++ lib.optionals (dkim.configFile != null) [ "-x" dkim.configFile ]; +in +{ + config = mkIf (cfg.dkimSigning && cfg.enable) { + services.opendkim = { + enable = true; + selector = cfg.dkimSelector; + domains = "csl:${builtins.concatStringsSep "," cfg.domains}"; + configFile = pkgs.writeText "opendkim.conf" ('' + Canonicalization relaxed/simple + UMask 0002 + Socket ${dkim.socket} + KeyTable file:${keyTable} + SigningTable file:${signingTable} + '' + (lib.optionalString cfg.debug '' + Syslog yes + SyslogSuccess yes + LogWhy yes + '')); + }; + + users.users = optionalAttrs (config.services.postfix.user == "postfix") { + postfix.extraGroups = [ "${config.services.opendkim.group}" ]; + }; + systemd.services.opendkim = { + preStart = create_dkim_cert; + serviceConfig.ExecStart = lib.mkForce "${pkgs.opendkim}/bin/opendkim ${escapeShellArgs args}"; + }; + }; +} \ No newline at end of file diff --git a/mail-server/postfix.nix b/mail-server/postfix.nix index c35c10e..aad87bb 100644 --- a/mail-server/postfix.nix +++ b/mail-server/postfix.nix @@ -90,6 +90,17 @@ let /^Message-ID:\s+<(.*?)@.*?>/ REPLACE Message-ID: <$1@${cfg.fqdn}> ''); + + inetSocket = addr: port: "inet:[${toString port}@${addr}]"; + unixSocket = sock: "unix:${sock}"; + + rmilter = config.services.rmilter; + rmilterSocket = if rmilter.bindSocket.type == "unix" then unixSocket rmilter.bindSocket.path + else inetSocket rmilter.bindSocket.address rmilter.bindSocket.port; + + smtpdMilters = + (lib.optional cfg.dkimSigning "unix:/run/opendkim/opendkim.sock") + ++ [ rmilterSocket ]; in { config = with cfg; lib.mkIf enable { @@ -151,6 +162,11 @@ in # Configure a non blocking source of randomness tls_random_source = dev:/dev/urandom + + smtpd_milters = ${lib.concatStringsSep "," smtpdMilters} + ${lib.optionalString cfg.dkimSigning "non_smtpd_milters = unix:/run/opendkim/opendkim.sock"} + 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} ''; submissionOptions = diff --git a/mail-server/rmilter.nix b/mail-server/rmilter.nix index ed2d019..aed5009 100644 --- a/mail-server/rmilter.nix +++ b/mail-server/rmilter.nix @@ -27,23 +27,8 @@ let }; '' else ""; - dkim = if cfg.dkimSigning - # Note: domain = "*"; causes Rmilter to try to search key in the key path - # as keypath/domain.selector.key for any domain. - then - '' - dkim { - domain { - key = "${cfg.dkimKeyDirectory}"; - domain = "*"; - selector = "${cfg.dkimSelector}"; - }; - sign_alg = sha256; - auth_only = yes; - header_canon = relaxed; - } - '' - else ""; + postfixCfg = config.services.postfix; + rmilter = config.services.rmilter; in { config = with cfg; lib.mkIf enable { @@ -54,7 +39,6 @@ in services.rmilter = { inherit debug; enable = true; - postfix.enable = true; rspamd = { enable = true; extraConfig = "extended_spam_headers = yes;"; @@ -65,10 +49,9 @@ in max_size = 20M; ${clamav} - - ${dkim} ''; }; + users.extraUsers.${postfixCfg.user}.extraGroups = [ rmilter.group ]; }; } diff --git a/mail-server/systemd.nix b/mail-server/systemd.nix index e083d85..9066eab 100644 --- a/mail-server/systemd.nix +++ b/mail-server/systemd.nix @@ -19,33 +19,6 @@ let cfg = config.mailserver; - createDomainDkimCert = dom: - let - dkim_key = "${cfg.dkimKeyDirectory}/${dom}.${cfg.dkimSelector}.key"; - dkim_txt = "${cfg.dkimKeyDirectory}/${dom}.${cfg.dkimSelector}.txt"; - in - '' - if [ ! -f "${dkim_key}" ] || [ ! -f "${dkim_txt}" ] - then - ${pkgs.opendkim}/bin/opendkim-genkey -s "${cfg.dkimSelector}" \ - -d "${dom}" \ - --directory="${cfg.dkimKeyDirectory}" - mv "${cfg.dkimKeyDirectory}/${cfg.dkimSelector}.private" "${dkim_key}" - mv "${cfg.dkimKeyDirectory}/${cfg.dkimSelector}.txt" "${dkim_txt}" - fi - ''; - createAllCerts = lib.concatStringsSep "\n" (map createDomainDkimCert cfg.domains); - create_dkim_cert = - '' - # Create dkim dir - mkdir -p "${cfg.dkimKeyDirectory}" - chown rmilter:rmilter "${cfg.dkimKeyDirectory}" - - ${createAllCerts} - - chown -R rmilter:rmilter "${cfg.dkimKeyDirectory}" - ''; - createDhParameterFile = let dovecotVersion = builtins.fromJSON (builtins.readFile (pkgs.callPackage ./dovecot-version.nix {})); @@ -121,19 +94,16 @@ in # Postfix requires rmilter socket, dovecot lmtp socket, dovecot auth socket and certificate to work systemd.services.postfix = { - after = [ "rmilter.socket" "dovecot2.service" "mailserver-certificates.target" ]; + after = [ "rmilter.socket" "dovecot2.service" "mailserver-certificates.target" ] + ++ (lib.optional cfg.dkimSigning "opendkim.service"); wants = [ "mailserver-certificates.target" ]; - requires = [ "rmilter.socket" "dovecot2.service" ]; + requires = [ "rmilter.socket" "dovecot2.service" ] + ++ (lib.optional cfg.dkimSigning "opendkim.service"); }; - # Create dkim certificates systemd.services.rmilter = { requires = [ "rmilter.socket" ]; after = [ "rmilter.socket" ]; - preStart = - '' - ${create_dkim_cert} - ''; }; }; } diff --git a/tests/extern.nix b/tests/extern.nix index 30a871e..4ad2ff7 100644 --- a/tests/extern.nix +++ b/tests/extern.nix @@ -23,6 +23,14 @@ import { ../default.nix ]; + services.rsyslogd = { + enable = true; + defaultConfig = '' + *.* /dev/console + ''; + }; + + mailserver = { enable = true; debug = true;