LCOV - code coverage report
Current view: top level - sshd - SshDaemon.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 261 349 74.8 %
Date: 2022-11-19 15:00:39 Functions: 41 49 83.7 %

          Line data    Source code
       1             : // Copyright (C) 2008 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.sshd;
      16             : 
      17             : import static com.google.gerrit.server.ssh.SshAddressesModule.IANA_SSH_PORT;
      18             : import static java.util.concurrent.TimeUnit.MILLISECONDS;
      19             : import static java.util.concurrent.TimeUnit.SECONDS;
      20             : import static org.apache.sshd.core.CoreModuleProperties.AUTH_TIMEOUT;
      21             : import static org.apache.sshd.core.CoreModuleProperties.IDLE_TIMEOUT;
      22             : import static org.apache.sshd.core.CoreModuleProperties.MAX_AUTH_REQUESTS;
      23             : import static org.apache.sshd.core.CoreModuleProperties.MAX_CONCURRENT_SESSIONS;
      24             : import static org.apache.sshd.core.CoreModuleProperties.NIO2_READ_TIMEOUT;
      25             : import static org.apache.sshd.core.CoreModuleProperties.REKEY_BYTES_LIMIT;
      26             : import static org.apache.sshd.core.CoreModuleProperties.REKEY_TIME_LIMIT;
      27             : import static org.apache.sshd.core.CoreModuleProperties.SERVER_IDENTIFICATION;
      28             : import static org.apache.sshd.core.CoreModuleProperties.WAIT_FOR_SPACE_TIMEOUT;
      29             : 
      30             : import com.google.common.base.Strings;
      31             : import com.google.common.collect.Iterables;
      32             : import com.google.common.flogger.FluentLogger;
      33             : import com.google.gerrit.common.Version;
      34             : import com.google.gerrit.extensions.events.LifecycleListener;
      35             : import com.google.gerrit.metrics.Counter0;
      36             : import com.google.gerrit.metrics.Description;
      37             : import com.google.gerrit.metrics.MetricMaker;
      38             : import com.google.gerrit.server.config.ConfigUtil;
      39             : import com.google.gerrit.server.config.GerritServerConfig;
      40             : import com.google.gerrit.server.ssh.HostKey;
      41             : import com.google.gerrit.server.ssh.SshAdvertisedAddresses;
      42             : import com.google.gerrit.server.ssh.SshInfo;
      43             : import com.google.gerrit.server.ssh.SshListenAddresses;
      44             : import com.google.gerrit.server.util.IdGenerator;
      45             : import com.google.gerrit.server.util.SocketUtil;
      46             : import com.google.inject.Inject;
      47             : import com.google.inject.Singleton;
      48             : import java.io.File;
      49             : import java.io.IOException;
      50             : import java.net.InetAddress;
      51             : import java.net.InetSocketAddress;
      52             : import java.net.SocketAddress;
      53             : import java.net.UnknownHostException;
      54             : import java.security.GeneralSecurityException;
      55             : import java.security.InvalidKeyException;
      56             : import java.security.KeyPair;
      57             : import java.security.PublicKey;
      58             : import java.time.Duration;
      59             : import java.util.ArrayList;
      60             : import java.util.Arrays;
      61             : import java.util.Collection;
      62             : import java.util.Collections;
      63             : import java.util.Iterator;
      64             : import java.util.List;
      65             : import java.util.concurrent.CountDownLatch;
      66             : import java.util.concurrent.ExecutorService;
      67             : import java.util.concurrent.TimeUnit;
      68             : import java.util.concurrent.atomic.AtomicInteger;
      69             : import org.apache.mina.transport.socket.SocketSessionConfig;
      70             : import org.apache.sshd.common.BaseBuilder;
      71             : import org.apache.sshd.common.NamedFactory;
      72             : import org.apache.sshd.common.NamedResource;
      73             : import org.apache.sshd.common.cipher.Cipher;
      74             : import org.apache.sshd.common.compression.BuiltinCompressions;
      75             : import org.apache.sshd.common.compression.Compression;
      76             : import org.apache.sshd.common.file.nonefs.NoneFileSystemFactory;
      77             : import org.apache.sshd.common.forward.DefaultForwarderFactory;
      78             : import org.apache.sshd.common.future.CloseFuture;
      79             : import org.apache.sshd.common.future.SshFutureListener;
      80             : import org.apache.sshd.common.io.AbstractIoServiceFactory;
      81             : import org.apache.sshd.common.io.IoAcceptor;
      82             : import org.apache.sshd.common.io.IoServiceFactory;
      83             : import org.apache.sshd.common.io.IoServiceFactoryFactory;
      84             : import org.apache.sshd.common.io.IoSession;
      85             : import org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory;
      86             : import org.apache.sshd.common.kex.BuiltinDHFactories;
      87             : import org.apache.sshd.common.kex.KeyExchangeFactory;
      88             : import org.apache.sshd.common.kex.extension.DefaultServerKexExtensionHandler;
      89             : import org.apache.sshd.common.keyprovider.KeyPairProvider;
      90             : import org.apache.sshd.common.mac.Mac;
      91             : import org.apache.sshd.common.random.Random;
      92             : import org.apache.sshd.common.random.SingletonRandomFactory;
      93             : import org.apache.sshd.common.session.Session;
      94             : import org.apache.sshd.common.session.helpers.AbstractSession;
      95             : import org.apache.sshd.common.session.helpers.DefaultUnknownChannelReferenceHandler;
      96             : import org.apache.sshd.common.util.buffer.Buffer;
      97             : import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
      98             : import org.apache.sshd.common.util.net.SshdSocketAddress;
      99             : import org.apache.sshd.common.util.security.SecurityUtils;
     100             : import org.apache.sshd.mina.MinaServiceFactoryFactory;
     101             : import org.apache.sshd.mina.MinaSession;
     102             : import org.apache.sshd.server.ServerBuilder;
     103             : import org.apache.sshd.server.SshServer;
     104             : import org.apache.sshd.server.auth.UserAuthFactory;
     105             : import org.apache.sshd.server.auth.gss.GSSAuthenticator;
     106             : import org.apache.sshd.server.auth.gss.UserAuthGSSFactory;
     107             : import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
     108             : import org.apache.sshd.server.auth.pubkey.UserAuthPublicKeyFactory;
     109             : import org.apache.sshd.server.command.CommandFactory;
     110             : import org.apache.sshd.server.forward.ForwardingFilter;
     111             : import org.apache.sshd.server.global.CancelTcpipForwardHandler;
     112             : import org.apache.sshd.server.global.KeepAliveHandler;
     113             : import org.apache.sshd.server.global.NoMoreSessionsHandler;
     114             : import org.apache.sshd.server.global.TcpipForwardHandler;
     115             : import org.apache.sshd.server.session.ServerSessionImpl;
     116             : import org.apache.sshd.server.session.SessionFactory;
     117             : import org.bouncycastle.crypto.prng.RandomGenerator;
     118             : import org.bouncycastle.crypto.prng.VMPCRandomGenerator;
     119             : import org.eclipse.jgit.lib.Config;
     120             : 
     121             : /**
     122             :  * SSH daemon to communicate with Gerrit.
     123             :  *
     124             :  * <p>Use a Git URL such as <code>ssh://${email}@${host}:${port}/${path}</code>, e.g. {@code
     125             :  * ssh://sop@google.com@gerrit.com:8010/tools/gerrit.git} to access the SSH daemon itself.
     126             :  *
     127             :  * <p>Versions of Git before 1.5.3 may require setting the username and port properties in the
     128             :  * user's {@code ~/.ssh/config} file, and using a host alias through a URL such as {@code
     129             :  * gerrit-alias:/tools/gerrit.git}:
     130             :  *
     131             :  * <pre>{@code
     132             :  * Host gerrit-alias
     133             :  *  User sop@google.com
     134             :  *  Hostname gerrit.com
     135             :  *  Port 8010
     136             :  * }</pre>
     137             :  */
     138             : @Singleton
     139             : public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
     140          17 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
     141             : 
     142          17 :   public enum SshSessionBackend {
     143          17 :     MINA,
     144          17 :     NIO2
     145             :   }
     146             : 
     147             :   private final List<SocketAddress> listen;
     148             :   private final List<String> advertised;
     149             :   private final boolean keepAlive;
     150             :   private final List<HostKey> hostKeys;
     151             :   private volatile IoAcceptor daemonAcceptor;
     152             :   private final Config cfg;
     153             :   private final long gracefulStopTimeout;
     154             : 
     155             :   @Inject
     156             :   SshDaemon(
     157             :       CommandFactory commandFactory,
     158             :       NoShell noShell,
     159             :       PublickeyAuthenticator userAuth,
     160             :       GerritGSSAuthenticator kerberosAuth,
     161             :       KeyPairProvider hostKeyProvider,
     162             :       IdGenerator idGenerator,
     163             :       @GerritServerConfig Config cfg,
     164             :       SshLog sshLog,
     165             :       @SshListenAddresses List<SocketAddress> listen,
     166             :       @SshAdvertisedAddresses List<String> advertised,
     167             :       MetricMaker metricMaker,
     168          17 :       LogMaxConnectionsPerUserExceeded logMaxConnectionsPerUserExceeded) {
     169          17 :     setPort(IANA_SSH_PORT /* never used */);
     170             : 
     171          17 :     this.cfg = cfg;
     172          17 :     this.listen = listen;
     173          17 :     this.advertised = advertised;
     174          17 :     keepAlive = cfg.getBoolean("sshd", "tcpkeepalive", true);
     175             : 
     176          17 :     SERVER_IDENTIFICATION.set(
     177             :         this,
     178             :         "GerritCodeReview_"
     179          17 :             + Version.getVersion() //
     180             :             + " ("
     181          17 :             + super.getVersion()
     182             :             + ")");
     183          17 :     MAX_AUTH_REQUESTS.set(this, cfg.getInt("sshd", "maxAuthTries", 6));
     184          17 :     AUTH_TIMEOUT.set(
     185             :         this,
     186          17 :         Duration.ofSeconds(
     187          17 :             MILLISECONDS.convert(
     188          17 :                 ConfigUtil.getTimeUnit(cfg, "sshd", null, "loginGraceTime", 120, SECONDS),
     189             :                 SECONDS)));
     190             : 
     191          17 :     long idleTimeoutSeconds = ConfigUtil.getTimeUnit(cfg, "sshd", null, "idleTimeout", 0, SECONDS);
     192          17 :     IDLE_TIMEOUT.set(this, Duration.ofSeconds(SECONDS.toMillis(idleTimeoutSeconds)));
     193          17 :     NIO2_READ_TIMEOUT.set(this, Duration.ofSeconds(SECONDS.toMillis(idleTimeoutSeconds)));
     194             : 
     195          17 :     long rekeyTimeLimit =
     196          17 :         ConfigUtil.getTimeUnit(cfg, "sshd", null, "rekeyTimeLimit", 3600, SECONDS);
     197          17 :     REKEY_TIME_LIMIT.set(this, Duration.ofSeconds(SECONDS.toMillis(rekeyTimeLimit)));
     198             : 
     199          17 :     REKEY_BYTES_LIMIT.set(
     200          17 :         this, cfg.getLong("sshd", "rekeyBytesLimit", 1024 * 1024 * 1024 /* 1GB */));
     201             : 
     202          17 :     long waitTimeoutSeconds = ConfigUtil.getTimeUnit(cfg, "sshd", null, "waitTimeout", 30, SECONDS);
     203          17 :     WAIT_FOR_SPACE_TIMEOUT.set(this, Duration.ofSeconds(SECONDS.toMillis(waitTimeoutSeconds)));
     204             : 
     205          17 :     final int maxConnectionsPerUser = cfg.getInt("sshd", "maxConnectionsPerUser", 64);
     206          17 :     if (0 < maxConnectionsPerUser) {
     207          17 :       MAX_CONCURRENT_SESSIONS.set(this, maxConnectionsPerUser);
     208             :     }
     209             : 
     210          17 :     final String kerberosKeytab = cfg.getString("sshd", null, "kerberosKeytab");
     211          17 :     final String kerberosPrincipal = cfg.getString("sshd", null, "kerberosPrincipal");
     212             : 
     213          17 :     final boolean enableCompression = cfg.getBoolean("sshd", "enableCompression", false);
     214             : 
     215          17 :     SshSessionBackend backend = cfg.getEnum("sshd", null, "backend", SshSessionBackend.NIO2);
     216          17 :     boolean channelIdTracking = cfg.getBoolean("sshd", "enableChannelIdTracking", true);
     217             : 
     218          17 :     gracefulStopTimeout = cfg.getTimeUnit("sshd", null, "gracefulStopTimeout", 0, TimeUnit.SECONDS);
     219             : 
     220          17 :     System.setProperty(
     221          17 :         IoServiceFactoryFactory.class.getName(),
     222          17 :         backend == SshSessionBackend.MINA
     223           0 :             ? MinaServiceFactoryFactory.class.getName()
     224          17 :             : Nio2ServiceFactoryFactory.class.getName());
     225             : 
     226          17 :     initProviderBouncyCastle(cfg);
     227          17 :     initCiphers(cfg);
     228          17 :     initKeyExchanges(cfg);
     229          17 :     initMacs(cfg);
     230          17 :     initSignatures();
     231          17 :     initChannels();
     232          17 :     initUnknownChannelReferenceHandler(channelIdTracking);
     233          17 :     initForwarding();
     234          17 :     initFileSystemFactory();
     235          17 :     initSubsystems();
     236          17 :     initCompression(enableCompression);
     237          17 :     initUserAuth(userAuth, kerberosAuth, kerberosKeytab, kerberosPrincipal);
     238          17 :     setKeyPairProvider(hostKeyProvider);
     239          17 :     setCommandFactory(commandFactory);
     240          17 :     setShellFactory(noShell);
     241          17 :     setSessionDisconnectHandler(logMaxConnectionsPerUserExceeded);
     242             : 
     243          17 :     final AtomicInteger connected = new AtomicInteger();
     244          17 :     metricMaker.newCallbackMetric(
     245             :         "sshd/sessions/connected",
     246             :         Integer.class,
     247          17 :         new Description("Currently connected SSH sessions").setGauge().setUnit("sessions"),
     248          17 :         connected::get);
     249             : 
     250          17 :     final Counter0 sessionsCreated =
     251          17 :         metricMaker.newCounter(
     252             :             "sshd/sessions/created",
     253          17 :             new Description("Rate of new SSH sessions").setRate().setUnit("sessions"));
     254             : 
     255          17 :     final Counter0 authFailures =
     256          17 :         metricMaker.newCounter(
     257             :             "sshd/sessions/authentication_failures",
     258          17 :             new Description("Rate of SSH authentication failures").setRate().setUnit("failures"));
     259             : 
     260          17 :     setSessionFactory(
     261          17 :         new SessionFactory(this) {
     262             :           @Override
     263             :           protected ServerSessionImpl createSession(IoSession io) throws Exception {
     264          17 :             connected.incrementAndGet();
     265          17 :             sessionsCreated.increment();
     266          17 :             if (io instanceof MinaSession) {
     267           0 :               if (((MinaSession) io).getSession().getConfig() instanceof SocketSessionConfig) {
     268           0 :                 ((SocketSessionConfig) ((MinaSession) io).getSession().getConfig())
     269           0 :                     .setKeepAlive(keepAlive);
     270             :               }
     271             :             }
     272             : 
     273          17 :             ServerSessionImpl s = super.createSession(io);
     274          17 :             int id = idGenerator.next();
     275          17 :             SocketAddress peer = io.getRemoteAddress();
     276          17 :             final SshSession sd = new SshSession(id, peer);
     277          17 :             s.setAttribute(SshSession.KEY, sd);
     278             : 
     279             :             // Log a session close without authentication as a failure.
     280             :             //
     281          17 :             s.addCloseFutureListener(
     282             :                 future -> {
     283          17 :                   connected.decrementAndGet();
     284          17 :                   if (sd.isAuthenticationError()) {
     285           1 :                     authFailures.increment();
     286           1 :                     sshLog.onAuthFail(sd);
     287             :                   }
     288          17 :                 });
     289          17 :             return s;
     290             :           }
     291             : 
     292             :           @Override
     293             :           protected ServerSessionImpl doCreateSession(IoSession ioSession) throws Exception {
     294          17 :             return new ServerSessionImpl(getServer(), ioSession);
     295             :           }
     296             :         });
     297          17 :     setGlobalRequestHandlers(
     298          17 :         Arrays.asList(
     299             :             new KeepAliveHandler(),
     300             :             new NoMoreSessionsHandler(),
     301             :             new TcpipForwardHandler(),
     302             :             new CancelTcpipForwardHandler()));
     303             : 
     304          17 :     hostKeys = computeHostKeys();
     305          17 :   }
     306             : 
     307             :   @Override
     308             :   public List<HostKey> getHostKeys() {
     309          12 :     return hostKeys;
     310             :   }
     311             : 
     312             :   public IoAcceptor getIoAcceptor() {
     313           1 :     return daemonAcceptor;
     314             :   }
     315             : 
     316             :   @Override
     317             :   public synchronized void start() {
     318          17 :     if (daemonAcceptor == null && !listen.isEmpty()) {
     319          17 :       checkConfig();
     320          17 :       if (getSessionFactory() == null) {
     321           0 :         setSessionFactory(createSessionFactory());
     322             :       }
     323          17 :       setupSessionTimeout(getSessionFactory());
     324          17 :       daemonAcceptor = createAcceptor();
     325             : 
     326             :       try {
     327          17 :         String listenAddress = cfg.getString("sshd", null, "listenAddress");
     328          17 :         boolean rewrite = !Strings.isNullOrEmpty(listenAddress) && listenAddress.endsWith(":0");
     329          17 :         daemonAcceptor.bind(listen);
     330          17 :         if (rewrite) {
     331          17 :           SocketAddress bound = Iterables.getOnlyElement(daemonAcceptor.getBoundAddresses());
     332          17 :           cfg.setString("sshd", null, "listenAddress", format((InetSocketAddress) bound));
     333             :         }
     334           0 :       } catch (IOException e) {
     335           0 :         throw new IllegalStateException("Cannot bind to " + addressList(), e);
     336          17 :       }
     337             : 
     338          17 :       logger.atInfo().log("Started Gerrit %s on %s", getVersion(), addressList());
     339             :     }
     340          17 :   }
     341             : 
     342             :   private static String format(InetSocketAddress s) {
     343          17 :     return String.format("%s:%d", s.getAddress().getHostAddress(), s.getPort());
     344             :   }
     345             : 
     346             :   @Override
     347             :   public synchronized void stop() {
     348          17 :     if (daemonAcceptor != null) {
     349             :       try {
     350          17 :         if (gracefulStopTimeout > 0) {
     351           1 :           logger.atInfo().log(
     352             :               "Stopping SSHD sessions gracefully with %d seconds timeout.", gracefulStopTimeout);
     353           1 :           daemonAcceptor.unbind(daemonAcceptor.getBoundAddresses());
     354           1 :           waitForSessionClose();
     355             :         }
     356          17 :         daemonAcceptor.close(true).await();
     357          17 :         shutdownExecutors();
     358          17 :         logger.atInfo().log("Stopped Gerrit SSHD");
     359           0 :       } catch (IOException e) {
     360           0 :         logger.atWarning().withCause(e).log("Exception caught while closing");
     361             :       } finally {
     362          17 :         daemonAcceptor = null;
     363             :       }
     364             :     }
     365          17 :   }
     366             : 
     367             :   private void waitForSessionClose() {
     368           1 :     Collection<IoSession> ioSessions = daemonAcceptor.getManagedSessions().values();
     369           1 :     CountDownLatch allSessionsClosed = new CountDownLatch(ioSessions.size());
     370           1 :     for (IoSession io : ioSessions) {
     371           1 :       AbstractSession serverSession = AbstractSession.getSession(io, true);
     372             :       SshSession sshSession =
     373           1 :           serverSession != null ? serverSession.getAttribute(SshSession.KEY) : null;
     374           1 :       if (sshSession != null && sshSession.requiresGracefulStop()) {
     375           1 :         logger.atFine().log("Waiting for session %s to stop.", io.getId());
     376           1 :         io.addCloseFutureListener(
     377           1 :             new SshFutureListener<CloseFuture>() {
     378             :               @Override
     379             :               public void operationComplete(CloseFuture future) {
     380           1 :                 logger.atFine().log("Session %s was stopped.", io.getId());
     381           1 :                 allSessionsClosed.countDown();
     382           1 :               }
     383             :             });
     384             :       } else {
     385           1 :         logger.atFine().log("Stopping session %s immediately.", io.getId());
     386           1 :         io.close(true);
     387           1 :         allSessionsClosed.countDown();
     388             :       }
     389           1 :     }
     390             :     try {
     391           1 :       if (!allSessionsClosed.await(gracefulStopTimeout, TimeUnit.SECONDS)) {
     392           1 :         logger.atWarning().log(
     393             :             "Timeout waiting for SSH session to close. SSHD will be shut down immediately.");
     394             :       }
     395           0 :     } catch (InterruptedException e) {
     396           0 :       logger.atWarning().withCause(e).log(
     397             :           "Interrupted waiting for SSH-sessions to close. SSHD will be shut down immediately.");
     398           1 :     }
     399           1 :   }
     400             : 
     401             :   private void shutdownExecutors() {
     402          17 :     if (executor != null) {
     403          17 :       executor.shutdownNow();
     404             :     }
     405             : 
     406          17 :     IoServiceFactory serviceFactory = getIoServiceFactory();
     407          17 :     if (serviceFactory instanceof AbstractIoServiceFactory) {
     408          17 :       shutdownServiceFactoryExecutor((AbstractIoServiceFactory) serviceFactory);
     409             :     }
     410          17 :   }
     411             : 
     412             :   private void shutdownServiceFactoryExecutor(AbstractIoServiceFactory ioServiceFactory) {
     413          17 :     ioServiceFactory.close(true);
     414          17 :     ExecutorService serviceFactoryExecutor = ioServiceFactory.getExecutorService();
     415          17 :     if (serviceFactoryExecutor != null && serviceFactoryExecutor != executor) {
     416          17 :       serviceFactoryExecutor.shutdownNow();
     417             :     }
     418          17 :   }
     419             : 
     420             :   @Override
     421             :   protected void checkConfig() {
     422          17 :     super.checkConfig();
     423          17 :     if (myHostKeys().isEmpty()) {
     424           0 :       throw new IllegalStateException("No SSHD host key");
     425             :     }
     426          17 :   }
     427             : 
     428             :   private List<HostKey> computeHostKeys() {
     429          17 :     if (listen.isEmpty()) {
     430           0 :       return Collections.emptyList();
     431             :     }
     432             : 
     433          17 :     List<HostKey> r = new ArrayList<>();
     434          17 :     List<PublicKey> keys = myHostKeys();
     435          17 :     for (PublicKey pub : keys) {
     436          17 :       Buffer buf = new ByteArrayBuffer();
     437          17 :       buf.putRawPublicKey(pub);
     438          17 :       byte[] keyBin = buf.getCompactData();
     439             : 
     440          17 :       for (String addr : advertised) {
     441          17 :         r.add(new HostKey(addr, keyBin));
     442          17 :       }
     443          17 :     }
     444             : 
     445          17 :     return Collections.unmodifiableList(r);
     446             :   }
     447             : 
     448             :   private List<PublicKey> myHostKeys() {
     449          17 :     KeyPairProvider p = getKeyPairProvider();
     450          17 :     List<PublicKey> keys = new ArrayList<>(6);
     451             :     try {
     452          17 :       addPublicKey(keys, p, KeyPairProvider.SSH_ED25519);
     453          17 :       addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP256);
     454          17 :       addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP384);
     455          17 :       addPublicKey(keys, p, KeyPairProvider.ECDSA_SHA2_NISTP521);
     456          17 :       addPublicKey(keys, p, KeyPairProvider.SSH_RSA);
     457          17 :       addPublicKey(keys, p, KeyPairProvider.SSH_DSS);
     458           0 :     } catch (IOException | GeneralSecurityException e) {
     459           0 :       throw new IllegalStateException("Cannot load SSHD host key", e);
     460          17 :     }
     461          17 :     return keys;
     462             :   }
     463             : 
     464             :   private static void addPublicKey(final Collection<PublicKey> out, KeyPairProvider p, String type)
     465             :       throws IOException, GeneralSecurityException {
     466          17 :     final KeyPair pair = p.loadKey(null, type);
     467          17 :     if (pair != null && pair.getPublic() != null) {
     468          17 :       out.add(pair.getPublic());
     469             :     }
     470          17 :   }
     471             : 
     472             :   private String addressList() {
     473          17 :     final StringBuilder r = new StringBuilder();
     474          17 :     for (Iterator<SocketAddress> i = listen.iterator(); i.hasNext(); ) {
     475          17 :       r.append(SocketUtil.format(i.next(), IANA_SSH_PORT));
     476          17 :       if (i.hasNext()) {
     477           0 :         r.append(", ");
     478             :       }
     479             :     }
     480          17 :     return r.toString();
     481             :   }
     482             : 
     483             :   private void initKeyExchanges(Config cfg) {
     484          17 :     setKexExtensionHandler(DefaultServerKexExtensionHandler.INSTANCE);
     485          17 :     List<KeyExchangeFactory> a =
     486          17 :         NamedFactory.setUpTransformedFactories(
     487             :             true,
     488          17 :             cfg.getBoolean("sshd", null, "enableDeprecatedKexAlgorithms", false)
     489           0 :                 ? BuiltinDHFactories.VALUES
     490          17 :                 : BaseBuilder.DEFAULT_KEX_PREFERENCE,
     491             :             ServerBuilder.DH2KEX);
     492          17 :     setKeyExchangeFactories(filter(cfg, "kex", a.toArray(new KeyExchangeFactory[a.size()])));
     493          17 :   }
     494             : 
     495             :   private void initProviderBouncyCastle(Config cfg) {
     496             :     NamedFactory<Random> factory;
     497          17 :     if (cfg.getBoolean("sshd", null, "testUseInsecureRandom", false)) {
     498          17 :       factory = new InsecureBouncyCastleRandom.Factory();
     499             :     } else {
     500           0 :       factory = SecurityUtils.getRandomFactory();
     501             :     }
     502          17 :     setRandomFactory(new SingletonRandomFactory(factory));
     503          17 :   }
     504             : 
     505             :   private static class InsecureBouncyCastleRandom implements Random {
     506             :     private static class Factory implements NamedFactory<Random> {
     507             :       @Override
     508             :       public String getName() {
     509           0 :         return "INSECURE_bouncycastle";
     510             :       }
     511             : 
     512             :       @Override
     513             :       public Random create() {
     514          17 :         return new InsecureBouncyCastleRandom();
     515             :       }
     516             :     }
     517             : 
     518             :     private final RandomGenerator random;
     519             : 
     520          17 :     private InsecureBouncyCastleRandom() {
     521          17 :       random = new VMPCRandomGenerator();
     522          17 :       random.addSeedMaterial(1234);
     523          17 :     }
     524             : 
     525             :     @Override
     526             :     public String getName() {
     527           0 :       return "InsecureBouncyCastleRandom";
     528             :     }
     529             : 
     530             :     @Override
     531             :     public void fill(byte[] bytes, int start, int len) {
     532          17 :       random.nextBytes(bytes, start, len);
     533          17 :     }
     534             : 
     535             :     @Override
     536             :     public void fill(byte[] bytes) {
     537           0 :       random.nextBytes(bytes);
     538           0 :     }
     539             : 
     540             :     @Override
     541             :     public int random(int n) {
     542          17 :       if (n > 0) {
     543          17 :         if ((n & -n) == n) {
     544          17 :           return (int) ((n * (long) next(31)) >> 31);
     545             :         }
     546             :         int bits;
     547             :         int val;
     548             :         do {
     549           0 :           bits = next(31);
     550           0 :           val = bits % n;
     551           0 :         } while (bits - val + (n - 1) < 0);
     552           0 :         return val;
     553             :       }
     554           0 :       throw new IllegalArgumentException();
     555             :     }
     556             : 
     557             :     protected final int next(int numBits) {
     558          17 :       int bytes = (numBits + 7) / 8;
     559          17 :       byte[] next = new byte[bytes];
     560          17 :       int ret = 0;
     561          17 :       random.nextBytes(next);
     562          17 :       for (int i = 0; i < bytes; i++) {
     563          17 :         ret = (next[i] & 0xFF) | (ret << 8);
     564             :       }
     565          17 :       return ret >>> (bytes * 8 - numBits);
     566             :     }
     567             :   }
     568             : 
     569             :   @SuppressWarnings("unchecked")
     570             :   private void initCiphers(Config cfg) {
     571          17 :     List<NamedFactory<Cipher>> a = BaseBuilder.setUpDefaultCiphers(true);
     572             : 
     573          17 :     for (Iterator<NamedFactory<Cipher>> i = a.iterator(); i.hasNext(); ) {
     574          17 :       NamedFactory<Cipher> f = i.next();
     575             :       try {
     576          17 :         Cipher c = f.create();
     577          17 :         byte[] key = new byte[c.getKdfSize()];
     578          17 :         byte[] iv = new byte[c.getIVSize()];
     579          17 :         c.init(Cipher.Mode.Encrypt, key, iv);
     580           0 :       } catch (InvalidKeyException e) {
     581           0 :         logger.atWarning().log(
     582             :             "Disabling cipher %s: %s; try installing unlimited cryptography extension",
     583           0 :             f.getName(), e.getMessage());
     584           0 :         i.remove();
     585           0 :       } catch (Exception e) {
     586           0 :         logger.atWarning().log("Disabling cipher %s: %s", f.getName(), e.getMessage());
     587           0 :         i.remove();
     588          17 :       }
     589          17 :     }
     590             : 
     591          17 :     a.add(null);
     592          17 :     setCipherFactories(
     593          17 :         filter(cfg, "cipher", (NamedFactory<Cipher>[]) a.toArray(new NamedFactory<?>[a.size()])));
     594          17 :   }
     595             : 
     596             :   @SuppressWarnings("unchecked")
     597             :   private void initMacs(Config cfg) {
     598          17 :     List<NamedFactory<Mac>> m = BaseBuilder.setUpDefaultMacs(true);
     599          17 :     setMacFactories(
     600          17 :         filter(cfg, "mac", (NamedFactory<Mac>[]) m.toArray(new NamedFactory<?>[m.size()])));
     601          17 :   }
     602             : 
     603             :   @SafeVarargs
     604             :   private static <T extends NamedResource> List<T> filter(Config cfg, String key, T... avail) {
     605          17 :     List<T> def = new ArrayList<>();
     606          17 :     for (T n : avail) {
     607          17 :       if (n == null) {
     608          17 :         break;
     609             :       }
     610          17 :       def.add(n);
     611             :     }
     612             : 
     613          17 :     String[] want = cfg.getStringList("sshd", null, key);
     614          17 :     if (want == null || want.length == 0) {
     615          17 :       return def;
     616             :     }
     617             : 
     618           0 :     boolean didClear = false;
     619           0 :     for (String setting : want) {
     620           0 :       String name = setting.trim();
     621           0 :       boolean add = true;
     622           0 :       if (name.startsWith("-")) {
     623           0 :         add = false;
     624           0 :         name = name.substring(1).trim();
     625           0 :       } else if (name.startsWith("+")) {
     626           0 :         name = name.substring(1).trim();
     627           0 :       } else if (!didClear) {
     628           0 :         didClear = true;
     629           0 :         def.clear();
     630             :       }
     631             : 
     632           0 :       T n = find(name, avail);
     633           0 :       if (n == null) {
     634           0 :         StringBuilder msg = new StringBuilder();
     635           0 :         msg.append("sshd.").append(key).append(" = ").append(name).append(" unsupported; only ");
     636           0 :         for (int i = 0; i < avail.length; i++) {
     637           0 :           if (avail[i] == null) {
     638           0 :             continue;
     639             :           }
     640           0 :           if (i > 0) {
     641           0 :             msg.append(", ");
     642             :           }
     643           0 :           msg.append(avail[i].getName());
     644             :         }
     645           0 :         msg.append(" is supported");
     646           0 :         logger.atSevere().log("%s", msg);
     647           0 :       } else if (add) {
     648           0 :         if (!def.contains(n)) {
     649           0 :           def.add(n);
     650             :         }
     651             :       } else {
     652           0 :         def.remove(n);
     653             :       }
     654             :     }
     655             : 
     656           0 :     return def;
     657             :   }
     658             : 
     659             :   @SafeVarargs
     660             :   private static <T extends NamedResource> T find(String name, T... avail) {
     661           0 :     for (T n : avail) {
     662           0 :       if (n != null && name.equals(n.getName())) {
     663           0 :         return n;
     664             :       }
     665             :     }
     666           0 :     return null;
     667             :   }
     668             : 
     669             :   private void initSignatures() {
     670          17 :     setSignatureFactories(ServerBuilder.setUpDefaultSignatureFactories(false));
     671          17 :   }
     672             : 
     673             :   private void initCompression(boolean enableCompression) {
     674          17 :     List<NamedFactory<Compression>> compressionFactories = new ArrayList<>();
     675             : 
     676             :     // Always support no compression over SSHD.
     677          17 :     compressionFactories.add(BuiltinCompressions.none);
     678             : 
     679             :     // In the general case, we want to disable transparent compression, since
     680             :     // the majority of our data transfer is highly compressed Git pack files
     681             :     // and we cannot make them any smaller than they already are.
     682             :     //
     683             :     // However, if there are CPU in abundance and the server is reachable through
     684             :     // slow networks, gits with huge amount of refs can benefit from SSH-compression
     685             :     // since git does not compress the ref announcement during the handshake.
     686             :     // Compression can be especially useful when Gerrit replica are being used
     687             :     // for the larger clones and fetches and the primary server handling write
     688             :     // operations mostly takes small receive-packs.
     689             : 
     690          17 :     if (enableCompression) {
     691           0 :       compressionFactories.add(BuiltinCompressions.zlib);
     692             :     }
     693             : 
     694          17 :     setCompressionFactories(compressionFactories);
     695          17 :   }
     696             : 
     697             :   private void initChannels() {
     698          17 :     setChannelFactories(ServerBuilder.DEFAULT_CHANNEL_FACTORIES);
     699          17 :   }
     700             : 
     701             :   private void initUnknownChannelReferenceHandler(boolean enableChannelIdTracking) {
     702          17 :     setUnknownChannelReferenceHandler(
     703          17 :         enableChannelIdTracking
     704          17 :             ? ChannelIdTrackingUnknownChannelReferenceHandler.TRACKER
     705           0 :             : DefaultUnknownChannelReferenceHandler.INSTANCE);
     706          17 :   }
     707             : 
     708             :   private void initSubsystems() {
     709          17 :     setSubsystemFactories(Collections.emptyList());
     710          17 :   }
     711             : 
     712             :   private void initUserAuth(
     713             :       final PublickeyAuthenticator pubkey,
     714             :       final GSSAuthenticator kerberosAuthenticator,
     715             :       String kerberosKeytab,
     716             :       String kerberosPrincipal) {
     717          17 :     List<UserAuthFactory> authFactories = new ArrayList<>();
     718          17 :     if (kerberosKeytab != null) {
     719           0 :       authFactories.add(UserAuthGSSFactory.INSTANCE);
     720           0 :       logger.atInfo().log("Enabling kerberos with keytab %s", kerberosKeytab);
     721           0 :       if (!new File(kerberosKeytab).canRead()) {
     722           0 :         logger.atSevere().log(
     723             :             "Keytab %s does not exist or is not readable; further errors are possible",
     724             :             kerberosKeytab);
     725             :       }
     726           0 :       kerberosAuthenticator.setKeytabFile(kerberosKeytab);
     727           0 :       if (kerberosPrincipal == null) {
     728             :         try {
     729           0 :           kerberosPrincipal = "host/" + InetAddress.getLocalHost().getCanonicalHostName();
     730           0 :         } catch (UnknownHostException e) {
     731           0 :           kerberosPrincipal = "host/localhost";
     732           0 :         }
     733             :       }
     734           0 :       logger.atInfo().log("Using kerberos principal %s", kerberosPrincipal);
     735           0 :       if (!kerberosPrincipal.startsWith("host/")) {
     736           0 :         logger.atWarning().log(
     737             :             "Host principal does not start with host/ "
     738             :                 + "which most SSH clients will supply automatically");
     739             :       }
     740           0 :       kerberosAuthenticator.setServicePrincipalName(kerberosPrincipal);
     741           0 :       setGSSAuthenticator(kerberosAuthenticator);
     742             :     }
     743          17 :     authFactories.add(UserAuthPublicKeyFactory.INSTANCE);
     744          17 :     setUserAuthFactories(authFactories);
     745          17 :     setPublickeyAuthenticator(pubkey);
     746          17 :   }
     747             : 
     748             :   private void initForwarding() {
     749          17 :     setForwardingFilter(
     750          17 :         new ForwardingFilter() {
     751             :           @Override
     752             :           public boolean canForwardAgent(Session session, String requestType) {
     753           0 :             return false;
     754             :           }
     755             : 
     756             :           @Override
     757             :           public boolean canForwardX11(Session session, String requestType) {
     758           0 :             return false;
     759             :           }
     760             : 
     761             :           @Override
     762             :           public boolean canListen(SshdSocketAddress address, Session session) {
     763           0 :             return false;
     764             :           }
     765             : 
     766             :           @Override
     767             :           public boolean canConnect(Type type, SshdSocketAddress address, Session session) {
     768           0 :             return false;
     769             :           }
     770             :         });
     771          17 :     setForwarderFactory(new DefaultForwarderFactory());
     772          17 :   }
     773             : 
     774             :   private void initFileSystemFactory() {
     775          17 :     setFileSystemFactory(NoneFileSystemFactory.INSTANCE);
     776          17 :   }
     777             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750