1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.mina.proxy.handlers.socks;
21
22 import java.io.UnsupportedEncodingException;
23 import java.net.Inet4Address;
24 import java.net.Inet6Address;
25 import java.net.InetSocketAddress;
26
27 import org.apache.mina.core.buffer.IoBuffer;
28 import org.apache.mina.core.filterchain.IoFilter.NextFilter;
29 import org.apache.mina.proxy.session.ProxyIoSession;
30 import org.apache.mina.proxy.utils.ByteUtilities;
31 import org.ietf.jgss.GSSContext;
32 import org.ietf.jgss.GSSException;
33 import org.ietf.jgss.GSSManager;
34 import org.ietf.jgss.GSSName;
35 import org.ietf.jgss.Oid;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39
40
41
42
43
44
45 public class Socks5LogicHandler extends AbstractSocksLogicHandler {
46
47 private static final Logger LOGGER = LoggerFactory.getLogger(Socks5LogicHandler.class);
48
49
50
51
52 private static final String SELECTED_AUTH_METHOD = Socks5LogicHandler.class.getName() + ".SelectedAuthMethod";
53
54
55
56
57 private static final String HANDSHAKE_STEP = Socks5LogicHandler.class.getName() + ".HandshakeStep";
58
59
60
61
62 private static final String GSS_CONTEXT = Socks5LogicHandler.class.getName() + ".GSSContext";
63
64
65
66
67 private static final String GSS_TOKEN = Socks5LogicHandler.class.getName() + ".GSSToken";
68
69
70
71
72
73
74 public Socks5LogicHandler(final ProxyIoSession proxyIoSession) {
75 super(proxyIoSession);
76 getSession().setAttribute(HANDSHAKE_STEP, SocksProxyConstants.SOCKS5_GREETING_STEP);
77 }
78
79
80
81
82
83
84 @Override
85 public synchronized void doHandshake(final NextFilter nextFilter) {
86 if (LOGGER.isDebugEnabled()) {
87 LOGGER.debug(" doHandshake()");
88 }
89
90
91 writeRequest(nextFilter, request, ((Integer) getSession().getAttribute(HANDSHAKE_STEP)).intValue());
92 }
93
94
95
96
97
98
99
100 private IoBuffer encodeInitialGreetingPacket(final SocksProxyRequest request) {
101 byte nbMethods = (byte) SocksProxyConstants.SUPPORTED_AUTH_METHODS.length;
102 IoBuffer buf = IoBuffer.allocate(2 + nbMethods);
103
104 buf.put(request.getProtocolVersion());
105 buf.put(nbMethods);
106 buf.put(SocksProxyConstants.SUPPORTED_AUTH_METHODS);
107
108 return buf;
109 }
110
111
112
113
114
115
116
117
118
119 private IoBuffer encodeProxyRequestPacket(final SocksProxyRequest request) throws UnsupportedEncodingException {
120 int len = 6;
121 InetSocketAddress adr = request.getEndpointAddress();
122 byte addressType = 0;
123 byte[] host = null;
124
125 if (adr != null && !adr.isUnresolved()) {
126 if (adr.getAddress() instanceof Inet6Address) {
127 len += 16;
128 addressType = SocksProxyConstants.IPV6_ADDRESS_TYPE;
129 } else if (adr.getAddress() instanceof Inet4Address) {
130 len += 4;
131 addressType = SocksProxyConstants.IPV4_ADDRESS_TYPE;
132 }
133 } else {
134 host = request.getHost() != null ? request.getHost().getBytes("ASCII") : null;
135
136 if (host != null) {
137 len += 1 + host.length;
138 addressType = SocksProxyConstants.DOMAIN_NAME_ADDRESS_TYPE;
139 } else {
140 throw new IllegalArgumentException("SocksProxyRequest object " + "has no suitable endpoint information");
141 }
142 }
143
144 IoBuffer buf = IoBuffer.allocate(len);
145
146 buf.put(request.getProtocolVersion());
147 buf.put(request.getCommandCode());
148 buf.put((byte) 0x00);
149 buf.put(addressType);
150
151 if (host == null) {
152 buf.put(request.getIpAddress());
153 } else {
154 buf.put((byte) host.length);
155 buf.put(host);
156 }
157
158 buf.put(request.getPort());
159
160 return buf;
161 }
162
163
164
165
166
167
168
169
170
171
172
173 private IoBuffer encodeAuthenticationPacket(final SocksProxyRequest request) throws UnsupportedEncodingException,
174 GSSException {
175 byte method = ((Byte) getSession().getAttribute(Socks5LogicHandler.SELECTED_AUTH_METHOD)).byteValue();
176
177 switch (method) {
178 case SocksProxyConstants.NO_AUTH:
179
180
181 getSession().setAttribute(HANDSHAKE_STEP, SocksProxyConstants.SOCKS5_REQUEST_STEP);
182 break;
183
184 case SocksProxyConstants.GSSAPI_AUTH:
185 return encodeGSSAPIAuthenticationPacket(request);
186
187 case SocksProxyConstants.BASIC_AUTH:
188
189 byte[] user = request.getUserName().getBytes("ASCII");
190 byte[] pwd = request.getPassword().getBytes("ASCII");
191 IoBuffer buf = IoBuffer.allocate(3 + user.length + pwd.length);
192
193 buf.put(SocksProxyConstants.BASIC_AUTH_SUBNEGOTIATION_VERSION);
194 buf.put((byte) user.length);
195 buf.put(user);
196 buf.put((byte) pwd.length);
197 buf.put(pwd);
198
199 return buf;
200 }
201
202 return null;
203 }
204
205
206
207
208
209
210
211
212 private IoBuffer encodeGSSAPIAuthenticationPacket(final SocksProxyRequest request) throws GSSException {
213 GSSContext ctx = (GSSContext) getSession().getAttribute(GSS_CONTEXT);
214 if (ctx == null) {
215
216 GSSManager manager = GSSManager.getInstance();
217 GSSName serverName = manager.createName(request.getServiceKerberosName(), null);
218 Oid krb5OID = new Oid(SocksProxyConstants.KERBEROS_V5_OID);
219
220 if (LOGGER.isDebugEnabled()) {
221 LOGGER.debug("Available mechs:");
222 for (Oid o : manager.getMechs()) {
223 if (o.equals(krb5OID)) {
224 LOGGER.debug("Found Kerberos V OID available");
225 }
226 LOGGER.debug("{} with oid = {}", manager.getNamesForMech(o), o);
227 }
228 }
229
230 ctx = manager.createContext(serverName, krb5OID, null, GSSContext.DEFAULT_LIFETIME);
231
232 ctx.requestMutualAuth(true);
233 ctx.requestConf(false);
234 ctx.requestInteg(false);
235
236 getSession().setAttribute(GSS_CONTEXT, ctx);
237 }
238
239 byte[] token = (byte[]) getSession().getAttribute(GSS_TOKEN);
240 if (token != null) {
241 if (LOGGER.isDebugEnabled()) {
242 LOGGER.debug(" Received Token[{}] = {}", token.length, ByteUtilities.asHex(token));
243 }
244 }
245 IoBuffer buf = null;
246
247 if (!ctx.isEstablished()) {
248
249 if (token == null) {
250 token = new byte[32];
251 }
252
253 token = ctx.initSecContext(token, 0, token.length);
254
255
256
257 if (token != null) {
258 if (LOGGER.isDebugEnabled()) {
259 LOGGER.debug(" Sending Token[{}] = {}", token.length, ByteUtilities.asHex(token));
260 }
261
262 getSession().setAttribute(GSS_TOKEN, token);
263 buf = IoBuffer.allocate(4 + token.length);
264 buf.put(new byte[] { SocksProxyConstants.GSSAPI_AUTH_SUBNEGOTIATION_VERSION,
265 SocksProxyConstants.GSSAPI_MSG_TYPE });
266
267 buf.put(ByteUtilities.intToNetworkByteOrder(token.length, 2));
268 buf.put(token);
269 }
270 }
271
272 return buf;
273 }
274
275
276
277
278
279
280
281
282
283 private void writeRequest(final NextFilter nextFilter, final SocksProxyRequest request, int step) {
284 try {
285 IoBuffer buf = null;
286
287 if (step == SocksProxyConstants.SOCKS5_GREETING_STEP) {
288 buf = encodeInitialGreetingPacket(request);
289 } else if (step == SocksProxyConstants.SOCKS5_AUTH_STEP) {
290
291 buf = encodeAuthenticationPacket(request);
292
293
294 if (buf == null) {
295 step = SocksProxyConstants.SOCKS5_REQUEST_STEP;
296 }
297 }
298
299 if (step == SocksProxyConstants.SOCKS5_REQUEST_STEP) {
300 buf = encodeProxyRequestPacket(request);
301 }
302
303 buf.flip();
304 writeData(nextFilter, buf);
305
306 } catch (Exception ex) {
307 closeSession("Unable to send Socks request: ", ex);
308 }
309 }
310
311
312
313
314
315
316
317
318 @Override
319 public synchronized void messageReceived(final NextFilter nextFilter, final IoBuffer buf) {
320 try {
321 int step = ((Integer) getSession().getAttribute(HANDSHAKE_STEP)).intValue();
322
323 if (step == SocksProxyConstants.SOCKS5_GREETING_STEP && buf.get(0) != SocksProxyConstants.SOCKS_VERSION_5) {
324 throw new IllegalStateException("Wrong socks version running on server");
325 }
326
327 if ((step == SocksProxyConstants.SOCKS5_GREETING_STEP || step == SocksProxyConstants.SOCKS5_AUTH_STEP)
328 && buf.remaining() >= 2) {
329 handleResponse(nextFilter, buf, step);
330 } else if (step == SocksProxyConstants.SOCKS5_REQUEST_STEP && buf.remaining() >= 5) {
331 handleResponse(nextFilter, buf, step);
332 }
333 } catch (Exception ex) {
334 closeSession("Proxy handshake failed: ", ex);
335 }
336 }
337
338
339
340
341
342
343
344
345
346 protected void handleResponse(final NextFilter nextFilter, final IoBuffer buf, int step) throws Exception {
347 int len = 2;
348 if (step == SocksProxyConstants.SOCKS5_GREETING_STEP) {
349
350 byte method = buf.get(1);
351
352 if (method == SocksProxyConstants.NO_ACCEPTABLE_AUTH_METHOD) {
353 throw new IllegalStateException("No acceptable authentication method to use with "
354 + "the socks proxy server");
355 }
356
357 getSession().setAttribute(SELECTED_AUTH_METHOD, Byte.valueOf(method));
358
359 } else if (step == SocksProxyConstants.SOCKS5_AUTH_STEP) {
360
361 byte method = ((Byte) getSession().getAttribute(Socks5LogicHandler.SELECTED_AUTH_METHOD)).byteValue();
362
363 if (method == SocksProxyConstants.GSSAPI_AUTH) {
364 int oldPos = buf.position();
365
366 if (buf.get(0) != 0x01) {
367 throw new IllegalStateException("Authentication failed");
368 }
369 if ((buf.get(1) & 0x00FF) == 0x00FF) {
370 throw new IllegalStateException("Authentication failed: GSS API Security Context Failure");
371 }
372
373 if (buf.remaining() >= 2) {
374 byte[] size = new byte[2];
375 buf.get(size);
376 int s = ByteUtilities.makeIntFromByte2(size);
377 if (buf.remaining() >= s) {
378 byte[] token = new byte[s];
379 buf.get(token);
380 getSession().setAttribute(GSS_TOKEN, token);
381 len = 0;
382 } else {
383 return;
384 }
385 } else {
386 buf.position(oldPos);
387 return;
388 }
389 } else if (buf.get(1) != SocksProxyConstants.V5_REPLY_SUCCEEDED) {
390 throw new IllegalStateException("Authentication failed");
391 }
392
393 } else if (step == SocksProxyConstants.SOCKS5_REQUEST_STEP) {
394
395 byte addressType = buf.get(3);
396 len = 6;
397 if (addressType == SocksProxyConstants.IPV6_ADDRESS_TYPE) {
398 len += 16;
399 } else if (addressType == SocksProxyConstants.IPV4_ADDRESS_TYPE) {
400 len += 4;
401 } else if (addressType == SocksProxyConstants.DOMAIN_NAME_ADDRESS_TYPE) {
402 len += 1 + (buf.get(4));
403 } else {
404 throw new IllegalStateException("Unknwon address type");
405 }
406
407 if (buf.remaining() >= len) {
408
409 byte status = buf.get(1);
410 if (LOGGER.isDebugEnabled()) {
411 LOGGER.debug(" response status: {}", SocksProxyConstants.getReplyCodeAsString(status));
412 }
413
414 if (status == SocksProxyConstants.V5_REPLY_SUCCEEDED) {
415 buf.position(buf.position() + len);
416 setHandshakeComplete();
417 return;
418 }
419
420 throw new Exception("Proxy handshake failed - Code: 0x" + ByteUtilities.asHex(new byte[] { status }));
421 }
422
423 return;
424 }
425
426 if (len > 0) {
427 buf.position(buf.position() + len);
428 }
429
430
431
432 boolean isAuthenticating = false;
433 if (step == SocksProxyConstants.SOCKS5_AUTH_STEP) {
434 byte method = ((Byte) getSession().getAttribute(Socks5LogicHandler.SELECTED_AUTH_METHOD)).byteValue();
435 if (method == SocksProxyConstants.GSSAPI_AUTH) {
436 GSSContext ctx = (GSSContext) getSession().getAttribute(GSS_CONTEXT);
437 if (ctx == null || !ctx.isEstablished()) {
438 isAuthenticating = true;
439 }
440 }
441 }
442
443 if (!isAuthenticating) {
444 getSession().setAttribute(HANDSHAKE_STEP, ++step);
445 }
446
447 doHandshake(nextFilter);
448 }
449
450
451
452
453
454
455
456 @Override
457 protected void closeSession(String message) {
458 GSSContext ctx = (GSSContext) getSession().getAttribute(GSS_CONTEXT);
459 if (ctx != null) {
460 try {
461 ctx.dispose();
462 } catch (GSSException e) {
463 e.printStackTrace();
464 super.closeSession(message, e);
465 return;
466 }
467 }
468 super.closeSession(message);
469 }
470 }