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);