diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index a66e38b104..12a9bb3649 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -181,6 +181,17 @@ is per-user, so 1024 items translates to 1024 unique user accounts.
As each individual user account may configure multiple SSH keys,
the total number of keys may be larger than the item count.
+Section sshd
+~~~~~~~~~~~~
+
+sshd.reuseAddress::
++
+If true, permits the daemon to bind to the port even if the port
+is already in use. If false, the daemon ensures the port is not
+in use before starting. Busy sites may need to set this to true
+to permit fast restarts.
++
+By default, true.
File `replication.config`
-------------------------
diff --git a/src/main/java/com/google/gerrit/server/GerritServer.java b/src/main/java/com/google/gerrit/server/GerritServer.java
index 9e2efeb0f5..19729fc9ec 100644
--- a/src/main/java/com/google/gerrit/server/GerritServer.java
+++ b/src/main/java/com/google/gerrit/server/GerritServer.java
@@ -771,6 +771,11 @@ public class GerritServer {
return u;
}
+ /** Get the parsed $site_path/gerrit.config
file. */
+ public RepositoryConfig getGerritConfig() {
+ return gerritConfigFile;
+ }
+
/** Get the repositories maintained by this server. */
public RepositoryCache getRepositoryCache() {
return repositories;
diff --git a/src/main/java/com/google/gerrit/server/ssh/GerritSshDaemon.java b/src/main/java/com/google/gerrit/server/ssh/GerritSshDaemon.java
index b6201b6483..a02e0629f7 100644
--- a/src/main/java/com/google/gerrit/server/ssh/GerritSshDaemon.java
+++ b/src/main/java/com/google/gerrit/server/ssh/GerritSshDaemon.java
@@ -19,24 +19,50 @@ import com.google.gerrit.server.GerritServer;
import com.google.gwtjsonrpc.server.XsrfException;
import com.google.gwtorm.client.OrmException;
+import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IoSession;
+import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.apache.sshd.SshServer;
+import org.apache.sshd.common.Cipher;
import org.apache.sshd.common.Compression;
+import org.apache.sshd.common.KeyExchange;
import org.apache.sshd.common.KeyPairProvider;
+import org.apache.sshd.common.Mac;
import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.Signature;
+import org.apache.sshd.common.cipher.AES128CBC;
+import org.apache.sshd.common.cipher.AES192CBC;
+import org.apache.sshd.common.cipher.AES256CBC;
+import org.apache.sshd.common.cipher.BlowfishCBC;
+import org.apache.sshd.common.cipher.TripleDESCBC;
import org.apache.sshd.common.compression.CompressionNone;
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+import org.apache.sshd.common.mac.HMACMD5;
+import org.apache.sshd.common.mac.HMACMD596;
+import org.apache.sshd.common.mac.HMACSHA1;
+import org.apache.sshd.common.mac.HMACSHA196;
+import org.apache.sshd.common.random.BouncyCastleRandom;
+import org.apache.sshd.common.random.JceRandom;
+import org.apache.sshd.common.random.SingletonRandomFactory;
import org.apache.sshd.common.session.AbstractSession;
+import org.apache.sshd.common.signature.SignatureDSA;
+import org.apache.sshd.common.signature.SignatureRSA;
import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.server.ServerChannel;
import org.apache.sshd.server.SessionFactory;
import org.apache.sshd.server.UserAuth;
import org.apache.sshd.server.auth.UserAuthPublicKey;
+import org.apache.sshd.server.channel.ChannelSession;
+import org.apache.sshd.server.kex.DHG1;
+import org.apache.sshd.server.kex.DHG14;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.spearce.jgit.lib.RepositoryConfig;
import java.io.File;
import java.io.IOException;
+import java.net.InetSocketAddress;
import java.net.SocketException;
import java.security.KeyPair;
import java.security.PublicKey;
@@ -63,80 +89,33 @@ import java.util.List;
* Port 8010
*
*/
-public class GerritSshDaemon {
+public class GerritSshDaemon extends SshServer {
private static final Logger log =
LoggerFactory.getLogger(GerritSshDaemon.class);
- private static SshServer sshd;
+ private static GerritSshDaemon sshd;
private static Collection hostKeys = Collections.emptyList();
public static synchronized void startSshd() throws OrmException,
XsrfException, SocketException {
final GerritServer srv = GerritServer.getInstance();
- final int myPort = Common.getGerritConfig().getSshdPort();
- final SshServer daemon = SshServer.setUpDefaultServer();
- daemon.setPort(myPort);
- daemon.setSessionFactory(new SessionFactory() {
- @Override
- protected AbstractSession createSession(final IoSession ioSession)
- throws Exception {
- final AbstractSession s = super.createSession(ioSession);
- s.setAttribute(SshUtil.REMOTE_PEER, ioSession.getRemoteAddress());
- return s;
- }
- });
-
- final File sitePath = srv.getSitePath();
- if (SecurityUtils.isBouncyCastleRegistered()) {
- daemon.setKeyPairProvider(new FileKeyPairProvider(new String[] {
- new File(sitePath, "ssh_host_rsa_key").getAbsolutePath(),
- new File(sitePath, "ssh_host_dsa_key").getAbsolutePath()}));
- } else {
- final SimpleGeneratorHostKeyProvider keyp;
-
- keyp = new SimpleGeneratorHostKeyProvider();
- keyp.setPath(new File(sitePath, "ssh_host_key").getAbsolutePath());
- daemon.setKeyPairProvider(keyp);
- }
-
- setCompression(daemon);
- setUserAuth(daemon);
- daemon.setPublickeyAuthenticator(new DatabasePubKeyAuth(srv));
- daemon.setCommandFactory(new GerritCommandFactory());
- daemon.setShellFactory(new NoShell());
-
+ final GerritSshDaemon daemon = new GerritSshDaemon(srv);
try {
sshd = daemon;
daemon.start();
hostKeys = computeHostKeys();
- log.info("Started Gerrit SSHD on 0.0.0.0:" + myPort);
+ log.info("Started Gerrit SSHD on 0.0.0.0:" + daemon.getPort());
} catch (IOException e) {
- log.error("Cannot start Gerrit SSHD on 0.0.0.0:" + myPort, e);
+ log.error("Cannot start Gerrit SSHD on 0.0.0.0:" + daemon.getPort(), e);
sshd = null;
hostKeys = Collections.emptyList();
final SocketException e2;
- e2 = new SocketException("Cannot start sshd on " + myPort);
+ e2 = new SocketException("Cannot start sshd on " + daemon.getPort());
e2.initCause(e);
throw e2;
}
}
- @SuppressWarnings("unchecked")
- private static void setCompression(final SshServer daemon) {
- // Always disable transparent compression. The majority of our data
- // transfer is highly compressed Git pack files. We cannot make them
- // any smaller than they already are.
- //
- daemon.setCompressionFactories(Arrays
- .> asList(new CompressionNone.Factory()));
- }
-
- @SuppressWarnings("unchecked")
- private static void setUserAuth(final SshServer daemon) {
- daemon.setUserAuthFactories(Arrays
- .> asList(new UserAuthPublicKey.Factory()));
- }
-
private static Collection computeHostKeys() {
final KeyPairProvider p = sshd.getKeyPairProvider();
final List keys = new ArrayList(2);
@@ -172,4 +151,140 @@ public class GerritSshDaemon {
public static synchronized Collection getHostKeys() {
return hostKeys;
}
+
+ private IoAcceptor acceptor;
+ private boolean reuseAddress;
+
+ private GerritSshDaemon(final GerritServer srv) {
+ setPort(Common.getGerritConfig().getSshdPort());
+
+ final RepositoryConfig cfg = srv.getGerritConfig();
+ reuseAddress = cfg.getBoolean("sshd", "reuseaddress", true);
+
+ if (SecurityUtils.isBouncyCastleRegistered()) {
+ initProviderBouncyCastle();
+ } else {
+ initProviderJce();
+ }
+ initCipers();
+ initMac();
+ initSignatures();
+ initChannels();
+ initCompression();
+ initUserAuth(srv);
+ initHostKey(srv);
+ setCommandFactory(new GerritCommandFactory());
+ setShellFactory(new NoShell());
+ setSessionFactory(new SessionFactory() {
+ @Override
+ protected AbstractSession createSession(final IoSession ioSession)
+ throws Exception {
+ final AbstractSession s = super.createSession(ioSession);
+ s.setAttribute(SshUtil.REMOTE_PEER, ioSession.getRemoteAddress());
+ return s;
+ }
+ });
+ }
+
+ @Override
+ public void start() throws IOException {
+ if (acceptor == null) {
+ checkConfig();
+
+ final NioSocketAcceptor ain = new NioSocketAcceptor();
+ final SessionFactory handler = getSessionFactory();
+ handler.setServer(this);
+ ain.setHandler(handler);
+ ain.setReuseAddress(reuseAddress);
+ ain.bind(new InetSocketAddress(getPort()));
+ acceptor = ain;
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (acceptor != null) {
+ try {
+ acceptor.dispose();
+ } finally {
+ acceptor = null;
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void initProviderBouncyCastle() {
+ setKeyExchangeFactories(Arrays.> asList(
+ new DHG14.Factory(), new DHG1.Factory()));
+ setRandomFactory(new SingletonRandomFactory(
+ new BouncyCastleRandom.Factory()));
+ }
+
+ @SuppressWarnings("unchecked")
+ private void initProviderJce() {
+ setKeyExchangeFactories(Arrays
+ .> asList(new DHG1.Factory()));
+ setRandomFactory(new SingletonRandomFactory(new JceRandom.Factory()));
+ }
+
+ @SuppressWarnings("unchecked")
+ private void initCipers() {
+ setCipherFactories(Arrays.> asList(
+ new AES128CBC.Factory(), new TripleDESCBC.Factory(),
+ new BlowfishCBC.Factory(), new AES192CBC.Factory(),
+ new AES256CBC.Factory()));
+ }
+
+ @SuppressWarnings("unchecked")
+ private void initMac() {
+ setMacFactories(Arrays.> asList(new HMACMD5.Factory(),
+ new HMACSHA1.Factory(), new HMACMD596.Factory(),
+ new HMACSHA196.Factory()));
+ }
+
+ @SuppressWarnings("unchecked")
+ private void initSignatures() {
+ setSignatureFactories(Arrays.> asList(
+ new SignatureDSA.Factory(), new SignatureRSA.Factory()));
+ }
+
+ @SuppressWarnings("unchecked")
+ private void initCompression() {
+ // Always disable transparent compression. The majority of our data
+ // transfer is highly compressed Git pack files. We cannot make them
+ // any smaller than they already are.
+ //
+ setCompressionFactories(Arrays
+ .> asList(new CompressionNone.Factory()));
+ }
+
+ @SuppressWarnings("unchecked")
+ private void initChannels() {
+ setChannelFactories(Arrays
+ .> asList(new ChannelSession.Factory()));
+ }
+
+ @SuppressWarnings("unchecked")
+ private void initUserAuth(final GerritServer srv) {
+ setUserAuthFactories(Arrays
+ .> asList(new UserAuthPublicKey.Factory()));
+ setPublickeyAuthenticator(new DatabasePubKeyAuth(srv));
+ }
+
+ private void initHostKey(final GerritServer srv) {
+ final File sitePath = srv.getSitePath();
+
+ if (SecurityUtils.isBouncyCastleRegistered()) {
+ setKeyPairProvider(new FileKeyPairProvider(new String[] {
+ new File(sitePath, "ssh_host_rsa_key").getAbsolutePath(),
+ new File(sitePath, "ssh_host_dsa_key").getAbsolutePath()}));
+
+ } else {
+ final SimpleGeneratorHostKeyProvider keyp;
+
+ keyp = new SimpleGeneratorHostKeyProvider();
+ keyp.setPath(new File(sitePath, "ssh_host_key").getAbsolutePath());
+ setKeyPairProvider(keyp);
+ }
+ }
}