<groupId>org.opendaylight.controller</groupId>
<artifactId>karaf-tomcat-security</artifactId>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller.thirdparty</groupId>
- <artifactId>ganymed</artifactId>
- </dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<bundle>wrap:mvn:io.netty/netty-common/${netty.version}</bundle>
<bundle>wrap:mvn:io.netty/netty-handler/${netty.version}</bundle>
<bundle>wrap:mvn:io.netty/netty-codec-http/${netty.version}</bundle>
- <bundle>mvn:org.opendaylight.controller.thirdparty/ganymed/1.2.0-SNAPSHOT</bundle>
</feature>
<feature name="odl-base-jersey" description="Jersey" version="${jersey.version}">
<feature>odl-base-gemini-web</feature>
<groupId>org.opendaylight.controller</groupId>
<artifactId>netconf-netty-util</artifactId>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller.thirdparty</groupId>
- <artifactId>ganymed</artifactId>
- </dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
<feature version='${project.version}'>odl-netconf-mapping-api</feature>
<feature version='${project.version}'>odl-netconf-util</feature>
<bundle>mvn:org.opendaylight.controller/netconf-netty-util/${project.version}</bundle>
- <bundle>mvn:org.opendaylight.controller.thirdparty/ganymed/${ganymed.version}</bundle>
<bundle>mvn:org.apache.sshd/sshd-core/${sshd-core.version}</bundle>
<bundle>mvn:org.openexi/nagasena/${exi.nagasena.version}</bundle>
<bundle>mvn:io.netty/netty-codec/${netty.version}</bundle>
<forwarding.staticrouting.northbound.version>0.5.0-SNAPSHOT</forwarding.staticrouting.northbound.version>
<forwardingrulesmanager.implementation.version>0.5.0-SNAPSHOT</forwardingrulesmanager.implementation.version>
<forwardingrulesmanager.version>0.7.0-SNAPSHOT</forwardingrulesmanager.version>
- <ganymed.version>1.2.0-SNAPSHOT</ganymed.version>
<hosttracker.api.version>0.6.0-SNAPSHOT</hosttracker.api.version>
<hosttracker.implementation.version>0.6.0-SNAPSHOT</hosttracker.implementation.version>
<hosttracker.northbound.version>0.5.0-SNAPSHOT</hosttracker.northbound.version>
<sonar.language>java</sonar.language>
<sonar.jacoco.reportPath>target/code-coverage/jacoco.exec</sonar.jacoco.reportPath>
<sonar.jacoco.itReportPath>target/code-coverage/jacoco-it.exec</sonar.jacoco.itReportPath>
- <sonar.skippedModules>org.openflow.openflowj,net.sf.jung2,org.opendaylight.controller.protobuff.messages,ch.ethz.ssh2</sonar.skippedModules>
+ <sonar.skippedModules>org.openflow.openflowj,net.sf.jung2,org.opendaylight.controller.protobuff.messages</sonar.skippedModules>
<sonar.profile>Sonar way with Findbugs</sonar.profile>
<spifly.version>1.0.0</spifly.version>
<spring-osgi.version>1.2.1</spring-osgi.version>
<artifactId>com.sun.jersey.jersey-servlet</artifactId>
<version>${jersey-servlet.version}</version>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller.thirdparty</groupId>
- <artifactId>ganymed</artifactId>
- <version>${ganymed.version}</version>
- </dependency>
<!-- Third parties from opendaylight released -->
<dependency>
<groupId>org.opendaylight.controller.thirdparty</groupId>
<artifactId>sample-toaster-provider</artifactId>
<version>${mdsal.version}</version>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller.thirdparty</groupId>
- <artifactId>ganymed</artifactId>
- </dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
mavenBundle("org.apache.sshd", "sshd-core").versionAsInProject(), //
mavenBundle("org.openexi", "nagasena").versionAsInProject(), //
mavenBundle("org.openexi", "nagasena-rta").versionAsInProject(), //
- mavenBundle(CONTROLLER + ".thirdparty", "ganymed").versionAsInProject(), //
mavenBundle(CONTROLLER, "netconf-mapping-api").versionAsInProject(), //
mavenBundle(CONTROLLER, "config-persister-impl").versionAsInProject(), //
<groupId>org.opendaylight.controller</groupId>
<artifactId>protocol-framework</artifactId>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller.thirdparty</groupId>
- <artifactId>ganymed</artifactId>
- </dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
- <Import-Package>org.apache.sshd.*, ch.ethz.ssh2, com.google.common.base, com.google.common.collect, io.netty.buffer,
+ <Import-Package>org.apache.sshd.*, com.google.common.base, com.google.common.collect, io.netty.buffer,
io.netty.channel, io.netty.channel.socket, io.netty.handler.codec, io.netty.handler.ssl, io.netty.util,
io.netty.util.concurrent, javax.xml.transform, javax.xml.transform.dom, javax.xml.transform.sax,
javax.xml.transform.stream, org.opendaylight.controller.netconf.api,
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller.thirdparty</groupId>
- <artifactId>ganymed</artifactId>
- </dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
<!-- <module>third-party/net.sf.jung2</module> -->
<!-- <module>third-party/jersey-servlet</module> -->
<!-- <module>third-party/org.apache.catalina.filters.CorsFilter</module> -->
- <module>third-party/ganymed</module>
-
+
<module>third-party/commons/thirdparty</module>
<!-- SAL bundles -->
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
-
- <parent>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>commons.thirdparty</artifactId>
- <version>1.2.0-SNAPSHOT</version>
- <relativePath>../commons/thirdparty</relativePath>
- </parent>
-
- <groupId>org.opendaylight.controller.thirdparty</groupId>
- <artifactId>ganymed</artifactId>
- <version>1.2.0-SNAPSHOT</version>
- <packaging>bundle</packaging>
-
- <dependencies>
- <dependency>
- <groupId>org.osgi</groupId>
- <artifactId>org.osgi.core</artifactId>
- <version>5.0.0</version>
- </dependency>
- <dependency>
- <groupId>ch.ethz.ganymed</groupId>
- <artifactId>ganymed-ssh2</artifactId>
- <version>261</version>
- </dependency>
- </dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.felix</groupId>
- <artifactId>maven-bundle-plugin</artifactId>
- <extensions>true</extensions>
- <configuration>
- <instructions>
- <Export-Package>ch.ethz.ssh2.*</Export-Package>
- <Embed-Dependency>ganymed-ssh2;scope=compile</Embed-Dependency>
- <Embed-Transitive>true</Embed-Transitive>
- </instructions>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-enforcer-plugin</artifactId>
- <version>${enforcer.version}</version>
- <executions>
- <execution>
- <id>enforce-no-snapshots</id>
- <goals>
- <goal>enforce</goal>
- </goals>
- <configuration>
- <rules>
- <bannedDependencies>
- <excludes>
- <exclude>ch.ethz.ganymed:ganymed-ssh2:*</exclude>
- </excludes>
- <includes>
- <include>ch.ethz.ganymed:ganymed-ssh2:[261]</include>
- </includes>
- </bannedDependencies>
- </rules>
- </configuration>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
-</project>
-
-
+++ /dev/null
-/*
- * Copyright (c) 2006-2011 Christian Plattner. All rights reserved.
- * Please refer to the LICENSE.txt for licensing details.
- */
-
-package ch.ethz.ssh2;
-
-import java.io.CharArrayWriter;
-import java.io.File;
-import java.net.Socket;
-import java.io.FileReader;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.SocketTimeoutException;
-import java.security.SecureRandom;
-import java.util.List;
-import java.util.Vector;
-
-import ch.ethz.ssh2.auth.AuthenticationManager;
-import ch.ethz.ssh2.channel.ChannelManager;
-import ch.ethz.ssh2.crypto.CryptoWishList;
-import ch.ethz.ssh2.crypto.cipher.BlockCipherFactory;
-import ch.ethz.ssh2.crypto.digest.MAC;
-import ch.ethz.ssh2.packets.PacketIgnore;
-import ch.ethz.ssh2.transport.KexManager;
-import ch.ethz.ssh2.transport.TransportManager;
-import ch.ethz.ssh2.util.TimeoutService;
-import ch.ethz.ssh2.util.TimeoutService.TimeoutToken;
-
-/**
- * A <code>Connection</code> is used to establish an encrypted TCP/IP
- * connection to a SSH-2 server.
- * <p>
- * Typically, one
- * <ol>
- * <li>creates a {@link #Connection(String) Connection} object.</li>
- * <li>calls the {@link #connect() connect()} method.</li>
- * <li>calls some of the authentication methods (e.g., {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()}).</li>
- * <li>calls one or several times the {@link #openSession() openSession()} method.</li>
- * <li>finally, one must close the connection and release resources with the {@link #close() close()} method.</li>
- * </ol>
- *
- * @author Christian Plattner
- * @version $Id: Connection.java 69 2013-08-09 06:39:56Z dkocher@sudo.ch $
- */
-
-public class Connection
-{
- /**
- * The identifier presented to the SSH-2 server. This is the same
- * as the "softwareversion" defined in RFC 4253.
- * <p/>
- * <b>NOTE: As per the RFC, the "softwareversion" string MUST consist of printable
- * US-ASCII characters, with the exception of whitespace characters and the minus sign (-).</b>
- */
- private String softwareversion = String.format("Ganymed_%s", Version.getSpecification());
-
- /* Will be used to generate all random data needed for the current connection.
- * Note: SecureRandom.nextBytes() is thread safe.
- */
-
- private SecureRandom generator;
-
- private Socket precreatedSocket;
-
- public Connection(Socket socket) {
- this.precreatedSocket = socket;
- this.hostname = socket.getInetAddress().getHostName();
- this.port = socket.getPort();
- }
-
- /**
- * Unless you know what you are doing, you will never need this.
- *
- * @return The list of supported cipher algorithms by this implementation.
- */
- public static synchronized String[] getAvailableCiphers()
- {
- return BlockCipherFactory.getDefaultCipherList();
- }
-
- /**
- * Unless you know what you are doing, you will never need this.
- *
- * @return The list of supported MAC algorthims by this implementation.
- */
- public static synchronized String[] getAvailableMACs()
- {
- return MAC.getMacList();
- }
-
- /**
- * Unless you know what you are doing, you will never need this.
- *
- * @return The list of supported server host key algorthims by this implementation.
- */
- public static synchronized String[] getAvailableServerHostKeyAlgorithms()
- {
- return KexManager.getDefaultServerHostkeyAlgorithmList();
- }
-
- private AuthenticationManager am;
-
- private boolean authenticated = false;
- private ChannelManager cm;
-
- private CryptoWishList cryptoWishList = new CryptoWishList();
-
- private DHGexParameters dhgexpara = new DHGexParameters();
-
- private final String hostname;
-
- private final int port;
-
- private TransportManager tm;
-
- private boolean tcpNoDelay = false;
-
- private ProxyData proxyData = null;
-
- private List<ConnectionMonitor> connectionMonitors = new Vector<ConnectionMonitor>();
-
- /**
- * Prepares a fresh <code>Connection</code> object which can then be used
- * to establish a connection to the specified SSH-2 server.
- * <p>
- * Same as {@link #Connection(String, int) Connection(hostname, 22)}.
- *
- * @param hostname the hostname of the SSH-2 server.
- */
- public Connection(String hostname)
- {
- this(hostname, 22);
- }
-
- /**
- * Prepares a fresh <code>Connection</code> object which can then be used
- * to establish a connection to the specified SSH-2 server.
- *
- * @param hostname
- * the host where we later want to connect to.
- * @param port
- * port on the server, normally 22.
- */
- public Connection(String hostname, int port)
- {
- this.hostname = hostname;
- this.port = port;
- }
-
- /**
- * Prepares a fresh <code>Connection</code> object which can then be used
- * to establish a connection to the specified SSH-2 server.
- *
- * @param hostname
- * the host where we later want to connect to.
- * @param port
- * port on the server, normally 22.
- * @param softwareversion
- * Allows you to set a custom "softwareversion" string as defined in RFC 4253.
- * <b>NOTE: As per the RFC, the "softwareversion" string MUST consist of printable
- * US-ASCII characters, with the exception of whitespace characters and the minus sign (-).</b>
- */
- public Connection(String hostname, int port, String softwareversion)
- {
- this.hostname = hostname;
- this.port = port;
- this.softwareversion = softwareversion;
- }
-
- /**
- * After a successful connect, one has to authenticate oneself. This method
- * is based on DSA (it uses DSA to sign a challenge sent by the server).
- * <p>
- * If the authentication phase is complete, <code>true</code> will be
- * returned. If the server does not accept the request (or if further
- * authentication steps are needed), <code>false</code> is returned and
- * one can retry either by using this or any other authentication method
- * (use the <code>getRemainingAuthMethods</code> method to get a list of
- * the remaining possible methods).
- *
- * @param user
- * A <code>String</code> holding the username.
- * @param pem
- * A <code>String</code> containing the DSA private key of the
- * user in OpenSSH key format (PEM, you can't miss the
- * "-----BEGIN DSA PRIVATE KEY-----" tag). The string may contain
- * linefeeds.
- * @param password
- * If the PEM string is 3DES encrypted ("DES-EDE3-CBC"), then you
- * must specify the password. Otherwise, this argument will be
- * ignored and can be set to <code>null</code>.
- *
- * @return whether the connection is now authenticated.
- * @throws IOException
- *
- * @deprecated You should use one of the {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()}
- * methods, this method is just a wrapper for it and will
- * disappear in future builds.
- *
- */
- public synchronized boolean authenticateWithDSA(String user, String pem, String password) throws IOException
- {
- if (tm == null)
- throw new IllegalStateException("Connection is not established!");
-
- if (authenticated)
- throw new IllegalStateException("Connection is already authenticated!");
-
- if (am == null)
- am = new AuthenticationManager(tm);
-
- if (cm == null)
- cm = new ChannelManager(tm);
-
- if (user == null)
- throw new IllegalArgumentException("user argument is null");
-
- if (pem == null)
- throw new IllegalArgumentException("pem argument is null");
-
- authenticated = am.authenticatePublicKey(user, pem.toCharArray(), password, getOrCreateSecureRND());
-
- return authenticated;
- }
-
- /**
- * A wrapper that calls {@link #authenticateWithKeyboardInteractive(String, String[], InteractiveCallback)
- * authenticateWithKeyboardInteractivewith} a <code>null</code> submethod list.
- *
- * @param user
- * A <code>String</code> holding the username.
- * @param cb
- * An <code>InteractiveCallback</code> which will be used to
- * determine the responses to the questions asked by the server.
- * @return whether the connection is now authenticated.
- * @throws IOException
- */
- public synchronized boolean authenticateWithKeyboardInteractive(String user, InteractiveCallback cb)
- throws IOException
- {
- return authenticateWithKeyboardInteractive(user, null, cb);
- }
-
- /**
- * After a successful connect, one has to authenticate oneself. This method
- * is based on "keyboard-interactive", specified in
- * draft-ietf-secsh-auth-kbdinteract-XX. Basically, you have to define a
- * callback object which will be feeded with challenges generated by the
- * server. Answers are then sent back to the server. It is possible that the
- * callback will be called several times during the invocation of this
- * method (e.g., if the server replies to the callback's answer(s) with
- * another challenge...)
- * <p>
- * If the authentication phase is complete, <code>true</code> will be
- * returned. If the server does not accept the request (or if further
- * authentication steps are needed), <code>false</code> is returned and
- * one can retry either by using this or any other authentication method
- * (use the <code>getRemainingAuthMethods</code> method to get a list of
- * the remaining possible methods).
- * <p>
- * Note: some SSH servers advertise "keyboard-interactive", however, any
- * interactive request will be denied (without having sent any challenge to
- * the client).
- *
- * @param user
- * A <code>String</code> holding the username.
- * @param submethods
- * An array of submethod names, see
- * draft-ietf-secsh-auth-kbdinteract-XX. May be <code>null</code>
- * to indicate an empty list.
- * @param cb
- * An <code>InteractiveCallback</code> which will be used to
- * determine the responses to the questions asked by the server.
- *
- * @return whether the connection is now authenticated.
- * @throws IOException
- */
- public synchronized boolean authenticateWithKeyboardInteractive(String user, String[] submethods,
- InteractiveCallback cb) throws IOException
- {
- if (cb == null)
- throw new IllegalArgumentException("Callback may not ne NULL!");
-
- if (tm == null)
- throw new IllegalStateException("Connection is not established!");
-
- if (authenticated)
- throw new IllegalStateException("Connection is already authenticated!");
-
- if (am == null)
- am = new AuthenticationManager(tm);
-
- if (cm == null)
- cm = new ChannelManager(tm);
-
- if (user == null)
- throw new IllegalArgumentException("user argument is null");
-
- authenticated = am.authenticateInteractive(user, submethods, cb);
-
- return authenticated;
- }
-
- /**
- * After a successful connect, one has to authenticate oneself. This method
- * sends username and password to the server.
- * <p>
- * If the authentication phase is complete, <code>true</code> will be
- * returned. If the server does not accept the request (or if further
- * authentication steps are needed), <code>false</code> is returned and
- * one can retry either by using this or any other authentication method
- * (use the <code>getRemainingAuthMethods</code> method to get a list of
- * the remaining possible methods).
- * <p>
- * Note: if this method fails, then please double-check that it is actually
- * offered by the server (use {@link #getRemainingAuthMethods(String) getRemainingAuthMethods()}.
- * <p>
- * Often, password authentication is disabled, but users are not aware of it.
- * Many servers only offer "publickey" and "keyboard-interactive". However,
- * even though "keyboard-interactive" *feels* like password authentication
- * (e.g., when using the putty or openssh clients) it is *not* the same mechanism.
- *
- * @param user
- * @param password
- * @return if the connection is now authenticated.
- * @throws IOException
- */
- public synchronized boolean authenticateWithPassword(String user, String password) throws IOException
- {
- if (tm == null)
- throw new IllegalStateException("Connection is not established!");
-
- if (authenticated)
- throw new IllegalStateException("Connection is already authenticated!");
-
- if (am == null)
- am = new AuthenticationManager(tm);
-
- if (cm == null)
- cm = new ChannelManager(tm);
-
- if (user == null)
- throw new IllegalArgumentException("user argument is null");
-
- if (password == null)
- throw new IllegalArgumentException("password argument is null");
-
- authenticated = am.authenticatePassword(user, password);
-
- return authenticated;
- }
-
- /**
- * After a successful connect, one has to authenticate oneself.
- * This method can be used to explicitly use the special "none"
- * authentication method (where only a username has to be specified).
- * <p>
- * Note 1: The "none" method may always be tried by clients, however as by
- * the specs, the server will not explicitly announce it. In other words,
- * the "none" token will never show up in the list returned by
- * {@link #getRemainingAuthMethods(String)}.
- * <p>
- * Note 2: no matter which one of the authenticateWithXXX() methods
- * you call, the library will always issue exactly one initial "none"
- * authentication request to retrieve the initially allowed list of
- * authentication methods by the server. Please read RFC 4252 for the
- * details.
- * <p>
- * If the authentication phase is complete, <code>true</code> will be
- * returned. If further authentication steps are needed, <code>false</code>
- * is returned and one can retry by any other authentication method
- * (use the <code>getRemainingAuthMethods</code> method to get a list of
- * the remaining possible methods).
- *
- * @param user
- * @return if the connection is now authenticated.
- * @throws IOException
- */
- public synchronized boolean authenticateWithNone(String user) throws IOException
- {
- if (tm == null)
- throw new IllegalStateException("Connection is not established!");
-
- if (authenticated)
- throw new IllegalStateException("Connection is already authenticated!");
-
- if (am == null)
- am = new AuthenticationManager(tm);
-
- if (cm == null)
- cm = new ChannelManager(tm);
-
- if (user == null)
- throw new IllegalArgumentException("user argument is null");
-
- /* Trigger the sending of the PacketUserauthRequestNone packet */
- /* (if not already done) */
-
- authenticated = am.authenticateNone(user);
-
- return authenticated;
- }
-
- /**
- * After a successful connect, one has to authenticate oneself.
- * The authentication method "publickey" works by signing a challenge
- * sent by the server. The signature is either DSA or RSA based - it
- * just depends on the type of private key you specify, either a DSA
- * or RSA private key in PEM format. And yes, this is may seem to be a
- * little confusing, the method is called "publickey" in the SSH-2 protocol
- * specification, however since we need to generate a signature, you
- * actually have to supply a private key =).
- * <p>
- * The private key contained in the PEM file may also be encrypted ("Proc-Type: 4,ENCRYPTED").
- * The library supports DES-CBC and DES-EDE3-CBC encryption, as well
- * as the more exotic PEM encrpytions AES-128-CBC, AES-192-CBC and AES-256-CBC.
- * <p>
- * If the authentication phase is complete, <code>true</code> will be
- * returned. If the server does not accept the request (or if further
- * authentication steps are needed), <code>false</code> is returned and
- * one can retry either by using this or any other authentication method
- * (use the <code>getRemainingAuthMethods</code> method to get a list of
- * the remaining possible methods).
- * <p>
- * NOTE PUTTY USERS: Event though your key file may start with "-----BEGIN..."
- * it is not in the expected format. You have to convert it to the OpenSSH
- * key format by using the "puttygen" tool (can be downloaded from the Putty
- * website). Simply load your key and then use the "Conversions/Export OpenSSH key"
- * functionality to get a proper PEM file.
- *
- * @param user
- * A <code>String</code> holding the username.
- * @param pemPrivateKey
- * A <code>char[]</code> containing a DSA or RSA private key of the
- * user in OpenSSH key format (PEM, you can't miss the
- * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE KEY-----"
- * tag). The char array may contain linebreaks/linefeeds.
- * @param password
- * If the PEM structure is encrypted ("Proc-Type: 4,ENCRYPTED") then
- * you must specify a password. Otherwise, this argument will be ignored
- * and can be set to <code>null</code>.
- *
- * @return whether the connection is now authenticated.
- * @throws IOException
- */
- public synchronized boolean authenticateWithPublicKey(String user, char[] pemPrivateKey, String password)
- throws IOException
- {
- if (tm == null)
- throw new IllegalStateException("Connection is not established!");
-
- if (authenticated)
- throw new IllegalStateException("Connection is already authenticated!");
-
- if (am == null)
- am = new AuthenticationManager(tm);
-
- if (cm == null)
- cm = new ChannelManager(tm);
-
- if (user == null)
- throw new IllegalArgumentException("user argument is null");
-
- if (pemPrivateKey == null)
- throw new IllegalArgumentException("pemPrivateKey argument is null");
-
- authenticated = am.authenticatePublicKey(user, pemPrivateKey, password, getOrCreateSecureRND());
-
- return authenticated;
- }
-
- /**
- * A convenience wrapper function which reads in a private key (PEM format, either DSA or RSA)
- * and then calls <code>authenticateWithPublicKey(String, char[], String)</code>.
- * <p>
- * NOTE PUTTY USERS: Event though your key file may start with "-----BEGIN..."
- * it is not in the expected format. You have to convert it to the OpenSSH
- * key format by using the "puttygen" tool (can be downloaded from the Putty
- * website). Simply load your key and then use the "Conversions/Export OpenSSH key"
- * functionality to get a proper PEM file.
- *
- * @param user
- * A <code>String</code> holding the username.
- * @param pemFile
- * A <code>File</code> object pointing to a file containing a DSA or RSA
- * private key of the user in OpenSSH key format (PEM, you can't miss the
- * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE KEY-----"
- * tag).
- * @param password
- * If the PEM file is encrypted then you must specify the password.
- * Otherwise, this argument will be ignored and can be set to <code>null</code>.
- *
- * @return whether the connection is now authenticated.
- * @throws IOException
- */
- public synchronized boolean authenticateWithPublicKey(String user, File pemFile, String password)
- throws IOException
- {
- if (pemFile == null)
- throw new IllegalArgumentException("pemFile argument is null");
-
- char[] buff = new char[256];
-
- CharArrayWriter cw = new CharArrayWriter();
-
- FileReader fr = new FileReader(pemFile);
-
- while (true)
- {
- int len = fr.read(buff);
- if (len < 0)
- break;
- cw.write(buff, 0, len);
- }
-
- fr.close();
-
- return authenticateWithPublicKey(user, cw.toCharArray(), password);
- }
-
- /**
- * Add a {@link ConnectionMonitor} to this connection. Can be invoked at any time,
- * but it is best to add connection monitors before invoking
- * <code>connect()</code> to avoid glitches (e.g., you add a connection monitor after
- * a successful connect(), but the connection has died in the mean time. Then,
- * your connection monitor won't be notified.)
- * <p>
- * You can add as many monitors as you like. If a monitor has already been added, then
- * this method does nothing.
- *
- * @see ConnectionMonitor
- *
- * @param cmon An object implementing the {@link ConnectionMonitor} interface.
- */
- public synchronized void addConnectionMonitor(ConnectionMonitor cmon)
- {
- if (cmon == null)
- throw new IllegalArgumentException("cmon argument is null");
-
- if (!connectionMonitors.contains(cmon))
- {
- connectionMonitors.add(cmon);
-
- if (tm != null)
- tm.setConnectionMonitors(connectionMonitors);
- }
- }
-
- /**
- * Remove a {@link ConnectionMonitor} from this connection.
- *
- * @param cmon
- * @return whether the monitor could be removed
- */
- public synchronized boolean removeConnectionMonitor(ConnectionMonitor cmon)
- {
- if (cmon == null)
- throw new IllegalArgumentException("cmon argument is null");
-
- boolean existed = connectionMonitors.remove(cmon);
-
- if (tm != null)
- tm.setConnectionMonitors(connectionMonitors);
-
- return existed;
- }
-
- /**
- * Close the connection to the SSH-2 server. All assigned sessions will be
- * closed, too. Can be called at any time. Don't forget to call this once
- * you don't need a connection anymore - otherwise the receiver thread may
- * run forever.
- */
- public synchronized void close()
- {
- Throwable t = new Throwable("Closed due to user request.");
- close(t, false);
- }
-
- public synchronized void close(Throwable t, boolean hard)
- {
- if (cm != null)
- cm.closeAllChannels();
-
- if (tm != null)
- {
- tm.close(t, hard == false);
- tm = null;
- }
- am = null;
- cm = null;
- authenticated = false;
- }
-
- /**
- * Same as {@link #connect(ServerHostKeyVerifier, int, int) connect(null, 0, 0)}.
- *
- * @return see comments for the {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} method.
- * @throws IOException
- */
- public synchronized ConnectionInfo connect() throws IOException
- {
- return connect(null, 0, 0);
- }
-
- /**
- * Same as {@link #connect(ServerHostKeyVerifier, int, int) connect(verifier, 0, 0)}.
- *
- * @return see comments for the {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} method.
- * @throws IOException
- */
- public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier) throws IOException
- {
- return connect(verifier, 0, 0);
- }
-
- /**
- * Connect to the SSH-2 server and, as soon as the server has presented its
- * host key, use the {@link ServerHostKeyVerifier#verifyServerHostKey(String,
- * int, String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()}
- * method of the <code>verifier</code> to ask for permission to proceed.
- * If <code>verifier</code> is <code>null</code>, then any host key will be
- * accepted - this is NOT recommended, since it makes man-in-the-middle attackes
- * VERY easy (somebody could put a proxy SSH server between you and the real server).
- * <p>
- * Note: The verifier will be called before doing any crypto calculations
- * (i.e., diffie-hellman). Therefore, if you don't like the presented host key then
- * no CPU cycles are wasted (and the evil server has less information about us).
- * <p>
- * However, it is still possible that the server presented a fake host key: the server
- * cheated (typically a sign for a man-in-the-middle attack) and is not able to generate
- * a signature that matches its host key. Don't worry, the library will detect such
- * a scenario later when checking the signature (the signature cannot be checked before
- * having completed the diffie-hellman exchange).
- * <p>
- * Note 2: The {@link ServerHostKeyVerifier#verifyServerHostKey(String,
- * int, String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method
- * will *NOT* be called from the current thread, the call is being made from a
- * background thread (there is a background dispatcher thread for every
- * established connection).
- * <p>
- * Note 3: This method will block as long as the key exchange of the underlying connection
- * has not been completed (and you have not specified any timeouts).
- * <p>
- * Note 4: If you want to re-use a connection object that was successfully connected,
- * then you must call the {@link #close()} method before invoking <code>connect()</code> again.
- *
- * @param verifier
- * An object that implements the
- * {@link ServerHostKeyVerifier} interface. Pass <code>null</code>
- * to accept any server host key - NOT recommended.
- *
- * @param connectTimeout
- * Connect the underlying TCP socket to the server with the given timeout
- * value (non-negative, in milliseconds). Zero means no timeout. If a proxy is being
- * used (see {@link #setProxyData(ProxyData)}), then this timeout is used for the
- * connection establishment to the proxy.
- *
- * @param kexTimeout
- * Timeout for complete connection establishment (non-negative,
- * in milliseconds). Zero means no timeout. The timeout counts from the
- * moment you invoke the connect() method and is cancelled as soon as the
- * first key-exchange round has finished. It is possible that
- * the timeout event will be fired during the invocation of the
- * <code>verifier</code> callback, but it will only have an effect after
- * the <code>verifier</code> returns.
- *
- * @return A {@link ConnectionInfo} object containing the details of
- * the established connection.
- *
- * @throws IOException
- * If any problem occurs, e.g., the server's host key is not
- * accepted by the <code>verifier</code> or there is problem during
- * the initial crypto setup (e.g., the signature sent by the server is wrong).
- * <p>
- * In case of a timeout (either connectTimeout or kexTimeout)
- * a SocketTimeoutException is thrown.
- * <p>
- * An exception may also be thrown if the connection was already successfully
- * connected (no matter if the connection broke in the mean time) and you invoke
- * <code>connect()</code> again without having called {@link #close()} first.
- * <p>
- * If a HTTP proxy is being used and the proxy refuses the connection,
- * then a {@link HTTPProxyException} may be thrown, which
- * contains the details returned by the proxy. If the proxy is buggy and does
- * not return a proper HTTP response, then a normal IOException is thrown instead.
- */
- public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier, int connectTimeout, int kexTimeout)
- throws IOException
- {
- final class TimeoutState
- {
- boolean isCancelled = false;
- boolean timeoutSocketClosed = false;
- }
-
- if (tm != null)
- throw new IOException("Connection to " + hostname + " is already in connected state!");
-
- if (connectTimeout < 0)
- throw new IllegalArgumentException("connectTimeout must be non-negative!");
-
- if (kexTimeout < 0)
- throw new IllegalArgumentException("kexTimeout must be non-negative!");
-
- final TimeoutState state = new TimeoutState();
-
- tm = new TransportManager();
- tm.setSoTimeout(connectTimeout);
- tm.setConnectionMonitors(connectionMonitors);
-
- /* Make sure that the runnable below will observe the new value of "tm"
- * and "state" (the runnable will be executed in a different thread, which
- * may be already running, that is why we need a memory barrier here).
- * See also the comment in Channel.java if you
- * are interested in the details.
- *
- * OKOK, this is paranoid since adding the runnable to the todo list
- * of the TimeoutService will ensure that all writes have been flushed
- * before the Runnable reads anything
- * (there is a synchronized block in TimeoutService.addTimeoutHandler).
- */
-
- synchronized (tm)
- {
- /* We could actually synchronize on anything. */
- }
-
- try
- {
- TimeoutToken token = null;
-
- if (kexTimeout > 0)
- {
- final Runnable timeoutHandler = new Runnable()
- {
- public void run()
- {
- synchronized (state)
- {
- if (state.isCancelled)
- return;
- state.timeoutSocketClosed = true;
- tm.close(new SocketTimeoutException("The connect timeout expired"), false);
- }
- }
- };
-
- long timeoutHorizont = System.currentTimeMillis() + kexTimeout;
-
- token = TimeoutService.addTimeoutHandler(timeoutHorizont, timeoutHandler);
- }
-
- try
- {
-
- if (precreatedSocket != null) {
- tm.clientInit(precreatedSocket, softwareversion, cryptoWishList, verifier, dhgexpara,
- getOrCreateSecureRND());
- } else {
- tm.clientInit(hostname, port, softwareversion, cryptoWishList, verifier, dhgexpara, connectTimeout,
- getOrCreateSecureRND(), proxyData);
- }
- }
- catch (SocketTimeoutException se)
- {
- throw (SocketTimeoutException) new SocketTimeoutException(
- "The connect() operation on the socket timed out.").initCause(se);
- }
-
- tm.setTcpNoDelay(tcpNoDelay);
-
- /* Wait until first KEX has finished */
-
- ConnectionInfo ci = tm.getConnectionInfo(1);
-
- /* Now try to cancel the timeout, if needed */
-
- if (token != null)
- {
- TimeoutService.cancelTimeoutHandler(token);
-
- /* Were we too late? */
-
- synchronized (state)
- {
- if (state.timeoutSocketClosed)
- throw new IOException("This exception will be replaced by the one below =)");
- /* Just in case the "cancelTimeoutHandler" invocation came just a little bit
- * too late but the handler did not enter the semaphore yet - we can
- * still stop it.
- */
- state.isCancelled = true;
- }
- }
-
- return ci;
- }
- catch (SocketTimeoutException ste)
- {
- throw ste;
- }
- catch (IOException e1)
- {
- /* This will also invoke any registered connection monitors */
- close(new Throwable("There was a problem during connect."), false);
-
- synchronized (state)
- {
- /* Show a clean exception, not something like "the socket is closed!?!" */
- if (state.timeoutSocketClosed)
- throw new SocketTimeoutException("The kexTimeout (" + kexTimeout + " ms) expired.");
- }
-
- /* Do not wrap a HTTPProxyException */
- if (e1 instanceof HTTPProxyException)
- throw e1;
-
- throw (IOException) new IOException("There was a problem while connecting to " + hostname + ":" + port)
- .initCause(e1);
- }
- }
-
- /**
- * Creates a new {@link LocalPortForwarder}.
- * A <code>LocalPortForwarder</code> forwards TCP/IP connections that arrive at a local
- * port via the secure tunnel to another host (which may or may not be
- * identical to the remote SSH-2 server).
- * <p>
- * This method must only be called after one has passed successfully the authentication step.
- * There is no limit on the number of concurrent forwardings.
- *
- * @param local_port the local port the LocalPortForwarder shall bind to.
- * @param host_to_connect target address (IP or hostname)
- * @param port_to_connect target port
- * @return A {@link LocalPortForwarder} object.
- * @throws IOException
- */
- public synchronized LocalPortForwarder createLocalPortForwarder(int local_port, String host_to_connect,
- int port_to_connect) throws IOException
- {
- if (tm == null)
- throw new IllegalStateException("Cannot forward ports, you need to establish a connection first.");
-
- if (!authenticated)
- throw new IllegalStateException("Cannot forward ports, connection is not authenticated.");
-
- return new LocalPortForwarder(cm, local_port, host_to_connect, port_to_connect);
- }
-
- /**
- * Creates a new {@link LocalPortForwarder}.
- * A <code>LocalPortForwarder</code> forwards TCP/IP connections that arrive at a local
- * port via the secure tunnel to another host (which may or may not be
- * identical to the remote SSH-2 server).
- * <p>
- * This method must only be called after one has passed successfully the authentication step.
- * There is no limit on the number of concurrent forwardings.
- *
- * @param addr specifies the InetSocketAddress where the local socket shall be bound to.
- * @param host_to_connect target address (IP or hostname)
- * @param port_to_connect target port
- * @return A {@link LocalPortForwarder} object.
- * @throws IOException
- */
- public synchronized LocalPortForwarder createLocalPortForwarder(InetSocketAddress addr, String host_to_connect,
- int port_to_connect) throws IOException
- {
- if (tm == null)
- throw new IllegalStateException("Cannot forward ports, you need to establish a connection first.");
-
- if (!authenticated)
- throw new IllegalStateException("Cannot forward ports, connection is not authenticated.");
-
- return new LocalPortForwarder(cm, addr, host_to_connect, port_to_connect);
- }
-
- /**
- * Creates a new {@link LocalStreamForwarder}.
- * A <code>LocalStreamForwarder</code> manages an Input/Outputstream pair
- * that is being forwarded via the secure tunnel into a TCP/IP connection to another host
- * (which may or may not be identical to the remote SSH-2 server).
- *
- * @param host_to_connect
- * @param port_to_connect
- * @return A {@link LocalStreamForwarder} object.
- * @throws IOException
- */
- public synchronized LocalStreamForwarder createLocalStreamForwarder(String host_to_connect, int port_to_connect)
- throws IOException
- {
- if (tm == null)
- throw new IllegalStateException("Cannot forward, you need to establish a connection first.");
-
- if (!authenticated)
- throw new IllegalStateException("Cannot forward, connection is not authenticated.");
-
- return new LocalStreamForwarder(cm, host_to_connect, port_to_connect);
- }
-
- /**
- * Create a very basic {@link SCPClient} that can be used to copy
- * files from/to the SSH-2 server.
- * <p>
- * Works only after one has passed successfully the authentication step.
- * There is no limit on the number of concurrent SCP clients.
- * <p>
- * Note: This factory method will probably disappear in the future.
- *
- * @return A {@link SCPClient} object.
- * @throws IOException
- */
- public synchronized SCPClient createSCPClient() throws IOException
- {
- if (tm == null)
- throw new IllegalStateException("Cannot create SCP client, you need to establish a connection first.");
-
- if (!authenticated)
- throw new IllegalStateException("Cannot create SCP client, connection is not authenticated.");
-
- return new SCPClient(this);
- }
-
- /**
- * Force an asynchronous key re-exchange (the call does not block). The
- * latest values set for MAC, Cipher and DH group exchange parameters will
- * be used. If a key exchange is currently in progress, then this method has
- * the only effect that the so far specified parameters will be used for the
- * next (server driven) key exchange.
- * <p>
- * Note: This implementation will never start a key exchange (other than the initial one)
- * unless you or the SSH-2 server ask for it.
- *
- * @throws IOException
- * In case of any failure behind the scenes.
- */
- public synchronized void forceKeyExchange() throws IOException
- {
- if (tm == null)
- throw new IllegalStateException("You need to establish a connection first.");
-
- tm.forceKeyExchange(cryptoWishList, dhgexpara, null, null);
- }
-
- /**
- * Returns the hostname that was passed to the constructor.
- *
- * @return the hostname
- */
- public synchronized String getHostname()
- {
- return hostname;
- }
-
- /**
- * Returns the port that was passed to the constructor.
- *
- * @return the TCP port
- */
- public synchronized int getPort()
- {
- return port;
- }
-
- /**
- * Returns a {@link ConnectionInfo} object containing the details of
- * the connection. Can be called as soon as the connection has been
- * established (successfully connected).
- *
- * @return A {@link ConnectionInfo} object.
- * @throws IOException
- * In case of any failure behind the scenes.
- */
- public synchronized ConnectionInfo getConnectionInfo() throws IOException
- {
- if (tm == null)
- throw new IllegalStateException(
- "Cannot get details of connection, you need to establish a connection first.");
- return tm.getConnectionInfo(1);
- }
-
- /**
- * After a successful connect, one has to authenticate oneself. This method
- * can be used to tell which authentication methods are supported by the
- * server at a certain stage of the authentication process (for the given
- * username).
- * <p>
- * Note 1: the username will only be used if no authentication step was done
- * so far (it will be used to ask the server for a list of possible
- * authentication methods by sending the initial "none" request). Otherwise,
- * this method ignores the user name and returns a cached method list
- * (which is based on the information contained in the last negative server response).
- * <p>
- * Note 2: the server may return method names that are not supported by this
- * implementation.
- * <p>
- * After a successful authentication, this method must not be called
- * anymore.
- *
- * @param user
- * A <code>String</code> holding the username.
- *
- * @return a (possibly emtpy) array holding authentication method names.
- * @throws IOException
- */
- public synchronized String[] getRemainingAuthMethods(String user) throws IOException
- {
- if (user == null)
- throw new IllegalArgumentException("user argument may not be NULL!");
-
- if (tm == null)
- throw new IllegalStateException("Connection is not established!");
-
- if (authenticated)
- throw new IllegalStateException("Connection is already authenticated!");
-
- if (am == null)
- am = new AuthenticationManager(tm);
-
- if (cm == null)
- cm = new ChannelManager(tm);
-
- return am.getRemainingMethods(user);
- }
-
- /**
- * Determines if the authentication phase is complete. Can be called at any
- * time.
- *
- * @return <code>true</code> if no further authentication steps are
- * needed.
- */
- public synchronized boolean isAuthenticationComplete()
- {
- return authenticated;
- }
-
- /**
- * Returns true if there was at least one failed authentication request and
- * the last failed authentication request was marked with "partial success"
- * by the server. This is only needed in the rare case of SSH-2 server setups
- * that cannot be satisfied with a single successful authentication request
- * (i.e., multiple authentication steps are needed.)
- * <p>
- * If you are interested in the details, then have a look at RFC4252.
- *
- * @return if the there was a failed authentication step and the last one
- * was marked as a "partial success".
- */
- public synchronized boolean isAuthenticationPartialSuccess()
- {
- if (am == null)
- return false;
-
- return am.getPartialSuccess();
- }
-
- /**
- * Checks if a specified authentication method is available. This method is
- * actually just a wrapper for {@link #getRemainingAuthMethods(String)
- * getRemainingAuthMethods()}.
- *
- * @param user
- * A <code>String</code> holding the username.
- * @param method
- * An authentication method name (e.g., "publickey", "password",
- * "keyboard-interactive") as specified by the SSH-2 standard.
- * @return if the specified authentication method is currently available.
- * @throws IOException
- */
- public synchronized boolean isAuthMethodAvailable(String user, String method) throws IOException
- {
- if (method == null)
- throw new IllegalArgumentException("method argument may not be NULL!");
-
- String methods[] = getRemainingAuthMethods(user);
-
- for (int i = 0; i < methods.length; i++)
- {
- if (methods[i].compareTo(method) == 0)
- return true;
- }
-
- return false;
- }
-
- private SecureRandom getOrCreateSecureRND()
- {
- if (generator == null)
- generator = new SecureRandom();
-
- return generator;
- }
-
- /**
- * Open a new {@link Session} on this connection. Works only after one has passed
- * successfully the authentication step. There is no limit on the number of
- * concurrent sessions.
- *
- * @return A {@link Session} object.
- * @throws IOException
- */
- public synchronized Session openSession() throws IOException
- {
- if (tm == null)
- throw new IllegalStateException("Cannot open session, you need to establish a connection first.");
-
- if (!authenticated)
- throw new IllegalStateException("Cannot open session, connection is not authenticated.");
-
- return new Session(cm, getOrCreateSecureRND());
- }
-
- /**
- * Send an SSH_MSG_IGNORE packet. This method will generate a random data attribute
- * (length between 0 (invlusive) and 16 (exclusive) bytes, contents are random bytes).
- * <p>
- * This method must only be called once the connection is established.
- *
- * @throws IOException
- */
- public synchronized void sendIgnorePacket() throws IOException
- {
- SecureRandom rnd = getOrCreateSecureRND();
-
- byte[] data = new byte[rnd.nextInt(16)];
- rnd.nextBytes(data);
-
- sendIgnorePacket(data);
- }
-
- /**
- * Send an SSH_MSG_IGNORE packet with the given data attribute.
- * <p>
- * This method must only be called once the connection is established.
- *
- * @throws IOException
- */
- public synchronized void sendIgnorePacket(byte[] data) throws IOException
- {
- if (data == null)
- throw new IllegalArgumentException("data argument must not be null.");
-
- if (tm == null)
- throw new IllegalStateException(
- "Cannot send SSH_MSG_IGNORE packet, you need to establish a connection first.");
-
- PacketIgnore pi = new PacketIgnore();
- pi.setData(data);
-
- tm.sendMessage(pi.getPayload());
- }
-
- /**
- * Removes duplicates from a String array, keeps only first occurence
- * of each element. Does not destroy order of elements; can handle nulls.
- * Uses a very efficient O(N^2) algorithm =)
- *
- * @param list a String array.
- * @return a cleaned String array.
- */
- private String[] removeDuplicates(String[] list)
- {
- if ((list == null) || (list.length < 2))
- return list;
-
- String[] list2 = new String[list.length];
-
- int count = 0;
-
- for (int i = 0; i < list.length; i++)
- {
- boolean duplicate = false;
-
- String element = list[i];
-
- for (int j = 0; j < count; j++)
- {
- if (((element == null) && (list2[j] == null)) || ((element != null) && (element.equals(list2[j]))))
- {
- duplicate = true;
- break;
- }
- }
-
- if (duplicate)
- continue;
-
- list2[count++] = list[i];
- }
-
- if (count == list2.length)
- return list2;
-
- String[] tmp = new String[count];
- System.arraycopy(list2, 0, tmp, 0, count);
-
- return tmp;
- }
-
- /**
- * Unless you know what you are doing, you will never need this.
- *
- * @param ciphers
- */
- public synchronized void setClient2ServerCiphers(String[] ciphers)
- {
- if ((ciphers == null) || (ciphers.length == 0))
- throw new IllegalArgumentException();
- ciphers = removeDuplicates(ciphers);
- BlockCipherFactory.checkCipherList(ciphers);
- cryptoWishList.c2s_enc_algos = ciphers;
- }
-
- /**
- * Unless you know what you are doing, you will never need this.
- *
- * @param macs
- */
- public synchronized void setClient2ServerMACs(String[] macs)
- {
- if ((macs == null) || (macs.length == 0))
- throw new IllegalArgumentException();
- macs = removeDuplicates(macs);
- MAC.checkMacList(macs);
- cryptoWishList.c2s_mac_algos = macs;
- }
-
- /**
- * Sets the parameters for the diffie-hellman group exchange. Unless you
- * know what you are doing, you will never need this. Default values are
- * defined in the {@link DHGexParameters} class.
- *
- * @param dgp {@link DHGexParameters}, non null.
- *
- */
- public synchronized void setDHGexParameters(DHGexParameters dgp)
- {
- if (dgp == null)
- throw new IllegalArgumentException();
-
- dhgexpara = dgp;
- }
-
- /**
- * Unless you know what you are doing, you will never need this.
- *
- * @param ciphers
- */
- public synchronized void setServer2ClientCiphers(String[] ciphers)
- {
- if ((ciphers == null) || (ciphers.length == 0))
- throw new IllegalArgumentException();
- ciphers = removeDuplicates(ciphers);
- BlockCipherFactory.checkCipherList(ciphers);
- cryptoWishList.s2c_enc_algos = ciphers;
- }
-
- /**
- * Unless you know what you are doing, you will never need this.
- *
- * @param macs
- */
- public synchronized void setServer2ClientMACs(String[] macs)
- {
- if ((macs == null) || (macs.length == 0))
- throw new IllegalArgumentException();
-
- macs = removeDuplicates(macs);
- MAC.checkMacList(macs);
- cryptoWishList.s2c_mac_algos = macs;
- }
-
- /**
- * Define the set of allowed server host key algorithms to be used for
- * the following key exchange operations.
- * <p>
- * Unless you know what you are doing, you will never need this.
- *
- * @param algos An array of allowed server host key algorithms.
- * SSH-2 defines <code>ssh-dss</code> and <code>ssh-rsa</code>.
- * The entries of the array must be ordered after preference, i.e.,
- * the entry at index 0 is the most preferred one. You must specify
- * at least one entry.
- */
- public synchronized void setServerHostKeyAlgorithms(String[] algos)
- {
- if ((algos == null) || (algos.length == 0))
- throw new IllegalArgumentException();
-
- algos = removeDuplicates(algos);
- KexManager.checkServerHostkeyAlgorithmsList(algos);
- cryptoWishList.serverHostKeyAlgorithms = algos;
- }
-
- /**
- * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) on the underlying socket.
- * <p>
- * Can be called at any time. If the connection has not yet been established
- * then the passed value will be stored and set after the socket has been set up.
- * The default value that will be used is <code>false</code>.
- *
- * @param enable the argument passed to the <code>Socket.setTCPNoDelay()</code> method.
- * @throws IOException
- */
- public synchronized void setTCPNoDelay(boolean enable) throws IOException
- {
- tcpNoDelay = enable;
-
- if (tm != null)
- tm.setTcpNoDelay(enable);
- }
-
- /**
- * Used to tell the library that the connection shall be established through a proxy server.
- * It only makes sense to call this method before calling the {@link #connect() connect()}
- * method.
- * <p>
- * At the moment, only HTTP proxies are supported.
- * <p>
- * Note: This method can be called any number of times. The {@link #connect() connect()}
- * method will use the value set in the last preceding invocation of this method.
- *
- * @see HTTPProxyData
- *
- * @param proxyData Connection information about the proxy. If <code>null</code>, then
- * no proxy will be used (non surprisingly, this is also the default).
- */
- public synchronized void setProxyData(ProxyData proxyData)
- {
- this.proxyData = proxyData;
- }
-
- /**
- * Request a remote port forwarding.
- * If successful, then forwarded connections will be redirected to the given target address.
- * You can cancle a requested remote port forwarding by calling
- * {@link #cancelRemotePortForwarding(int) cancelRemotePortForwarding()}.
- * <p>
- * A call of this method will block until the peer either agreed or disagreed to your request-
- * <p>
- * Note 1: this method typically fails if you
- * <ul>
- * <li>pass a port number for which the used remote user has not enough permissions (i.e., port
- * < 1024)</li>
- * <li>or pass a port number that is already in use on the remote server</li>
- * <li>or if remote port forwarding is disabled on the server.</li>
- * </ul>
- * <p>
- * Note 2: (from the openssh man page): By default, the listening socket on the server will be
- * bound to the loopback interface only. This may be overriden by specifying a bind address.
- * Specifying a remote bind address will only succeed if the server's <b>GatewayPorts</b> option
- * is enabled (see sshd_config(5)).
- *
- * @param bindAddress address to bind to on the server:
- * <ul>
- * <li>"" means that connections are to be accepted on all protocol families
- * supported by the SSH implementation</li>
- * <li>"0.0.0.0" means to listen on all IPv4 addresses</li>
- * <li>"::" means to listen on all IPv6 addresses</li>
- * <li>"localhost" means to listen on all protocol families supported by the SSH
- * implementation on loopback addresses only, [RFC3330] and RFC3513]</li>
- * <li>"127.0.0.1" and "::1" indicate listening on the loopback interfaces for
- * IPv4 and IPv6 respectively</li>
- * </ul>
- * @param bindPort port number to bind on the server (must be > 0)
- * @param targetAddress the target address (IP or hostname)
- * @param targetPort the target port
- * @throws IOException
- */
- public synchronized void requestRemotePortForwarding(String bindAddress, int bindPort, String targetAddress,
- int targetPort) throws IOException
- {
- if (tm == null)
- throw new IllegalStateException("You need to establish a connection first.");
-
- if (!authenticated)
- throw new IllegalStateException("The connection is not authenticated.");
-
- if ((bindAddress == null) || (targetAddress == null) || (bindPort <= 0) || (targetPort <= 0))
- throw new IllegalArgumentException();
-
- cm.requestGlobalForward(bindAddress, bindPort, targetAddress, targetPort);
- }
-
- /**
- * Cancel an earlier requested remote port forwarding.
- * Currently active forwardings will not be affected (e.g., disrupted).
- * Note that further connection forwarding requests may be received until
- * this method has returned.
- *
- * @param bindPort the allocated port number on the server
- * @throws IOException if the remote side refuses the cancel request or another low
- * level error occurs (e.g., the underlying connection is closed)
- */
- public synchronized void cancelRemotePortForwarding(int bindPort) throws IOException
- {
- if (tm == null)
- throw new IllegalStateException("You need to establish a connection first.");
-
- if (!authenticated)
- throw new IllegalStateException("The connection is not authenticated.");
-
- cm.requestCancelGlobalForward(bindPort);
- }
-
- /**
- * Provide your own instance of SecureRandom. Can be used, e.g., if you
- * want to seed the used SecureRandom generator manually.
- * <p>
- * The SecureRandom instance is used during key exchanges, public key authentication,
- * x11 cookie generation and the like.
- *
- * @param rnd a SecureRandom instance
- */
- public synchronized void setSecureRandom(SecureRandom rnd)
- {
- if (rnd == null)
- throw new IllegalArgumentException();
-
- this.generator = rnd;
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2006-2013 Christian Plattner. All rights reserved.
- * Please refer to the LICENSE.txt for licensing details.
- */
-
-package ch.ethz.ssh2.channel;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Vector;
-
-import ch.ethz.ssh2.ChannelCondition;
-import ch.ethz.ssh2.PtySettings;
-import ch.ethz.ssh2.ServerConnectionCallback;
-import ch.ethz.ssh2.ServerSessionCallback;
-import ch.ethz.ssh2.log.Logger;
-import ch.ethz.ssh2.packets.PacketChannelFailure;
-import ch.ethz.ssh2.packets.PacketChannelOpenConfirmation;
-import ch.ethz.ssh2.packets.PacketChannelOpenFailure;
-import ch.ethz.ssh2.packets.PacketChannelSuccess;
-import ch.ethz.ssh2.packets.PacketGlobalCancelForwardRequest;
-import ch.ethz.ssh2.packets.PacketGlobalForwardRequest;
-import ch.ethz.ssh2.packets.PacketOpenDirectTCPIPChannel;
-import ch.ethz.ssh2.packets.PacketOpenSessionChannel;
-import ch.ethz.ssh2.packets.PacketSessionExecCommand;
-import ch.ethz.ssh2.packets.PacketSessionPtyRequest;
-import ch.ethz.ssh2.packets.PacketSessionStartShell;
-import ch.ethz.ssh2.packets.PacketSessionSubsystemRequest;
-import ch.ethz.ssh2.packets.PacketSessionX11Request;
-import ch.ethz.ssh2.packets.Packets;
-import ch.ethz.ssh2.packets.TypesReader;
-import ch.ethz.ssh2.server.ServerConnectionState;
-import ch.ethz.ssh2.transport.MessageHandler;
-import ch.ethz.ssh2.transport.TransportManager;
-
-/**
- * ChannelManager. Please read the comments in Channel.java.
- * <p/>
- * Besides the crypto part, this is the core of the library.
- *
- * @author Christian Plattner
- * @version $Id: ChannelManager.java 48 2013-08-01 12:22:33Z cleondris@gmail.com $
- */
-public class ChannelManager implements MessageHandler
-{
- private static final Logger log = Logger.getLogger(ChannelManager.class);
-
- private final ServerConnectionState server_state;
- private final TransportManager tm;
-
- private final HashMap<String, X11ServerData> x11_magic_cookies = new HashMap<String, X11ServerData>();
-
- private final List<Channel> channels = new Vector<Channel>();
- private int nextLocalChannel = 100;
- private boolean shutdown = false;
- private int globalSuccessCounter = 0;
- private int globalFailedCounter = 0;
-
- private final HashMap<Integer, RemoteForwardingData> remoteForwardings = new HashMap<Integer, RemoteForwardingData>();
-
- private final List<IChannelWorkerThread> listenerThreads = new Vector<IChannelWorkerThread>();
-
- private boolean listenerThreadsAllowed = true;
-
- /**
- * Constructor for client-mode.
- * @param tm
- */
- public ChannelManager(TransportManager tm)
- {
- this.server_state = null;
- this.tm = tm;
- tm.registerMessageHandler(this, 80, 100);
- }
-
- /**
- * Constructor for server-mode.
- * @param state
- */
- public ChannelManager(ServerConnectionState state)
- {
- this.server_state = state;
- this.tm = state.tm;
- tm.registerMessageHandler(this, 80, 100);
- }
-
- private Channel getChannel(int id)
- {
- synchronized (channels)
- {
- for (Channel c : channels)
- {
- if (c.localID == id)
- return c;
- }
- }
- return null;
- }
-
- private void removeChannel(int id)
- {
- synchronized (channels)
- {
- for (Channel c : channels)
- {
- if (c.localID == id)
- {
- channels.remove(c);
- break;
- }
- }
- }
- }
-
- private int addChannel(Channel c)
- {
- synchronized (channels)
- {
- channels.add(c);
- return nextLocalChannel++;
- }
- }
-
- private void waitUntilChannelOpen(Channel c) throws IOException
- {
- boolean wasInterrupted = false;
-
- synchronized (c)
- {
- while (c.state == Channel.STATE_OPENING)
- {
- try
- {
- c.wait();
- }
- catch (InterruptedException ignore)
- {
- wasInterrupted = true;
- }
- }
-
- if (c.state != Channel.STATE_OPEN)
- {
- removeChannel(c.localID);
-
- String detail = c.getReasonClosed();
-
- if (detail == null)
- detail = "state: " + c.state;
-
- throw new IOException("Could not open channel (" + detail + ")");
- }
- }
-
- if (wasInterrupted)
- Thread.currentThread().interrupt();
- }
-
- private void waitForGlobalSuccessOrFailure() throws IOException
- {
- boolean wasInterrupted = false;
-
- try
- {
- synchronized (channels)
- {
- while ((globalSuccessCounter == 0) && (globalFailedCounter == 0))
- {
- if (shutdown)
- {
- throw new IOException("The connection is being shutdown");
- }
-
- try
- {
- channels.wait();
- }
- catch (InterruptedException ignore)
- {
- wasInterrupted = true;
- }
- }
-
- if (globalFailedCounter != 0)
- {
- throw new IOException("The server denied the request (did you enable port forwarding?)");
- }
-
- if (globalSuccessCounter == 0)
- {
- throw new IOException("Illegal state.");
- }
- }
- }
- finally
- {
- if (wasInterrupted)
- Thread.currentThread().interrupt();
- }
- }
-
- private void waitForChannelSuccessOrFailure(Channel c) throws IOException
- {
- boolean wasInterrupted = false;
-
- try
- {
- synchronized (c)
- {
- while ((c.successCounter == 0) && (c.failedCounter == 0))
- {
- if (c.state != Channel.STATE_OPEN)
- {
- String detail = c.getReasonClosed();
-
- if (detail == null)
- detail = "state: " + c.state;
-
- throw new IOException("This SSH2 channel is not open (" + detail + ")");
- }
-
- try
- {
- c.wait();
- }
- catch (InterruptedException ignore)
- {
- wasInterrupted = true;
- }
- }
-
- if (c.failedCounter != 0)
- {
- throw new IOException("The server denied the request.");
- }
- }
- }
- finally
- {
- if (wasInterrupted)
- Thread.currentThread().interrupt();
- }
- }
-
- public void registerX11Cookie(String hexFakeCookie, X11ServerData data)
- {
- synchronized (x11_magic_cookies)
- {
- x11_magic_cookies.put(hexFakeCookie, data);
- }
- }
-
- public void unRegisterX11Cookie(String hexFakeCookie, boolean killChannels)
- {
- if (hexFakeCookie == null)
- throw new IllegalStateException("hexFakeCookie may not be null");
-
- synchronized (x11_magic_cookies)
- {
- x11_magic_cookies.remove(hexFakeCookie);
- }
-
- if (killChannels == false)
- return;
-
- log.debug("Closing all X11 channels for the given fake cookie");
-
- List<Channel> channel_copy = new Vector<Channel>();
-
- synchronized (channels)
- {
- channel_copy.addAll(channels);
- }
-
- for (Channel c : channel_copy)
- {
- synchronized (c)
- {
- if (hexFakeCookie.equals(c.hexX11FakeCookie) == false)
- continue;
- }
-
- try
- {
- closeChannel(c, "Closing X11 channel since the corresponding session is closing", true);
- }
- catch (IOException ignored)
- {
- }
- }
- }
-
- public X11ServerData checkX11Cookie(String hexFakeCookie)
- {
- synchronized (x11_magic_cookies)
- {
- if (hexFakeCookie != null)
- return x11_magic_cookies.get(hexFakeCookie);
- }
- return null;
- }
-
- public void closeAllChannels()
- {
- log.debug("Closing all channels");
-
- List<Channel> channel_copy = new Vector<Channel>();
-
- synchronized (channels)
- {
- channel_copy.addAll(channels);
- }
-
- for (Channel c : channel_copy)
- {
- try
- {
- closeChannel(c, "Closing all channels", true);
- }
- catch (IOException ignored)
- {
- }
- }
- }
-
- public void closeChannel(Channel c, String reason, boolean force) throws IOException
- {
- byte msg[] = new byte[5];
-
- synchronized (c)
- {
- if (force)
- {
- c.state = Channel.STATE_CLOSED;
- c.EOF = true;
- }
-
- c.setReasonClosed(reason);
-
- msg[0] = Packets.SSH_MSG_CHANNEL_CLOSE;
- msg[1] = (byte) (c.remoteID >> 24);
- msg[2] = (byte) (c.remoteID >> 16);
- msg[3] = (byte) (c.remoteID >> 8);
- msg[4] = (byte) (c.remoteID);
-
- c.notifyAll();
- }
-
- synchronized (c.channelSendLock)
- {
- if (c.closeMessageSent == true)
- return;
- tm.sendMessage(msg);
- c.closeMessageSent = true;
- }
-
- log.debug("Sent SSH_MSG_CHANNEL_CLOSE (channel " + c.localID + ")");
- }
-
- public void sendEOF(Channel c) throws IOException
- {
- byte[] msg = new byte[5];
-
- synchronized (c)
- {
- if (c.state != Channel.STATE_OPEN)
- return;
-
- msg[0] = Packets.SSH_MSG_CHANNEL_EOF;
- msg[1] = (byte) (c.remoteID >> 24);
- msg[2] = (byte) (c.remoteID >> 16);
- msg[3] = (byte) (c.remoteID >> 8);
- msg[4] = (byte) (c.remoteID);
- }
-
- synchronized (c.channelSendLock)
- {
- if (c.closeMessageSent == true)
- return;
- tm.sendMessage(msg);
- }
-
-
- log.debug("Sent EOF (Channel " + c.localID + "/" + c.remoteID + ")");
- }
-
- public void sendOpenConfirmation(Channel c) throws IOException
- {
- PacketChannelOpenConfirmation pcoc = null;
-
- synchronized (c)
- {
- if (c.state != Channel.STATE_OPENING)
- return;
-
- c.state = Channel.STATE_OPEN;
-
- pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, c.localWindow, c.localMaxPacketSize);
- }
-
- synchronized (c.channelSendLock)
- {
- if (c.closeMessageSent == true)
- return;
- tm.sendMessage(pcoc.getPayload());
- }
- }
-
- public void sendData(Channel c, byte[] buffer, int pos, int len) throws IOException
- {
- boolean wasInterrupted = false;
-
- try
- {
- while (len > 0)
- {
- int thislen = 0;
- byte[] msg;
-
- synchronized (c)
- {
- while (true)
- {
- if (c.state == Channel.STATE_CLOSED)
- throw new ChannelClosedException("SSH channel is closed. (" + c.getReasonClosed() + ")");
-
- if (c.state != Channel.STATE_OPEN)
- throw new ChannelClosedException("SSH channel in strange state. (" + c.state + ")");
-
- if (c.remoteWindow != 0)
- break;
-
- try
- {
- c.wait();
- }
- catch (InterruptedException ignore)
- {
- wasInterrupted = true;
- }
- }
-
- /* len > 0, no sign extension can happen when comparing */
-
- thislen = (c.remoteWindow >= len) ? len : (int) c.remoteWindow;
-
- int estimatedMaxDataLen = c.remoteMaxPacketSize - (tm.getPacketOverheadEstimate() + 9);
-
- /* The worst case scenario =) a true bottleneck */
-
- if (estimatedMaxDataLen <= 0)
- {
- estimatedMaxDataLen = 1;
- }
-
- if (thislen > estimatedMaxDataLen)
- thislen = estimatedMaxDataLen;
-
- c.remoteWindow -= thislen;
-
- msg = new byte[1 + 8 + thislen];
-
- msg[0] = Packets.SSH_MSG_CHANNEL_DATA;
- msg[1] = (byte) (c.remoteID >> 24);
- msg[2] = (byte) (c.remoteID >> 16);
- msg[3] = (byte) (c.remoteID >> 8);
- msg[4] = (byte) (c.remoteID);
- msg[5] = (byte) (thislen >> 24);
- msg[6] = (byte) (thislen >> 16);
- msg[7] = (byte) (thislen >> 8);
- msg[8] = (byte) (thislen);
-
- System.arraycopy(buffer, pos, msg, 9, thislen);
- }
-
- synchronized (c.channelSendLock)
- {
- if (c.closeMessageSent == true)
- throw new ChannelClosedException("SSH channel is closed. (" + c.getReasonClosed() + ")");
-
- tm.sendMessage(msg);
- }
-
- pos += thislen;
- len -= thislen;
- }
- }
- finally
- {
- if (wasInterrupted)
- Thread.currentThread().interrupt();
- }
- }
-
- public int requestGlobalForward(String bindAddress, int bindPort, String targetAddress, int targetPort)
- throws IOException
- {
- RemoteForwardingData rfd = new RemoteForwardingData();
-
- rfd.bindAddress = bindAddress;
- rfd.bindPort = bindPort;
- rfd.targetAddress = targetAddress;
- rfd.targetPort = targetPort;
-
- synchronized (remoteForwardings)
- {
- Integer key = new Integer(bindPort);
-
- if (remoteForwardings.get(key) != null)
- {
- throw new IOException("There is already a forwarding for remote port " + bindPort);
- }
-
- remoteForwardings.put(key, rfd);
- }
-
- synchronized (channels)
- {
- globalSuccessCounter = globalFailedCounter = 0;
- }
-
- PacketGlobalForwardRequest pgf = new PacketGlobalForwardRequest(true, bindAddress, bindPort);
- tm.sendMessage(pgf.getPayload());
-
- log.debug("Requesting a remote forwarding ('" + bindAddress + "', " + bindPort + ")");
-
- try
- {
- waitForGlobalSuccessOrFailure();
- }
- catch (IOException e)
- {
- synchronized (remoteForwardings)
- {
- remoteForwardings.remove(rfd);
- }
- throw e;
- }
-
- return bindPort;
- }
-
- public void requestCancelGlobalForward(int bindPort) throws IOException
- {
- RemoteForwardingData rfd = null;
-
- synchronized (remoteForwardings)
- {
- rfd = remoteForwardings.get(new Integer(bindPort));
-
- if (rfd == null)
- throw new IOException("Sorry, there is no known remote forwarding for remote port " + bindPort);
- }
-
- synchronized (channels)
- {
- globalSuccessCounter = globalFailedCounter = 0;
- }
-
- PacketGlobalCancelForwardRequest pgcf = new PacketGlobalCancelForwardRequest(true, rfd.bindAddress,
- rfd.bindPort);
- tm.sendMessage(pgcf.getPayload());
-
- log.debug("Requesting cancelation of remote forward ('" + rfd.bindAddress + "', " + rfd.bindPort + ")");
-
- waitForGlobalSuccessOrFailure();
-
- /* Only now we are sure that no more forwarded connections will arrive */
-
- synchronized (remoteForwardings)
- {
- remoteForwardings.remove(rfd);
- }
- }
-
- public void registerThread(IChannelWorkerThread thr) throws IOException
- {
- synchronized (listenerThreads)
- {
- if (listenerThreadsAllowed == false)
- throw new IOException("Too late, this connection is closed.");
- listenerThreads.add(thr);
- }
- }
-
- public Channel openDirectTCPIPChannel(String host_to_connect, int port_to_connect, String originator_IP_address,
- int originator_port) throws IOException
- {
- Channel c = new Channel(this);
-
- synchronized (c)
- {
- c.localID = addChannel(c);
- // end of synchronized block forces writing out to main memory
- }
-
- PacketOpenDirectTCPIPChannel dtc = new PacketOpenDirectTCPIPChannel(c.localID, c.localWindow,
- c.localMaxPacketSize, host_to_connect, port_to_connect, originator_IP_address, originator_port);
-
- tm.sendMessage(dtc.getPayload());
-
- waitUntilChannelOpen(c);
-
- return c;
- }
-
- public Channel openSessionChannel() throws IOException
- {
- Channel c = new Channel(this);
-
- synchronized (c)
- {
- c.localID = addChannel(c);
- // end of synchronized block forces the writing out to main memory
- }
-
- log.debug("Sending SSH_MSG_CHANNEL_OPEN (Channel " + c.localID + ")");
-
- PacketOpenSessionChannel smo = new PacketOpenSessionChannel(c.localID, c.localWindow, c.localMaxPacketSize);
- tm.sendMessage(smo.getPayload());
-
- waitUntilChannelOpen(c);
-
- return c;
- }
-
- public void requestPTY(Channel c, String term, int term_width_characters, int term_height_characters,
- int term_width_pixels, int term_height_pixels, byte[] terminal_modes) throws IOException
- {
- PacketSessionPtyRequest spr;
-
- synchronized (c)
- {
- if (c.state != Channel.STATE_OPEN)
- throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")");
-
- spr = new PacketSessionPtyRequest(c.remoteID, true, term, term_width_characters, term_height_characters,
- term_width_pixels, term_height_pixels, terminal_modes);
-
- c.successCounter = c.failedCounter = 0;
- }
-
- synchronized (c.channelSendLock)
- {
- if (c.closeMessageSent)
- throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")");
- tm.sendMessage(spr.getPayload());
- }
-
- try
- {
- waitForChannelSuccessOrFailure(c);
- }
- catch (IOException e)
- {
- throw (IOException) new IOException("PTY request failed").initCause(e);
- }
- }
-
- public void requestX11(Channel c, boolean singleConnection, String x11AuthenticationProtocol,
- String x11AuthenticationCookie, int x11ScreenNumber) throws IOException
- {
- PacketSessionX11Request psr;
-
- synchronized (c)
- {
- if (c.state != Channel.STATE_OPEN)
- throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")");
-
- psr = new PacketSessionX11Request(c.remoteID, true, singleConnection, x11AuthenticationProtocol,
- x11AuthenticationCookie, x11ScreenNumber);
-
- c.successCounter = c.failedCounter = 0;
- }
-
- synchronized (c.channelSendLock)
- {
- if (c.closeMessageSent)
- throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")");
- tm.sendMessage(psr.getPayload());
- }
-
- log.debug("Requesting X11 forwarding (Channel " + c.localID + "/" + c.remoteID + ")");
-
- try
- {
- waitForChannelSuccessOrFailure(c);
- }
- catch (IOException e)
- {
- throw (IOException) new IOException("The X11 request failed.").initCause(e);
- }
- }
-
- public void requestSubSystem(Channel c, String subSystemName) throws IOException
- {
- PacketSessionSubsystemRequest ssr;
-
- synchronized (c)
- {
- if (c.state != Channel.STATE_OPEN)
- throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")");
-
- ssr = new PacketSessionSubsystemRequest(c.remoteID, true, subSystemName);
-
- c.successCounter = c.failedCounter = 0;
- }
-
- synchronized (c.channelSendLock)
- {
- if (c.closeMessageSent)
- throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")");
- tm.sendMessage(ssr.getPayload());
- }
-
- try
- {
- waitForChannelSuccessOrFailure(c);
- }
- catch (IOException e)
- {
- throw (IOException) new IOException("The subsystem request failed.").initCause(e);
- }
- }
-
- public void requestExecCommand(Channel c, String cmd) throws IOException
- {
- this.requestExecCommand(c, cmd, null);
- }
-
- /**
- * @param charsetName The charset used to convert between Java Unicode Strings and byte encodings
- */
- public void requestExecCommand(Channel c, String cmd, String charsetName) throws IOException
- {
- PacketSessionExecCommand sm;
-
- synchronized (c)
- {
- if (c.state != Channel.STATE_OPEN)
- throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")");
-
- sm = new PacketSessionExecCommand(c.remoteID, true, cmd);
-
- c.successCounter = c.failedCounter = 0;
- }
-
- synchronized (c.channelSendLock)
- {
- if (c.closeMessageSent)
- throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")");
- tm.sendMessage(sm.getPayload(charsetName));
- }
-
- log.debug("Executing command (channel " + c.localID + ", '" + cmd + "')");
-
- try
- {
- waitForChannelSuccessOrFailure(c);
- }
- catch (IOException e)
- {
- throw (IOException) new IOException("The execute request failed.").initCause(e);
- }
- }
-
- public void requestShell(Channel c) throws IOException
- {
- PacketSessionStartShell sm;
-
- synchronized (c)
- {
- if (c.state != Channel.STATE_OPEN)
- throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")");
-
- sm = new PacketSessionStartShell(c.remoteID, true);
-
- c.successCounter = c.failedCounter = 0;
- }
-
- synchronized (c.channelSendLock)
- {
- if (c.closeMessageSent)
- throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")");
- tm.sendMessage(sm.getPayload());
- }
-
- try
- {
- waitForChannelSuccessOrFailure(c);
- }
- catch (IOException e)
- {
- throw (IOException) new IOException("The shell request failed.").initCause(e);
- }
- }
-
- public void msgChannelExtendedData(byte[] msg, int msglen) throws IOException
- {
- if (msglen <= 13)
- throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong size (" + msglen + ")");
-
- int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
- int dataType = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff);
- int len = ((msg[9] & 0xff) << 24) | ((msg[10] & 0xff) << 16) | ((msg[11] & 0xff) << 8) | (msg[12] & 0xff);
-
- Channel c = getChannel(id);
-
- if (c == null)
- throw new IOException("Unexpected SSH_MSG_CHANNEL_EXTENDED_DATA message for non-existent channel " + id);
-
- if (dataType != Packets.SSH_EXTENDED_DATA_STDERR)
- throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has unknown type (" + dataType + ")");
-
- if (len != (msglen - 13))
- throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong len (calculated " + (msglen - 13)
- + ", got " + len + ")");
-
- log.debug("Got SSH_MSG_CHANNEL_EXTENDED_DATA (channel " + id + ", " + len + ")");
-
- synchronized (c)
- {
- if (c.state == Channel.STATE_CLOSED)
- return; // ignore
-
- if (c.state != Channel.STATE_OPEN)
- throw new IOException("Got SSH_MSG_CHANNEL_EXTENDED_DATA, but channel is not in correct state ("
- + c.state + ")");
-
- if (c.localWindow < len)
- throw new IOException("Remote sent too much data, does not fit into window.");
-
- c.localWindow -= len;
-
- System.arraycopy(msg, 13, c.stderrBuffer, c.stderrWritepos, len);
- c.stderrWritepos += len;
-
- c.notifyAll();
- }
- }
-
- /**
- * Wait until for a condition.
- *
- * @param c Channel
- * @param timeout in ms, 0 means no timeout.
- * @param condition_mask minimum event mask (at least one of the conditions must be fulfilled)
- * @return all current events
- */
- public int waitForCondition(Channel c, long timeout, int condition_mask)
- {
- boolean wasInterrupted = false;
-
- try
- {
- long end_time = 0;
- boolean end_time_set = false;
-
- synchronized (c)
- {
- while (true)
- {
- int current_cond = 0;
-
- int stdoutAvail = c.stdoutWritepos - c.stdoutReadpos;
- int stderrAvail = c.stderrWritepos - c.stderrReadpos;
-
- if (stdoutAvail > 0)
- current_cond = current_cond | ChannelCondition.STDOUT_DATA;
-
- if (stderrAvail > 0)
- current_cond = current_cond | ChannelCondition.STDERR_DATA;
-
- if (c.EOF)
- current_cond = current_cond | ChannelCondition.EOF;
-
- if (c.getExitStatus() != null)
- current_cond = current_cond | ChannelCondition.EXIT_STATUS;
-
- if (c.getExitSignal() != null)
- current_cond = current_cond | ChannelCondition.EXIT_SIGNAL;
-
- if (c.state == Channel.STATE_CLOSED)
- return current_cond | ChannelCondition.CLOSED | ChannelCondition.EOF;
-
- if ((current_cond & condition_mask) != 0)
- return current_cond;
-
- if (timeout > 0)
- {
- if (!end_time_set)
- {
- end_time = System.currentTimeMillis() + timeout;
- end_time_set = true;
- }
- else
- {
- timeout = end_time - System.currentTimeMillis();
-
- if (timeout <= 0)
- return current_cond | ChannelCondition.TIMEOUT;
- }
- }
-
- try
- {
- if (timeout > 0)
- c.wait(timeout);
- else
- c.wait();
- }
- catch (InterruptedException e)
- {
- wasInterrupted = true;
- }
- }
- }
- }
- finally
- {
- if (wasInterrupted)
- Thread.currentThread().interrupt();
- }
- }
-
- public int getAvailable(Channel c, boolean extended) throws IOException
- {
- synchronized (c)
- {
- int avail;
-
- if (extended)
- avail = c.stderrWritepos - c.stderrReadpos;
- else
- avail = c.stdoutWritepos - c.stdoutReadpos;
-
- return ((avail > 0) ? avail : (c.EOF ? -1 : 0));
- }
- }
-
- public int getChannelData(Channel c, boolean extended, byte[] target, int off, int len) throws IOException
- {
- boolean wasInterrupted = false;
-
- try
- {
- int copylen = 0;
- int increment = 0;
- int remoteID = 0;
- int localID = 0;
-
- synchronized (c)
- {
- int stdoutAvail = 0;
- int stderrAvail = 0;
-
- while (true)
- {
- /*
- * Data available? We have to return remaining data even if the
- * channel is already closed.
- */
-
- stdoutAvail = c.stdoutWritepos - c.stdoutReadpos;
- stderrAvail = c.stderrWritepos - c.stderrReadpos;
-
- if ((!extended) && (stdoutAvail != 0))
- break;
-
- if ((extended) && (stderrAvail != 0))
- break;
-
- /* Do not wait if more data will never arrive (EOF or CLOSED) */
-
- if ((c.EOF) || (c.state != Channel.STATE_OPEN))
- return -1;
-
- try
- {
- c.wait();
- }
- catch (InterruptedException ignore)
- {
- wasInterrupted = true;
- }
- }
-
- /* OK, there is some data. Return it. */
-
- if (!extended)
- {
- copylen = (stdoutAvail > len) ? len : stdoutAvail;
- System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, target, off, copylen);
- c.stdoutReadpos += copylen;
-
- if (c.stdoutReadpos != c.stdoutWritepos)
-
- System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, c.stdoutBuffer, 0, c.stdoutWritepos
- - c.stdoutReadpos);
-
- c.stdoutWritepos -= c.stdoutReadpos;
- c.stdoutReadpos = 0;
- }
- else
- {
- copylen = (stderrAvail > len) ? len : stderrAvail;
- System.arraycopy(c.stderrBuffer, c.stderrReadpos, target, off, copylen);
- c.stderrReadpos += copylen;
-
- if (c.stderrReadpos != c.stderrWritepos)
-
- System.arraycopy(c.stderrBuffer, c.stderrReadpos, c.stderrBuffer, 0, c.stderrWritepos
- - c.stderrReadpos);
-
- c.stderrWritepos -= c.stderrReadpos;
- c.stderrReadpos = 0;
- }
-
- if (c.state != Channel.STATE_OPEN)
- return copylen;
-
- if (c.localWindow < ((Channel.CHANNEL_BUFFER_SIZE + 1) / 2))
- {
- int minFreeSpace = Math.min(Channel.CHANNEL_BUFFER_SIZE - c.stdoutWritepos,
- Channel.CHANNEL_BUFFER_SIZE - c.stderrWritepos);
-
- increment = minFreeSpace - c.localWindow;
- c.localWindow = minFreeSpace;
- }
-
- remoteID = c.remoteID; /* read while holding the lock */
- localID = c.localID; /* read while holding the lock */
- }
-
- /*
- * If a consumer reads stdout and stdin in parallel, we may end up with
- * sending two msgWindowAdjust messages. Luckily, it
- * does not matter in which order they arrive at the server.
- */
-
- if (increment > 0)
- {
- log.debug("Sending SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + localID + ", " + increment + ")");
-
- synchronized (c.channelSendLock)
- {
- byte[] msg = c.msgWindowAdjust;
-
- msg[0] = Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST;
- msg[1] = (byte) (remoteID >> 24);
- msg[2] = (byte) (remoteID >> 16);
- msg[3] = (byte) (remoteID >> 8);
- msg[4] = (byte) (remoteID);
- msg[5] = (byte) (increment >> 24);
- msg[6] = (byte) (increment >> 16);
- msg[7] = (byte) (increment >> 8);
- msg[8] = (byte) (increment);
-
- if (c.closeMessageSent == false)
- tm.sendMessage(msg);
- }
- }
-
- return copylen;
- }
- finally
- {
- if (wasInterrupted)
- Thread.currentThread().interrupt();
- }
-
- }
-
- public void msgChannelData(byte[] msg, int msglen) throws IOException
- {
- if (msglen <= 9)
- throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong size (" + msglen + ")");
-
- int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
- int len = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff);
-
- Channel c = getChannel(id);
-
- if (c == null)
- throw new IOException("Unexpected SSH_MSG_CHANNEL_DATA message for non-existent channel " + id);
-
- if (len != (msglen - 9))
- throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong len (calculated " + (msglen - 9) + ", got "
- + len + ")");
-
- log.debug("Got SSH_MSG_CHANNEL_DATA (channel " + id + ", " + len + ")");
-
- synchronized (c)
- {
- if (c.state == Channel.STATE_CLOSED)
- return; // ignore
-
- if (c.state != Channel.STATE_OPEN)
- throw new IOException("Got SSH_MSG_CHANNEL_DATA, but channel is not in correct state (" + c.state + ")");
-
- if (c.localWindow < len)
- throw new IOException("Remote sent too much data, does not fit into window.");
-
- c.localWindow -= len;
-
- System.arraycopy(msg, 9, c.stdoutBuffer, c.stdoutWritepos, len);
- c.stdoutWritepos += len;
-
- c.notifyAll();
- }
- }
-
- public void msgChannelWindowAdjust(byte[] msg, int msglen) throws IOException
- {
- if (msglen != 9)
- throw new IOException("SSH_MSG_CHANNEL_WINDOW_ADJUST message has wrong size (" + msglen + ")");
-
- int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
- int windowChange = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff);
-
- Channel c = getChannel(id);
-
- if (c == null)
- throw new IOException("Unexpected SSH_MSG_CHANNEL_WINDOW_ADJUST message for non-existent channel " + id);
-
- synchronized (c)
- {
- final long huge = 0xFFFFffffL; /* 2^32 - 1 */
-
- c.remoteWindow += (windowChange & huge); /* avoid sign extension */
-
- /* TODO - is this a good heuristic? */
-
- if ((c.remoteWindow > huge))
- c.remoteWindow = huge;
-
- c.notifyAll();
- }
-
-
- log.debug("Got SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + id + ", " + windowChange + ")");
- }
-
- public void msgChannelOpen(byte[] msg, int msglen) throws IOException
- {
- TypesReader tr = new TypesReader(msg, 0, msglen);
-
- tr.readByte(); // skip packet type
- String channelType = tr.readString();
- int remoteID = tr.readUINT32(); /* sender channel */
- int remoteWindow = tr.readUINT32(); /* initial window size */
- int remoteMaxPacketSize = tr.readUINT32(); /* maximum packet size */
-
- if ("x11".equals(channelType))
- {
- synchronized (x11_magic_cookies)
- {
- /* If we did not request X11 forwarding, then simply ignore this bogus request. */
-
- if (x11_magic_cookies.size() == 0)
- {
- PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID,
- Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "X11 forwarding not activated", "");
-
- tm.sendAsynchronousMessage(pcof.getPayload());
-
- log.warning("Unexpected X11 request, denying it!");
-
- return;
- }
- }
-
- String remoteOriginatorAddress = tr.readString();
- int remoteOriginatorPort = tr.readUINT32();
-
- Channel c = new Channel(this);
-
- synchronized (c)
- {
- c.remoteID = remoteID;
- c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */
- c.remoteMaxPacketSize = remoteMaxPacketSize;
- c.localID = addChannel(c);
- }
-
- /*
- * The open confirmation message will be sent from another thread
- */
-
- RemoteX11AcceptThread rxat = new RemoteX11AcceptThread(c, remoteOriginatorAddress, remoteOriginatorPort);
- rxat.setDaemon(true);
- rxat.start();
-
- return;
- }
-
- if ("forwarded-tcpip".equals(channelType))
- {
- String remoteConnectedAddress = tr.readString(); /* address that was connected */
- int remoteConnectedPort = tr.readUINT32(); /* port that was connected */
- String remoteOriginatorAddress = tr.readString(); /* originator IP address */
- int remoteOriginatorPort = tr.readUINT32(); /* originator port */
-
- RemoteForwardingData rfd = null;
-
- synchronized (remoteForwardings)
- {
- rfd = remoteForwardings.get(new Integer(remoteConnectedPort));
- }
-
- if (rfd == null)
- {
- PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID,
- Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
- "No thanks, unknown port in forwarded-tcpip request", "");
-
- /* Always try to be polite. */
-
- tm.sendAsynchronousMessage(pcof.getPayload());
-
- log.debug("Unexpected forwarded-tcpip request, denying it!");
-
- return;
- }
-
- Channel c = new Channel(this);
-
- synchronized (c)
- {
- c.remoteID = remoteID;
- c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */
- c.remoteMaxPacketSize = remoteMaxPacketSize;
- c.localID = addChannel(c);
- }
-
- /*
- * The open confirmation message will be sent from another thread.
- */
-
- RemoteAcceptThread rat = new RemoteAcceptThread(c, remoteConnectedAddress, remoteConnectedPort,
- remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort);
-
- rat.setDaemon(true);
- rat.start();
-
- return;
- }
-
- if ((server_state != null) && ("session".equals(channelType)))
- {
- ServerConnectionCallback cb = null;
-
- synchronized (server_state)
- {
- cb = server_state.cb_conn;
- }
-
- if (cb == null)
- {
- tm.sendAsynchronousMessage(new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
- "Sessions are currently not enabled", "en").getPayload());
-
- return;
- }
-
- final Channel c = new Channel(this);
-
- synchronized (c)
- {
- c.remoteID = remoteID;
- c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */
- c.remoteMaxPacketSize = remoteMaxPacketSize;
- c.localID = addChannel(c);
- c.state = Channel.STATE_OPEN;
- c.ss = new ServerSessionImpl(c);
- }
-
- PacketChannelOpenConfirmation pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID,
- c.localWindow, c.localMaxPacketSize);
-
- tm.sendAsynchronousMessage(pcoc.getPayload());
-
- c.ss.sscb = cb.acceptSession(c.ss);
-
- return;
- }
-
- /* Tell the server that we have no idea what it is talking about */
-
- PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_UNKNOWN_CHANNEL_TYPE,
- "Unknown channel type", "");
-
- tm.sendAsynchronousMessage(pcof.getPayload());
-
-
- log.warning("The peer tried to open an unsupported channel type (" + channelType + ")");
- }
-
- /* Starts the given runnable in a foreground (non-daemon) thread */
- private void runAsync(Runnable r)
- {
- Thread t = new Thread(r);
- t.start();
- }
-
- public void msgChannelRequest(byte[] msg, int msglen) throws IOException
- {
- TypesReader tr = new TypesReader(msg, 0, msglen);
-
- tr.readByte(); // skip packet type
- int id = tr.readUINT32();
-
- Channel c = getChannel(id);
-
- if (c == null)
- throw new IOException("Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id);
-
- ServerSessionImpl server_session = null;
-
- if (server_state != null)
- {
- synchronized (c)
- {
- server_session = c.ss;
- }
- }
-
- String type = tr.readString("US-ASCII");
- boolean wantReply = tr.readBoolean();
-
- log.debug("Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')");
-
- if (type.equals("exit-status"))
- {
- if (wantReply != false)
- throw new IOException(
- "Badly formatted SSH_MSG_CHANNEL_REQUEST exit-status message, 'want reply' is true");
-
- int exit_status = tr.readUINT32();
-
- if (tr.remain() != 0)
- throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message");
-
- synchronized (c)
- {
- c.exit_status = new Integer(exit_status);
- c.notifyAll();
- }
-
- log.debug("Got EXIT STATUS (channel " + id + ", status " + exit_status + ")");
-
- return;
- }
-
- if ((server_state == null) && (type.equals("exit-signal")))
- {
- if (wantReply != false)
- throw new IOException(
- "Badly formatted SSH_MSG_CHANNEL_REQUEST exit-signal message, 'want reply' is true");
-
- String signame = tr.readString("US-ASCII");
- tr.readBoolean();
- tr.readString();
- tr.readString();
-
- if (tr.remain() != 0)
- throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message");
-
- synchronized (c)
- {
- c.exit_signal = signame;
- c.notifyAll();
- }
-
- log.debug("Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")");
-
- return;
- }
-
- if ((server_session != null) && (type.equals("pty-req")))
- {
- PtySettings pty = new PtySettings();
-
- pty.term = tr.readString();
- pty.term_width_characters = tr.readUINT32();
- pty.term_height_characters = tr.readUINT32();
- pty.term_width_pixels = tr.readUINT32();
- pty.term_height_pixels = tr.readUINT32();
- pty.terminal_modes = tr.readByteString();
-
- if (tr.remain() != 0)
- throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message");
-
- Runnable run_after_sending_success = null;
-
- ServerSessionCallback sscb = server_session.getServerSessionCallback();
-
- if (sscb != null)
- run_after_sending_success = sscb.requestPtyReq(server_session, pty);
-
- if (wantReply)
- {
- if (run_after_sending_success != null)
- {
- tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload());
- }
- else
- {
- tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload());
- }
- }
-
- if (run_after_sending_success != null)
- {
- runAsync(run_after_sending_success);
- }
-
- return;
- }
-
- if ((server_session != null) && (type.equals("subsystem")))
- {
- String command = tr.readString();
- if (tr.remain() != 0)
- throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message");
-
- Runnable run_after_sending_success = null;
- ServerSessionCallback sscb = server_session.getServerSessionCallback();
-
- if (sscb != null)
- run_after_sending_success = sscb.requestSubsystem(server_session, command);
-
- if (wantReply)
- {
- if (run_after_sending_success != null)
- {
- tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload());
- }
- else
- {
- tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload());
- }
- }
-
- if (run_after_sending_success != null)
- {
- runAsync(run_after_sending_success);
- }
-
- return;
- }
-
- if ((server_session != null) && (type.equals("shell")))
- {
- if (tr.remain() != 0)
- throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message");
-
- Runnable run_after_sending_success = null;
- ServerSessionCallback sscb = server_session.getServerSessionCallback();
-
- if (sscb != null)
- run_after_sending_success = sscb.requestShell(server_session);
-
- if (wantReply)
- {
- if (run_after_sending_success != null)
- {
- tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload());
- }
- else
- {
- tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload());
- }
- }
-
- if (run_after_sending_success != null)
- {
- runAsync(run_after_sending_success);
- }
-
- return;
- }
-
- if ((server_session != null) && (type.equals("exec")))
- {
- String command = tr.readString();
-
- if (tr.remain() != 0)
- throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message");
-
- Runnable run_after_sending_success = null;
- ServerSessionCallback sscb = server_session.getServerSessionCallback();
-
- if (sscb != null)
- run_after_sending_success = sscb.requestExec(server_session, command);
-
- if (wantReply)
- {
- if (run_after_sending_success != null)
- {
- tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload());
- }
- else
- {
- tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload());
- }
- }
-
- if (run_after_sending_success != null)
- {
- runAsync(run_after_sending_success);
- }
-
- return;
- }
-
- /* We simply ignore unknown channel requests, however, if the server wants a reply,
- * then we signal that we have no idea what it is about.
- */
-
- if (wantReply)
- {
- tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload());
- }
-
- log.debug("Channel request '" + type + "' is not known, ignoring it");
- }
-
- public void msgChannelEOF(byte[] msg, int msglen) throws IOException
- {
- if (msglen != 5)
- throw new IOException("SSH_MSG_CHANNEL_EOF message has wrong size (" + msglen + ")");
-
- int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
-
- Channel c = getChannel(id);
-
- if (c == null)
- throw new IOException("Unexpected SSH_MSG_CHANNEL_EOF message for non-existent channel " + id);
-
- synchronized (c)
- {
- c.EOF = true;
- c.notifyAll();
- }
-
- log.debug("Got SSH_MSG_CHANNEL_EOF (channel " + id + ")");
- }
-
- public void msgChannelClose(byte[] msg, int msglen) throws IOException
- {
- if (msglen != 5)
- throw new IOException("SSH_MSG_CHANNEL_CLOSE message has wrong size (" + msglen + ")");
-
- int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
-
- Channel c = getChannel(id);
-
- if (c == null)
- throw new IOException("Unexpected SSH_MSG_CHANNEL_CLOSE message for non-existent channel " + id);
-
- synchronized (c)
- {
- c.EOF = true;
- c.state = Channel.STATE_CLOSED;
- c.setReasonClosed("Close requested by remote");
- c.closeMessageRecv = true;
-
- removeChannel(c.localID);
-
- c.notifyAll();
- }
-
- log.debug("Got SSH_MSG_CHANNEL_CLOSE (channel " + id + ")");
- }
-
- public void msgChannelSuccess(byte[] msg, int msglen) throws IOException
- {
- if (msglen != 5)
- throw new IOException("SSH_MSG_CHANNEL_SUCCESS message has wrong size (" + msglen + ")");
-
- int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
-
- Channel c = getChannel(id);
-
- if (c == null)
- throw new IOException("Unexpected SSH_MSG_CHANNEL_SUCCESS message for non-existent channel " + id);
-
- synchronized (c)
- {
- c.successCounter++;
- c.notifyAll();
- }
-
- log.debug("Got SSH_MSG_CHANNEL_SUCCESS (channel " + id + ")");
- }
-
- public void msgChannelFailure(byte[] msg, int msglen) throws IOException
- {
- if (msglen != 5)
- throw new IOException("SSH_MSG_CHANNEL_FAILURE message has wrong size (" + msglen + ")");
-
- int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
-
- Channel c = getChannel(id);
-
- if (c == null)
- throw new IOException("Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id);
-
- synchronized (c)
- {
- c.failedCounter++;
- c.notifyAll();
- }
-
- log.debug("Got SSH_MSG_CHANNEL_FAILURE (channel " + id + ")");
- }
-
- public void msgChannelOpenConfirmation(byte[] msg, int msglen) throws IOException
- {
- PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg, 0, msglen);
-
- Channel c = getChannel(sm.recipientChannelID);
-
- if (c == null)
- throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for non-existent channel "
- + sm.recipientChannelID);
-
- synchronized (c)
- {
- if (c.state != Channel.STATE_OPENING)
- throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for channel "
- + sm.recipientChannelID);
-
- c.remoteID = sm.senderChannelID;
- c.remoteWindow = sm.initialWindowSize & 0xFFFFffffL; /* convert UINT32 to long */
- c.remoteMaxPacketSize = sm.maxPacketSize;
- c.state = Channel.STATE_OPEN;
- c.notifyAll();
- }
-
- log.debug("Got SSH_MSG_CHANNEL_OPEN_CONFIRMATION (channel " + sm.recipientChannelID + " / remote: "
- + sm.senderChannelID + ")");
- }
-
- public void msgChannelOpenFailure(byte[] msg, int msglen) throws IOException
- {
- if (msglen < 5)
- throw new IOException("SSH_MSG_CHANNEL_OPEN_FAILURE message has wrong size (" + msglen + ")");
-
- TypesReader tr = new TypesReader(msg, 0, msglen);
-
- tr.readByte(); // skip packet type
- int id = tr.readUINT32(); /* sender channel */
-
- Channel c = getChannel(id);
-
- if (c == null)
- throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE message for non-existent channel " + id);
-
- int reasonCode = tr.readUINT32();
- String description = tr.readString("UTF-8");
-
- String reasonCodeSymbolicName = null;
-
- switch (reasonCode)
- {
- case 1:
- reasonCodeSymbolicName = "SSH_OPEN_ADMINISTRATIVELY_PROHIBITED";
- break;
- case 2:
- reasonCodeSymbolicName = "SSH_OPEN_CONNECT_FAILED";
- break;
- case 3:
- reasonCodeSymbolicName = "SSH_OPEN_UNKNOWN_CHANNEL_TYPE";
- break;
- case 4:
- reasonCodeSymbolicName = "SSH_OPEN_RESOURCE_SHORTAGE";
- break;
- default:
- reasonCodeSymbolicName = "UNKNOWN REASON CODE (" + reasonCode + ")";
- }
-
- StringBuilder descriptionBuffer = new StringBuilder();
- descriptionBuffer.append(description);
-
- for (int i = 0; i < descriptionBuffer.length(); i++)
- {
- char cc = descriptionBuffer.charAt(i);
-
- if ((cc >= 32) && (cc <= 126))
- continue;
- descriptionBuffer.setCharAt(i, '\uFFFD');
- }
-
- synchronized (c)
- {
- c.EOF = true;
- c.state = Channel.STATE_CLOSED;
- c.setReasonClosed("The server refused to open the channel (" + reasonCodeSymbolicName + ", '"
- + descriptionBuffer.toString() + "')");
- c.notifyAll();
- }
-
- log.debug("Got SSH_MSG_CHANNEL_OPEN_FAILURE (channel " + id + ")");
- }
-
- public void msgGlobalRequest(byte[] msg, int msglen) throws IOException
- {
- /* Currently we do not support any kind of global request */
-
- TypesReader tr = new TypesReader(msg, 0, msglen);
-
- tr.readByte(); // skip packet type
- String requestName = tr.readString();
- boolean wantReply = tr.readBoolean();
-
- if (wantReply)
- {
- byte[] reply_failure = new byte[1];
- reply_failure[0] = Packets.SSH_MSG_REQUEST_FAILURE;
-
- tm.sendAsynchronousMessage(reply_failure);
- }
-
- /* We do not clean up the requestName String - that is OK for debug */
-
- log.debug("Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")");
- }
-
- public void msgGlobalSuccess() throws IOException
- {
- synchronized (channels)
- {
- globalSuccessCounter++;
- channels.notifyAll();
- }
-
- log.debug("Got SSH_MSG_REQUEST_SUCCESS");
- }
-
- public void msgGlobalFailure() throws IOException
- {
- synchronized (channels)
- {
- globalFailedCounter++;
- channels.notifyAll();
- }
-
- log.debug("Got SSH_MSG_REQUEST_FAILURE");
- }
-
- public void handleMessage(byte[] msg, int msglen) throws IOException
- {
- if (msg == null)
- {
-
- log.debug("HandleMessage: got shutdown");
-
- synchronized (listenerThreads)
- {
- for (IChannelWorkerThread lat : listenerThreads)
- {
- lat.stopWorking();
- }
- listenerThreadsAllowed = false;
- }
-
- synchronized (channels)
- {
- shutdown = true;
-
- for (Channel c : channels)
- {
- synchronized (c)
- {
- c.EOF = true;
- c.state = Channel.STATE_CLOSED;
- c.setReasonClosed("The connection is being shutdown");
- c.closeMessageRecv = true; /*
- * You never know, perhaps
- * we are waiting for a
- * pending close message
- * from the server...
- */
- c.notifyAll();
- }
- }
-
- channels.clear();
- channels.notifyAll(); /* Notify global response waiters */
- return;
- }
- }
-
- switch (msg[0])
- {
- case Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION:
- msgChannelOpenConfirmation(msg, msglen);
- break;
- case Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST:
- msgChannelWindowAdjust(msg, msglen);
- break;
- case Packets.SSH_MSG_CHANNEL_DATA:
- msgChannelData(msg, msglen);
- break;
- case Packets.SSH_MSG_CHANNEL_EXTENDED_DATA:
- msgChannelExtendedData(msg, msglen);
- break;
- case Packets.SSH_MSG_CHANNEL_REQUEST:
- msgChannelRequest(msg, msglen);
- break;
- case Packets.SSH_MSG_CHANNEL_EOF:
- msgChannelEOF(msg, msglen);
- break;
- case Packets.SSH_MSG_CHANNEL_OPEN:
- msgChannelOpen(msg, msglen);
- break;
- case Packets.SSH_MSG_CHANNEL_CLOSE:
- msgChannelClose(msg, msglen);
- break;
- case Packets.SSH_MSG_CHANNEL_SUCCESS:
- msgChannelSuccess(msg, msglen);
- break;
- case Packets.SSH_MSG_CHANNEL_FAILURE:
- msgChannelFailure(msg, msglen);
- break;
- case Packets.SSH_MSG_CHANNEL_OPEN_FAILURE:
- msgChannelOpenFailure(msg, msglen);
- break;
- case Packets.SSH_MSG_GLOBAL_REQUEST:
- msgGlobalRequest(msg, msglen);
- break;
- case Packets.SSH_MSG_REQUEST_SUCCESS:
- msgGlobalSuccess();
- break;
- case Packets.SSH_MSG_REQUEST_FAILURE:
- msgGlobalFailure();
- break;
- default:
- throw new IOException("Cannot handle unknown channel message " + (msg[0] & 0xff));
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2006-2013 Christian Plattner. All rights reserved.
- * Please refer to the LICENSE.txt for licensing details.
- */
-
-package ch.ethz.ssh2.transport;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.SocketTimeoutException;
-import java.net.UnknownHostException;
-import java.security.SecureRandom;
-import java.util.List;
-import java.util.Vector;
-
-import ch.ethz.ssh2.ConnectionInfo;
-import ch.ethz.ssh2.ConnectionMonitor;
-import ch.ethz.ssh2.DHGexParameters;
-import ch.ethz.ssh2.HTTPProxyData;
-import ch.ethz.ssh2.HTTPProxyException;
-import ch.ethz.ssh2.ProxyData;
-import ch.ethz.ssh2.ServerHostKeyVerifier;
-import ch.ethz.ssh2.crypto.Base64;
-import ch.ethz.ssh2.crypto.CryptoWishList;
-import ch.ethz.ssh2.crypto.cipher.BlockCipher;
-import ch.ethz.ssh2.crypto.digest.MAC;
-import ch.ethz.ssh2.log.Logger;
-import ch.ethz.ssh2.packets.PacketDisconnect;
-import ch.ethz.ssh2.packets.Packets;
-import ch.ethz.ssh2.packets.TypesReader;
-import ch.ethz.ssh2.server.ServerConnectionState;
-import ch.ethz.ssh2.signature.DSAPrivateKey;
-import ch.ethz.ssh2.signature.RSAPrivateKey;
-import ch.ethz.ssh2.util.StringEncoder;
-import ch.ethz.ssh2.util.Tokenizer;
-
-/*
- * Yes, the "standard" is a big mess. On one side, the say that arbitary channel
- * packets are allowed during kex exchange, on the other side we need to blindly
- * ignore the next _packet_ if the KEX guess was wrong. Where do we know from that
- * the next packet is not a channel data packet? Yes, we could check if it is in
- * the KEX range. But the standard says nothing about this. The OpenSSH guys
- * block local "normal" traffic during KEX. That's fine - however, they assume
- * that the other side is doing the same. During re-key, if they receive traffic
- * other than KEX, they become horribly irritated and kill the connection. Since
- * we are very likely going to communicate with OpenSSH servers, we have to play
- * the same game - even though we could do better.
- *
- * btw: having stdout and stderr on the same channel, with a shared window, is
- * also a VERY good idea... =(
- */
-
-/**
- * TransportManager.
- *
- * @author Christian Plattner
- * @version $Id: TransportManager.java 47 2013-07-31 23:59:52Z cleondris@gmail.com $
- */
-public class TransportManager
-{
- private static final Logger log = Logger.getLogger(TransportManager.class);
-
- private static class HandlerEntry
- {
- MessageHandler mh;
- int low;
- int high;
- }
-
- private final List<AsynchronousEntry> asynchronousQueue = new Vector<AsynchronousEntry>();
- private Thread asynchronousThread = null;
- private boolean asynchronousPending = false;
-
- class AsynchronousEntry
- {
- public byte[] msg;
- public Runnable run;
-
- public AsynchronousEntry(byte[] msg, Runnable run)
- {
- this.msg = msg;
- this.run = run;
- }
- }
-
- class AsynchronousWorker extends Thread
- {
- @Override
- public void run()
- {
- while (true)
- {
- AsynchronousEntry item = null;
-
- synchronized (asynchronousQueue)
- {
- if (asynchronousQueue.size() == 0)
- {
- /* Only now we may reset the flag, since we are sure that all queued items
- * have been sent (there is a slight delay between de-queuing and sending,
- * this is why we need this flag! See code below. Sending takes place outside
- * of this lock, this is why a test for size()==0 (from another thread) does not ensure
- * that all messages have been sent.
- */
-
- asynchronousPending = false;
-
- /* Notify any senders that they can proceed, all async messages have been delivered */
-
- asynchronousQueue.notifyAll();
-
- /* After the queue is empty for about 2 seconds, stop this thread */
-
- try
- {
- asynchronousQueue.wait(2000);
- }
- catch (InterruptedException ignore)
- {
- }
-
- if (asynchronousQueue.size() == 0)
- {
- asynchronousThread = null;
- return;
- }
- }
-
- item = asynchronousQueue.remove(0);
- }
-
- /* The following invocation may throw an IOException.
- * There is no point in handling it - it simply means
- * that the connection has a problem and we should stop
- * sending asynchronously messages. We do not need to signal that
- * we have exited (asynchronousThread = null): further
- * messages in the queue cannot be sent by this or any
- * other thread.
- * Other threads will sooner or later (when receiving or
- * sending the next message) get the same IOException and
- * get to the same conclusion.
- */
-
- try
- {
- sendMessageImmediate(item.msg);
- }
- catch (IOException e)
- {
- return;
- }
-
- if (item.run != null)
- {
- try
- {
- item.run.run();
- }
- catch (Exception ignore)
- {
- }
-
- }
- }
- }
- }
-
- private Socket sock = new Socket();
-
- private final Object connectionSemaphore = new Object();
-
- private boolean flagKexOngoing = false;
- private boolean connectionClosed = false;
-
- private Throwable reasonClosedCause = null;
-
- private TransportConnection tc;
- private KexManager km;
-
- private final List<HandlerEntry> messageHandlers = new Vector<HandlerEntry>();
-
- private Thread receiveThread;
-
- private List<ConnectionMonitor> connectionMonitors = new Vector<ConnectionMonitor>();
- private boolean monitorsWereInformed = false;
-
- /**
- * There were reports that there are JDKs which use
- * the resolver even though one supplies a dotted IP
- * address in the Socket constructor. That is why we
- * try to generate the InetAdress "by hand".
- *
- * @param host
- * @return the InetAddress
- * @throws UnknownHostException
- */
- private static InetAddress createInetAddress(String host) throws UnknownHostException
- {
- /* Check if it is a dotted IP4 address */
-
- InetAddress addr = parseIPv4Address(host);
-
- if (addr != null)
- {
- return addr;
- }
-
- return InetAddress.getByName(host);
- }
-
- private static InetAddress parseIPv4Address(String host) throws UnknownHostException
- {
- if (host == null)
- {
- return null;
- }
-
- String[] quad = Tokenizer.parseTokens(host, '.');
-
- if ((quad == null) || (quad.length != 4))
- {
- return null;
- }
-
- byte[] addr = new byte[4];
-
- for (int i = 0; i < 4; i++)
- {
- int part = 0;
-
- if ((quad[i].length() == 0) || (quad[i].length() > 3))
- {
- return null;
- }
-
- for (int k = 0; k < quad[i].length(); k++)
- {
- char c = quad[i].charAt(k);
-
- /* No, Character.isDigit is not the same */
- if ((c < '0') || (c > '9'))
- {
- return null;
- }
-
- part = part * 10 + (c - '0');
- }
-
- if (part > 255) /* 300.1.2.3 is invalid =) */
- {
- return null;
- }
-
- addr[i] = (byte) part;
- }
-
- return InetAddress.getByAddress(host, addr);
- }
-
- public int getPacketOverheadEstimate()
- {
- return tc.getPacketOverheadEstimate();
- }
-
- public void setTcpNoDelay(boolean state) throws IOException
- {
- sock.setTcpNoDelay(state);
- }
-
- public void setSoTimeout(int timeout) throws IOException
- {
- sock.setSoTimeout(timeout);
- }
-
- public ConnectionInfo getConnectionInfo(int kexNumber) throws IOException
- {
- return km.getOrWaitForConnectionInfo(kexNumber);
- }
-
- public Throwable getReasonClosedCause()
- {
- synchronized (connectionSemaphore)
- {
- return reasonClosedCause;
- }
- }
-
- public byte[] getSessionIdentifier()
- {
- return km.sessionId;
- }
-
- public void close(Throwable cause, boolean useDisconnectPacket)
- {
- if (useDisconnectPacket == false)
- {
- /* OK, hard shutdown - do not aquire the semaphore,
- * perhaps somebody is inside (and waits until the remote
- * side is ready to accept new data). */
-
- try
- {
- sock.close();
- }
- catch (IOException ignore)
- {
- }
-
- /* OK, whoever tried to send data, should now agree that
- * there is no point in further waiting =)
- * It is safe now to aquire the semaphore.
- */
- }
-
- synchronized (connectionSemaphore)
- {
- if (connectionClosed == false)
- {
- if (useDisconnectPacket == true)
- {
- try
- {
- byte[] msg = new PacketDisconnect(Packets.SSH_DISCONNECT_BY_APPLICATION, cause.getMessage(), "")
- .getPayload();
- if (tc != null)
- {
- tc.sendMessage(msg);
- }
- }
- catch (IOException ignore)
- {
- }
-
- try
- {
- sock.close();
- }
- catch (IOException ignore)
- {
- }
- }
-
- connectionClosed = true;
- reasonClosedCause = cause; /* may be null */
- }
- connectionSemaphore.notifyAll();
- }
-
- /* No check if we need to inform the monitors */
-
- List<ConnectionMonitor> monitors = new Vector<ConnectionMonitor>();
-
- synchronized (this)
- {
- /* Short term lock to protect "connectionMonitors"
- * and "monitorsWereInformed"
- * (they may be modified concurrently)
- */
-
- if (monitorsWereInformed == false)
- {
- monitorsWereInformed = true;
- monitors.addAll(connectionMonitors);
- }
- }
-
- for (ConnectionMonitor cmon : monitors)
- {
- try
- {
- cmon.connectionLost(reasonClosedCause);
- }
- catch (Exception ignore)
- {
- }
- }
- }
-
- private static Socket establishConnection(String hostname, int port, ProxyData proxyData, int connectTimeout)
- throws IOException
- {
- /* See the comment for createInetAddress() */
-
- if (proxyData == null)
- {
- InetAddress addr = createInetAddress(hostname);
- Socket s = new Socket();
- s.connect(new InetSocketAddress(addr, port), connectTimeout);
- return s;
- }
-
- if (proxyData instanceof HTTPProxyData)
- {
- HTTPProxyData pd = (HTTPProxyData) proxyData;
-
- /* At the moment, we only support HTTP proxies */
-
- InetAddress addr = createInetAddress(pd.proxyHost);
- Socket s = new Socket();
- s.connect(new InetSocketAddress(addr, pd.proxyPort), connectTimeout);
-
- /* OK, now tell the proxy where we actually want to connect to */
-
- StringBuilder sb = new StringBuilder();
-
- sb.append("CONNECT ");
- sb.append(hostname);
- sb.append(':');
- sb.append(port);
- sb.append(" HTTP/1.0\r\n");
-
- if ((pd.proxyUser != null) && (pd.proxyPass != null))
- {
- String credentials = pd.proxyUser + ":" + pd.proxyPass;
- char[] encoded = Base64.encode(StringEncoder.GetBytes(credentials));
- sb.append("Proxy-Authorization: Basic ");
- sb.append(encoded);
- sb.append("\r\n");
- }
-
- if (pd.requestHeaderLines != null)
- {
- for (int i = 0; i < pd.requestHeaderLines.length; i++)
- {
- if (pd.requestHeaderLines[i] != null)
- {
- sb.append(pd.requestHeaderLines[i]);
- sb.append("\r\n");
- }
- }
- }
-
- sb.append("\r\n");
-
- OutputStream out = s.getOutputStream();
-
- out.write(StringEncoder.GetBytes(sb.toString()));
- out.flush();
-
- /* Now parse the HTTP response */
-
- byte[] buffer = new byte[1024];
- InputStream in = s.getInputStream();
-
- int len = ClientServerHello.readLineRN(in, buffer);
-
- String httpReponse = StringEncoder.GetString(buffer, 0, len);
-
- if (httpReponse.startsWith("HTTP/") == false)
- {
- throw new IOException("The proxy did not send back a valid HTTP response.");
- }
-
- /* "HTTP/1.X XYZ X" => 14 characters minimum */
-
- if ((httpReponse.length() < 14) || (httpReponse.charAt(8) != ' ') || (httpReponse.charAt(12) != ' '))
- {
- throw new IOException("The proxy did not send back a valid HTTP response.");
- }
-
- int errorCode = 0;
-
- try
- {
- errorCode = Integer.parseInt(httpReponse.substring(9, 12));
- }
- catch (NumberFormatException ignore)
- {
- throw new IOException("The proxy did not send back a valid HTTP response.");
- }
-
- if ((errorCode < 0) || (errorCode > 999))
- {
- throw new IOException("The proxy did not send back a valid HTTP response.");
- }
-
- if (errorCode != 200)
- {
- throw new HTTPProxyException(httpReponse.substring(13), errorCode);
- }
-
- /* OK, read until empty line */
-
- while (true)
- {
- len = ClientServerHello.readLineRN(in, buffer);
- if (len == 0)
- {
- break;
- }
- }
- return s;
- }
-
- throw new IOException("Unsupported ProxyData");
- }
-
- private void startReceiver() throws IOException
- {
- receiveThread = new Thread(new Runnable()
- {
- public void run()
- {
- try
- {
- receiveLoop();
- }
- catch (Exception e)
- {
- close(e, false);
-
- log.warning("Receive thread: error in receiveLoop: " + e.getMessage());
- }
-
- if (log.isDebugEnabled())
- {
- log.debug("Receive thread: back from receiveLoop");
- }
-
- /* Tell all handlers that it is time to say goodbye */
-
- if (km != null)
- {
- try
- {
- km.handleMessage(null, 0);
- }
- catch (IOException ignored)
- {
- }
- }
-
- for (HandlerEntry he : messageHandlers)
- {
- try
- {
- he.mh.handleMessage(null, 0);
- }
- catch (Exception ignore)
- {
- }
- }
- }
- });
-
- receiveThread.setDaemon(true);
- receiveThread.start();
- }
-
- public void clientInit(Socket socket, String softwareversion, CryptoWishList cwl,
- ServerHostKeyVerifier verifier, DHGexParameters dhgex, SecureRandom rnd) throws IOException
- {
- /* First, establish the TCP connection to the SSH-2 server */
-
- sock = socket;
-
- /* Parse the server line and say hello - important: this information is later needed for the
- * key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object
- * for later use.
- */
-
- ClientServerHello csh = ClientServerHello.clientHello(softwareversion, sock.getInputStream(),
- sock.getOutputStream());
-
- tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), rnd);
- String hostname = sock.getInetAddress().getHostName();
- int port = sock.getPort();
-
- km = new ClientKexManager(this, csh, cwl, hostname, port, verifier, rnd);
- km.initiateKEX(cwl, dhgex, null, null);
-
- startReceiver();
- }
-
- public void clientInit(String hostname, int port, String softwareversion, CryptoWishList cwl,
- ServerHostKeyVerifier verifier, DHGexParameters dhgex, int connectTimeout, SecureRandom rnd,
- ProxyData proxyData) throws IOException
- {
- /* First, establish the TCP connection to the SSH-2 server */
-
- sock = establishConnection(hostname, port, proxyData, connectTimeout);
-
- /* Parse the server line and say hello - important: this information is later needed for the
- * key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object
- * for later use.
- */
-
- ClientServerHello csh = ClientServerHello.clientHello(softwareversion, sock.getInputStream(),
- sock.getOutputStream());
-
- tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), rnd);
-
- km = new ClientKexManager(this, csh, cwl, hostname, port, verifier, rnd);
- km.initiateKEX(cwl, dhgex, null, null);
-
- startReceiver();
- }
-
- public void serverInit(ServerConnectionState state) throws IOException
- {
- /* TCP connection is already established */
-
- this.sock = state.s;
-
- /* Parse the client line and say hello - important: this information is later needed for the
- * key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object
- * for later use.
- */
-
- state.csh = ClientServerHello.serverHello(state.softwareversion, sock.getInputStream(), sock.getOutputStream());
-
- tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), state.generator);
-
- km = new ServerKexManager(state);
- km.initiateKEX(state.next_cryptoWishList, null, state.next_dsa_key, state.next_rsa_key);
-
- startReceiver();
- }
-
- public void registerMessageHandler(MessageHandler mh, int low, int high)
- {
- HandlerEntry he = new HandlerEntry();
- he.mh = mh;
- he.low = low;
- he.high = high;
-
- synchronized (messageHandlers)
- {
- messageHandlers.add(he);
- }
- }
-
- public void removeMessageHandler(MessageHandler mh, int low, int high)
- {
- synchronized (messageHandlers)
- {
- for (int i = 0; i < messageHandlers.size(); i++)
- {
- HandlerEntry he = messageHandlers.get(i);
- if ((he.mh == mh) && (he.low == low) && (he.high == high))
- {
- messageHandlers.remove(i);
- break;
- }
- }
- }
- }
-
- public void sendKexMessage(byte[] msg) throws IOException
- {
- synchronized (connectionSemaphore)
- {
- if (connectionClosed)
- {
- throw (IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause);
- }
-
- flagKexOngoing = true;
-
- try
- {
- tc.sendMessage(msg);
- }
- catch (IOException e)
- {
- close(e, false);
- throw e;
- }
- }
- }
-
- public void kexFinished() throws IOException
- {
- synchronized (connectionSemaphore)
- {
- flagKexOngoing = false;
- connectionSemaphore.notifyAll();
- }
- }
-
- /**
- *
- * @param cwl
- * @param dhgex
- * @param dsa may be null if this is a client connection
- * @param rsa may be null if this is a client connection
- * @throws IOException
- */
- public void forceKeyExchange(CryptoWishList cwl, DHGexParameters dhgex, DSAPrivateKey dsa, RSAPrivateKey rsa)
- throws IOException
- {
- synchronized (connectionSemaphore)
- {
- if (connectionClosed)
- /* Inform the caller that there is no point in triggering a new kex */
- throw (IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause);
- }
-
- km.initiateKEX(cwl, dhgex, dsa, rsa);
- }
-
- public void changeRecvCipher(BlockCipher bc, MAC mac)
- {
- tc.changeRecvCipher(bc, mac);
- }
-
- public void changeSendCipher(BlockCipher bc, MAC mac)
- {
- tc.changeSendCipher(bc, mac);
- }
-
- public void sendAsynchronousMessage(byte[] msg) throws IOException
- {
- sendAsynchronousMessage(msg, null);
- }
-
- public void sendAsynchronousMessage(byte[] msg, Runnable run) throws IOException
- {
- synchronized (asynchronousQueue)
- {
- asynchronousQueue.add(new AsynchronousEntry(msg, run));
- asynchronousPending = true;
-
- /* This limit should be flexible enough. We need this, otherwise the peer
- * can flood us with global requests (and other stuff where we have to reply
- * with an asynchronous message) and (if the server just sends data and does not
- * read what we send) this will probably put us in a low memory situation
- * (our send queue would grow and grow and...) */
-
- if (asynchronousQueue.size() > 100)
- {
- throw new IOException("Error: the peer is not consuming our asynchronous replies.");
- }
-
- /* Check if we have an asynchronous sending thread */
-
- if (asynchronousThread == null)
- {
- asynchronousThread = new AsynchronousWorker();
- asynchronousThread.setDaemon(true);
- asynchronousThread.start();
-
- /* The thread will stop after 2 seconds of inactivity (i.e., empty queue) */
- }
-
- asynchronousQueue.notifyAll();
- }
- }
-
- public void setConnectionMonitors(List<ConnectionMonitor> monitors)
- {
- synchronized (this)
- {
- connectionMonitors = new Vector<ConnectionMonitor>();
- connectionMonitors.addAll(monitors);
- }
- }
-
- /**
- * True if no response message expected.
- */
- private boolean idle;
-
- /**
- * Send a message but ensure that all queued messages are being sent first.
- *
- * @param msg
- * @throws IOException
- */
- public void sendMessage(byte[] msg) throws IOException
- {
- synchronized (asynchronousQueue)
- {
- while (asynchronousPending)
- {
- try
- {
- asynchronousQueue.wait(1000);
- }
- catch (InterruptedException e)
- {
- }
- }
- }
-
- sendMessageImmediate(msg);
- }
-
- /**
- * Send message, ignore queued async messages that have not been delivered yet.
- * Will be called directly from the asynchronousThread thread.
- *
- * @param msg
- * @throws IOException
- */
- public void sendMessageImmediate(byte[] msg) throws IOException
- {
- if (Thread.currentThread() == receiveThread)
- {
- throw new IOException("Assertion error: sendMessage may never be invoked by the receiver thread!");
- }
-
- boolean wasInterrupted = false;
-
- try
- {
- synchronized (connectionSemaphore)
- {
- while (true)
- {
- if (connectionClosed)
- {
- throw (IOException) new IOException("Sorry, this connection is closed.")
- .initCause(reasonClosedCause);
- }
-
- if (flagKexOngoing == false)
- {
- break;
- }
-
- try
- {
- connectionSemaphore.wait();
- }
- catch (InterruptedException e)
- {
- wasInterrupted = true;
- }
- }
-
- try
- {
- tc.sendMessage(msg);
- idle = false;
- }
- catch (IOException e)
- {
- close(e, false);
- throw e;
- }
- }
- }
- finally
- {
- if (wasInterrupted)
- Thread.currentThread().interrupt();
- }
- }
-
- public void receiveLoop() throws IOException
- {
- byte[] msg = new byte[35000];
-
- while (true)
- {
- int msglen;
- try
- {
- msglen = tc.receiveMessage(msg, 0, msg.length);
- }
- catch (SocketTimeoutException e)
- {
- // Timeout in read
- if (idle)
- {
- log.debug("Ignoring socket timeout");
- continue;
- }
- throw e;
- }
- idle = true;
-
- int type = msg[0] & 0xff;
-
- if (type == Packets.SSH_MSG_IGNORE)
- {
- continue;
- }
-
- if (type == Packets.SSH_MSG_DEBUG)
- {
- if (log.isDebugEnabled())
- {
- TypesReader tr = new TypesReader(msg, 0, msglen);
- tr.readByte();
- tr.readBoolean();
- StringBuilder debugMessageBuffer = new StringBuilder();
- debugMessageBuffer.append(tr.readString("UTF-8"));
-
- for (int i = 0; i < debugMessageBuffer.length(); i++)
- {
- char c = debugMessageBuffer.charAt(i);
-
- if ((c >= 32) && (c <= 126))
- {
- continue;
- }
- debugMessageBuffer.setCharAt(i, '\uFFFD');
- }
-
- log.debug("DEBUG Message from remote: '" + debugMessageBuffer.toString() + "'");
- }
- continue;
- }
-
- if (type == Packets.SSH_MSG_UNIMPLEMENTED)
- {
- throw new IOException("Peer sent UNIMPLEMENTED message, that should not happen.");
- }
-
- if (type == Packets.SSH_MSG_DISCONNECT)
- {
- TypesReader tr = new TypesReader(msg, 0, msglen);
- tr.readByte();
- int reason_code = tr.readUINT32();
- StringBuilder reasonBuffer = new StringBuilder();
- reasonBuffer.append(tr.readString("UTF-8"));
-
- /*
- * Do not get fooled by servers that send abnormal long error
- * messages
- */
-
- if (reasonBuffer.length() > 255)
- {
- reasonBuffer.setLength(255);
- reasonBuffer.setCharAt(254, '.');
- reasonBuffer.setCharAt(253, '.');
- reasonBuffer.setCharAt(252, '.');
- }
-
- /*
- * Also, check that the server did not send characters that may
- * screw up the receiver -> restrict to reasonable US-ASCII
- * subset -> "printable characters" (ASCII 32 - 126). Replace
- * all others with 0xFFFD (UNICODE replacement character).
- */
-
- for (int i = 0; i < reasonBuffer.length(); i++)
- {
- char c = reasonBuffer.charAt(i);
-
- if ((c >= 32) && (c <= 126))
- {
- continue;
- }
- reasonBuffer.setCharAt(i, '\uFFFD');
- }
-
- throw new IOException("Peer sent DISCONNECT message (reason code " + reason_code + "): "
- + reasonBuffer.toString());
- }
-
- /*
- * Is it a KEX Packet?
- */
-
- if ((type == Packets.SSH_MSG_KEXINIT) || (type == Packets.SSH_MSG_NEWKEYS)
- || ((type >= 30) && (type <= 49)))
- {
- km.handleMessage(msg, msglen);
- continue;
- }
-
- MessageHandler mh = null;
-
- for (int i = 0; i < messageHandlers.size(); i++)
- {
- HandlerEntry he = messageHandlers.get(i);
- if ((he.low <= type) && (type <= he.high))
- {
- mh = he.mh;
- break;
- }
- }
-
- if (mh == null)
- {
- throw new IOException("Unexpected SSH message (type " + type + ")");
- }
-
- mh.handleMessage(msg, msglen);
- }
- }
-}