View Javadoc
1   /*
2    * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  package org.eclipse.jgit.internal.transport.sshd.proxy;
44  
45  import static java.nio.charset.StandardCharsets.US_ASCII;
46  import static java.nio.charset.StandardCharsets.UTF_8;
47  import static java.text.MessageFormat.format;
48  
49  import java.io.IOException;
50  import java.net.InetAddress;
51  import java.net.InetSocketAddress;
52  
53  import org.apache.sshd.client.session.ClientSession;
54  import org.apache.sshd.common.io.IoSession;
55  import org.apache.sshd.common.util.Readable;
56  import org.apache.sshd.common.util.buffer.Buffer;
57  import org.apache.sshd.common.util.buffer.BufferUtils;
58  import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
59  import org.eclipse.jgit.annotations.NonNull;
60  import org.eclipse.jgit.internal.transport.sshd.GssApiMechanisms;
61  import org.eclipse.jgit.internal.transport.sshd.SshdText;
62  import org.eclipse.jgit.internal.transport.sshd.auth.AuthenticationHandler;
63  import org.eclipse.jgit.internal.transport.sshd.auth.BasicAuthentication;
64  import org.eclipse.jgit.internal.transport.sshd.auth.GssApiAuthentication;
65  import org.eclipse.jgit.transport.SshConstants;
66  import org.ietf.jgss.GSSContext;
67  
68  /**
69   * A {@link AbstractClientProxyConnector} to connect through a SOCKS5 proxy.
70   *
71   * @see <a href="https://tools.ietf.org/html/rfc1928">RFC 1928</a>
72   */
73  public class Socks5ClientConnector extends AbstractClientProxyConnector {
74  
75  	// private static final byte SOCKS_VERSION_4 = 4;
76  	private static final byte SOCKS_VERSION_5 = 5;
77  
78  	private static final byte SOCKS_CMD_CONNECT = 1;
79  	// private static final byte SOCKS5_CMD_BIND = 2;
80  	// private static final byte SOCKS5_CMD_UDP_ASSOCIATE = 3;
81  
82  	// Address types
83  
84  	private static final byte SOCKS_ADDRESS_IPv4 = 1;
85  
86  	private static final byte SOCKS_ADDRESS_FQDN = 3;
87  
88  	private static final byte SOCKS_ADDRESS_IPv6 = 4;
89  
90  	// Reply codes
91  
92  	private static final byte SOCKS_REPLY_SUCCESS = 0;
93  
94  	private static final byte SOCKS_REPLY_FAILURE = 1;
95  
96  	private static final byte SOCKS_REPLY_FORBIDDEN = 2;
97  
98  	private static final byte SOCKS_REPLY_NETWORK_UNREACHABLE = 3;
99  
100 	private static final byte SOCKS_REPLY_HOST_UNREACHABLE = 4;
101 
102 	private static final byte SOCKS_REPLY_CONNECTION_REFUSED = 5;
103 
104 	private static final byte SOCKS_REPLY_TTL_EXPIRED = 6;
105 
106 	private static final byte SOCKS_REPLY_COMMAND_UNSUPPORTED = 7;
107 
108 	private static final byte SOCKS_REPLY_ADDRESS_UNSUPPORTED = 8;
109 
110 	/**
111 	 * Authentication methods for SOCKS5.
112 	 *
113 	 * @see <a href=
114 	 *      "https://www.iana.org/assignments/socks-methods/socks-methods.xhtml">SOCKS
115 	 *      Methods, IANA.org</a>
116 	 */
117 	private enum SocksAuthenticationMethod {
118 
119 		ANONYMOUS(0),
120 		GSSAPI(1),
121 		PASSWORD(2),
122 		// CHALLENGE_HANDSHAKE(3),
123 		// CHALLENGE_RESPONSE(5),
124 		// SSL(6),
125 		// NDS(7),
126 		// MULTI_AUTH(8),
127 		// JSON(9),
128 		NONE_ACCEPTABLE(0xFF);
129 
130 		private byte value;
131 
132 		SocksAuthenticationMethod(int value) {
133 			this.value = (byte) value;
134 		}
135 
136 		public byte getValue() {
137 			return value;
138 		}
139 	}
140 
141 	private enum ProtocolState {
142 		NONE,
143 
144 		INIT {
145 			@Override
146 			public void handleMessage(Socks5ClientConnector connector,
147 					IoSession session, Buffer data) throws Exception {
148 				connector.versionCheck(data.getByte());
149 				SocksAuthenticationMethod authMethod = connector.getAuthMethod(
150 						data.getByte());
151 				switch (authMethod) {
152 				case ANONYMOUS:
153 					connector.sendConnectInfo(session);
154 					break;
155 				case PASSWORD:
156 					connector.doPasswordAuth(session);
157 					break;
158 				case GSSAPI:
159 					connector.doGssApiAuth(session);
160 					break;
161 				default:
162 					throw new IOException(
163 							format(SshdText.get().proxyCannotAuthenticate,
164 									connector.proxyAddress));
165 				}
166 			}
167 		},
168 
169 		AUTHENTICATING {
170 			@Override
171 			public void handleMessage(Socks5ClientConnector connector,
172 					IoSession session, Buffer data) throws Exception {
173 				connector.authStep(session, data);
174 			}
175 		},
176 
177 		CONNECTING {
178 			@Override
179 			public void handleMessage(Socks5ClientConnector connector,
180 					IoSession session, Buffer data) throws Exception {
181 				// Special case: when GSS-API authentication completes, the
182 				// client moves into CONNECTING as soon as the GSS context is
183 				// established and sends the connect request. This is per RFC
184 				// 1961. But for the server, RFC 1961 says it _should_ send an
185 				// empty token even if none generated when its server side
186 				// context is established. That means we may actually get an
187 				// empty token here. That message is 4 bytes long (and has
188 				// content 0x01, 0x01, 0x00, 0x00). We simply skip this message
189 				// if we get it here. If the server for whatever reason sends
190 				// back a "GSS failed" message (it shouldn't, at this point)
191 				// it will be two bytes 0x01 0xFF, which will fail the version
192 				// check.
193 				if (data.available() != 4) {
194 					connector.versionCheck(data.getByte());
195 					connector.establishConnection(data);
196 				}
197 			}
198 		},
199 
200 		CONNECTED,
201 
202 		FAILED;
203 
204 		public void handleMessage(Socks5ClientConnector connector,
205 				@SuppressWarnings("unused") IoSession session, Buffer data)
206 				throws Exception {
207 			throw new IOException(
208 					format(SshdText.get().proxySocksUnexpectedMessage,
209 							connector.proxyAddress, this,
210 							BufferUtils.toHex(data.array())));
211 		}
212 	}
213 
214 	private ProtocolState state;
215 
216 	private AuthenticationHandler<Buffer, Buffer> authenticator;
217 
218 	private GSSContext context;
219 
220 	private byte[] authenticationProposals;
221 
222 	/**
223 	 * Creates a new {@link Socks5ClientConnector}. The connector supports
224 	 * anonymous connections as well as username-password or Kerberos5 (GSS-API)
225 	 * authentication.
226 	 *
227 	 * @param proxyAddress
228 	 *            of the proxy server we're connecting to
229 	 * @param remoteAddress
230 	 *            of the target server to connect to
231 	 */
232 	public Socks5ClientConnector(@NonNull InetSocketAddress proxyAddress,
233 			@NonNull InetSocketAddress remoteAddress) {
234 		this(proxyAddress, remoteAddress, null, null);
235 	}
236 
237 	/**
238 	 * Creates a new {@link Socks5ClientConnector}. The connector supports
239 	 * anonymous connections as well as username-password or Kerberos5 (GSS-API)
240 	 * authentication.
241 	 *
242 	 * @param proxyAddress
243 	 *            of the proxy server we're connecting to
244 	 * @param remoteAddress
245 	 *            of the target server to connect to
246 	 * @param proxyUser
247 	 *            to authenticate at the proxy with
248 	 * @param proxyPassword
249 	 *            to authenticate at the proxy with
250 	 */
251 	public Socks5ClientConnector(@NonNull InetSocketAddress proxyAddress,
252 			@NonNull InetSocketAddress remoteAddress,
253 			String proxyUser, char[] proxyPassword) {
254 		super(proxyAddress, remoteAddress, proxyUser, proxyPassword);
255 		this.state = ProtocolState.NONE;
256 	}
257 
258 	@Override
259 	public void sendClientProxyMetadata(ClientSession sshSession)
260 			throws Exception {
261 		init(sshSession);
262 		IoSession session = sshSession.getIoSession();
263 		// Send the initial request
264 		Buffer buffer = new ByteArrayBuffer(5, false);
265 		buffer.putByte(SOCKS_VERSION_5);
266 		context = getGSSContext(remoteAddress);
267 		authenticationProposals = getAuthenticationProposals();
268 		buffer.putByte((byte) authenticationProposals.length);
269 		buffer.putRawBytes(authenticationProposals);
270 		state = ProtocolState.INIT;
271 		session.writePacket(buffer).verify(getTimeout());
272 	}
273 
274 	private byte[] getAuthenticationProposals() {
275 		byte[] proposals = new byte[3];
276 		int i = 0;
277 		proposals[i++] = SocksAuthenticationMethod.ANONYMOUS.getValue();
278 		proposals[i++] = SocksAuthenticationMethod.PASSWORD.getValue();
279 		if (context != null) {
280 			proposals[i++] = SocksAuthenticationMethod.GSSAPI.getValue();
281 		}
282 		if (i == proposals.length) {
283 			return proposals;
284 		} else {
285 			byte[] result = new byte[i];
286 			System.arraycopy(proposals, 0, result, 0, i);
287 			return result;
288 		}
289 	}
290 
291 	private void sendConnectInfo(IoSession session) throws Exception {
292 		GssApiMechanisms.closeContextSilently(context);
293 
294 		byte[] rawAddress = getRawAddress(remoteAddress);
295 		byte[] remoteName = null;
296 		byte type;
297 		int length = 0;
298 		if (rawAddress == null) {
299 			remoteName = remoteAddress.getHostString().getBytes(US_ASCII);
300 			if (remoteName == null || remoteName.length == 0) {
301 				throw new IOException(
302 						format(SshdText.get().proxySocksNoRemoteHostName,
303 								remoteAddress));
304 			} else if (remoteName.length > 255) {
305 				// Should not occur; host names must not be longer than 255
306 				// US_ASCII characters. Internal error, no translation.
307 				throw new IOException(format(
308 						"Proxy host name too long for SOCKS (at most 255 characters): {0}", //$NON-NLS-1$
309 						remoteAddress.getHostString()));
310 			}
311 			type = SOCKS_ADDRESS_FQDN;
312 			length = remoteName.length + 1;
313 		} else {
314 			length = rawAddress.length;
315 			type = length == 4 ? SOCKS_ADDRESS_IPv4 : SOCKS_ADDRESS_IPv6;
316 		}
317 		Buffer buffer = new ByteArrayBuffer(4 + length + 2, false);
318 		buffer.putByte(SOCKS_VERSION_5);
319 		buffer.putByte(SOCKS_CMD_CONNECT);
320 		buffer.putByte((byte) 0); // Reserved
321 		buffer.putByte(type);
322 		if (remoteName != null) {
323 			buffer.putByte((byte) remoteName.length);
324 			buffer.putRawBytes(remoteName);
325 		} else {
326 			buffer.putRawBytes(rawAddress);
327 		}
328 		int port = remoteAddress.getPort();
329 		if (port <= 0) {
330 			port = SshConstants.SSH_DEFAULT_PORT;
331 		}
332 		buffer.putByte((byte) ((port >> 8) & 0xFF));
333 		buffer.putByte((byte) (port & 0xFF));
334 		state = ProtocolState.CONNECTING;
335 		session.writePacket(buffer).verify(getTimeout());
336 	}
337 
338 	private void doPasswordAuth(IoSession session) throws Exception {
339 		GssApiMechanisms.closeContextSilently(context);
340 		authenticator = new SocksBasicAuthentication();
341 		session.addCloseFutureListener(f -> close());
342 		startAuth(session);
343 	}
344 
345 	private void doGssApiAuth(IoSession session) throws Exception {
346 		authenticator = new SocksGssApiAuthentication();
347 		session.addCloseFutureListener(f -> close());
348 		startAuth(session);
349 	}
350 
351 	private void close() {
352 		AuthenticationHandler<?, ?> handler = authenticator;
353 		authenticator = null;
354 		if (handler != null) {
355 			handler.close();
356 		}
357 	}
358 
359 	private void startAuth(IoSession session) throws Exception {
360 		Buffer buffer = null;
361 		try {
362 			authenticator.setParams(null);
363 			authenticator.start();
364 			buffer = authenticator.getToken();
365 			state = ProtocolState.AUTHENTICATING;
366 			if (buffer == null) {
367 				// Internal error; no translation
368 				throw new IOException(
369 						"No data for proxy authentication with " //$NON-NLS-1$
370 								+ proxyAddress);
371 			}
372 			session.writePacket(buffer).verify(getTimeout());
373 		} finally {
374 			if (buffer != null) {
375 				buffer.clear(true);
376 			}
377 		}
378 	}
379 
380 	private void authStep(IoSession session, Buffer input) throws Exception {
381 		Buffer buffer = null;
382 		try {
383 			authenticator.setParams(input);
384 			authenticator.process();
385 			buffer = authenticator.getToken();
386 			if (buffer != null) {
387 				session.writePacket(buffer).verify(getTimeout());
388 			}
389 		} finally {
390 			if (buffer != null) {
391 				buffer.clear(true);
392 			}
393 		}
394 		if (authenticator.isDone()) {
395 			sendConnectInfo(session);
396 		}
397 	}
398 
399 	private void establishConnection(Buffer data) throws Exception {
400 		byte reply = data.getByte();
401 		switch (reply) {
402 		case SOCKS_REPLY_SUCCESS:
403 			state = ProtocolState.CONNECTED;
404 			setDone(true);
405 			return;
406 		case SOCKS_REPLY_FAILURE:
407 			throw new IOException(format(
408 					SshdText.get().proxySocksFailureGeneral, proxyAddress));
409 		case SOCKS_REPLY_FORBIDDEN:
410 			throw new IOException(
411 					format(SshdText.get().proxySocksFailureForbidden,
412 							proxyAddress, remoteAddress));
413 		case SOCKS_REPLY_NETWORK_UNREACHABLE:
414 			throw new IOException(
415 					format(SshdText.get().proxySocksFailureNetworkUnreachable,
416 							proxyAddress, remoteAddress));
417 		case SOCKS_REPLY_HOST_UNREACHABLE:
418 			throw new IOException(
419 					format(SshdText.get().proxySocksFailureHostUnreachable,
420 							proxyAddress, remoteAddress));
421 		case SOCKS_REPLY_CONNECTION_REFUSED:
422 			throw new IOException(
423 					format(SshdText.get().proxySocksFailureRefused,
424 							proxyAddress, remoteAddress));
425 		case SOCKS_REPLY_TTL_EXPIRED:
426 			throw new IOException(
427 					format(SshdText.get().proxySocksFailureTTL, proxyAddress));
428 		case SOCKS_REPLY_COMMAND_UNSUPPORTED:
429 			throw new IOException(
430 					format(SshdText.get().proxySocksFailureUnsupportedCommand,
431 							proxyAddress));
432 		case SOCKS_REPLY_ADDRESS_UNSUPPORTED:
433 			throw new IOException(
434 					format(SshdText.get().proxySocksFailureUnsupportedAddress,
435 							proxyAddress));
436 		default:
437 			throw new IOException(format(
438 					SshdText.get().proxySocksFailureUnspecified, proxyAddress));
439 		}
440 	}
441 
442 	@Override
443 	public void messageReceived(IoSession session, Readable buffer)
444 			throws Exception {
445 		try {
446 			// Dispatch according to protocol state
447 			ByteArrayBuffer data = new ByteArrayBuffer(buffer.available(),
448 					false);
449 			data.putBuffer(buffer);
450 			data.compact();
451 			state.handleMessage(this, session, data);
452 		} catch (Exception e) {
453 			state = ProtocolState.FAILED;
454 			if (authenticator != null) {
455 				authenticator.close();
456 				authenticator = null;
457 			}
458 			try {
459 				setDone(false);
460 			} catch (Exception inner) {
461 				e.addSuppressed(inner);
462 			}
463 			throw e;
464 		}
465 	}
466 
467 	private void versionCheck(byte version) throws Exception {
468 		if (version != SOCKS_VERSION_5) {
469 			throw new IOException(
470 					format(SshdText.get().proxySocksUnexpectedVersion,
471 							Integer.toString(version & 0xFF)));
472 		}
473 	}
474 
475 	private SocksAuthenticationMethod getAuthMethod(byte value) {
476 		if (value != SocksAuthenticationMethod.NONE_ACCEPTABLE.getValue()) {
477 			for (byte proposed : authenticationProposals) {
478 				if (proposed == value) {
479 					for (SocksAuthenticationMethod method : SocksAuthenticationMethod
480 							.values()) {
481 						if (method.getValue() == value) {
482 							return method;
483 						}
484 					}
485 					break;
486 				}
487 			}
488 		}
489 		return SocksAuthenticationMethod.NONE_ACCEPTABLE;
490 	}
491 
492 	private static byte[] getRawAddress(@NonNull InetSocketAddress address) {
493 		InetAddress ipAddress = GssApiMechanisms.resolve(address);
494 		return ipAddress == null ? null : ipAddress.getAddress();
495 	}
496 
497 	private static GSSContext getGSSContext(
498 			@NonNull InetSocketAddress address) {
499 		if (!GssApiMechanisms.getSupportedMechanisms()
500 				.contains(GssApiMechanisms.KERBEROS_5)) {
501 			return null;
502 		}
503 		return GssApiMechanisms.createContext(GssApiMechanisms.KERBEROS_5,
504 				GssApiMechanisms.getCanonicalName(address));
505 	}
506 
507 	/**
508 	 * @see <a href="https://tools.ietf.org/html/rfc1929">RFC 1929</a>
509 	 */
510 	private class SocksBasicAuthentication
511 			extends BasicAuthentication<Buffer, Buffer> {
512 
513 		private static final byte SOCKS_BASIC_PROTOCOL_VERSION = 1;
514 
515 		private static final byte SOCKS_BASIC_AUTH_SUCCESS = 0;
516 
517 		public SocksBasicAuthentication() {
518 			super(proxyAddress, proxyUser, proxyPassword);
519 		}
520 
521 		@Override
522 		public void process() throws Exception {
523 			// Retries impossible. RFC 1929 specifies that the server MUST
524 			// close the connection if authentication is unsuccessful.
525 			done = true;
526 			if (params.getByte() != SOCKS_BASIC_PROTOCOL_VERSION
527 					|| params.getByte() != SOCKS_BASIC_AUTH_SUCCESS) {
528 				throw new IOException(format(
529 						SshdText.get().proxySocksAuthenticationFailed, proxy));
530 			}
531 		}
532 
533 		@Override
534 		protected void askCredentials() {
535 			super.askCredentials();
536 			adjustTimeout();
537 		}
538 
539 		@Override
540 		public Buffer getToken() throws IOException {
541 			if (done) {
542 				return null;
543 			}
544 			try {
545 				byte[] rawUser = user.getBytes(UTF_8);
546 				if (rawUser.length > 255) {
547 					throw new IOException(format(
548 							SshdText.get().proxySocksUsernameTooLong, proxy,
549 							Integer.toString(rawUser.length), user));
550 				}
551 
552 				if (password.length > 255) {
553 					throw new IOException(
554 							format(SshdText.get().proxySocksPasswordTooLong,
555 									proxy, Integer.toString(password.length)));
556 				}
557 				ByteArrayBuffer buffer = new ByteArrayBuffer(
558 						3 + rawUser.length + password.length, false);
559 				buffer.putByte(SOCKS_BASIC_PROTOCOL_VERSION);
560 				buffer.putByte((byte) rawUser.length);
561 				buffer.putRawBytes(rawUser);
562 				buffer.putByte((byte) password.length);
563 				buffer.putRawBytes(password);
564 				return buffer;
565 			} finally {
566 				clearPassword();
567 				done = true;
568 			}
569 		}
570 	}
571 
572 	/**
573 	 * @see <a href="https://tools.ietf.org/html/rfc1961">RFC 1961</a>
574 	 */
575 	private class SocksGssApiAuthentication
576 			extends GssApiAuthentication<Buffer, Buffer> {
577 
578 		private static final byte SOCKS5_GSSAPI_VERSION = 1;
579 
580 		private static final byte SOCKS5_GSSAPI_TOKEN = 1;
581 
582 		private static final int SOCKS5_GSSAPI_FAILURE = 0xFF;
583 
584 		public SocksGssApiAuthentication() {
585 			super(proxyAddress);
586 		}
587 
588 		@Override
589 		protected GSSContext createContext() throws Exception {
590 			return context;
591 		}
592 
593 		@Override
594 		public Buffer getToken() throws Exception {
595 			if (token == null) {
596 				return null;
597 			}
598 			Buffer buffer = new ByteArrayBuffer(4 + token.length, false);
599 			buffer.putByte(SOCKS5_GSSAPI_VERSION);
600 			buffer.putByte(SOCKS5_GSSAPI_TOKEN);
601 			buffer.putByte((byte) ((token.length >> 8) & 0xFF));
602 			buffer.putByte((byte) (token.length & 0xFF));
603 			buffer.putRawBytes(token);
604 			return buffer;
605 		}
606 
607 		@Override
608 		protected byte[] extractToken(Buffer input) throws Exception {
609 			if (context == null) {
610 				return null;
611 			}
612 			int version = input.getUByte();
613 			if (version != SOCKS5_GSSAPI_VERSION) {
614 				throw new IOException(
615 						format(SshdText.get().proxySocksGssApiVersionMismatch,
616 								remoteAddress, Integer.toString(version)));
617 			}
618 			int msgType = input.getUByte();
619 			if (msgType == SOCKS5_GSSAPI_FAILURE) {
620 				throw new IOException(format(
621 						SshdText.get().proxySocksGssApiFailure, remoteAddress));
622 			} else if (msgType != SOCKS5_GSSAPI_TOKEN) {
623 				throw new IOException(format(
624 						SshdText.get().proxySocksGssApiUnknownMessage,
625 						remoteAddress, Integer.toHexString(msgType & 0xFF)));
626 			}
627 			if (input.available() >= 2) {
628 				int length = (input.getUByte() << 8) + input.getUByte();
629 				if (input.available() >= length) {
630 					byte[] value = new byte[length];
631 					if (length > 0) {
632 						input.getRawBytes(value);
633 					}
634 					return value;
635 				}
636 			}
637 			throw new IOException(
638 					format(SshdText.get().proxySocksGssApiMessageTooShort,
639 							remoteAddress));
640 		}
641 	}
642 }