2e751c0772
the conversion procedure is simple: - find all things that look like options, ie calls to either `mkOption` or `lib.mkOption` that take an attrset. remember the attrset as the option - for all options, find a `description` attribute who's value is not a call to `mdDoc` or `lib.mdDoc` - textually convert the entire value of the attribute to MD with a few simple regexes (the set from mdize-module.sh) - if the change produced a change in the manual output, discard - if the change kept the manual unchanged, add some text to the description to make sure we've actually found an option. if the manual changes this time, keep the converted description this procedure converts 80% of nixos options to markdown. around 2000 options remain to be inspected, but most of those fail the "does not change the manual output check": currently the MD conversion process does not faithfully convert docbook tags like <code> and <package>, so any option using such tags will not be converted at all.
462 lines
15 KiB
Nix
462 lines
15 KiB
Nix
{ options, config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.services.dovecot2;
|
|
dovecotPkg = pkgs.dovecot;
|
|
|
|
baseDir = "/run/dovecot2";
|
|
stateDir = "/var/lib/dovecot";
|
|
|
|
dovecotConf = concatStrings [
|
|
''
|
|
base_dir = ${baseDir}
|
|
protocols = ${concatStringsSep " " cfg.protocols}
|
|
sendmail_path = /run/wrappers/bin/sendmail
|
|
# 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 = $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
|
|
disable_plaintext_auth = no
|
|
'' else ''
|
|
ssl_cert = <${cfg.sslServerCert}
|
|
ssl_key = <${cfg.sslServerKey}
|
|
${optionalString (cfg.sslCACert != null) ("ssl_ca = <" + cfg.sslCACert)}
|
|
${optionalString cfg.enableDHE ''ssl_dh = <${config.security.dhparams.params.dovecot2.path}''}
|
|
disable_plaintext_auth = yes
|
|
''
|
|
)
|
|
|
|
''
|
|
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_location = ${cfg.mailLocation}
|
|
|
|
maildir_copy_with_hardlinks = yes
|
|
pop3_uidl_format = %08Xv%08Xu
|
|
|
|
auth_mechanisms = plain login
|
|
|
|
service auth {
|
|
user = root
|
|
}
|
|
''
|
|
|
|
(
|
|
optionalString cfg.enablePAM ''
|
|
userdb {
|
|
driver = passwd
|
|
}
|
|
|
|
passdb {
|
|
driver = pam
|
|
args = ${optionalString cfg.showPAMFailure "failure_show_msg=yes"} dovecot2
|
|
}
|
|
''
|
|
)
|
|
|
|
(
|
|
optionalString (cfg.sieveScripts != {}) ''
|
|
plugin {
|
|
${concatStringsSep "\n" (mapAttrsToList (to: from: "sieve_${to} = ${stateDir}/sieve/${to}") cfg.sieveScripts)}
|
|
}
|
|
''
|
|
)
|
|
|
|
(
|
|
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 {
|
|
port = ${cfg.quotaPort}
|
|
}
|
|
client_limit = 1
|
|
}
|
|
|
|
plugin {
|
|
quota_rule = *:storage=${cfg.quotaGlobalPerUser}
|
|
quota = count:User quota # per virtual mail user quota
|
|
quota_status_success = DUNNO
|
|
quota_status_nouser = DUNNO
|
|
quota_status_overquota = "552 5.2.2 Mailbox is full"
|
|
quota_grace = 10%%
|
|
quota_vsizes = yes
|
|
}
|
|
''
|
|
)
|
|
|
|
cfg.extraConfig
|
|
];
|
|
|
|
modulesDir = pkgs.symlinkJoin {
|
|
name = "dovecot-modules";
|
|
paths = map (pkg: "${pkg}/lib/dovecot") ([ dovecotPkg ] ++ map (module: module.override { dovecot = dovecotPkg; }) cfg.modules);
|
|
};
|
|
|
|
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 = lib.mdDoc "The name of the mailbox.";
|
|
};
|
|
auto = mkOption {
|
|
type = types.enum [ "no" "create" "subscribe" ];
|
|
default = "no";
|
|
example = "subscribe";
|
|
description = lib.mdDoc "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 = lib.mdDoc "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 = lib.mdDoc ''
|
|
To automatically remove all email from the mailbox which is older than the
|
|
specified time.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
in
|
|
{
|
|
imports = [
|
|
(mkRemovedOptionModule [ "services" "dovecot2" "package" ] "")
|
|
];
|
|
|
|
options.services.dovecot2 = {
|
|
enable = mkEnableOption "the dovecot 2.x 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).";
|
|
|
|
protocols = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [];
|
|
description = lib.mdDoc "Additional listeners to start when Dovecot is enabled.";
|
|
};
|
|
|
|
user = mkOption {
|
|
type = types.str;
|
|
default = "dovecot2";
|
|
description = lib.mdDoc "Dovecot user name.";
|
|
};
|
|
|
|
group = mkOption {
|
|
type = types.str;
|
|
default = "dovecot2";
|
|
description = lib.mdDoc "Dovecot group name.";
|
|
};
|
|
|
|
extraConfig = mkOption {
|
|
type = types.lines;
|
|
default = "";
|
|
example = "mail_debug = yes";
|
|
description = lib.mdDoc "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 = lib.mdDoc "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 = lib.mdDoc "Additional entries to add to the mail_plugins variable for all protocols";
|
|
type = plugins "top-level";
|
|
example = { enable = [ "virtual" ]; };
|
|
default = { enable = []; };
|
|
};
|
|
perProtocol = mkOption {
|
|
description = lib.mdDoc "Additional entries to add to the mail_plugins variable, per protocol";
|
|
type = attrsOf (plugins "corresponding per-protocol");
|
|
default = {};
|
|
example = { imap = [ "imap_acl" ]; };
|
|
};
|
|
};
|
|
};
|
|
description = lib.mdDoc "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 = lib.mdDoc "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 = "maildir:/var/spool/mail/%u"; /* Same as inbox, as postfix */
|
|
example = "maildir:~/mail:INBOX=/var/spool/mail/%u";
|
|
description = lib.mdDoc ''
|
|
Location that dovecot will use for mail folders. Dovecot mail_location option.
|
|
'';
|
|
};
|
|
|
|
mailUser = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = lib.mdDoc "Default user to store mail for virtual users.";
|
|
};
|
|
|
|
mailGroup = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = lib.mdDoc "Default group to store mail for virtual users.";
|
|
};
|
|
|
|
createMailUser = mkEnableOption ''automatically creating the user
|
|
given in <option>services.dovecot.user</option> and the group
|
|
given in <option>services.dovecot.group</option>.'' // { default = true; };
|
|
|
|
modules = mkOption {
|
|
type = types.listOf types.package;
|
|
default = [];
|
|
example = literalExpression "[ pkgs.dovecot_pigeonhole ]";
|
|
description = lib.mdDoc ''
|
|
Symlinks the contents of lib/dovecot of every given package into
|
|
/etc/dovecot/modules. This will make the given modules available
|
|
if a dovecot package with the module_dir patch applied is being used.
|
|
'';
|
|
};
|
|
|
|
sslCACert = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = lib.mdDoc "Path to the server's CA certificate key.";
|
|
};
|
|
|
|
sslServerCert = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = lib.mdDoc "Path to the server's public key.";
|
|
};
|
|
|
|
sslServerKey = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = lib.mdDoc "Path to the server's private key.";
|
|
};
|
|
|
|
enablePAM = mkEnableOption "creating a own Dovecot PAM service and configure PAM user logins." // { default = true; };
|
|
|
|
enableDHE = mkEnableOption "enable ssl_dh and generation of primes for the key exchange." // { default = true; };
|
|
|
|
sieveScripts = mkOption {
|
|
type = types.attrsOf types.path;
|
|
default = {};
|
|
description = lib.mdDoc "Sieve scripts to be executed. Key is a sequence, e.g. 'before2', 'after' etc.";
|
|
};
|
|
|
|
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 = lib.mdDoc "Configure mailboxes and auto create or subscribe them.";
|
|
};
|
|
|
|
enableQuota = mkEnableOption "the dovecot quota service.";
|
|
|
|
quotaPort = mkOption {
|
|
type = types.str;
|
|
default = "12340";
|
|
description = lib.mdDoc ''
|
|
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 = lib.mdDoc "Quota limit for the user in bytes. Supports suffixes b, k, M, G, T and %.";
|
|
};
|
|
|
|
};
|
|
|
|
|
|
config = mkIf cfg.enable {
|
|
security.pam.services.dovecot2 = mkIf cfg.enablePAM {};
|
|
|
|
security.dhparams = mkIf (cfg.sslServerCert != null && cfg.enableDHE) {
|
|
enable = true;
|
|
params.dovecot2 = {};
|
|
};
|
|
services.dovecot2.protocols =
|
|
optional cfg.enableImap "imap"
|
|
++ optional cfg.enablePop3 "pop3"
|
|
++ optional cfg.enableLmtp "lmtp";
|
|
|
|
services.dovecot2.mailPlugins = mkIf cfg.enableQuota {
|
|
globally.enable = [ "quota" ];
|
|
perProtocol.imap.enable = [ "imap_quota" ];
|
|
};
|
|
|
|
users.users = {
|
|
dovenull =
|
|
{
|
|
uid = config.ids.uids.dovenull2;
|
|
description = "Dovecot user for untrusted logins";
|
|
group = "dovenull";
|
|
};
|
|
} // optionalAttrs (cfg.user == "dovecot2") {
|
|
dovecot2 =
|
|
{
|
|
uid = config.ids.uids.dovecot2;
|
|
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 == "dovecot2") {
|
|
dovecot2.gid = config.ids.gids.dovecot2;
|
|
} // optionalAttrs (cfg.createMailUser && cfg.mailGroup != null) {
|
|
${cfg.mailGroup} = {};
|
|
};
|
|
|
|
environment.etc."dovecot/modules".source = modulesDir;
|
|
environment.etc."dovecot/dovecot.conf".source = cfg.configFile;
|
|
|
|
systemd.services.dovecot2 = {
|
|
description = "Dovecot IMAP/POP3 server";
|
|
|
|
after = [ "network.target" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
restartTriggers = [ cfg.configFile modulesDir ];
|
|
|
|
startLimitIntervalSec = 60; # 1 min
|
|
serviceConfig = {
|
|
Type = "notify";
|
|
ExecStart = "${dovecotPkg}/sbin/dovecot -F";
|
|
ExecReload = "${dovecotPkg}/sbin/doveadm reload";
|
|
Restart = "on-failure";
|
|
RestartSec = "1s";
|
|
RuntimeDirectory = [ "dovecot2" ];
|
|
};
|
|
|
|
# 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
|
|
'' + optionalString (cfg.sieveScripts != {}) ''
|
|
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.sieveScripts
|
|
)}
|
|
chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/sieve'
|
|
'';
|
|
};
|
|
|
|
environment.systemPackages = [ dovecotPkg ];
|
|
|
|
warnings = mkIf (any isList options.services.dovecot2.mailboxes.definitions) [
|
|
"Declaring `services.dovecot2.mailboxes' as a list is deprecated and will break eval in 21.05! See the release notes for more info for migration."
|
|
];
|
|
|
|
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.sieveScripts != {} -> (cfg.mailUser != null && cfg.mailGroup != null);
|
|
message = "dovecot requires mailUser and mailGroup to be set when sieveScripts is set";
|
|
}
|
|
];
|
|
|
|
};
|
|
|
|
}
|