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