From 7a020d9dc33053fc630a38b88cec81d870fb595f Mon Sep 17 00:00:00 2001 From: Moraxyc Date: Sun, 31 Mar 2024 17:24:35 +0800 Subject: [PATCH 1/3] cyrus-imapd: init at 3.10.0 Co-authored-by: jtbx Co-authored-by: drupol --- pkgs/by-name/cy/cyrus-imapd/package.nix | 204 ++++++++++++++++++++++++ test | 0 2 files changed, 204 insertions(+) create mode 100644 pkgs/by-name/cy/cyrus-imapd/package.nix create mode 100644 test diff --git a/pkgs/by-name/cy/cyrus-imapd/package.nix b/pkgs/by-name/cy/cyrus-imapd/package.nix new file mode 100644 index 000000000000..860eaf162016 --- /dev/null +++ b/pkgs/by-name/cy/cyrus-imapd/package.nix @@ -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; + }; +}) diff --git a/test b/test new file mode 100644 index 000000000000..e69de29bb2d1 From 8d90446d39dba6ce3f1f15af5430a4e2ce368f16 Mon Sep 17 00:00:00 2001 From: Moraxyc Date: Fri, 10 May 2024 10:29:44 +0800 Subject: [PATCH 2/3] nixos/cyrus-imap: init module Co-authored-by: jtbx Co-authored-by: pluiedev --- nixos/modules/module-list.nix | 1 + nixos/modules/services/mail/cyrus-imap.nix | 379 +++++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/cyrus-imap.nix | 120 +++++++ test | 0 5 files changed, 501 insertions(+) create mode 100644 nixos/modules/services/mail/cyrus-imap.nix create mode 100644 nixos/tests/cyrus-imap.nix delete mode 100644 test diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 6a1fd5a45dc2..b97db06ebb3a 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -660,6 +660,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 diff --git a/nixos/modules/services/mail/cyrus-imap.nix b/nixos/modules/services/mail/cyrus-imap.nix new file mode 100644 index 000000000000..f5ace168b045 --- /dev/null +++ b/nixos/modules/services/mail/cyrus-imap.nix @@ -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 ]; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 8ddcc779ea90..8b26b710dfad 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -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 {}; diff --git a/nixos/tests/cyrus-imap.nix b/nixos/tests/cyrus-imap.nix new file mode 100644 index 000000000000..36145ed4748e --- /dev/null +++ b/nixos/tests/cyrus-imap.nix @@ -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'") + ''; +} diff --git a/test b/test deleted file mode 100644 index e69de29bb2d1..000000000000 From cc1aeb4fbd903132ee2203c21b047d812439edf9 Mon Sep 17 00:00:00 2001 From: Moraxyc Date: Sat, 19 Oct 2024 21:54:20 +0800 Subject: [PATCH 3/3] nixos/docs: add services.cyrus-imap to rl-2411 --- nixos/doc/manual/release-notes/rl-2411.section.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md index 371d338a3418..f3810842723d 100644 --- a/nixos/doc/manual/release-notes/rl-2411.section.md +++ b/nixos/doc/manual/release-notes/rl-2411.section.md @@ -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.