Merge branch 'master' of github.com:r-raymond/nixos-mailserver

This commit is contained in:
Robin Raymond 2017-11-13 12:43:11 +01:00
commit 8e86234228
15 changed files with 311 additions and 86 deletions

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

View File

@ -1,6 +1,5 @@
language: nix language: nix
script: script:
- git clone --depth 1 https://github.com/NixOS/nixpkgs ../nixpkgs
- nix-build tests/intern.nix - nix-build tests/intern.nix
- nix-build tests/extern.nix - nix-build tests/extern.nix

104
README.md
View File

@ -1,5 +1,6 @@
# ![Simple Nixos MailServer][logo] # ![Simple Nixos MailServer][logo]
![license](https://img.shields.io/badge/license-GPL3-brightgreen.svg) ![license](https://img.shields.io/badge/license-GPL3-brightgreen.svg)
![status](https://travis-ci.org/r-raymond/nixos-mailserver.svg?branch=master)
## Stable Releases ## Stable Releases
@ -9,7 +10,8 @@ None so far.
[Latest Release Candidate](https://github.com/r-raymond/nixos-mailserver/releases/latest) [Latest Release Candidate](https://github.com/r-raymond/nixos-mailserver/releases/latest)
## Features ## Features
### v1.1 ### v2.0
* [x] Multiple Domains
* Postfix MTA * Postfix MTA
- [x] smtp on port 25 - [x] smtp on port 25
- [x] submission port 587 - [x] submission port 587
@ -21,6 +23,7 @@ None so far.
* Certificates * Certificates
- [x] manual certificates - [x] manual certificates
- [x] on the fly creation - [x] on the fly creation
- [x] Let's Encrypt
* Spam Filtering * Spam Filtering
- [x] via rspamd - [x] via rspamd
- [x] hard coded sieve script to move spam into Junk folder - [x] hard coded sieve script to move spam into Junk folder
@ -32,17 +35,13 @@ None so far.
- [x] declarative user management - [x] declarative user management
- [x] declarative password management - [x] declarative password management
### In the future
### v1.2
* Certificates
- [ ] Let's Encrypt
* Sieves * Sieves
- [ ] Allow user defined sieve scripts - [ ] Allow user defined sieve scripts
* User Aliases * User Aliases
- [ ] More complete alias support - [ ] More complete alias support
* DKIM Signing
### v2.0 - [ ] Allow a per domain selector
* [ ] Multiple Domains
### Changelog ### Changelog
@ -50,6 +49,45 @@ None so far.
* Changed structure to Nix Modules * Changed structure to Nix Modules
* Adds Sieve support * Adds Sieve support
#### v1.1 -> v2.0
* rename domain to fqdn, seperate fqdn from domains
* multi domain support
### How to Deploy
```nix
{ config, pkgs, ... }:
{
imports = [
(builtins.fetchTarball "https://github.com/r-raymond/nixos-mailserver/releases/tag/v2.0-rc1")
];
mailserver = {
enable = true;
fqdn = "mail.example.com";
domains = [ "example.com" "example2.com" ];
loginAccounts = {
"user1@example.com" = {
hashedPassword = "$6$/z4n8AQl6K$kiOkBTWlZfBd7PvF5GsJ8PmPgdZsFGN1jPGZufxxr60PoR0oUsrvzm2oQiflyz5ir9fFJ.d/zKm/NgLXNUsNX/";
};
};
virtualAliases = {
# address = forward address;
"info@example.com" = "user1@example.com";
"postmaster@example.com" = "user1@example.com";
"abuse@example.com" = "user1@example.com";
"user1@example2.com" = "user1@example.com";
"info@example2.com" = "user1@example.com";
"postmaster@example2.com" = "user1@example.com";
"abuse@example2.com" = "user1@example.com";
};
};
}
```
For a complete list of options, see `default.nix`.
### How to Test ### How to Test
You can test the setup via `nixops`. After installation, do You can test the setup via `nixops`. After installation, do
@ -73,6 +111,52 @@ openssl s_client -host mail.example.com -port 143 -starttls imap
``` ```
## How to Set Up a 10/10 Mail Server
Mail servers can be a tricky thing to set up. This guide is supposed to run you
through the most important steps to achieve a 10/10 score on `mail-tester.com`.
### Fully Qualified Domain Name
No matter how many domains you want to serve on your mail server, you need to
settle on a _Fully Qualified Domain Name_ (FQDN) where your server is reachable,
so that other servers can find yours. Common FQDN include `mx.example.com`
(where `example.com` is a domain you own) or `mail.example.com`.
After you settled on a FQDN (we will assume `mx.example.com` henceforth) you
need to
* Set a DNS entry on your domain to point to the IP of the server. For this
add a DNS record such as
| Name (Subdomain) | TTL | Type | Priority | Value |
| ---------------- | ----- | ---- | -------- | ----------------- |
| mx.example.com | 10800 | A | | `xxx.xxx.xxx.xxx` |
to your domain, where `xxx.xxx.xxx.xxx` is the IP of your server.
* Set a `rDNS` (reverse DNS) entry for your FQDN. You need to do so wherever
you have rented your server. Make sure that `xxx.xxx.xxx.xxx` resolves to
`mx.example.com`.
### MX Record
| Name (Subdomain) | TTL | Type | Priority | Value |
| ---------------- | ----- | ---- | -------- | ----------------- |
| domain1.com | | MX | 10 | mx.exmaple.com |
### Spf record
| Name (Subdomain) | TTL | Type | Priority | Value |
| ---------------- | ----- | ---- | -------- | ----------------- |
| domain1.com | 10800 | TXT | | `v=spf1 ip4:xxx.xxx.xxx.xxx -all` |
### DKIM signature
| Name (Subdomain) | TTL | Type | Priority | Value |
| ---------------- | ----- | ---- | -------- | ----------------- |
| dkim._domainkey.domain1.com | 10800 | TXT | | `v=DKIM1; p=yyyyyyyyyyyy` |
where `yyyyyyyyyyyy` is the `DKIM` signature
## A Complete Mail Server Without Moving Parts ## A Complete Mail Server Without Moving Parts
### Used Technologies ### Used Technologies
@ -87,7 +171,7 @@ openssl s_client -host mail.example.com -port 143 -starttls imap
* Pam * Pam
### Features ### Features
* one domain * unlimited domain
* unlimited mail accounts * unlimited mail accounts
* unlimited aliases for every mail account * unlimited aliases for every mail account
* spam and virus checking * spam and virus checking
@ -104,8 +188,10 @@ openssl s_client -host mail.example.com -port 143 -starttls imap
## Contributors ## Contributors
* Special thanks to @Infinisil for the module rewrite * Special thanks to @Infinisil for the module rewrite
* Special thanks to @jbboehr for multidomain implementation
* @danbst * @danbst
* @phdoerfler * @phdoerfler
* @eqyiel
### Credits ### Credits

View File

@ -26,19 +26,17 @@ in
options.mailserver = { options.mailserver = {
enable = mkEnableOption "nixos-mailserver"; enable = mkEnableOption "nixos-mailserver";
domain = mkOption { fqdn = mkOption {
type = types.str; type = types.str;
example = "example.com"; example = "example.com";
description = "The domain that this mail server serves. So far only one domain is supported"; description = "The fully qualified domain name of the mail server.";
}; };
hostPrefix = mkOption { domains = mkOption {
type = types.str; type = types.listOf types.str;
default = "mail"; example = [ "example.com" ];
description = '' default = [];
The prefix of the FQDN of the server. In this example the FQDN of the server description = "The domains that this mail server serves.";
is given by 'mail.example.com'
'';
}; };
loginAccounts = mkOption { loginAccounts = mkOption {
@ -113,7 +111,7 @@ in
vmailUserName = mkOption { vmailUserName = mkOption {
type = types.str; type = types.str;
default = "vmail"; default = "virtualMail";
description = '' description = ''
The user name and group name of the user that owns the directory where all The user name and group name of the user that owns the directory where all
the mail is stored. the mail is stored.
@ -122,7 +120,7 @@ in
vmailGroupName = mkOption { vmailGroupName = mkOption {
type = types.str; type = types.str;
default = "vmail"; default = "virtualMail";
description = '' description = ''
The user name and group name of the user that owns the directory where all The user name and group name of the user that owns the directory where all
the mail is stored. the mail is stored.
@ -138,7 +136,7 @@ in
}; };
certificateScheme = mkOption { certificateScheme = mkOption {
type = types.enum [ 1 2 ]; type = types.enum [ 1 2 3 ];
default = 2; default = 2;
description = '' description = ''
Certificate Files. There are three options for these. Certificate Files. There are three options for these.
@ -149,8 +147,6 @@ in
this implies that a stripped down webserver has to be started. This also this implies that a stripped down webserver has to be started. This also
implies that the FQDN must be set as an `A` record to point to the IP of implies that the FQDN must be set as an `A` record to point to the IP of
the server. TODO: Explain more details the server. TODO: Explain more details
TODO: Only certificate scheme 1) and 2) work as of yet.
''; '';
}; };
@ -188,7 +184,7 @@ in
default = true; default = true;
description = '' description = ''
Whether to enable imap / pop3. Both variants are only supported in the Whether to enable imap / pop3. Both variants are only supported in the
(sane) startTLS configuration. (TODO: Allow SSL ports). The ports are (sane) startTLS configuration. The ports are
110 - Pop3 110 - Pop3
143 - IMAP 143 - IMAP
@ -196,12 +192,21 @@ in
''; '';
}; };
enableImapSsl = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable IMAPS, setting this option to true will open port 993
in the firewall.
'';
};
enablePop3 = mkOption { enablePop3 = mkOption {
type = types.bool; type = types.bool;
default = false; default = false;
description = '' description = ''
Whether to enable POP3. Both variants are only supported in the Whether to enable POP3. Both variants are only supported in the (sane)
(sane) startTLS configuration. (TODO: Allow SSL ports). The ports are startTLS configuration. The ports are
110 - Pop3 110 - Pop3
143 - IMAP 143 - IMAP
@ -209,8 +214,14 @@ in
''; '';
}; };
# imapSsl = mkOption {} #< TODO enablePop3Ssl = mkOption {
# pop3Ssl = mkOption {} #< TODO type = types.bool;
default = false;
description = ''
Whether to enable POP3S, setting this option to true will open port 995
in the firewall.
'';
};
virusScanning = mkOption { virusScanning = mkOption {
type = types.bool; type = types.bool;
@ -256,5 +267,6 @@ in
./mail-server/dovecot.nix ./mail-server/dovecot.nix
./mail-server/postfix.nix ./mail-server/postfix.nix
./mail-server/rmilter.nix ./mail-server/rmilter.nix
./mail-server/nginx.nix
]; ];
} }

View File

@ -24,13 +24,17 @@ in
certificatePath = if cfg.certificateScheme == 1 certificatePath = if cfg.certificateScheme == 1
then cfg.certificateFile then cfg.certificateFile
else if cfg.certificateScheme == 2 else if cfg.certificateScheme == 2
then "${cfg.certificateDirectory}/cert-${cfg.domain}.pem" then "${cfg.certificateDirectory}/cert-${cfg.fqdn}.pem"
else ""; else if cfg.certificateScheme == 3
then "/var/lib/acme/${cfg.fqdn}/fullchain.pem"
else throw "Error: Certificate Scheme must be in { 1, 2, 3 }";
# key :: PATH # key :: PATH
keyPath = if cfg.certificateScheme == 1 keyPath = if cfg.certificateScheme == 1
then cfg.keyFile then cfg.keyFile
else if cfg.certificateScheme == 2 else if cfg.certificateScheme == 2
then "${cfg.certificateDirectory}/key-${cfg.domain}.pem" then "${cfg.certificateDirectory}/key-${cfg.fqdn}.pem"
else ""; else if cfg.certificateScheme == 3
then "/var/lib/acme/${cfg.fqdn}/key.pem"
else throw "Error: Certificate Scheme must be in { 1, 2, 3 }";
} }

View File

@ -24,8 +24,10 @@ in
networking.firewall = { networking.firewall = {
allowedTCPPorts = [ 25 587 ] allowedTCPPorts = [ 25 587 ]
++ (if enableImap then [ 143 ] else []) ++ lib.optional enableImap 143
++ (if enablePop3 then [ 110 ] else []); ++ lib.optional enableImapSsl 993
++ lib.optional enablePop3 110
++ lib.optional enablePop3Ssl 995;
}; };
}; };
} }

48
mail-server/nginx.nix Normal file
View File

@ -0,0 +1,48 @@
# nixos-mailserver: a simple mail server
# Copyright (C) 2016-2017 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, ... }:
with (import ./common.nix { inherit config; });
let
cfg = config.mailserver;
acmeRoot = "/var/lib/acme/acme-challenge";
in
{
config = lib.mkIf (cfg.certificateScheme == 3) {
services.nginx = {
enable = true;
virtualHosts."${cfg.fqdn}" = {
serverName = cfg.fqdn;
forceSSL = true;
enableACME = true;
acmeRoot = acmeRoot;
};
};
security.acme.certs."${cfg.fqdn}".postRun = #{
# domain = "${cfg.fqdn}";
# webroot = acmeRoot;
# postRun =
''
systemctl reload nginx
systemctl reload postfix
systemctl reload dovecot2
'';
# };
};
}

View File

@ -19,17 +19,18 @@
with (import ./common.nix { inherit config; }); with (import ./common.nix { inherit config; });
let let
inherit (lib.strings) concatStringsSep;
cfg = config.mailserver; cfg = config.mailserver;
# valiases_postfix :: [ String ] # valiases_postfix :: [ String ]
valiases_postfix = map valiases_postfix = map
(from: (from:
let to = cfg.virtualAliases.${from}; let to = cfg.virtualAliases.${from};
in "${from}@${cfg.domain} ${to}@${cfg.domain}") in "${from} ${to}")
(builtins.attrNames cfg.virtualAliases); (builtins.attrNames cfg.virtualAliases);
# accountToIdentity :: User -> String # accountToIdentity :: User -> String
accountToIdentity = account: "${account.name}@${cfg.domain} ${account.name}@${cfg.domain}"; accountToIdentity = account: "${account.name} ${account.name}";
# vaccounts_identity :: [ String ] # vaccounts_identity :: [ String ]
vaccounts_identity = map accountToIdentity (lib.attrValues cfg.loginAccounts); vaccounts_identity = map accountToIdentity (lib.attrValues cfg.loginAccounts);
@ -38,7 +39,7 @@ let
valiases_file = builtins.toFile "valias" (lib.concatStringsSep "\n" valiases_postfix); valiases_file = builtins.toFile "valias" (lib.concatStringsSep "\n" valiases_postfix);
# vhosts_file :: Path # vhosts_file :: Path
vhosts_file = builtins.toFile "vhosts" cfg.domain; vhosts_file = builtins.toFile "vhosts" (concatStringsSep "\n" cfg.domains);
# vaccounts_file :: Path # vaccounts_file :: Path
# see # see
@ -49,9 +50,9 @@ let
vaccounts_file = builtins.toFile "vaccounts" (lib.concatStringsSep "\n" (vaccounts_identity ++ valiases_postfix)); vaccounts_file = builtins.toFile "vaccounts" (lib.concatStringsSep "\n" (vaccounts_identity ++ valiases_postfix));
submissionHeaderCleanupRules = pkgs.writeText "submission_header_cleanup_rules" '' submissionHeaderCleanupRules = pkgs.writeText "submission_header_cleanup_rules" ''
### Removes sensitive headers from mails handed in via the submission port. # Removes sensitive headers from mails handed in via the submission port.
### See https://thomas-leister.de/mailserver-debian-stretch/ # See https://thomas-leister.de/mailserver-debian-stretch/
### Uses "pcre" style regex. # Uses "pcre" style regex.
/^Received:/ IGNORE /^Received:/ IGNORE
/^X-Originating-IP:/ IGNORE /^X-Originating-IP:/ IGNORE
@ -76,7 +77,7 @@ in
'' ''
# Extra Config # Extra Config
smtpd_banner = $myhostname ESMTP NO UCE smtpd_banner = ${fqdn} ESMTP NO UCE
disable_vrfy_command = yes disable_vrfy_command = yes
message_size_limit = 20971520 message_size_limit = 20971520

View File

@ -28,6 +28,8 @@ let
'' ''
else ""; else "";
dkim = if cfg.dkimSigning 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 then
'' ''
dkim { dkim {

View File

@ -24,14 +24,14 @@ let
cert = if cfg.certificateScheme == 1 cert = if cfg.certificateScheme == 1
then cfg.certificateFile then cfg.certificateFile
else if cfg.certificateScheme == 2 else if cfg.certificateScheme == 2
then "${cfg.certificateDirectory}/cert-${cfg.domain}.pem" then "${cfg.certificateDirectory}/cert-${cfg.fqdn.pem"
else ""; else "";
# key :: PATH # key :: PATH
key = if cfg.certificateScheme == 1 key = if cfg.certificateScheme == 1
then cfg.keyFile then cfg.keyFile
else if cfg.certificateScheme == 2 else if cfg.certificateScheme == 2
then "${cfg.certificateDirectory}/key-${cfg.domain}.pem" then "${cfg.certificateDirectory}/key-${cfg.fqdn}.pem"
else ""; else "";
in in
{ {

View File

@ -23,10 +23,10 @@ let
'' ''
# Create certificates if they do not exist yet # Create certificates if they do not exist yet
dir="${cfg.certificateDirectory}" dir="${cfg.certificateDirectory}"
fqdn="${cfg.hostPrefix}.${cfg.domain}" fqdn="${cfg.fqdn}"
case $fqdn in /*) fqdn=$(cat "$fqdn");; esac case $fqdn in /*) fqdn=$(cat "$fqdn");; esac
key="''${dir}/key-${cfg.domain}.pem"; key="''${dir}/key-${cfg.fqdn}.pem";
cert="''${dir}/cert-${cfg.domain}.pem"; cert="''${dir}/cert-${cfg.fqdn}.pem";
if [ ! -f "''${key}" ] || [ ! -f "''${cert}" ] if [ ! -f "''${key}" ] || [ ! -f "''${cert}" ]
then then
@ -38,22 +38,31 @@ let
'' ''
else ""; else "";
dkim_key = "${cfg.dkimKeyDirectory}/${cfg.dkimSelector}.private"; createDomainDkimCert = dom:
dkim_txt = "${cfg.dkimKeyDirectory}/${cfg.dkimSelector}.txt"; 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_cert =
'' ''
# Create dkim dir # Create dkim dir
mkdir -p "${cfg.dkimKeyDirectory}" mkdir -p "${cfg.dkimKeyDirectory}"
chown rmilter:rmilter "${cfg.dkimKeyDirectory}" chown rmilter:rmilter "${cfg.dkimKeyDirectory}"
if [ ! -f "${dkim_key}" ] || [ ! -f "${dkim_txt}" ] ${createAllCerts}
then
${pkgs.opendkim}/bin/opendkim-genkey -s "${cfg.dkimSelector}" \ chown -R rmilter:rmilter "${cfg.dkimKeyDirectory}"
-d ${cfg.domain} \
--directory="${cfg.dkimKeyDirectory}"
chown rmilter:rmilter "${dkim_key}"
fi
''; '';
in in
{ {
@ -63,6 +72,7 @@ in
# Create certificates and maildir folder # Create certificates and maildir folder
systemd.services.postfix = { systemd.services.postfix = {
after = (if (certificateScheme == 3) then [ "nginx.service" ] else []);
preStart = preStart =
'' ''
# Create mail directory and set permissions. See # Create mail directory and set permissions. See

View File

@ -19,25 +19,26 @@
with config.mailserver; with config.mailserver;
let let
vmail_user = [{ vmail_user = {
#name = vmailUserName; name = vmailUserName;
isNormalUser = false; isNormalUser = false;
uid = vmailUIDStart; uid = vmailUIDStart;
home = mailDirectory; home = mailDirectory;
createHome = true; createHome = true;
#group = vmailGroupName; group = vmailGroupName;
}]; };
# accountsToUser :: String -> UserRecord # accountsToUser :: String -> UserRecord
accountsToUser = account: { accountsToUser = account: {
name = account.name + "@" + domain; name = account.name;
isNormalUser = false; isNormalUser = false;
group = vmailGroupName; group = vmailGroupName;
inherit (account) hashedPassword; inherit (account) hashedPassword;
}; };
# mail_user :: [ UserRecord ] # mail_users :: { [String]: UserRecord }
mail_user = map accountsToUser (lib.attrValues loginAccounts); mail_users = lib.foldl (prev: next: prev // { "${next.name}" = next; }) {}
(map accountsToUser (lib.attrValues loginAccounts));
in in
{ {
@ -45,10 +46,12 @@ in
config = lib.mkIf enable { config = lib.mkIf enable {
# set the vmail gid to a specific value # set the vmail gid to a specific value
users.groups = { users.groups = {
vmail = { gid = vmailUIDStart; }; "${vmailGroupName}" = { gid = vmailUIDStart; };
}; };
# define all users # define all users
users.extraUsers = vmail_user ++ mail_user; users.users = mail_users // {
"${vmail_user.name}" = lib.mkForce vmail_user;
};
}; };
} }

View File

@ -10,18 +10,21 @@
mailserver = { mailserver = {
enable = true; enable = true;
domain = "example.com"; fqdn = "mail.example.com";
domains = [ "example.com" "example2.com" ];
hostPrefix = "mail";
loginAccounts = { loginAccounts = {
user1 = { "user1@example.com" = {
hashedPassword = "$6$/z4n8AQl6K$kiOkBTWlZfBd7PvF5GsJ8PmPgdZsFGN1jPGZufxxr60PoR0oUsrvzm2oQiflyz5ir9fFJ.d/zKm/NgLXNUsNX/"; hashedPassword = "$6$/z4n8AQl6K$kiOkBTWlZfBd7PvF5GsJ8PmPgdZsFGN1jPGZufxxr60PoR0oUsrvzm2oQiflyz5ir9fFJ.d/zKm/NgLXNUsNX/";
}; };
}; };
virtualAliases = { virtualAliases = {
info = "user1"; "info@example.com" = "user1@example.com";
postmaster = "user1"; "postmaster@example.com" = "user1@example.com";
abuse = "user1"; "abuse@example.com" = "user1@example.com";
"user1@example2.com" = "user1@example.com";
"info@example2.com" = "user1@example.com";
"postmaster@example2.com" = "user1@example.com";
"abuse@example2.com" = "user1@example.com";
}; };
}; };
}; };

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/> # along with this program. If not, see <http://www.gnu.org/licenses/>
import ./../../nixpkgs/nixos/tests/make-test.nix { import <nixpkgs/nixos/tests/make-test.nix> {
nodes = nodes =
{ server = { config, pkgs, ... }: { server = { config, pkgs, ... }:
@ -25,14 +25,17 @@ import ./../../nixpkgs/nixos/tests/make-test.nix {
mailserver = { mailserver = {
enable = true; enable = true;
domain = "example.com"; fqdn = "mail.example.com";
domains = [ "example.com" "example2.com" ];
hostPrefix = "mail";
loginAccounts = { loginAccounts = {
user1 = { "user1@example.com" = {
hashedPassword = "$6$/z4n8AQl6K$kiOkBTWlZfBd7PvF5GsJ8PmPgdZsFGN1jPGZufxxr60PoR0oUsrvzm2oQiflyz5ir9fFJ.d/zKm/NgLXNUsNX/"; hashedPassword = "$6$/z4n8AQl6K$kiOkBTWlZfBd7PvF5GsJ8PmPgdZsFGN1jPGZufxxr60PoR0oUsrvzm2oQiflyz5ir9fFJ.d/zKm/NgLXNUsNX/";
}; };
user2 = { "user2@example.com" = {
hashedPassword = "$6$u61JrAtuI0a$nGEEfTP5.eefxoScUGVG/Tl0alqla2aGax4oTd85v3j3xSmhv/02gNfSemv/aaMinlv9j/ZABosVKBrRvN5Qv0";
};
"user@example2.com" = {
hashedPassword = "$6$u61JrAtuI0a$nGEEfTP5.eefxoScUGVG/Tl0alqla2aGax4oTd85v3j3xSmhv/02gNfSemv/aaMinlv9j/ZABosVKBrRvN5Qv0"; hashedPassword = "$6$u61JrAtuI0a$nGEEfTP5.eefxoScUGVG/Tl0alqla2aGax4oTd85v3j3xSmhv/02gNfSemv/aaMinlv9j/ZABosVKBrRvN5Qv0";
}; };
}; };
@ -68,6 +71,13 @@ import ./../../nixpkgs/nixos/tests/make-test.nix {
from user2\@example.com from user2\@example.com
user user2\@example.com user user2\@example.com
password user2 password user2
account test2
host SERVER
port 587
from user\@example2.com
user user\@example2.com
password user2
''; '';
email1 = email1 =
'' ''
@ -82,6 +92,21 @@ import ./../../nixpkgs/nixos/tests/make-test.nix {
how are you doing today? how are you doing today?
''; '';
email2 =
''
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
'';
in in
'' ''
startAll; startAll;
@ -121,5 +146,19 @@ import ./../../nixpkgs/nixos/tests/make-test.nix {
# make sure our IP is _not_ in the email header # make sure our IP is _not_ in the email header
$client->fail("grep `ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print \$2}' | cut -f1 -d'/'` ~/mail/*"); $client->fail("grep `ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print \$2}' | cut -f1 -d'/'` ~/mail/*");
}; };
subtest "dkim singing, multiple domains", sub {
$client->succeed("rm ~/mail/*");
$client->succeed("rm mail.txt");
$client->succeed("echo '${email2}' > mail.txt");
# send email from user2 to user1
$client->succeed("msmtp -a test2 --tls=on --tls-certcheck=off --auth=on user1\@example.com < mail.txt >&2");
$client->succeed("sleep 5");
# fetchmail returns EXIT_CODE 0 when it retrieves mail
$client->succeed("fetchmail -v");
$client->succeed("cat ~/mail/* >&2");
# make sure it is dkim signed
$client->succeed("grep DKIM ~/mail/*");
};
''; '';
} }

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/> # along with this program. If not, see <http://www.gnu.org/licenses/>
import ./../../nixpkgs/nixos/tests/make-test.nix { import <nixpkgs/nixos/tests/make-test.nix> {
machine = machine =
{ config, pkgs, ... }: { config, pkgs, ... }:
@ -25,14 +25,17 @@ import ./../../nixpkgs/nixos/tests/make-test.nix {
mailserver = { mailserver = {
enable = true; enable = true;
domain = "example.com"; fqdn = "mail.example.com";
domains = [ "example.com" ];
hostPrefix = "mail";
loginAccounts = { loginAccounts = {
user1 = { "user1@example.com" = {
hashedPassword = "$6$/z4n8AQl6K$kiOkBTWlZfBd7PvF5GsJ8PmPgdZsFGN1jPGZufxxr60PoR0oUsrvzm2oQiflyz5ir9fFJ.d/zKm/NgLXNUsNX/"; hashedPassword = "$6$/z4n8AQl6K$kiOkBTWlZfBd7PvF5GsJ8PmPgdZsFGN1jPGZufxxr60PoR0oUsrvzm2oQiflyz5ir9fFJ.d/zKm/NgLXNUsNX/";
}; };
}; };
vmailGroupName = "vmail";
vmailUIDStart = 5000;
}; };
}; };
@ -49,5 +52,9 @@ import ./../../nixpkgs/nixos/tests/make-test.nix {
$machine->succeed("cat /etc/shadow | grep 'user1\@example.com:\$6\$/z4n8AQl6K\$kiOkBTWlZfBd7PvF5GsJ8PmPgdZsFGN1jPGZufxxr60PoR0oUsrvzm2oQiflyz5ir9fFJ.d/zKm/NgLXNUsNX/:1::::::'"); $machine->succeed("cat /etc/shadow | grep 'user1\@example.com:\$6\$/z4n8AQl6K\$kiOkBTWlZfBd7PvF5GsJ8PmPgdZsFGN1jPGZufxxr60PoR0oUsrvzm2oQiflyz5ir9fFJ.d/zKm/NgLXNUsNX/:1::::::'");
}; };
subtest "vmail gid is set correctly", sub {
$machine->succeed("getent group vmail | grep 5000");
};
''; '';
} }