package org.openslx.dozmod.util; import java.io.IOException; import java.net.Proxy; import java.net.ProxySelector; import java.net.Socket; import java.text.MessageFormat; import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner; import org.apache.hc.client5.http.socket.ConnectionSocketFactory; import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.config.RegistryBuilder; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.ssl.TLS; import org.apache.hc.core5.util.Timeout; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.openslx.bwlp.thrift.iface.MasterServer; import org.openslx.dozmod.App; import org.openslx.dozmod.authentication.ShibbolethEcp; import org.openslx.thrifthelper.ThriftManager; import org.openslx.util.Util; import com.btr.proxy.search.ProxySearch; import com.btr.proxy.search.wpad.WpadProxySearchStrategy; import com.btr.proxy.util.Logger.LogBackEnd; import com.btr.proxy.util.Logger.LogLevel; /** * Configures the proxy * * @author Jonathan Bauer */ public class ProxyConfigurator { /** * Logger for this class */ private final static Logger LOGGER = LogManager.getLogger(ProxyConfigurator.class); private static AtomicReference apacheClient = new AtomicReference<>(); private static final TLS[] SUPPORTED_TLS_VERSIONS = { TLS.V_1_3, TLS.V_1_2, TLS.V_1_1 }; private static final Timeout TIMEOUT_CONNECT = Timeout.ofSeconds(8); private static final Timeout TIMEOUT_SOCKET = Timeout.ofSeconds(8); private static final Timeout TIMEOUT_REQUEST = Timeout.ofSeconds(3); /** * Initialization method. */ public static void init() { MasterServer.Client masterClient = ThriftManager.getNewMasterClient(null, App.getMasterServerAddress(), App.THRIFT_PORT, 4000); if (masterClient != null) { try { masterClient.ping(); try { masterClient.getInputProtocol().getTransport().close(); masterClient.getOutputProtocol().getTransport().close(); } catch (Throwable e) { } } catch (Exception e) { masterClient = null; } } // To be discussed: Let's bail out if the master server is reachable if (masterClient != null && testHttpsMaster()) { LOGGER.info("Not setting up proxy because master server seems reachable."); return; } // Build special HTTP Client Builder for apache and try again apacheClient.set(createSlxBuilder().build()); if (masterClient != null && testHttpsMaster()) { LOGGER.info("Not setting up proxy since master server is reachable with custom client builder"); return; } // first setup the logger of proxy-vole com.btr.proxy.util.Logger.setBackend(new LogBackEnd() { public void log(Class clazz, LogLevel loglevel, String msg, Object... params) { Level priority; switch (loglevel) { case ERROR: priority = Level.ERROR; break; case WARNING: priority = Level.WARN; break; case INFO: priority = Level.INFO; break; default: priority = Level.DEBUG; } LogManager.getLogger(clazz).log(priority, MessageFormat.format(msg, params)); } public boolean isLogginEnabled(LogLevel logLevel) { return true; } }); LOGGER.info("Master server not directly reachable; trying to determine proxy"); // try to find local proxy settings ProxySearch proxySearch = ProxySearch.getDefaultProxySearch(); ProxySelector myProxySelector = proxySearch.getProxySelector(); if (myProxySelector == null) { // didn't work, try WPAD detection LOGGER.error("No suitable proxy settings found, trying WPAD..."); WpadProxySearchStrategy ss = new WpadProxySearchStrategy(); myProxySelector = ss.getProxySelector(); } // final check to see if WPAD actually worked if (myProxySelector != null) { ProxySelector.setDefault(myProxySelector); LOGGER.debug("Proxy initialised."); Util.sleep(10); } else { LOGGER.error("Could not find a suitable proxy!"); } } /** * Get the HttpClient to be used with apache * httpclient. * * @return client */ public static CloseableHttpClient getClient() { CloseableHttpClient inst = apacheClient.get(); if (inst != null) return inst; inst = createShortTimeoutBuilder().build(); apacheClient.compareAndSet(null, inst); return inst; } private static HttpClientBuilder createShortTimeoutBuilder() { return HttpClientBuilder.create() .setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create() .setSSLSocketFactory(SSLConnectionSocketFactoryBuilder.create() .setTlsVersions(ProxyConfigurator.SUPPORTED_TLS_VERSIONS) .build()) .setDefaultTlsConfig(TlsConfig.custom() .setSupportedProtocols(ProxyConfigurator.SUPPORTED_TLS_VERSIONS) .build()) .setDefaultConnectionConfig(ConnectionConfig.custom() .setConnectTimeout(ProxyConfigurator.TIMEOUT_CONNECT) .setSocketTimeout(ProxyConfigurator.TIMEOUT_SOCKET) .build()) .setMaxConnPerRoute(4) .build()); } private static HttpClientBuilder createSlxBuilder() { final RegistryBuilder registryBuilder = RegistryBuilder.create() .register(URIScheme.HTTP.id, SlxSocketFactory.getSocketFactory()) .register(URIScheme.HTTPS.id, SSLConnectionSocketFactoryBuilder.create() .setTlsVersions(ProxyConfigurator.SUPPORTED_TLS_VERSIONS) .build()); final PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registryBuilder.build()); connectionManager.setDefaultTlsConfig(TlsConfig.custom() .setSupportedProtocols(ProxyConfigurator.SUPPORTED_TLS_VERSIONS) .build()); connectionManager.setDefaultConnectionConfig(ConnectionConfig.custom() .setConnectTimeout(ProxyConfigurator.TIMEOUT_CONNECT) .setSocketTimeout(ProxyConfigurator.TIMEOUT_SOCKET) .build()); connectionManager.setDefaultMaxPerRoute(4); return HttpClientBuilder.create() .setRoutePlanner(new SlxRoutePlanner(null)) .setConnectionManager(connectionManager); } private static boolean testHttpsMaster() { final RequestConfig requestConfig = RequestConfig.custom() .setConnectionRequestTimeout(ProxyConfigurator.TIMEOUT_REQUEST) .build(); HttpGet httpGet = new HttpGet(ShibbolethEcp.BWLP_SP.toString()); httpGet.setConfig(requestConfig); httpGet.setHeader("Accept", "text/html, application/vnd.paos+xml"); httpGet.setHeader("PAOS", "ver=\"urn:liberty:paos:2003-08\";\"urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp\""); try { HttpResponse response = getClient().execute(httpGet); LOGGER.debug("Master server replies with " + response.getCode()); return response.getCode() == 200; } catch (Exception e) { LOGGER.debug("Cannot reach master server via HTTPS", e); return false; } } private static class SlxSocketFactory extends PlainConnectionSocketFactory { public static final SlxSocketFactory INSTANCE = new SlxSocketFactory(); public static SlxSocketFactory getSocketFactory() { return INSTANCE; } @Override public Socket createSocket(HttpContext context) throws IOException { Object obj = context.getAttribute("openslx.l7proxy"); if (obj instanceof HttpRoute) { HttpRoute route = (HttpRoute) obj; if (route != null && route.getProxyHost() != null) { return new Socket(Proxy.NO_PROXY); } } return new Socket(); } } private static class SlxRoutePlanner extends SystemDefaultRoutePlanner { public SlxRoutePlanner(ProxySelector proxySelector) { super(proxySelector); } @Override public HttpHost determineProxy(final HttpHost target, final HttpContext context) throws HttpException { HttpHost host = super.determineProxy(target, context); context.setAttribute("openslx.l7proxy", host); return host; } } }