Merge branch 'pr-old-upstream-126' into 'master'
griff:my-changes See merge request simple-nixos-mailserver/nixos-mailserver!132
This commit is contained in:
commit
3aecb1299d
|
@ -0,0 +1 @@
|
||||||
|
*.cvd filter=lfs diff=lfs merge=lfs -text
|
|
@ -5,3 +5,4 @@ env:
|
||||||
script:
|
script:
|
||||||
- nix-build tests/intern.nix
|
- nix-build tests/intern.nix
|
||||||
- nix-build tests/extern.nix
|
- nix-build tests/extern.nix
|
||||||
|
- nix-build tests/clamav.nix
|
||||||
|
|
15
default.nix
15
default.nix
|
@ -454,6 +454,18 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
policydSPFExtraConfig = mkOption {
|
||||||
|
type = types.lines;
|
||||||
|
default = "";
|
||||||
|
example = ''
|
||||||
|
skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
Extra configuration options for policyd-spf. This can be use to among
|
||||||
|
other things skip spf checking for some IP addresses.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
monitoring = {
|
monitoring = {
|
||||||
enable = mkEnableOption "monitoring via monit";
|
enable = mkEnableOption "monitoring via monit";
|
||||||
|
|
||||||
|
@ -733,8 +745,9 @@ in
|
||||||
./mail-server/networking.nix
|
./mail-server/networking.nix
|
||||||
./mail-server/systemd.nix
|
./mail-server/systemd.nix
|
||||||
./mail-server/dovecot.nix
|
./mail-server/dovecot.nix
|
||||||
|
./mail-server/opendkim.nix
|
||||||
./mail-server/postfix.nix
|
./mail-server/postfix.nix
|
||||||
./mail-server/rmilter.nix
|
./mail-server/rspamd.nix
|
||||||
./mail-server/nginx.nix
|
./mail-server/nginx.nix
|
||||||
./mail-server/kresd.nix
|
./mail-server/kresd.nix
|
||||||
./mail-server/post-upgrade-check.nix
|
./mail-server/post-upgrade-check.nix
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
{ dovecot, gawk, gnused, jq, runCommand }:
|
|
||||||
|
|
||||||
runCommand "dovecot-version" {
|
|
||||||
buildInputs = [dovecot gnused jq];
|
|
||||||
} ''
|
|
||||||
jq -n \
|
|
||||||
--arg dovecot_version "$(dovecot --version |
|
|
||||||
sed 's/\([0-9.]*\).*/\1/' |
|
|
||||||
awk -F '.' '{ print $1"."$2"."$3 }')" \
|
|
||||||
'[$dovecot_version | split("."), ["major", "minor", "patch"]]
|
|
||||||
| transpose | map( { (.[1]): .[0] | tonumber }) | add' > $out
|
|
||||||
''
|
|
|
@ -23,11 +23,30 @@ let
|
||||||
|
|
||||||
maildirLayoutAppendix = lib.optionalString cfg.useFsLayout ":LAYOUT=fs";
|
maildirLayoutAppendix = lib.optionalString cfg.useFsLayout ":LAYOUT=fs";
|
||||||
|
|
||||||
dovecotVersion = builtins.fromJSON
|
|
||||||
(builtins.readFile (pkgs.callPackage ./dovecot-version.nix {}));
|
|
||||||
|
|
||||||
# maildir in format "/${domain}/${user}"
|
# maildir in format "/${domain}/${user}"
|
||||||
dovecotMaildir = "maildir:${cfg.mailDirectory}/%d/%n${maildirLayoutAppendix}";
|
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
|
||||||
|
'';
|
||||||
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = with cfg; lib.mkIf enable {
|
config = with cfg; lib.mkIf enable {
|
||||||
|
@ -69,6 +88,7 @@ in
|
||||||
|
|
||||||
protocol imap {
|
protocol imap {
|
||||||
mail_max_userip_connections = ${toString cfg.maxConnectionsPerUser}
|
mail_max_userip_connections = ${toString cfg.maxConnectionsPerUser}
|
||||||
|
mail_plugins = $mail_plugins imap_sieve
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol pop3 {
|
protocol pop3 {
|
||||||
|
@ -77,15 +97,15 @@ in
|
||||||
|
|
||||||
mail_access_groups = ${vmailGroupName}
|
mail_access_groups = ${vmailGroupName}
|
||||||
ssl = required
|
ssl = required
|
||||||
${lib.optionalString (dovecotVersion.major == 2 && dovecotVersion.minor >= 3) ''
|
${lib.optionalString (lib.versionAtLeast (lib.getVersion pkgs.dovecot) "2.3") ''
|
||||||
ssl_dh = <${certificateDirectory}/dh.pem
|
ssl_dh = <${certificateDirectory}/dh.pem
|
||||||
''}
|
''}
|
||||||
|
|
||||||
service lmtp {
|
service lmtp {
|
||||||
unix_listener /var/lib/postfix/queue/private/dovecot-lmtp {
|
unix_listener dovecot-lmtp {
|
||||||
group = postfix
|
group = ${postfixCfg.group}
|
||||||
mode = 0600
|
mode = 0600
|
||||||
user = postfix # TODO: < make variable
|
user = ${postfixCfg.user}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,10 +124,10 @@ in
|
||||||
}
|
}
|
||||||
|
|
||||||
service auth {
|
service auth {
|
||||||
unix_listener /var/lib/postfix/queue/private/auth {
|
unix_listener auth {
|
||||||
mode = 0660
|
mode = 0660
|
||||||
user = postfix # TODO: < make variable
|
user = ${postfixCfg.user}
|
||||||
group = postfix # TODO: < make variable
|
group = ${postfixCfg.group}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,14 +139,40 @@ in
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin {
|
plugin {
|
||||||
|
sieve_plugins = sieve_imapsieve sieve_extprograms
|
||||||
sieve = file:/var/sieve/%u/scripts;active=/var/sieve/%u/active.sieve
|
sieve = file:/var/sieve/%u/scripts;active=/var/sieve/%u/active.sieve
|
||||||
sieve_default = file:/var/sieve/%u/default.sieve
|
sieve_default = file:/var/sieve/%u/default.sieve
|
||||||
sieve_default_name = default
|
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_autosubscribe = yes
|
||||||
lda_mailbox_autocreate = yes
|
lda_mailbox_autocreate = yes
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
systemd.services.dovecot2.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'
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
|
||||||
|
|
||||||
|
if environment :matches "imap.mailbox" "*" {
|
||||||
|
set "mailbox" "${1}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if string "${mailbox}" "Trash" {
|
||||||
|
stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
if environment :matches "imap.user" "*" {
|
||||||
|
set "username" "${1}";
|
||||||
|
}
|
||||||
|
|
||||||
|
pipe :copy "sa-learn-ham.sh" [ "${username}" ];
|
|
@ -0,0 +1,7 @@
|
||||||
|
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
|
||||||
|
|
||||||
|
if environment :matches "imap.user" "*" {
|
||||||
|
set "username" "${1}";
|
||||||
|
}
|
||||||
|
|
||||||
|
pipe :copy "sa-learn-spam.sh" [ "${username}" ];
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -o errexit
|
||||||
|
exec rspamc -h /run/rspamd/worker-controller.sock learn_ham
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -o errexit
|
||||||
|
exec rspamc -h /run/rspamd/worker-controller.sock learn_spam
|
|
@ -22,7 +22,7 @@ in
|
||||||
{
|
{
|
||||||
config = with cfg; lib.mkIf enable {
|
config = with cfg; lib.mkIf enable {
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
dovecot opendkim openssh postfix rspamd rmilter
|
dovecot opendkim openssh postfix rspamd
|
||||||
] ++ (if certificateScheme == 2 then [ openssl ] else []);
|
] ++ (if certificateScheme == 2 then [ openssl ] else []);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>
|
||||||
|
{ 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}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -90,6 +90,19 @@ let
|
||||||
|
|
||||||
/^Message-ID:\s+<(.*?)@.*?>/ REPLACE Message-ID: <$1@${cfg.fqdn}>
|
/^Message-ID:\s+<(.*?)@.*?>/ REPLACE Message-ID: <$1@${cfg.fqdn}>
|
||||||
'');
|
'');
|
||||||
|
|
||||||
|
inetSocket = addr: port: "inet:[${toString port}@${addr}]";
|
||||||
|
unixSocket = sock: "unix:${sock}";
|
||||||
|
|
||||||
|
smtpdMilters =
|
||||||
|
(lib.optional cfg.dkimSigning "unix:/run/opendkim/opendkim.sock")
|
||||||
|
++ [ "unix:/run/rspamd/rspamd-milter.sock" ];
|
||||||
|
|
||||||
|
policyd-spf = pkgs.writeText "policyd-spf.conf" (
|
||||||
|
cfg.policydSPFExtraConfig
|
||||||
|
+ (lib.optionalString cfg.debug ''
|
||||||
|
debugLevel = 4
|
||||||
|
''));
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = with cfg; lib.mkIf enable {
|
config = with cfg; lib.mkIf enable {
|
||||||
|
@ -121,16 +134,21 @@ in
|
||||||
virtual_mailbox_domains = ${vhosts_file}
|
virtual_mailbox_domains = ${vhosts_file}
|
||||||
virtual_mailbox_maps = hash:/var/lib/postfix/conf/valias
|
virtual_mailbox_maps = hash:/var/lib/postfix/conf/valias
|
||||||
virtual_alias_maps = hash:/var/lib/postfix/conf/valias
|
virtual_alias_maps = hash:/var/lib/postfix/conf/valias
|
||||||
virtual_transport = lmtp:unix:private/dovecot-lmtp
|
virtual_transport = lmtp:unix:/run/dovecot2/dovecot-lmtp
|
||||||
|
|
||||||
# sasl with dovecot
|
# sasl with dovecot
|
||||||
smtpd_sasl_type = dovecot
|
smtpd_sasl_type = dovecot
|
||||||
smtpd_sasl_path = private/auth
|
smtpd_sasl_path = /run/dovecot2/auth
|
||||||
smtpd_sasl_auth_enable = yes
|
smtpd_sasl_auth_enable = yes
|
||||||
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
|
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
|
||||||
|
|
||||||
# reject selected recipients, quota
|
policy-spf_time_limit = 3600s
|
||||||
smtpd_recipient_restrictions = check_recipient_access hash:/var/lib/postfix/conf/reject_recipients, check_policy_service inet:localhost:12340
|
|
||||||
|
# quota and spf checking
|
||||||
|
smtpd_recipient_restrictions =
|
||||||
|
check_recipient_access hash:/var/lib/postfix/conf/reject_recipients,
|
||||||
|
check_policy_service inet:localhost:12340,
|
||||||
|
check_policy_service unix:private/policy-spf
|
||||||
|
|
||||||
# TLS settings, inspired by https://github.com/jeaye/nix-files
|
# TLS settings, inspired by https://github.com/jeaye/nix-files
|
||||||
# Submission by mail clients is handled in submissionOptions
|
# Submission by mail clients is handled in submissionOptions
|
||||||
|
@ -151,6 +169,11 @@ in
|
||||||
|
|
||||||
# Configure a non blocking source of randomness
|
# Configure a non blocking source of randomness
|
||||||
tls_random_source = dev:/dev/urandom
|
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 =
|
submissionOptions =
|
||||||
|
@ -158,7 +181,7 @@ in
|
||||||
smtpd_tls_security_level = "encrypt";
|
smtpd_tls_security_level = "encrypt";
|
||||||
smtpd_sasl_auth_enable = "yes";
|
smtpd_sasl_auth_enable = "yes";
|
||||||
smtpd_sasl_type = "dovecot";
|
smtpd_sasl_type = "dovecot";
|
||||||
smtpd_sasl_path = "private/auth";
|
smtpd_sasl_path = "/run/dovecot2/auth";
|
||||||
smtpd_sasl_security_options = "noanonymous";
|
smtpd_sasl_security_options = "noanonymous";
|
||||||
smtpd_sasl_local_domain = "$myhostname";
|
smtpd_sasl_local_domain = "$myhostname";
|
||||||
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
|
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
|
||||||
|
@ -167,11 +190,23 @@ in
|
||||||
smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
|
smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
|
||||||
cleanup_service_name = "submission-header-cleanup";
|
cleanup_service_name = "submission-header-cleanup";
|
||||||
};
|
};
|
||||||
|
masterConfig = {
|
||||||
extraMasterConf = ''
|
"policy-spf" = {
|
||||||
submission-header-cleanup unix n - n - 0 cleanup
|
type = "unix";
|
||||||
-o header_checks=pcre:${submissionHeaderCleanupRules}
|
privileged = true;
|
||||||
'';
|
chroot = false;
|
||||||
|
command = "spawn";
|
||||||
|
args = [ "user=nobody" "argv=${pkgs.pypolicyd-spf}/bin/policyd-spf" "${policyd-spf}"];
|
||||||
|
};
|
||||||
|
"submission-header-cleanup" = {
|
||||||
|
type = "unix";
|
||||||
|
private = false;
|
||||||
|
chroot = false;
|
||||||
|
maxproc = 0;
|
||||||
|
command = "cleanup";
|
||||||
|
args = ["-o" "header_checks=pcre:${submissionHeaderCleanupRules}"];
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
# 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 <http://www.gnu.org/licenses/>
|
|
||||||
|
|
||||||
{ config, pkgs, lib, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
cfg = config.mailserver;
|
|
||||||
|
|
||||||
clamav = if cfg.virusScanning
|
|
||||||
then
|
|
||||||
''
|
|
||||||
clamav {
|
|
||||||
servers = /var/run/clamav/clamd.ctl;
|
|
||||||
};
|
|
||||||
''
|
|
||||||
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 "";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config = with cfg; lib.mkIf enable {
|
|
||||||
services.rspamd = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
services.rmilter = {
|
|
||||||
inherit debug;
|
|
||||||
enable = true;
|
|
||||||
postfix.enable = true;
|
|
||||||
rspamd = {
|
|
||||||
enable = true;
|
|
||||||
extraConfig = "extended_spam_headers = yes;";
|
|
||||||
};
|
|
||||||
extraConfig =
|
|
||||||
''
|
|
||||||
use_redis = true;
|
|
||||||
max_size = 20M;
|
|
||||||
|
|
||||||
${clamav}
|
|
||||||
|
|
||||||
${dkim}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
# 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 <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.mailserver;
|
||||||
|
|
||||||
|
postfixCfg = config.services.postfix;
|
||||||
|
rspamdCfg = config.services.rspamd;
|
||||||
|
rspamdSocket = if rspamdCfg.socketActivation
|
||||||
|
then "rspamd-rspamd_proxy-1.socket"
|
||||||
|
else "rspamd.service";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = with cfg; lib.mkIf enable {
|
||||||
|
services.rspamd = {
|
||||||
|
enable = true;
|
||||||
|
socketActivation = false;
|
||||||
|
extraConfig = ''
|
||||||
|
extended_spam_headers = yes;
|
||||||
|
'' + (lib.optionalString cfg.virusScanning ''
|
||||||
|
antivirus {
|
||||||
|
clamav {
|
||||||
|
action = "reject";
|
||||||
|
symbol = "CLAM_VIRUS";
|
||||||
|
type = "clamav";
|
||||||
|
log_clean = true;
|
||||||
|
servers = "/run/clamav/clamd.ctl";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'');
|
||||||
|
|
||||||
|
workers.rspamd_proxy = {
|
||||||
|
type = "proxy";
|
||||||
|
bindSockets = [{
|
||||||
|
socket = "/run/rspamd/rspamd-milter.sock";
|
||||||
|
mode = "0664";
|
||||||
|
}];
|
||||||
|
count = 1; # Do not spawn too many processes of this type
|
||||||
|
extraConfig = ''
|
||||||
|
milter = yes; # Enable milter mode
|
||||||
|
timeout = 120s; # Needed for Milter usually
|
||||||
|
|
||||||
|
upstream "local" {
|
||||||
|
default = yes; # Self-scan upstreams are always default
|
||||||
|
self_scan = yes; # Enable self-scan
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
workers.controller = {
|
||||||
|
type = "controller";
|
||||||
|
count = 1;
|
||||||
|
bindSockets = [{
|
||||||
|
socket = "/run/rspamd/worker-controller.sock";
|
||||||
|
mode = "0666";
|
||||||
|
}];
|
||||||
|
includes = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
systemd.services.rspamd = {
|
||||||
|
requires = (lib.optional cfg.virusScanning "clamav-daemon.service");
|
||||||
|
after = (lib.optional cfg.virusScanning "clamav-daemon.service");
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.postfix = {
|
||||||
|
after = [ rspamdSocket ];
|
||||||
|
requires = [ rspamdSocket ];
|
||||||
|
};
|
||||||
|
|
||||||
|
users.extraUsers.${postfixCfg.user}.extraGroups = [ rspamdCfg.group ];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -19,8 +19,40 @@
|
||||||
let
|
let
|
||||||
cfg = config.mailserver;
|
cfg = config.mailserver;
|
||||||
|
|
||||||
create_certificate = if cfg.certificateScheme == 2 then
|
createDhParameterFile =
|
||||||
|
lib.optionalString (lib.versionAtLeast (lib.getVersion pkgs.dovecot) "2.3")
|
||||||
''
|
''
|
||||||
|
# Create a dh parameter file
|
||||||
|
if [ ! -s "${cfg.certificateDirectory}/dh.pem" ]
|
||||||
|
then
|
||||||
|
mkdir -p "${cfg.certificateDirectory}"
|
||||||
|
${pkgs.openssl}/bin/openssl \
|
||||||
|
dhparam ${builtins.toString cfg.dhParamBitLength} \
|
||||||
|
> "${cfg.certificateDirectory}/dh.pem"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
|
preliminarySelfsigned = config.security.acme.preliminarySelfsigned;
|
||||||
|
acmeWantsTarget = [ "acme-certificates.target" ]
|
||||||
|
++ (lib.optional preliminarySelfsigned "acme-selfsigned-certificates.target");
|
||||||
|
acmeAfterTarget = if preliminarySelfsigned
|
||||||
|
then [ "acme-selfsigned-certificates.target" ]
|
||||||
|
else [ "acme-certificates.target" ];
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = with cfg; lib.mkIf enable {
|
||||||
|
# Add target for when certificates are available
|
||||||
|
systemd.targets."mailserver-certificates" = {
|
||||||
|
wants = lib.mkIf (cfg.certificateScheme == 3) acmeWantsTarget;
|
||||||
|
after = lib.mkIf (cfg.certificateScheme == 3) acmeAfterTarget;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Create self signed certificate
|
||||||
|
systemd.services.mailserver-selfsigned-certificate = lib.mkIf (cfg.certificateScheme == 2) {
|
||||||
|
wantedBy = [ "mailserver-certificates.target" ];
|
||||||
|
after = [ "local-fs.target" ];
|
||||||
|
before = [ "mailserver-certificates.target" ];
|
||||||
|
script = ''
|
||||||
# Create certificates if they do not exist yet
|
# Create certificates if they do not exist yet
|
||||||
dir="${cfg.certificateDirectory}"
|
dir="${cfg.certificateDirectory}"
|
||||||
fqdn="${cfg.fqdn}"
|
fqdn="${cfg.fqdn}"
|
||||||
|
@ -35,83 +67,35 @@ let
|
||||||
"${pkgs.openssl}/bin/openssl" req -new -key "''${key}" -x509 -subj "/CN=''${fqdn}" \
|
"${pkgs.openssl}/bin/openssl" req -new -key "''${key}" -x509 -subj "/CN=''${fqdn}" \
|
||||||
-days 3650 -out "''${cert}"
|
-days 3650 -out "''${cert}"
|
||||||
fi
|
fi
|
||||||
''
|
|
||||||
else "";
|
|
||||||
|
|
||||||
createDhParameterFile =
|
|
||||||
''
|
|
||||||
# Create a dh parameter file
|
|
||||||
if [ ! -s "${cfg.certificateDirectory}/dh.pem" ]
|
|
||||||
then
|
|
||||||
mkdir -p "${cfg.certificateDirectory}"
|
|
||||||
${pkgs.openssl}/bin/openssl \
|
|
||||||
dhparam ${builtins.toString cfg.dhParamBitLength} \
|
|
||||||
> "${cfg.certificateDirectory}/dh.pem"
|
|
||||||
fi
|
|
||||||
'';
|
'';
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
PrivateTmp = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
createDomainDkimCert = dom:
|
# Create maildir folder and dh parameters before dovecot startup
|
||||||
let
|
systemd.services.dovecot2 = {
|
||||||
dkim_key = "${cfg.dkimKeyDirectory}/${dom}.${cfg.dkimSelector}.key";
|
after = [ "mailserver-certificates.target" ];
|
||||||
dkim_txt = "${cfg.dkimKeyDirectory}/${dom}.${cfg.dkimSelector}.txt";
|
wants = [ "mailserver-certificates.target" ];
|
||||||
in
|
preStart = ''
|
||||||
''
|
|
||||||
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}"
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config = with cfg; lib.mkIf enable {
|
|
||||||
# Make sure postfix gets started first, so that the certificates are in place
|
|
||||||
systemd.services.dovecot2.after = [ "postfix.service" ];
|
|
||||||
|
|
||||||
# Create certificates and maildir folder
|
|
||||||
systemd.services.postfix = {
|
|
||||||
after = (if (certificateScheme == 3) then [ "nginx.service" ] else []);
|
|
||||||
preStart =
|
|
||||||
''
|
|
||||||
# Create mail directory and set permissions. See
|
# Create mail directory and set permissions. See
|
||||||
# <http://wiki2.dovecot.org/SharedMailboxes/Permissions>.
|
# <http://wiki2.dovecot.org/SharedMailboxes/Permissions>.
|
||||||
mkdir -p "${mailDirectory}"
|
mkdir -p "${mailDirectory}"
|
||||||
chgrp "${vmailGroupName}" "${mailDirectory}"
|
chgrp "${vmailGroupName}" "${mailDirectory}"
|
||||||
chmod 02770 "${mailDirectory}"
|
chmod 02770 "${mailDirectory}"
|
||||||
|
|
||||||
${create_certificate}
|
${createDhParameterFile}
|
||||||
|
|
||||||
${let
|
|
||||||
dovecotVersion = builtins.fromJSON
|
|
||||||
(builtins.readFile (pkgs.callPackage ./dovecot-version.nix {}));
|
|
||||||
in lib.optionalString
|
|
||||||
(dovecotVersion.major == 2 && dovecotVersion.minor >= 3)
|
|
||||||
createDhParameterFile}
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
# Create dkim certificates
|
# Postfix requires dovecot lmtp socket, dovecot auth socket and certificate to work
|
||||||
systemd.services.rmilter = {
|
systemd.services.postfix = {
|
||||||
requires = [ "rmilter.socket" ];
|
after = [ "dovecot2.service" "mailserver-certificates.target" ]
|
||||||
after = [ "rmilter.socket" ];
|
++ (lib.optional cfg.dkimSigning "opendkim.service");
|
||||||
preStart =
|
wants = [ "mailserver-certificates.target" ];
|
||||||
''
|
requires = [ "dovecot2.service" ]
|
||||||
${create_dkim_cert}
|
++ (lib.optional cfg.dkimSigning "opendkim.service");
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,228 @@
|
||||||
|
# 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 <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
import <nixpkgs/nixos/tests/make-test.nix> {
|
||||||
|
|
||||||
|
nodes = {
|
||||||
|
server = { config, pkgs, lib, ... }:
|
||||||
|
let
|
||||||
|
clamav-db = pkgs.srcOnly {
|
||||||
|
name = "ClamAV-db";
|
||||||
|
src = pkgs.fetchurl {
|
||||||
|
url = "https://files.griff.name/ClamAV-db.tar";
|
||||||
|
sha256 = "eecad99f4c071d216bd91565f84c0d90a1f93e5e3e22d8f3087686ba3bd219e7";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../default.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
virtualisation.memorySize = 1500;
|
||||||
|
|
||||||
|
services.rsyslogd = {
|
||||||
|
enable = true;
|
||||||
|
defaultConfig = ''
|
||||||
|
*.* /dev/console
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
services.clamav.updater.enable = lib.mkForce false;
|
||||||
|
systemd.services.old-clam = {
|
||||||
|
before = [ "clamav-daemon.service" ];
|
||||||
|
requiredBy = [ "clamav-daemon.service" ];
|
||||||
|
description = "ClamAV virus database";
|
||||||
|
|
||||||
|
preStart = ''
|
||||||
|
mkdir -m 0755 -p /var/lib/clamav
|
||||||
|
chown clamav:clamav /var/lib/clamav
|
||||||
|
'';
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
cp ${clamav-db}/bytecode.cvd /var/lib/clamav/
|
||||||
|
cp ${clamav-db}/main.cvd /var/lib/clamav/
|
||||||
|
cp ${clamav-db}/daily.cvd /var/lib/clamav/
|
||||||
|
chown clamav:clamav /var/lib/clamav/*
|
||||||
|
'';
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
PrivateTmp = "yes";
|
||||||
|
PrivateDevices = "yes";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
mailserver = {
|
||||||
|
enable = true;
|
||||||
|
debug = true;
|
||||||
|
fqdn = "mail.example.com";
|
||||||
|
domains = [ "example.com" "example2.com" ];
|
||||||
|
dhParamBitLength = 512;
|
||||||
|
virusScanning = true;
|
||||||
|
|
||||||
|
loginAccounts = {
|
||||||
|
"user1@example.com" = {
|
||||||
|
hashedPassword = "$6$/z4n8AQl6K$kiOkBTWlZfBd7PvF5GsJ8PmPgdZsFGN1jPGZufxxr60PoR0oUsrvzm2oQiflyz5ir9fFJ.d/zKm/NgLXNUsNX/";
|
||||||
|
aliases = [ "postmaster@example.com" ];
|
||||||
|
catchAll = [ "example.com" ];
|
||||||
|
};
|
||||||
|
"user@example2.com" = {
|
||||||
|
hashedPassword = "$6$u61JrAtuI0a$nGEEfTP5.eefxoScUGVG/Tl0alqla2aGax4oTd85v3j3xSmhv/02gNfSemv/aaMinlv9j/ZABosVKBrRvN5Qv0";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
enableImap = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.etc = {
|
||||||
|
"root/eicar.com.txt".text = "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
client = { nodes, config, pkgs, ... }: let
|
||||||
|
serverIP = nodes.server.config.networking.primaryIPAddress;
|
||||||
|
clientIP = nodes.client.config.networking.primaryIPAddress;
|
||||||
|
grep-ip = pkgs.writeScriptBin "grep-ip" ''
|
||||||
|
#!${pkgs.stdenv.shell}
|
||||||
|
echo grep '${clientIP}' "$@" >&2
|
||||||
|
exec grep '${clientIP}' "$@"
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
fetchmail msmtp procmail findutils grep-ip
|
||||||
|
];
|
||||||
|
environment.etc = {
|
||||||
|
"root/.fetchmailrc" = {
|
||||||
|
text = ''
|
||||||
|
poll ${serverIP} with proto IMAP
|
||||||
|
user 'user1@example.com' there with password 'user1' is 'root' here
|
||||||
|
mda procmail
|
||||||
|
'';
|
||||||
|
mode = "0700";
|
||||||
|
};
|
||||||
|
"root/.procmailrc" = {
|
||||||
|
text = "DEFAULT=$HOME/mail";
|
||||||
|
};
|
||||||
|
"root/.msmtprc" = {
|
||||||
|
text = ''
|
||||||
|
account test2
|
||||||
|
host ${serverIP}
|
||||||
|
port 587
|
||||||
|
from user@example2.com
|
||||||
|
user user@example2.com
|
||||||
|
password user2
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
"root/virus-email".text = ''
|
||||||
|
From: User2 <user@example2.com>
|
||||||
|
Content-Type: multipart/mixed;
|
||||||
|
boundary="Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607"
|
||||||
|
Mime-Version: 1.0 (Mac OS X Mail 11.3 \(3445.6.18\))
|
||||||
|
Subject: Testy McTest
|
||||||
|
Message-Id: <94550DD9-1FF1-4ED1-9F09-8812FF2E59AA@example.com>
|
||||||
|
Date: Sat, 12 May 2018 14:15:44 +0200
|
||||||
|
To: User1 <user1@example.com>
|
||||||
|
X-Mailer: Apple Mail (2.3445.6.18)
|
||||||
|
|
||||||
|
|
||||||
|
--Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
Content-Type: text/plain;
|
||||||
|
charset=us-ascii
|
||||||
|
|
||||||
|
Hello
|
||||||
|
|
||||||
|
I have attached a dangerous virus.
|
||||||
|
|
||||||
|
Mfg.
|
||||||
|
User2
|
||||||
|
|
||||||
|
|
||||||
|
--Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607
|
||||||
|
Content-Disposition: attachment;
|
||||||
|
filename=eicar.com.txt
|
||||||
|
Content-Type: text/plain;
|
||||||
|
x-unix-mode=0644;
|
||||||
|
name="eicar.com.txt"
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
||||||
|
--Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607--
|
||||||
|
'';
|
||||||
|
"root/email2".text = ''
|
||||||
|
From: User <user@example2.com>
|
||||||
|
To: User1 <user1@example.com>
|
||||||
|
Cc:
|
||||||
|
Bcc:
|
||||||
|
Subject: This is a test Email from user@example2.com to user1
|
||||||
|
Reply-To:
|
||||||
|
|
||||||
|
Hello User1,
|
||||||
|
|
||||||
|
how are you doing today?
|
||||||
|
|
||||||
|
XOXO User1
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript =
|
||||||
|
''
|
||||||
|
startAll;
|
||||||
|
|
||||||
|
$server->waitForUnit("multi-user.target");
|
||||||
|
$client->waitForUnit("multi-user.target");
|
||||||
|
|
||||||
|
$client->execute("cp -p /etc/root/.* ~/");
|
||||||
|
$client->succeed("mkdir -p ~/mail");
|
||||||
|
$client->succeed("ls -la ~/ >&2");
|
||||||
|
$client->succeed("cat ~/.fetchmailrc >&2");
|
||||||
|
$client->succeed("cat ~/.procmailrc >&2");
|
||||||
|
$client->succeed("cat ~/.msmtprc >&2");
|
||||||
|
|
||||||
|
# fetchmail returns EXIT_CODE 1 when no new mail
|
||||||
|
$client->succeed("fetchmail -v || [ \$? -eq 1 ] >&2");
|
||||||
|
|
||||||
|
# Verify that mail can be sent and received before testing virus scanner
|
||||||
|
$client->execute("rm ~/mail/*");
|
||||||
|
$client->succeed("msmtp -a test2 --tls=on --tls-certcheck=off --auth=on user1\@example.com < /etc/root/email2 >&2");
|
||||||
|
# give the mail server some time to process the mail
|
||||||
|
$server->waitUntilFails('[ "$(postqueue -p)" != "Mail queue is empty" ]');
|
||||||
|
$client->execute("rm ~/mail/*");
|
||||||
|
# fetchmail returns EXIT_CODE 0 when it retrieves mail
|
||||||
|
$client->succeed("fetchmail -v >&2");
|
||||||
|
$client->execute("rm ~/mail/*");
|
||||||
|
|
||||||
|
|
||||||
|
subtest "virus scan file", sub {
|
||||||
|
$server->fail("clamscan --follow-file-symlinks=2 -r /etc/root/ >&2");
|
||||||
|
};
|
||||||
|
|
||||||
|
subtest "virus scanner", sub {
|
||||||
|
$client->fail("msmtp -a test2 --tls=on --tls-certcheck=off --auth=on user1\@example.com < /etc/root/virus-email >&2");
|
||||||
|
# give the mail server some time to process the mail
|
||||||
|
$server->waitUntilFails('[ "$(postqueue -p)" != "Mail queue is empty" ]');
|
||||||
|
};
|
||||||
|
|
||||||
|
subtest "no warnings or errors", sub {
|
||||||
|
$server->fail("journalctl -u postfix | grep -i error >&2");
|
||||||
|
$server->fail("journalctl -u postfix | grep -i warning >&2");
|
||||||
|
$server->fail("journalctl -u dovecot2 | grep -i error >&2");
|
||||||
|
$server->fail("journalctl -u dovecot2 | grep -i warning >&2");
|
||||||
|
};
|
||||||
|
|
||||||
|
'';
|
||||||
|
}
|
|
@ -23,6 +23,14 @@ import <nixpkgs/nixos/tests/make-test.nix> {
|
||||||
../default.nix
|
../default.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
services.rsyslogd = {
|
||||||
|
enable = true;
|
||||||
|
defaultConfig = ''
|
||||||
|
*.* /dev/console
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
mailserver = {
|
mailserver = {
|
||||||
enable = true;
|
enable = true;
|
||||||
debug = true;
|
debug = true;
|
||||||
|
@ -56,6 +64,7 @@ import <nixpkgs/nixos/tests/make-test.nix> {
|
||||||
};
|
};
|
||||||
|
|
||||||
enableImap = true;
|
enableImap = true;
|
||||||
|
enableImapSsl = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
client = { nodes, config, pkgs, ... }: let
|
client = { nodes, config, pkgs, ... }: let
|
||||||
|
@ -71,9 +80,63 @@ import <nixpkgs/nixos/tests/make-test.nix> {
|
||||||
echo grep '^Message-ID:.*@mail.example.com>$' "$@" >&2
|
echo grep '^Message-ID:.*@mail.example.com>$' "$@" >&2
|
||||||
exec grep '^Message-ID:.*@mail.example.com>$' "$@"
|
exec grep '^Message-ID:.*@mail.example.com>$' "$@"
|
||||||
'';
|
'';
|
||||||
|
test-imap-spam = pkgs.writeScriptBin "imap-mark-spam" ''
|
||||||
|
#!${pkgs.python3.interpreter}
|
||||||
|
import imaplib
|
||||||
|
|
||||||
|
with imaplib.IMAP4_SSL('${serverIP}') as imap:
|
||||||
|
imap.login('user1@example.com', 'user1')
|
||||||
|
imap.select()
|
||||||
|
status, [response] = imap.search(None, 'ALL')
|
||||||
|
msg_ids = response.decode("utf-8").split(' ')
|
||||||
|
print(msg_ids)
|
||||||
|
assert status == 'OK'
|
||||||
|
assert len(msg_ids) == 1
|
||||||
|
|
||||||
|
imap.copy(','.join(msg_ids), 'Junk')
|
||||||
|
for num in msg_ids:
|
||||||
|
imap.store(num, '+FLAGS', '\\Deleted')
|
||||||
|
imap.expunge()
|
||||||
|
|
||||||
|
imap.select('Junk')
|
||||||
|
status, [response] = imap.search(None, 'ALL')
|
||||||
|
msg_ids = response.decode("utf-8").split(' ')
|
||||||
|
print(msg_ids)
|
||||||
|
assert status == 'OK'
|
||||||
|
assert len(msg_ids) == 1
|
||||||
|
|
||||||
|
imap.close()
|
||||||
|
'';
|
||||||
|
test-imap-ham = pkgs.writeScriptBin "imap-mark-ham" ''
|
||||||
|
#!${pkgs.python3.interpreter}
|
||||||
|
import imaplib
|
||||||
|
|
||||||
|
with imaplib.IMAP4_SSL('${serverIP}') as imap:
|
||||||
|
imap.login('user1@example.com', 'user1')
|
||||||
|
imap.select('Junk')
|
||||||
|
status, [response] = imap.search(None, 'ALL')
|
||||||
|
msg_ids = response.decode("utf-8").split(' ')
|
||||||
|
print(msg_ids)
|
||||||
|
assert status == 'OK'
|
||||||
|
assert len(msg_ids) == 1
|
||||||
|
|
||||||
|
imap.copy(','.join(msg_ids), 'INBOX')
|
||||||
|
for num in msg_ids:
|
||||||
|
imap.store(num, '+FLAGS', '\\Deleted')
|
||||||
|
imap.expunge()
|
||||||
|
|
||||||
|
imap.select('INBOX')
|
||||||
|
status, [response] = imap.search(None, 'ALL')
|
||||||
|
msg_ids = response.decode("utf-8").split(' ')
|
||||||
|
print(msg_ids)
|
||||||
|
assert status == 'OK'
|
||||||
|
assert len(msg_ids) == 1
|
||||||
|
|
||||||
|
imap.close()
|
||||||
|
'';
|
||||||
in {
|
in {
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
fetchmail msmtp procmail findutils grep-ip check-mail-id
|
fetchmail msmtp procmail findutils grep-ip check-mail-id test-imap-spam test-imap-ham
|
||||||
];
|
];
|
||||||
environment.etc = {
|
environment.etc = {
|
||||||
"root/.fetchmailrc" = {
|
"root/.fetchmailrc" = {
|
||||||
|
@ -87,7 +150,7 @@ import <nixpkgs/nixos/tests/make-test.nix> {
|
||||||
"root/.fetchmailRcLowQuota" = {
|
"root/.fetchmailRcLowQuota" = {
|
||||||
text = ''
|
text = ''
|
||||||
poll ${serverIP} with proto IMAP
|
poll ${serverIP} with proto IMAP
|
||||||
user 'lowquota\@example.com' there with password 'user1' is 'root' here
|
user 'lowquota@example.com' there with password 'user2' is 'root' here
|
||||||
mda procmail
|
mda procmail
|
||||||
'';
|
'';
|
||||||
mode = "0700";
|
mode = "0700";
|
||||||
|
@ -217,7 +280,7 @@ import <nixpkgs/nixos/tests/make-test.nix> {
|
||||||
$client->waitForUnit("multi-user.target");
|
$client->waitForUnit("multi-user.target");
|
||||||
|
|
||||||
$client->execute("cp -p /etc/root/.* ~/");
|
$client->execute("cp -p /etc/root/.* ~/");
|
||||||
$client->succeed("mkdir ~/mail");
|
$client->succeed("mkdir -p ~/mail");
|
||||||
$client->succeed("ls -la ~/ >&2");
|
$client->succeed("ls -la ~/ >&2");
|
||||||
$client->succeed("cat ~/.fetchmailrc >&2");
|
$client->succeed("cat ~/.fetchmailrc >&2");
|
||||||
$client->succeed("cat ~/.procmailrc >&2");
|
$client->succeed("cat ~/.procmailrc >&2");
|
||||||
|
@ -317,6 +380,18 @@ import <nixpkgs/nixos/tests/make-test.nix> {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
subtest "imap sieve junk trainer", sub {
|
||||||
|
# send email from user2 to user1
|
||||||
|
$client->succeed("msmtp -a test --tls=on --tls-certcheck=off --auth=on user1\@example.com < /etc/root/email1 >&2");
|
||||||
|
# give the mail server some time to process the mail
|
||||||
|
$server->waitUntilFails('[ "$(postqueue -p)" != "Mail queue is empty" ]');
|
||||||
|
|
||||||
|
$client->succeed("imap-mark-spam >&2");
|
||||||
|
$server->waitUntilSucceeds("journalctl -u dovecot2 | grep -i sa-learn-spam.sh >&2");
|
||||||
|
$client->succeed("imap-mark-ham >&2");
|
||||||
|
$server->waitUntilSucceeds("journalctl -u dovecot2 | grep -i sa-learn-ham.sh >&2");
|
||||||
|
};
|
||||||
|
|
||||||
subtest "no warnings or errors", sub {
|
subtest "no warnings or errors", sub {
|
||||||
$server->fail("journalctl -u postfix | grep -i error >&2");
|
$server->fail("journalctl -u postfix | grep -i error >&2");
|
||||||
$server->fail("journalctl -u postfix | grep -i warning >&2");
|
$server->fail("journalctl -u postfix | grep -i warning >&2");
|
||||||
|
|
Loading…
Reference in New Issue