From bbbd3d6cd89cc797fab9245decca62e05eb994b7 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Fri, 5 Feb 2021 17:43:24 +0100
Subject: [PATCH 1/5] krebs.setuid: disambiguate `config`

---
 krebs/3modules/setuid.nix | 22 ++++++++++------------
 1 file changed, 10 insertions(+), 12 deletions(-)

diff --git a/krebs/3modules/setuid.nix b/krebs/3modules/setuid.nix
index 97cf21cdd..3e068736b 100644
--- a/krebs/3modules/setuid.nix
+++ b/krebs/3modules/setuid.nix
@@ -1,11 +1,9 @@
-{ config, pkgs, lib, ... }:
 with import <stockholm/lib>;
-let
-  cfg = config.krebs.setuid;
+{ config, pkgs, ... }: let
 
   out = {
     options.krebs.setuid = api;
-    config = mkIf (cfg != {}) imp;
+    config = mkIf (config.krebs.setuid != {}) imp;
   };
 
   api = mkOption {
@@ -14,11 +12,11 @@ let
       # TODO make wrapperDir configurable
       inherit (config.security) wrapperDir;
       inherit (config.users) groups users;
-    in types.attrsOf (types.submodule ({ config, ... }: {
+    in types.attrsOf (types.submodule (self: let cfg = self.config; in {
       options = {
         name = mkOption {
           type = types.filename;
-          default = config._module.args.name;
+          default = cfg._module.args.name;
         };
         envp = mkOption {
           type = types.nullOr (types.attrsOf types.str);
@@ -58,21 +56,21 @@ let
         };
       };
       config.activate = let
-        src = pkgs.exec config.name {
-          inherit (config) envp filename;
+        src = pkgs.exec cfg.name {
+          inherit (cfg) envp filename;
         };
-        dst = "${wrapperDir}/${config.name}";
+        dst = "${wrapperDir}/${cfg.name}";
       in ''
         cp ${src} ${dst}
-        chown ${config.owner}.${config.group} ${dst}
-        chmod ${config.mode} ${dst}
+        chown ${cfg.owner}.${cfg.group} ${dst}
+        chmod ${cfg.mode} ${dst}
       '';
     }));
   };
 
   imp = {
     system.activationScripts."krebs.setuid" = stringAfter [ "wrappers" ]
-      (concatMapStringsSep "\n" (getAttr "activate") (attrValues cfg));
+      (concatMapStringsSep "\n" (getAttr "activate") (attrValues config.krebs.setuid));
   };
 
 in out

From 315dcf3cbff0980495c0899a38ecdf538651dabc Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Fri, 5 Feb 2021 17:48:54 +0100
Subject: [PATCH 2/5] krebs.setuid: make wrapperDir configurable

---
 krebs/3modules/setuid.nix | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/krebs/3modules/setuid.nix b/krebs/3modules/setuid.nix
index 3e068736b..64fedb911 100644
--- a/krebs/3modules/setuid.nix
+++ b/krebs/3modules/setuid.nix
@@ -9,8 +9,6 @@ with import <stockholm/lib>;
   api = mkOption {
     default = {};
     type = let
-      # TODO make wrapperDir configurable
-      inherit (config.security) wrapperDir;
       inherit (config.users) groups users;
     in types.attrsOf (types.submodule (self: let cfg = self.config; in {
       options = {
@@ -49,6 +47,10 @@ with import <stockholm/lib>;
             merge = mergeOneOption;
           };
         };
+        wrapperDir = mkOption {
+          default = config.security.wrapperDir;
+          type = types.absolute-pathname;
+        };
         activate = mkOption {
           type = types.str;
           visible = false;
@@ -59,8 +61,9 @@ with import <stockholm/lib>;
         src = pkgs.exec cfg.name {
           inherit (cfg) envp filename;
         };
-        dst = "${wrapperDir}/${cfg.name}";
+        dst = "${cfg.wrapperDir}/${cfg.name}";
       in ''
+        mkdir -p ${cfg.wrapperDir}
         cp ${src} ${dst}
         chown ${cfg.owner}.${cfg.group} ${dst}
         chmod ${cfg.mode} ${dst}

From 1ff4a60b8d241230c580fc5e9a705335c9c415a6 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Fri, 5 Feb 2021 19:52:07 +0100
Subject: [PATCH 3/5] krebs.shadow: admit password changes

---
 krebs/3modules/shadow.nix | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/krebs/3modules/shadow.nix b/krebs/3modules/shadow.nix
index cff66492d..9505efb0c 100644
--- a/krebs/3modules/shadow.nix
+++ b/krebs/3modules/shadow.nix
@@ -4,19 +4,21 @@ with import <stockholm/lib>;
   cfg = config.krebs.shadow;
 
   mergeShadowsJq = pkgs.writeJq "merge-shadows.jq" ''
-    def fields_3_to_9: ["1", "", "", "", "", "", ""];
+    def is_int: . == (. | floor);
+    def fields_4_to_9: ["", "", "", "", "", ""];
+    def check_fields_3_to_9: (.[2] | tonumber | is_int) and .[3:] == fields_4_to_9;
 
     def read_value:
       split(":") |
       if length == 9 then
-        if .[2:] == fields_3_to_9 then
+        if check_fields_3_to_9 then
           .
         else
           error("unrecognized field contents")
         end
       elif length == 2 then
         if .[1] | test("^\\$6\\$") then
-          . + fields_3_to_9
+          . + ["1"] + fields_4_to_9
         else
           error("unrecognized hashed password")
         end

From a94d2ce6c7e83ecb0337affd91200c4774ba7920 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Sun, 7 Feb 2021 21:52:37 +0100
Subject: [PATCH 4/5] tv xdpytools: init

---
 tv/5pkgs/simple/xdpytools/default.nix  | 31 ++++++++++++++++
 tv/5pkgs/simple/xdpytools/src/xdpychvt | 11 ++++++
 tv/5pkgs/simple/xdpytools/src/xdpysel  | 49 ++++++++++++++++++++++++++
 3 files changed, 91 insertions(+)
 create mode 100644 tv/5pkgs/simple/xdpytools/default.nix
 create mode 100755 tv/5pkgs/simple/xdpytools/src/xdpychvt
 create mode 100755 tv/5pkgs/simple/xdpytools/src/xdpysel

diff --git a/tv/5pkgs/simple/xdpytools/default.nix b/tv/5pkgs/simple/xdpytools/default.nix
new file mode 100644
index 000000000..7d1ee071e
--- /dev/null
+++ b/tv/5pkgs/simple/xdpytools/default.nix
@@ -0,0 +1,31 @@
+{ lib, pkgs }:
+
+let
+  install = name: { path }: /* sh */ ''
+    (
+      mkdir -p $out/bin
+      touch $out/bin/${name}
+      chmod +x $out/bin/${name}
+      exec >$out/bin/${name}
+
+      echo '#! ${pkgs.dash}/bin/dash'
+      echo export PATH=${lib.makeBinPath path}
+      sed 1d ${./src + "/${name}"}
+    )
+  '';
+in
+
+pkgs.runCommand "xdpytools" {}
+  (toString
+    (lib.mapAttrsToList install {
+      xdpychvt.path = [
+        "$out"
+        "/run/wrappers/'$LOGNAME'"
+        "/run/wrappers"
+      ];
+      xdpysel.path = [
+        "$out"
+        pkgs.findutils
+        pkgs.jq
+      ];
+    }))
diff --git a/tv/5pkgs/simple/xdpytools/src/xdpychvt b/tv/5pkgs/simple/xdpytools/src/xdpychvt
new file mode 100755
index 000000000..84c1907b9
--- /dev/null
+++ b/tv/5pkgs/simple/xdpytools/src/xdpychvt
@@ -0,0 +1,11 @@
+#! /bin/sh
+# usage: xdpychvt {prev,next}
+# Changes to the VT based on the selected X display.
+#
+# This allows switching between X servers when display names and VT numbers
+# correlate.  A more sophisticated tool would try to determine the correct VT
+# by e.g. looking at /proc, but this might not possible when e.g.  using
+# security.hideProcessInformation.
+#
+
+chvt "$(xdpysel "$1")"
diff --git a/tv/5pkgs/simple/xdpytools/src/xdpysel b/tv/5pkgs/simple/xdpytools/src/xdpysel
new file mode 100755
index 000000000..e08015576
--- /dev/null
+++ b/tv/5pkgs/simple/xdpytools/src/xdpysel
@@ -0,0 +1,49 @@
+#! /bin/sh
+# usage: xdpysel {prev,next}
+# Print the number of the selected X display.
+
+find /tmp/.X11-unix -mindepth 1 -maxdepth 1 |
+jq -Rrs --arg command "$1" '
+  (
+    split("\n") |
+    map(
+      select(.!="") |
+      match("^.*/X([0-9]+)$").captures[0].string |
+      tonumber
+    )
+  )
+    as $all_displays |
+
+  (
+    env.DISPLAY |
+    match("^:([0-9]+)(?:[.][0-9]+)?$").captures[0].string |
+    tonumber
+  )
+    as $current_display |
+
+  ($all_displays | length) as $all_displays_count |
+
+  ($all_displays|index($current_display))
+    as $current_index |
+
+  (($current_index + 1) % $all_displays_count)
+    as $next_index |
+
+  (($all_displays_count + $current_index - 1) % $all_displays_count)
+    as $prev_index |
+
+  $all_displays[$prev_index] as $prev_display |
+  $all_displays[$next_index] as $next_display |
+
+  {
+    prev: $prev_display,
+    next: $next_display,
+  }[$command]
+    as $result |
+
+  if $result | type == "number" then
+    $result
+  else
+    "xdpysel: bad argument: \($command)\n" | halt_error(-1)
+  end
+'

From bda725bbfc4a4e1ecf8a8fd8d3dbff69b5cf4d60 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Sun, 7 Feb 2021 22:28:50 +0100
Subject: [PATCH 5/5] tv xmonad: integrate xdpychvt

---
 tv/5pkgs/haskell/xmonad-tv/src/Paths.hs | 3 +++
 tv/5pkgs/haskell/xmonad-tv/src/main.hs  | 3 +++
 2 files changed, 6 insertions(+)

diff --git a/tv/5pkgs/haskell/xmonad-tv/src/Paths.hs b/tv/5pkgs/haskell/xmonad-tv/src/Paths.hs
index 6b7235530..b2ad01ae7 100644
--- a/tv/5pkgs/haskell/xmonad-tv/src/Paths.hs
+++ b/tv/5pkgs/haskell/xmonad-tv/src/Paths.hs
@@ -29,3 +29,6 @@ urxvtc = findExecutable "urxvtc"
 
 xcalib :: FilePath
 xcalib = findExecutable "xcalib"
+
+xdpychvt :: FilePath
+xdpychvt = findExecutable "xdpychvt"
diff --git a/tv/5pkgs/haskell/xmonad-tv/src/main.hs b/tv/5pkgs/haskell/xmonad-tv/src/main.hs
index 50b03d81c..e5a4473fe 100644
--- a/tv/5pkgs/haskell/xmonad-tv/src/main.hs
+++ b/tv/5pkgs/haskell/xmonad-tv/src/main.hs
@@ -190,6 +190,9 @@ myKeys conf = Map.fromList $
     , ((_4, xK_Prior), forkFile Paths.xcalib ["-invert", "-alter"] Nothing)
 
     , ((0, xK_Print), forkFile Paths.flameshot [] Nothing)
+
+    , ((_C, xF86XK_Forward), forkFile Paths.xdpychvt ["next"] Nothing)
+    , ((_C, xF86XK_Back), forkFile Paths.xdpychvt ["prev"] Nothing)
     ]
     where
     _4 = mod4Mask