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