LCOV - code coverage report
Current view: top level - sshd/commands - ShowConnections.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 54 111 48.6 %
Date: 2022-11-19 15:00:39 Functions: 8 10 80.0 %

          Line data    Source code
       1             : // Copyright (C) 2009 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.commands;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkState;
      18             : import static com.google.common.collect.ImmutableList.toImmutableList;
      19             : import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
      20             : 
      21             : import com.google.common.collect.ImmutableList;
      22             : import com.google.gerrit.common.data.GlobalCapability;
      23             : import com.google.gerrit.extensions.annotations.RequiresCapability;
      24             : import com.google.gerrit.server.CurrentUser;
      25             : import com.google.gerrit.server.IdentifiedUser;
      26             : import com.google.gerrit.server.ioutil.HexFormat;
      27             : import com.google.gerrit.server.util.time.TimeUtil;
      28             : import com.google.gerrit.sshd.CommandMetaData;
      29             : import com.google.gerrit.sshd.SshCommand;
      30             : import com.google.gerrit.sshd.SshDaemon;
      31             : import com.google.gerrit.sshd.SshSession;
      32             : import com.google.inject.Inject;
      33             : import java.io.IOException;
      34             : import java.net.InetAddress;
      35             : import java.net.InetSocketAddress;
      36             : import java.net.SocketAddress;
      37             : import java.time.Instant;
      38             : import java.time.ZoneId;
      39             : import java.time.format.DateTimeFormatter;
      40             : import java.util.Optional;
      41             : import org.apache.sshd.common.io.IoAcceptor;
      42             : import org.apache.sshd.common.io.IoSession;
      43             : import org.apache.sshd.common.io.nio2.Nio2Acceptor;
      44             : import org.apache.sshd.common.session.helpers.AbstractSession;
      45             : import org.apache.sshd.mina.MinaAcceptor;
      46             : import org.apache.sshd.mina.MinaSession;
      47             : import org.apache.sshd.server.Environment;
      48             : import org.apache.sshd.server.channel.ChannelSession;
      49             : import org.kohsuke.args4j.Option;
      50             : 
      51             : /** Show the current SSH connections. */
      52             : @RequiresCapability(GlobalCapability.VIEW_CONNECTIONS)
      53             : @CommandMetaData(
      54             :     name = "show-connections",
      55             :     description = "Display active client SSH connections",
      56             :     runsAt = MASTER_OR_SLAVE)
      57           1 : final class ShowConnections extends SshCommand {
      58             :   @Option(
      59             :       name = "--numeric",
      60             :       aliases = {"-n"},
      61             :       usage = "don't resolve names")
      62             :   private boolean numeric;
      63             : 
      64             :   @Option(
      65             :       name = "--wide",
      66             :       aliases = {"-w"},
      67             :       usage = "display without line width truncation")
      68             :   private boolean wide;
      69             : 
      70             :   @Inject private SshDaemon daemon;
      71             : 
      72             :   private int hostNameWidth;
      73           1 :   private int columns = 80;
      74             : 
      75             :   @Override
      76             :   public void start(ChannelSession channel, Environment env) throws IOException {
      77           1 :     String s = env.getEnv().get(Environment.ENV_COLUMNS);
      78           1 :     if (s != null && !s.isEmpty()) {
      79             :       try {
      80           0 :         columns = Integer.parseInt(s);
      81           0 :       } catch (NumberFormatException err) {
      82           0 :         columns = 80;
      83           0 :       }
      84             :     }
      85           1 :     super.start(channel, env);
      86           1 :   }
      87             : 
      88             :   @Override
      89             :   protected void run() throws Failure {
      90           1 :     enableGracefulStop();
      91           1 :     final IoAcceptor acceptor = daemon.getIoAcceptor();
      92           1 :     if (acceptor == null) {
      93           0 :       throw new Failure(1, "fatal: sshd no longer running");
      94             :     }
      95             : 
      96           1 :     final ImmutableList<IoSession> list =
      97           1 :         acceptor.getManagedSessions().values().stream()
      98           1 :             .sorted(
      99             :                 (arg0, arg1) -> {
     100           1 :                   if (arg0 instanceof MinaSession) {
     101           0 :                     MinaSession mArg0 = (MinaSession) arg0;
     102           0 :                     MinaSession mArg1 = (MinaSession) arg1;
     103           0 :                     if (mArg0.getSession().getCreationTime()
     104           0 :                         < mArg1.getSession().getCreationTime()) {
     105           0 :                       return -1;
     106           0 :                     } else if (mArg0.getSession().getCreationTime()
     107           0 :                         > mArg1.getSession().getCreationTime()) {
     108           0 :                       return 1;
     109             :                     }
     110             :                   }
     111           1 :                   return (int) (arg0.getId() - arg1.getId());
     112             :                 })
     113           1 :             .collect(toImmutableList());
     114             : 
     115           1 :     hostNameWidth = wide ? Integer.MAX_VALUE : columns - 9 - 9 - 10 - 32;
     116             : 
     117           1 :     if (getBackend().equals("mina")) {
     118           0 :       long now = TimeUtil.nowMs();
     119           0 :       stdout.print(
     120           0 :           String.format(
     121             :               "%-8s %8s %8s   %-15s %s\n", "Session", "Start", "Idle", "User", "Remote Host"));
     122           0 :       stdout.print("--------------------------------------------------------------\n");
     123           0 :       for (IoSession io : list) {
     124           0 :         checkState(io instanceof MinaSession, "expected MinaSession");
     125           0 :         MinaSession minaSession = (MinaSession) io;
     126           0 :         long start = minaSession.getSession().getCreationTime();
     127           0 :         long idle = now - minaSession.getSession().getLastIoTime();
     128           0 :         AbstractSession s = AbstractSession.getSession(io, true);
     129           0 :         SshSession sd = s != null ? s.getAttribute(SshSession.KEY) : null;
     130             : 
     131           0 :         stdout.print(
     132           0 :             String.format(
     133             :                 "%8s %8s %8s   %-15.15s %s\n",
     134           0 :                 id(sd),
     135           0 :                 time(now, start),
     136           0 :                 age(idle),
     137           0 :                 username(sd),
     138           0 :                 hostname(io.getRemoteAddress())));
     139           0 :       }
     140           0 :     } else {
     141           1 :       stdout.print(String.format("%-8s   %-15s %s\n", "Session", "User", "Remote Host"));
     142           1 :       stdout.print("--------------------------------------------------------------\n");
     143           1 :       for (IoSession io : list) {
     144           1 :         AbstractSession s = AbstractSession.getSession(io, true);
     145           1 :         SshSession sd = s != null ? s.getAttribute(SshSession.KEY) : null;
     146             : 
     147           1 :         stdout.print(
     148           1 :             String.format(
     149           1 :                 "%8s   %-15.15s %s\n", id(sd), username(sd), hostname(io.getRemoteAddress())));
     150           1 :       }
     151             :     }
     152             : 
     153           1 :     stdout.print("--\n");
     154           1 :     stdout.print(String.format(" %d connections; SSHD Backend: %s\n", list.size(), getBackend()));
     155           1 :   }
     156             : 
     157             :   private String getBackend() {
     158           1 :     IoAcceptor acceptor = daemon.getIoAcceptor();
     159           1 :     if (acceptor == null) {
     160           0 :       return "";
     161           1 :     } else if (acceptor instanceof MinaAcceptor) {
     162           0 :       return "mina";
     163           1 :     } else if (acceptor instanceof Nio2Acceptor) {
     164           1 :       return "nio2";
     165             :     } else {
     166           0 :       return "unknown";
     167             :     }
     168             :   }
     169             : 
     170             :   private static String id(SshSession sd) {
     171           1 :     return sd != null ? HexFormat.fromInt(sd.getSessionId()) : "";
     172             :   }
     173             : 
     174             :   private static String time(long now, long time) {
     175           0 :     Instant instant = Instant.ofEpochMilli(time);
     176           0 :     if (now - time < 24 * 60 * 60 * 1000L) {
     177           0 :       return DateTimeFormatter.ofPattern("HH:mm:ss")
     178           0 :           .withZone(ZoneId.systemDefault())
     179           0 :           .format(instant);
     180             :     }
     181           0 :     return DateTimeFormatter.ofPattern("MMM-dd").withZone(ZoneId.systemDefault()).format(instant);
     182             :   }
     183             : 
     184             :   private static String age(long age) {
     185           0 :     age /= 1000;
     186             : 
     187           0 :     final int sec = (int) (age % 60);
     188           0 :     age /= 60;
     189             : 
     190           0 :     final int min = (int) (age % 60);
     191           0 :     age /= 60;
     192             : 
     193           0 :     final int hr = (int) (age % 60);
     194           0 :     return String.format("%02d:%02d:%02d", hr, min, sec);
     195             :   }
     196             : 
     197             :   private String username(SshSession sd) {
     198           1 :     if (sd == null) {
     199           0 :       return "";
     200             :     }
     201             : 
     202           1 :     final CurrentUser user = sd.getUser();
     203           1 :     if (user != null && user.isIdentifiedUser()) {
     204           1 :       IdentifiedUser u = user.asIdentifiedUser();
     205             : 
     206           1 :       if (!numeric) {
     207           1 :         Optional<String> name = u.getUserName();
     208           1 :         if (name.isPresent()) {
     209           1 :           return name.get();
     210             :         }
     211             :       }
     212             : 
     213           0 :       return "a/" + u.getAccountId().toString();
     214             :     }
     215           0 :     return "";
     216             :   }
     217             : 
     218             :   private String hostname(SocketAddress remoteAddress) {
     219           1 :     if (remoteAddress == null) {
     220           0 :       return "?";
     221             :     }
     222           1 :     String host = null;
     223           1 :     if (remoteAddress instanceof InetSocketAddress) {
     224           1 :       final InetSocketAddress sa = (InetSocketAddress) remoteAddress;
     225           1 :       final InetAddress in = sa.getAddress();
     226           1 :       if (numeric) {
     227           0 :         return in.getHostAddress();
     228             :       }
     229           1 :       if (in != null) {
     230           1 :         host = in.getCanonicalHostName();
     231             :       } else {
     232           0 :         host = sa.getHostName();
     233             :       }
     234             :     }
     235           1 :     if (host == null) {
     236           0 :       host = remoteAddress.toString();
     237             :     }
     238             : 
     239           1 :     if (host.length() > hostNameWidth) {
     240           0 :       return host.substring(0, hostNameWidth);
     241             :     }
     242             : 
     243           1 :     return host;
     244             :   }
     245             : }

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