feat: mailserver
This commit is contained in:
parent
0ebf0d7a29
commit
b8a31b6264
28 changed files with 2446 additions and 1350 deletions
871
pkgs/options/dovecot.nix
Normal file
871
pkgs/options/dovecot.nix
Normal file
|
|
@ -0,0 +1,871 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib)
|
||||
attrValues
|
||||
concatMapStringsSep
|
||||
concatStrings
|
||||
concatStringsSep
|
||||
flatten
|
||||
imap1
|
||||
literalExpression
|
||||
mapAttrsToList
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
optional
|
||||
optionalAttrs
|
||||
optionalString
|
||||
singleton
|
||||
types
|
||||
nameValuePair
|
||||
mapAttrs'
|
||||
listToAttrs
|
||||
filter
|
||||
;
|
||||
inherit (lib.strings) match hasPrefix;
|
||||
|
||||
cfg = config.services.dovecot;
|
||||
dovecotPkg = pkgs.dovecot;
|
||||
|
||||
pversion = dovecotPkg.version;
|
||||
isVersion24 = hasPrefix "2.4" pversion;
|
||||
|
||||
baseDir = "/run/dovecot";
|
||||
stateDir = "/var/lib/dovecot";
|
||||
|
||||
sieveScriptSettings = mapAttrs' (
|
||||
to: _: nameValuePair "sieve_${to}" "${stateDir}/sieve/${to}"
|
||||
) cfg.sieve.scripts;
|
||||
imapSieveMailboxSettings = listToAttrs (
|
||||
flatten (
|
||||
imap1 (
|
||||
idx: el:
|
||||
singleton {
|
||||
name = "imapsieve_mailbox${toString idx}_name";
|
||||
value = el.name;
|
||||
}
|
||||
++ optional (el.from != null) {
|
||||
name = "imapsieve_mailbox${toString idx}_from";
|
||||
value = el.from;
|
||||
}
|
||||
++ optional (el.causes != [ ]) {
|
||||
name = "imapsieve_mailbox${toString idx}_causes";
|
||||
value = concatStringsSep "," el.causes;
|
||||
}
|
||||
++ optional (el.before != null) {
|
||||
name = "imapsieve_mailbox${toString idx}_before";
|
||||
value = "file:${stateDir}/imapsieve/before/${baseNameOf el.before}";
|
||||
}
|
||||
++ optional (el.after != null) {
|
||||
name = "imapsieve_mailbox${toString idx}_after";
|
||||
value = "file:${stateDir}/imapsieve/after/${baseNameOf el.after}";
|
||||
}
|
||||
) cfg.imapsieve.mailbox
|
||||
)
|
||||
);
|
||||
|
||||
mkExtraConfigCollisionWarning = term: ''
|
||||
You referred to ${term} in `services.dovecot.extraConfig`.
|
||||
|
||||
Due to gradual transition to structured configuration for plugin configuration, it is possible
|
||||
this will cause your plugin configuration to be ignored.
|
||||
|
||||
Consider setting `services.dovecot.pluginSettings.${term}` instead.
|
||||
'';
|
||||
|
||||
# Those settings are automatically set based on other parts
|
||||
# of this module.
|
||||
automaticallySetPluginSettings =
|
||||
[
|
||||
"sieve_plugins"
|
||||
"sieve_extensions"
|
||||
"sieve_global_extensions"
|
||||
"sieve_pipe_bin_dir"
|
||||
]
|
||||
++ (builtins.attrNames sieveScriptSettings)
|
||||
++ (builtins.attrNames imapSieveMailboxSettings);
|
||||
|
||||
# The idea is to match everything that looks like `$term =`
|
||||
# but not `# $term something something`
|
||||
# or `# $term = some value` because those are comments.
|
||||
configContainsSetting = lines: term: (match "[[:blank:]]*${term}[[:blank:]]*=.*" lines) != null;
|
||||
|
||||
warnAboutExtraConfigCollisions = map mkExtraConfigCollisionWarning (
|
||||
filter (configContainsSetting cfg.extraConfig) automaticallySetPluginSettings
|
||||
);
|
||||
|
||||
sievePipeBinScriptDirectory = pkgs.linkFarm "sieve-pipe-bins" (
|
||||
map (el: {
|
||||
name = builtins.unsafeDiscardStringContext (baseNameOf el);
|
||||
path = el;
|
||||
}) cfg.sieve.pipeBins
|
||||
);
|
||||
|
||||
dovecotConf = concatStrings [
|
||||
(optionalString isVersion24 ''
|
||||
dovecot_config_version = ${pversion}
|
||||
dovecot_storage_version = ${pversion}
|
||||
'')
|
||||
|
||||
''
|
||||
base_dir = ${baseDir}
|
||||
protocols = ${(concatStringsSep " " cfg.protocols)}
|
||||
sendmail_path = /run/wrappers/bin/sendmail
|
||||
mail_plugin_dir = /run/current-system/sw/lib/dovecot/modules
|
||||
# defining mail_plugins must be done before the first protocol {} filter because of https://doc.dovecot.org/configuration_manual/config_file/config_file_syntax/#variable-expansion
|
||||
mail_plugins = ${concatStringsSep " " cfg.mailPlugins.globally.enable}
|
||||
''
|
||||
|
||||
(concatStringsSep "\n" (
|
||||
mapAttrsToList (protocol: plugins: ''
|
||||
protocol ${protocol} {
|
||||
mail_plugins = $mail_plugins ${concatStringsSep " " plugins.enable}
|
||||
}
|
||||
'') cfg.mailPlugins.perProtocol
|
||||
))
|
||||
|
||||
(
|
||||
if cfg.sslServerCert == null then
|
||||
''
|
||||
ssl = no
|
||||
auth_allow_cleartext = yes
|
||||
''
|
||||
else
|
||||
''
|
||||
ssl_server_cert_file = ${cfg.sslServerCert}
|
||||
ssl_server_key_file = ${cfg.sslServerKey}
|
||||
${optionalString (cfg.sslCACert != null) ("ssl_server_ca_file = " + cfg.sslCACert)}
|
||||
${optionalString cfg.enableDHE ''ssl_server_dh_file = ${config.security.dhparams.params.dovecot.path}''}
|
||||
auth_allow_cleartext = no
|
||||
''
|
||||
)
|
||||
|
||||
''
|
||||
default_internal_user = ${cfg.user}
|
||||
default_internal_group = ${cfg.group}
|
||||
${optionalString (cfg.mailUser != null) "mail_uid = ${cfg.mailUser}"}
|
||||
${optionalString (cfg.mailGroup != null) "mail_gid = ${cfg.mailGroup}"}
|
||||
|
||||
|
||||
mail_driver = maildir
|
||||
mail_path = ${cfg.mailLocation}
|
||||
mail_inbox_path = ${cfg.mailLocation}/.INBOX
|
||||
|
||||
maildir_copy_with_hardlinks = yes
|
||||
${
|
||||
if isVersion24 then
|
||||
''
|
||||
pop3_uidl_format = %{uidvalidity | hex(8)}%{user | hex(8)}
|
||||
''
|
||||
else
|
||||
''
|
||||
pop3_uidl_format = %08Xv%08Xu
|
||||
''
|
||||
}
|
||||
|
||||
auth_mechanisms = plain login
|
||||
|
||||
service auth {
|
||||
user = root
|
||||
}
|
||||
''
|
||||
|
||||
(optionalString cfg.enablePAM ''
|
||||
userdb passwd {
|
||||
}
|
||||
|
||||
passdb pam {
|
||||
session = yes
|
||||
service_name = dovecot
|
||||
${optionalString cfg.showPAMFailure "failure_show_msg=yes"}
|
||||
}
|
||||
'')
|
||||
|
||||
(optionalString (cfg.mailboxes != { }) ''
|
||||
namespace inbox {
|
||||
inbox=yes
|
||||
${concatStringsSep "\n" (map mailboxConfig (attrValues cfg.mailboxes))}
|
||||
}
|
||||
'')
|
||||
|
||||
(optionalString cfg.enableQuota ''
|
||||
service quota-status {
|
||||
executable = ${dovecotPkg}/libexec/dovecot/quota-status -p postfix
|
||||
inet_listener quota {
|
||||
port ${cfg.quotaPort}
|
||||
}
|
||||
client_limit = 1
|
||||
}
|
||||
|
||||
quota "User quota" {
|
||||
driver = count
|
||||
|
||||
storage = {
|
||||
size = ${cfg.quotaGlobalPerUser}
|
||||
grace = 10M
|
||||
}
|
||||
|
||||
status {
|
||||
success = DUNNO
|
||||
nouser = DUNNO
|
||||
overquota = "552 5.2.2 Mailbox is full"
|
||||
}
|
||||
}
|
||||
'')
|
||||
|
||||
# General plugin settings:
|
||||
# - sieve is mostly generated here, refer to `pluginSettings` to follow
|
||||
# the control flow.
|
||||
''
|
||||
${concatStringsSep "\n" (
|
||||
mapAttrsToList (
|
||||
key: value:
|
||||
if (key == "sieve_extensions") then
|
||||
''
|
||||
${key} {
|
||||
${value}
|
||||
}
|
||||
''
|
||||
else
|
||||
"${key} = ${value}"
|
||||
) cfg.pluginSettings
|
||||
)}
|
||||
''
|
||||
|
||||
(optionalString cfg.enableHealthCheck (
|
||||
let
|
||||
healthCheckWrapper = pkgs.writeShellScript "health-check-wrapper.sh" ''
|
||||
export PATH="${pkgs.coreutils}/bin:${pkgs.gnused}/bin:$PATH"
|
||||
${pkgs.dovecot}/libexec/dovecot/health-check.sh
|
||||
'';
|
||||
in
|
||||
''
|
||||
service health-check {
|
||||
executable = script -p ${healthCheckWrapper}
|
||||
inet_listener health-check {
|
||||
port = ${toString cfg.healthCheckPort}
|
||||
}
|
||||
}
|
||||
''
|
||||
))
|
||||
|
||||
cfg.extraConfig
|
||||
];
|
||||
|
||||
mailboxConfig =
|
||||
mailbox:
|
||||
''
|
||||
mailbox "${mailbox.name}" {
|
||||
auto = ${toString mailbox.auto}
|
||||
''
|
||||
+ optionalString (mailbox.autoexpunge != null) ''
|
||||
autoexpunge = ${mailbox.autoexpunge}
|
||||
''
|
||||
+ optionalString (mailbox.specialUse != null) ''
|
||||
special_use = \${toString mailbox.specialUse}
|
||||
''
|
||||
+ "}";
|
||||
|
||||
mailboxes =
|
||||
{ name, ... }:
|
||||
{
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.strMatching ''[^"]+'';
|
||||
example = "Spam";
|
||||
default = name;
|
||||
readOnly = true;
|
||||
description = "The name of the mailbox.";
|
||||
};
|
||||
auto = mkOption {
|
||||
type = types.enum [
|
||||
"no"
|
||||
"create"
|
||||
"subscribe"
|
||||
];
|
||||
default = "no";
|
||||
example = "subscribe";
|
||||
description = "Whether to automatically create or create and subscribe to the mailbox or not.";
|
||||
};
|
||||
specialUse = mkOption {
|
||||
type = types.nullOr (
|
||||
types.enum [
|
||||
"All"
|
||||
"Archive"
|
||||
"Drafts"
|
||||
"Flagged"
|
||||
"Junk"
|
||||
"Sent"
|
||||
"Trash"
|
||||
]
|
||||
);
|
||||
default = null;
|
||||
example = "Junk";
|
||||
description = "Null if no special use flag is set. Other than that every use flag mentioned in the RFC is valid.";
|
||||
};
|
||||
autoexpunge = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "60d";
|
||||
description = ''
|
||||
To automatically remove all email from the mailbox which is older than the
|
||||
specified time.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
options.services.dovecot = {
|
||||
enable = mkEnableOption "the dovecot POP3/IMAP server";
|
||||
|
||||
enablePop3 = mkEnableOption "starting the POP3 listener (when Dovecot is enabled)";
|
||||
|
||||
enableImap = mkEnableOption "starting the IMAP listener (when Dovecot is enabled)" // {
|
||||
default = true;
|
||||
};
|
||||
|
||||
enableLmtp = mkEnableOption "starting the LMTP listener (when Dovecot is enabled)";
|
||||
|
||||
enableHealthCheck = mkEnableOption "starting the HealthCheck listener (when Dovecot is enabled)";
|
||||
|
||||
healthCheckPort = mkOption {
|
||||
type = types.int;
|
||||
default = 5001;
|
||||
description = "Listen port for health check service";
|
||||
};
|
||||
|
||||
protocols = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
description = "Additional listeners to start when Dovecot is enabled.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "dovecot";
|
||||
description = "Dovecot user name.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "dovecot";
|
||||
description = "Dovecot group name.";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = "mail_debug = yes";
|
||||
description = "Additional entries to put verbatim into Dovecot's config file.";
|
||||
};
|
||||
|
||||
mailPlugins =
|
||||
let
|
||||
plugins =
|
||||
hint:
|
||||
types.submodule {
|
||||
options = {
|
||||
enable = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
description = "mail plugins to enable as a list of strings to append to the ${hint} `$mail_plugins` configuration variable";
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
mkOption {
|
||||
type =
|
||||
with types;
|
||||
submodule {
|
||||
options = {
|
||||
globally = mkOption {
|
||||
description = "Additional entries to add to the mail_plugins variable for all protocols";
|
||||
type = plugins "top-level";
|
||||
example = {
|
||||
enable = [ "virtual" ];
|
||||
};
|
||||
default = {
|
||||
enable = [ ];
|
||||
};
|
||||
};
|
||||
perProtocol = mkOption {
|
||||
description = "Additional entries to add to the mail_plugins variable, per protocol";
|
||||
type = attrsOf (plugins "corresponding per-protocol");
|
||||
default = { };
|
||||
example = {
|
||||
imap = [ "imap_acl" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
description = "Additional entries to add to the mail_plugins variable, globally and per protocol";
|
||||
example = {
|
||||
globally.enable = [ "acl" ];
|
||||
perProtocol.imap.enable = [ "imap_acl" ];
|
||||
};
|
||||
default = {
|
||||
globally.enable = [ ];
|
||||
perProtocol = { };
|
||||
};
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = "Config file used for the whole dovecot configuration.";
|
||||
apply = v: if v != null then v else pkgs.writeText "dovecot.conf" dovecotConf;
|
||||
};
|
||||
|
||||
mailLocation = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/spool/mail/%{user}"; # Same as inbox, as postfix
|
||||
example = "~/mail";
|
||||
description = ''
|
||||
Location that dovecot will use for mail folders. Dovecot mail_location option.
|
||||
'';
|
||||
};
|
||||
|
||||
mailUser = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Default user to store mail for virtual users.";
|
||||
};
|
||||
|
||||
mailGroup = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Default group to store mail for virtual users.";
|
||||
};
|
||||
|
||||
createMailUser =
|
||||
mkEnableOption ''
|
||||
automatically creating the user
|
||||
given in {option}`services.dovecot.user` and the group
|
||||
given in {option}`services.dovecot.group`''
|
||||
// {
|
||||
default = true;
|
||||
};
|
||||
|
||||
sslCACert = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Path to the server's CA certificate key.";
|
||||
};
|
||||
|
||||
sslServerCert = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Path to the server's public key.";
|
||||
};
|
||||
|
||||
sslServerKey = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Path to the server's private key.";
|
||||
};
|
||||
|
||||
enablePAM = mkEnableOption "creating a own Dovecot PAM service and configure PAM user logins" // {
|
||||
default = true;
|
||||
};
|
||||
|
||||
enableDHE = mkEnableOption "ssl_dh and generation of primes for the key exchange" // {
|
||||
default = true;
|
||||
};
|
||||
|
||||
showPAMFailure = mkEnableOption "showing the PAM failure message on authentication error (useful for OTPW)";
|
||||
|
||||
mailboxes = mkOption {
|
||||
type =
|
||||
with types;
|
||||
coercedTo (listOf unspecified) (
|
||||
list:
|
||||
listToAttrs (
|
||||
map (entry: {
|
||||
name = entry.name;
|
||||
value = removeAttrs entry [ "name" ];
|
||||
}) list
|
||||
)
|
||||
) (attrsOf (submodule mailboxes));
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
Spam = { specialUse = "Junk"; auto = "create"; };
|
||||
}
|
||||
'';
|
||||
description = "Configure mailboxes and auto create or subscribe them.";
|
||||
};
|
||||
|
||||
enableQuota = mkEnableOption "the dovecot quota service";
|
||||
|
||||
quotaPort = mkOption {
|
||||
type = types.str;
|
||||
default = "12340";
|
||||
description = ''
|
||||
The Port the dovecot quota service binds to.
|
||||
If using postfix, add check_policy_service inet:localhost:12340 to your smtpd_recipient_restrictions in your postfix config.
|
||||
'';
|
||||
};
|
||||
quotaGlobalPerUser = mkOption {
|
||||
type = types.str;
|
||||
default = "100G";
|
||||
example = "10G";
|
||||
description = "Quota limit for the user in bytes. Supports suffixes b, k, M, G, T and %.";
|
||||
};
|
||||
|
||||
pluginSettings = mkOption {
|
||||
# types.str does not coerce from packages, like `sievePipeBinScriptDirectory`.
|
||||
type = types.attrsOf (
|
||||
types.oneOf [
|
||||
types.str
|
||||
types.package
|
||||
]
|
||||
);
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
sieve = "file:~/sieve;active=~/.dovecot.sieve";
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Plugin settings for dovecot in general, e.g. `sieve`, `sieve_default`, etc.
|
||||
|
||||
Some of the other knobs of this module will influence by default the plugin settings, but you
|
||||
can still override any plugin settings.
|
||||
|
||||
If you override a plugin setting, its value is cleared and you have to copy over the defaults.
|
||||
'';
|
||||
};
|
||||
|
||||
imapsieve.mailbox = mkOption {
|
||||
default = [ ];
|
||||
description = "Configure Sieve filtering rules on IMAP actions";
|
||||
type = types.listOf (
|
||||
types.submodule (
|
||||
{ config, ... }:
|
||||
{
|
||||
options = {
|
||||
name = mkOption {
|
||||
description = ''
|
||||
This setting configures the name of a mailbox for which administrator scripts are configured.
|
||||
|
||||
The settings defined hereafter with matching sequence numbers apply to the mailbox named by this setting.
|
||||
|
||||
This setting supports wildcards with a syntax compatible with the IMAP LIST command, meaning that this setting can apply to multiple or even all ("*") mailboxes.
|
||||
'';
|
||||
example = "Junk";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
from = mkOption {
|
||||
default = null;
|
||||
description = ''
|
||||
Only execute the administrator Sieve scripts for the mailbox configured with services.dovecot.imapsieve.mailbox.<name>.name when the message originates from the indicated mailbox.
|
||||
|
||||
This setting supports wildcards with a syntax compatible with the IMAP LIST command, meaning that this setting can apply to multiple or even all ("*") mailboxes.
|
||||
'';
|
||||
example = "*";
|
||||
type = types.nullOr types.str;
|
||||
};
|
||||
|
||||
causes = mkOption {
|
||||
default = [ ];
|
||||
description = ''
|
||||
Only execute the administrator Sieve scripts for the mailbox configured with services.dovecot.imapsieve.mailbox.<name>.name when one of the listed IMAPSIEVE causes apply.
|
||||
|
||||
This has no effect on the user script, which is always executed no matter the cause.
|
||||
'';
|
||||
example = [
|
||||
"COPY"
|
||||
"APPEND"
|
||||
];
|
||||
type = types.listOf (
|
||||
types.enum [
|
||||
"APPEND"
|
||||
"COPY"
|
||||
"FLAG"
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
before = mkOption {
|
||||
default = null;
|
||||
description = ''
|
||||
When an IMAP event of interest occurs, this sieve script is executed before any user script respectively.
|
||||
|
||||
This setting each specify the location of a single sieve script. The semantics of this setting is similar to sieve_before: the specified scripts form a sequence together with the user script in which the next script is only executed when an (implicit) keep action is executed.
|
||||
'';
|
||||
example = literalExpression "./report-spam.sieve";
|
||||
type = types.nullOr types.path;
|
||||
};
|
||||
|
||||
after = mkOption {
|
||||
default = null;
|
||||
description = ''
|
||||
When an IMAP event of interest occurs, this sieve script is executed after any user script respectively.
|
||||
|
||||
This setting each specify the location of a single sieve script. The semantics of this setting is similar to sieve_after: the specified scripts form a sequence together with the user script in which the next script is only executed when an (implicit) keep action is executed.
|
||||
'';
|
||||
example = literalExpression "./report-spam.sieve";
|
||||
type = types.nullOr types.path;
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
sieve = {
|
||||
plugins = mkOption {
|
||||
default = [ ];
|
||||
example = [ "sieve_extprograms" ];
|
||||
description = "Sieve plugins to load";
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
|
||||
extensions = mkOption {
|
||||
default = [ ];
|
||||
description = "Sieve extensions for use in user scripts";
|
||||
example = [
|
||||
"notify"
|
||||
"imapflags"
|
||||
"vnd.dovecot.filter"
|
||||
];
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
|
||||
globalExtensions = mkOption {
|
||||
default = [ ];
|
||||
example = [ "vnd.dovecot.environment" ];
|
||||
description = "Sieve extensions for use in global scripts";
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
|
||||
scripts = mkOption {
|
||||
type = types.attrsOf types.path;
|
||||
default = { };
|
||||
description = "Sieve scripts to be executed. Key is a sequence, e.g. 'before2', 'after' etc.";
|
||||
};
|
||||
|
||||
pipeBins = mkOption {
|
||||
default = [ ];
|
||||
example = literalExpression ''
|
||||
map lib.getExe [
|
||||
(pkgs.writeShellScriptBin "learn-ham.sh" "exec ''${pkgs.rspamd}/bin/rspamc learn_ham")
|
||||
(pkgs.writeShellScriptBin "learn-spam.sh" "exec ''${pkgs.rspamd}/bin/rspamc learn_spam")
|
||||
]
|
||||
'';
|
||||
description = "Programs available for use by the vnd.dovecot.pipe extension";
|
||||
type = types.listOf types.path;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
security.pam.services.dovecot = mkIf cfg.enablePAM { };
|
||||
|
||||
security.dhparams = mkIf (cfg.sslServerCert != null && cfg.enableDHE) {
|
||||
enable = true;
|
||||
params.dovecot = { };
|
||||
};
|
||||
|
||||
services.dovecot = {
|
||||
protocols =
|
||||
optional cfg.enableImap "imap" ++ optional cfg.enablePop3 "pop3" ++ optional cfg.enableLmtp "lmtp";
|
||||
|
||||
mailPlugins = mkIf cfg.enableQuota {
|
||||
globally.enable = [ "quota" ];
|
||||
perProtocol.imap.enable = [ "imap_quota" ];
|
||||
};
|
||||
|
||||
sieve.plugins =
|
||||
optional (cfg.imapsieve.mailbox != [ ]) "sieve_imapsieve"
|
||||
++ optional (cfg.sieve.pipeBins != [ ]) "sieve_extprograms";
|
||||
|
||||
sieve.globalExtensions = optional (cfg.sieve.pipeBins != [ ]) "vnd.dovecot.pipe";
|
||||
|
||||
pluginSettings = lib.mapAttrs (n: lib.mkDefault) (
|
||||
{
|
||||
# sieve_plugins = concatStringsSep " " cfg.sieve.plugins;
|
||||
# sieve_extensions = concatMapStrings (p: p + " = yes\n") cfg.sieve.extensions;
|
||||
# sieve_global_extensions = concatStringsSep " " (map (el: "+${el}") cfg.sieve.globalExtensions);
|
||||
# sieve_pipe_bin_dir = sievePipeBinScriptDirectory;
|
||||
}
|
||||
// sieveScriptSettings
|
||||
// imapSieveMailboxSettings
|
||||
);
|
||||
};
|
||||
|
||||
users.users =
|
||||
{
|
||||
dovenull = {
|
||||
uid = config.ids.uids.dovenull2;
|
||||
description = "Dovecot user for untrusted logins";
|
||||
group = "dovenull";
|
||||
};
|
||||
}
|
||||
// optionalAttrs (cfg.user == "dovecot") {
|
||||
dovecot = {
|
||||
uid = config.ids.uids.dovecot;
|
||||
description = "Dovecot user";
|
||||
group = cfg.group;
|
||||
};
|
||||
}
|
||||
// optionalAttrs (cfg.createMailUser && cfg.mailUser != null) {
|
||||
${cfg.mailUser} = {
|
||||
description = "Virtual Mail User";
|
||||
isSystemUser = true;
|
||||
} // optionalAttrs (cfg.mailGroup != null) { group = cfg.mailGroup; };
|
||||
};
|
||||
|
||||
users.groups =
|
||||
{
|
||||
dovenull.gid = config.ids.gids.dovenull2;
|
||||
}
|
||||
// optionalAttrs (cfg.group == "dovecot") {
|
||||
dovecot.gid = config.ids.gids.dovecot;
|
||||
}
|
||||
// optionalAttrs (cfg.createMailUser && cfg.mailGroup != null) {
|
||||
${cfg.mailGroup} = { };
|
||||
};
|
||||
|
||||
environment.etc."dovecot/dovecot.conf".source = cfg.configFile;
|
||||
|
||||
systemd.services.dovecot = {
|
||||
description = "Dovecot IMAP/POP3 server";
|
||||
documentation = [
|
||||
"man:dovecot(1)"
|
||||
"https://doc.dovecot.org"
|
||||
];
|
||||
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
restartTriggers = [ cfg.configFile ];
|
||||
|
||||
startLimitIntervalSec = 60; # 1 min
|
||||
serviceConfig = {
|
||||
Type = "notify";
|
||||
ExecStart = "${dovecotPkg}/sbin/dovecot -F";
|
||||
ExecReload = "${dovecotPkg}/sbin/doveadm reload";
|
||||
|
||||
CapabilityBoundingSet = [
|
||||
"CAP_CHOWN"
|
||||
"CAP_DAC_OVERRIDE"
|
||||
"CAP_FOWNER"
|
||||
"CAP_KILL" # Required for child process management
|
||||
"CAP_NET_BIND_SERVICE"
|
||||
"CAP_SETGID"
|
||||
"CAP_SETUID"
|
||||
"CAP_SYS_CHROOT"
|
||||
"CAP_SYS_RESOURCE"
|
||||
];
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
NoNewPrivileges = false; # e.g for sendmail
|
||||
OOMPolicy = "continue";
|
||||
PrivateTmp = true;
|
||||
ProcSubset = "pid";
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = lib.mkDefault false;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
ProtectSystem = "full";
|
||||
PrivateDevices = true;
|
||||
Restart = "on-failure";
|
||||
RestartSec = "1s";
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
"AF_NETLINK" # e.g. getifaddrs in sieve handling
|
||||
"AF_UNIX"
|
||||
];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = false; # sets sgid on maildirs
|
||||
RuntimeDirectory = [ "dovecot" ];
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [
|
||||
"@system-service @resources"
|
||||
"~@privileged"
|
||||
"@chown @setuid capset chroot"
|
||||
];
|
||||
};
|
||||
|
||||
# When copying sieve scripts preserve the original time stamp
|
||||
# (should be 0) so that the compiled sieve script is newer than
|
||||
# the source file and Dovecot won't try to compile it.
|
||||
preStart =
|
||||
''
|
||||
rm -rf ${stateDir}/sieve ${stateDir}/imapsieve
|
||||
''
|
||||
+ optionalString (cfg.sieve.scripts != { }) ''
|
||||
mkdir -p ${stateDir}/sieve
|
||||
${concatStringsSep "\n" (
|
||||
mapAttrsToList (to: from: ''
|
||||
if [ -d '${from}' ]; then
|
||||
mkdir '${stateDir}/sieve/${to}'
|
||||
cp -p "${from}/"*.sieve '${stateDir}/sieve/${to}'
|
||||
else
|
||||
cp -p '${from}' '${stateDir}/sieve/${to}'
|
||||
fi
|
||||
${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/sieve/${to}'
|
||||
'') cfg.sieve.scripts
|
||||
)}
|
||||
chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/sieve'
|
||||
''
|
||||
+ optionalString (cfg.imapsieve.mailbox != [ ]) ''
|
||||
mkdir -p ${stateDir}/imapsieve/{before,after}
|
||||
|
||||
${concatMapStringsSep "\n" (
|
||||
el:
|
||||
optionalString (el.before != null) ''
|
||||
cp -p ${el.before} ${stateDir}/imapsieve/before/${baseNameOf el.before}
|
||||
${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/imapsieve/before/${baseNameOf el.before}'
|
||||
''
|
||||
+ optionalString (el.after != null) ''
|
||||
cp -p ${el.after} ${stateDir}/imapsieve/after/${baseNameOf el.after}
|
||||
${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/imapsieve/after/${baseNameOf el.after}'
|
||||
''
|
||||
) cfg.imapsieve.mailbox}
|
||||
|
||||
${optionalString (
|
||||
cfg.mailUser != null && cfg.mailGroup != null
|
||||
) "chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/imapsieve'"}
|
||||
'';
|
||||
};
|
||||
|
||||
environment.systemPackages = [ dovecotPkg ];
|
||||
|
||||
warnings = warnAboutExtraConfigCollisions;
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion =
|
||||
(cfg.sslServerCert == null) == (cfg.sslServerKey == null)
|
||||
&& (cfg.sslCACert != null -> !(cfg.sslServerCert == null || cfg.sslServerKey == null));
|
||||
message = "dovecot needs both sslServerCert and sslServerKey defined for working crypto";
|
||||
}
|
||||
{
|
||||
assertion = cfg.showPAMFailure -> cfg.enablePAM;
|
||||
message = "dovecot is configured with showPAMFailure while enablePAM is disabled";
|
||||
}
|
||||
{
|
||||
assertion = cfg.sieve.scripts != { } -> (cfg.mailUser != null && cfg.mailGroup != null);
|
||||
message = "dovecot requires mailUser and mailGroup to be set when `sieve.scripts` is set";
|
||||
}
|
||||
];
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = [ lib.maintainers.dblsaiko ];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue