You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Socks5ClientConnector.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  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. import static java.nio.charset.StandardCharsets.US_ASCII;
  45. import static java.nio.charset.StandardCharsets.UTF_8;
  46. import static java.text.MessageFormat.format;
  47. import java.io.IOException;
  48. import java.net.InetAddress;
  49. import java.net.InetSocketAddress;
  50. import org.apache.sshd.client.session.ClientSession;
  51. import org.apache.sshd.common.io.IoSession;
  52. import org.apache.sshd.common.util.Readable;
  53. import org.apache.sshd.common.util.buffer.Buffer;
  54. import org.apache.sshd.common.util.buffer.BufferUtils;
  55. import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
  56. import org.eclipse.jgit.annotations.NonNull;
  57. import org.eclipse.jgit.internal.transport.sshd.GssApiMechanisms;
  58. import org.eclipse.jgit.internal.transport.sshd.SshdText;
  59. import org.eclipse.jgit.internal.transport.sshd.auth.AuthenticationHandler;
  60. import org.eclipse.jgit.internal.transport.sshd.auth.BasicAuthentication;
  61. import org.eclipse.jgit.internal.transport.sshd.auth.GssApiAuthentication;
  62. import org.eclipse.jgit.transport.SshConstants;
  63. import org.ietf.jgss.GSSContext;
  64. /**
  65. * A {@link AbstractClientProxyConnector} to connect through a SOCKS5 proxy.
  66. *
  67. * @see <a href="https://tools.ietf.org/html/rfc1928">RFC 1928</a>
  68. */
  69. public class Socks5ClientConnector extends AbstractClientProxyConnector {
  70. // private static final byte SOCKS_VERSION_4 = 4;
  71. private static final byte SOCKS_VERSION_5 = 5;
  72. private static final byte SOCKS_CMD_CONNECT = 1;
  73. // private static final byte SOCKS5_CMD_BIND = 2;
  74. // private static final byte SOCKS5_CMD_UDP_ASSOCIATE = 3;
  75. // Address types
  76. private static final byte SOCKS_ADDRESS_IPv4 = 1;
  77. private static final byte SOCKS_ADDRESS_FQDN = 3;
  78. private static final byte SOCKS_ADDRESS_IPv6 = 4;
  79. // Reply codes
  80. private static final byte SOCKS_REPLY_SUCCESS = 0;
  81. private static final byte SOCKS_REPLY_FAILURE = 1;
  82. private static final byte SOCKS_REPLY_FORBIDDEN = 2;
  83. private static final byte SOCKS_REPLY_NETWORK_UNREACHABLE = 3;
  84. private static final byte SOCKS_REPLY_HOST_UNREACHABLE = 4;
  85. private static final byte SOCKS_REPLY_CONNECTION_REFUSED = 5;
  86. private static final byte SOCKS_REPLY_TTL_EXPIRED = 6;
  87. private static final byte SOCKS_REPLY_COMMAND_UNSUPPORTED = 7;
  88. private static final byte SOCKS_REPLY_ADDRESS_UNSUPPORTED = 8;
  89. /**
  90. * Authentication methods for SOCKS5.
  91. *
  92. * @see <a href=
  93. * "https://www.iana.org/assignments/socks-methods/socks-methods.xhtml">SOCKS
  94. * Methods, IANA.org</a>
  95. */
  96. private enum SocksAuthenticationMethod {
  97. ANONYMOUS(0),
  98. GSSAPI(1),
  99. PASSWORD(2),
  100. // CHALLENGE_HANDSHAKE(3),
  101. // CHALLENGE_RESPONSE(5),
  102. // SSL(6),
  103. // NDS(7),
  104. // MULTI_AUTH(8),
  105. // JSON(9),
  106. NONE_ACCEPTABLE(0xFF);
  107. private byte value;
  108. SocksAuthenticationMethod(int value) {
  109. this.value = (byte) value;
  110. }
  111. public byte getValue() {
  112. return value;
  113. }
  114. }
  115. private enum ProtocolState {
  116. NONE,
  117. INIT {
  118. @Override
  119. public void handleMessage(Socks5ClientConnector connector,
  120. IoSession session, Buffer data) throws Exception {
  121. connector.versionCheck(data.getByte());
  122. SocksAuthenticationMethod authMethod = connector.getAuthMethod(
  123. data.getByte());
  124. switch (authMethod) {
  125. case ANONYMOUS:
  126. connector.sendConnectInfo(session);
  127. break;
  128. case PASSWORD:
  129. connector.doPasswordAuth(session);
  130. break;
  131. case GSSAPI:
  132. connector.doGssApiAuth(session);
  133. break;
  134. default:
  135. throw new IOException(
  136. format(SshdText.get().proxyCannotAuthenticate,
  137. connector.proxyAddress));
  138. }
  139. }
  140. },
  141. AUTHENTICATING {
  142. @Override
  143. public void handleMessage(Socks5ClientConnector connector,
  144. IoSession session, Buffer data) throws Exception {
  145. connector.authStep(session, data);
  146. }
  147. },
  148. CONNECTING {
  149. @Override
  150. public void handleMessage(Socks5ClientConnector connector,
  151. IoSession session, Buffer data) throws Exception {
  152. // Special case: when GSS-API authentication completes, the
  153. // client moves into CONNECTING as soon as the GSS context is
  154. // established and sends the connect request. This is per RFC
  155. // 1961. But for the server, RFC 1961 says it _should_ send an
  156. // empty token even if none generated when its server side
  157. // context is established. That means we may actually get an
  158. // empty token here. That message is 4 bytes long (and has
  159. // content 0x01, 0x01, 0x00, 0x00). We simply skip this message
  160. // if we get it here. If the server for whatever reason sends
  161. // back a "GSS failed" message (it shouldn't, at this point)
  162. // it will be two bytes 0x01 0xFF, which will fail the version
  163. // check.
  164. if (data.available() != 4) {
  165. connector.versionCheck(data.getByte());
  166. connector.establishConnection(data);
  167. }
  168. }
  169. },
  170. CONNECTED,
  171. FAILED;
  172. public void handleMessage(Socks5ClientConnector connector,
  173. @SuppressWarnings("unused") IoSession session, Buffer data)
  174. throws Exception {
  175. throw new IOException(
  176. format(SshdText.get().proxySocksUnexpectedMessage,
  177. connector.proxyAddress, this,
  178. BufferUtils.toHex(data.array())));
  179. }
  180. }
  181. private ProtocolState state;
  182. private AuthenticationHandler<Buffer, Buffer> authenticator;
  183. private GSSContext context;
  184. private byte[] authenticationProposals;
  185. /**
  186. * Creates a new {@link Socks5ClientConnector}. The connector supports
  187. * anonymous connections as well as username-password or Kerberos5 (GSS-API)
  188. * authentication.
  189. *
  190. * @param proxyAddress
  191. * of the proxy server we're connecting to
  192. * @param remoteAddress
  193. * of the target server to connect to
  194. */
  195. public Socks5ClientConnector(@NonNull InetSocketAddress proxyAddress,
  196. @NonNull InetSocketAddress remoteAddress) {
  197. this(proxyAddress, remoteAddress, null, null);
  198. }
  199. /**
  200. * Creates a new {@link Socks5ClientConnector}. The connector supports
  201. * anonymous connections as well as username-password or Kerberos5 (GSS-API)
  202. * authentication.
  203. *
  204. * @param proxyAddress
  205. * of the proxy server we're connecting to
  206. * @param remoteAddress
  207. * of the target server to connect to
  208. * @param proxyUser
  209. * to authenticate at the proxy with
  210. * @param proxyPassword
  211. * to authenticate at the proxy with
  212. */
  213. public Socks5ClientConnector(@NonNull InetSocketAddress proxyAddress,
  214. @NonNull InetSocketAddress remoteAddress,
  215. String proxyUser, char[] proxyPassword) {
  216. super(proxyAddress, remoteAddress, proxyUser, proxyPassword);
  217. this.state = ProtocolState.NONE;
  218. }
  219. @Override
  220. public void sendClientProxyMetadata(ClientSession sshSession)
  221. throws Exception {
  222. init(sshSession);
  223. IoSession session = sshSession.getIoSession();
  224. // Send the initial request
  225. Buffer buffer = new ByteArrayBuffer(5, false);
  226. buffer.putByte(SOCKS_VERSION_5);
  227. context = getGSSContext(remoteAddress);
  228. authenticationProposals = getAuthenticationProposals();
  229. buffer.putByte((byte) authenticationProposals.length);
  230. buffer.putRawBytes(authenticationProposals);
  231. state = ProtocolState.INIT;
  232. session.writePacket(buffer).verify(getTimeout());
  233. }
  234. private byte[] getAuthenticationProposals() {
  235. byte[] proposals = new byte[3];
  236. int i = 0;
  237. proposals[i++] = SocksAuthenticationMethod.ANONYMOUS.getValue();
  238. proposals[i++] = SocksAuthenticationMethod.PASSWORD.getValue();
  239. if (context != null) {
  240. proposals[i++] = SocksAuthenticationMethod.GSSAPI.getValue();
  241. }
  242. if (i == proposals.length) {
  243. return proposals;
  244. }
  245. byte[] result = new byte[i];
  246. System.arraycopy(proposals, 0, result, 0, i);
  247. return result;
  248. }
  249. private void sendConnectInfo(IoSession session) throws Exception {
  250. GssApiMechanisms.closeContextSilently(context);
  251. byte[] rawAddress = getRawAddress(remoteAddress);
  252. byte[] remoteName = null;
  253. byte type;
  254. int length = 0;
  255. if (rawAddress == null) {
  256. remoteName = remoteAddress.getHostString().getBytes(US_ASCII);
  257. if (remoteName == null || remoteName.length == 0) {
  258. throw new IOException(
  259. format(SshdText.get().proxySocksNoRemoteHostName,
  260. remoteAddress));
  261. } else if (remoteName.length > 255) {
  262. // Should not occur; host names must not be longer than 255
  263. // US_ASCII characters. Internal error, no translation.
  264. throw new IOException(format(
  265. "Proxy host name too long for SOCKS (at most 255 characters): {0}", //$NON-NLS-1$
  266. remoteAddress.getHostString()));
  267. }
  268. type = SOCKS_ADDRESS_FQDN;
  269. length = remoteName.length + 1;
  270. } else {
  271. length = rawAddress.length;
  272. type = length == 4 ? SOCKS_ADDRESS_IPv4 : SOCKS_ADDRESS_IPv6;
  273. }
  274. Buffer buffer = new ByteArrayBuffer(4 + length + 2, false);
  275. buffer.putByte(SOCKS_VERSION_5);
  276. buffer.putByte(SOCKS_CMD_CONNECT);
  277. buffer.putByte((byte) 0); // Reserved
  278. buffer.putByte(type);
  279. if (remoteName != null) {
  280. buffer.putByte((byte) remoteName.length);
  281. buffer.putRawBytes(remoteName);
  282. } else {
  283. buffer.putRawBytes(rawAddress);
  284. }
  285. int port = remoteAddress.getPort();
  286. if (port <= 0) {
  287. port = SshConstants.SSH_DEFAULT_PORT;
  288. }
  289. buffer.putByte((byte) ((port >> 8) & 0xFF));
  290. buffer.putByte((byte) (port & 0xFF));
  291. state = ProtocolState.CONNECTING;
  292. session.writePacket(buffer).verify(getTimeout());
  293. }
  294. private void doPasswordAuth(IoSession session) throws Exception {
  295. GssApiMechanisms.closeContextSilently(context);
  296. authenticator = new SocksBasicAuthentication();
  297. session.addCloseFutureListener(f -> close());
  298. startAuth(session);
  299. }
  300. private void doGssApiAuth(IoSession session) throws Exception {
  301. authenticator = new SocksGssApiAuthentication();
  302. session.addCloseFutureListener(f -> close());
  303. startAuth(session);
  304. }
  305. private void close() {
  306. AuthenticationHandler<?, ?> handler = authenticator;
  307. authenticator = null;
  308. if (handler != null) {
  309. handler.close();
  310. }
  311. }
  312. private void startAuth(IoSession session) throws Exception {
  313. Buffer buffer = null;
  314. try {
  315. authenticator.setParams(null);
  316. authenticator.start();
  317. buffer = authenticator.getToken();
  318. state = ProtocolState.AUTHENTICATING;
  319. if (buffer == null) {
  320. // Internal error; no translation
  321. throw new IOException(
  322. "No data for proxy authentication with " //$NON-NLS-1$
  323. + proxyAddress);
  324. }
  325. session.writePacket(buffer).verify(getTimeout());
  326. } finally {
  327. if (buffer != null) {
  328. buffer.clear(true);
  329. }
  330. }
  331. }
  332. private void authStep(IoSession session, Buffer input) throws Exception {
  333. Buffer buffer = null;
  334. try {
  335. authenticator.setParams(input);
  336. authenticator.process();
  337. buffer = authenticator.getToken();
  338. if (buffer != null) {
  339. session.writePacket(buffer).verify(getTimeout());
  340. }
  341. } finally {
  342. if (buffer != null) {
  343. buffer.clear(true);
  344. }
  345. }
  346. if (authenticator.isDone()) {
  347. sendConnectInfo(session);
  348. }
  349. }
  350. private void establishConnection(Buffer data) throws Exception {
  351. byte reply = data.getByte();
  352. switch (reply) {
  353. case SOCKS_REPLY_SUCCESS:
  354. state = ProtocolState.CONNECTED;
  355. setDone(true);
  356. return;
  357. case SOCKS_REPLY_FAILURE:
  358. throw new IOException(format(
  359. SshdText.get().proxySocksFailureGeneral, proxyAddress));
  360. case SOCKS_REPLY_FORBIDDEN:
  361. throw new IOException(
  362. format(SshdText.get().proxySocksFailureForbidden,
  363. proxyAddress, remoteAddress));
  364. case SOCKS_REPLY_NETWORK_UNREACHABLE:
  365. throw new IOException(
  366. format(SshdText.get().proxySocksFailureNetworkUnreachable,
  367. proxyAddress, remoteAddress));
  368. case SOCKS_REPLY_HOST_UNREACHABLE:
  369. throw new IOException(
  370. format(SshdText.get().proxySocksFailureHostUnreachable,
  371. proxyAddress, remoteAddress));
  372. case SOCKS_REPLY_CONNECTION_REFUSED:
  373. throw new IOException(
  374. format(SshdText.get().proxySocksFailureRefused,
  375. proxyAddress, remoteAddress));
  376. case SOCKS_REPLY_TTL_EXPIRED:
  377. throw new IOException(
  378. format(SshdText.get().proxySocksFailureTTL, proxyAddress));
  379. case SOCKS_REPLY_COMMAND_UNSUPPORTED:
  380. throw new IOException(
  381. format(SshdText.get().proxySocksFailureUnsupportedCommand,
  382. proxyAddress));
  383. case SOCKS_REPLY_ADDRESS_UNSUPPORTED:
  384. throw new IOException(
  385. format(SshdText.get().proxySocksFailureUnsupportedAddress,
  386. proxyAddress));
  387. default:
  388. throw new IOException(format(
  389. SshdText.get().proxySocksFailureUnspecified, proxyAddress));
  390. }
  391. }
  392. @Override
  393. public void messageReceived(IoSession session, Readable buffer)
  394. throws Exception {
  395. try {
  396. // Dispatch according to protocol state
  397. ByteArrayBuffer data = new ByteArrayBuffer(buffer.available(),
  398. false);
  399. data.putBuffer(buffer);
  400. data.compact();
  401. state.handleMessage(this, session, data);
  402. } catch (Exception e) {
  403. state = ProtocolState.FAILED;
  404. if (authenticator != null) {
  405. authenticator.close();
  406. authenticator = null;
  407. }
  408. try {
  409. setDone(false);
  410. } catch (Exception inner) {
  411. e.addSuppressed(inner);
  412. }
  413. throw e;
  414. }
  415. }
  416. private void versionCheck(byte version) throws Exception {
  417. if (version != SOCKS_VERSION_5) {
  418. throw new IOException(
  419. format(SshdText.get().proxySocksUnexpectedVersion,
  420. Integer.toString(version & 0xFF)));
  421. }
  422. }
  423. private SocksAuthenticationMethod getAuthMethod(byte value) {
  424. if (value != SocksAuthenticationMethod.NONE_ACCEPTABLE.getValue()) {
  425. for (byte proposed : authenticationProposals) {
  426. if (proposed == value) {
  427. for (SocksAuthenticationMethod method : SocksAuthenticationMethod
  428. .values()) {
  429. if (method.getValue() == value) {
  430. return method;
  431. }
  432. }
  433. break;
  434. }
  435. }
  436. }
  437. return SocksAuthenticationMethod.NONE_ACCEPTABLE;
  438. }
  439. private static byte[] getRawAddress(@NonNull InetSocketAddress address) {
  440. InetAddress ipAddress = GssApiMechanisms.resolve(address);
  441. return ipAddress == null ? null : ipAddress.getAddress();
  442. }
  443. private static GSSContext getGSSContext(
  444. @NonNull InetSocketAddress address) {
  445. if (!GssApiMechanisms.getSupportedMechanisms()
  446. .contains(GssApiMechanisms.KERBEROS_5)) {
  447. return null;
  448. }
  449. return GssApiMechanisms.createContext(GssApiMechanisms.KERBEROS_5,
  450. GssApiMechanisms.getCanonicalName(address));
  451. }
  452. /**
  453. * @see <a href="https://tools.ietf.org/html/rfc1929">RFC 1929</a>
  454. */
  455. private class SocksBasicAuthentication
  456. extends BasicAuthentication<Buffer, Buffer> {
  457. private static final byte SOCKS_BASIC_PROTOCOL_VERSION = 1;
  458. private static final byte SOCKS_BASIC_AUTH_SUCCESS = 0;
  459. public SocksBasicAuthentication() {
  460. super(proxyAddress, proxyUser, proxyPassword);
  461. }
  462. @Override
  463. public void process() throws Exception {
  464. // Retries impossible. RFC 1929 specifies that the server MUST
  465. // close the connection if authentication is unsuccessful.
  466. done = true;
  467. if (params.getByte() != SOCKS_BASIC_PROTOCOL_VERSION
  468. || params.getByte() != SOCKS_BASIC_AUTH_SUCCESS) {
  469. throw new IOException(format(
  470. SshdText.get().proxySocksAuthenticationFailed, proxy));
  471. }
  472. }
  473. @Override
  474. protected void askCredentials() {
  475. super.askCredentials();
  476. adjustTimeout();
  477. }
  478. @Override
  479. public Buffer getToken() throws IOException {
  480. if (done) {
  481. return null;
  482. }
  483. try {
  484. byte[] rawUser = user.getBytes(UTF_8);
  485. if (rawUser.length > 255) {
  486. throw new IOException(format(
  487. SshdText.get().proxySocksUsernameTooLong, proxy,
  488. Integer.toString(rawUser.length), user));
  489. }
  490. if (password.length > 255) {
  491. throw new IOException(
  492. format(SshdText.get().proxySocksPasswordTooLong,
  493. proxy, Integer.toString(password.length)));
  494. }
  495. ByteArrayBuffer buffer = new ByteArrayBuffer(
  496. 3 + rawUser.length + password.length, false);
  497. buffer.putByte(SOCKS_BASIC_PROTOCOL_VERSION);
  498. buffer.putByte((byte) rawUser.length);
  499. buffer.putRawBytes(rawUser);
  500. buffer.putByte((byte) password.length);
  501. buffer.putRawBytes(password);
  502. return buffer;
  503. } finally {
  504. clearPassword();
  505. done = true;
  506. }
  507. }
  508. }
  509. /**
  510. * @see <a href="https://tools.ietf.org/html/rfc1961">RFC 1961</a>
  511. */
  512. private class SocksGssApiAuthentication
  513. extends GssApiAuthentication<Buffer, Buffer> {
  514. private static final byte SOCKS5_GSSAPI_VERSION = 1;
  515. private static final byte SOCKS5_GSSAPI_TOKEN = 1;
  516. private static final int SOCKS5_GSSAPI_FAILURE = 0xFF;
  517. public SocksGssApiAuthentication() {
  518. super(proxyAddress);
  519. }
  520. @Override
  521. protected GSSContext createContext() throws Exception {
  522. return context;
  523. }
  524. @Override
  525. public Buffer getToken() throws Exception {
  526. if (token == null) {
  527. return null;
  528. }
  529. Buffer buffer = new ByteArrayBuffer(4 + token.length, false);
  530. buffer.putByte(SOCKS5_GSSAPI_VERSION);
  531. buffer.putByte(SOCKS5_GSSAPI_TOKEN);
  532. buffer.putByte((byte) ((token.length >> 8) & 0xFF));
  533. buffer.putByte((byte) (token.length & 0xFF));
  534. buffer.putRawBytes(token);
  535. return buffer;
  536. }
  537. @Override
  538. protected byte[] extractToken(Buffer input) throws Exception {
  539. if (context == null) {
  540. return null;
  541. }
  542. int version = input.getUByte();
  543. if (version != SOCKS5_GSSAPI_VERSION) {
  544. throw new IOException(
  545. format(SshdText.get().proxySocksGssApiVersionMismatch,
  546. remoteAddress, Integer.toString(version)));
  547. }
  548. int msgType = input.getUByte();
  549. if (msgType == SOCKS5_GSSAPI_FAILURE) {
  550. throw new IOException(format(
  551. SshdText.get().proxySocksGssApiFailure, remoteAddress));
  552. } else if (msgType != SOCKS5_GSSAPI_TOKEN) {
  553. throw new IOException(format(
  554. SshdText.get().proxySocksGssApiUnknownMessage,
  555. remoteAddress, Integer.toHexString(msgType & 0xFF)));
  556. }
  557. if (input.available() >= 2) {
  558. int length = (input.getUByte() << 8) + input.getUByte();
  559. if (input.available() >= length) {
  560. byte[] value = new byte[length];
  561. if (length > 0) {
  562. input.getRawBytes(value);
  563. }
  564. return value;
  565. }
  566. }
  567. throw new IOException(
  568. format(SshdText.get().proxySocksGssApiMessageTooShort,
  569. remoteAddress));
  570. }
  571. }
  572. }