{ config, lib, pkgs, ... }: with lib; let portSpec = name: node: concatStringsSep "\n" (map (port: '' port ${name}.${port} type pipe protocol ${node.protocols} reliable true command ${pkgs.openssh}/bin/ssh -x -o batchmode=yes ${name}.${port} '') node.hostnames); sysSpec = name: node: '' system ${name} time any chat-seven-bit false chat . "" protocol ${node.protocols} command-path ${concatStringsSep " " cfg.commandPath} commands ${concatStringsSep " " node.commands} ${concatStringsSep "\nalternate\n" (map (port: '' port ${name}.${port} '') node.hostnames)} ''; sshConfig = name: node: concatStringsSep "\n" (map (port: '' Host ${name}.${port} Hostname ${port} IdentitiesOnly Yes IdentityFile ${cfg.sshKeyDir}/${name} '') node.hostnames); sshKeyGen = name: node: '' if [[ ! -e ${cfg.sshKeyDir}/${name} ]]; then ${pkgs.openssh}/bin/ssh-keygen ${escapeShellArgs node.generateKey} -f ${cfg.sshKeyDir}/${name} fi ''; restrictKey = key: '' restrict,command="${chat}" ${key} ''; chat = pkgs.writeScript "chat" '' #!${pkgs.stdenv.shell} echo . exec ${config.security.wrapperDir}/uucico ''; nodeCfg = { options = { commands = mkOption { type = types.listOf types.string; default = cfg.defaultCommands; description = "Commands to allow for this remote"; }; protocols = mkOption { type = types.string; default = cfg.defaultProtocols; description = "UUCP protocols to use for this remote"; }; publicKeys = mkOption { type = types.listOf types.string; default = []; description = "SSH public keys for this node"; }; generateKey = mkOption { type = types.listOf types.string; default = [ "-t" "ed25519" "-N" "" ]; description = "Arguments to pass to `ssh-keygen` to generate a keypair for communication with this host"; }; hostnames = mkOption { type = types.listOf types.string; default = []; description = "Hostnames to try in order when connecting"; }; }; }; cfg = config.services.uucp; in { options = { services.uucp = { enable = mkOption { type = types.bool; default = false; description = '' If enabled we set up an account accesible via uucp over ssh ''; }; nodeName = mkOption { type = types.str; default = "nixos"; description = "uucp node name"; }; sshUser = mkOption { type = types.attrs; default = {}; description = "Overrides for the local uucp linux-user"; }; extraSSHConfig = mkOption { type = types.str; default = ""; description = "Extra SSH config"; }; remoteNodes = mkOption { type = types.attrsOf (types.submodule nodeCfg); default = {}; description = '' Ports to set up Names will probably need to be configured in sshConfig ''; }; commandPath = mkOption { type = types.listOf types.path; default = [ "${pkgs.rmail}/bin" ]; description = '' Command search path for all systems ''; }; defaultCommands = mkOption { type = types.listOf types.string; default = ["rmail"]; description = "Commands allowed for remotes without explicit override"; }; defaultProtocols = mkOption { type = types.string; default = "te"; description = "UUCP protocol to use within ssh unless overriden"; }; incomingProtocols = mkOption { type = types.string; default = "te"; description = "UUCP protocols to use when called"; }; homeDir = mkOption { type = types.path; default = "/var/uucp"; description = "Home of the uucp user"; }; sshKeyDir = mkOption { type = types.path; default = "${cfg.homeDir}/.ssh/"; description = "Directory to store ssh keypairs"; }; spoolDir = mkOption { type = types.path; default = "/var/spool/uucp"; description = "Spool directory"; }; lockDir = mkOption { type = types.path; default = "/var/spool/uucp"; description = "Lock directory"; }; pubDir = mkOption { type = types.path; default = "/var/spool/uucppublic"; description = "Public directory"; }; logFile = mkOption { type = types.path; default = "/var/log/uucp"; description = "Log file"; }; statFile = mkOption { type = types.path; default = "/var/log/uucp.stat"; description = "Statistics file"; }; debugFile = mkOption { type = types.path; default = "/var/log/uucp.debug"; description = "Debug file"; }; interval = mkOption { type = types.nullOr types.str; default = "1h"; description = '' Specification of when to run `uucico' in format used by systemd timers The default is to do so every hour ''; }; nmDispatch = mkOption { type = types.bool; default = config.networking.networkmanager.enable; description = '' Install a network-manager dispatcher script to automatically call all remotes when networking is available ''; }; extraConfig = mkOption { type = types.string; default = '' run-uuxqt 1 ''; description = "Extra configuration to append verbatim to `/etc/uucp/config'"; }; extraSys = mkOption { type = types.string; default = '' protocol-parameter g packet-size 4096 ''; description = "Extra configuration to prepend verbatim to `/etc/uucp/sys`"; }; }; }; config = mkIf cfg.enable { environment.etc."uucp/config" = { text = '' hostname ${cfg.nodeName} spool ${cfg.spoolDir} lockdir ${cfg.lockDir} pubdir ${cfg.pubDir} logfile ${cfg.logFile} statfile ${cfg.statFile} debugfile ${cfg.debugFile} ${cfg.extraConfig} ''; }; users.users."uucp" = { name = "uucp"; isSystemUser = true; isNormalUser = false; createHome = true; home = cfg.homeDir; description = "User for uucp over ssh"; useDefaultShell = true; openssh.authorizedKeys.keys = map restrictKey (concatLists (mapAttrsToList (name: node: node.publicKeys) cfg.remoteNodes)); } // cfg.sshUser; system.activationScripts."uucp-sshconfig" = '' mkdir -p ${config.users.users."uucp".home}/.ssh chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${config.users.users."uucp".home}/.ssh chmod 700 ${config.users.users."uucp".home}/.ssh ln -fs ${builtins.toFile "ssh-config" '' ${concatStringsSep "\n" (mapAttrsToList sshConfig cfg.remoteNodes)} ${cfg.extraSSHConfig} ''} ${config.users.users."uucp".home}/.ssh/config mkdir -p ${cfg.sshKeyDir} chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.sshKeyDir} chmod 700 ${cfg.sshKeyDir} ${concatStringsSep "\n" (mapAttrsToList sshKeyGen cfg.remoteNodes)} ''; system.activationScripts."uucp-logs" = '' touch ${cfg.logFile} chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.logFile} chmod 644 ${cfg.logFile} touch ${cfg.statFile} chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.statFile} chmod 644 ${cfg.statFile} touch ${cfg.debugFile} chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.debugFile} chmod 644 ${cfg.debugFile} ''; environment.etc."uucp/port" = { text = '' port ssh type stdin protocol ${cfg.incomingProtocols} '' + concatStringsSep "\n" (mapAttrsToList portSpec cfg.remoteNodes); }; environment.etc."uucp/sys" = { text = cfg.extraSys + "\n" + concatStringsSep "\n" (mapAttrsToList sysSpec cfg.remoteNodes); }; security.wrappers = let niceWrapper = p: { name = p; value = { source = pkgs.writeScript "${p}-nice" '' #!${pkgs.stdenv.shell} exec ${pkgs.coreutils}/bin/nice -n 15 ${pkgs.utillinux}/bin/ionice -c 3 ${pkgs.uucp}/bin/${p} $@ ''; owner = "root"; group = "root"; setuid = true; setgid = false; }; }; in listToAttrs (map niceWrapper ["uucico" "cu" "uucp" "uuname" "uustat" "uux" "uuxqt"]); nixpkgs.overlays = [(self: super: { uucp = super.stdenv.lib.overrideDerivation super.uucp (oldAttrs: { configureFlags = "--with-newconfigdir=/etc/uucp"; patches = [ (super.writeText "mailprogram" '' policy.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/policy.h b/policy.h index 5afe34b..8e92c8b 100644 --- a/policy.h +++ b/policy.h @@ -240,7 +240,7 @@ the sendmail choice below. Otherwise, select one of the other choices as appropriate. */ #if 1 -#define MAIL_PROGRAM "/usr/lib/sendmail -t" +#define MAIL_PROGRAM "${config.security.wrapperDir}/sendmail -t" /* #define MAIL_PROGRAM "/usr/sbin/sendmail -t" */ #define MAIL_PROGRAM_TO_BODY 1 #define MAIL_PROGRAM_SUBJECT_BODY 1 '') ]; }); rmail = super.writeScriptBin "rmail" '' #!${super.stdenv.shell} # Dummy UUCP rmail command for postfix/qmail systems IFS=" " read junk from junk junk junk junk junk junk junk relay case "$from" in *[@!]*) ;; *) from="$from@$relay";; esac exec ${config.security.wrapperDir}/sendmail -G -i -f "$from" -- "$@" ''; })]; environment.systemPackages = with pkgs; [ uucp ]; systemd.services."uucico@" = { serviceConfig = { User = "uucp"; Type = "oneshot"; ExecStart = "${config.security.wrapperDir}/uucico -D -S %i"; }; }; systemd.timers."uucico@" = { timerConfig.OnActiveSec = cfg.interval; timerConfig.OnUnitActiveSec = cfg.interval; }; systemd.targets."multi-user" = { wants = mapAttrsToList (name: node: "uucico@${name}.timer") cfg.remoteNodes; }; networking.networkmanager.dispatcherScripts = optional cfg.nmDispatch { type = "basic"; source = pkgs.writeScript "callRemotes.sh" '' #!${pkgs.stdenv.shell} shopt -s extglob case "''${2}" in (?(vpn-)up) ${concatStringsSep "\n " (mapAttrsToList (name: node: "${pkgs.systemd}/bin/systemctl start uucico@${name}.service") cfg.remoteNodes)} ;; esac ''; }; }; }