diff options
Diffstat (limited to 'nixos/modules/services/mail')
-rw-r--r-- | nixos/modules/services/mail/dovecot.nix | 168 | ||||
-rw-r--r-- | nixos/modules/services/mail/freepops.nix | 87 | ||||
-rw-r--r-- | nixos/modules/services/mail/mail.nix | 33 | ||||
-rw-r--r-- | nixos/modules/services/mail/opensmtpd.nix | 83 | ||||
-rw-r--r-- | nixos/modules/services/mail/postfix.nix | 405 | ||||
-rw-r--r-- | nixos/modules/services/mail/spamassassin.nix | 64 |
6 files changed, 840 insertions, 0 deletions
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix new file mode 100644 index 00000000000..5f8e8e1ade3 --- /dev/null +++ b/nixos/modules/services/mail/dovecot.nix @@ -0,0 +1,168 @@ +{ config, pkgs, ... }: + +with pkgs.lib; + +let + + cfg = config.services.dovecot2; + + dovecotConf = + '' + base_dir = /var/run/dovecot2/ + + protocols = ${optionalString cfg.enableImap "imap"} ${optionalString cfg.enablePop3 "pop3"} + '' + + (if cfg.sslServerCert!="" then + '' + ssl_cert = <${cfg.sslServerCert} + ssl_key = <${cfg.sslServerKey} + ssl_ca = <${cfg.sslCACert} + disable_plaintext_auth = yes + '' else '' + ssl = no + disable_plaintext_auth = no + '') + + + '' + default_internal_user = ${cfg.user} + + mail_location = ${cfg.mailLocation} + + maildir_copy_with_hardlinks = yes + + auth_mechanisms = plain login + service auth { + user = root + } + userdb { + driver = passwd + } + passdb { + driver = pam + args = ${optionalString cfg.showPAMFailure "failure_show_msg=yes"} dovecot2 + } + + pop3_uidl_format = %08Xv%08Xu + '' + cfg.extraConfig; + + confFile = pkgs.writeText "dovecot.conf" dovecotConf; + +in + +{ + + ###### interface + + options = { + + services.dovecot2 = { + + enable = mkOption { + default = false; + description = "Whether to enable the Dovecot 2.x POP3/IMAP server."; + }; + + enablePop3 = mkOption { + default = true; + description = "Start the POP3 listener (when Dovecot is enabled)."; + }; + + enableImap = mkOption { + default = true; + description = "Start the IMAP listener (when Dovecot is enabled)."; + }; + + user = mkOption { + default = "dovecot2"; + description = "Dovecot user name."; + }; + + group = mkOption { + default = "dovecot2"; + description = "Dovecot group name."; + }; + + extraConfig = mkOption { + default = ""; + example = "mail_debug = yes"; + description = "Additional entries to put verbatim into Dovecot's config file."; + }; + + mailLocation = mkOption { + default = "maildir:/var/spool/mail/%u"; /* Same as inbox, as postfix */ + example = "maildir:~/mail:INBOX=/var/spool/mail/%u"; + description = '' + Location that dovecot will use for mail folders. Dovecot mail_location option. + ''; + }; + + sslServerCert = mkOption { + default = ""; + description = "Server certificate"; + }; + + sslCACert = mkOption { + default = ""; + description = "CA certificate used by the server certificate."; + }; + + sslServerKey = mkOption { + default = ""; + description = "Server key."; + }; + + showPAMFailure = mkOption { + default = false; + description = "Show the PAM failure message on authentication error (useful for OTPW)."; + }; + }; + + }; + + + ###### implementation + + config = mkIf config.services.dovecot2.enable { + + security.pam.services = [ { name = "dovecot2"; } ]; + + users.extraUsers = [ + { name = cfg.user; + uid = config.ids.uids.dovecot2; + description = "Dovecot user"; + group = cfg.group; + } + { name = "dovenull"; + uid = config.ids.uids.dovenull2; + description = "Dovecot user for untrusted logins"; + group = cfg.group; + } + ]; + + users.extraGroups = singleton + { name = cfg.group; + gid = config.ids.gids.dovecot2; + }; + + jobs.dovecot2 = + { description = "Dovecot IMAP/POP3 server"; + + startOn = "started networking"; + + preStart = + '' + ${pkgs.coreutils}/bin/mkdir -p /var/run/dovecot2 /var/run/dovecot2/login + ${pkgs.coreutils}/bin/chown -R ${cfg.user}:${cfg.group} /var/run/dovecot2 + ''; + + exec = "${pkgs.dovecot}/sbin/dovecot -F -c ${confFile}"; + }; + + environment.systemPackages = [ pkgs.dovecot ]; + + assertions = [{ assertion = cfg.enablePop3 || cfg.enableImap; + message = "dovecot needs at least one of the IMAP or POP3 listeners enabled";}]; + + }; + +} diff --git a/nixos/modules/services/mail/freepops.nix b/nixos/modules/services/mail/freepops.nix new file mode 100644 index 00000000000..8f6e9382607 --- /dev/null +++ b/nixos/modules/services/mail/freepops.nix @@ -0,0 +1,87 @@ +{config, pkgs, ...}: + +with pkgs.lib; + +let + cfg = config.services.mail.freepopsd; +in + +{ + options = { + services.mail.freepopsd = { + enable = mkOption { + default = false; + type = with types; bool; + description = '' + Enables Freepops, a POP3 webmail wrapper. + ''; + }; + + port = mkOption { + default = 2000; + type = with types; uniq int; + description = '' + Port on which the pop server will listen. + ''; + }; + + threads = mkOption { + default = 5; + type = with types; uniq int; + description = '' + Max simultaneous connections. + ''; + }; + + bind = mkOption { + default = "0.0.0.0"; + type = with types; uniq string; + description = '' + Bind over an IPv4 address instead of any. + ''; + }; + + logFile = mkOption { + default = "/var/log/freepopsd"; + example = "syslog"; + type = with types; uniq string; + description = '' + Filename of the log file or syslog to rely on the logging daemon. + ''; + }; + + suid = { + user = mkOption { + default = "nobody"; + type = with types; uniq string; + description = '' + User name under which freepopsd will be after binding the port. + ''; + }; + + group = mkOption { + default = "nogroup"; + type = with types; uniq string; + description = '' + Group under which freepopsd will be after binding the port. + ''; + }; + }; + + }; + }; + + config = mkIf cfg.enable { + jobs.freepopsd = { + description = "Freepopsd (webmail over POP3)"; + startOn = "ip-up"; + exec = ''${pkgs.freepops}/bin/freepopsd \ + -p ${toString cfg.port} \ + -t ${toString cfg.threads} \ + -b ${cfg.bind} \ + -vv -l ${cfg.logFile} \ + -s ${cfg.suid.user}.${cfg.suid.group} + ''; + }; + }; +} diff --git a/nixos/modules/services/mail/mail.nix b/nixos/modules/services/mail/mail.nix new file mode 100644 index 00000000000..bad0b22625d --- /dev/null +++ b/nixos/modules/services/mail/mail.nix @@ -0,0 +1,33 @@ +{ config, pkgs, ... }: + +with pkgs.lib; + +{ + + ###### interface + + options = { + + services.mail = { + + sendmailSetuidWrapper = mkOption { + default = null; + description = '' + Configuration for the sendmail setuid wrwapper (like an element of + security.setuidOwners)"; + ''; + }; + + }; + + }; + + ###### implementation + + config = mkIf (config.services.mail.sendmailSetuidWrapper != null) { + + security.setuidOwners = [ config.services.mail.sendmailSetuidWrapper ]; + + }; + +} diff --git a/nixos/modules/services/mail/opensmtpd.nix b/nixos/modules/services/mail/opensmtpd.nix new file mode 100644 index 00000000000..2732fd60200 --- /dev/null +++ b/nixos/modules/services/mail/opensmtpd.nix @@ -0,0 +1,83 @@ +{ pkgs, config, ... }: + +with pkgs; +with pkgs.lib; + +let + + cfg = config.services.opensmtpd; + conf = writeText "smtpd.conf" cfg.serverConfiguration; + args = concatStringsSep " " cfg.extraServerArgs; + +in { + + ###### interface + + options = { + + services.opensmtpd = { + + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable the OpenSMTPD server."; + }; + + extraServerArgs = mkOption { + type = types.listOf types.string; + default = []; + example = [ "-v" "-P mta" ]; + description = '' + Extra command line arguments provided when the smtpd process + is started. + ''; + }; + + serverConfiguration = mkOption { + type = types.string; + default = ""; + example = '' + listen on lo + accept for any deliver to lmtp localhost:24 + ''; + description = '' + The contents of the smtpd.conf configuration file. See the + OpenSMTPD documentation for syntax information. If this option + is left empty, the OpenSMTPD server will not start. + ''; + }; + }; + + }; + + + ###### implementation + + config = mkIf config.services.opensmtpd.enable { + users.extraGroups = { + smtpd.gid = config.ids.gids.smtpd; + smtpq.gid = config.ids.gids.smtpq; + }; + + users.extraUsers = { + smtpd = { + description = "OpenSMTPD process user"; + uid = config.ids.uids.smtpd; + group = "smtpd"; + }; + smtpq = { + description = "OpenSMTPD queue user"; + uid = config.ids.uids.smtpq; + group = "smtpq"; + }; + }; + + systemd.services.opensmtpd = { + wantedBy = [ "multi-user.target" ]; + wants = [ "network.target" ]; + after = [ "network.target" ]; + preStart = "mkdir -p /var/spool"; + serviceConfig.ExecStart = "${opensmtpd}/sbin/smtpd -d -f ${conf} ${args}"; + }; + }; +} diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix new file mode 100644 index 00000000000..2633289b46d --- /dev/null +++ b/nixos/modules/services/mail/postfix.nix @@ -0,0 +1,405 @@ +{ config, pkgs, ... }: + +with pkgs.lib; + +let + + cfg = config.services.postfix; + user = cfg.user; + group = cfg.group; + setgidGroup = cfg.setgidGroup; + + mainCf = + '' + queue_directory = /var/postfix/queue + command_directory = ${pkgs.postfix}/sbin + daemon_directory = ${pkgs.postfix}/libexec/postfix + + mail_owner = ${user} + default_privs = nobody + + '' + + optionalString config.networking.enableIPv6 '' + inet_protocols = all + '' + + (if cfg.networks != null then + '' + mynetworks = ${concatStringsSep ", " cfg.networks} + '' + else if cfg.networksStyle != "" then + '' + mynetworks_style = ${cfg.networksStyle} + '' + else + # Postfix default is subnet, but let's play safe + '' + mynetworks_style = host + '') + + optionalString (cfg.hostname != "") '' + myhostname = ${cfg.hostname} + '' + + optionalString (cfg.domain != "") '' + mydomain = ${cfg.domain} + '' + + optionalString (cfg.origin != "") '' + myorigin = ${cfg.origin} + '' + + optionalString (cfg.destination != null) '' + mydestination = ${concatStringsSep ", " cfg.destination} + '' + + optionalString (cfg.relayDomains != null) '' + relay_domains = ${concatStringsSep ", " cfg.relayDomains} + '' + + '' + local_recipient_maps = + + relayhost = ${if cfg.lookupMX || cfg.relayHost == "" then + cfg.relayHost + else + "[" + cfg.relayHost + "]"} + + alias_maps = hash:/var/postfix/conf/aliases + + mail_spool_directory = /var/spool/mail/ + + setgid_group = ${setgidGroup} + '' + + optionalString (cfg.sslCert != "") '' + + smtp_tls_CAfile = ${cfg.sslCACert} + smtp_tls_cert_file = ${cfg.sslCert} + smtp_tls_key_file = ${cfg.sslKey} + + smtp_use_tls = yes + + smtpd_tls_CAfile = ${cfg.sslCACert} + smtpd_tls_cert_file = ${cfg.sslCert} + smtpd_tls_key_file = ${cfg.sslKey} + + smtpd_use_tls = yes + + recipientDelimiter = ${cfg.recipientDelimiter} + '' + + optionalString (cfg.virtual != "") '' + virtual_alias_maps = hash:/etc/postfix/virtual + '' + + cfg.extraConfig; + + masterCf = '' + # ========================================================================== + # service type private unpriv chroot wakeup maxproc command + args + # (yes) (yes) (yes) (never) (100) + # ========================================================================== + smtp inet n - n - - smtpd + #submission inet n - n - - smtpd + # -o smtpd_tls_security_level=encrypt + # -o smtpd_sasl_auth_enable=yes + # -o smtpd_client_restrictions=permit_sasl_authenticated,reject + # -o milter_macro_daemon_name=ORIGINATING + pickup fifo n - n 60 1 pickup + cleanup unix n - n - 0 cleanup + qmgr fifo n - n 300 1 qmgr + tlsmgr unix - - n 1000? 1 tlsmgr + rewrite unix - - n - - trivial-rewrite + bounce unix - - n - 0 bounce + defer unix - - n - 0 bounce + trace unix - - n - 0 bounce + verify unix - - n - 1 verify + flush unix n - n 1000? 0 flush + proxymap unix - - n - - proxymap + proxywrite unix - - n - 1 proxymap + smtp unix - - n - - smtp + relay unix - - n - - smtp + -o smtp_fallback_relay= + # -o smtp_helo_timeout=5 -o smtp_connect_timeout=5 + showq unix n - n - - showq + error unix - - n - - error + retry unix - - n - - error + discard unix - - n - - discard + local unix - n n - - local + virtual unix - n n - - virtual + lmtp unix - - n - - lmtp + anvil unix - - n - 1 anvil + scache unix - - n - 1 scache + ${cfg.extraMasterConf} + ''; + + aliases = + optionalString (cfg.postmasterAlias != "") '' + postmaster: ${cfg.postmasterAlias} + '' + + optionalString (cfg.rootAlias != "") '' + root: ${cfg.rootAlias} + '' + + cfg.extraAliases + ; + + aliasesFile = pkgs.writeText "postfix-aliases" aliases; + virtualFile = pkgs.writeText "postfix-virtual" cfg.virtual; + mainCfFile = pkgs.writeText "postfix-main.cf" mainCf; + masterCfFile = pkgs.writeText "postfix-master.cf" masterCf; + +in + +{ + + ###### interface + + options = { + + services.postfix = { + + enable = mkOption { + default = false; + description = "Whether to run the Postfix mail server."; + }; + + setSendmail = mkOption { + default = true; + description = "Whether to set the system sendmail to postfix's."; + }; + + user = mkOption { + default = "postfix"; + description = "What to call the Postfix user (must be used only for postfix)."; + }; + + group = mkOption { + default = "postfix"; + description = "What to call the Postfix group (must be used only for postfix)."; + }; + + setgidGroup = mkOption { + default = "postdrop"; + description = " + How to call postfix setgid group (for postdrop). Should + be uniquely used group. + "; + }; + + networks = mkOption { + default = null; + example = ["192.168.0.1/24"]; + description = " + Net masks for trusted - allowed to relay mail to third parties - + hosts. Leave empty to use mynetworks_style configuration or use + default (localhost-only). + "; + }; + + networksStyle = mkOption { + default = ""; + description = " + Name of standard way of trusted network specification to use, + leave blank if you specify it explicitly or if you want to use + default (localhost-only). + "; + }; + + hostname = mkOption { + default = ""; + description =" + Hostname to use. Leave blank to use just the hostname of machine. + It should be FQDN. + "; + }; + + domain = mkOption { + default = ""; + description =" + Domain to use. Leave blank to use hostname minus first component. + "; + }; + + origin = mkOption { + default = ""; + description =" + Origin to use in outgoing e-mail. Leave blank to use hostname. + "; + }; + + destination = mkOption { + default = null; + example = ["localhost"]; + description = " + Full (!) list of domains we deliver locally. Leave blank for + acceptable Postfix default. + "; + }; + + relayDomains = mkOption { + default = null; + example = ["localdomain"]; + description = " + List of domains we agree to relay to. Default is the same as + destination. + "; + }; + + relayHost = mkOption { + default = ""; + description = " + Mail relay for outbound mail. + "; + }; + + lookupMX = mkOption { + default = false; + description = " + Whether relay specified is just domain whose MX must be used. + "; + }; + + postmasterAlias = mkOption { + default = "root"; + description = "Who should receive postmaster e-mail."; + }; + + rootAlias = mkOption { + default = ""; + description = " + Who should receive root e-mail. Blank for no redirection. + "; + }; + + extraAliases = mkOption { + default = ""; + description = " + Additional entries to put verbatim into aliases file. + "; + }; + + extraConfig = mkOption { + default = ""; + description = " + Extra lines to be added verbatim to the main.cf configuration file. + "; + }; + + sslCert = mkOption { + default = ""; + description = "SSL certificate to use."; + }; + + sslCACert = mkOption { + default = ""; + description = "SSL certificate of CA."; + }; + + sslKey = mkOption { + default = ""; + description = "SSL key to use."; + }; + + recipientDelimiter = mkOption { + default = ""; + example = "+"; + description = " + Delimiter for address extension: so mail to user+test can be handled by ~user/.forward+test + "; + }; + + virtual = mkOption { + default = ""; + description = " + Entries for the virtual alias map. + "; + }; + + extraMasterConf = mkOption { + default = ""; + example = "submission inet n - n - - smtpd"; + description = "Extra lines to append to the generated master.cf file."; + }; + + }; + + }; + + + ###### implementation + + config = mkIf config.services.postfix.enable { + + environment = { + etc = singleton + { source = "/var/postfix/conf"; + target = "postfix"; + }; + + # This makes comfortable for root to run 'postqueue' for example. + systemPackages = [ pkgs.postfix ]; + }; + + services.mail.sendmailSetuidWrapper = mkIf config.services.postfix.setSendmail { + program = "sendmail"; + source = "${pkgs.postfix}/bin/sendmail"; + owner = "nobody"; + group = "postdrop"; + setuid = false; + setgid = true; + }; + + users.extraUsers = singleton + { name = user; + description = "Postfix mail server user"; + uid = config.ids.uids.postfix; + group = group; + }; + + users.extraGroups = + [ { name = group; + gid = config.ids.gids.postfix; + } + { name = setgidGroup; + gid = config.ids.gids.postdrop; + } + ]; + + jobs.postfix = + # I copy _lots_ of shipped configuration filed + # that can be left as is. I am afraid the exact + # will list slightly change in next Postfix + # release, so listing them all one-by-one in an + # accurate way is unlikely to be better. + { description = "Postfix mail server"; + + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + daemonType = "fork"; + + preStart = + '' + if ! [ -d /var/spool/postfix ]; then + ${pkgs.coreutils}/bin/mkdir -p /var/spool/mail /var/postfix/conf /var/postfix/queue + fi + + ${pkgs.coreutils}/bin/chown -R ${user}:${group} /var/postfix + ${pkgs.coreutils}/bin/chown -R ${user}:${setgidGroup} /var/postfix/queue + ${pkgs.coreutils}/bin/chmod -R ug+rwX /var/postfix/queue + ${pkgs.coreutils}/bin/chown root:root /var/spool/mail + ${pkgs.coreutils}/bin/chmod a+rwxt /var/spool/mail + + ln -sf "${pkgs.postfix}/share/postfix/conf/"* /var/postfix/conf + + ln -sf ${aliasesFile} /var/postfix/conf/aliases + ln -sf ${virtualFile} /var/postfix/conf/virtual + ln -sf ${mainCfFile} /var/postfix/conf/main.cf + ln -sf ${masterCfFile} /var/postfix/conf/master.cf + + ${pkgs.postfix}/sbin/postalias -c /var/postfix/conf /var/postfix/conf/aliases + ${pkgs.postfix}/sbin/postmap -c /var/postfix/conf /var/postfix/conf/virtual + + ${pkgs.postfix}/sbin/postfix -c /var/postfix/conf start + ''; + + preStop = '' + ${pkgs.postfix}/sbin/postfix -c /var/postfix/conf stop + ''; + + }; + + }; + +} diff --git a/nixos/modules/services/mail/spamassassin.nix b/nixos/modules/services/mail/spamassassin.nix new file mode 100644 index 00000000000..aaf1dfcc210 --- /dev/null +++ b/nixos/modules/services/mail/spamassassin.nix @@ -0,0 +1,64 @@ +{ config, pkgs, ... }: + +with pkgs.lib; + +let + + cfg = config.services.spamassassin; + +in + +{ + + ###### interface + + options = { + + services.spamassassin = { + + enable = mkOption { + default = false; + description = "Whether to run the SpamAssassin daemon."; + }; + + debug = mkOption { + default = false; + description = "Whether to run the SpamAssassin daemon in debug mode."; + }; + + }; + + }; + + + ###### implementation + + config = mkIf cfg.enable { + + # Allow users to run 'spamc'. + environment.systemPackages = [ pkgs.spamassassin ]; + + users.extraUsers = singleton { + name = "spamd"; + description = "Spam Assassin Daemon"; + uid = config.ids.uids.spamd; + group = "spamd"; + }; + + users.extraGroups = singleton { + name = "spamd"; + gid = config.ids.gids.spamd; + }; + + jobs.spamd = { + description = "Spam Assassin Server"; + + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + exec = "${pkgs.spamassassin}/bin/spamd ${optionalString cfg.debug "-D"} --username=spamd --groupname=spamd --nouser-config --virtual-config-dir=/var/lib/spamassassin/user-%u --allow-tell --pidfile=/var/run/spamd.pid"; + }; + + }; + +} |