ch.ethz.ssh2.channel.RemoteX11AcceptThread Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ganymed-ssh2 Show documentation
Show all versions of ganymed-ssh2 Show documentation
Ganymed SSH-2: Java based SSH-2 Protocol Implementation
The newest version!
/*
* Copyright (c) 2006-2011 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.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import ch.ethz.ssh2.log.Logger;
import ch.ethz.ssh2.util.StringEncoder;
/**
* RemoteX11AcceptThread.
*
* @author Christian Plattner
* @version $Id: RemoteX11AcceptThread.java 41 2011-06-02 10:36:41Z [email protected] $
*/
public class RemoteX11AcceptThread extends Thread
{
private static final Logger log = Logger.getLogger(RemoteX11AcceptThread.class);
Channel c;
String remoteOriginatorAddress;
int remoteOriginatorPort;
Socket s;
public RemoteX11AcceptThread(Channel c, String remoteOriginatorAddress, int remoteOriginatorPort)
{
this.c = c;
this.remoteOriginatorAddress = remoteOriginatorAddress;
this.remoteOriginatorPort = remoteOriginatorPort;
}
@Override
public void run()
{
try
{
/* Send Open Confirmation */
c.cm.sendOpenConfirmation(c);
/* Read startup packet from client */
OutputStream remote_os = c.getStdinStream();
InputStream remote_is = c.getStdoutStream();
/* The following code is based on the protocol description given in:
* Scheifler/Gettys,
* X Windows System: Core and Extension Protocols:
* X Version 11, Releases 6 and 6.1 ISBN 1-55558-148-X
* (from the ETH library - after being here for almost ten
* years one of the few books I borrowed... sad but true =)
*/
/*
* Client startup:
*
* 1 0X42 MSB first/0x6c lSB first - byteorder
* 1 - unused
* 2 card16 - protocol-major-version
* 2 card16 - protocol-minor-version
* 2 n - lenght of authorization-protocol-name
* 2 d - lenght of authorization-protocol-data
* 2 - unused
* string8 - authorization-protocol-name
* p - unused, p=pad(n)
* string8 - authorization-protocol-data
* q - unused, q=pad(d)
*
* pad(X) = (4 - (X mod 4)) mod 4
*
* Server response:
*
* 1 (0 failed, 2 authenticate, 1 success)
* ...
*
*/
/* Later on we will simply forward the first 6 header bytes to the "real" X11 server */
byte[] header = new byte[6];
if (remote_is.read(header) != 6)
throw new IOException("Unexpected EOF on X11 startup!");
if ((header[0] != 0x42) && (header[0] != 0x6c)) // 0x42 MSB first, 0x6C LSB first
throw new IOException("Unknown endian format in X11 message!");
/* Yes, I came up with this myself - shall I file an application for a patent? =) */
int idxMSB = (header[0] == 0x42) ? 0 : 1;
/* Read authorization data header */
byte[] auth_buff = new byte[6];
if (remote_is.read(auth_buff) != 6)
throw new IOException("Unexpected EOF on X11 startup!");
int authProtocolNameLength = ((auth_buff[idxMSB] & 0xff) << 8) | (auth_buff[1 - idxMSB] & 0xff);
int authProtocolDataLength = ((auth_buff[2 + idxMSB] & 0xff) << 8) | (auth_buff[3 - idxMSB] & 0xff);
if ((authProtocolNameLength > 256) || (authProtocolDataLength > 256))
throw new IOException("Buggy X11 authorization data");
int authProtocolNamePadding = ((4 - (authProtocolNameLength % 4)) % 4);
int authProtocolDataPadding = ((4 - (authProtocolDataLength % 4)) % 4);
byte[] authProtocolName = new byte[authProtocolNameLength];
byte[] authProtocolData = new byte[authProtocolDataLength];
byte[] paddingBuffer = new byte[4];
if (remote_is.read(authProtocolName) != authProtocolNameLength)
throw new IOException("Unexpected EOF on X11 startup! (authProtocolName)");
if (remote_is.read(paddingBuffer, 0, authProtocolNamePadding) != authProtocolNamePadding)
throw new IOException("Unexpected EOF on X11 startup! (authProtocolNamePadding)");
if (remote_is.read(authProtocolData) != authProtocolDataLength)
throw new IOException("Unexpected EOF on X11 startup! (authProtocolData)");
if (remote_is.read(paddingBuffer, 0, authProtocolDataPadding) != authProtocolDataPadding)
throw new IOException("Unexpected EOF on X11 startup! (authProtocolDataPadding)");
if ("MIT-MAGIC-COOKIE-1".equals(StringEncoder.GetString(authProtocolName)) == false)
throw new IOException("Unknown X11 authorization protocol!");
if (authProtocolDataLength != 16)
throw new IOException("Wrong data length for X11 authorization data!");
StringBuilder tmp = new StringBuilder(32);
for (int i = 0; i < authProtocolData.length; i++)
{
String digit2 = Integer.toHexString(authProtocolData[i] & 0xff);
tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2);
}
String hexEncodedFakeCookie = tmp.toString();
/* Order is very important here - it may be that a certain x11 forwarding
* gets disabled right in the moment when we check and register our connection
* */
synchronized (c)
{
/* Please read the comment in Channel.java */
c.hexX11FakeCookie = hexEncodedFakeCookie;
}
/* Now check our fake cookie directory to see if we produced this cookie */
X11ServerData sd = c.cm.checkX11Cookie(hexEncodedFakeCookie);
if (sd == null)
throw new IOException("Invalid X11 cookie received.");
/* If the session which corresponds to this cookie is closed then we will
* detect this: the session's close code will close all channels
* with the session's assigned x11 fake cookie.
*/
s = new Socket(sd.hostname, sd.port);
OutputStream x11_os = s.getOutputStream();
InputStream x11_is = s.getInputStream();
/* Now we are sending the startup packet to the real X11 server */
x11_os.write(header);
if (sd.x11_magic_cookie == null)
{
byte[] emptyAuthData = new byte[6];
/* empty auth data, hopefully you are connecting to localhost =) */
x11_os.write(emptyAuthData);
}
else
{
if (sd.x11_magic_cookie.length != 16)
throw new IOException("The real X11 cookie has an invalid length!");
/* send X11 cookie specified by client */
x11_os.write(auth_buff);
x11_os.write(authProtocolName); /* re-use */
x11_os.write(paddingBuffer, 0, authProtocolNamePadding);
x11_os.write(sd.x11_magic_cookie);
x11_os.write(paddingBuffer, 0, authProtocolDataPadding);
}
x11_os.flush();
/* Start forwarding traffic */
StreamForwarder r2l = new StreamForwarder(c, null, null, remote_is, x11_os, "RemoteToX11");
StreamForwarder l2r = new StreamForwarder(c, null, null, x11_is, remote_os, "X11ToRemote");
/* No need to start two threads, one can be executed in the current thread */
r2l.setDaemon(true);
r2l.start();
l2r.run();
while (r2l.isAlive())
{
try
{
r2l.join();
}
catch (InterruptedException ignored)
{
}
}
/* If the channel is already closed, then this is a no-op */
c.cm.closeChannel(c, "EOF on both X11 streams reached.", true);
s.close();
}
catch (IOException e)
{
log.warning("IOException in X11 proxy code: " + e.getMessage());
try
{
c.cm.closeChannel(c, "IOException in X11 proxy code (" + e.getMessage() + ")", true);
}
catch (IOException ignored)
{
}
try
{
if (s != null)
s.close();
}
catch (IOException ignored)
{
}
}
}
}