blob: b498072f7fdc8df742180ccd1439963bda23351a (
plain) (
tree)
|
|
package de.bwlehrpool.bwlp_guac;
import java.io.*;
import java.net.InetSocketAddress;
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(); // TODO Port?
socket.connect(new InetSocketAddress(this.clientip, 7551), 1100);
socket.setSoTimeout(1000);
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;
}
}
|