Home | History | Annotate | Download | only in channel
      1 /*
      2  * Copyright (c) 2006-2011 Christian Plattner. All rights reserved.
      3  * Please refer to the LICENSE.txt for licensing details.
      4  */
      5 package ch.ethz.ssh2.channel;
      6 
      7 import java.io.IOException;
      8 import java.io.InputStream;
      9 import java.io.OutputStream;
     10 import java.net.Socket;
     11 
     12 import ch.ethz.ssh2.log.Logger;
     13 import ch.ethz.ssh2.util.StringEncoder;
     14 
     15 /**
     16  * RemoteX11AcceptThread.
     17  *
     18  * @author Christian Plattner
     19  * @version $Id: RemoteX11AcceptThread.java 41 2011-06-02 10:36:41Z dkocher (at) sudo.ch $
     20  */
     21 public class RemoteX11AcceptThread extends Thread
     22 {
     23 	private static final Logger log = Logger.getLogger(RemoteX11AcceptThread.class);
     24 
     25 	Channel c;
     26 
     27 	String remoteOriginatorAddress;
     28 	int remoteOriginatorPort;
     29 
     30 	Socket s;
     31 
     32 	public RemoteX11AcceptThread(Channel c, String remoteOriginatorAddress, int remoteOriginatorPort)
     33 	{
     34 		this.c = c;
     35 		this.remoteOriginatorAddress = remoteOriginatorAddress;
     36 		this.remoteOriginatorPort = remoteOriginatorPort;
     37 	}
     38 
     39 	@Override
     40 	public void run()
     41 	{
     42 		try
     43 		{
     44 			/* Send Open Confirmation */
     45 
     46 			c.cm.sendOpenConfirmation(c);
     47 
     48 			/* Read startup packet from client */
     49 
     50 			OutputStream remote_os = c.getStdinStream();
     51 			InputStream remote_is = c.getStdoutStream();
     52 
     53 			/* The following code is based on the protocol description given in:
     54 			 * Scheifler/Gettys,
     55 			 * X Windows System: Core and Extension Protocols:
     56 			 * X Version 11, Releases 6 and 6.1 ISBN 1-55558-148-X
     57 			 * (from the ETH library - after being here for almost ten
     58 			 * years one of the few books I borrowed... sad but true =)
     59 			 */
     60 
     61 			/*
     62 			 * Client startup:
     63 			 *
     64 			 * 1 0X42 MSB first/0x6c lSB first - byteorder
     65 			 * 1 - unused
     66 			 * 2 card16 - protocol-major-version
     67 			 * 2 card16 - protocol-minor-version
     68 			 * 2 n - lenght of authorization-protocol-name
     69 			 * 2 d - lenght of authorization-protocol-data
     70 			 * 2 - unused
     71 			 * string8 - authorization-protocol-name
     72 			 * p - unused, p=pad(n)
     73 			 * string8 - authorization-protocol-data
     74 			 * q - unused, q=pad(d)
     75 			 *
     76 			 * pad(X) = (4 - (X mod 4)) mod 4
     77 			 *
     78 			 * Server response:
     79 			 *
     80 			 * 1 (0 failed, 2 authenticate, 1 success)
     81 			 * ...
     82 			 *
     83 			 */
     84 
     85 			/* Later on we will simply forward the first 6 header bytes to the "real" X11 server */
     86 
     87 			byte[] header = new byte[6];
     88 
     89 			if (remote_is.read(header) != 6)
     90 				throw new IOException("Unexpected EOF on X11 startup!");
     91 
     92 			if ((header[0] != 0x42) && (header[0] != 0x6c)) // 0x42 MSB first, 0x6C LSB first
     93 				throw new IOException("Unknown endian format in X11 message!");
     94 
     95 			/* Yes, I came up with this myself - shall I file an application for a patent? =) */
     96 
     97 			int idxMSB = (header[0] == 0x42) ? 0 : 1;
     98 
     99 			/* Read authorization data header */
    100 
    101 			byte[] auth_buff = new byte[6];
    102 
    103 			if (remote_is.read(auth_buff) != 6)
    104 				throw new IOException("Unexpected EOF on X11 startup!");
    105 
    106 			int authProtocolNameLength = ((auth_buff[idxMSB] & 0xff) << 8) | (auth_buff[1 - idxMSB] & 0xff);
    107 			int authProtocolDataLength = ((auth_buff[2 + idxMSB] & 0xff) << 8) | (auth_buff[3 - idxMSB] & 0xff);
    108 
    109 			if ((authProtocolNameLength > 256) || (authProtocolDataLength > 256))
    110 				throw new IOException("Buggy X11 authorization data");
    111 
    112 			int authProtocolNamePadding = ((4 - (authProtocolNameLength % 4)) % 4);
    113 			int authProtocolDataPadding = ((4 - (authProtocolDataLength % 4)) % 4);
    114 
    115 			byte[] authProtocolName = new byte[authProtocolNameLength];
    116 			byte[] authProtocolData = new byte[authProtocolDataLength];
    117 
    118 			byte[] paddingBuffer = new byte[4];
    119 
    120 			if (remote_is.read(authProtocolName) != authProtocolNameLength)
    121 				throw new IOException("Unexpected EOF on X11 startup! (authProtocolName)");
    122 
    123 			if (remote_is.read(paddingBuffer, 0, authProtocolNamePadding) != authProtocolNamePadding)
    124 				throw new IOException("Unexpected EOF on X11 startup! (authProtocolNamePadding)");
    125 
    126 			if (remote_is.read(authProtocolData) != authProtocolDataLength)
    127 				throw new IOException("Unexpected EOF on X11 startup! (authProtocolData)");
    128 
    129 			if (remote_is.read(paddingBuffer, 0, authProtocolDataPadding) != authProtocolDataPadding)
    130 				throw new IOException("Unexpected EOF on X11 startup! (authProtocolDataPadding)");
    131 
    132 			if ("MIT-MAGIC-COOKIE-1".equals(StringEncoder.GetString(authProtocolName)) == false)
    133 				throw new IOException("Unknown X11 authorization protocol!");
    134 
    135 			if (authProtocolDataLength != 16)
    136 				throw new IOException("Wrong data length for X11 authorization data!");
    137 
    138 			StringBuilder tmp = new StringBuilder(32);
    139 			for (int i = 0; i < authProtocolData.length; i++)
    140 			{
    141 				String digit2 = Integer.toHexString(authProtocolData[i] & 0xff);
    142 				tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2);
    143 			}
    144 			String hexEncodedFakeCookie = tmp.toString();
    145 
    146 			/* Order is very important here - it may be that a certain x11 forwarding
    147 			 * gets disabled right in the moment when we check and register our connection
    148 			 * */
    149 
    150 			synchronized (c)
    151 			{
    152 				/* Please read the comment in Channel.java */
    153 				c.hexX11FakeCookie = hexEncodedFakeCookie;
    154 			}
    155 
    156 			/* Now check our fake cookie directory to see if we produced this cookie */
    157 
    158 			X11ServerData sd = c.cm.checkX11Cookie(hexEncodedFakeCookie);
    159 
    160 			if (sd == null)
    161 				throw new IOException("Invalid X11 cookie received.");
    162 
    163 			/* If the session which corresponds to this cookie is closed then we will
    164 			 * detect this: the session's close code will close all channels
    165 			 * with the session's assigned x11 fake cookie.
    166 			 */
    167 
    168 			s = new Socket(sd.hostname, sd.port);
    169 
    170 			OutputStream x11_os = s.getOutputStream();
    171 			InputStream x11_is = s.getInputStream();
    172 
    173 			/* Now we are sending the startup packet to the real X11 server */
    174 
    175 			x11_os.write(header);
    176 
    177 			if (sd.x11_magic_cookie == null)
    178 			{
    179 				byte[] emptyAuthData = new byte[6];
    180 				/* empty auth data, hopefully you are connecting to localhost =) */
    181 				x11_os.write(emptyAuthData);
    182 			}
    183 			else
    184 			{
    185 				if (sd.x11_magic_cookie.length != 16)
    186 					throw new IOException("The real X11 cookie has an invalid length!");
    187 
    188 				/* send X11 cookie specified by client */
    189 				x11_os.write(auth_buff);
    190 				x11_os.write(authProtocolName); /* re-use */
    191 				x11_os.write(paddingBuffer, 0, authProtocolNamePadding);
    192 				x11_os.write(sd.x11_magic_cookie);
    193 				x11_os.write(paddingBuffer, 0, authProtocolDataPadding);
    194 			}
    195 
    196 			x11_os.flush();
    197 
    198 			/* Start forwarding traffic */
    199 
    200 			StreamForwarder r2l = new StreamForwarder(c, null, null, remote_is, x11_os, "RemoteToX11");
    201 			StreamForwarder l2r = new StreamForwarder(c, null, null, x11_is, remote_os, "X11ToRemote");
    202 
    203 			/* No need to start two threads, one can be executed in the current thread */
    204 
    205 			r2l.setDaemon(true);
    206 			r2l.start();
    207 			l2r.run();
    208 
    209 			while (r2l.isAlive())
    210 			{
    211 				try
    212 				{
    213 					r2l.join();
    214 				}
    215 				catch (InterruptedException ignored)
    216 				{
    217 				}
    218 			}
    219 
    220 			/* If the channel is already closed, then this is a no-op */
    221 
    222 			c.cm.closeChannel(c, "EOF on both X11 streams reached.", true);
    223 			s.close();
    224 		}
    225 		catch (IOException e)
    226 		{
    227 			log.warning("IOException in X11 proxy code: " + e.getMessage());
    228 
    229 			try
    230 			{
    231 				c.cm.closeChannel(c, "IOException in X11 proxy code (" + e.getMessage() + ")", true);
    232 			}
    233 			catch (IOException ignored)
    234 			{
    235 			}
    236 			try
    237 			{
    238 				if (s != null)
    239 					s.close();
    240 			}
    241 			catch (IOException ignored)
    242 			{
    243 			}
    244 		}
    245 	}
    246 }
    247