From a21c97d0113ebc43983ff688b2d9abddd07d6f6e Mon Sep 17 00:00:00 2001
From: Damien Cassou <damien@cassou.me>
Date: Mon, 1 Jun 2020 16:50:34 +0200
Subject: [PATCH] ssh: add support for ServerAliveCountMax

PR #1299
---
 modules/misc/news.nix                         | 15 ++++++++++++
 modules/programs/ssh.nix                      | 23 ++++++++++++++++++-
 .../programs/ssh/default-config-expected.conf |  1 +
 ...ynamic-valid-bind-no-asserts-expected.conf |  1 +
 .../ssh/match-blocks-attrs-expected.conf      |  2 ++
 .../programs/ssh/match-blocks-attrs.nix       |  1 +
 6 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/modules/misc/news.nix b/modules/misc/news.nix
index 933446aa1..4f962eadd 100644
--- a/modules/misc/news.nix
+++ b/modules/misc/news.nix
@@ -1527,6 +1527,21 @@ in
           A new module is available: 'programs.zoxide'
         '';
       }
+
+      {
+        time = "2020-06-03T17:46:11+00:00";
+        condition = config.programs.ssh.enable;
+        message = ''
+          The ssh module now supports the 'ServerAliveCountMax' option
+          both globally through
+
+              programs.ssh.serverAliveCountMax
+
+          and per match blocks
+
+              programs.ssh.matchBlocks.<name>.serverAliveCountMax
+        '';
+      }
     ];
   };
 }
diff --git a/modules/programs/ssh.nix b/modules/programs/ssh.nix
index 95d4edc4b..ae1f22180 100644
--- a/modules/programs/ssh.nix
+++ b/modules/programs/ssh.nix
@@ -143,6 +143,15 @@ let
           "Set timeout in seconds after which response will be requested.";
       };
 
+      serverAliveCountMax = mkOption {
+        type = types.ints.positive;
+        default = 3;
+        description = ''
+          Sets the number of server alive messages which may be sent
+          without SSH receiving any messages back from the server.
+        '';
+      };
+
       sendEnv = mkOption {
         type = types.listOf types.str;
         default = [];
@@ -281,7 +290,9 @@ let
     ++ optional (cf.addressFamily != null)   "  AddressFamily ${cf.addressFamily}"
     ++ optional (cf.sendEnv != [])           "  SendEnv ${unwords cf.sendEnv}"
     ++ optional (cf.serverAliveInterval != 0)
-         "  ServerAliveInterval ${toString cf.serverAliveInterval}"
+      "  ServerAliveInterval ${toString cf.serverAliveInterval}"
+    ++ optional (cf.serverAliveCountMax != 3)
+      "  ServerAliveCountMax ${toString cf.serverAliveCountMax}"
     ++ optional (cf.compression != null)     "  Compression ${yn cf.compression}"
     ++ optional (!cf.checkHostIP)            "  CheckHostIP no"
     ++ optional (cf.proxyCommand != null)    "  ProxyCommand ${cf.proxyCommand}"
@@ -325,6 +336,15 @@ in
       '';
     };
 
+    serverAliveCountMax = mkOption {
+      type = types.ints.positive;
+      default = 3;
+      description = ''
+        Sets the default number of server alive messages which may be
+        sent without SSH receiving any messages back from the server.
+      '';
+    };
+
     hashKnownHosts = mkOption {
       default = false;
       type = types.bool;
@@ -459,6 +479,7 @@ in
         ForwardAgent ${yn cfg.forwardAgent}
         Compression ${yn cfg.compression}
         ServerAliveInterval ${toString cfg.serverAliveInterval}
+        ServerAliveCountMax ${toString cfg.serverAliveCountMax}
         HashKnownHosts ${yn cfg.hashKnownHosts}
         UserKnownHostsFile ${cfg.userKnownHostsFile}
         ControlMaster ${cfg.controlMaster}
diff --git a/tests/modules/programs/ssh/default-config-expected.conf b/tests/modules/programs/ssh/default-config-expected.conf
index 55748ea6c..6885392b2 100644
--- a/tests/modules/programs/ssh/default-config-expected.conf
+++ b/tests/modules/programs/ssh/default-config-expected.conf
@@ -6,6 +6,7 @@ Host *
   ForwardAgent no
   Compression no
   ServerAliveInterval 0
+  ServerAliveCountMax 3
   HashKnownHosts no
   UserKnownHostsFile ~/.ssh/known_hosts
   ControlMaster no
diff --git a/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts-expected.conf b/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts-expected.conf
index 5213d282c..02268e8cb 100644
--- a/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts-expected.conf
+++ b/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts-expected.conf
@@ -10,6 +10,7 @@ Host *
   ForwardAgent no
   Compression no
   ServerAliveInterval 0
+  ServerAliveCountMax 3
   HashKnownHosts no
   UserKnownHostsFile ~/.ssh/known_hosts
   ControlMaster no
diff --git a/tests/modules/programs/ssh/match-blocks-attrs-expected.conf b/tests/modules/programs/ssh/match-blocks-attrs-expected.conf
index 131918161..e2c329015 100644
--- a/tests/modules/programs/ssh/match-blocks-attrs-expected.conf
+++ b/tests/modules/programs/ssh/match-blocks-attrs-expected.conf
@@ -10,6 +10,7 @@ Host abc
 
 Host xyz
   ServerAliveInterval 60
+  ServerAliveCountMax 10
   IdentityFile file
   LocalForward [localhost]:8080 [10.0.0.1]:80
   RemoteForward [localhost]:8081 [10.0.0.2]:80
@@ -23,6 +24,7 @@ Host *
   ForwardAgent no
   Compression no
   ServerAliveInterval 0
+  ServerAliveCountMax 3
   HashKnownHosts no
   UserKnownHostsFile ~/.ssh/known_hosts
   ControlMaster no
diff --git a/tests/modules/programs/ssh/match-blocks-attrs.nix b/tests/modules/programs/ssh/match-blocks-attrs.nix
index 1b6f0ae01..eaa20c6e3 100644
--- a/tests/modules/programs/ssh/match-blocks-attrs.nix
+++ b/tests/modules/programs/ssh/match-blocks-attrs.nix
@@ -17,6 +17,7 @@ with lib;
         xyz = {
           identityFile = "file";
           serverAliveInterval = 60;
+          serverAliveCountMax = 10;
           localForwards = [{
             bind.port = 8080;
             host.address = "10.0.0.1";