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