summaryrefslogblamecommitdiffstats
path: root/src/main/java/de/bwlehrpool/bwlp_guac/ConnectionManager.java
blob: 1f7d9286e671ca52e67bc8cb7ee75c53dbaf3f94 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12











                                               







                                                  








                                        


                                                                               

                                                                                           









                                                                            
 

                                                                                              

                                                                           


                                                   

                                                                                                                              


                                                   
                                                                                                                   
 



















                                                                                          











                                                                   




                                                                            
                                                                                                    












                                                                                               
 



                                                                                 
                                                                            
                                                              







                                                                                    
                                    

                                                           
                                                                            
                                                           
                                                                                         

                                                                                                                   

                                                 


                                                                                             

                                                                                                                                              
                                                                                                                          

                                                           
                                                                           


                                                                            
                                                                                                                    
                                                                         
                                                                                                              




                                                                        
                                                                                    
                                                                                                                      
                                                                             
                                                                                                                      





                                                                                
                                                         
                                                                                   

                                                                                                                                              
                                                                                                          

                                           



                                                                                                                   

                                                                                                                                                          



                                                                                   

                                                           











                                                           
                                                      






















                                                                                       

                                                                                                                                                   














                                                                                               
                              
                     
                                                                      




                                                                                          
 






                                                                         
                 

                                           
                                                                                      
                                                               

                                                             
                                                                            

                                                                  

                                                                     
                                                                 
                                                                                                        
                                        



                                                                                                     

                                                                          

                                                        
                                                                                                           









                                                                                                
                                                              
                         


                                                                                                     

                                                    


                         
                                           
                                                              




                                                                                         

                                                                                
 

                                                                                    
                                                                                    
                                                                                               



                                                                      

                                                                                   




                                                                                                          


                                                                                                       



                                                                 







                                                                                                            
                                                                                    
                                                                           
                                         

                                                    

























































                                                                                                                
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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

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.language.TranslatableGuacamoleCredentialsException;
import org.apache.guacamole.language.TranslatableGuacamoleInsufficientCredentialsException;
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);

	private static final String SOURCE_URL = SlxConfig.clientListUrl();

	/**
	 * synchronize on clientPool when accessing
	 */
	private static final LinkedHashMap<String, AvailableClient> clientPool = new LinkedHashMap<String, AvailableClient>();

	/**
	 * synchronize on clientPool when accessing
	 */
	private static final LinkedHashMap<Integer, JsonGroup> groupPool = new LinkedHashMap<Integer, JsonGroup>();

	private static final Thread listUpdater;
	
	static {
		listUpdater = new Thread() {
			@Override
			public void run() {
				for (;;) {
					try {
						Thread.sleep(120000);
						updateList();
					} catch (InterruptedException e) {
						LOGGER.info("Stopping list fetch thread");
						return;
					}
				}
			}
		};
		listUpdater.setDaemon(true);
		listUpdater.start();
	}
	
	public static JsonGroup getGroup(int id) {
		synchronized (clientPool) {
			return groupPool.get(id);
		}
	}

	public static Collection<JsonGroup> getGroupList() {
		synchronized (clientPool) {
			return new ArrayList<>(groupPool.values());
		}
	}
	
	public static WrappedConnection getExistingConnection(String user) {
		AvailableClient freeClient = null;
		synchronized (clientPool) {
			for (AvailableClient ac : clientPool.values()) {
				LOGGER.debug("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<AvailableClient> clients;
					if (groupid == 0) {
						clients = new HashSet<AvailableClient>();
						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 TranslatableGuacamoleInsufficientCredentialsException(
									"GROUP_SELECTION.TITLE", "GROUP_SELECTION.TITLE", new CredentialsInfo(
									Collections.<Field>singletonList(new GroupField())
							));
						}
						clients = group.clientList;
					}

					for (AvailableClient ac : clients) {
						LOGGER.debug("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 TranslatableGuacamoleCredentialsException(
							"GROUP_SELECTION.NO_FREE_ERROR", "GROUP_SELECTION.NO_FREE_ERROR", new CredentialsInfo(
							Collections.<Field>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);
		}
		con.setRequestProperty("Bwlp-Plugin-Build-Revision", BwlpAuthenticationProvider.MANIFEST.getProperty("Build-Revision"));
		con.setRequestProperty("Bwlp-Plugin-Build-Timestamp", BwlpAuthenticationProvider.MANIFEST.getProperty("Build-Revision-Timestamp"));
		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;
		}

		if (root.locations == null) {
			LOGGER.info("META DATA ERROR: Group list null");
			return;
		}
		if (root.clients == null) {
			LOGGER.info("META DATA ERROR: Client list null");
			return;
		}
		
		synchronized (clientPool) {
			HashSet<JsonGroup> processedGroups = new HashSet<JsonGroup>();
			for (JsonGroup gnew : root.locations) {
				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;
					LOGGER.info("Group " + gnew.name + " with pw " + gnew.password);
				} 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 ConcurrentLinkedQueue<AvailableClient>();
					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);
			}
			for (Iterator<JsonGroup> it = groupPool.values().iterator(); it.hasNext();) {
				JsonGroup g = it.next();
				if (!processedGroups.contains(g)) {
					it.remove();
				}
			}
		}

		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);
								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);
								}
								if (id == cnew.locationid) {
									group.clientList.add(existing);
								}
							}
						}
					}
					existing.update(cnew);
				}
			}
			final long NOW = System.currentTimeMillis();
			for (Iterator<AvailableClient> it = clientPool.values().iterator(); it.hasNext();) {
				AvailableClient c = it.next();
				if (c.isTimeout(NOW)) {
					LOGGER.info("Removing client " + c + " from list");
					for (JsonGroup group : groupPool.values()) {
						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;
		}
	};

}