diff --git a/default.nix b/default.nix
index 9631a7b..7fd29fd 100644
--- a/default.nix
+++ b/default.nix
@@ -78,6 +78,16 @@ in
'';
};
+ quota = mkOption {
+ type = with types; nullOr types.str;
+ default = null;
+ example = "2G";
+ description = ''
+ Per user quota rules. Accepted sizes are `xx k/M/G/T` with the
+ obvious meaning. Leave blank for the standard quota `100G`.
+ '';
+ };
+
sieveScript = mkOption {
type = with types; nullOr lines;
default = null;
diff --git a/mail-server/common.nix b/mail-server/common.nix
index c1741e3..7e968d9 100644
--- a/mail-server/common.nix
+++ b/mail-server/common.nix
@@ -14,10 +14,17 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see
-{ config }:
+{ config, lib }:
let
cfg = config.mailserver;
+ # passwd :: [ String ]
+ passwd = lib.mapAttrsToList
+ (name: value: "${name}:${value.hashedPassword}:${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;
in
{
# cert :: PATH
@@ -37,4 +44,7 @@ in
else if cfg.certificateScheme == 3
then "/var/lib/acme/${cfg.fqdn}/key.pem"
else throw "Error: Certificate Scheme must be in { 1, 2, 3 }";
+
+ # passwdFile :: PATH
+ passwdFile = builtins.toFile "passwd" (lib.concatStringsSep "\n" passwd);
}
diff --git a/mail-server/dovecot.nix b/mail-server/dovecot.nix
index 7bd52fa..fd99837 100644
--- a/mail-server/dovecot.nix
+++ b/mail-server/dovecot.nix
@@ -16,7 +16,7 @@
{ config, pkgs, lib, ... }:
-with (import ./common.nix { inherit config; });
+with (import ./common.nix { inherit config lib; });
let
cfg = config.mailserver;
@@ -33,6 +33,8 @@ in
enable = true;
enableImap = enableImap;
enablePop3 = enablePop3;
+ enablePAM = false;
+ enableQuota = true;
mailGroup = vmailGroupName;
mailUser = vmailUserName;
mailLocation = dovecot_maildir;
@@ -79,6 +81,16 @@ in
mail_plugins = $mail_plugins sieve
}
+ passdb {
+ driver = passwd-file
+ args = ${passwdFile}
+ }
+
+ userdb {
+ driver = passwd-file
+ args = ${passwdFile}
+ }
+
service auth {
unix_listener /var/lib/postfix/queue/private/auth {
mode = 0660
diff --git a/mail-server/postfix.nix b/mail-server/postfix.nix
index 27d54bc..6a6395a 100644
--- a/mail-server/postfix.nix
+++ b/mail-server/postfix.nix
@@ -16,7 +16,7 @@
{ config, pkgs, lib, ... }:
-with (import ./common.nix { inherit config; });
+with (import ./common.nix { inherit config lib; });
let
inherit (lib.strings) concatStringsSep;
@@ -124,6 +124,9 @@ in
smtpd_sasl_auth_enable = yes
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
+ # quota
+ smtpd_recipient_restrictions = check_policy_service inet:mailstore.example.com:12340
+
# TLS settings, inspired by https://github.com/jeaye/nix-files
# Submission by mail clients is handled in submissionOptions
smtpd_tls_security_level = may
diff --git a/mail-server/users.nix b/mail-server/users.nix
index 9fb341a..a2728f0 100644
--- a/mail-server/users.nix
+++ b/mail-server/users.nix
@@ -28,16 +28,6 @@ let
group = vmailGroupName;
};
- # accountsToUser :: String -> UserRecord
- accountsToUser = account: {
- isNormalUser = false;
- group = vmailGroupName;
- inherit (account) hashedPassword name;
- };
-
- # mail_users :: { [String]: UserRecord }
- mail_users = lib.foldl (prev: next: prev // { "${next.name}" = next; }) {}
- (map accountsToUser (lib.attrValues loginAccounts));
virtualMailUsersActivationScript = pkgs.writeScript "activate-virtual-mail-users" ''
#!${pkgs.stdenv.shell}
@@ -82,7 +72,7 @@ in {
};
# define all users
- users.users = mail_users // {
+ users.users = {
"${vmail_user.name}" = lib.mkForce vmail_user;
};
diff --git a/tests/extern.nix b/tests/extern.nix
index c06c983..0416f4e 100644
--- a/tests/extern.nix
+++ b/tests/extern.nix
@@ -25,6 +25,7 @@ import {
mailserver = {
enable = true;
+ debug = true;
fqdn = "mail.example.com";
domains = [ "example.com" "example2.com" ];
dhParamBitLength = 512;
@@ -42,6 +43,10 @@ import {
"user@example2.com" = {
hashedPassword = "$6$u61JrAtuI0a$nGEEfTP5.eefxoScUGVG/Tl0alqla2aGax4oTd85v3j3xSmhv/02gNfSemv/aaMinlv9j/ZABosVKBrRvN5Qv0";
};
+ "lowquota@example.com" = {
+ hashedPassword = "$6$u61JrAtuI0a$nGEEfTP5.eefxoScUGVG/Tl0alqla2aGax4oTd85v3j3xSmhv/02gNfSemv/aaMinlv9j/ZABosVKBrRvN5Qv0";
+ quota = "1B";
+ };
};
enableImap = true;
@@ -224,5 +229,22 @@ import {
# if this succeeds, it means that user1 recieved the mail that was intended for chuck.
$client->fail("fetchmail -v");
};
+
+ subtest "quota", sub {
+ $client->execute("rm ~/mail/*");
+
+ $client->succeed("echo '${fetchmailRcLowQuota}' > ~/.fetchmailrc");
+ $client->succeed("sed -i s/SERVER/`getent hosts server | awk '{ print \$1 }'`/g ~/.fetchmailrc");
+
+ $client->succeed("chmod 0700 ~/.fetchmailrc");
+ $client->succeed("echo '${email2}' > mail.txt");
+ # send email from chuck to non exsitent account
+ $client->succeed("msmtp -a test3 --tls=on --tls-certcheck=off --auth=on lowquota\@example.com < mail.txt >&2");
+ $client->succeed("sleep 5");
+ # fetchmail returns EXIT_CODE 0 when it retrieves mail
+ $client->fail("fetchmail -v");
+
+ };
+
'';
}
diff --git a/tests/intern.nix b/tests/intern.nix
index 9facc44..e191d60 100644
--- a/tests/intern.nix
+++ b/tests/intern.nix
@@ -45,14 +45,6 @@ import {
$machine->start;
$machine->waitForUnit("multi-user.target");
- subtest "user exists", sub {
- $machine->succeed("cat /etc/shadow | grep 'user1\@example.com'");
- };
-
- subtest "password is set", sub {
- $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");
};