mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-08 00:00:54 +03:00
minor changes, reformat etc.
This commit is contained in:
@@ -67,16 +67,16 @@ public interface Config {
|
||||
List<Factory.Named<MAC>> getMACFactories();
|
||||
|
||||
/**
|
||||
* Retrieve the {@link net.schmizz.sshj.transport.random.Random} factory.
|
||||
* Retrieve the {@link Random} factory.
|
||||
*
|
||||
* @return the {@link net.schmizz.sshj.transport.random.Random} factory
|
||||
* @return the {@link Random} factory
|
||||
*/
|
||||
Factory<Random> getRandomFactory();
|
||||
|
||||
/**
|
||||
* Retrieve the list of named factories for {@link net.schmizz.sshj.signature.Signature}
|
||||
* Retrieve the list of named factories for {@link Signature}
|
||||
*
|
||||
* @return a list of named {@link net.schmizz.sshj.signature.Signature} factories
|
||||
* @return a list of named {@link Signature} factories
|
||||
*/
|
||||
List<Factory.Named<Signature>> getSignatureFactories();
|
||||
|
||||
@@ -87,49 +87,49 @@ public interface Config {
|
||||
String getVersion();
|
||||
|
||||
/**
|
||||
* Set the named factories for {@link net.schmizz.sshj.transport.cipher.Cipher}.
|
||||
* Set the named factories for {@link Cipher}.
|
||||
*
|
||||
* @param cipherFactories a list of named factories
|
||||
*/
|
||||
void setCipherFactories(List<Factory.Named<Cipher>> cipherFactories);
|
||||
|
||||
/**
|
||||
* Set the named factories for {@link net.schmizz.sshj.transport.compression.Compression}.
|
||||
* Set the named factories for {@link Compression}.
|
||||
*
|
||||
* @param compressionFactories a list of named factories
|
||||
*/
|
||||
void setCompressionFactories(List<Factory.Named<Compression>> compressionFactories);
|
||||
|
||||
/**
|
||||
* Set the named factories for {@link net.schmizz.sshj.userauth.keyprovider.FileKeyProvider}.
|
||||
* Set the named factories for {@link FileKeyProvider}.
|
||||
*
|
||||
* @param fileKeyProviderFactories a list of named factories
|
||||
*/
|
||||
void setFileKeyProviderFactories(List<Factory.Named<FileKeyProvider>> fileKeyProviderFactories);
|
||||
|
||||
/**
|
||||
* Set the named factories for {@link net.schmizz.sshj.transport.kex.KeyExchange}.
|
||||
* Set the named factories for {@link KeyExchange}.
|
||||
*
|
||||
* @param kexFactories a list of named factories
|
||||
*/
|
||||
void setKeyExchangeFactories(List<Factory.Named<KeyExchange>> kexFactories);
|
||||
|
||||
/**
|
||||
* Set the named factories for {@link net.schmizz.sshj.transport.mac.MAC}.
|
||||
* Set the named factories for {@link MAC}.
|
||||
*
|
||||
* @param macFactories a list of named factories
|
||||
*/
|
||||
void setMACFactories(List<Factory.Named<MAC>> macFactories);
|
||||
|
||||
/**
|
||||
* Set the factory for {@link net.schmizz.sshj.transport.random.Random}.
|
||||
* Set the factory for {@link Random}.
|
||||
*
|
||||
* @param randomFactory the factory
|
||||
*/
|
||||
void setRandomFactory(Factory<Random> randomFactory);
|
||||
|
||||
/**
|
||||
* Set the named factories for {@link net.schmizz.sshj.signature.Signature}.
|
||||
* Set the named factories for {@link Signature}.
|
||||
*
|
||||
* @param signatureFactories a list of named factories
|
||||
*/
|
||||
|
||||
@@ -99,8 +99,7 @@ import java.util.List;
|
||||
* <p/>
|
||||
* {@link #startSession()} caters to the most typical use case of starting a {@code session} channel and executing a
|
||||
* remote command, starting a subsystem, etc. If you wish to request X11 forwarding for some session, first {@link
|
||||
* #registerX11Forwarder(net.schmizz.sshj.connection.channel.forwarded.ConnectListener) register} a {@link
|
||||
* net.schmizz.sshj.connection.channel.forwarded.ConnectListener} for {@code x11} channels.
|
||||
* #registerX11Forwarder(ConnectListener) register} a {@link ConnectListener} for {@code x11} channels.
|
||||
* <p/>
|
||||
* {@link #newLocalPortForwarder Local} and {@link #getRemotePortForwarder() remote} port forwarding is possible. There
|
||||
* are also utility method for easily creating {@link #newSCPFileTransfer SCP} and {@link #newSFTPClient() SFTP}
|
||||
@@ -127,7 +126,9 @@ import java.util.List;
|
||||
* Where a password or passphrase is required, if you're extra-paranoid use the {@code char[]} based method. The {@code
|
||||
* char[]} will be blanked out after use.
|
||||
*/
|
||||
public class SSHClient extends SocketClient implements SessionFactory {
|
||||
public class SSHClient
|
||||
extends SocketClient
|
||||
implements SessionFactory {
|
||||
|
||||
/** Default port for SSH */
|
||||
public static final int DEFAULT_PORT = 22;
|
||||
@@ -180,7 +181,7 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* @param port the port for which the {@code fingerprint} applies
|
||||
* @param fingerprint expected fingerprint in colon-delimited format (16 octets in hex delimited by a colon)
|
||||
*
|
||||
* @see net.schmizz.sshj.common.SecurityUtils#getFingerprint
|
||||
* @see SecurityUtils#getFingerprint
|
||||
*/
|
||||
public void addHostKeyVerifier(final String host, final int port, final String fingerprint) {
|
||||
addHostKeyVerifier(new HostKeyVerifier() {
|
||||
@@ -199,7 +200,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* @throws UserAuthException in case of authentication failure
|
||||
* @throws TransportException if there was a transport-layer error
|
||||
*/
|
||||
public void auth(String username, AuthMethod... methods) throws UserAuthException, TransportException {
|
||||
public void auth(String username, AuthMethod... methods)
|
||||
throws UserAuthException, TransportException {
|
||||
assert isConnected();
|
||||
auth(username, Arrays.<AuthMethod>asList(methods));
|
||||
}
|
||||
@@ -213,7 +215,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* @throws UserAuthException in case of authentication failure
|
||||
* @throws TransportException if there was a transport-layer error
|
||||
*/
|
||||
public void auth(String username, Iterable<AuthMethod> methods) throws UserAuthException, TransportException {
|
||||
public void auth(String username, Iterable<AuthMethod> methods)
|
||||
throws UserAuthException, TransportException {
|
||||
assert isConnected();
|
||||
auth.authenticate(username, conn, methods);
|
||||
}
|
||||
@@ -228,7 +231,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* @throws UserAuthException in case of authentication failure
|
||||
* @throws TransportException if there was a transport-layer error
|
||||
*/
|
||||
public void authPassword(String username, char[] password) throws UserAuthException, TransportException {
|
||||
public void authPassword(String username, char[] password)
|
||||
throws UserAuthException, TransportException {
|
||||
authPassword(username, PasswordUtils.createOneOff(password));
|
||||
}
|
||||
|
||||
@@ -236,12 +240,13 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* Authenticate {@code username} using the {@code "password"} authentication method.
|
||||
*
|
||||
* @param username user to authenticate
|
||||
* @param pfinder the {@link net.schmizz.sshj.userauth.password.PasswordFinder} to use for authentication
|
||||
* @param pfinder the {@link PasswordFinder} to use for authentication
|
||||
*
|
||||
* @throws UserAuthException in case of authentication failure
|
||||
* @throws TransportException if there was a transport-layer error
|
||||
*/
|
||||
public void authPassword(String username, PasswordFinder pfinder) throws UserAuthException, TransportException {
|
||||
public void authPassword(String username, PasswordFinder pfinder)
|
||||
throws UserAuthException, TransportException {
|
||||
auth(username, new AuthPassword(pfinder));
|
||||
}
|
||||
|
||||
@@ -254,7 +259,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* @throws UserAuthException in case of authentication failure
|
||||
* @throws TransportException if there was a transport-layer error
|
||||
*/
|
||||
public void authPassword(String username, String password) throws UserAuthException, TransportException {
|
||||
public void authPassword(String username, String password)
|
||||
throws UserAuthException, TransportException {
|
||||
authPassword(username, password.toCharArray());
|
||||
}
|
||||
|
||||
@@ -269,7 +275,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* @throws UserAuthException in case of authentication failure
|
||||
* @throws TransportException if there was a transport-layer error
|
||||
*/
|
||||
public void authPublickey(String username) throws UserAuthException, TransportException {
|
||||
public void authPublickey(String username)
|
||||
throws UserAuthException, TransportException {
|
||||
String base = System.getProperty("user.home") + File.separator + ".ssh" + File.separator;
|
||||
authPublickey(username, base + "id_rsa", base + "id_dsa");
|
||||
}
|
||||
@@ -287,7 +294,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* @throws UserAuthException in case of authentication failure
|
||||
* @throws TransportException if there was a transport-layer error
|
||||
*/
|
||||
public void authPublickey(String username, Iterable<KeyProvider> keyProviders) throws UserAuthException,
|
||||
public void authPublickey(String username, Iterable<KeyProvider> keyProviders)
|
||||
throws UserAuthException,
|
||||
TransportException {
|
||||
List<AuthMethod> am = new LinkedList<AuthMethod>();
|
||||
for (KeyProvider kp : keyProviders)
|
||||
@@ -308,7 +316,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* @throws UserAuthException in case of authentication failure
|
||||
* @throws TransportException if there was a transport-layer error
|
||||
*/
|
||||
public void authPublickey(String username, KeyProvider... keyProviders) throws UserAuthException,
|
||||
public void authPublickey(String username, KeyProvider... keyProviders)
|
||||
throws UserAuthException,
|
||||
TransportException {
|
||||
authPublickey(username, Arrays.<KeyProvider>asList(keyProviders));
|
||||
}
|
||||
@@ -329,7 +338,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* @throws UserAuthException in case of authentication failure
|
||||
* @throws TransportException if there was a transport-layer error
|
||||
*/
|
||||
public void authPublickey(String username, String... locations) throws UserAuthException, TransportException {
|
||||
public void authPublickey(String username, String... locations)
|
||||
throws UserAuthException, TransportException {
|
||||
List<KeyProvider> keyProviders = new LinkedList<KeyProvider>();
|
||||
for (String loc : locations)
|
||||
try {
|
||||
@@ -349,7 +359,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* cleanup is done and the thread spawned by the transport layer for dealing with incoming packets is stopped.
|
||||
*/
|
||||
@Override
|
||||
public void disconnect() throws IOException {
|
||||
public void disconnect()
|
||||
throws IOException {
|
||||
assert isConnected();
|
||||
trans.disconnect();
|
||||
super.disconnect();
|
||||
@@ -411,7 +422,7 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
/**
|
||||
* Returns a {@link KeyProvider} instance created from a location on the file system where an <em>unencrypted</em>
|
||||
* private key file (does not require a passphrase) can be found. Simply calls {@link #loadKeys(String,
|
||||
* PasswordFinder)} with the {@link net.schmizz.sshj.userauth.password.PasswordFinder} argument as {@code null}.
|
||||
* PasswordFinder)} with the {@link PasswordFinder} argument as {@code null}.
|
||||
*
|
||||
* @param location the location for the key file
|
||||
*
|
||||
@@ -421,26 +432,27 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* BouncyCastle is not in the classpath
|
||||
* @throws IOException if the key file format is not known, if the file could not be read, etc.
|
||||
*/
|
||||
public KeyProvider loadKeys(String location) throws IOException {
|
||||
public KeyProvider loadKeys(String location)
|
||||
throws IOException {
|
||||
return loadKeys(location, (PasswordFinder) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function for createing a {@link KeyProvider} instance from given location on the file system. Creates a
|
||||
* one-off {@link PasswordFinder} using {@link net.schmizz.sshj.userauth.password.PasswordUtils#createOneOff(char[])},
|
||||
* and calls {@link #loadKeys(String,PasswordFinder)}.
|
||||
* one-off {@link PasswordFinder} using {@link PasswordUtils#createOneOff(char[])}, and calls {@link
|
||||
* #loadKeys(String,PasswordFinder)}.
|
||||
*
|
||||
* @param location location of the key file
|
||||
* @param passphrase passphrase as a char-array
|
||||
*
|
||||
* @return the key provider ready for use in authentication
|
||||
*
|
||||
* @throws net.schmizz.sshj.common.SSHException
|
||||
* if there was no suitable key provider available for the file format; typically because
|
||||
* @throws SSHException if there was no suitable key provider available for the file format; typically because
|
||||
* BouncyCastle is not in the classpath
|
||||
* @throws IOException if the key file format is not known, if the file could not be read, etc.
|
||||
*/
|
||||
public KeyProvider loadKeys(String location, char[] passphrase) throws IOException {
|
||||
public KeyProvider loadKeys(String location, char[] passphrase)
|
||||
throws IOException {
|
||||
return loadKeys(location, PasswordUtils.createOneOff(passphrase));
|
||||
}
|
||||
|
||||
@@ -459,7 +471,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* BouncyCastle is not in the classpath
|
||||
* @throws IOException if the key file format is not known, if the file could not be read, etc.
|
||||
*/
|
||||
public KeyProvider loadKeys(String location, PasswordFinder passwordFinder) throws IOException {
|
||||
public KeyProvider loadKeys(String location, PasswordFinder passwordFinder)
|
||||
throws IOException {
|
||||
File loc = new File(location);
|
||||
FileKeyProvider.Format format = KeyProviderUtil.detectKeyFileFormat(loc);
|
||||
FileKeyProvider fkp = Factory.Named.Util.create(trans.getConfig().getFileKeyProviderFactories(), format
|
||||
@@ -482,7 +495,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
*
|
||||
* @throws IOException if the key file format is not known, if the file could not be read etc.
|
||||
*/
|
||||
public KeyProvider loadKeys(String location, String passphrase) throws IOException {
|
||||
public KeyProvider loadKeys(String location, String passphrase)
|
||||
throws IOException {
|
||||
return loadKeys(location, passphrase.toCharArray());
|
||||
}
|
||||
|
||||
@@ -495,7 +509,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
*
|
||||
* @throws IOException if there is an error loading from <em>both</em> locations
|
||||
*/
|
||||
public void loadKnownHosts() throws IOException {
|
||||
public void loadKnownHosts()
|
||||
throws IOException {
|
||||
boolean loaded = false;
|
||||
final File sshDir = OpenSSHKnownHosts.detectSSHDir();
|
||||
if (sshDir != null) {
|
||||
@@ -515,7 +530,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
*
|
||||
* @throws IOException if there is an error loading from any of these locations
|
||||
*/
|
||||
public void loadKnownHosts(File location) throws IOException {
|
||||
public void loadKnownHosts(File location)
|
||||
throws IOException {
|
||||
addHostKeyVerifier(new OpenSSHKnownHosts(location));
|
||||
}
|
||||
|
||||
@@ -526,7 +542,7 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* The returned forwarder's {@link LocalPortForwarder#listen() listen()} method should be called to actually start
|
||||
* listening, this method just creates an instance.
|
||||
*
|
||||
* @param address defines where the {@link net.schmizz.sshj.connection.channel.direct.LocalPortForwarder} listens
|
||||
* @param address defines where the {@link LocalPortForwarder} listens
|
||||
* @param host hostname to which the server will forward
|
||||
* @param port the port at {@code hostname} to which the server wil forward
|
||||
*
|
||||
@@ -534,7 +550,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
*
|
||||
* @throws IOException if there is an error opening a local server socket
|
||||
*/
|
||||
public LocalPortForwarder newLocalPortForwarder(SocketAddress address, String host, int port) throws IOException {
|
||||
public LocalPortForwarder newLocalPortForwarder(SocketAddress address, String host, int port)
|
||||
throws IOException {
|
||||
return new LocalPortForwarder(getServerSocketFactory(), conn, address, host, port);
|
||||
}
|
||||
|
||||
@@ -549,8 +566,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* @param listener the {@link ConnectListener} that should be delegated the responsibility of handling forwarded
|
||||
* {@link X11Channel} 's
|
||||
*
|
||||
* @return an {@link net.schmizz.sshj.connection.channel.forwarded.X11Forwarder} that allows to {@link
|
||||
* X11Forwarder#stop() stop acting} on X11 requests from server
|
||||
* @return an {@link X11Forwarder} that allows to {@link X11Forwarder#stop() stop acting} on X11 requests from
|
||||
* server
|
||||
*/
|
||||
public X11Forwarder registerX11Forwarder(ConnectListener listener) {
|
||||
X11Forwarder x11f = new X11Forwarder(conn, listener);
|
||||
@@ -570,7 +587,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* @throws IOException if there is an error starting the {@code sftp} subsystem
|
||||
* @see StatefulSFTPClient
|
||||
*/
|
||||
public SFTPClient newSFTPClient() throws IOException {
|
||||
public SFTPClient newSFTPClient()
|
||||
throws IOException {
|
||||
assert isConnected() && isAuthenticated();
|
||||
return new SFTPClient(this);
|
||||
}
|
||||
@@ -580,11 +598,13 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
*
|
||||
* @throws TransportException if an error occurs during key exchange
|
||||
*/
|
||||
public void rekey() throws TransportException {
|
||||
public void rekey()
|
||||
throws TransportException {
|
||||
doKex();
|
||||
}
|
||||
|
||||
public Session startSession() throws ConnectionException, TransportException {
|
||||
public Session startSession()
|
||||
throws ConnectionException, TransportException {
|
||||
assert isConnected() && isAuthenticated();
|
||||
SessionChannel sess = new SessionChannel(conn);
|
||||
sess.open();
|
||||
@@ -602,7 +622,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
* @throws ClassNotFoundException if {@code JZlib} is not in classpath
|
||||
* @throws TransportException if an error occurs during renegotiation
|
||||
*/
|
||||
public void useCompression() throws ClassNotFoundException, TransportException {
|
||||
public void useCompression()
|
||||
throws ClassNotFoundException, TransportException {
|
||||
trans.getConfig().setCompressionFactories(Arrays.asList(
|
||||
new DelayedZlibCompression.Factory(),
|
||||
new ZlibCompression.Factory(),
|
||||
@@ -613,7 +634,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
|
||||
/** On connection establishment, also initialize the SSH transport via {@link Transport#init} and {@link #doKex()}. */
|
||||
@Override
|
||||
protected void onConnect() throws IOException {
|
||||
protected void onConnect()
|
||||
throws IOException {
|
||||
super.onConnect();
|
||||
trans.init(getRemoteHostname(), getRemotePort(), getInputStream(), getOutputStream());
|
||||
doKex();
|
||||
@@ -624,7 +646,8 @@ public class SSHClient extends SocketClient implements SessionFactory {
|
||||
*
|
||||
* @throws TransportException if error during kex
|
||||
*/
|
||||
protected void doKex() throws TransportException {
|
||||
protected void doKex()
|
||||
throws TransportException {
|
||||
assert trans.isRunning();
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
@@ -21,7 +21,8 @@ import net.schmizz.sshj.common.SSHPacketHandler;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
|
||||
/** Represents a service running on top of the SSH {@link net.schmizz.sshj.transport.Transport transport layer}. */
|
||||
public interface Service extends SSHPacketHandler, ErrorNotifiable {
|
||||
public interface Service
|
||||
extends SSHPacketHandler, ErrorNotifiable {
|
||||
|
||||
/** @return The assigned name for this SSH service. */
|
||||
String getName();
|
||||
@@ -34,17 +35,20 @@ public interface Service extends SSHPacketHandler, ErrorNotifiable {
|
||||
*
|
||||
* @throws SSHException if the packet is unexpected and may represent a disruption
|
||||
*/
|
||||
void notifyUnimplemented(long seqNum) throws SSHException;
|
||||
void notifyUnimplemented(long seqNum)
|
||||
throws SSHException;
|
||||
|
||||
/**
|
||||
* Request and install this service with the associated transport. Implementations should aim to make this method
|
||||
* idempotent by first checking the {@link net.schmizz.sshj.transport.Transport#getService() currently active
|
||||
* idempotent by first checking the {@link net.schmizz.sshj.transport.Transport#getService()} currently active
|
||||
* service}.
|
||||
*
|
||||
* @throws TransportException if there is an error sending the service request
|
||||
*/
|
||||
void request() throws TransportException;
|
||||
void request()
|
||||
throws TransportException;
|
||||
|
||||
void notifyDisconnect() throws SSHException;
|
||||
void notifyDisconnect()
|
||||
throws SSHException;
|
||||
|
||||
}
|
||||
@@ -48,12 +48,33 @@ import java.util.Arrays;
|
||||
|
||||
public class Buffer<T extends Buffer<T>> {
|
||||
|
||||
public static class BufferException extends SSHRuntimeException {
|
||||
public static class BufferException
|
||||
extends SSHRuntimeException {
|
||||
public BufferException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PlainBuffer
|
||||
extends Buffer<PlainBuffer> {
|
||||
|
||||
public PlainBuffer() {
|
||||
super();
|
||||
}
|
||||
|
||||
public PlainBuffer(Buffer<?> from) {
|
||||
super(from);
|
||||
}
|
||||
|
||||
public PlainBuffer(byte[] b) {
|
||||
super(b);
|
||||
}
|
||||
|
||||
public PlainBuffer(int size) {
|
||||
super(size);
|
||||
}
|
||||
}
|
||||
|
||||
/** The default size for a {@code Buffer} (256 bytes) */
|
||||
public static final int DEFAULT_SIZE = 256;
|
||||
|
||||
@@ -473,18 +494,22 @@ public class Buffer<T extends Buffer<T>> {
|
||||
public T putPublicKey(PublicKey key) {
|
||||
KeyType type = KeyType.fromKey(key);
|
||||
switch (type) {
|
||||
case RSA:
|
||||
case RSA: {
|
||||
final RSAPublicKey rsaKey = (RSAPublicKey) key;
|
||||
putString(type.toString()) // ssh-rsa
|
||||
.putMPInt(((RSAPublicKey) key).getPublicExponent()) // e
|
||||
.putMPInt(((RSAPublicKey) key).getModulus()); // n
|
||||
.putMPInt(rsaKey.getPublicExponent()) // e
|
||||
.putMPInt(rsaKey.getModulus()); // n
|
||||
break;
|
||||
case DSA:
|
||||
}
|
||||
case DSA: {
|
||||
final DSAPublicKey dsaKey = (DSAPublicKey) key;
|
||||
putString(type.toString()) // ssh-dss
|
||||
.putMPInt(((DSAPublicKey) key).getParams().getP()) // p
|
||||
.putMPInt(((DSAPublicKey) key).getParams().getQ()) // q
|
||||
.putMPInt(((DSAPublicKey) key).getParams().getG()) // g
|
||||
.putMPInt(((DSAPublicKey) key).getY()); // y
|
||||
.putMPInt(dsaKey.getParams().getP()) // p
|
||||
.putMPInt(dsaKey.getParams().getQ()) // q
|
||||
.putMPInt(dsaKey.getParams().getG()) // g
|
||||
.putMPInt(dsaKey.getY()); // y
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert false;
|
||||
}
|
||||
@@ -492,7 +517,8 @@ public class Buffer<T extends Buffer<T>> {
|
||||
}
|
||||
|
||||
public T putSignature(String sigFormat, byte[] sigData) {
|
||||
return putString(new PlainBuffer().putString(sigFormat).putBytes(sigData).getCompactData());
|
||||
final byte[] sig = new PlainBuffer().putString(sigFormat).putBytes(sigData).getCompactData();
|
||||
return putString(sig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -509,23 +535,4 @@ public class Buffer<T extends Buffer<T>> {
|
||||
return "Buffer [rpos=" + rpos + ", wpos=" + wpos + ", size=" + data.length + "]";
|
||||
}
|
||||
|
||||
public static class PlainBuffer extends Buffer<PlainBuffer> {
|
||||
|
||||
public PlainBuffer() {
|
||||
super();
|
||||
}
|
||||
|
||||
public PlainBuffer(Buffer<?> from) {
|
||||
super(from);
|
||||
}
|
||||
|
||||
public PlainBuffer(byte[] b) {
|
||||
super(b);
|
||||
}
|
||||
|
||||
public PlainBuffer(int size) {
|
||||
super(size);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ public interface Factory<T> {
|
||||
*
|
||||
* @param <T> type of object created by this factory
|
||||
*/
|
||||
interface Named<T> extends Factory<T> {
|
||||
interface Named<T>
|
||||
extends Factory<T> {
|
||||
|
||||
/** Utility functions */
|
||||
public static class Util {
|
||||
|
||||
@@ -43,7 +43,8 @@ import java.io.IOException;
|
||||
* Most exceptions in {@code org.apache.commons.net.ssh} are instances of this class. An {@link SSHException} is itself
|
||||
* an {@link IOException} and can be caught like that if this level of granularity is not desired.
|
||||
*/
|
||||
public class SSHException extends IOException {
|
||||
public class SSHException
|
||||
extends IOException {
|
||||
|
||||
public static final ExceptionChainer<SSHException> chainer = new ExceptionChainer<SSHException>() {
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@ package net.schmizz.sshj.common;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class SSHPacket extends Buffer<SSHPacket> {
|
||||
public class SSHPacket
|
||||
extends Buffer<SSHPacket> {
|
||||
|
||||
public SSHPacket() {
|
||||
super();
|
||||
|
||||
@@ -30,6 +30,7 @@ public interface SSHPacketHandler {
|
||||
*
|
||||
* @throws SSHException if there is a non-recoverable error
|
||||
*/
|
||||
void handle(Message msg, SSHPacket buf) throws SSHException;
|
||||
void handle(Message msg, SSHPacket buf)
|
||||
throws SSHException;
|
||||
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
package net.schmizz.sshj.common;
|
||||
|
||||
/** Represents unrecoverable exceptions in the {@code org.apache.commons.net.ssh} package. */
|
||||
public class SSHRuntimeException extends RuntimeException {
|
||||
public class SSHRuntimeException
|
||||
extends RuntimeException {
|
||||
|
||||
public SSHRuntimeException() {
|
||||
this(null, null);
|
||||
|
||||
@@ -52,11 +52,14 @@ import java.security.NoSuchProviderException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
|
||||
// TODO refactor
|
||||
|
||||
/** Static utility method relating to security facilities. */
|
||||
public class SecurityUtils {
|
||||
|
||||
private static class BouncyCastleRegistration {
|
||||
public void run() throws Exception {
|
||||
public void run()
|
||||
throws Exception {
|
||||
if (java.security.Security.getProvider(BOUNCY_CASTLE) == null) {
|
||||
LOG.info("Trying to register BouncyCastle as a JCE provider");
|
||||
java.security.Security.addProvider(new BouncyCastleProvider());
|
||||
@@ -83,8 +86,8 @@ public class SecurityUtils {
|
||||
private static Boolean registerBouncyCastle;
|
||||
private static boolean registrationDone;
|
||||
|
||||
public static synchronized Cipher getCipher(String transformation) throws NoSuchAlgorithmException,
|
||||
NoSuchPaddingException, NoSuchProviderException {
|
||||
public static synchronized Cipher getCipher(String transformation)
|
||||
throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException {
|
||||
register();
|
||||
if (getSecurityProvider() == null)
|
||||
return Cipher.getInstance(transformation);
|
||||
@@ -127,8 +130,8 @@ public class SecurityUtils {
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws NoSuchProviderException
|
||||
*/
|
||||
public static synchronized KeyAgreement getKeyAgreement(String algorithm) throws NoSuchAlgorithmException,
|
||||
NoSuchProviderException {
|
||||
public static synchronized KeyAgreement getKeyAgreement(String algorithm)
|
||||
throws NoSuchAlgorithmException, NoSuchProviderException {
|
||||
register();
|
||||
if (getSecurityProvider() == null)
|
||||
return KeyAgreement.getInstance(algorithm);
|
||||
@@ -146,8 +149,8 @@ public class SecurityUtils {
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws NoSuchProviderException
|
||||
*/
|
||||
public static synchronized KeyFactory getKeyFactory(String algorithm) throws NoSuchAlgorithmException,
|
||||
NoSuchProviderException {
|
||||
public static synchronized KeyFactory getKeyFactory(String algorithm)
|
||||
throws NoSuchAlgorithmException, NoSuchProviderException {
|
||||
register();
|
||||
if (getSecurityProvider() == null)
|
||||
return KeyFactory.getInstance(algorithm);
|
||||
@@ -165,8 +168,8 @@ public class SecurityUtils {
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws NoSuchProviderException
|
||||
*/
|
||||
public static synchronized KeyPairGenerator getKeyPairGenerator(String algorithm) throws NoSuchAlgorithmException,
|
||||
NoSuchProviderException {
|
||||
public static synchronized KeyPairGenerator getKeyPairGenerator(String algorithm)
|
||||
throws NoSuchAlgorithmException, NoSuchProviderException {
|
||||
register();
|
||||
if (getSecurityProvider() == null)
|
||||
return KeyPairGenerator.getInstance(algorithm);
|
||||
@@ -184,7 +187,8 @@ public class SecurityUtils {
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws NoSuchProviderException
|
||||
*/
|
||||
public static synchronized Mac getMAC(String algorithm) throws NoSuchAlgorithmException, NoSuchProviderException {
|
||||
public static synchronized Mac getMAC(String algorithm)
|
||||
throws NoSuchAlgorithmException, NoSuchProviderException {
|
||||
register();
|
||||
if (getSecurityProvider() == null)
|
||||
return Mac.getInstance(algorithm);
|
||||
@@ -202,8 +206,8 @@ public class SecurityUtils {
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws NoSuchProviderException
|
||||
*/
|
||||
public static synchronized MessageDigest getMessageDigest(String algorithm) throws NoSuchAlgorithmException,
|
||||
NoSuchProviderException {
|
||||
public static synchronized MessageDigest getMessageDigest(String algorithm)
|
||||
throws NoSuchAlgorithmException, NoSuchProviderException {
|
||||
register();
|
||||
if (getSecurityProvider() == null)
|
||||
return MessageDigest.getInstance(algorithm);
|
||||
@@ -221,8 +225,8 @@ public class SecurityUtils {
|
||||
return securityProvider;
|
||||
}
|
||||
|
||||
public static synchronized Signature getSignature(String algorithm) throws NoSuchAlgorithmException,
|
||||
NoSuchProviderException {
|
||||
public static synchronized Signature getSignature(String algorithm)
|
||||
throws NoSuchAlgorithmException, NoSuchProviderException {
|
||||
register();
|
||||
if (getSecurityProvider() == null)
|
||||
return Signature.getInstance(algorithm);
|
||||
|
||||
@@ -24,7 +24,8 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class StreamCopier extends Thread {
|
||||
public class StreamCopier
|
||||
extends Thread {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(StreamCopier.class);
|
||||
|
||||
@@ -41,7 +42,8 @@ public class StreamCopier extends Thread {
|
||||
};
|
||||
}
|
||||
|
||||
public static long copy(InputStream in, OutputStream out, int bufSize, boolean keepFlushing) throws IOException {
|
||||
public static long copy(InputStream in, OutputStream out, int bufSize, boolean keepFlushing)
|
||||
throws IOException {
|
||||
long count = 0;
|
||||
|
||||
final long startTime = System.currentTimeMillis();
|
||||
@@ -64,7 +66,8 @@ public class StreamCopier extends Thread {
|
||||
return count;
|
||||
}
|
||||
|
||||
public static String copyStreamToString(InputStream stream) throws IOException {
|
||||
public static String copyStreamToString(InputStream stream)
|
||||
throws IOException {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
int read;
|
||||
while ((read = stream.read()) != -1)
|
||||
|
||||
@@ -12,26 +12,6 @@
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file may incorporate work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.connection;
|
||||
|
||||
@@ -43,16 +23,14 @@ import net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener;
|
||||
import net.schmizz.sshj.transport.Transport;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
|
||||
/**
|
||||
* Connection layer of the SSH protocol.
|
||||
*
|
||||
* @see rfc4254
|
||||
*/
|
||||
/** Connection layer of the SSH protocol. Refer to RFC 254. */
|
||||
public interface Connection {
|
||||
|
||||
/**
|
||||
* Attach a {@link net.schmizz.sshj.connection.channel.Channel} to this connection. A channel must be attached to
|
||||
* the connection if it is to receive any channel-specific data that is received.
|
||||
*
|
||||
* @param chan
|
||||
*/
|
||||
void attach(Channel chan);
|
||||
|
||||
@@ -60,24 +38,45 @@ public interface Connection {
|
||||
* Attach a {@link net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener} to this connection, which
|
||||
* will be delegated opening of any {@code CHANNEL_OPEN} packets {@link net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener#getChannelType()
|
||||
* for which it is responsible}.
|
||||
*
|
||||
* @param opener
|
||||
*/
|
||||
void attach(ForwardedChannelOpener opener);
|
||||
|
||||
/** Forget an attached {@link Channel}. */
|
||||
/**
|
||||
* Forget an attached {@link Channel}.
|
||||
*
|
||||
* @param chan
|
||||
*/
|
||||
void forget(Channel chan);
|
||||
|
||||
/** Forget an attached {@link net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener}. */
|
||||
/**
|
||||
* Forget an attached {@link net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener}.
|
||||
*
|
||||
* @param handler
|
||||
*/
|
||||
void forget(ForwardedChannelOpener handler);
|
||||
|
||||
/** Returns an attached {@link Channel} of specified channel-id, or {@code null} if no such channel was attached */
|
||||
/**
|
||||
* Returns an attached {@link Channel} of specified channel-id, or {@code null} if no such channel was attached
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
Channel get(int id);
|
||||
|
||||
/** Wait for the situation that no channels are attached (e.g., got closed). */
|
||||
void join() throws InterruptedException;
|
||||
/**
|
||||
* Wait for the situation that no channels are attached (e.g., got closed).
|
||||
*
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
void join()
|
||||
throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Returns an attached {@link net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener} of specified
|
||||
* channel-type, or {@code null} if no such channel was attached
|
||||
* @param chanType channel type
|
||||
*
|
||||
* @return an attached {@link ForwardedChannelOpener} of specified channel-type, or {@code null} if no such channel
|
||||
* was attached
|
||||
*/
|
||||
ForwardedChannelOpener get(String chanType);
|
||||
|
||||
@@ -97,7 +96,8 @@ public interface Connection {
|
||||
* @throws TransportException if there is an error sending the request
|
||||
*/
|
||||
public Future<SSHPacket, ConnectionException> sendGlobalRequest(String name, boolean wantReply,
|
||||
byte[] specifics) throws TransportException;
|
||||
byte[] specifics)
|
||||
throws TransportException;
|
||||
|
||||
/**
|
||||
* Send a {@code SSH_MSG_OPEN_FAILURE} for specified {@code Reason} and {@code message}.
|
||||
@@ -108,7 +108,8 @@ public interface Connection {
|
||||
*
|
||||
* @throws TransportException
|
||||
*/
|
||||
void sendOpenFailure(int recipient, OpenFailException.Reason reason, String message) throws TransportException;
|
||||
void sendOpenFailure(int recipient, OpenFailException.Reason reason, String message)
|
||||
throws TransportException;
|
||||
|
||||
/**
|
||||
* Get the maximum packet size for the local window this connection recommends to any {@link Channel}'s that ask for
|
||||
|
||||
@@ -12,26 +12,6 @@
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file may incorporate work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.connection;
|
||||
|
||||
@@ -40,7 +20,8 @@ import net.schmizz.sshj.common.DisconnectReason;
|
||||
import net.schmizz.sshj.common.SSHException;
|
||||
|
||||
/** Connection-layer exception. */
|
||||
public class ConnectionException extends SSHException {
|
||||
public class ConnectionException
|
||||
extends SSHException {
|
||||
|
||||
public static final ExceptionChainer<ConnectionException> chainer = new ExceptionChainer<ConnectionException>() {
|
||||
public ConnectionException chain(Throwable t) {
|
||||
|
||||
@@ -37,7 +37,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/** {@link Connection} implementation. */
|
||||
public class ConnectionImpl extends AbstractService implements Connection {
|
||||
public class ConnectionImpl
|
||||
extends AbstractService
|
||||
implements Connection {
|
||||
|
||||
private final Object internalSynchronizer = new Object();
|
||||
|
||||
@@ -93,7 +95,8 @@ public class ConnectionImpl extends AbstractService implements Connection {
|
||||
openers.put(opener.getChannelType(), opener);
|
||||
}
|
||||
|
||||
private Channel getChannel(SSHPacket buffer) throws ConnectionException {
|
||||
private Channel getChannel(SSHPacket buffer)
|
||||
throws ConnectionException {
|
||||
int recipient = buffer.readInt();
|
||||
Channel channel = get(recipient);
|
||||
if (channel != null)
|
||||
@@ -106,7 +109,8 @@ public class ConnectionImpl extends AbstractService implements Connection {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Message msg, SSHPacket buf) throws SSHException {
|
||||
public void handle(Message msg, SSHPacket buf)
|
||||
throws SSHException {
|
||||
if (msg.in(91, 100))
|
||||
getChannel(buf).handle(msg, buf);
|
||||
|
||||
@@ -162,7 +166,8 @@ public class ConnectionImpl extends AbstractService implements Connection {
|
||||
this.windowSize = windowSize;
|
||||
}
|
||||
|
||||
public void join() throws InterruptedException {
|
||||
public void join()
|
||||
throws InterruptedException {
|
||||
synchronized (internalSynchronizer) {
|
||||
while (!channels.isEmpty())
|
||||
internalSynchronizer.wait();
|
||||
@@ -174,10 +179,12 @@ public class ConnectionImpl extends AbstractService implements Connection {
|
||||
}
|
||||
|
||||
public Future<SSHPacket, ConnectionException> sendGlobalRequest(String name, boolean wantReply,
|
||||
byte[] specifics) throws TransportException {
|
||||
byte[] specifics)
|
||||
throws TransportException {
|
||||
synchronized (globalReqFutures) {
|
||||
log.info("Making global request for `{}`", name);
|
||||
trans.write(new SSHPacket(Message.GLOBAL_REQUEST).putString(name).putBoolean(wantReply).putRawBytes(specifics));
|
||||
trans.write(new SSHPacket(Message.GLOBAL_REQUEST).putString(name)
|
||||
.putBoolean(wantReply).putRawBytes(specifics));
|
||||
|
||||
Future<SSHPacket, ConnectionException> future = null;
|
||||
if (wantReply) {
|
||||
@@ -188,7 +195,8 @@ public class ConnectionImpl extends AbstractService implements Connection {
|
||||
}
|
||||
}
|
||||
|
||||
private void gotGlobalReqResponse(SSHPacket response) throws ConnectionException {
|
||||
private void gotGlobalReqResponse(SSHPacket response)
|
||||
throws ConnectionException {
|
||||
synchronized (globalReqFutures) {
|
||||
Future<SSHPacket, ConnectionException> gr = globalReqFutures.poll();
|
||||
if (gr == null)
|
||||
@@ -201,7 +209,8 @@ public class ConnectionImpl extends AbstractService implements Connection {
|
||||
}
|
||||
}
|
||||
|
||||
private void gotChannelOpen(SSHPacket buf) throws ConnectionException, TransportException {
|
||||
private void gotChannelOpen(SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
final String type = buf.readString();
|
||||
log.debug("Received CHANNEL_OPEN for `{}` channel", type);
|
||||
if (openers.containsKey(type))
|
||||
@@ -212,17 +221,19 @@ public class ConnectionImpl extends AbstractService implements Connection {
|
||||
}
|
||||
}
|
||||
|
||||
public void sendOpenFailure(int recipient, Reason reason, String message) throws TransportException {
|
||||
trans.write(new SSHPacket(Message.CHANNEL_OPEN_FAILURE) //
|
||||
.putInt(recipient) //
|
||||
.putInt(reason.getCode()) //
|
||||
public void sendOpenFailure(int recipient, Reason reason, String message)
|
||||
throws TransportException {
|
||||
trans.write(new SSHPacket(Message.CHANNEL_OPEN_FAILURE)
|
||||
.putInt(recipient)
|
||||
.putInt(reason.getCode())
|
||||
.putString(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDisconnect() throws SSHException {
|
||||
public void notifyDisconnect()
|
||||
throws SSHException {
|
||||
super.notifyDisconnect();
|
||||
// wh'about them futures?
|
||||
FutureUtils.alertAll(new ConnectionException("Disconnected."), globalReqFutures);
|
||||
for (Channel chan : channels.values())
|
||||
chan.close();
|
||||
}
|
||||
|
||||
@@ -58,7 +58,8 @@ import java.util.Queue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public abstract class AbstractChannel implements Channel {
|
||||
public abstract class AbstractChannel
|
||||
implements Channel {
|
||||
|
||||
/** Logger */
|
||||
protected final Logger log;
|
||||
@@ -164,7 +165,8 @@ public abstract class AbstractChannel implements Channel {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void handle(Message msg, SSHPacket buf) throws ConnectionException, TransportException {
|
||||
public void handle(Message msg, SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
switch (msg) {
|
||||
|
||||
case CHANNEL_DATA:
|
||||
@@ -205,7 +207,8 @@ public abstract class AbstractChannel implements Channel {
|
||||
}
|
||||
}
|
||||
|
||||
private void gotClose() throws TransportException {
|
||||
private void gotClose()
|
||||
throws TransportException {
|
||||
log.info("Got close");
|
||||
try {
|
||||
closeAllStreams();
|
||||
@@ -236,7 +239,8 @@ public abstract class AbstractChannel implements Channel {
|
||||
this.autoExpand = autoExpand;
|
||||
}
|
||||
|
||||
public void close() throws ConnectionException, TransportException {
|
||||
public void close()
|
||||
throws ConnectionException, TransportException {
|
||||
lock.lock();
|
||||
try {
|
||||
try {
|
||||
@@ -251,7 +255,8 @@ public abstract class AbstractChannel implements Channel {
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized void sendClose() throws TransportException {
|
||||
protected synchronized void sendClose()
|
||||
throws TransportException {
|
||||
try {
|
||||
if (!closeRequested) {
|
||||
log.info("Sending close");
|
||||
@@ -271,7 +276,8 @@ public abstract class AbstractChannel implements Channel {
|
||||
}
|
||||
}
|
||||
|
||||
private void gotChannelRequest(SSHPacket buf) throws ConnectionException, TransportException {
|
||||
private void gotChannelRequest(SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
final String reqType = buf.readString();
|
||||
buf.readBoolean(); // We don't care about the 'want-reply' value
|
||||
log.info("Got chan request for `{}`", reqType);
|
||||
@@ -289,15 +295,18 @@ public abstract class AbstractChannel implements Channel {
|
||||
close.set();
|
||||
}
|
||||
|
||||
protected void gotExtendedData(int dataTypeCode, SSHPacket buf) throws ConnectionException, TransportException {
|
||||
protected void gotExtendedData(int dataTypeCode, SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
throw new ConnectionException(DisconnectReason.PROTOCOL_ERROR, "Extended data not supported on " + type
|
||||
+ " channel");
|
||||
}
|
||||
|
||||
protected void gotUnknown(Message msg, SSHPacket buf) throws ConnectionException, TransportException {
|
||||
protected void gotUnknown(Message msg, SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
}
|
||||
|
||||
protected void handleRequest(String reqType, SSHPacket buf) throws ConnectionException, TransportException {
|
||||
protected void handleRequest(String reqType, SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
trans.write(newBuffer(Message.CHANNEL_FAILURE));
|
||||
}
|
||||
|
||||
@@ -305,7 +314,8 @@ public abstract class AbstractChannel implements Channel {
|
||||
return new SSHPacket(cmd).putInt(recipient);
|
||||
}
|
||||
|
||||
protected void receiveInto(ChannelInputStream stream, SSHPacket buf) throws ConnectionException, TransportException {
|
||||
protected void receiveInto(ChannelInputStream stream, SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
final int len = buf.readInt();
|
||||
if (len < 0 || len > getLocalMaxPacketSize() || len != buf.available())
|
||||
throw new ConnectionException(DisconnectReason.PROTOCOL_ERROR, "Bad item length: " + len);
|
||||
@@ -315,7 +325,8 @@ public abstract class AbstractChannel implements Channel {
|
||||
}
|
||||
|
||||
protected synchronized Event<ConnectionException> sendChannelRequest(String reqType, boolean wantReply,
|
||||
Buffer.PlainBuffer reqSpecific) throws TransportException {
|
||||
Buffer.PlainBuffer reqSpecific)
|
||||
throws TransportException {
|
||||
log.info("Sending channel request for `{}`", reqType);
|
||||
trans.write(
|
||||
newBuffer(Message.CHANNEL_REQUEST)
|
||||
@@ -332,7 +343,8 @@ public abstract class AbstractChannel implements Channel {
|
||||
return responseEvent;
|
||||
}
|
||||
|
||||
private synchronized void gotResponse(boolean success) throws ConnectionException {
|
||||
private synchronized void gotResponse(boolean success)
|
||||
throws ConnectionException {
|
||||
final Event<ConnectionException> responseEvent = chanReqResponseEvents.poll();
|
||||
if (responseEvent != null) {
|
||||
if (success)
|
||||
@@ -345,7 +357,8 @@ public abstract class AbstractChannel implements Channel {
|
||||
"Received response to channel request when none was requested");
|
||||
}
|
||||
|
||||
private synchronized void gotEOF() throws TransportException {
|
||||
private synchronized void gotEOF()
|
||||
throws TransportException {
|
||||
log.info("Got EOF");
|
||||
eofGot = true;
|
||||
eofInputStreams();
|
||||
@@ -358,7 +371,8 @@ public abstract class AbstractChannel implements Channel {
|
||||
in.eof();
|
||||
}
|
||||
|
||||
public synchronized void sendEOF() throws TransportException {
|
||||
public synchronized void sendEOF()
|
||||
throws TransportException {
|
||||
try {
|
||||
if (!closeRequested && !eofSent) {
|
||||
log.info("Sending EOF");
|
||||
|
||||
@@ -12,26 +12,6 @@
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file may incorporate work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.connection.channel;
|
||||
|
||||
@@ -45,10 +25,12 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/** A channel is the basic medium for application-layer data on top of an SSH transport. */
|
||||
public interface Channel extends Closeable, SSHPacketHandler, ErrorNotifiable {
|
||||
public interface Channel
|
||||
extends Closeable, SSHPacketHandler, ErrorNotifiable {
|
||||
|
||||
/** Direct channels are those that are initiated by us. */
|
||||
interface Direct extends Channel {
|
||||
interface Direct
|
||||
extends Channel {
|
||||
/**
|
||||
* Request opening this channel from remote end.
|
||||
*
|
||||
@@ -57,19 +39,22 @@ public interface Channel extends Closeable, SSHPacketHandler, ErrorNotifiable {
|
||||
* other connection-layer error
|
||||
* @throws TransportException error writing packets etc.
|
||||
*/
|
||||
void open() throws OpenFailException, ConnectionException, TransportException;
|
||||
void open()
|
||||
throws OpenFailException, ConnectionException, TransportException;
|
||||
|
||||
}
|
||||
|
||||
/** Forwarded channels are those that are initiated by the server. */
|
||||
interface Forwarded extends Channel {
|
||||
interface Forwarded
|
||||
extends Channel {
|
||||
|
||||
/**
|
||||
* Confirm {@code CHANNEL_OPEN} request.
|
||||
*
|
||||
* @throws TransportException error sending confirmation packet
|
||||
*/
|
||||
void confirm() throws TransportException;
|
||||
void confirm()
|
||||
throws TransportException;
|
||||
|
||||
/** Returns the IP of where the forwarded connection originates. */
|
||||
String getOriginatorIP();
|
||||
@@ -85,13 +70,15 @@ public interface Channel extends Closeable, SSHPacketHandler, ErrorNotifiable {
|
||||
*
|
||||
* @throws TransportException error sending rejection packet
|
||||
*/
|
||||
void reject(OpenFailException.Reason reason, String message) throws TransportException;
|
||||
void reject(OpenFailException.Reason reason, String message)
|
||||
throws TransportException;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** Close this channel. */
|
||||
void close() throws TransportException, ConnectionException;
|
||||
void close()
|
||||
throws TransportException, ConnectionException;
|
||||
|
||||
/**
|
||||
* Returns whether auto-expansion of local window is set.
|
||||
@@ -103,38 +90,41 @@ public interface Channel extends Closeable, SSHPacketHandler, ErrorNotifiable {
|
||||
/** Returns the channel ID */
|
||||
int getID();
|
||||
|
||||
/** Returns the {@code InputStream} for this channel. */
|
||||
/** @return the {@code InputStream} for this channel. */
|
||||
InputStream getInputStream();
|
||||
|
||||
/** Returns the maximum packet size that we have specified. */
|
||||
/** @return the maximum packet size that we have specified. */
|
||||
int getLocalMaxPacketSize();
|
||||
|
||||
/** Returns the current local window size. */
|
||||
/** @return the current local window size. */
|
||||
int getLocalWinSize();
|
||||
|
||||
/** Returns an {@code OutputStream} for this channel. */
|
||||
/** @return an {@code OutputStream} for this channel. */
|
||||
OutputStream getOutputStream();
|
||||
|
||||
/** Returns the channel ID at the remote end. */
|
||||
/** @return the channel ID at the remote end. */
|
||||
int getRecipient();
|
||||
|
||||
/** Returns the maximum packet size as specified by the remote end. */
|
||||
/** @return the maximum packet size as specified by the remote end. */
|
||||
int getRemoteMaxPacketSize();
|
||||
|
||||
/** Returns the current remote window size. */
|
||||
/** @return the current remote window size. */
|
||||
int getRemoteWinSize();
|
||||
|
||||
/** Returns the channel type identifier. */
|
||||
/** @return the channel type identifier. */
|
||||
String getType();
|
||||
|
||||
/** Returns whether the channel is open. */
|
||||
/** @return whether the channel is open. */
|
||||
boolean isOpen();
|
||||
|
||||
/**
|
||||
* Sends an EOF message to the server for this channel; indicating that no more data will be sent by us. The {@code
|
||||
* OutputStream} for this channel will be closed and no longer usable.
|
||||
*
|
||||
* @throws TransportException
|
||||
*/
|
||||
void sendEOF() throws TransportException;
|
||||
void sendEOF()
|
||||
throws TransportException;
|
||||
|
||||
/**
|
||||
* Set whether local window should automatically expand when data is received, irrespective of whether data has been
|
||||
|
||||
@@ -55,7 +55,9 @@ import java.io.InterruptedIOException;
|
||||
* {@link InputStream} for channels. Can {@link #receive(byte[], int, int) receive} data into its buffer for serving to
|
||||
* readers.
|
||||
*/
|
||||
public final class ChannelInputStream extends InputStream implements ErrorNotifiable {
|
||||
public final class ChannelInputStream
|
||||
extends InputStream
|
||||
implements ErrorNotifiable {
|
||||
|
||||
private final Logger log;
|
||||
|
||||
@@ -104,14 +106,16 @@ public final class ChannelInputStream extends InputStream implements ErrorNotifi
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
public int read()
|
||||
throws IOException {
|
||||
synchronized (b) {
|
||||
return read(b, 0, 1) == -1 ? -1 : b[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
public int read(byte[] b, int off, int len)
|
||||
throws IOException {
|
||||
synchronized (buf) {
|
||||
for (; ;) {
|
||||
if (buf.available() > 0)
|
||||
@@ -140,7 +144,8 @@ public final class ChannelInputStream extends InputStream implements ErrorNotifi
|
||||
return len;
|
||||
}
|
||||
|
||||
public void receive(byte[] data, int offset, int len) throws ConnectionException, TransportException {
|
||||
public void receive(byte[] data, int offset, int len)
|
||||
throws ConnectionException, TransportException {
|
||||
if (eof)
|
||||
throw new ConnectionException("Getting data on EOF'ed stream");
|
||||
synchronized (buf) {
|
||||
@@ -152,12 +157,14 @@ public final class ChannelInputStream extends InputStream implements ErrorNotifi
|
||||
checkWindow();
|
||||
}
|
||||
|
||||
private void checkWindow() throws TransportException {
|
||||
private void checkWindow()
|
||||
throws TransportException {
|
||||
synchronized (win) {
|
||||
final int adjustment = win.neededAdjustment();
|
||||
if (adjustment > 0) {
|
||||
log.info("Sending SSH_MSG_CHANNEL_WINDOW_ADJUST to #{} for {} bytes", chan.getRecipient(), adjustment);
|
||||
trans.write(new SSHPacket(Message.CHANNEL_WINDOW_ADJUST).putInt(chan.getRecipient()).putInt(adjustment));
|
||||
trans.write(new SSHPacket(Message.CHANNEL_WINDOW_ADJUST)
|
||||
.putInt(chan.getRecipient()).putInt(adjustment));
|
||||
win.expand(adjustment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,9 @@ import java.io.OutputStream;
|
||||
* {@link OutputStream} for channels. Buffers data upto the remote window's maximum packet size. Data can also be
|
||||
* flushed via {@link #flush()} and is also flushed on {@link #close()}.
|
||||
*/
|
||||
public final class ChannelOutputStream extends OutputStream implements ErrorNotifiable {
|
||||
public final class ChannelOutputStream
|
||||
extends OutputStream
|
||||
implements ErrorNotifiable {
|
||||
|
||||
private final Channel chan;
|
||||
private Transport trans;
|
||||
@@ -77,13 +79,15 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(int w) throws IOException {
|
||||
public synchronized void write(int w)
|
||||
throws IOException {
|
||||
b[0] = (byte) w;
|
||||
write(b, 0, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte[] data, int off, int len) throws IOException {
|
||||
public synchronized void write(byte[] data, int off, int len)
|
||||
throws IOException {
|
||||
checkClose();
|
||||
while (len > 0) {
|
||||
final int x = Math.min(len, win.getMaxPacketSize() - bufferLength);
|
||||
@@ -102,7 +106,8 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
private synchronized void checkClose() throws SSHException {
|
||||
private synchronized void checkClose()
|
||||
throws SSHException {
|
||||
if (closed)
|
||||
if (error != null)
|
||||
throw error;
|
||||
@@ -111,7 +116,8 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
public synchronized void close()
|
||||
throws IOException {
|
||||
if (!closed)
|
||||
try {
|
||||
flush();
|
||||
@@ -126,7 +132,8 @@ public final class ChannelOutputStream extends OutputStream implements ErrorNoti
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void flush() throws IOException {
|
||||
public synchronized void flush()
|
||||
throws IOException {
|
||||
checkClose();
|
||||
|
||||
if (bufferLength <= 0) // No data to send
|
||||
|
||||
@@ -12,35 +12,20 @@
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file may incorporate work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.connection.channel;
|
||||
|
||||
import net.schmizz.sshj.connection.ConnectionException;
|
||||
|
||||
public class OpenFailException extends ConnectionException {
|
||||
public class OpenFailException
|
||||
extends ConnectionException {
|
||||
|
||||
public enum Reason {
|
||||
UNKNOWN(0), ADMINISTRATIVELY_PROHIBITED(1), CONNECT_FAILED(2), UNKNOWN_CHANNEL_TYPE(3), RESOURCE_SHORTAGE(4);
|
||||
UNKNOWN(0),
|
||||
ADMINISTRATIVELY_PROHIBITED(1),
|
||||
CONNECT_FAILED(2),
|
||||
UNKNOWN_CHANNEL_TYPE(3),
|
||||
RESOURCE_SHORTAGE(4);
|
||||
|
||||
public static Reason fromInt(int code) {
|
||||
for (Reason rc : Reason.values())
|
||||
|
||||
@@ -68,13 +68,15 @@ public abstract class Window {
|
||||
}
|
||||
|
||||
/** Controls how much data we can send before an adjustment notification from remote end is required. */
|
||||
public static final class Remote extends Window {
|
||||
public static final class Remote
|
||||
extends Window {
|
||||
|
||||
public Remote(int chanID, int initialWinSize, int maxPacketSize) {
|
||||
super(chanID, "remote win", initialWinSize, maxPacketSize);
|
||||
}
|
||||
|
||||
public void waitAndConsume(int howMuch) throws ConnectionException {
|
||||
public void waitAndConsume(int howMuch)
|
||||
throws ConnectionException {
|
||||
synchronized (lock) {
|
||||
while (size < howMuch) {
|
||||
log.debug("Waiting, need window space for {} bytes", howMuch);
|
||||
@@ -91,7 +93,8 @@ public abstract class Window {
|
||||
}
|
||||
|
||||
/** Controls how much data remote end can send before an adjustment notification from us is required. */
|
||||
public static final class Local extends Window {
|
||||
public static final class Local
|
||||
extends Window {
|
||||
|
||||
private final int initialSize;
|
||||
private final int threshold;
|
||||
@@ -102,7 +105,8 @@ public abstract class Window {
|
||||
threshold = Math.min(maxPacketSize * 20, initialSize / 4);
|
||||
}
|
||||
|
||||
public int neededAdjustment() throws TransportException {
|
||||
public int neededAdjustment()
|
||||
throws TransportException {
|
||||
synchronized (lock) {
|
||||
return (size - threshold <= 0) ? (initialSize - size) : 0;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,9 @@ import net.schmizz.sshj.transport.TransportException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** Base class for direct channels whose open is initated by the client. */
|
||||
public abstract class AbstractDirectChannel extends AbstractChannel implements Channel.Direct {
|
||||
public abstract class AbstractDirectChannel
|
||||
extends AbstractChannel
|
||||
implements Channel.Direct {
|
||||
|
||||
protected AbstractDirectChannel(String name, Connection conn) {
|
||||
super(name, conn);
|
||||
@@ -58,7 +60,8 @@ public abstract class AbstractDirectChannel extends AbstractChannel implements C
|
||||
conn.attach(this);
|
||||
}
|
||||
|
||||
public void open() throws ConnectionException, TransportException {
|
||||
public void open()
|
||||
throws ConnectionException, TransportException {
|
||||
trans.write(buildOpenReq());
|
||||
open.await(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
}
|
||||
@@ -82,7 +85,8 @@ public abstract class AbstractDirectChannel extends AbstractChannel implements C
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void gotUnknown(Message cmd, SSHPacket buf) throws ConnectionException, TransportException {
|
||||
protected void gotUnknown(Message cmd, SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
switch (cmd) {
|
||||
|
||||
case CHANNEL_OPEN_CONFIRMATION:
|
||||
|
||||
@@ -51,7 +51,8 @@ import java.net.SocketAddress;
|
||||
|
||||
public class LocalPortForwarder {
|
||||
|
||||
private class DirectTCPIPChannel extends AbstractDirectChannel {
|
||||
private class DirectTCPIPChannel
|
||||
extends AbstractDirectChannel {
|
||||
|
||||
private final Socket sock;
|
||||
|
||||
@@ -60,13 +61,15 @@ public class LocalPortForwarder {
|
||||
this.sock = sock;
|
||||
}
|
||||
|
||||
private void start() throws IOException {
|
||||
private void start()
|
||||
throws IOException {
|
||||
sock.setSendBufferSize(getLocalMaxPacketSize());
|
||||
sock.setReceiveBufferSize(getRemoteMaxPacketSize());
|
||||
|
||||
final ErrorCallback closer = StreamCopier.closeOnErrorCallback(this,
|
||||
new Closeable() {
|
||||
public void close() throws IOException {
|
||||
public void close()
|
||||
throws IOException {
|
||||
sock.close();
|
||||
}
|
||||
});
|
||||
@@ -102,7 +105,8 @@ public class LocalPortForwarder {
|
||||
private final String host;
|
||||
private final int port;
|
||||
|
||||
public LocalPortForwarder(Connection conn, SocketAddress listeningAddr, String host, int port) throws IOException {
|
||||
public LocalPortForwarder(Connection conn, SocketAddress listeningAddr, String host, int port)
|
||||
throws IOException {
|
||||
this(ServerSocketFactory.getDefault(), conn, listeningAddr, host, port);
|
||||
}
|
||||
|
||||
@@ -118,7 +122,8 @@ public class LocalPortForwarder {
|
||||
*
|
||||
* @throws IOException if there is an error binding on specified {@code listeningAddr}
|
||||
*/
|
||||
public LocalPortForwarder(ServerSocketFactory ssf, Connection conn, SocketAddress listeningAddr, String host, int port) throws IOException {
|
||||
public LocalPortForwarder(ServerSocketFactory ssf, Connection conn, SocketAddress listeningAddr, String host, int port)
|
||||
throws IOException {
|
||||
this.conn = conn;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
@@ -132,7 +137,8 @@ public class LocalPortForwarder {
|
||||
}
|
||||
|
||||
/** Start listening for incoming connections and forward to remote host as a channel. */
|
||||
public void listen() throws IOException {
|
||||
public void listen()
|
||||
throws IOException {
|
||||
log.info("Listening on {}", ss.getLocalSocketAddress());
|
||||
Socket sock;
|
||||
while (true) {
|
||||
|
||||
@@ -56,10 +56,12 @@ import java.util.Map;
|
||||
* @see Shell
|
||||
* @see Subsystem
|
||||
*/
|
||||
public interface Session extends Channel {
|
||||
public interface Session
|
||||
extends Channel {
|
||||
|
||||
/** Command API. */
|
||||
interface Command extends Channel {
|
||||
interface Command
|
||||
extends Channel {
|
||||
|
||||
/**
|
||||
* Read from the command's {@code stderr} stream into a string (blocking).
|
||||
@@ -68,7 +70,8 @@ public interface Session extends Channel {
|
||||
*
|
||||
* @throws IOException if error reading from the stream
|
||||
*/
|
||||
String getErrorAsString() throws IOException;
|
||||
String getErrorAsString()
|
||||
throws IOException;
|
||||
|
||||
/** Returns the command's {@code stderr} stream. */
|
||||
InputStream getErrorStream();
|
||||
@@ -105,7 +108,8 @@ public interface Session extends Channel {
|
||||
*
|
||||
* @throws IOException if error reading from the stream
|
||||
*/
|
||||
String getOutputAsString() throws IOException;
|
||||
String getOutputAsString()
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Send a signal to the remote command.
|
||||
@@ -114,12 +118,14 @@ public interface Session extends Channel {
|
||||
*
|
||||
* @throws TransportException if error sending the signal
|
||||
*/
|
||||
void signal(Signal signal) throws TransportException;
|
||||
void signal(Signal signal)
|
||||
throws TransportException;
|
||||
|
||||
}
|
||||
|
||||
/** Shell API. */
|
||||
interface Shell extends Channel {
|
||||
interface Shell
|
||||
extends Channel {
|
||||
|
||||
/**
|
||||
* Whether the client can do local flow control using {@code control-S} and {@code control-Q}.
|
||||
@@ -139,7 +145,8 @@ public interface Session extends Channel {
|
||||
*
|
||||
* @throws TransportException
|
||||
*/
|
||||
void changeWindowDimensions(int cols, int rows, int width, int height) throws TransportException;
|
||||
void changeWindowDimensions(int cols, int rows, int width, int height)
|
||||
throws TransportException;
|
||||
|
||||
/** Returns the shell's {@code stderr} stream. */
|
||||
InputStream getErrorStream();
|
||||
@@ -151,12 +158,14 @@ public interface Session extends Channel {
|
||||
*
|
||||
* @throws TransportException if error sending the signal
|
||||
*/
|
||||
void signal(Signal signal) throws TransportException;
|
||||
void signal(Signal signal)
|
||||
throws TransportException;
|
||||
|
||||
}
|
||||
|
||||
/** Subsystem API. */
|
||||
interface Subsystem extends Channel {
|
||||
interface Subsystem
|
||||
extends Channel {
|
||||
Integer getExitStatus();
|
||||
}
|
||||
|
||||
@@ -167,7 +176,8 @@ public interface Session extends Channel {
|
||||
*
|
||||
* @throws TransportException
|
||||
*/
|
||||
void allocateDefaultPTY() throws ConnectionException, TransportException;
|
||||
void allocateDefaultPTY()
|
||||
throws ConnectionException, TransportException;
|
||||
|
||||
/**
|
||||
* Allocate a psuedo-terminal for this session.
|
||||
@@ -197,7 +207,8 @@ public interface Session extends Channel {
|
||||
* @throws ConnectionException if the request to execute the command failed
|
||||
* @throws TransportException if there is an error sending the request
|
||||
*/
|
||||
Command exec(String command) throws ConnectionException, TransportException;
|
||||
Command exec(String command)
|
||||
throws ConnectionException, TransportException;
|
||||
|
||||
/**
|
||||
* Request X11 forwarding.
|
||||
@@ -209,7 +220,8 @@ public interface Session extends Channel {
|
||||
* @throws ConnectionException if the request failed
|
||||
* @throws TransportException if there was an error sending the request
|
||||
*/
|
||||
void reqX11Forwarding(String authProto, String authCookie, int screen) throws ConnectionException,
|
||||
void reqX11Forwarding(String authProto, String authCookie, int screen)
|
||||
throws ConnectionException,
|
||||
TransportException;
|
||||
|
||||
/**
|
||||
@@ -221,7 +233,8 @@ public interface Session extends Channel {
|
||||
* @throws ConnectionException if the request failed
|
||||
* @throws TransportException if there was an error sending the request
|
||||
*/
|
||||
void setEnvVar(String name, String value) throws ConnectionException, TransportException;
|
||||
void setEnvVar(String name, String value)
|
||||
throws ConnectionException, TransportException;
|
||||
|
||||
/**
|
||||
* Request a shell.
|
||||
@@ -231,7 +244,8 @@ public interface Session extends Channel {
|
||||
* @throws ConnectionException if the request failed
|
||||
* @throws TransportException if there was an error sending the request
|
||||
*/
|
||||
Shell startShell() throws ConnectionException, TransportException;
|
||||
Shell startShell()
|
||||
throws ConnectionException, TransportException;
|
||||
|
||||
/**
|
||||
* Request a subsystem.
|
||||
@@ -243,6 +257,7 @@ public interface Session extends Channel {
|
||||
* @throws ConnectionException if the request failed
|
||||
* @throws TransportException if there was an error sending the request
|
||||
*/
|
||||
Subsystem startSubsystem(String name) throws ConnectionException, TransportException;
|
||||
Subsystem startSubsystem(String name)
|
||||
throws ConnectionException, TransportException;
|
||||
|
||||
}
|
||||
|
||||
@@ -52,8 +52,9 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** {@link Session} implementation. */
|
||||
public class
|
||||
SessionChannel extends AbstractDirectChannel implements Session, Session.Command, Session.Shell,
|
||||
Session.Subsystem {
|
||||
SessionChannel
|
||||
extends AbstractDirectChannel
|
||||
implements Session, Session.Command, Session.Shell, Session.Subsystem {
|
||||
|
||||
private Integer exitStatus;
|
||||
|
||||
@@ -69,7 +70,8 @@ public class
|
||||
super("session", conn);
|
||||
}
|
||||
|
||||
public void allocateDefaultPTY() throws ConnectionException, TransportException {
|
||||
public void allocateDefaultPTY()
|
||||
throws ConnectionException, TransportException {
|
||||
// TODO FIXME (maybe?): These modes were originally copied from what SSHD was doing;
|
||||
// and then the echo modes were set to 0 to better serve the PTY example.
|
||||
// Not sure what default PTY modes should be.
|
||||
@@ -103,7 +105,8 @@ public class
|
||||
return canDoFlowControl;
|
||||
}
|
||||
|
||||
public void changeWindowDimensions(int cols, int rows, int width, int height) throws TransportException {
|
||||
public void changeWindowDimensions(int cols, int rows, int width, int height)
|
||||
throws TransportException {
|
||||
sendChannelRequest(
|
||||
"pty-req",
|
||||
false,
|
||||
@@ -115,13 +118,16 @@ public class
|
||||
);
|
||||
}
|
||||
|
||||
public Command exec(String command) throws ConnectionException, TransportException {
|
||||
public Command exec(String command)
|
||||
throws ConnectionException, TransportException {
|
||||
log.info("Will request to exec `{}`", command);
|
||||
sendChannelRequest("exec", true, new Buffer.PlainBuffer().putString(command)).await(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
sendChannelRequest("exec", true, new Buffer.PlainBuffer().putString(command))
|
||||
.await(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getErrorAsString() throws IOException {
|
||||
public String getErrorAsString()
|
||||
throws IOException {
|
||||
return StreamCopier.copyStreamToString(err);
|
||||
}
|
||||
|
||||
@@ -141,12 +147,14 @@ public class
|
||||
return exitStatus;
|
||||
}
|
||||
|
||||
public String getOutputAsString() throws IOException {
|
||||
public String getOutputAsString()
|
||||
throws IOException {
|
||||
return StreamCopier.copyStreamToString(getInputStream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(String req, SSHPacket buf) throws ConnectionException, TransportException {
|
||||
public void handleRequest(String req, SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
if ("xon-xoff".equals(req))
|
||||
canDoFlowControl = buf.readBoolean();
|
||||
else if ("exit-status".equals(req))
|
||||
@@ -160,7 +168,8 @@ public class
|
||||
super.handleRequest(req, buf);
|
||||
}
|
||||
|
||||
public void reqX11Forwarding(String authProto, String authCookie, int screen) throws ConnectionException,
|
||||
public void reqX11Forwarding(String authProto, String authCookie, int screen)
|
||||
throws ConnectionException,
|
||||
TransportException {
|
||||
sendChannelRequest(
|
||||
"x11-req",
|
||||
@@ -173,22 +182,28 @@ public class
|
||||
).await(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void setEnvVar(String name, String value) throws ConnectionException, TransportException {
|
||||
sendChannelRequest("env", true, new Buffer.PlainBuffer().putString(name).putString(value)).await(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
public void setEnvVar(String name, String value)
|
||||
throws ConnectionException, TransportException {
|
||||
sendChannelRequest("env", true, new Buffer.PlainBuffer().putString(name).putString(value))
|
||||
.await(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void signal(Signal sig) throws TransportException {
|
||||
public void signal(Signal sig)
|
||||
throws TransportException {
|
||||
sendChannelRequest("signal", false, new Buffer.PlainBuffer().putString(sig.toString()));
|
||||
}
|
||||
|
||||
public Shell startShell() throws ConnectionException, TransportException {
|
||||
public Shell startShell()
|
||||
throws ConnectionException, TransportException {
|
||||
sendChannelRequest("shell", true, null).await(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Subsystem startSubsystem(String name) throws ConnectionException, TransportException {
|
||||
public Subsystem startSubsystem(String name)
|
||||
throws ConnectionException, TransportException {
|
||||
log.info("Will request `{}` subsystem", name);
|
||||
sendChannelRequest("subsystem", true, new Buffer.PlainBuffer().putString(name)).await(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
sendChannelRequest("subsystem", true, new Buffer.PlainBuffer().putString(name))
|
||||
.await(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -209,7 +224,8 @@ public class
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void gotExtendedData(int dataTypeCode, SSHPacket buf) throws ConnectionException, TransportException {
|
||||
protected void gotExtendedData(int dataTypeCode, SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
if (dataTypeCode == 1)
|
||||
receiveInto(err, buf);
|
||||
else
|
||||
|
||||
@@ -30,6 +30,7 @@ public interface SessionFactory {
|
||||
* @throws SSHException
|
||||
* @see {@link Session}
|
||||
*/
|
||||
Session startSession() throws SSHException;
|
||||
Session startSession()
|
||||
throws SSHException;
|
||||
|
||||
}
|
||||
|
||||
@@ -39,8 +39,21 @@ package net.schmizz.sshj.connection.channel.direct;
|
||||
/** Various signals that may be sent or received. The signals are from POSIX and simply miss the {@code "SIG_"} prefix. */
|
||||
public enum Signal {
|
||||
|
||||
ABRT("ABRT"), ALRM("ALRM"), FPE("FPE"), HUP("HUP"), ILL("ILL"), INT("INT"), KILL("KILL"), PIPE("PIPE"), QUIT(
|
||||
"QUIT"), SEGV("SEGV"), TERM("TERM"), USR1("USR1"), USR2("USR2"), UNKNOWN("UNKNOWN");
|
||||
ABRT("ABRT"),
|
||||
ALRM("ALRM"),
|
||||
FPE("FPE"),
|
||||
HUP("HUP"),
|
||||
ILL("ILL"),
|
||||
INT("INT"),
|
||||
KILL("KILL"),
|
||||
PIPE("PIPE"),
|
||||
QUIT(
|
||||
"QUIT"),
|
||||
SEGV("SEGV"),
|
||||
TERM("TERM"),
|
||||
USR1("USR1"),
|
||||
USR2("USR2"),
|
||||
UNKNOWN("UNKNOWN");
|
||||
|
||||
/**
|
||||
* Create from the string representation used when the signal is received as part of an SSH packet.
|
||||
|
||||
@@ -43,7 +43,9 @@ import net.schmizz.sshj.connection.channel.OpenFailException.Reason;
|
||||
import net.schmizz.sshj.transport.TransportException;
|
||||
|
||||
/** Base class for forwarded channels whose open is initiated by the server. */
|
||||
public abstract class AbstractForwardedChannel extends AbstractChannel implements Channel.Forwarded {
|
||||
public abstract class AbstractForwardedChannel
|
||||
extends AbstractChannel
|
||||
implements Channel.Forwarded {
|
||||
|
||||
protected final String origIP;
|
||||
protected final int origPort;
|
||||
@@ -60,7 +62,8 @@ public abstract class AbstractForwardedChannel extends AbstractChannel implement
|
||||
init(recipient, remoteWinSize, remoteMaxPacketSize);
|
||||
}
|
||||
|
||||
public void confirm() throws TransportException {
|
||||
public void confirm()
|
||||
throws TransportException {
|
||||
log.info("Confirming `{}` channel #{}", getType(), getID());
|
||||
// Must ensure channel is attached before confirming, data could start coming in immediately!
|
||||
conn.attach(this);
|
||||
@@ -71,7 +74,8 @@ public abstract class AbstractForwardedChannel extends AbstractChannel implement
|
||||
open.set();
|
||||
}
|
||||
|
||||
public void reject(Reason reason, String message) throws TransportException {
|
||||
public void reject(Reason reason, String message)
|
||||
throws TransportException {
|
||||
log.info("Rejecting `{}` channel: {}", getType(), message);
|
||||
conn.sendOpenFailure(getRecipient(), reason, message);
|
||||
}
|
||||
|
||||
@@ -12,26 +12,6 @@
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file may incorporate work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package net.schmizz.sshj.connection.channel.forwarded;
|
||||
@@ -47,7 +27,8 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
|
||||
/** Base class for {@link net.schmizz.sshj.connection.channel.forwarded.ForwardedChannelOpener}'s. */
|
||||
public abstract class AbstractForwardedChannelOpener implements ForwardedChannelOpener {
|
||||
public abstract class AbstractForwardedChannelOpener
|
||||
implements ForwardedChannelOpener {
|
||||
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
|
||||
@@ -24,13 +24,14 @@ public interface ConnectListener {
|
||||
|
||||
/**
|
||||
* Notify this listener of a new forwarded channel. An implementation should firstly {@link
|
||||
* net.schmizz.sshj.connection.channel.Channel.Forwarded#confirm() confirm} or {@link
|
||||
* net.schmizz.sshj.connection.channel.Channel.Forwarded#reject() reject} that channel.
|
||||
* Channel.Forwarded#confirm() confirm} or {@link Channel.Forwarded#reject(net.schmizz.sshj.connection.channel.OpenFailException.Reason,
|
||||
* String)} reject} that channel.
|
||||
*
|
||||
* @param chan the {@link net.schmizz.sshj.connection.channel.Channel.Forwarded forwarded channel}
|
||||
*
|
||||
* @throws java.io.IOException
|
||||
* @throws IOException
|
||||
*/
|
||||
void gotConnect(Channel.Forwarded chan) throws IOException;
|
||||
void gotConnect(Channel.Forwarded chan)
|
||||
throws IOException;
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public interface ForwardedChannelOpener {
|
||||
* @param buf {@link net.schmizz.sshj.common.SSHPacket} containg the request except for the message identifier and
|
||||
* channel type field
|
||||
*/
|
||||
void handleOpen(SSHPacket buf) throws ConnectionException, TransportException;
|
||||
void handleOpen(SSHPacket buf)
|
||||
throws ConnectionException, TransportException;
|
||||
|
||||
}
|
||||
|
||||
@@ -12,26 +12,6 @@
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file may incorporate work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.connection.channel.forwarded;
|
||||
|
||||
@@ -48,7 +28,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** Handles remote port forwarding. */
|
||||
public class RemotePortForwarder extends AbstractForwardedChannelOpener {
|
||||
public class RemotePortForwarder
|
||||
extends AbstractForwardedChannelOpener {
|
||||
|
||||
/**
|
||||
* Represents a particular forwarding. From RFC 4254, s. 7.1
|
||||
@@ -139,14 +120,16 @@ public class RemotePortForwarder extends AbstractForwardedChannelOpener {
|
||||
}
|
||||
|
||||
/** A {@code forwarded-tcpip} channel. */
|
||||
public static class ForwardedTCPIPChannel extends AbstractForwardedChannel {
|
||||
public static class ForwardedTCPIPChannel
|
||||
extends AbstractForwardedChannel {
|
||||
|
||||
public static final String TYPE = "forwarded-tcpip";
|
||||
|
||||
private final Forward fwd;
|
||||
|
||||
public ForwardedTCPIPChannel(Connection conn, int recipient, int remoteWinSize, int remoteMaxPacketSize,
|
||||
Forward fwd, String origIP, int origPort) throws TransportException {
|
||||
Forward fwd, String origIP, int origPort)
|
||||
throws TransportException {
|
||||
super(TYPE, conn, recipient, remoteWinSize, remoteMaxPacketSize, origIP, origPort);
|
||||
this.fwd = fwd;
|
||||
}
|
||||
@@ -182,7 +165,8 @@ public class RemotePortForwarder extends AbstractForwardedChannelOpener {
|
||||
* @throws ConnectionException if there is an error requesting the forwarding
|
||||
* @throws TransportException
|
||||
*/
|
||||
public Forward bind(Forward forward, ConnectListener listener) throws ConnectionException, TransportException {
|
||||
public Forward bind(Forward forward, ConnectListener listener)
|
||||
throws ConnectionException, TransportException {
|
||||
SSHPacket reply = req(PF_REQ, forward);
|
||||
if (forward.port == 0)
|
||||
forward.port = reply.readInt();
|
||||
@@ -199,7 +183,8 @@ public class RemotePortForwarder extends AbstractForwardedChannelOpener {
|
||||
* @throws ConnectionException if there is an error with the cancellation request
|
||||
* @throws TransportException
|
||||
*/
|
||||
public void cancel(Forward forward) throws ConnectionException, TransportException {
|
||||
public void cancel(Forward forward)
|
||||
throws ConnectionException, TransportException {
|
||||
try {
|
||||
req(PF_CANCEL, forward);
|
||||
} finally {
|
||||
@@ -207,8 +192,10 @@ public class RemotePortForwarder extends AbstractForwardedChannelOpener {
|
||||
}
|
||||
}
|
||||
|
||||
protected SSHPacket req(String reqName, Forward forward) throws ConnectionException, TransportException {
|
||||
final byte[] specifics = new Buffer.PlainBuffer().putString(forward.address).putInt(forward.port).getCompactData();
|
||||
protected SSHPacket req(String reqName, Forward forward)
|
||||
throws ConnectionException, TransportException {
|
||||
final byte[] specifics = new Buffer.PlainBuffer().putString(forward.address).putInt(forward.port)
|
||||
.getCompactData();
|
||||
return conn.sendGlobalRequest(reqName, true, specifics)
|
||||
.get(conn.getTimeout(), TimeUnit.SECONDS);
|
||||
}
|
||||
@@ -222,7 +209,8 @@ public class RemotePortForwarder extends AbstractForwardedChannelOpener {
|
||||
* Internal API. Creates a {@link ForwardedTCPIPChannel} from the {@code CHANNEL_OPEN} request and calls associated
|
||||
* {@code ConnectListener} for that forward in a separate thread.
|
||||
*/
|
||||
public void handleOpen(SSHPacket buf) throws ConnectionException, TransportException {
|
||||
public void handleOpen(SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
final ForwardedTCPIPChannel chan = new ForwardedTCPIPChannel(conn, buf.readInt(), buf.readInt(), buf.readInt(),
|
||||
new Forward(buf.readString(), buf.readInt()),
|
||||
buf.readString(), buf.readInt());
|
||||
|
||||
@@ -30,7 +30,8 @@ import java.net.SocketAddress;
|
||||
* A {@link net.schmizz.sshj.connection.channel.forwarded.ConnectListener} that forwards what is received over the
|
||||
* channel to a socket and vice-versa.
|
||||
*/
|
||||
public class SocketForwardingConnectListener implements ConnectListener {
|
||||
public class SocketForwardingConnectListener
|
||||
implements ConnectListener {
|
||||
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@@ -42,7 +43,8 @@ public class SocketForwardingConnectListener implements ConnectListener {
|
||||
}
|
||||
|
||||
/** On connect, confirm the channel and start forwarding. */
|
||||
public void gotConnect(Channel.Forwarded chan) throws IOException {
|
||||
public void gotConnect(Channel.Forwarded chan)
|
||||
throws IOException {
|
||||
log.info("New connection from " + chan.getOriginatorIP() + ":" + chan.getOriginatorPort());
|
||||
|
||||
final Socket sock = new Socket();
|
||||
@@ -55,7 +57,8 @@ public class SocketForwardingConnectListener implements ConnectListener {
|
||||
chan.confirm();
|
||||
|
||||
final ErrorCallback closer = StreamCopier.closeOnErrorCallback(chan, new Closeable() {
|
||||
public void close() throws IOException {
|
||||
public void close()
|
||||
throws IOException {
|
||||
sock.close();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -24,10 +24,12 @@ import net.schmizz.sshj.transport.TransportException;
|
||||
* Handles forwarded {@code x11} channels. The actual request to forward X11 should be made from the specific {@link
|
||||
* net.schmizz.sshj.connection.channel.direct.Session}.
|
||||
*/
|
||||
public class X11Forwarder extends AbstractForwardedChannelOpener {
|
||||
public class X11Forwarder
|
||||
extends AbstractForwardedChannelOpener {
|
||||
|
||||
/** An {@code x11} forwarded channel. */
|
||||
public static class X11Channel extends AbstractForwardedChannel {
|
||||
public static class X11Channel
|
||||
extends AbstractForwardedChannel {
|
||||
|
||||
public static final String TYPE = "x11";
|
||||
|
||||
@@ -52,7 +54,8 @@ public class X11Forwarder extends AbstractForwardedChannelOpener {
|
||||
}
|
||||
|
||||
/** Internal API */
|
||||
public void handleOpen(SSHPacket buf) throws ConnectionException, TransportException {
|
||||
public void handleOpen(SSHPacket buf)
|
||||
throws ConnectionException, TransportException {
|
||||
callListener(listener, new X11Channel(conn, buf.readInt(), buf.readInt(), buf.readInt(), buf.readString(), buf
|
||||
.readInt()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user