package de.bwlehrpool.bwlp_guac; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.*; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.guacamole.form.Field; import org.apache.guacamole.net.auth.credentials.CredentialsInfo; import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException; import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException; import org.codehaus.jackson.map.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Broker for all clients available. * Keeps track of which clients are online, offline, assigned to a user etc. */ public class ConnectionManager { private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionManager.class); // TODO: Config private static final String SOURCE_URL = SlxConfig.clientListUrl(); private static final LinkedHashMap clientPool = new LinkedHashMap(); private static final LinkedHashMap groupPool = new LinkedHashMap(); public static LinkedHashMap getGroupPool() { return groupPool; } public static WrappedConnection getExistingConnection(String user) { AvailableClient freeClient = null; synchronized (clientPool) { for (AvailableClient ac : clientPool.values()) { LOGGER.info("Looking for existing mapping: Checking client " + ac); if (ac.isInUseBy(user)) { LOGGER.info("Client " + ac + " is in use by " + user); freeClient = ac; break; } } } if (freeClient != null && freeClient.checkConnection(1)) { LOGGER.info("Existing mapping for user " + user + " to " + freeClient); return freeClient.getConnection(user); } return null; } /** * Pass plain user name, get existing connection (if any), or a fresh one * @param user (LDAP) user name */ public static WrappedConnection getForUser(String user, int groupid) throws GuacamoleCredentialsException { if (SOURCE_URL == null) { LOGGER.warn("Don't have a source URL for client machines!"); return null; } try { updateList(); // Find existing/free client AvailableClient freeClient; for (; ; ) { freeClient = null; synchronized (clientPool) { Collection clients; if (groupid == 0) { clients = new HashSet(); for (JsonGroup group : groupPool.values()) { if (!group.hasPassword()) clients.addAll(group.clientList); } } else { JsonGroup group = groupPool.get(groupid); if (group == null) { // Request the user to select a group throw new GuacamoleInsufficientCredentialsException( "GROUP_SELECTION.TITLE", new CredentialsInfo( Collections.singletonList(new GroupField()) )); } clients = group.clientList; } for (AvailableClient ac : clients) { LOGGER.info("Looking for existing mapping: Checking client " + ac); if (ac.isInUseBy(user)) { LOGGER.info("Client " + ac + " is in use by " + user); freeClient = ac; break; } } if (freeClient == null) { for (AvailableClient ac : clients) { LOGGER.info("Looking for free client: Checking client " + ac); if (ac.claim(user)) { LOGGER.info("Claiming client " + ac + " for " + user); freeClient = ac; break; } } } } if (freeClient == null) { // Request the user to select another Group throw new GuacamoleCredentialsException( "GROUP_SELECTION.NO_FREE_ERROR", new CredentialsInfo( Collections.singletonList(new GroupField()) )); } // Found free or existing client, check if connection is (still) possible if (freeClient.checkConnection(0)) { LOGGER.info("Establishing mapping for user " + user + " to " + freeClient); return freeClient.getConnection(user); } else { LOGGER.info("Failed to establish mapping for user " + user + " to " + freeClient + ". Trying to find a new one."); } // Connection check failed - release and loop again freeClient.releaseConnection(user); } } catch (GuacamoleCredentialsException e) { throw e; } catch (Exception e) { LOGGER.warn("KACKE AM DAMPFEN", e); return null; } } private static long lastUpdate = 0; /** * Fetch fresh client list from satellite server. * Cache for 15 seconds. */ public static synchronized void updateList() { long now = System.currentTimeMillis(); if (now < lastUpdate) { lastUpdate = now; } if (now - lastUpdate < 15000) return; // OK GO lastUpdate = now; ByteArrayOutputStream baos = new ByteArrayOutputStream(3000); HttpURLConnection con; try { con = (HttpURLConnection) new URL(SOURCE_URL).openConnection(); } catch (MalformedURLException e1) { LOGGER.warn("Bad Connection Pool URL", e1); return; } catch (IOException e1) { LOGGER.warn("Cannot connect to Connection Pool URL", e1); return; } if (con instanceof HttpsURLConnection) { ((HttpsURLConnection) con).setHostnameVerifier(ignorer); ((HttpsURLConnection) con).setSSLSocketFactory(sockFac); } try (BufferedInputStream in = new BufferedInputStream(con.getInputStream())) { byte dataBuffer[] = new byte[2048]; int bytesRead; while ((bytesRead = in.read(dataBuffer, 0, dataBuffer.length)) != -1) { baos.write(dataBuffer, 0, bytesRead); } } catch (IOException e) { LOGGER.warn("Error while reading reply of Connection Pool", e); return; } populateList(baos.toByteArray()); } private static void populateList(byte[] data) { ObjectMapper mapper = new ObjectMapper(); JsonRoot root; try { root = mapper.readValue(data, JsonRoot.class); } catch (Exception e) { LOGGER.warn("Could not deserialize JSON from Connection Pool", e); LOGGER.warn("Not updating local list"); return; } // Temporary JsonGroup[] groups = root.locations; if (groups == null) { LOGGER.info("Group list null"); } synchronized (groupPool) { HashSet processedGroups = new HashSet(); for (JsonGroup gnew : groups) { if (gnew.locationids == null) continue; JsonGroup existing = groupPool.get(gnew.id); boolean redoClientMapping = false; if (existing == null) { groupPool.put(gnew.id, gnew); existing = gnew; redoClientMapping = true; } else { if (!Arrays.equals(existing.locationids, gnew.locationids)) { redoClientMapping = true; existing.locationids = gnew.locationids; } existing.name = gnew.name; existing.password = gnew.password; } if (redoClientMapping) { existing.clientList = new ArrayList(); for (AvailableClient client : clientPool.values()) { int locationid = client.getLocationid(); for (int id : existing.locationids) { if (id == locationid) { existing.clientList.add(client); break; } } } } processedGroups.add(existing); LOGGER.info("Group " + gnew.name + " with pw " + gnew.password); } for (Iterator it = groupPool.values().iterator(); it.hasNext();) { JsonGroup g = it.next(); if (!processedGroups.contains(g)) { for (AvailableClient client : g.clientList) { client.groupList.remove(g); } it.remove(); } } } if (root.clients == null) { LOGGER.info("Client list null"); } synchronized (clientPool) { for (JsonClient cnew : root.clients) { if (cnew.password == null || cnew.clientip == null) continue; // Invalid AvailableClient existing = clientPool.get(cnew.clientip); if (existing == null) { // New client existing = new AvailableClient(cnew); clientPool.put(cnew.clientip, existing); for (JsonGroup group : groupPool.values()) { for (int id : group.locationids) { if (id == cnew.locationid) { group.clientList.add(existing); existing.groupList.add(group); break; } } } LOGGER.info("New client " + cnew.clientip); } else { if (existing.getLocationid() != cnew.locationid) { for (JsonGroup group : groupPool.values()) { for (int id : group.locationids) { if (id == existing.getLocationid()) { group.clientList.remove(existing); existing.groupList.remove(group); } if (id == cnew.locationid) { group.clientList.add(existing); existing.groupList.add(group); } } } } existing.update(cnew); } } final long NOW = System.currentTimeMillis(); for (Iterator it = clientPool.values().iterator(); it.hasNext();) { AvailableClient c = it.next(); if (c.isTimeout(NOW)) { LOGGER.info("Removing client " + c + " from list"); for (JsonGroup group : c.groupList) { group.clientList.remove(c); } it.remove(); } } LOGGER.info("List updated. " + clientPool.size() + " clients."); } } /* * Make SSL insecure */ static { SSLContext ctx = null; try { ctx = SSLContext.getInstance("TLSv1.2"); } catch (NoSuchAlgorithmException e) { LOGGER.warn("Could not get TLSv1.2 context, SSL will be secure :-(", e); } if (ctx != null) { try { ctx.init(null, new TrustManager[] { new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } } }, null); } catch (KeyManagementException e) { LOGGER.warn("Could not initialize TLSv1.2 SSL context", e); ctx = null; } } if (ctx == null) { sockFac = null; } else { sockFac = ctx.getSocketFactory(); } } private static final SSLSocketFactory sockFac; private static final HostnameVerifier ignorer = new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }; }