{ config, lib, pkgs, ... }: with lib; let cfg = config.services.clamsmtp; clamdSocket = "/run/clamav/clamd.ctl"; # See services/security/clamav.nix in { ##### interface options = { services.clamsmtp = { enable = mkOption { type = types.bool; default = false; description = "Whether to enable clamsmtp."; }; instances = mkOption { description = "Instances of clamsmtp to run."; type = types.listOf (types.submodule { options = { action = mkOption { type = types.enum [ "bounce" "drop" "pass" ]; default = "drop"; description = '' Action to take when a virus is detected. Note that viruses often spoof sender addresses, so bouncing is in most cases not a good idea. ''; }; header = mkOption { type = types.str; default = ""; example = "X-Virus-Scanned: ClamAV using ClamSMTP"; description = '' A header to add to scanned messages. See clamsmtpd.conf(5) for more details. Empty means no header. ''; }; keepAlives = mkOption { type = types.int; default = 0; description = '' Number of seconds to wait between each NOOP sent to the sending server. 0 to disable. This is meant for slow servers where the sending MTA times out waiting for clamd to scan the file. ''; }; listen = mkOption { type = types.str; example = "127.0.0.1:10025"; description = '' Address to wait for incoming SMTP connections on. See clamsmtpd.conf(5) for more details. ''; }; quarantine = mkOption { type = types.bool; default = false; description = '' Whether to quarantine files that contain viruses by leaving them in the temporary directory. ''; }; maxConnections = mkOption { type = types.int; default = 64; description = "Maximum number of connections to accept at once."; }; outAddress = mkOption { type = types.str; description = '' Address of the SMTP server to send email to once it has been scanned. ''; }; tempDirectory = mkOption { type = types.str; default = "/tmp"; description = '' Temporary directory that needs to be accessible to both clamd and clamsmtpd. ''; }; timeout = mkOption { type = types.int; default = 180; description = "Time-out for network connections."; }; transparentProxy = mkOption { type = types.bool; default = false; description = "Enable clamsmtp's transparent proxy support."; }; virusAction = mkOption { type = with types; nullOr path; default = null; description = '' Command to run when a virus is found. Please see VIRUS ACTION in clamsmtpd(8) for a discussion of this option and its safe use. ''; }; xClient = mkOption { type = types.bool; default = false; description = '' Send the XCLIENT command to the receiving server, for forwarding client addresses and connection information if the receiving server supports this feature. ''; }; };}); }; }; }; ##### implementation config = let configfile = conf: pkgs.writeText "clamsmtpd.conf" '' Action: ${conf.action} ClamAddress: ${clamdSocket} Header: ${conf.header} KeepAlives: ${toString conf.keepAlives} Listen: ${conf.listen} Quarantine: ${if conf.quarantine then "on" else "off"} MaxConnections: ${toString conf.maxConnections} OutAddress: ${conf.outAddress} TempDirectory: ${conf.tempDirectory} TimeOut: ${toString conf.timeout} TransparentProxy: ${if conf.transparentProxy then "on" else "off"} User: clamav ${optionalString (conf.virusAction != null) "VirusAction: ${conf.virusAction}"} XClient: ${if conf.xClient then "on" else "off"} ''; in mkIf cfg.enable { assertions = [ { assertion = config.services.clamav.daemon.enable; message = "clamsmtp requires clamav to be enabled"; } ]; systemd.services = listToAttrs (imap1 (i: conf: nameValuePair "clamsmtp-${toString i}" { description = "ClamSMTP instance ${toString i}"; wantedBy = [ "multi-user.target" ]; script = "exec ${pkgs.clamsmtp}/bin/clamsmtpd -f ${configfile conf}"; after = [ "clamav-daemon.service" ]; requires = [ "clamav-daemon.service" ]; serviceConfig.Type = "forking"; serviceConfig.PrivateTmp = "yes"; unitConfig.JoinsNamespaceOf = "clamav-daemon.service"; } ) cfg.instances); }; }