{ config, lib, pkgs, ... }:

with lib;

let
  portSpec = name: ''
    port ${name}
    type pipe
    protocol ${if builtins.hasAttr name config.services.uucp.protocols then config.services.uucp.protocols."${name}" else config.services.uucp.defaultProtocol}
    reliable true
    command ${pkgs.openssh}/bin/ssh -x -o batchmode=yes ${name}
  '';
  sysSpec = name: ''
    system ${name}
    time Any
    port ${name}
    chat ""
    protocol ${if builtins.hasAttr name config.services.uucp.protocols then config.services.uucp.protocols."${name}" else config.services.uucp.defaultProtocol}
    command-path ${concatStringsSep " " config.services.uucp.commandPath}
    commands ${concatStringsSep " " (if builtins.hasAttr name config.services.uucp.commands then config.services.uucp.commands."${name}" else config.services.uucp.defaultCommands)}
  '';
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";
      };

      sshConfig = mkOption {
        type = types.str;
        default = "";
        description = "~uucp/.ssh/config";
      };

      remoteNodes = mkOption {
        type = types.listOf types.str;
        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";
      };

      commands = mkOption {
        type = types.attrsOf (types.listOf types.string);
        default = {};
        description = "Override commands for specific remotes";
      };

      defaultProtocol = mkOption {
        type = types.string;
	default = "e";
	description = "UUCP protocol to use within ssh unless overriden";
      };

      protocols = mkOption {
        type = types.attrsOf types.string;
	default = {};
	description = "UUCP protocols to use for specific remotes";
      };

      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 = "00 * * * *";
        description = ''
          Specification of when to run `uucico' in format used by cron
          The default is to do so every hour
        '';
      };

      extraConfig = mkOption {
        type = types.string;
        default = "";
        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 config.services.uucp.enable {
    environment.etc."uucp/config" = {
      text = ''
        hostname ${config.services.uucp.nodeName}
      
        spool ${config.services.uucp.spoolDir}
        lockdir ${config.services.uucp.lockDir}
        pubdir ${config.services.uucp.pubDir}
        logfile ${config.services.uucp.logFile}
        statfile ${config.services.uucp.statFile}
        debugfile ${config.services.uucp.debugFile}
        
        ${config.services.uucp.extraConfig}
      '';
    };

    users.users."uucp" = {
      name = "uucp";
      isSystemUser = true;
      isNormalUser = false;
      createHome = true;
      home = config.services.uucp.spoolDir;
      description = "User for uucp over ssh";
      useDefaultShell = true;
    } // config.services.uucp.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" config.services.uucp.sshConfig} ${config.users.users."uucp".home}/.ssh/config
    '';

    system.activationScripts."uucp-logs" = ''
      touch ${config.services.uucp.logFile}
      chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${config.services.uucp.logFile}
      chmod 644 ${config.services.uucp.logFile}
      touch ${config.services.uucp.statFile}
      chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${config.services.uucp.statFile}
      chmod 644 ${config.services.uucp.statFile}
      touch ${config.services.uucp.debugFile}
      chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${config.services.uucp.debugFile}
      chmod 644 ${config.services.uucp.debugFile}
    '';

    environment.etc."uucp/port" = {
      text = ''
        port ssh
        type stdin
        protocol e
      '' + concatStringsSep "\n" (map portSpec config.services.uucp.remoteNodes);
    };
    environment.etc."uucp/sys" = {
      text = config.services.uucp.extraSys + "\n" + concatStringsSep "\n" (map sysSpec config.services.uucp.remoteNodes);
    };

    security.setuidOwners = map (p: {program = p; owner = "root"; group = "root"; setuid = true; setgid = false;}) ["uucico" "uuxqt" "cu" "uucp" "uuname" "uustat" "uux"];

    nixpkgs.config.packageOverrides = pkgs: with pkgs; {
      uucp = stdenv.lib.overrideDerivation uucp (oldAttrs: {
        configureFlags = "--with-newconfigdir=/etc/uucp";
        patches = [
          (pkgs.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 "/var/setuid-wrappers/sendmail -t"
             /* #define MAIL_PROGRAM "/usr/sbin/sendmail -t" */
             #define MAIL_PROGRAM_TO_BODY 1
             #define MAIL_PROGRAM_SUBJECT_BODY 1
          '')
        ];
      });
      rmail = pkgs.writeScriptBin "rmail" ''
          #!${pkgs.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 /var/setuid-wrappers/sendmail -i -f "$from" -- "$@"
        '';
    };

    environment.systemPackages = with pkgs; [
      uucp
    ];

    services.cron.systemCronJobs = (map (name: "${config.services.uucp.interval} /var/setuid-wrappers/uucico -D -S ${name}") (if (config.services.uucp.interval != null) then config.services.uucp.remoteNodes else []));
  };
}