{ config, lib, pkgs, ... }:
with lib;
let
  cfg = config.security.grsecurity;
  customGrsecPkg =
    (import ../../../pkgs/build-support/grsecurity {
      grsecOptions = cfg;
      inherit pkgs lib;
    }).grsecPackage;
in
{
  options = {
    security.grsecurity = {
      enable = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Enable grsecurity support. This enables advanced exploit
          hardening for the Linux kernel, and adds support for
          administrative Role-Based Acess Control (RBAC) via
          gradm. It also includes traditional
          utilities for PaX.
        '';
      };
      stable = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Enable the stable grsecurity patch, based on Linux 3.14.
        '';
      };
      testing = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Enable the testing grsecurity patch, based on Linux 3.17.
        '';
      };
      config = {
        mode = mkOption {
          type = types.str;
          default = "auto";
          example = "custom";
          description = ''
            grsecurity configuration mode. This specifies whether
            grsecurity is auto-configured or otherwise completely
            manually configured. Can either be
            custom or auto.
            auto is recommended.
          '';
        };
        priority = mkOption {
          type = types.str;
          default = "security";
          example = "performance";
          description = ''
            grsecurity configuration priority. This specifies whether
            the kernel configuration should emphasize speed or
            security. Can either be security or
            performance.
          '';
        };
        system = mkOption {
          type = types.str;
          default = "";
          example = "desktop";
          description = ''
            grsecurity system configuration. This specifies whether
            the kernel configuration should be suitable for a Desktop
            or a Server. Can either be server or
            desktop.
          '';
        };
        virtualisationConfig = mkOption {
          type = types.str;
          default = "none";
          example = "host";
          description = ''
            grsecurity virtualisation configuration. This specifies
            the virtualisation role of the machine - that is, whether
            it will be a virtual machine guest, a virtual machine
            host, or neither. Can be one of none,
            host, or guest.
          '';
        };
        hardwareVirtualisation = mkOption {
          type = types.nullOr types.bool;
          default = null;
          example = true;
          description = ''
            grsecurity hardware virtualisation configuration. Set to
            true if your machine supports hardware
            accelerated virtualisation.
          '';
        };
        virtualisationSoftware = mkOption {
          type = types.str;
          default = "";
          example = "kvm";
          description = ''
            grsecurity virtualisation software. Set this to the
            specified virtual machine technology if the machine is
            running as a guest, or a host.
            Can be one of kvm,
            xen, vmware or
            virtualbox.
          '';
        };
        sysctl = mkOption {
          type = types.bool;
          default = false;
          description = ''
            If true, then set GRKERN_SYSCTL y. If
            enabled then grsecurity can be controlled using sysctl
            (and turned off). You are advised to *never* enable this,
            but if you do, make sure to always set the sysctl
            kernel.grsecurity.grsec_lock to
            non-zero as soon as all sysctl options are set. *THIS IS
            EXTREMELY IMPORTANT*!
            If disabled, this also turns off the
            systemd-sysctl service.
          '';
        };
        denyChrootChmod = mkOption {
          type = types.bool;
          default = false;
          description = ''
            If true, then set GRKERN_CHROOT_CHMOD
            y. If enabled, this denies processes inside a
            chroot from setting the suid or sgid bits using
            chmod or fchmod.
            By default this protection is disabled - it makes it
            impossible to use Nix to build software on your system,
            which is what most users want.
            If you are using NixOps to deploy your software to a
            remote machine, you're encouraged to enable this as you
            won't need to compile code.
          '';
        };
        restrictProc = mkOption {
          type = types.bool;
          default = false;
          description = ''
            If true, then set GRKERN_PROC_USER
            y. This restricts non-root users to only viewing
            their own processes and restricts network-related
            information, kernel symbols, and module information.
          '';
        };
        restrictProcWithGroup = mkOption {
          type = types.bool;
          default = true;
          description = ''
            If true, then set GRKERN_PROC_USERGROUP
            y. This is similar to
            restrictProc except it allows a special
            group (specified by unrestrictProcGid)
            to still access otherwise classified information in
            /proc.
          '';
        };
        unrestrictProcGid = mkOption {
          type = types.int;
          default = config.ids.gids.grsecurity;
          description = ''
            If set, specifies a GID which is exempt from
            /proc restrictions (set by
            GRKERN_PROC_USERGROUP). By default,
            this is set to the GID for grsecurity,
            a predefined NixOS group, which the
            root account is a member of. You may
            conveniently add other users to this group if you need
            access to /proc
          '';
        };
        disableRBAC = mkOption {
          type = types.bool;
          default = false;
          description = ''
            If true, then set GRKERN_NO_RBAC
            y. This disables the
            /dev/grsec device, which in turn
            disables the RBAC system (and gradm).
          '';
        };
        verboseVersion = mkOption {
          type = types.bool;
          default = false;
          description = "Use verbose version in kernel localversion.";
        };
        kernelExtraConfig = mkOption {
          type = types.str;
          default = "";
          description = "Extra kernel configuration parameters.";
        };
      };
    };
  };
  config = mkIf cfg.enable {
    assertions =
      [ { assertion = cfg.stable || cfg.testing;
          message   = ''
            If grsecurity is enabled, you must select either the
            stable patch (with kernel 3.14), or the testing patch (with
            kernel 3.17) to continue.
          '';
        }
        { assertion = (cfg.stable -> !cfg.testing) || (cfg.testing -> !cfg.stable);
          message   = ''
            You must select either the stable or testing patch, not
            both.
          '';
        }
        { assertion = (cfg.config.restrictProc -> !cfg.config.restrictProcWithGroup) ||
                      (cfg.config.restrictProcWithGroup -> !cfg.config.restrictProc);
          message   = "You cannot enable both restrictProc and restrictProcWithGroup";
        }
        { assertion = config.boot.kernelPackages.kernel.features ? grsecurity
                   && config.boot.kernelPackages.kernel.features.grsecurity;
          message = "grsecurity enabled, but kernel doesn't have grsec support";
        }
        { assertion = elem cfg.config.mode [ "auto" "custom" ];
          message = "grsecurity mode must either be 'auto' or 'custom'.";
        }
        { assertion = cfg.config.mode == "auto" -> elem cfg.config.system [ "desktop" "server" ];
          message = "when using auto grsec mode, system must be either 'desktop' or 'server'";
        }
        { assertion = cfg.config.mode == "auto" -> elem cfg.config.priority [ "performance" "security" ];
          message = "when using auto grsec mode, priority must be 'performance' or 'security'.";
        }
        { assertion = cfg.config.mode == "auto" -> elem cfg.config.virtualisationConfig [ "host" "guest" "none" ];
          message = "when using auto grsec mode, 'virt' must be 'host', 'guest' or 'none'.";
        }
        { assertion = (cfg.config.mode == "auto" && (elem cfg.config.virtualisationConfig [ "host" "guest" ])) ->
              cfg.config.hardwareVirtualisation != null;
          message   = "when using auto grsec mode with virtualisation, you must specify if your hardware has virtualisation extensions";
        }
        { assertion = (cfg.config.mode == "auto" && (elem cfg.config.virtualisationConfig [ "host" "guest" ])) ->
              elem cfg.config.virtualisationSoftware [ "kvm" "xen" "virtualbox" "vmware" ];
          message   = "virtualisation software must be 'kvm', 'xen', 'vmware' or 'virtualbox'";
        }
      ];
    systemd.services.grsec-lock = mkIf cfg.config.sysctl {
      description     = "grsecurity sysctl-lock Service";
      requires        = [ "sysctl.service" ];
      wantedBy        = [ "multi-user.target" ];
      serviceConfig.Type = "oneshot";
      serviceConfig.RemainAfterExit = "yes";
      script = ''
        locked=`cat /proc/sys/kernel/grsecurity/grsec_lock`
        if [ "$locked" == "0" ]; then
            echo 1 > /proc/sys/kernel/grsecurity/grsec_lock
            echo grsecurity sysctl lock - enabled
        else
            echo grsecurity sysctl lock already enabled - doing nothing
        fi
      '';
    };
#   systemd.services.grsec-learn = {
#     description     = "grsecurity learning Service";
#     wantedBy        = [ "local-fs.target" ];
#     serviceConfig   = {
#       Type = "oneshot";
#       RemainAfterExit = "yes";
#       ExecStart = "${pkgs.gradm}/sbin/gradm -VFL /etc/grsec/learning.logs";
#       ExecStop  = "${pkgs.gradm}/sbin/gradm -D";
#     };
#   };
    system.activationScripts.grsec =
      ''
        mkdir -p /etc/grsec
        if [ ! -f /etc/grsec/learn_config ]; then
          cp ${pkgs.gradm}/etc/grsec/learn_config /etc/grsec
        fi
        if [ ! -f /etc/grsec/policy ]; then
          cp ${pkgs.gradm}/etc/grsec/policy /etc/grsec
        fi
        chmod -R 0600 /etc/grsec
      '';
    # Enable AppArmor, gradm udev rules, and utilities
    security.apparmor.enable   = true;
    boot.kernelPackages        = customGrsecPkg;
    services.udev.packages     = [ pkgs.gradm ];
    environment.systemPackages = [ pkgs.gradm pkgs.paxctl pkgs.pax-utils ];
  };
}