summaryrefslogblamecommitdiffstats
path: root/src/main/java/de/bwlehrpool/bwlp_guac/AvailableClient.java
blob: 664bc3f19bfb1cd8e5304904a7ee36e26cd09cb4 (plain) (tree)
1
2
3
4
5
6
7
8
9

                                

                       
                                         
                           
                        

                                              
                                                 





                                                            
 
                                                   




                                                                                            
                                                                           
 



                                      

                               



                               

                                             









                                                          
                                                    

                               



                                                  






                                                                                        






                                                                                                                        


                                                                                      
                                                                                              
                                                            
                                                                                                  




                                                        



                                                                       









                                                                                                              








                                                                                   




                                                                                                                              




                                                                          
                                                                                  

                                                                            

                                                                                                                



















                                                                                   







                                    



                                           


















                                                                                    
                                                                 

                                                         
                                                      
                                                                                                  
                                                                  




                                                                                                                     
                                                                          
                                                                                                
                                                                                                      


                                                                                
                                                                                                        











                                                                                           
                                                                              



                                                                                                                                      
 




                                                                                          
                                                                                      




                                                                       
                                                                                                      

                                                                                                       
                                       





                                                                                               







                                                                       

 
package de.bwlehrpool.bwlp_guac;

import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.protocol.GuacamoleConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.bwlehrpool.bwlp_guac.JsonClient.State;


public class AvailableClient implements Cloneable {

	private static final Logger LOGGER = LoggerFactory.getLogger(AvailableClient.class);

	private static final AtomicLong CON_ID = new AtomicLong();

	public ArrayList<JsonGroup> groupList = new ArrayList<JsonGroup>();

	private final String clientip;

	private String password;

	private int locationid;

	private State state;

	private String inUseBy;

	private WrappedConnection connection;

	private long deadline;

	private long lastConnectionCheck;

	private boolean connectionOk;

	private final Object connCheckLock = new Object();

	public AvailableClient(JsonClient source) {
		this.clientip = source.clientip;
		this.locationid = source.locationid;
		update(source);
	}
	
	private AvailableClient(String clientip) {
		this.clientip = clientip;
	}

	/**
	 * Update this client's state, resetting "in use" if appropriate. Ie, the
	 * password changed, which would mean the remote VNC server was restarted, so it
	 * can't possibly be in use by the old user anymore.
	 */
	public synchronized void update(JsonClient source) {
		if (this.inUseBy != null && source.state != State.OCCUPIED && !TunnelListener.hasTunnel(this.inUseBy)) {
			LOGGER.info("Free client blocked by a disconnected user detected.");
			LOGGER.info("Client " + this + " is available again");
			this.inUseBy = null;
			if (this.connection != null) this.connection.invalidate();
		}

		if (this.password == null || !this.password.equals(source.password)) {
			if (source.state != State.OCCUPIED) {
				if (this.inUseBy != null) {
					LOGGER.info("Client " + this + " is available again");
					this.inUseBy = null;
					if (this.connection != null) this.connection.invalidate();
				}
			}
			this.lastConnectionCheck = 0;
			this.password = source.password;
		}

		if (this.inUseBy == null || source.state != State.IDLE)
			this.state = source.state;

		this.deadline = 0;
	}

	/**
	 * Try to reserve this client for the given user.
	 */
	public synchronized boolean claim(String user) {
		if (this.inUseBy != null || this.password == null || this.state != State.IDLE || user == null)
			return false;
		this.inUseBy = user;
		this.state = State.OCCUPIED;
		return true;
	}

	public synchronized boolean isInUseBy(String user) {
		return (this.inUseBy != null && this.inUseBy.equals(user));
	}

	public synchronized WrappedConnection getConnection(String expectedOwner) {
		if (isInUseBy(expectedOwner)) {
			if (this.connection == null || !this.connection.isValid())
				this.connection = new WrappedConnection(this.clientip + "/" + CON_ID.incrementAndGet(), this);
			return this.connection;
		}
		return null;
	}

	public synchronized void releaseConnection(String expectedOwner) {
		if (isInUseBy(expectedOwner)) {
			if (this.connection != null) this.connection.invalidate();
			LOGGER.info("Prematurely releasing client " + this);
			this.inUseBy = null;
		} else {
			LOGGER.info("Could not release client " + this + ". Already in use by " + this.inUseBy);
		}
	}

	/**
	 * If this connection is not returned by the sat server anymore, we keep it
	 * around for another 5 minutes just in case.
	 */
	public boolean isTimeout(long NOW) {
		if (deadline == 0) {
			deadline = NOW + 300000;
			return false;
		}
		return deadline < NOW;
	}

	@Override
	public String toString() {
		return clientip + "/" + state + "/" + inUseBy;
	}

	public State getState() {
		return state;
	}

	public int getLocationid() {
		return locationid;
	}

	public void markAsMissing() {
		this.state = State.OFFLINE;
	}

	public GuacamoleConfiguration toGuacConfig() {
		GuacamoleConfiguration cfg = new GuacamoleConfiguration();
		cfg.setProtocol("vnc");
		cfg.setParameter("hostname", this.clientip);
		cfg.setParameter("port", Integer.toString(5900)); // TODO
		cfg.setParameter("password", password);
		return cfg;
	}

	/**
	 * Check if the given VNC credentials actually work, so we avoid assigning a
	 * bogus entry to a user.
	 */
	public boolean checkConnection(int retries) {
		long now = System.currentTimeMillis();
		synchronized (connCheckLock) {
			if (now < this.lastConnectionCheck) {
				this.lastConnectionCheck = 0;
			}
			if (now - this.lastConnectionCheck < 750)
				return this.connectionOk;
			for (;;) {
				String version = null;
				try (VncConnection vnc = new VncConnection(this.clientip, 5900)) {
					version = vnc.handshake();
					if (version == null) {
						LOGGER.info("Host " + this.clientip + " doesn't speak RFB protocol");
						break;
					}
					LOGGER.debug("VNC Version for " + this.clientip + " is " + version);
					if (vnc.tryLogin(this.password)) {
						LOGGER.info("Connection to " + this + " is OK");
						this.lastConnectionCheck = System.currentTimeMillis();
						return this.connectionOk = true;
					}
				} catch (IOException e) {
					LOGGER.info("Connection error VNC (" + version + ") @ " + this);
					if (retries-- > 0) {
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e1) {
							Thread.currentThread().interrupt();
							break;
						}
						continue;
					}
				}
				break;
			}
			this.lastConnectionCheck = System.currentTimeMillis();
			this.password = null; // Render invalid, so ConnectionManager::getForUser() doesn't turn into an infinite loop
			return this.connectionOk = false;
		}
	}

	public void remoteLogin(Credentials credentials, String resolution) {
		String username = credentials.getUsername();
		try {
			LOGGER.info("Logging in user " + username + " on client " + this);

			Socket socket = new Socket(this.clientip, 7551); // TODO Port?
			OutputStream output = socket.getOutputStream();

			int version = 1;
			output.write(version & 0xFF);
			output.write(version >> 8);
			String data = username + "\n" + credentials.getPassword() + "\n" + resolution;
			byte[] enc = Base64.getEncoder().encode(data.getBytes(StandardCharsets.UTF_8));
			output.write(enc);
			output.flush();
			socket.close();
		} catch (IOException e) {
			LOGGER.warn("Login failed. User: " + username + " Client: " + this, e);
		}
	}

	@Override
	public AvailableClient clone() {
		AvailableClient c = new AvailableClient(this.clientip);
		c.state = this.state;
		c.inUseBy = this.inUseBy;
		c.password = this.password;
		return c;
	}

}