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 : }