aboutsummaryrefslogtreecommitdiff
path: root/flakes/private/milters
diff options
context:
space:
mode:
Diffstat (limited to 'flakes/private/milters')
-rw-r--r--flakes/private/milters/flake.lock186
-rw-r--r--flakes/private/milters/flake.nix106
-rwxr-xr-xflakes/private/milters/verify_from.py60
3 files changed, 352 insertions, 0 deletions
diff --git a/flakes/private/milters/flake.lock b/flakes/private/milters/flake.lock
new file mode 100644
index 0000000..1a0c138
--- /dev/null
+++ b/flakes/private/milters/flake.lock
@@ -0,0 +1,186 @@
1{
2 "nodes": {
3 "environment": {
4 "locked": {
5 "lastModified": 1,
6 "narHash": "sha256-rMKbM7fHqWQbI7y59BsPG8KwoDj2jyrvN2niPWB24uE=",
7 "path": "../environment",
8 "type": "path"
9 },
10 "original": {
11 "path": "../environment",
12 "type": "path"
13 }
14 },
15 "files-watcher": {
16 "locked": {
17 "lastModified": 1,
18 "narHash": "sha256-ZsdumUVoSPkV/DB6gO6dNDttjzalye0ToVBF9bl5W0k=",
19 "path": "../../files-watcher",
20 "type": "path"
21 },
22 "original": {
23 "path": "../../files-watcher",
24 "type": "path"
25 }
26 },
27 "flake-utils": {
28 "locked": {
29 "lastModified": 1609246779,
30 "narHash": "sha256-eq6ZXE/VWo3EMC65jmIT6H/rrUc9UWOWVujkzav025k=",
31 "owner": "numtide",
32 "repo": "flake-utils",
33 "rev": "08c7ad4a0844adc4a7f9f5bb3beae482e789afa4",
34 "type": "github"
35 },
36 "original": {
37 "owner": "numtide",
38 "repo": "flake-utils",
39 "type": "github"
40 }
41 },
42 "flake-utils_2": {
43 "locked": {
44 "lastModified": 1609246779,
45 "narHash": "sha256-eq6ZXE/VWo3EMC65jmIT6H/rrUc9UWOWVujkzav025k=",
46 "owner": "numtide",
47 "repo": "flake-utils",
48 "rev": "08c7ad4a0844adc4a7f9f5bb3beae482e789afa4",
49 "type": "github"
50 },
51 "original": {
52 "owner": "numtide",
53 "repo": "flake-utils",
54 "type": "github"
55 }
56 },
57 "myuids": {
58 "locked": {
59 "lastModified": 1,
60 "narHash": "sha256-HkW9YCLQCNBX3Em7J7MjraVEZO3I3PizkVV2QrUdULQ=",
61 "path": "../myuids",
62 "type": "path"
63 },
64 "original": {
65 "path": "../myuids",
66 "type": "path"
67 }
68 },
69 "myuids_2": {
70 "locked": {
71 "lastModified": 1,
72 "narHash": "sha256-HkW9YCLQCNBX3Em7J7MjraVEZO3I3PizkVV2QrUdULQ=",
73 "path": "../myuids",
74 "type": "path"
75 },
76 "original": {
77 "path": "../myuids",
78 "type": "path"
79 }
80 },
81 "nixpkgs": {
82 "locked": {
83 "lastModified": 1597943282,
84 "narHash": "sha256-G/VQBlqO7YeFOSvn29RqdvABZxmQBtiRYVA6kjqWZ6o=",
85 "owner": "NixOS",
86 "repo": "nixpkgs",
87 "rev": "c59ea8b8a0e7f927e7291c14ea6cd1bd3a16ff38",
88 "type": "github"
89 },
90 "original": {
91 "owner": "NixOS",
92 "repo": "nixpkgs",
93 "type": "github"
94 }
95 },
96 "nixpkgs_2": {
97 "locked": {
98 "lastModified": 1597943282,
99 "narHash": "sha256-G/VQBlqO7YeFOSvn29RqdvABZxmQBtiRYVA6kjqWZ6o=",
100 "owner": "NixOS",
101 "repo": "nixpkgs",
102 "rev": "c59ea8b8a0e7f927e7291c14ea6cd1bd3a16ff38",
103 "type": "github"
104 },
105 "original": {
106 "owner": "NixOS",
107 "repo": "nixpkgs",
108 "type": "github"
109 }
110 },
111 "openarc": {
112 "inputs": {
113 "flake-utils": "flake-utils",
114 "myuids": "myuids",
115 "nixpkgs": "nixpkgs",
116 "openarc": "openarc_2"
117 },
118 "locked": {
119 "lastModified": 1,
120 "narHash": "sha256-+X3x0t7DSYBvgFAUGNnMV4F/vQOUWE+9Q4Az6V8/iTw=",
121 "path": "../../openarc",
122 "type": "path"
123 },
124 "original": {
125 "path": "../../openarc",
126 "type": "path"
127 }
128 },
129 "openarc_2": {
130 "flake": false,
131 "locked": {
132 "lastModified": 1537545083,
133 "narHash": "sha256-xUSRARC7875vFjtZ66t8KBlKmkEdIZblWHc4zqGZAQQ=",
134 "owner": "trusteddomainproject",
135 "repo": "OpenARC",
136 "rev": "355ee2a1ca85acccce494478991983b54f794f4e",
137 "type": "github"
138 },
139 "original": {
140 "owner": "trusteddomainproject",
141 "repo": "OpenARC",
142 "type": "github"
143 }
144 },
145 "opendmarc": {
146 "inputs": {
147 "flake-utils": "flake-utils_2",
148 "myuids": "myuids_2",
149 "nixpkgs": "nixpkgs_2"
150 },
151 "locked": {
152 "lastModified": 1,
153 "narHash": "sha256-dDS9a1XujZU6KVCgz2RKbx2T3yT1k7z0EknUh1OyMdQ=",
154 "path": "../../opendmarc",
155 "type": "path"
156 },
157 "original": {
158 "path": "../../opendmarc",
159 "type": "path"
160 }
161 },
162 "root": {
163 "inputs": {
164 "environment": "environment",
165 "files-watcher": "files-watcher",
166 "openarc": "openarc",
167 "opendmarc": "opendmarc",
168 "secrets": "secrets"
169 }
170 },
171 "secrets": {
172 "locked": {
173 "lastModified": 1,
174 "narHash": "sha256-5AakznhrJFmwCD7lr4JEh55MtdAJL6WA/YuBks6ISSE=",
175 "path": "../../secrets",
176 "type": "path"
177 },
178 "original": {
179 "path": "../../secrets",
180 "type": "path"
181 }
182 }
183 },
184 "root": "root",
185 "version": 7
186}
diff --git a/flakes/private/milters/flake.nix b/flakes/private/milters/flake.nix
new file mode 100644
index 0000000..c4de5b6
--- /dev/null
+++ b/flakes/private/milters/flake.nix
@@ -0,0 +1,106 @@
1{
2 inputs.secrets.url = "path:../../secrets";
3 inputs.environment.url = "path:../environment";
4 inputs.files-watcher.url = "path:../../files-watcher";
5 inputs.opendmarc.url = "path:../../opendmarc";
6 inputs.openarc.url = "path:../../openarc";
7 outputs = { self, secrets, environment, opendmarc, openarc, files-watcher }: {
8 nixosModule = self.nixosModules.milters;
9 nixosModules.milters = { lib, pkgs, config, nodes, ... }:
10 {
11 imports = [
12 secrets.nixosModule
13 environment.nixosModule
14 files-watcher.nixosModule
15 opendmarc.nixosModule
16 openarc.nixosModule
17 ];
18 options.myServices.mail.milters.enable = lib.mkEnableOption "enable Mail milters";
19 options.myServices.mail.milters.sockets = lib.mkOption {
20 type = lib.types.attrsOf lib.types.path;
21 default = {
22 opendkim = "/run/opendkim/opendkim.sock";
23 opendmarc = config.services.opendmarc.socket;
24 openarc = config.services.openarc.socket;
25 };
26 readOnly = true;
27 description = ''
28 milters sockets
29 '';
30 };
31 config = lib.mkIf config.myServices.mail.milters.enable {
32 secrets.keys = {
33 "opendkim" = {
34 isDir = true;
35 user = config.services.opendkim.user;
36 group = config.services.opendkim.group;
37 permissions = "0550";
38 };
39 "opendkim/eldiron.private" = {
40 user = config.services.opendkim.user;
41 group = config.services.opendkim.group;
42 permissions = "0400";
43 text = config.myEnv.mail.dkim.eldiron.private;
44 };
45 };
46 users.users."${config.services.opendkim.user}".extraGroups = [ "keys" ];
47 services.opendkim = {
48 enable = true;
49 socket = "local:${config.myServices.mail.milters.sockets.opendkim}";
50 domains =
51 let
52 getDomains = p: lib.mapAttrsToList (n: v: v.fqdn) p.emailPolicies;
53 bydomain = builtins.mapAttrs (n: getDomains) nodes.eldiron.config.myServices.dns.zones;
54 domains' = lib.flatten (builtins.attrValues bydomain);
55 in
56 builtins.concatStringsSep "," domains';
57 keyPath = config.secrets.fullPaths."opendkim";
58 selector = "eldiron";
59 configFile = pkgs.writeText "opendkim.conf" ''
60 SubDomains yes
61 UMask 002
62 AlwaysAddARHeader yes
63 '';
64 group = config.services.postfix.group;
65 };
66 systemd.services.opendkim.serviceConfig.Slice = "mail.slice";
67 systemd.services.opendkim.preStart = lib.mkBefore ''
68 # Skip the prestart script as keys are handled in secrets
69 exit 0
70 '';
71 services.filesWatcher.opendkim = {
72 restart = true;
73 paths = [
74 config.secrets.fullPaths."opendkim/eldiron.private"
75 ];
76 };
77
78 systemd.services.milter_verify_from = {
79 description = "Verify from milter";
80 after = [ "network.target" ];
81 wantedBy = [ "multi-user.target" ];
82
83 serviceConfig = {
84 Slice = "mail.slice";
85 User = "postfix";
86 Group = "postfix";
87 ExecStart = let
88 pymilter = with pkgs.python38Packages; buildPythonPackage rec {
89 pname = "pymilter";
90 version = "1.0.4";
91 src = fetchPypi {
92 inherit pname version;
93 sha256 = "1bpcvq7d72q0zi7c8h5knhasywwz9gxc23n9fxmw874n5k8hsn7k";
94 };
95 doCheck = false;
96 buildInputs = [ pkgs.libmilter ];
97 };
98 python = pkgs.python38.withPackages (p: [ pymilter ]);
99 in "${python}/bin/python ${./verify_from.py} -s /run/milter_verify_from/verify_from.sock";
100 RuntimeDirectory = "milter_verify_from";
101 };
102 };
103 };
104 };
105 };
106}
diff --git a/flakes/private/milters/verify_from.py b/flakes/private/milters/verify_from.py
new file mode 100755
index 0000000..b75001e
--- /dev/null
+++ b/flakes/private/milters/verify_from.py
@@ -0,0 +1,60 @@
1#!/usr/bin/env python3
2import Milter
3import argparse
4from email.header import decode_header
5from email.utils import parseaddr
6
7class CheckMilter(Milter.Base):
8 def __init__(self):
9 self.envelope_from = None
10 self.header_from = None
11
12 @Milter.noreply
13 def connect(self, IPname, family, hostaddr):
14 return Milter.CONTINUE
15
16 def hello(self, heloname):
17 return Milter.CONTINUE
18
19 def envfrom(self, mailfrom, *args):
20 self.envelope_from = parseaddr(mailfrom)[1]
21 return Milter.CONTINUE
22
23 @Milter.noreply
24 def envrcpt(self, to, *str):
25 return Milter.CONTINUE
26
27 @Milter.noreply
28 def header(self, name, hval):
29 if name.lower() == "from":
30 self.header_from = parseaddr(decode_header(hval)[-1][0])[1]
31 return Milter.CONTINUE
32
33 def eoh(self):
34 if self.header_from is not None and self.header_from != "" and self.header_from != self.envelope_from:
35 self.setreply("553", xcode="5.7.1", msg="<%s>: From header rejected: not matching envelope From %s"
36 % (self.header_from, self.envelope_from))
37 return Milter.REJECT
38
39 return Milter.CONTINUE
40
41 @Milter.noreply
42 def body(self, chunk):
43 return Milter.CONTINUE
44
45 def eom(self):
46 return Milter.ACCEPT
47
48 def close(self):
49 return Milter.CONTINUE
50
51 def abort(self):
52 return Milter.CONTINUE
53
54if __name__ == "__main__":
55 parser = argparse.ArgumentParser()
56 parser.add_argument("--socket", "-s", type=str, help="socket to listen to")
57 config = parser.parse_args()
58
59 Milter.factory = CheckMilter
60 Milter.runmilter("check_from", config.socket, timeout=300)