Merge "Add command to close SSH connection"
diff --git a/Documentation/cmd-close-connection.txt b/Documentation/cmd-close-connection.txt
new file mode 100644
index 0000000..3314326
--- /dev/null
+++ b/Documentation/cmd-close-connection.txt
@@ -0,0 +1,38 @@
+= gerrit close-connection
+
+== NAME
+gerrit close-connection - Close the specified SSH connection
+
+== SYNOPSIS
+--
+'ssh' -p <port> <host> 'gerrit close-connection' <SESSION_ID>
+   [--wait]
+--
+
+== DESCRIPTION
+Close an SSH connection.
+
+The connection closing is done asynchronously by default. Use `--wait` option to
+wait for connection to close.
+
+An error message will be displayed if no connection with the specified session
+ID is found.
+
+== ACCESS
+Caller must be a member of the privileged 'Administrators' group.
+
+== SCRIPTING
+Intended for interactive use only.
+
+OPTIONS
+-------
+
+`--wait`
+:	Wait for connection to close before exiting.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index 22d65ad..7c664ac 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -96,6 +96,9 @@
 [[admin_commands]]Administrator Commands
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+link:cmd-close-connection.html[gerrit close-connection]::
+	Close the specified SSH connection.
+
 link:cmd-create-account.html[gerrit create-account]::
 	Create a new user account.
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CloseConnection.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CloseConnection.java
new file mode 100644
index 0000000..91dd1db
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CloseConnection.java
@@ -0,0 +1,97 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd.commands;
+
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.sshd.AdminHighPriorityCommand;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.gerrit.sshd.SshDaemon;
+import com.google.gerrit.sshd.SshSession;
+import com.google.inject.Inject;
+
+import org.apache.sshd.common.io.IoAcceptor;
+import org.apache.sshd.common.io.IoCloseFuture;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.server.session.ServerSession;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Close specified SSH connections */
+@AdminHighPriorityCommand
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@CommandMetaData(name = "close-connection",
+  description = "Close the specified SSH connection", runsAt = MASTER_OR_SLAVE)
+final class CloseConnection extends SshCommand {
+
+  private static final Logger log = LoggerFactory.getLogger(CloseConnection.class);
+
+  @Inject
+  private SshDaemon sshDaemon;
+
+  @Argument(index = 0, multiValued = true, required = true,
+      metaVar = "SESSION_ID", usage = "List of SSH session IDs to be closed")
+  private final List<String> sessionIds = new ArrayList<>();
+
+  @Option(name = "--wait",
+      usage = "wait for connection to close before exiting")
+  private boolean wait;
+
+  @Override
+  protected void run() throws Failure {
+    IoAcceptor acceptor = sshDaemon.getIoAcceptor();
+    if (acceptor == null) {
+      throw new Failure(1, "fatal: sshd no longer running");
+    }
+    for (String sessionId : sessionIds) {
+      boolean connectionFound = false;
+      int id = (int) Long.parseLong(sessionId, 16);
+      for (IoSession io : acceptor.getManagedSessions().values()) {
+        ServerSession serverSession =
+            (ServerSession) ServerSession.getSession(io, true);
+        SshSession sshSession =
+            serverSession != null
+                ? serverSession.getAttribute(SshSession.KEY)
+                : null;
+        if (sshSession != null && sshSession.getSessionId() == id) {
+          connectionFound = true;
+          stdout.println("closing connection " + sessionId + "...");
+          IoCloseFuture future = io.close(true);
+          if (wait) {
+            try {
+              future.await();
+              stdout.println("closed connection " + sessionId);
+            } catch (InterruptedException e) {
+              log.warn("Wait for connection to close interrupted: "
+                  + e.getMessage());
+            }
+          }
+          break;
+        }
+      }
+      if (!connectionFound) {
+        stderr.print("close connection " + sessionId + ": no such connection\n");
+      }
+    }
+  }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index cd20f2a..f75eb2b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -38,6 +38,7 @@
     command(gerrit).toProvider(new DispatchCommandProvider(gerrit));
     command(gerrit, AproposCommand.class);
     command(gerrit, BanCommitCommand.class);
+    command(gerrit, CloseConnection.class);
     command(gerrit, FlushCaches.class);
     command(gerrit, ListProjectsCommand.class);
     command(gerrit, ListMembersCommand.class);