diff options
Diffstat (limited to 'flakes/private/milters')
-rw-r--r-- | flakes/private/milters/flake.lock | 186 | ||||
-rw-r--r-- | flakes/private/milters/flake.nix | 106 | ||||
-rwxr-xr-x | flakes/private/milters/verify_from.py | 60 |
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 | ||
2 | import Milter | ||
3 | import argparse | ||
4 | from email.header import decode_header | ||
5 | from email.utils import parseaddr | ||
6 | |||
7 | class 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 | |||
54 | if __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) | ||