summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorJonas Heinrich <onny@project-insanity.org>2021-09-10 13:08:58 +0200
committerPatrick Hilhorst <git@hilhorst.be>2021-12-07 22:58:22 +0100
commitecd88f91a0aa5a25e2cd44a1a09e404bea53d458 (patch)
treebdb62f89e6535994b2d862c9f03b6754bcbb095b /nixos
parent61123a6453c38fe5a279fbfd11a7f5046b3a32bd (diff)
downloadnixpkgs-ecd88f91a0aa5a25e2cd44a1a09e404bea53d458.tar
nixpkgs-ecd88f91a0aa5a25e2cd44a1a09e404bea53d458.tar.gz
nixpkgs-ecd88f91a0aa5a25e2cd44a1a09e404bea53d458.tar.bz2
nixpkgs-ecd88f91a0aa5a25e2cd44a1a09e404bea53d458.tar.lz
nixpkgs-ecd88f91a0aa5a25e2cd44a1a09e404bea53d458.tar.xz
nixpkgs-ecd88f91a0aa5a25e2cd44a1a09e404bea53d458.tar.zst
nixpkgs-ecd88f91a0aa5a25e2cd44a1a09e404bea53d458.zip
nixos/maddy: Add module for maddy
Co-authored-by: Patrick Hilhorst <git@hilhorst.be>
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2111.section.xml7
-rw-r--r--nixos/doc/manual/release-notes/rl-2111.section.md2
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/mail/maddy.nix247
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/maddy.nix58
6 files changed, 316 insertions, 0 deletions
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
index 6b706e4aeaa..e2bda7604e4 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
@@ -275,6 +275,13 @@
       </listitem>
       <listitem>
         <para>
+          <link xlink:href="https://maddy.email">maddy</link>, a
+          composable all-in-one mail server. Available as
+          <link xlink:href="options.html#opt-services.maddy.enable">services.maddy</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           <link xlink:href="https://sr.ht">sourcehut</link>, a
           collection of tools useful for software development. Available
           as
diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md
index 48adc4ad33c..2520d176096 100644
--- a/nixos/doc/manual/release-notes/rl-2111.section.md
+++ b/nixos/doc/manual/release-notes/rl-2111.section.md
@@ -74,6 +74,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [PeerTube](https://joinpeertube.org/), developed by Framasoft, is the free and decentralized alternative to video platforms. Available at [services.peertube](options.html#opt-services.peertube.enable).
 
+- [maddy](https://maddy.email), a composable all-in-one mail server. Available as [services.maddy](options.html#opt-services.maddy.enable).
+
 - [sourcehut](https://sr.ht), a collection of tools useful for software development. Available as [services.sourcehut](options.html#opt-services.sourcehut.enable).
 
 - [ucarp](https://download.pureftpd.org/pub/ucarp/README), an userspace implementation of the Common Address Redundancy Protocol (CARP). Available as [networking.ucarp](options.html#opt-networking.ucarp.enable).
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index f36e7dd67ea..8cb7c39005c 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -467,6 +467,7 @@
   ./services/mail/dovecot.nix
   ./services/mail/dspam.nix
   ./services/mail/exim.nix
+  ./services/mail/maddy.nix
   ./services/mail/mail.nix
   ./services/mail/mailcatcher.nix
   ./services/mail/mailhog.nix
diff --git a/nixos/modules/services/mail/maddy.nix b/nixos/modules/services/mail/maddy.nix
new file mode 100644
index 00000000000..44cfa3c2908
--- /dev/null
+++ b/nixos/modules/services/mail/maddy.nix
@@ -0,0 +1,247 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  name = "maddy";
+  cfg = config.services.maddy;
+  defaultConfig = ''
+    tls off
+
+    auth.pass_table local_authdb {
+      table sql_table {
+        driver sqlite3
+        dsn credentials.db
+        table_name passwords
+      }
+    }
+
+    storage.imapsql local_mailboxes {
+      driver sqlite3
+      dsn imapsql.db
+    }
+
+    table.chain local_rewrites {
+      optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3"
+      optional_step static {
+        entry postmaster postmaster@$(primary_domain)
+      }
+      optional_step file /etc/maddy/aliases
+    }
+    msgpipeline local_routing {
+      destination postmaster $(local_domains) {
+        modify {
+          replace_rcpt &local_rewrites
+        }
+        deliver_to &local_mailboxes
+      }
+      default_destination {
+        reject 550 5.1.1 "User doesn't exist"
+      }
+    }
+
+    smtp tcp://0.0.0.0:25 {
+      limits {
+        all rate 20 1s
+        all concurrency 10
+      }
+      dmarc yes
+      check {
+        require_mx_record
+        dkim
+        spf
+      }
+      source $(local_domains) {
+        reject 501 5.1.8 "Use Submission for outgoing SMTP"
+      }
+      default_source {
+        destination postmaster $(local_domains) {
+          deliver_to &local_routing
+        }
+        default_destination {
+          reject 550 5.1.1 "User doesn't exist"
+        }
+      }
+    }
+
+    submission tcp://0.0.0.0:587 {
+      limits {
+        all rate 50 1s
+      }
+      auth &local_authdb
+      source $(local_domains) {
+        check {
+            authorize_sender {
+                prepare_email &local_rewrites
+                user_to_email identity
+            }
+        }
+        destination postmaster $(local_domains) {
+            deliver_to &local_routing
+        }
+        default_destination {
+            modify {
+                dkim $(primary_domain) $(local_domains) default
+            }
+            deliver_to &remote_queue
+        }
+      }
+      default_source {
+        reject 501 5.1.8 "Non-local sender domain"
+      }
+    }
+
+    target.remote outbound_delivery {
+      limits {
+        destination rate 20 1s
+        destination concurrency 10
+      }
+      mx_auth {
+        dane
+        mtasts {
+          cache fs
+          fs_dir mtasts_cache/
+        }
+        local_policy {
+            min_tls_level encrypted
+            min_mx_level none
+        }
+      }
+    }
+
+    target.queue remote_queue {
+      target &outbound_delivery
+      autogenerated_msg_domain $(primary_domain)
+      bounce {
+        destination postmaster $(local_domains) {
+          deliver_to &local_routing
+        }
+        default_destination {
+            reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
+        }
+      }
+    }
+
+    imap tcp://0.0.0.0:143 {
+      auth &local_authdb
+      storage &local_mailboxes
+    }
+  '';
+
+in {
+  options = {
+    services.maddy = {
+      enable = mkEnableOption "Maddy, a free an open source mail server";
+
+      user = mkOption {
+        default = "maddy";
+        type = with types; uniq string;
+        description = ''
+          Name of the user under which maddy will run. If not specified, a
+          default user will be created.
+        '';
+      };
+      group = mkOption {
+        default = "maddy";
+        type = with types; uniq string;
+        description = ''
+          Name of the group under which maddy will run. If not specified, a
+          default group will be created.
+        '';
+      };
+
+      hostname = mkOption {
+        default = "localhost";
+        type = with types; uniq string;
+        example = ''example.com'';
+        description = ''
+          Hostname to use. It should be FQDN.
+        '';
+      };
+      primaryDomain = mkOption {
+        default = "localhost";
+        type = with types; uniq string;
+        example = ''mail.example.com'';
+        description = ''
+          Primary MX domain to use. It should be FQDN.
+        '';
+      };
+      localDomains = mkOption {
+        type = with types; listOf str;
+        default = ["$(primary_domain)"];
+        example = [
+          "$(primary_domain)"
+          "example.com"
+          "other.example.com"
+        ];
+        description = ''
+          Define list of allowed domains.
+        '';
+      };
+      config = mkOption {
+        type = with types; nullOr lines;
+        default = defaultConfig;
+        description = ''
+          Server configuration.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open the configured incoming and outgoing mail server ports.
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd = {
+      packages = [ pkgs.maddy ];
+      services.maddy = {
+        serviceConfig = {
+          User = "${cfg.user}";
+          Group = "${cfg.group}";
+        };
+        wantedBy = [ "multi-user.target" ];
+      };
+    };
+
+    environment.etc."maddy/maddy.conf" = {
+      text = ''
+        $(hostname) = ${cfg.hostname}
+        $(primary_domain) = ${cfg.primaryDomain}
+        $(local_domains) = ${toString cfg.localDomains}
+        hostname ${cfg.hostname}
+        ${cfg.config}
+      '';
+    };
+
+    users.users = optionalAttrs (cfg.user == "maddy") {
+      maddy = {
+        description = "Maddy service user";
+        group = cfg.group;
+        home = "/var/lib/maddy";
+        createHome = true;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "maddy") {
+      maddy = pkgs.lib.mkForce {
+        name = cfg.group;
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 25 143 587 ];
+    };
+
+    environment.systemPackages = [
+      pkgs.maddy
+    ];
+  };
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index bee2935b84f..63be6789201 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -247,6 +247,7 @@ in
   lxd-image-server = handleTest ./lxd-image-server.nix {};
   #logstash = handleTest ./logstash.nix {};
   lorri = handleTest ./lorri/default.nix {};
+  maddy = handleTest ./maddy.nix {};
   magic-wormhole-mailbox-server = handleTest ./magic-wormhole-mailbox-server.nix {};
   magnetico = handleTest ./magnetico.nix {};
   mailcatcher = handleTest ./mailcatcher.nix {};
diff --git a/nixos/tests/maddy.nix b/nixos/tests/maddy.nix
new file mode 100644
index 00000000000..581748c1fa5
--- /dev/null
+++ b/nixos/tests/maddy.nix
@@ -0,0 +1,58 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "maddy";
+  meta = with pkgs.lib.maintainers; { maintainers = [ onny ]; };
+
+  nodes = {
+    server = { ... }: {
+      services.maddy = {
+        enable = true;
+        hostname = "server";
+        primaryDomain = "server";
+        openFirewall = true;
+      };
+    };
+
+    client = { ... }: {
+      environment.systemPackages = [
+        (pkgs.writers.writePython3Bin "send-testmail" { } ''
+          import smtplib
+          from email.mime.text import MIMEText
+
+          msg = MIMEText("Hello World")
+          msg['Subject'] = 'Test'
+          msg['From'] = "postmaster@server"
+          msg['To'] = "postmaster@server"
+          with smtplib.SMTP('server', 587) as smtp:
+              smtp.login('postmaster@server', 'test')
+              smtp.sendmail('postmaster@server', 'postmaster@server', msg.as_string())
+        '')
+        (pkgs.writers.writePython3Bin "test-imap" { } ''
+          import imaplib
+
+          with imaplib.IMAP4('server') as imap:
+              imap.login('postmaster@server', 'test')
+              imap.select()
+              status, refs = imap.search(None, 'ALL')
+              assert status == 'OK'
+              assert len(refs) == 1
+              status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
+              assert status == 'OK'
+              assert msg[0][1].strip() == b"Hello World"
+        '')
+      ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("maddy.service")
+    server.wait_for_open_port(143)
+    server.wait_for_open_port(587)
+
+    server.succeed("echo test | maddyctl creds create postmaster@server")
+    server.succeed("maddyctl imap-acct create postmaster@server")
+
+    client.succeed("send-testmail")
+    client.succeed("test-imap")
+  '';
+})