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<String, AvailableClient> clientPool = new LinkedHashMap<String, AvailableClient>();
private static final LinkedHashMap<Integer, JsonGroup> groupPool = new LinkedHashMap<Integer, JsonGroup>();
public static LinkedHashMap<Integer, JsonGroup> 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<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 GuacamoleInsufficientCredentialsException(
"Select Location", new CredentialsInfo(
Collections.<Field>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(
"No free client. Select another Location.", 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);
}
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<JsonGroup> processedGroups = new HashSet<JsonGroup>();
for (JsonGroup gnew : groups) {
JsonGroup existing = groupPool.get(gnew.id);
boolean redoClientMapping = false;
if (existing == null) {
groupPool.put(gnew.id, gnew);
existing = gnew;
redoClientMapping = true;
} else {
if (existing.locationids != gnew.locationids) redoClientMapping = true;
existing.locationids = gnew.locationids;
existing.name = gnew.name;
existing.password = gnew.password;
}
if (redoClientMapping) {
existing.clientList = new ArrayList<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);
LOGGER.info("Group " + gnew.name + " with pw " + gnew.password);
}
for (Iterator<JsonGroup> 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) {
HashSet<AvailableClient> processedClients = new HashSet<AvailableClient>();
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);
}
processedClients.add(existing);
}
final long NOW = System.currentTimeMillis();
for (Iterator<AvailableClient> it = clientPool.values().iterator(); it.hasNext();) {
AvailableClient c = it.next();
if (!processedClients.contains(c)) {
c.markAsMissing();
}
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;
}
};
}