LCOV - code coverage report
Current view: top level - sshd - NoShell.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 63 73 86.3 %
Date: 2022-11-19 15:00:39 Functions: 12 16 75.0 %

          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 com.google.gerrit.entities.Account;
      18             : import com.google.gerrit.server.IdentifiedUser;
      19             : import com.google.gerrit.server.config.AnonymousCowardName;
      20             : import com.google.gerrit.server.config.CanonicalWebUrl;
      21             : import com.google.gerrit.server.ssh.SshInfo;
      22             : import com.google.gerrit.sshd.SshScope.Context;
      23             : import com.google.inject.Inject;
      24             : import com.google.inject.Provider;
      25             : import java.io.IOException;
      26             : import java.io.InputStream;
      27             : import java.io.OutputStream;
      28             : import java.net.MalformedURLException;
      29             : import java.net.URL;
      30             : import org.apache.sshd.common.io.IoInputStream;
      31             : import org.apache.sshd.common.io.IoOutputStream;
      32             : import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
      33             : import org.apache.sshd.server.Environment;
      34             : import org.apache.sshd.server.ExitCallback;
      35             : import org.apache.sshd.server.channel.ChannelSession;
      36             : import org.apache.sshd.server.command.AsyncCommand;
      37             : import org.apache.sshd.server.command.Command;
      38             : import org.apache.sshd.server.session.ServerSession;
      39             : import org.apache.sshd.server.session.ServerSessionAware;
      40             : import org.apache.sshd.server.shell.ShellFactory;
      41             : import org.eclipse.jgit.lib.Constants;
      42             : import org.eclipse.jgit.util.SystemReader;
      43             : 
      44             : /**
      45             :  * Dummy shell which prints a message and terminates.
      46             :  *
      47             :  * <p>This implementation is used to ensure clients who try to SSH directly to this server without
      48             :  * supplying a command will get a reasonable error message, but cannot continue further.
      49             :  */
      50             : class NoShell implements ShellFactory {
      51             :   private final Provider<SendMessage> shell;
      52             : 
      53             :   @Inject
      54          17 :   NoShell(Provider<SendMessage> shell) {
      55          17 :     this.shell = shell;
      56          17 :   }
      57             : 
      58             :   @Override
      59             :   public Command createShell(ChannelSession channel) {
      60           1 :     return shell.get();
      61             :   }
      62             : 
      63             :   /**
      64             :    * When AsyncCommand is implemented by a command as below, the usual blocking streams aren't set.
      65             :    *
      66             :    * @see org.apache.sshd.server.command.AsyncCommand
      67             :    */
      68             :   static class SendMessage implements AsyncCommand, ServerSessionAware {
      69             :     private final Provider<MessageFactory> messageFactory;
      70             :     private final SshScope sshScope;
      71             : 
      72             :     private IoInputStream in;
      73             :     private IoOutputStream out;
      74             :     private IoOutputStream err;
      75             : 
      76             :     private ExitCallback exit;
      77             :     private Context context;
      78             : 
      79             :     @Inject
      80           1 :     SendMessage(Provider<MessageFactory> messageFactory, SshScope sshScope) {
      81           1 :       this.messageFactory = messageFactory;
      82           1 :       this.sshScope = sshScope;
      83           1 :     }
      84             : 
      85             :     @Override
      86             :     public void setIoInputStream(IoInputStream in) {
      87           1 :       this.in = in;
      88           1 :     }
      89             : 
      90             :     @Override
      91             :     public void setIoOutputStream(IoOutputStream out) {
      92           1 :       this.out = out;
      93           1 :     }
      94             : 
      95             :     @Override
      96             :     public void setIoErrorStream(IoOutputStream err) {
      97           1 :       this.err = err;
      98           1 :     }
      99             : 
     100             :     @Override
     101             :     public void setInputStream(InputStream in) {
     102             :       // ignored
     103           0 :     }
     104             : 
     105             :     @Override
     106             :     public void setOutputStream(OutputStream out) {
     107             :       // ignore
     108           0 :     }
     109             : 
     110             :     @Override
     111             :     public void setErrorStream(OutputStream err) {
     112             :       // ignore
     113           0 :     }
     114             : 
     115             :     @Override
     116             :     public void setExitCallback(ExitCallback callback) {
     117           1 :       this.exit = callback;
     118           1 :     }
     119             : 
     120             :     @Override
     121             :     public void setSession(ServerSession session) {
     122           1 :       SshSession s = session.getAttribute(SshSession.KEY);
     123           1 :       this.context = sshScope.newContext(s, "");
     124           1 :     }
     125             : 
     126             :     @Override
     127             :     public void start(ChannelSession channel, Environment env) throws IOException {
     128           1 :       Context old = sshScope.set(context);
     129             :       String message;
     130             :       try {
     131           1 :         message = messageFactory.get().getMessage();
     132             :       } finally {
     133           1 :         sshScope.set(old);
     134             :       }
     135           1 :       err.writeBuffer(new ByteArrayBuffer(Constants.encode(message)));
     136             : 
     137           1 :       in.close();
     138           1 :       out.close();
     139           1 :       err.close();
     140           1 :       exit.onExit(127);
     141           1 :     }
     142             : 
     143             :     @Override
     144           1 :     public void destroy(ChannelSession channel) {}
     145             :   }
     146             : 
     147             :   static class MessageFactory {
     148             :     private final IdentifiedUser user;
     149             :     private final SshInfo sshInfo;
     150             :     private final Provider<String> urlProvider;
     151             :     private final String anonymousCowardName;
     152             : 
     153             :     @Inject
     154             :     MessageFactory(
     155             :         IdentifiedUser user,
     156             :         SshInfo sshInfo,
     157             :         @CanonicalWebUrl Provider<String> urlProvider,
     158           1 :         @AnonymousCowardName String anonymousCowardName) {
     159           1 :       this.user = user;
     160           1 :       this.sshInfo = sshInfo;
     161           1 :       this.urlProvider = urlProvider;
     162           1 :       this.anonymousCowardName = anonymousCowardName;
     163           1 :     }
     164             : 
     165             :     String getMessage() {
     166           1 :       StringBuilder msg = new StringBuilder();
     167             : 
     168           1 :       msg.append("\r\n");
     169           1 :       msg.append("  ****    Welcome to Gerrit Code Review    ****\r\n");
     170           1 :       msg.append("\r\n");
     171             : 
     172           1 :       Account account = user.getAccount();
     173           1 :       String name = account.fullName();
     174           1 :       if (name == null || name.isEmpty()) {
     175           0 :         name = user.getUserName().orElse(anonymousCowardName);
     176             :       }
     177           1 :       msg.append("  Hi ");
     178           1 :       msg.append(name);
     179           1 :       msg.append(", you have successfully connected over SSH.");
     180           1 :       msg.append("\r\n");
     181           1 :       msg.append("\r\n");
     182             : 
     183           1 :       msg.append("  Unfortunately, interactive shells are disabled.\r\n");
     184           1 :       msg.append("  To clone a hosted Git repository, use:\r\n");
     185           1 :       msg.append("\r\n");
     186             : 
     187           1 :       if (!sshInfo.getHostKeys().isEmpty()) {
     188           1 :         String host = sshInfo.getHostKeys().get(0).getHost();
     189           1 :         if (host.startsWith("*:")) {
     190           0 :           host = getGerritHost() + host.substring(1);
     191             :         }
     192             : 
     193           1 :         msg.append("  git clone ssh://");
     194           1 :         if (user.getUserName().isPresent()) {
     195           1 :           msg.append(user.getUserName().get());
     196           1 :           msg.append("@");
     197             :         }
     198           1 :         msg.append(host);
     199           1 :         msg.append("/");
     200           1 :         msg.append("REPOSITORY_NAME.git");
     201           1 :         msg.append("\r\n");
     202             :       }
     203             : 
     204           1 :       msg.append("\r\n");
     205           1 :       return msg.toString();
     206             :     }
     207             : 
     208             :     private String getGerritHost() {
     209           0 :       String url = urlProvider.get();
     210           0 :       if (url != null) {
     211             :         try {
     212           0 :           return new URL(url).getHost();
     213           0 :         } catch (MalformedURLException e) {
     214             :           // Ignored
     215             :         }
     216             :       }
     217           0 :       return SystemReader.getInstance().getHostname();
     218             :     }
     219             :   }
     220             : }

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