netty5/handler-proxy/src/test/java/io/netty/handler/proxy/Socks5ProxyServer.java
Tim Brooks 3344cd21ac Wrap operations requiring SocketPermission with doPrivileged blocks
Motivation:

Currently Netty does not wrap socket connect, bind, or accept
operations in doPrivileged blocks. Nor does it wrap cases where a dns
lookup might happen.

This prevents an application utilizing the SecurityManager from
isolating SocketPermissions to Netty.

Modifications:

I have introduced a class (SocketUtils) that wraps operations
requiring SocketPermissions in doPrivileged blocks.

Result:

A user of Netty can grant SocketPermissions explicitly to the Netty
jar, without granting it to the rest of their application.
2017-01-19 21:12:52 +01:00

180 lines
6.9 KiB
Java

/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.proxy;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.socksx.v5.DefaultSocks5CommandResponse;
import io.netty.handler.codec.socksx.v5.DefaultSocks5InitialResponse;
import io.netty.handler.codec.socksx.v5.DefaultSocks5PasswordAuthResponse;
import io.netty.handler.codec.socksx.v5.Socks5AddressType;
import io.netty.handler.codec.socksx.v5.Socks5AuthMethod;
import io.netty.handler.codec.socksx.v5.Socks5CommandRequest;
import io.netty.handler.codec.socksx.v5.Socks5CommandRequestDecoder;
import io.netty.handler.codec.socksx.v5.Socks5CommandResponse;
import io.netty.handler.codec.socksx.v5.Socks5CommandStatus;
import io.netty.handler.codec.socksx.v5.Socks5CommandType;
import io.netty.handler.codec.socksx.v5.Socks5InitialRequest;
import io.netty.handler.codec.socksx.v5.Socks5InitialRequestDecoder;
import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthRequest;
import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthRequestDecoder;
import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthStatus;
import io.netty.handler.codec.socksx.v5.Socks5ServerEncoder;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.SocketUtils;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
final class Socks5ProxyServer extends ProxyServer {
private static final String ENCODER = "encoder";
private static final String DECODER = "decoder";
Socks5ProxyServer(boolean useSsl, TestMode testMode, InetSocketAddress destination) {
super(useSsl, testMode, destination);
}
Socks5ProxyServer(
boolean useSsl, TestMode testMode, InetSocketAddress destination, String username, String password) {
super(useSsl, testMode, destination, username, password);
}
@Override
protected void configure(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
switch (testMode) {
case INTERMEDIARY:
p.addLast(DECODER, new Socks5InitialRequestDecoder());
p.addLast(ENCODER, Socks5ServerEncoder.DEFAULT);
p.addLast(new Socks5IntermediaryHandler());
break;
case TERMINAL:
p.addLast(DECODER, new Socks5InitialRequestDecoder());
p.addLast(ENCODER, Socks5ServerEncoder.DEFAULT);
p.addLast(new Socks5TerminalHandler());
break;
case UNRESPONSIVE:
p.addLast(UnresponsiveHandler.INSTANCE);
break;
}
}
boolean authenticate(ChannelHandlerContext ctx, Object msg) {
if (username == null) {
ctx.pipeline().replace(DECODER, DECODER, new Socks5CommandRequestDecoder());
ctx.write(new DefaultSocks5InitialResponse(Socks5AuthMethod.NO_AUTH));
return true;
}
if (msg instanceof Socks5InitialRequest) {
ctx.pipeline().replace(DECODER, DECODER, new Socks5PasswordAuthRequestDecoder());
ctx.write(new DefaultSocks5InitialResponse(Socks5AuthMethod.PASSWORD));
return false;
}
Socks5PasswordAuthRequest req = (Socks5PasswordAuthRequest) msg;
if (req.username().equals(username) && req.password().equals(password)) {
ctx.pipeline().replace(DECODER, DECODER, new Socks5CommandRequestDecoder());
ctx.write(new DefaultSocks5PasswordAuthResponse(Socks5PasswordAuthStatus.SUCCESS));
return true;
}
ctx.pipeline().replace(DECODER, DECODER, new Socks5PasswordAuthRequestDecoder());
ctx.write(new DefaultSocks5PasswordAuthResponse(Socks5PasswordAuthStatus.FAILURE));
return false;
}
private final class Socks5IntermediaryHandler extends IntermediaryHandler {
private boolean authenticated;
private SocketAddress intermediaryDestination;
@Override
protected boolean handleProxyProtocol(ChannelHandlerContext ctx, Object msg) throws Exception {
if (!authenticated) {
authenticated = authenticate(ctx, msg);
return false;
}
Socks5CommandRequest req = (Socks5CommandRequest) msg;
assertThat(req.type(), is(Socks5CommandType.CONNECT));
Socks5CommandResponse res =
new DefaultSocks5CommandResponse(Socks5CommandStatus.SUCCESS, Socks5AddressType.IPv4);
intermediaryDestination = SocketUtils.socketAddress(req.dstAddr(), req.dstPort());
ctx.write(res);
ctx.pipeline().remove(ENCODER);
ctx.pipeline().remove(DECODER);
return true;
}
@Override
protected SocketAddress intermediaryDestination() {
return intermediaryDestination;
}
}
private final class Socks5TerminalHandler extends TerminalHandler {
private boolean authenticated;
@Override
protected boolean handleProxyProtocol(ChannelHandlerContext ctx, Object msg) throws Exception {
if (!authenticated) {
authenticated = authenticate(ctx, msg);
return false;
}
Socks5CommandRequest req = (Socks5CommandRequest) msg;
assertThat(req.type(), is(Socks5CommandType.CONNECT));
ctx.pipeline().addBefore(ctx.name(), "lineDecoder", new LineBasedFrameDecoder(64, false, true));
Socks5CommandResponse res;
boolean sendGreeting = false;
if (!req.dstAddr().equals(destination.getHostString()) ||
req.dstPort() != destination.getPort()) {
res = new DefaultSocks5CommandResponse(Socks5CommandStatus.FORBIDDEN, Socks5AddressType.IPv4);
} else {
res = new DefaultSocks5CommandResponse(Socks5CommandStatus.SUCCESS, Socks5AddressType.IPv4);
sendGreeting = true;
}
ctx.write(res);
ctx.pipeline().remove(ENCODER);
ctx.pipeline().remove(DECODER);
if (sendGreeting) {
ctx.write(Unpooled.copiedBuffer("0\n", CharsetUtil.US_ASCII));
}
return true;
}
}
}