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