# nixos-mailserver: a simple mail server # Copyright (C) 2016-2018 Robin Raymond # # 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, pkgs, lib, ... }: with (import ./common.nix { inherit config pkgs lib; }); let cfg = config.mailserver; passwdDir = "/run/dovecot2"; passwdFile = "${passwdDir}/passwd"; maildirLayoutAppendix = lib.optionalString cfg.useFsLayout ":LAYOUT=fs"; # maildir in format "/${domain}/${user}" dovecotMaildir = "maildir:${cfg.mailDirectory}/%d/%n${maildirLayoutAppendix}"; postfixCfg = config.services.postfix; dovecot2Cfg = config.services.dovecot2; stateDir = "/var/lib/dovecot"; pipeBin = pkgs.stdenv.mkDerivation { name = "pipe_bin"; src = ./dovecot/pipe_bin; buildInputs = with pkgs; [ makeWrapper coreutils bash rspamd ]; buildCommand = '' mkdir -p $out/pipe/bin cp $src/* $out/pipe/bin/ chmod a+x $out/pipe/bin/* patchShebangs $out/pipe/bin for file in $out/pipe/bin/*; do wrapProgram $file \ --set PATH "${pkgs.coreutils}/bin:${pkgs.rspamd}/bin" done ''; }; genPasswdScript = pkgs.writeScript "generate-password-file" '' #!${pkgs.stdenv.shell} set -euo pipefail if (! test -d "${passwdDir}"); then mkdir "${passwdDir}" chmod 755 "${passwdDir}" fi for f in ${builtins.toString (lib.mapAttrsToList (name: value: passwordFiles."${name}") cfg.loginAccounts)}; do if [ ! -f "$f" ]; then echo "Expected password hash file $f does not exist!" exit 1 fi done cat < ${passwdFile} ${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name}:${"$(cat ${passwordFiles."${name}"})"}:${builtins.toString cfg.vmailUID}:${builtins.toString cfg.vmailUID}::${cfg.mailDirectory}:/run/current-system/sw/bin/nologin:" + (if lib.isString value.quota then "userdb_quota_rule=*:storage=${value.quota}" else "") ) cfg.loginAccounts)} EOF chmod 600 ${passwdFile} ''; in { config = with cfg; lib.mkIf enable { services.dovecot2 = { enable = true; enableImap = enableImap; enablePop3 = enablePop3; enablePAM = false; enableQuota = true; mailGroup = vmailGroupName; mailUser = vmailUserName; mailLocation = dovecotMaildir; sslServerCert = certificatePath; sslServerKey = keyPath; enableLmtp = true; modules = [ pkgs.dovecot_pigeonhole ]; protocols = [ "sieve" ]; sieveScripts = { after = builtins.toFile "spam.sieve" '' require "fileinto"; if header :is "X-Spam" "Yes" { fileinto "Junk"; stop; } ''; }; mailboxes = cfg.mailboxes; extraConfig = '' #Extra Config ${lib.optionalString debug '' mail_debug = yes auth_debug = yes verbose_ssl = yes ''} protocol imap { mail_max_userip_connections = ${toString cfg.maxConnectionsPerUser} mail_plugins = $mail_plugins imap_sieve } protocol pop3 { mail_max_userip_connections = ${toString cfg.maxConnectionsPerUser} } mail_access_groups = ${vmailGroupName} ssl = required service lmtp { unix_listener dovecot-lmtp { group = ${postfixCfg.group} mode = 0600 user = ${postfixCfg.user} } } recipient_delimiter = + lmtp_save_to_detail_mailbox = ${cfg.lmptSaveToDetailMailbox} protocol lmtp { mail_plugins = $mail_plugins sieve } passdb { driver = passwd-file args = ${passwdFile} } userdb { driver = passwd-file args = ${passwdFile} } service auth { unix_listener auth { mode = 0660 user = ${postfixCfg.user} group = ${postfixCfg.group} } } auth_mechanisms = plain login namespace inbox { separator = ${cfg.hierarchySeparator} inbox = yes } plugin { sieve_plugins = sieve_imapsieve sieve_extprograms sieve = file:/var/sieve/%u/scripts;active=/var/sieve/%u/active.sieve sieve_default = file:/var/sieve/%u/default.sieve sieve_default_name = default # From elsewhere to Spam folder imapsieve_mailbox1_name = Junk imapsieve_mailbox1_causes = COPY imapsieve_mailbox1_before = file:${stateDir}/imap_sieve/report-spam.sieve # From Spam folder to elsewhere imapsieve_mailbox2_name = * imapsieve_mailbox2_from = Junk imapsieve_mailbox2_causes = COPY imapsieve_mailbox2_before = file:${stateDir}/imap_sieve/report-ham.sieve sieve_pipe_bin_dir = ${pipeBin}/pipe/bin sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment } lda_mailbox_autosubscribe = yes lda_mailbox_autocreate = yes ''; }; systemd.services.gen-passwd-file = { serviceConfig = { ExecStart = genPasswdScript; Type = "oneshot"; }; }; systemd.services.dovecot2 = { after = [ "gen-passwd-file.service" ]; wants = [ "gen-passwd-file.service" ]; requires = [ "gen-passwd-file.service" ]; preStart = '' rm -rf '${stateDir}/imap_sieve' mkdir '${stateDir}/imap_sieve' cp -p "${./dovecot/imap_sieve}"/*.sieve '${stateDir}/imap_sieve/' for k in "${stateDir}/imap_sieve"/*.sieve ; do ${pkgs.dovecot_pigeonhole}/bin/sievec "$k" done chown -R '${dovecot2Cfg.mailUser}:${dovecot2Cfg.mailGroup}' '${stateDir}/imap_sieve' ''; }; systemd.services.postfix.restartTriggers = [ passwdFile ]; }; }