| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF 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 streamer.apr; | |
| import static streamer.debug.MockServer.Packet.PacketType.CLIENT; | |
| import static streamer.debug.MockServer.Packet.PacketType.SERVER; | |
| import java.io.IOException; | |
| import java.net.InetSocketAddress; | |
| import java.util.HashMap; | |
| import org.apache.tomcat.jni.Address; | |
| import org.apache.tomcat.jni.Error; | |
| import org.apache.tomcat.jni.Library; | |
| import org.apache.tomcat.jni.Pool; | |
| import org.apache.tomcat.jni.SSL; | |
| import org.apache.tomcat.jni.SSLContext; | |
| import org.apache.tomcat.jni.SSLSocket; | |
| import org.apache.tomcat.jni.Socket; | |
| import streamer.BaseElement; | |
| import streamer.Direction; | |
| import streamer.Element; | |
| import streamer.Event; | |
| import streamer.Pipeline; | |
| import streamer.PipelineImpl; | |
| import streamer.Queue; | |
| import streamer.SocketWrapper; | |
| import streamer.debug.MockServer; | |
| import streamer.debug.MockServer.Packet; | |
| import streamer.ssl.SSLState; | |
| import sun.security.x509.X509CertImpl; | |
| public class AprSocketWrapperImpl extends PipelineImpl implements SocketWrapper { | |
| static { | |
| try { | |
| Library.initialize(null); | |
| SSL.initialize(null); | |
| } catch (Exception e) { | |
| throw new RuntimeException("Cannot load Tomcat Native Library (Apache Portable Runtime).", e); | |
| } | |
| } | |
| private final SSLState sslState; | |
| final long pool = Pool.create(0); | |
| long inetAddress; | |
| long socket; | |
| private AprSocketSource source; | |
| private AprSocketSink sink; | |
| boolean shutdown = false; | |
| private final boolean shutdowned = false; | |
| public AprSocketWrapperImpl(String id, SSLState sslState) { | |
| super(id); | |
| this.sslState = sslState; | |
| } | |
| @Override | |
| protected HashMap<String, Element> initElementMap(String id) { | |
| HashMap<String, Element> map = new HashMap<String, Element>(); | |
| source = new AprSocketSource(id + "." + OUT, this); | |
| sink = new AprSocketSink(id + "." + IN, this); | |
| // Pass requests to read data to socket input stream | |
| map.put(OUT, source); | |
| // All incoming data, which is sent to this socket wrapper, will be sent | |
| // to socket remote | |
| map.put(IN, sink); | |
| return map; | |
| } | |
| /** | |
| * Connect this socket wrapper to remote server and start main loop on | |
| * AprSocketSource stdout link, to watch for incoming data, and | |
| * AprSocketSink stdin link, to pull for outgoing data. | |
| */ | |
| @Override | |
| public void connect(InetSocketAddress address) throws IOException { | |
| try { | |
| inetAddress = Address.info(address.getHostName(), Socket.APR_UNSPEC, address.getPort(), 0, pool); | |
| socket = Socket.create(Address.getInfo(inetAddress).family, Socket.SOCK_STREAM, Socket.APR_PROTO_TCP, pool); | |
| } catch (Exception e) { | |
| throw new IOException("[" + this + "] ERROR: Cannot create socket for \"" + address + "\".", e); | |
| } | |
| int ret = Socket.connect(socket, inetAddress); | |
| if (ret != 0) | |
| throw new IOException("[" + this + "] ERROR: Cannot connect to remote host \"" + address + "\": " + Error.strerror(ret)); | |
| source.setSocket(socket); | |
| sink.setSocket(socket); | |
| // Start polling for data to send to remote sever | |
| runMainLoop(IN, STDIN, true, true); | |
| // Push incoming data from server to handlers | |
| runMainLoop(OUT, STDOUT, false, false); | |
| } | |
| @Override | |
| public void handleEvent(Event event, Direction direction) { | |
| switch (event) { | |
| case SOCKET_UPGRADE_TO_SSL: | |
| upgradeToSsl(); | |
| break; | |
| default: | |
| super.handleEvent(event, direction); | |
| break; | |
| } | |
| } | |
| @Override | |
| public void validate() { | |
| if (getPads(Direction.IN).size() == 0) | |
| throw new RuntimeException("[ " + this + "] BUG: Input of socket is not connected."); | |
| if (getPads(Direction.OUT).size() == 0) | |
| throw new RuntimeException("[ " + this + "] BUG: Output of socket is not connected."); | |
| } | |
| @Override | |
| public void upgradeToSsl() { | |
| try { | |
| long sslContext; | |
| try { | |
| sslContext = SSLContext.make(pool, SSL.SSL_PROTOCOL_TLSV1, SSL.SSL_MODE_CLIENT); | |
| } catch (Exception e) { | |
| throw new RuntimeException("Cannot create SSL context using Tomcat native library.", e); | |
| } | |
| SSLContext.setOptions(sslContext, SSL.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS | SSL.SSL_OP_TLS_BLOCK_PADDING_BUG | SSL.SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER | |
| | SSL.SSL_OP_MSIE_SSLV2_RSA_PADDING); | |
| // FIXME: verify certificate by default | |
| SSLContext.setVerify(sslContext, SSL.SSL_CVERIFY_NONE, 0); | |
| int ret; | |
| try { | |
| ret = SSLSocket.attach(sslContext, socket); | |
| } catch (Exception e) { | |
| throw new RuntimeException("[" + this + "] ERROR: Cannot attach SSL context to socket: ", e); | |
| } | |
| if (ret != 0) | |
| throw new RuntimeException("[" + this + "] ERROR: Cannot attach SSL context to socket(" + ret + "): " + SSL.getLastError()); | |
| try { | |
| ret = SSLSocket.handshake(socket); | |
| } catch (Exception e) { | |
| throw new RuntimeException("[" + this + "] ERROR: Cannot make SSL handshake with server: ", e); | |
| } | |
| if (ret != 0 && ret != 20014) // 20014: bad certificate signature FIXME: show prompt for self signed certificate | |
| throw new RuntimeException("[" + this + "] ERROR: Cannot make SSL handshake with server(" + ret + "): " + SSL.getLastError()); | |
| try { | |
| byte[] key = SSLSocket.getInfoB(socket, SSL.SSL_INFO_CLIENT_CERT); | |
| //*DEBUG*/System.out.println("DEBUG: Server cert:\n"+new ByteBuffer(key).dump()); | |
| sslState.serverCertificateSubjectPublicKeyInfo = new X509CertImpl(key).getPublicKey().getEncoded(); | |
| } catch (Exception e) { | |
| throw new RuntimeException("[" + this + "] ERROR: Cannot get server public key: ", e); | |
| } | |
| } catch (RuntimeException e) { | |
| shutdown(); | |
| throw e; | |
| } | |
| } | |
| @Override | |
| public void shutdown() { | |
| if (shutdown) | |
| return; | |
| shutdown = true; | |
| try { | |
| handleEvent(Event.STREAM_CLOSE, Direction.IN); | |
| } catch (Exception e) { | |
| logger.info("[ignored]" | |
| + "handling stream close event failed on input: " + e.getLocalizedMessage()); | |
| } | |
| try { | |
| handleEvent(Event.STREAM_CLOSE, Direction.OUT); | |
| } catch (Exception e) { | |
| logger.info("[ignored]" | |
| + "handling event close event failed on output: " + e.getLocalizedMessage()); | |
| } | |
| } | |
| void destroyPull() { | |
| if (shutdowned) | |
| return; | |
| // Causes segfault in AprSocketSource.poll() method, so this function must be called from it | |
| try { | |
| Socket.close(socket); | |
| // or | |
| // Socket.shutdown(socket, Socket.APR_SHUTDOWN_READWRITE); | |
| Pool.destroy(pool); | |
| } catch (Exception e) { | |
| logger.info("[ignored]" | |
| + "failure during network cleanup: " + e.getLocalizedMessage()); | |
| } | |
| } | |
| @Override | |
| public String toString() { | |
| return "AprSocketWrapper(" + id + ")"; | |
| } | |
| /** | |
| * Example. | |
| */ | |
| public static void main(String args[]) { | |
| try { | |
| System.setProperty("streamer.Link.debug", "true"); | |
| System.setProperty("streamer.Element.debug", "true"); | |
| System.setProperty("rdpclient.MockServer.debug", "true"); | |
| Pipeline pipeline = new PipelineImpl("echo client"); | |
| AprSocketWrapperImpl socketWrapper = new AprSocketWrapperImpl("socket", null); | |
| pipeline.add(socketWrapper); | |
| pipeline.add(new BaseElement("echo")); | |
| pipeline.add(new Queue("queue")); // To decouple input and output | |
| pipeline.link("socket", "echo", "queue", "socket"); | |
| final byte[] mockData = new byte[] {0x01, 0x02, 0x03}; | |
| MockServer server = new MockServer(new Packet[] {new Packet("Server hello") { | |
| { | |
| type = SERVER; | |
| data = mockData; | |
| } | |
| }, new Packet("Client hello") { | |
| { | |
| type = CLIENT; | |
| data = mockData; | |
| } | |
| }, new Packet("Server hello") { | |
| { | |
| type = SERVER; | |
| data = mockData; | |
| } | |
| }, new Packet("Client hello") { | |
| { | |
| type = CLIENT; | |
| data = mockData; | |
| } | |
| }}); | |
| server.start(); | |
| InetSocketAddress address = server.getAddress(); | |
| socketWrapper.connect(address); | |
| } catch (Exception e) { | |
| e.printStackTrace(System.err); | |
| } | |
| } | |
| } |