cyrus-imapd: init at 3.8.2 (#305538)

This commit is contained in:
Pol Dellaiera 2024-10-20 07:51:45 +02:00 committed by GitHub
commit 88d3f02d30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 707 additions and 0 deletions

View File

@ -73,6 +73,8 @@
## New Modules {#sec-release-24.11-new-modules}
- [Cyrus IMAP](https://github.com/cyrusimap/cyrus-imapd), an email, contacts and calendar server. Available as [services.cyrus-imap](#opt-services.cyrus-imap.enable) service.
- [TaskChampion Sync-Server](https://github.com/GothenburgBitFactory/taskchampion-sync-server), a [Taskwarrior 3](https://taskwarrior.org/docs/upgrade-3/) sync server, replacing Taskwarrior 2's sync server named [`taskserver`](https://github.com/GothenburgBitFactory/taskserver).
- [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr), proxy server to bypass Cloudflare protection. Available as [services.flaresolverr](#opt-services.flaresolverr.enable) service.

View File

@ -661,6 +661,7 @@
./services/logging/ulogd.nix
./services/mail/automx2.nix
./services/mail/clamsmtp.nix
./services/mail/cyrus-imap.nix
./services/mail/davmail.nix
./services/mail/dkimproxy-out.nix
./services/mail/dovecot.nix

View File

@ -0,0 +1,379 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.services.cyrus-imap;
cyrus-imapdPkg = pkgs.cyrus-imapd;
inherit (lib)
mkEnableOption
mkIf
mkOption
optionalAttrs
optionalString
generators
mapAttrsToList
;
inherit (lib.strings) concatStringsSep;
inherit (lib.types)
attrsOf
submodule
listOf
oneOf
str
int
bool
nullOr
path
;
mkCyrusConfig =
settings:
let
mkCyrusOptionsList =
v:
mapAttrsToList (
p: q:
if (q != null) then
if builtins.isInt q then
"${p}=${builtins.toString q}"
else
"${p}=\"${if builtins.isList q then (concatStringsSep " " q) else q}\""
else
""
) v;
mkCyrusOptionsString = v: concatStringsSep " " (mkCyrusOptionsList v);
in
concatStringsSep "\n " (mapAttrsToList (n: v: n + " " + (mkCyrusOptionsString v)) settings);
cyrusConfig = lib.concatStringsSep "\n" (
lib.mapAttrsToList (n: v: ''
${n} {
${mkCyrusConfig v}
}
'') cfg.cyrusSettings
);
imapdConfig =
with generators;
toKeyValue {
mkKeyValue = mkKeyValueDefault {
mkValueString =
v:
if builtins.isBool v then
if v then "yes" else "no"
else if builtins.isList v then
concatStringsSep " " v
else
mkValueStringDefault { } v;
} ": ";
} cfg.imapdSettings;
in
{
options.services.cyrus-imap = {
enable = mkEnableOption "Cyrus IMAP, an email, contacts and calendar server";
debug = mkEnableOption "debugging messages for the Cyrus master process";
listenQueue = mkOption {
type = int;
default = 32;
description = ''
Socket listen queue backlog size. See listen(2) for more information about a backlog.
Default is 32, which may be increased if you have a very high connection rate.
'';
};
tmpDBDir = mkOption {
type = path;
default = "/run/cyrus/db";
description = ''
Location where DB files are stored.
Databases in this directory are recreated upon startup, so ideally they should live in ephemeral storage for best performance.
'';
};
cyrusSettings = mkOption {
type = submodule {
freeformType = attrsOf (
attrsOf (oneOf [
bool
int
(listOf str)
])
);
options = {
START = mkOption {
default = {
recover = {
cmd = [
"ctl_cyrusdb"
"-r"
];
};
};
description = ''
This section lists the processes to run before any SERVICES are spawned.
This section is typically used to initialize databases.
Master itself will not startup until all tasks in START have completed, so put no blocking commands here.
'';
};
SERVICES = mkOption {
default = {
imap = {
cmd = [ "imapd" ];
listen = "imap";
prefork = 0;
};
pop3 = {
cmd = [ "pop3d" ];
listen = "pop3";
prefork = 0;
};
lmtpunix = {
cmd = [ "lmtpd" ];
listen = "/run/cyrus/lmtp";
prefork = 0;
};
notify = {
cmd = [ "notifyd" ];
listen = "/run/cyrus/notify";
proto = "udp";
prefork = 0;
};
};
description = ''
This section is the heart of the cyrus.conf file. It lists the processes that should be spawned to handle client connections made on certain Internet/UNIX sockets.
'';
};
EVENTS = mkOption {
default = {
tlsprune = {
cmd = [ "tls_prune" ];
at = 400;
};
delprune = {
cmd = [
"cyr_expire"
"-E"
"3"
];
at = 400;
};
deleteprune = {
cmd = [
"cyr_expire"
"-E"
"4"
"-D"
"28"
];
at = 430;
};
expungeprune = {
cmd = [
"cyr_expire"
"-E"
"4"
"-X"
"28"
];
at = 445;
};
checkpoint = {
cmd = [
"ctl_cyrusdb"
"-c"
];
period = 30;
};
};
description = ''
This section lists processes that should be run at specific intervals, similar to cron jobs. This section is typically used to perform scheduled cleanup/maintenance.
'';
};
DAEMON = mkOption {
default = { };
description = ''
This section lists long running daemons to start before any SERVICES are spawned. master(8) will ensure that these processes are running, restarting any process which dies or forks. All listed processes will be shutdown when master(8) is exiting.
'';
};
};
};
description = "Cyrus configuration settings. See [cyrus.conf(5)](https://www.cyrusimap.org/imap/reference/manpages/configs/cyrus.conf.html)";
};
imapdSettings = mkOption {
type = submodule {
freeformType = attrsOf (oneOf [
str
int
bool
(listOf str)
]);
options = {
configdirectory = mkOption {
type = path;
default = "/var/lib/cyrus";
description = ''
The pathname of the IMAP configuration directory.
'';
};
lmtpsocket = mkOption {
type = path;
default = "/run/cyrus/lmtp";
description = ''
Unix socket that lmtpd listens on, used by deliver(8). This should match the path specified in cyrus.conf(5).
'';
};
idlesocket = mkOption {
type = path;
default = "/run/cyrus/idle";
description = ''
Unix socket that idled listens on.
'';
};
notifysocket = mkOption {
type = path;
default = "/run/cyrus/notify";
description = ''
Unix domain socket that the mail notification daemon listens on.
'';
};
};
};
default = {
admins = [ "cyrus" ];
allowplaintext = true;
defaultdomain = "localhost";
defaultpartition = "default";
duplicate_db_path = "/run/cyrus/db/deliver.db";
hashimapspool = true;
httpmodules = [
"carddav"
"caldav"
];
mboxname_lockpath = "/run/cyrus/lock";
partition-default = "/var/lib/cyrus/storage";
popminpoll = 1;
proc_path = "/run/cyrus/proc";
ptscache_db_path = "/run/cyrus/db/ptscache.db";
sasl_auto_transition = true;
sasl_pwcheck_method = [ "saslauthd" ];
sievedir = "/var/lib/cyrus/sieve";
statuscache_db_path = "/run/cyrus/db/statuscache.db";
syslog_prefix = "cyrus";
tls_client_ca_dir = "/etc/ssl/certs";
tls_session_timeout = 1440;
tls_sessions_db_path = "/run/cyrus/db/tls_sessions.db";
virtdomains = "on";
};
description = "IMAP configuration settings. See [imapd.conf(5)](https://www.cyrusimap.org/imap/reference/manpages/configs/imapd.conf.html)";
};
user = mkOption {
type = nullOr str;
default = null;
description = "Cyrus IMAP user name. If this is not set, a user named `cyrus` will be created.";
};
group = mkOption {
type = nullOr str;
default = null;
description = "Cyrus IMAP group name. If this is not set, a group named `cyrus` will be created.";
};
imapdConfigFile = mkOption {
type = nullOr path;
default = null;
description = "Path to the configuration file used for cyrus-imap.";
apply = v: if v != null then v else pkgs.writeText "imapd.conf" imapdConfig;
};
cyrusConfigFile = mkOption {
type = nullOr path;
default = null;
description = "Path to the configuration file used for Cyrus.";
apply = v: if v != null then v else pkgs.writeText "cyrus.conf" cyrusConfig;
};
sslCACert = mkOption {
type = nullOr str;
default = null;
description = "File path which containing one or more CA certificates to use.";
};
sslServerCert = mkOption {
type = nullOr str;
default = null;
description = "File containing the global certificate used for all services (IMAP, POP3, LMTP, Sieve)";
};
sslServerKey = mkOption {
type = nullOr str;
default = null;
description = "File containing the private key belonging to the global server certificate.";
};
};
config = mkIf cfg.enable {
users.users.cyrus = optionalAttrs (cfg.user == null) {
description = "Cyrus IMAP user";
isSystemUser = true;
group = optionalString (cfg.group == null) "cyrus";
};
users.groups.cyrus = optionalAttrs (cfg.group == null) { };
environment.etc."imapd.conf".source = cfg.imapdConfigFile;
environment.etc."cyrus.conf".source = cfg.cyrusConfigFile;
systemd.services.cyrus-imap = {
description = "Cyrus IMAP server";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
restartTriggers = [
"/etc/imapd.conf"
"/etc/cyrus.conf"
];
startLimitIntervalSec = 60;
environment = {
CYRUS_VERBOSE = mkIf cfg.debug "1";
LISTENQUEUE = builtins.toString cfg.listenQueue;
};
serviceConfig = {
User = if (cfg.user == null) then "cyrus" else cfg.user;
Group = if (cfg.group == null) then "cyrus" else cfg.group;
Type = "simple";
ExecStart = "${cyrus-imapdPkg}/libexec/master -l $LISTENQUEUE -C /etc/imapd.conf -M /etc/cyrus.conf -p /run/cyrus/master.pid -D";
Restart = "on-failure";
RestartSec = "1s";
RuntimeDirectory = "cyrus";
StateDirectory = "cyrus";
# Hardening
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
PrivateTmp = true;
PrivateDevices = true;
ProtectSystem = "full";
CapabilityBoundingSet = [ "~CAP_NET_ADMIN CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_MODULE" ];
MemoryDenyWriteExecute = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_NETLINK"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
};
preStart = ''
mkdir -p '${cfg.imapdSettings.configdirectory}/socket' '${cfg.tmpDBDir}' /run/cyrus/proc /run/cyrus/lock
'';
};
environment.systemPackages = [ cyrus-imapdPkg ];
};
}

View File

@ -245,6 +245,7 @@ in {
curl-impersonate = handleTest ./curl-impersonate.nix {};
custom-ca = handleTest ./custom-ca.nix {};
croc = handleTest ./croc.nix {};
cyrus-imap = runTest ./cyrus-imap.nix;
darling = handleTest ./darling.nix {};
darling-dmg = runTest ./darling-dmg.nix;
dae = handleTest ./dae.nix {};

120
nixos/tests/cyrus-imap.nix Normal file
View File

@ -0,0 +1,120 @@
{ lib, pkgs, ... }:
{
name = "cyrus-imap";
meta = {
maintainers = with lib.maintainers; [ moraxyc ];
};
nodes.machine =
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
curl
sudo
];
services.saslauthd = {
enable = true;
config = ''
DESC="SASL Authentication Daemon"
NAME="saslauthd"
MECH_OPTIONS=""
THREADS=5
START=yes
OPTIONS="-c -m /run/saslauthd"
'';
};
services.cyrus-imap = {
enable = true;
cyrusSettings = {
START = {
recover = {
cmd = [
"ctl_cyrusdb"
"-r"
];
};
};
EVENTS = {
tlsprune = {
cmd = [ "tls_prune" ];
at = 400;
};
delprune = {
cmd = [
"cyr_expire"
"-E"
"3"
];
at = 400;
};
deleteprune = {
cmd = [
"cyr_expire"
"-E"
"4"
"-D"
"28"
];
at = 430;
};
expungeprune = {
cmd = [
"cyr_expire"
"-E"
"4"
"-X"
"28"
];
at = 445;
};
checkpoint = {
cmd = [
"ctl_cyrusdb"
"-c"
];
period = 30;
};
};
SERVICES = {
http = {
cmd = [ "httpd" ];
listen = "80";
prefork = 0;
};
imap = {
cmd = [ "imapd" ];
listen = "143";
prefork = 0;
};
lmtpunix = {
cmd = [ "lmtpd" ];
listen = "/run/cyrus/lmtp";
prefork = 0;
};
notify = {
cmd = [ "notifyd" ];
listen = "/run/cyrus/notify";
proto = "udp";
prefork = 0;
};
};
};
};
};
testScript = ''
machine.wait_for_unit("saslauthd.service")
machine.wait_for_unit("cyrus-imap.service")
machine.wait_for_open_port(80)
machine.wait_for_open_port(143)
machine.succeed("echo 'secret' | ${lib.getExe' pkgs.cyrus_sasl.bin "saslpasswd2"} -p -c cyrus")
machine.succeed("chown cyrus /etc/sasldb2")
machine.succeed("sudo -ucyrus curl --fail --max-time 10 imap://cyrus:secret@localhost:143")
machine.fail("curl --fail --max-time 10 imap://cyrus:wrongsecret@localhost:143")
machine.fail("curl --fail --max-time 10 -X PROPFIND -H 'Depth: 1' 'http://localhost/dav/addressbooks/user/cyrus@localhost/Default'")
'';
}

View File

@ -0,0 +1,204 @@
{
# build tools
stdenv,
autoreconfHook,
makeWrapper,
pkg-config,
# check hook
versionCheckHook,
# fetchers
fetchFromGitHub,
fetchpatch,
fetchurl,
# build inputs
bison,
brotli,
coreutils,
cunit,
cyrus_sasl,
fig2dev,
flex,
icu,
jansson,
lib,
libbsd,
libcap,
libchardet,
libical,
libmysqlclient,
libsrs2,
libuuid,
libxml2,
nghttp2,
openssl,
pcre2,
perl,
postgresql,
rsync,
shapelib,
sqlite,
unixtools,
valgrind,
wslay,
xapian,
zlib,
# feature flags
enableAutoCreate ? true,
enableBackup ? true,
enableCalalarmd ? true,
enableHttp ? true,
enableIdled ? true,
enableJMAP ? true,
enableMurder ? true,
enableNNTP ? false,
enableReplication ? true,
enableSrs ? true,
enableUnitTests ? true,
enableXapian ? true,
withLibcap ? true,
withMySQL ? false,
withOpenssl ? true,
withPgSQL ? false,
withSQLite ? true,
withZlib ? true,
}:
stdenv.mkDerivation (finalAttrs: {
pname = "cyrus-imapd";
version = "3.10.0";
src = fetchFromGitHub {
owner = "cyrusimap";
repo = "cyrus-imapd";
rev = "refs/tags/cyrus-imapd-${finalAttrs.version}";
hash = "sha256-dyybRqmrVX+ERGpToS5JjGC6S/B0t967dLCWfeUrLKA=";
};
nativeBuildInputs = [
makeWrapper
pkg-config
autoreconfHook
];
buildInputs =
[
unixtools.xxd
pcre2
flex
valgrind
fig2dev
perl
cyrus_sasl.dev
icu
jansson
libbsd
libuuid
openssl
zlib
bison
libsrs2
]
++ lib.optionals stdenv.isLinux [ libcap ]
++ lib.optionals (enableHttp || enableCalalarmd || enableJMAP) [
brotli.dev
libical.dev
libxml2.dev
nghttp2.dev
shapelib
]
++ lib.optionals enableJMAP [
libchardet
wslay
]
++ lib.optionals enableXapian [
rsync
xapian
]
++ lib.optionals withMySQL [ libmysqlclient ]
++ lib.optionals withPgSQL [ postgresql ]
++ lib.optionals withSQLite [ sqlite ];
enableParallelBuilding = true;
postPatch =
let
managesieveLibs =
[
zlib
cyrus_sasl
]
# Darwin doesn't have libuuid, try to build without it
++ lib.optional (!stdenv.isDarwin) libuuid;
imapLibs = managesieveLibs ++ [ pcre2 ];
mkLibsString = lib.strings.concatMapStringsSep " " (l: "-L${lib.getLib l}/lib");
in
''
patchShebangs cunit/*.pl
patchShebangs imap/promdatagen
patchShebangs tools/*
echo ${finalAttrs.version} > VERSION
substituteInPlace cunit/command.testc \
--replace-fail /usr/bin/touch ${lib.getExe' coreutils "touch"} \
--replace-fail /bin/echo ${lib.getExe' coreutils "echo"} \
--replace-fail /usr/bin/tr ${lib.getExe' coreutils "tr"} \
--replace-fail /bin/sh ${stdenv.shell}
# fix for https://github.com/cyrusimap/cyrus-imapd/issues/3893
substituteInPlace perl/imap/Makefile.PL.in \
--replace-fail '"$LIB_SASL' '"${mkLibsString imapLibs} -lpcre2-posix $LIB_SASL'
substituteInPlace perl/sieve/managesieve/Makefile.PL.in \
--replace-fail '"$LIB_SASL' '"${mkLibsString managesieveLibs} $LIB_SASL'
'';
postFixup = ''
wrapProgram $out/bin/cyradm --set PERL5LIB $(find $out/lib/perl5 -type d | tr "\\n" ":")
'';
configureFlags = [
"--with-pidfile=/run/cyrus/master.pid"
(lib.enableFeature enableAutoCreate "autocreate")
(lib.enableFeature enableSrs "srs")
(lib.enableFeature enableIdled "idled")
(lib.enableFeature enableMurder "murder")
(lib.enableFeature enableBackup "backup")
(lib.enableFeature enableReplication "replication")
(lib.enableFeature enableUnitTests "unit-tests")
(lib.enableFeature (enableHttp || enableCalalarmd || enableJMAP) "http")
(lib.enableFeature enableJMAP "jmap")
(lib.enableFeature enableNNTP "nntp")
(lib.enableFeature enableXapian "xapian")
(lib.enableFeature enableCalalarmd "calalarmd")
(lib.withFeature withZlib "zlib=${zlib}")
(lib.withFeature withOpenssl "openssl")
(lib.withFeature withLibcap "libcap=${libcap}")
(lib.withFeature withMySQL "mysql")
(lib.withFeature withPgSQL "pgsql")
(lib.withFeature withSQLite "sqlite")
];
checkInputs = [ cunit ];
doCheck = true;
versionCheckProgram = "${builtins.placeholder "out"}/libexec/master";
versionCheckProgramArg = "-V";
nativeInstallCheckInputs = [
versionCheckHook
];
doInstallCheck = true;
meta = {
homepage = "https://www.cyrusimap.org";
description = "Email, contacts and calendar server";
license = with lib.licenses; [ bsdOriginal ];
mainProgram = "cyrus";
maintainers = with lib.maintainers; [
moraxyc
pingiun
];
platforms = lib.platforms.unix;
};
})