summaryrefslogtreecommitdiff
path: root/modules/uucp.nix
diff options
context:
space:
mode:
Diffstat (limited to 'modules/uucp.nix')
-rw-r--r--modules/uucp.nix391
1 files changed, 391 insertions, 0 deletions
diff --git a/modules/uucp.nix b/modules/uucp.nix
new file mode 100644
index 00000000..0334a3db
--- /dev/null
+++ b/modules/uucp.nix
@@ -0,0 +1,391 @@
1{ flake, config, lib, pkgs, ... }:
2
3with lib;
4
5let
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;
81in {
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.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}