/* This file is part of leafdigital leafChat. leafChat is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. leafChat is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with leafChat. If not, see <http://www.gnu.org/licenses/>. Copyright 2011 Samuel Marshall. */ package com.leafdigital.net; import java.io.IOException; import java.net.*; import java.security.*; import java.util.*; import javax.net.ssl.*; import net.sbbi.upnp.impls.InternetGatewayDevice; import com.leafdigital.net.api.Network; import leafchat.core.api.PluginContext; /** Implements the Network API with support for UPnP and SOCKS proxies */ public class NetworkSingleton implements Network { private PluginContext context; private TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkClientTrusted( java.security.cert.X509Certificate[] certs, String authType) { } @Override public void checkServerTrusted( java.security.cert.X509Certificate[] certs, String authType) { } } }; /** * @param context Plugin context */ public NetworkSingleton(PluginContext context) { this.context = context; } @Override public Socket connect(String host, int port, int timeout) throws IOException { return connect(host, port, timeout, SECURE_NONE); } @Override public Socket connect(String host, int port, int timeout, int secureMode) throws IOException { NetPlugin np=(NetPlugin)context.getPlugin(); if(np.getConnectionType()==NetPlugin.CONNECTION_SOCKS5) { if(secureMode == SECURE_REQUIRED) { throw new IOException( "Secure connections are not supported via SOCKS proxy"); } SOCKS5Socket s=new SOCKS5Socket(host,port,context,timeout); np.setReportedAddress(s.getProxyExternalAddress(),false); return s; } else { Socket s = null; if(secureMode != SECURE_NONE) { context.logDebug("Attempting secure connection"); try { SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, trustAllCerts, new SecureRandom()); s = sslContext.getSocketFactory().createSocket(); // Java claims to support Diffie-Hellman key exchange, but does not // really, because everyone uses 2048-bit keys but Java only supports // up to 1024. This causes negotiation to fail (which is stupid). // Java bug 6521495. To work around it, I disable cipher suites that // use DH exchange. Connection will still fail if a server ONLY // supports DH, but I didn't find any in that position (yet). // Note: this code should be removed if Oracle ever fix the Java bug. List<String> limited = new LinkedList<String>(); for(String suite : ((SSLSocket)s).getEnabledCipherSuites()) { if(!suite.contains("_DHE_")) { limited.add(suite); } } ((SSLSocket)s).setEnabledCipherSuites(limited.toArray(new String[limited.size()])); s.connect(new InetSocketAddress(host,port),timeout); s.setSoTimeout(0); ((SSLSocket)s).startHandshake(); if(((SSLSocket)s).getSession().getCipherSuite().equals( "SSL_NULL_WITH_NULL_NULL")) { throw new IOException("Failed secure connection"); } else { context.logDebug("Secure connection OK"); } } catch(Exception e) { e.printStackTrace(); context.logDebug("Secure connection failed"); try { if(s != null) { s.close(); } } catch(IOException e2) { // Ignore close errors } s = null; if(secureMode == SECURE_REQUIRED) { throw new IOException( "Secure connection requested but not available"); } // Delay avoids server restrictions on fast reconnect try { Thread.sleep(20000); } catch(InterruptedException ie) { } } } if(s == null) { context.logDebug("Using standard (insecure) connection"); s = new Socket(); s.connect(new InetSocketAddress(host,port),timeout); s.setSoTimeout(0); } np.setReportedAddress(s.getLocalAddress(),false); return s; } } @Override public boolean needsListenTarget() { NetPlugin np=(NetPlugin)context.getPlugin(); // SOCKS proxy requires listening target return np.getConnectionType()==NetPlugin.CONNECTION_SOCKS5; } @Override public Network.Port listen(String remoteHost) throws IOException { // If not using SOCKS proxy, just do the normal version if(!needsListenTarget()) return listen(); return new SOCKS5Port(remoteHost,context); } @Override public Network.Port listen() throws IOException { if(needsListenTarget()) { throw new IOException( "Cannot open ports via SOCKS proxy without remote target"); } NetPlugin np=(NetPlugin)context.getPlugin(); if(np.getConnectionType()==NetPlugin.CONNECTION_UPNP) { InternetGatewayDevice gateway=(np).getUPnPGateway(); UPnPServerSocket socket=new UPnPServerSocket(context,gateway); return new ServerSocketWrapper(socket, InetAddress.getByName(socket.getExternalAddress()), socket.getExternalPort()); } else { DirectServerSocket socket=new DirectServerSocket(context); return new ServerSocketWrapper(socket, InetAddress.getByName(socket.getExternalAddress()), socket.getLocalPort()); } } @Override public void reportPublicAddress(InetAddress ia) { NetPlugin np=(NetPlugin)context.getPlugin(); np.setReportedAddress(ia,true); } @Override public InetAddress getPublicAddress() { NetPlugin np=(NetPlugin)context.getPlugin(); if(np.getConnectionType()==NetPlugin.CONNECTION_SOCKS5) return null; else return np.getReportedAddress(); } }