diff options
Diffstat (limited to 'modules/uucp.nix')
-rw-r--r-- | modules/uucp.nix | 391 |
1 files changed, 391 insertions, 0 deletions
diff --git a/modules/uucp.nix b/modules/uucp.nix new file mode 100644 index 00000000..8d8ac1a2 --- /dev/null +++ b/modules/uucp.nix | |||
@@ -0,0 +1,391 @@ | |||
1 | { flake, config, lib, pkgs, ... }: | ||
2 | |||
3 | with lib; | ||
4 | |||
5 | let | ||
6 | portSpec = name: node: concatStringsSep "\n" (map (port: '' | ||
7 | port ${name}.${port} | ||
8 | type pipe | ||
9 | protocol ${node.protocols} | ||
10 | reliable true | ||
11 | command ${pkgs.openssh}/bin/ssh -x -o batchmode=yes ${name}.${port} | ||
12 | '') node.hostnames); | ||
13 | sysSpec = name: node: '' | ||
14 | system ${name} | ||
15 | time any | ||
16 | chat-seven-bit false | ||
17 | chat . "" | ||
18 | protocol ${node.protocols} | ||
19 | command-path ${concatStringsSep " " cfg.commandPath} | ||
20 | commands ${concatStringsSep " " node.commands} | ||
21 | ${concatStringsSep "\nalternate\n" (map (port: '' | ||
22 | port ${name}.${port} | ||
23 | '') node.hostnames)} | ||
24 | ''; | ||
25 | sshConfig = name: node: concatStringsSep "\n" (map (port: '' | ||
26 | Host ${name}.${port} | ||
27 | Hostname ${port} | ||
28 | IdentitiesOnly Yes | ||
29 | IdentityFile ${cfg.sshKeyDir}/${name} | ||
30 | '') node.hostnames); | ||
31 | sshKeyGen = name: node: '' | ||
32 | if [[ ! -e ${cfg.sshKeyDir}/${name} ]]; then | ||
33 | ${pkgs.openssh}/bin/ssh-keygen ${escapeShellArgs node.generateKey} -f ${cfg.sshKeyDir}/${name} | ||
34 | fi | ||
35 | ''; | ||
36 | restrictKey = key: '' | ||
37 | restrict,command="${chat}" ${key} | ||
38 | ''; | ||
39 | chat = pkgs.writeScript "chat" '' | ||
40 | #!${pkgs.stdenv.shell} | ||
41 | |||
42 | echo . | ||
43 | exec ${config.security.wrapperDir}/uucico | ||
44 | ''; | ||
45 | |||
46 | nodeCfg = { | ||
47 | options = { | ||
48 | commands = mkOption { | ||
49 | type = types.listOf types.str; | ||
50 | default = cfg.defaultCommands; | ||
51 | description = "Commands to allow for this remote"; | ||
52 | }; | ||
53 | |||
54 | protocols = mkOption { | ||
55 | type = types.separatedString ""; | ||
56 | default = cfg.defaultProtocols; | ||
57 | description = "UUCP protocols to use for this remote"; | ||
58 | }; | ||
59 | |||
60 | publicKeys = mkOption { | ||
61 | type = types.listOf types.str; | ||
62 | default = []; | ||
63 | description = "SSH client public keys for this node"; | ||
64 | }; | ||
65 | |||
66 | generateKey = mkOption { | ||
67 | type = types.listOf types.str; | ||
68 | default = [ "-t" "ed25519" "-N" "" ]; | ||
69 | description = "Arguments to pass to `ssh-keygen` to generate a keypair for communication with this host"; | ||
70 | }; | ||
71 | |||
72 | hostnames = mkOption { | ||
73 | type = types.listOf types.str; | ||
74 | default = []; | ||
75 | description = "Hostnames to try in order when connecting"; | ||
76 | }; | ||
77 | }; | ||
78 | }; | ||
79 | |||
80 | cfg = config.services.uucp; | ||
81 | in { | ||
82 | options = { | ||
83 | services.uucp = { | ||
84 | enable = mkOption { | ||
85 | type = types.bool; | ||
86 | default = false; | ||
87 | description = '' | ||
88 | If enabled we set up an account accesible via uucp over ssh | ||
89 | ''; | ||
90 | }; | ||
91 | |||
92 | nodeName = mkOption { | ||
93 | type = types.str; | ||
94 | default = "nixos"; | ||
95 | description = "uucp node name"; | ||
96 | }; | ||
97 | |||
98 | sshUser = mkOption { | ||
99 | type = types.attrs; | ||
100 | default = {}; | ||
101 | description = "Overrides for the local uucp linux-user"; | ||
102 | }; | ||
103 | |||
104 | extraSSHConfig = mkOption { | ||
105 | type = types.str; | ||
106 | default = ""; | ||
107 | description = "Extra SSH config"; | ||
108 | }; | ||
109 | |||
110 | remoteNodes = mkOption { | ||
111 | type = types.attrsOf (types.submodule nodeCfg); | ||
112 | default = {}; | ||
113 | description = '' | ||
114 | Ports to set up | ||
115 | Names will probably need to be configured in sshConfig | ||
116 | ''; | ||
117 | }; | ||
118 | |||
119 | commandPath = mkOption { | ||
120 | type = types.listOf types.path; | ||
121 | default = [ "${pkgs.rmail}/bin" ]; | ||
122 | description = '' | ||
123 | Command search path for all systems | ||
124 | ''; | ||
125 | }; | ||
126 | |||
127 | defaultCommands = mkOption { | ||
128 | type = types.listOf types.str; | ||
129 | default = ["rmail"]; | ||
130 | description = "Commands allowed for remotes without explicit override"; | ||
131 | }; | ||
132 | |||
133 | defaultProtocols = mkOption { | ||
134 | type = types.separatedString ""; | ||
135 | default = "te"; | ||
136 | description = "UUCP protocol to use within ssh unless overriden"; | ||
137 | }; | ||
138 | |||
139 | incomingProtocols = mkOption { | ||
140 | type = types.separatedString ""; | ||
141 | default = "te"; | ||
142 | description = "UUCP protocols to use when called"; | ||
143 | }; | ||
144 | |||
145 | homeDir = mkOption { | ||
146 | type = types.path; | ||
147 | default = "/var/uucp"; | ||
148 | description = "Home of the uucp user"; | ||
149 | }; | ||
150 | |||
151 | sshKeyDir = mkOption { | ||
152 | type = types.path; | ||
153 | default = "${cfg.homeDir}/.ssh/"; | ||
154 | description = "Directory to store ssh keypairs"; | ||
155 | }; | ||
156 | |||
157 | spoolDir = mkOption { | ||
158 | type = types.path; | ||
159 | default = "/var/spool/uucp"; | ||
160 | description = "Spool directory"; | ||
161 | }; | ||
162 | |||
163 | lockDir = mkOption { | ||
164 | type = types.path; | ||
165 | default = "/var/spool/uucp"; | ||
166 | description = "Lock directory"; | ||
167 | }; | ||
168 | |||
169 | pubDir = mkOption { | ||
170 | type = types.path; | ||
171 | default = "/var/spool/uucppublic"; | ||
172 | description = "Public directory"; | ||
173 | }; | ||
174 | |||
175 | logFile = mkOption { | ||
176 | type = types.path; | ||
177 | default = "/var/log/uucp"; | ||
178 | description = "Log file"; | ||
179 | }; | ||
180 | |||
181 | statFile = mkOption { | ||
182 | type = types.path; | ||
183 | default = "/var/log/uucp.stat"; | ||
184 | description = "Statistics file"; | ||
185 | }; | ||
186 | |||
187 | debugFile = mkOption { | ||
188 | type = types.path; | ||
189 | default = "/var/log/uucp.debug"; | ||
190 | description = "Debug file"; | ||
191 | }; | ||
192 | |||
193 | interval = mkOption { | ||
194 | type = types.nullOr types.str; | ||
195 | default = "1h"; | ||
196 | description = '' | ||
197 | Specification of when to run `uucico' in format used by systemd timers | ||
198 | The default is to do so every hour | ||
199 | ''; | ||
200 | }; | ||
201 | |||
202 | nmDispatch = mkOption { | ||
203 | type = types.bool; | ||
204 | default = config.networking.networkmanager.enable; | ||
205 | description = '' | ||
206 | Install a network-manager dispatcher script to automatically | ||
207 | call all remotes when networking is available | ||
208 | ''; | ||
209 | }; | ||
210 | |||
211 | extraConfig = mkOption { | ||
212 | type = types.lines; | ||
213 | default = '' | ||
214 | run-uuxqt 1 | ||
215 | ''; | ||
216 | description = "Extra configuration to append verbatim to `/etc/uucp/config'"; | ||
217 | }; | ||
218 | |||
219 | extraSys = mkOption { | ||
220 | type = types.lines; | ||
221 | default = '' | ||
222 | protocol-parameter g packet-size 4096 | ||
223 | ''; | ||
224 | description = "Extra configuration to prepend verbatim to `/etc/uucp/sys`"; | ||
225 | }; | ||
226 | }; | ||
227 | }; | ||
228 | |||
229 | config = mkIf cfg.enable { | ||
230 | environment.etc."uucp/config" = { | ||
231 | text = '' | ||
232 | hostname ${cfg.nodeName} | ||
233 | |||
234 | spool ${cfg.spoolDir} | ||
235 | lockdir ${cfg.lockDir} | ||
236 | pubdir ${cfg.pubDir} | ||
237 | logfile ${cfg.logFile} | ||
238 | statfile ${cfg.statFile} | ||
239 | debugfile ${cfg.debugFile} | ||
240 | |||
241 | ${cfg.extraConfig} | ||
242 | ''; | ||
243 | }; | ||
244 | |||
245 | users.users."uucp" = { | ||
246 | name = "uucp"; | ||
247 | isSystemUser = true; | ||
248 | isNormalUser = false; | ||
249 | createHome = true; | ||
250 | home = cfg.homeDir; | ||
251 | description = "User for uucp over ssh"; | ||
252 | useDefaultShell = true; | ||
253 | openssh.authorizedKeys.keys = map restrictKey (concatLists (mapAttrsToList (name: node: node.publicKeys) cfg.remoteNodes)); | ||
254 | } // cfg.sshUser; | ||
255 | |||
256 | system.activationScripts."uucp-sshconfig" = '' | ||
257 | mkdir -p ${config.users.users."uucp".home}/.ssh | ||
258 | chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${config.users.users."uucp".home}/.ssh | ||
259 | chmod 700 ${config.users.users."uucp".home}/.ssh | ||
260 | ln -fs ${builtins.toFile "ssh-config" '' | ||
261 | ${concatStringsSep "\n" (mapAttrsToList sshConfig cfg.remoteNodes)} | ||
262 | |||
263 | ${cfg.extraSSHConfig} | ||
264 | ''} ${config.users.users."uucp".home}/.ssh/config | ||
265 | |||
266 | mkdir -p ${cfg.sshKeyDir} | ||
267 | chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.sshKeyDir} | ||
268 | chmod 700 ${cfg.sshKeyDir} | ||
269 | |||
270 | ${concatStringsSep "\n" (mapAttrsToList sshKeyGen cfg.remoteNodes)} | ||
271 | ''; | ||
272 | |||
273 | system.activationScripts."uucp-logs" = '' | ||
274 | touch ${cfg.logFile} | ||
275 | chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.logFile} | ||
276 | chmod 644 ${cfg.logFile} | ||
277 | touch ${cfg.statFile} | ||
278 | chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.statFile} | ||
279 | chmod 644 ${cfg.statFile} | ||
280 | touch ${cfg.debugFile} | ||
281 | chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.debugFile} | ||
282 | chmod 644 ${cfg.debugFile} | ||
283 | ''; | ||
284 | |||
285 | environment.etc."uucp/port" = { | ||
286 | text = '' | ||
287 | port ssh | ||
288 | type stdin | ||
289 | protocol ${cfg.incomingProtocols} | ||
290 | '' + concatStringsSep "\n" (mapAttrsToList portSpec cfg.remoteNodes); | ||
291 | }; | ||
292 | environment.etc."uucp/sys" = { | ||
293 | text = cfg.extraSys + "\n" + concatStringsSep "\n" (mapAttrsToList sysSpec cfg.remoteNodes); | ||
294 | }; | ||
295 | |||
296 | security.wrappers = let | ||
297 | wrapper = p: { | ||
298 | name = p; | ||
299 | value = { | ||
300 | source = "${pkgs.uucp}/bin/${p}"; | ||
301 | owner = "root"; | ||
302 | group = "root"; | ||
303 | setuid = true; | ||
304 | setgid = false; | ||
305 | }; | ||
306 | }; | ||
307 | in listToAttrs (map wrapper ["uucico" "cu" "uucp" "uuname" "uustat" "uux" "uuxqt"]); | ||
308 | |||
309 | nixpkgs.overlays = [(self: super: { | ||
310 | uucp = super.stdenv.lib.overrideDerivation super.uucp (oldAttrs: { | ||
311 | configureFlags = "--with-newconfigdir=/etc/uucp"; | ||
312 | patches = [ | ||
313 | (super.writeText "mailprogram" '' | ||
314 | policy.h | 2 +- | ||
315 | 1 file changed, 1 insertion(+), 1 deletion(-) | ||
316 | |||
317 | diff --git a/policy.h b/policy.h | ||
318 | index 5afe34b..8e92c8b 100644 | ||
319 | --- a/policy.h | ||
320 | +++ b/policy.h | ||
321 | @@ -240,7 +240,7 @@ | ||
322 | the sendmail choice below. Otherwise, select one of the other | ||
323 | choices as appropriate. */ | ||
324 | #if 1 | ||
325 | -#define MAIL_PROGRAM "/usr/lib/sendmail -t" | ||
326 | +#define MAIL_PROGRAM "${config.security.wrapperDir}/sendmail -t" | ||
327 | /* #define MAIL_PROGRAM "/usr/sbin/sendmail -t" */ | ||
328 | #define MAIL_PROGRAM_TO_BODY 1 | ||
329 | #define MAIL_PROGRAM_SUBJECT_BODY 1 | ||
330 | '') | ||
331 | ]; | ||
332 | }); | ||
333 | rmail = super.writeScriptBin "rmail" '' | ||
334 | #!${super.stdenv.shell} | ||
335 | |||
336 | # Dummy UUCP rmail command for postfix/qmail systems | ||
337 | |||
338 | IFS=" " read junk from junk junk junk junk junk junk junk relay | ||
339 | |||
340 | case "$from" in | ||
341 | *[@!]*) ;; | ||
342 | *) from="$from@$relay";; | ||
343 | esac | ||
344 | |||
345 | exec ${config.security.wrapperDir}/sendmail -G -i -f "$from" -- "$@" | ||
346 | ''; | ||
347 | })]; | ||
348 | |||
349 | environment.systemPackages = with pkgs; [ | ||
350 | uucp | ||
351 | ]; | ||
352 | |||
353 | systemd.services."uucico@" = { | ||
354 | serviceConfig = { | ||
355 | User = "uucp"; | ||
356 | Type = "oneshot"; | ||
357 | ExecStart = "${config.security.wrapperDir}/uucico -D -S %i"; | ||
358 | }; | ||
359 | }; | ||
360 | |||
361 | systemd.timers."uucico@" = { | ||
362 | timerConfig.OnActiveSec = cfg.interval; | ||
363 | timerConfig.OnUnitActiveSec = cfg.interval; | ||
364 | }; | ||
365 | |||
366 | systemd.targets."multi-user" = { | ||
367 | wants = mapAttrsToList (name: node: "uucico@${name}.timer") cfg.remoteNodes; | ||
368 | }; | ||
369 | |||
370 | systemd.kill-user.enable = true; | ||
371 | systemd.targets."sleep" = { | ||
372 | after = [ "kill-user@uucp.service" ]; | ||
373 | wants = [ "kill-user@uucp.service" ]; | ||
374 | }; | ||
375 | |||
376 | networking.networkmanager.dispatcherScripts = optional cfg.nmDispatch { | ||
377 | type = "basic"; | ||
378 | source = pkgs.writeScript "callRemotes.sh" '' | ||
379 | #!${pkgs.stdenv.shell} | ||
380 | |||
381 | shopt -s extglob | ||
382 | |||
383 | case "''${2}" in | ||
384 | (?(vpn-)up) | ||
385 | ${concatStringsSep "\n " (mapAttrsToList (name: node: "${pkgs.systemd}/bin/systemctl start uucico@${name}.service") cfg.remoteNodes)} | ||
386 | ;; | ||
387 | esac | ||
388 | ''; | ||
389 | }; | ||
390 | }; | ||
391 | } | ||