From 93a9e8a41a6e4847e05b5acac7d4c3c08bc38107 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 8 Dec 2010 13:14:04 -0800 Subject: [PATCH] More renaming --- guacamole-common/pom.xml | 57 ++++ .../net/sourceforge/guacamole/Client.java | 30 ++ .../guacamole/GuacamoleClient.java | 129 +++++++ .../guacamole/GuacamoleException.java | 36 ++ .../guacamole/net/Configuration.java | 129 +++++++ .../guacamole/net/GuacamoleConfiguration.java | 80 +++++ .../guacamole/net/GuacamoleProperties.java | 55 +++ .../guacamole/net/GuacamoleServlet.java | 83 +++++ .../guacamole/net/GuacamoleSession.java | 209 ++++++++++++ .../GuacamoleSessionProvider.java | 30 ++ .../NullGuacamoleSessionProvider.java | 32 ++ .../BasicFileAuthenticationProvider.java | 318 ++++++++++++++++++ .../basic/BasicGuacamoleSessionProvider.java | 50 +++ .../net/authentication/basic/BasicLogin.java | 161 +++++++++ .../guacamole/net/tunnel/Connect.java | 59 ++++ .../guacamole/net/tunnel/Inbound.java | 59 ++++ .../guacamole/net/tunnel/Outbound.java | 99 ++++++ 17 files changed, 1616 insertions(+) create mode 100644 guacamole-common/pom.xml create mode 100644 guacamole-common/src/main/java/net/sourceforge/guacamole/Client.java create mode 100644 guacamole-common/src/main/java/net/sourceforge/guacamole/GuacamoleClient.java create mode 100644 guacamole-common/src/main/java/net/sourceforge/guacamole/GuacamoleException.java create mode 100644 guacamole-common/src/main/java/net/sourceforge/guacamole/net/Configuration.java create mode 100644 guacamole-common/src/main/java/net/sourceforge/guacamole/net/GuacamoleConfiguration.java create mode 100644 guacamole-common/src/main/java/net/sourceforge/guacamole/net/GuacamoleProperties.java create mode 100644 guacamole-common/src/main/java/net/sourceforge/guacamole/net/GuacamoleServlet.java create mode 100644 guacamole-common/src/main/java/net/sourceforge/guacamole/net/GuacamoleSession.java create mode 100644 guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/GuacamoleSessionProvider.java create mode 100644 guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/NullGuacamoleSessionProvider.java create mode 100644 guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/basic/BasicFileAuthenticationProvider.java create mode 100644 guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/basic/BasicGuacamoleSessionProvider.java create mode 100644 guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/basic/BasicLogin.java create mode 100644 guacamole-common/src/main/java/net/sourceforge/guacamole/net/tunnel/Connect.java create mode 100644 guacamole-common/src/main/java/net/sourceforge/guacamole/net/tunnel/Inbound.java create mode 100644 guacamole-common/src/main/java/net/sourceforge/guacamole/net/tunnel/Outbound.java diff --git a/guacamole-common/pom.xml b/guacamole-common/pom.xml new file mode 100644 index 000000000..70ee49b8c --- /dev/null +++ b/guacamole-common/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + net.sourceforge.guacamole + guacamole-common + jar + 0.3.0rc1 + guacamole-common + http://guacamole.sourceforge.net/ + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + + + + + + + + org.apache.maven.wagon + wagon-ssh-external + + + + + + + + javax.servlet + servlet-api + 2.5 + provided + + + + + + guac-dev + http://guac-dev.org/repo + + + + + + guac-dev + scpexe://guac-dev.org/var/www/repo + + + + diff --git a/guacamole-common/src/main/java/net/sourceforge/guacamole/Client.java b/guacamole-common/src/main/java/net/sourceforge/guacamole/Client.java new file mode 100644 index 000000000..e57d479a0 --- /dev/null +++ b/guacamole-common/src/main/java/net/sourceforge/guacamole/Client.java @@ -0,0 +1,30 @@ + +package net.sourceforge.guacamole; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import net.sourceforge.guacamole.GuacamoleException; + +public abstract class Client { + + public abstract void write(char[] chunk, int off, int len) throws GuacamoleException; + public abstract char[] read() throws GuacamoleException; + public abstract void disconnect() throws GuacamoleException; + +} diff --git a/guacamole-common/src/main/java/net/sourceforge/guacamole/GuacamoleClient.java b/guacamole-common/src/main/java/net/sourceforge/guacamole/GuacamoleClient.java new file mode 100644 index 000000000..f0748d8df --- /dev/null +++ b/guacamole-common/src/main/java/net/sourceforge/guacamole/GuacamoleClient.java @@ -0,0 +1,129 @@ + +package net.sourceforge.guacamole; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; + +import java.io.InputStream; +import java.io.Reader; +import java.io.InputStreamReader; + +import java.io.OutputStream; +import java.io.Writer; +import java.io.OutputStreamWriter; + +import net.sourceforge.guacamole.GuacamoleException; + +public class GuacamoleClient extends Client { + + private Socket sock; + private Reader input; + private Writer output; + + public GuacamoleClient(String hostname, int port) throws GuacamoleException { + + try { + sock = new Socket(InetAddress.getByName(hostname), port); + input = new InputStreamReader(sock.getInputStream()); + output = new OutputStreamWriter(sock.getOutputStream()); + } + catch (IOException e) { + throw new GuacamoleException(e); + } + + } + + public void write(char[] chunk, int off, int len) throws GuacamoleException { + try { + output.write(chunk, off, len); + output.flush(); + } + catch (IOException e) { + throw new GuacamoleException(e); + } + } + + public void disconnect() throws GuacamoleException { + try { + sock.close(); + } + catch (IOException e) { + throw new GuacamoleException(e); + } + } + + private int usedLength = 0; + private char[] buffer = new char[20000]; + + public char[] read() throws GuacamoleException { + + try { + + // While we're blocking, or input is available + for (;;) { + + // If past threshold, resize buffer before reading + if (usedLength > buffer.length/2) { + char[] biggerBuffer = new char[buffer.length*2]; + System.arraycopy(buffer, 0, biggerBuffer, 0, usedLength); + buffer = biggerBuffer; + } + + // Attempt to fill buffer + int numRead = input.read(buffer, usedLength, buffer.length - usedLength); + if (numRead == -1) + return null; + + int prevLength = usedLength; + usedLength += numRead; + + for (int i=usedLength-1; i>=prevLength; i--) { + + char readChar = buffer[i]; + + // If end of instruction, return it. + if (readChar == ';') { + + // Get instruction + char[] chunk = new char[i+1]; + System.arraycopy(buffer, 0, chunk, 0, i+1); + + // Reset buffer + usedLength -= i+1; + System.arraycopy(buffer, i+1, buffer, 0, usedLength); + + // Return instruction string + return chunk; + } + + } + + } // End read loop + + } + catch (IOException e) { + throw new GuacamoleException(e); + } + + } + +} diff --git a/guacamole-common/src/main/java/net/sourceforge/guacamole/GuacamoleException.java b/guacamole-common/src/main/java/net/sourceforge/guacamole/GuacamoleException.java new file mode 100644 index 000000000..3a09c5ac7 --- /dev/null +++ b/guacamole-common/src/main/java/net/sourceforge/guacamole/GuacamoleException.java @@ -0,0 +1,36 @@ + +package net.sourceforge.guacamole; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +public class GuacamoleException extends Exception { + + public GuacamoleException(String message, Throwable cause) { + super(message, cause); + } + + public GuacamoleException(String message) { + super(message); + } + + public GuacamoleException(Throwable cause) { + super(cause); + } + +} diff --git a/guacamole-common/src/main/java/net/sourceforge/guacamole/net/Configuration.java b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/Configuration.java new file mode 100644 index 000000000..6e78eb296 --- /dev/null +++ b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/Configuration.java @@ -0,0 +1,129 @@ + +package net.sourceforge.guacamole.net; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import javax.servlet.ServletContext; +import net.sourceforge.guacamole.GuacamoleException; + +public abstract class Configuration { + + protected String humanReadableList(Object... values) { + + String list = ""; + for (int i=0; i= 1) + list += ", "; + + if (i == values.length -1) + list += " or "; + + list += "\"" + values[i] + "\""; + } + + return list; + + } + + protected String readParameter(String name) throws GuacamoleException { + String value = GuacamoleProperties.getProperty(name); + return value; + } + + protected String readParameter(String name, String defaultValue, String... allowedValues) throws GuacamoleException { + + String value = GuacamoleProperties.getProperty(name); + + // Use default if not specified + if (value == null) { + if (defaultValue == null) + throw new GuacamoleException("Parameter \"" + name + "\" is required."); + + return defaultValue; + } + + // If not restricted to certain values, just return whatever is given. + if (allowedValues.length == 0) + return value; + + // If restricted, only return value within given list + for (String allowedValue : allowedValues) + if (value.equals(allowedValue)) + return value; + + throw new GuacamoleException("Parameter \"" + name + "\" must be " + humanReadableList((Object) allowedValues)); + } + + protected boolean readBooleanParameter(String name, Boolean defaultValue) throws GuacamoleException { + + String value = GuacamoleProperties.getProperty(name); + + // Use default if not specified + if (value == null) { + if (defaultValue == null) + throw new GuacamoleException("Parameter \"" + name + "\" is required."); + + return defaultValue; + } + + value = value.trim(); + if (value.equals("true")) + return true; + + if (value.equals("false")) + return false; + + throw new GuacamoleException("Parameter \"" + name + "\" must be \"true\" or \"false\"."); + + } + + protected int readIntParameter(String name, Integer defaultValue, Integer... allowedValues) throws GuacamoleException { + + String parmString = GuacamoleProperties.getProperty(name); + + // Use default if not specified + if (parmString== null) { + if (defaultValue == null) + throw new GuacamoleException("Parameter \"" + name + "\" is required."); + + return defaultValue; + } + + try { + int value = Integer.parseInt(parmString); + + // If not restricted to certain values, just return whatever is given. + if (allowedValues.length == 0) + return value; + + // If restricted, only return value within given list + for (int allowedValue : allowedValues) + if (value == allowedValue) + return value; + + throw new GuacamoleException("Parameter \"" + name + "\" must be " + humanReadableList((Object) allowedValues)); + } + catch (NumberFormatException e) { + throw new GuacamoleException("Parameter \"" + name + "\" must be an integer.", e); + } + + } + +} diff --git a/guacamole-common/src/main/java/net/sourceforge/guacamole/net/GuacamoleConfiguration.java b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/GuacamoleConfiguration.java new file mode 100644 index 000000000..a6bfeeb52 --- /dev/null +++ b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/GuacamoleConfiguration.java @@ -0,0 +1,80 @@ + +package net.sourceforge.guacamole.net; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import net.sourceforge.guacamole.net.authentication.GuacamoleSessionProvider; +import java.lang.reflect.InvocationTargetException; +import javax.servlet.http.HttpSession; +import net.sourceforge.guacamole.GuacamoleException; + +public class GuacamoleConfiguration extends Configuration { + + private String guacd_hostname; + private int guacd_port; + private GuacamoleSessionProvider sessionProvider; + + public GuacamoleConfiguration() throws GuacamoleException { + + guacd_hostname = readParameter("guacd-hostname"); + guacd_port = readIntParameter("guacd-port", null); + + // Get session provider instance + try { + String sessionProviderClassName = readParameter("session-provider"); + Object obj = Class.forName(sessionProviderClassName).getConstructor().newInstance(); + if (!(obj instanceof GuacamoleSessionProvider)) + throw new GuacamoleException("Specified session provider class is not a GuacamoleSessionProvider"); + + sessionProvider = (GuacamoleSessionProvider) obj; + } + catch (ClassNotFoundException e) { + throw new GuacamoleException("Session provider class not found", e); + } + catch (NoSuchMethodException e) { + throw new GuacamoleException("Default constructor for session provider not present", e); + } + catch (SecurityException e) { + throw new GuacamoleException("Creation of session provider disallowed; check your security settings", e); + } + catch (InstantiationException e) { + throw new GuacamoleException("Unable to instantiate session provider", e); + } + catch (IllegalAccessException e) { + throw new GuacamoleException("Unable to access default constructor of session provider", e); + } + catch (InvocationTargetException e) { + throw new GuacamoleException("Internal error in constructor of session provider", e.getTargetException()); + } + + } + + public int getProxyPort() { + return guacd_port; + } + + public String getProxyHostname() { + return guacd_hostname; + } + + public GuacamoleSession createSession(HttpSession session) throws GuacamoleException { + return sessionProvider.createSession(session); + } + +} diff --git a/guacamole-common/src/main/java/net/sourceforge/guacamole/net/GuacamoleProperties.java b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/GuacamoleProperties.java new file mode 100644 index 000000000..6c4340dd1 --- /dev/null +++ b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/GuacamoleProperties.java @@ -0,0 +1,55 @@ + +package net.sourceforge.guacamole.net; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import net.sourceforge.guacamole.GuacamoleException; + +public class GuacamoleProperties { + + private static final Properties properties; + private static GuacamoleException exception; + + static { + + properties = new Properties(); + + try { + + InputStream stream = GuacamoleProperties.class.getResourceAsStream("/guacamole.properties"); + if (stream == null) + throw new IOException("Resource /guacamole.properties not found."); + + properties.load(stream); + } + catch (IOException e) { + exception = new GuacamoleException("Error reading guacamole.properties", e); + } + + } + + public static String getProperty(String name) throws GuacamoleException { + if (exception != null) throw exception; + return properties.getProperty(name); + } + +} diff --git a/guacamole-common/src/main/java/net/sourceforge/guacamole/net/GuacamoleServlet.java b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/GuacamoleServlet.java new file mode 100644 index 000000000..a1e713132 --- /dev/null +++ b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/GuacamoleServlet.java @@ -0,0 +1,83 @@ + +package net.sourceforge.guacamole.net; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.io.IOException; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import net.sourceforge.guacamole.GuacamoleException; + +public abstract class GuacamoleServlet extends HttpServlet { + + private GuacamoleConfiguration config; + + @Override + public void init() throws ServletException { + try { + this.config = new GuacamoleConfiguration(); + } + catch (GuacamoleException e) { + throw new ServletException(e); + } + } + + @Override + protected final void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + try { + handleRequest(req, resp); + } + catch (GuacamoleException e) { + throw new ServletException(e); + } + } + + @Override + protected final void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + try { + handleRequest(req, resp); + } + catch (GuacamoleException e) { + throw new ServletException(e); + } + } + + private final void handleRequest(HttpServletRequest request, HttpServletResponse response) throws GuacamoleException { + + HttpSession httpSession = request.getSession(shouldCreateSession()); + + if (httpSession != null) { + GuacamoleSession session = config.createSession(httpSession); + handleRequest(session, request, response); + } + else + throw new GuacamoleException("No session"); + } + + protected abstract void handleRequest(GuacamoleSession session, HttpServletRequest request, HttpServletResponse response) throws GuacamoleException; + + protected boolean shouldCreateSession() { + return false; + } + +} diff --git a/guacamole-common/src/main/java/net/sourceforge/guacamole/net/GuacamoleSession.java b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/GuacamoleSession.java new file mode 100644 index 000000000..2eb01326b --- /dev/null +++ b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/GuacamoleSession.java @@ -0,0 +1,209 @@ + +package net.sourceforge.guacamole.net; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.concurrent.locks.ReentrantLock; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; +import net.sourceforge.guacamole.Client; +import net.sourceforge.guacamole.GuacamoleClient; +import net.sourceforge.guacamole.GuacamoleException; + +public class GuacamoleSession { + + private GuacamoleConfiguration config; + private final HttpSession session; + private SessionClient client; + private ReentrantLock instructionStreamLock; + + private String protocol; + private String hostname; + private int port; + private String password; + + public class SessionClient extends Client implements HttpSessionBindingListener { + + private Client client; + private ReentrantLock authorizedLock; + + public SessionClient(Client client) { + this.client = client; + + authorizedLock = new ReentrantLock(); + authorizedLock.lock(); + } + + public void authorize() { + authorizedLock.unlock(); + } + + public void waitForAuthorization() { + if (authorizedLock.isLocked()) { + try { + authorizedLock.lock(); + authorizedLock.unlock(); + } + catch (Throwable t) { + throw new Error("Internal error waiting for authorization", t); + } + } + } + + public void valueBound(HttpSessionBindingEvent event) { + // Do nothing + } + + public void valueUnbound(HttpSessionBindingEvent event) { + try { + disconnect(); + } + catch (GuacamoleException e) { + // Ignore + } + } + + public void write(char[] data, int off, int len) throws GuacamoleException { + client.write(data, off, len); + } + + public char[] read() throws GuacamoleException { + return client.read(); + } + + public void disconnect() throws GuacamoleException { + client.disconnect(); + } + + } + + public GuacamoleSession(HttpSession session) throws GuacamoleException { + + if (session == null) + throw new GuacamoleException("User has no session."); + + this.session = session; + synchronized (session) { + + // Read configuration parameters + config = new GuacamoleConfiguration(); + + client = (SessionClient) session.getAttribute("CLIENT"); + instructionStreamLock = (ReentrantLock) session.getAttribute("INSTRUCTION_STREAM_LOCK"); + } + } + + public void connect() throws GuacamoleException { + synchronized (session) { + + if (client != null) + client.disconnect(); + + + client = new SessionClient( + new GuacamoleClient ( + config.getProxyHostname(), + config.getProxyPort() + ) + ); + + session.setAttribute("CLIENT", client); + + instructionStreamLock = new ReentrantLock(); + session.setAttribute("INSTRUCTION_STREAM_LOCK", instructionStreamLock); + + } + } + + public boolean isConnected() { + synchronized (session) { + return client != null; + } + } + + public GuacamoleConfiguration getConfiguration() { + return config; + } + + public SessionClient getClient() { + synchronized (session) { + return client; + } + } + + public void invalidate() { + session.invalidate(); + } + + public void disconnect() throws GuacamoleException { + if (client != null) { + client.disconnect(); + + session.removeAttribute("CLIENT"); + client = null; + } + } + + public ReentrantLock getInstructionStreamLock() { + return instructionStreamLock; + } + + public void setConnection(String protocol, String hostname, int port) { + this.protocol = protocol; + this.hostname = hostname; + this.port = port; + } + + public String getProtocol() { + return protocol; + } + + public String getHostname() { + return hostname; + } + + public int getPort() { + return port; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getConnectMessage() throws GuacamoleException { + + if (getProtocol() == null) + throw new GuacamoleException("Protocol not specified"); + + if (getHostname() == null) + throw new GuacamoleException("Hostname not specified"); + + if (getPassword() == null) + return "connect:" + getProtocol() + "," + getHostname() + "," + getPort() + ";"; + else + return "connect:" + getProtocol() + "," + getHostname() + "," + getPort() + "," + getPassword() + ";"; + } + +} diff --git a/guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/GuacamoleSessionProvider.java b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/GuacamoleSessionProvider.java new file mode 100644 index 000000000..bbe82a2de --- /dev/null +++ b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/GuacamoleSessionProvider.java @@ -0,0 +1,30 @@ + +package net.sourceforge.guacamole.net.authentication; + +import javax.servlet.http.HttpSession; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.GuacamoleSession; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +public interface GuacamoleSessionProvider { + + public GuacamoleSession createSession(HttpSession session) throws GuacamoleException; + +} diff --git a/guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/NullGuacamoleSessionProvider.java b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/NullGuacamoleSessionProvider.java new file mode 100644 index 000000000..a46d0fe57 --- /dev/null +++ b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/NullGuacamoleSessionProvider.java @@ -0,0 +1,32 @@ + +package net.sourceforge.guacamole.net.authentication; + +import javax.servlet.http.HttpSession; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.GuacamoleSession; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +public class NullGuacamoleSessionProvider implements GuacamoleSessionProvider { + + public GuacamoleSession createSession(HttpSession session) throws GuacamoleException { + throw new GuacamoleException("Null provider will not create sessions"); + } + +} diff --git a/guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/basic/BasicFileAuthenticationProvider.java b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/basic/BasicFileAuthenticationProvider.java new file mode 100644 index 000000000..83fcfd34c --- /dev/null +++ b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/basic/BasicFileAuthenticationProvider.java @@ -0,0 +1,318 @@ + +package net.sourceforge.guacamole.net.authentication.basic; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.io.File; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.GuacamoleProperties; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.helpers.XMLReaderFactory; + +public class BasicFileAuthenticationProvider implements BasicLogin.AuthenticationProvider { + + private long mappingTime; + private Map mapping; + + private File getUserMappingFile() throws GuacamoleException { + + // Get user mapping filename + String filename = GuacamoleProperties.getProperty("basic-user-mapping"); + if (filename == null) + return null; + + return new File(filename); + + } + + public synchronized void init() throws GuacamoleException { + + // Get user mapping file + File mapFile = getUserMappingFile(); + if (mapFile == null) + throw new GuacamoleException("Missing \"basic-user-mapping\" parameter required for basic login."); + + // Parse document + try { + + BasicUserMappingContentHandler contentHandler = new BasicUserMappingContentHandler(); + + XMLReader parser = XMLReaderFactory.createXMLReader(); + parser.setContentHandler(contentHandler); + parser.parse(mapFile.getAbsolutePath()); + + mappingTime = mapFile.lastModified(); + mapping = contentHandler.getUserMapping(); + + } + catch (IOException e) { + throw new GuacamoleException("Error reading basic user mapping file.", e); + } + catch (SAXException e) { + throw new GuacamoleException("Error parsing basic user mapping XML.", e); + } + + } + + @Override + public BasicLogin.AuthorizedConfiguration getAuthorizedConfiguration(String username, String password) throws GuacamoleException { + + // Check mapping file mod time + File userMappingFile = getUserMappingFile(); + if (userMappingFile.exists() && mappingTime < userMappingFile.lastModified()) { + + // If modified recently, gain exclusive access and recheck + synchronized (this) { + if (userMappingFile.exists() && mappingTime < userMappingFile.lastModified()) + init(); // If still not up to date, re-init + } + + } + + AuthInfo info = mapping.get(username); + if (info != null && info.validate(username, password)) + return new BasicLogin.AuthorizedConfiguration( + info.getProtocol(), + info.getHostname(), + info.getPort(), + info.getPassword() + ); + + return null; + + } + + public static class AuthInfo { + + public static enum Encoding { + PLAIN_TEXT, + MD5 + } + + private String auth_username; + private String auth_password; + private Encoding auth_encoding; + + private String protocol; + private String hostname; + private int port; + private String password; + + public AuthInfo(String auth_username, String auth_password, Encoding auth_encoding) { + this.auth_username = auth_username; + this.auth_password = auth_password; + this.auth_encoding = auth_encoding; + } + + private static final char HEX_CHARS[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + public static String getHexString(byte[] bytes) { + + if (bytes == null) + return null; + + StringBuilder hex = new StringBuilder(2 * bytes.length); + for (byte b : bytes) { + hex.append(HEX_CHARS[(b & 0xF0) >> 4]) + .append(HEX_CHARS[(b & 0x0F) ]); + } + + return hex.toString(); + + } + + + public boolean validate(String username, String password) { + + // If username matches + if (username != null && password != null && username.equals(auth_username)) { + + switch (auth_encoding) { + + case PLAIN_TEXT: + + // Compare plaintext + return password.equals(auth_password); + + case MD5: + + // Compare hashed password + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + String hashedPassword = getHexString(digest.digest(password.getBytes())); + return hashedPassword.equals(auth_password.toUpperCase()); + } + catch (NoSuchAlgorithmException e) { + throw new UnsupportedOperationException("Unexpected lack of MD5 support.", e); + } + + } + + } + + return false; + + } + + public String getHostname() { + return hostname; + } + + public String getPassword() { + return password; + } + + public int getPort() { + return port; + } + + public String getProtocol() { + return protocol; + } + + } + + + private static class BasicUserMappingContentHandler extends DefaultHandler { + + private Map authMapping = new HashMap(); + + public Map getUserMapping() { + return Collections.unmodifiableMap(authMapping); + } + + private AuthInfo current; + + private enum AUTH_INFO_STATE { + PROTOCOL, + HOSTNAME, + PORT, + PASSWORD + }; + + private AUTH_INFO_STATE infoState; + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + if (localName.equals("authorize")) { + + // Finalize mapping for this user + authMapping.put( + current.auth_username, + current + ); + + } + + infoState = null; + + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + if (localName.equals("authorize")) { + + AuthInfo.Encoding encoding; + String encodingString = attributes.getValue("encoding"); + if (encodingString == null) + encoding = AuthInfo.Encoding.PLAIN_TEXT; + else if (encodingString.equals("plain")) + encoding = AuthInfo.Encoding.PLAIN_TEXT; + else if (encodingString.equals("md5")) + encoding = AuthInfo.Encoding.MD5; + else + throw new SAXException("Invalid encoding type"); + + + current = new AuthInfo( + attributes.getValue("username"), + attributes.getValue("password"), + encoding + ); + + infoState = null; + + } + + else if (localName.equals("protocol")) + infoState = AUTH_INFO_STATE.PROTOCOL; + + else if (localName.equals("hostname")) + infoState = AUTH_INFO_STATE.HOSTNAME; + + else if (localName.equals("port")) + infoState = AUTH_INFO_STATE.PORT; + + else if (localName.equals("password")) + infoState = AUTH_INFO_STATE.PASSWORD; + + else + infoState = null; + + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + + String str = new String(ch, start, length); + + if (infoState == null) + return; + + switch (infoState) { + + case PROTOCOL: + current.protocol = str; + break; + + case HOSTNAME: + current.hostname = str; + break; + + case PORT: + current.port = Integer.parseInt(str); + break; + + case PASSWORD: + current.password = str; + break; + + } + + } + + + } + + +} diff --git a/guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/basic/BasicGuacamoleSessionProvider.java b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/basic/BasicGuacamoleSessionProvider.java new file mode 100644 index 000000000..959bf0e6e --- /dev/null +++ b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/basic/BasicGuacamoleSessionProvider.java @@ -0,0 +1,50 @@ + +package net.sourceforge.guacamole.net.authentication.basic; + +import javax.servlet.http.HttpSession; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.GuacamoleSession; +import net.sourceforge.guacamole.net.authentication.GuacamoleSessionProvider; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +public class BasicGuacamoleSessionProvider implements GuacamoleSessionProvider { + + public GuacamoleSession createSession(HttpSession session) throws GuacamoleException { + + // Retrieve authorized config data from session + BasicLogin.AuthorizedConfiguration config = (BasicLogin.AuthorizedConfiguration) + session.getAttribute("BASIC-LOGIN-AUTH"); + + // If no data, not authorized + if (config == null) + throw new GuacamoleException("Unauthorized"); + + // Configure session from authorized config info + GuacamoleSession guacSession = new GuacamoleSession(session); + guacSession.setConnection(config.getProtocol(), config.getHostname(), config.getPort()); + if (config.getPassword() != null) + guacSession.setPassword(config.getPassword()); + + // Return authorized session + return guacSession; + + } + +} diff --git a/guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/basic/BasicLogin.java b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/basic/BasicLogin.java new file mode 100644 index 000000000..43e8721d9 --- /dev/null +++ b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/authentication/basic/BasicLogin.java @@ -0,0 +1,161 @@ + +package net.sourceforge.guacamole.net.authentication.basic; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.Configuration; + +public class BasicLogin extends HttpServlet { + + private Config config; + + @Override + public void init() throws ServletException { + try { + config = new Config(); + } + catch (GuacamoleException e) { + throw new ServletException(e); + } + } + + + private class Config extends Configuration { + + private AuthenticationProvider authProvider; + + public Config() throws GuacamoleException { + + // Get auth provider instance + try { + String authProviderClassName = readParameter("auth-provider"); + Object obj = Class.forName(authProviderClassName).getConstructor().newInstance(); + if (!(obj instanceof AuthenticationProvider)) + throw new GuacamoleException("Specified session provider class is not a GuacamoleSessionProvider"); + + authProvider = (AuthenticationProvider) obj; + } + catch (ClassNotFoundException e) { + throw new GuacamoleException("Session provider class not found", e); + } + catch (NoSuchMethodException e) { + throw new GuacamoleException("Default constructor for session provider not present", e); + } + catch (SecurityException e) { + throw new GuacamoleException("Creation of session provider disallowed; check your security settings", e); + } + catch (InstantiationException e) { + throw new GuacamoleException("Unable to instantiate session provider", e); + } + catch (IllegalAccessException e) { + throw new GuacamoleException("Unable to access default constructor of session provider", e); + } + catch (InvocationTargetException e) { + throw new GuacamoleException("Internal error in constructor of session provider", e.getTargetException()); + } + + } + + public AuthenticationProvider getAuthenticationProvider() { + return authProvider; + } + + } + + public static interface AuthenticationProvider { + public AuthorizedConfiguration getAuthorizedConfiguration(String username, String password) throws GuacamoleException; + } + + // Added to session when session validated + public static class AuthorizedConfiguration { + + private String protocol; + private String hostname; + private int port; + private String password; + + public AuthorizedConfiguration(String protocol, String hostname, int port, String password) { + this.protocol = protocol; + this.hostname = hostname; + this.port = port; + this.password = password; + } + + public String getHostname() { + return hostname; + } + + public String getPassword() { + return password; + } + + public int getPort() { + return port; + } + + public String getProtocol() { + return protocol; + } + + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + + // Retrieve username and password from parms + String username = req.getParameter("username"); + String password = req.getParameter("password"); + + // Validate username and password + try { + + AuthorizedConfiguration info = config.getAuthenticationProvider().getAuthorizedConfiguration(username, password); + if (info != null) { + + // Store authorized configuration + HttpSession session = req.getSession(true); + session.setAttribute( + "BASIC-LOGIN-AUTH", + info + ); + + // Success + return; + + } + + // Report "forbidden" on any failure + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Login invalid"); + } + catch (GuacamoleException e) { + throw new ServletException("Error validating credentials", e); + } + + } + + +} diff --git a/guacamole-common/src/main/java/net/sourceforge/guacamole/net/tunnel/Connect.java b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/tunnel/Connect.java new file mode 100644 index 000000000..79aa94d40 --- /dev/null +++ b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/tunnel/Connect.java @@ -0,0 +1,59 @@ +package net.sourceforge.guacamole.net.tunnel; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import net.sourceforge.guacamole.GuacamoleException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.sourceforge.guacamole.net.GuacamoleServlet; + +import net.sourceforge.guacamole.net.GuacamoleSession; + +public class Connect extends GuacamoleServlet { + + @Override + protected boolean shouldCreateSession() { + return true; + } + + @Override + protected void handleRequest(GuacamoleSession session, HttpServletRequest request, HttpServletResponse response) throws GuacamoleException { + + // Disconnect if already connected + if (session.isConnected()) + session.disconnect(); + + // Obtain new connection + session.connect(); + + // Send data + try { + char[] connect = session.getConnectMessage().toCharArray(); + session.getClient().write(connect, 0, connect.length); + session.getClient().authorize(); + } + catch (GuacamoleException e) { + throw new GuacamoleException("Error sending data to server: " + e.getMessage(), e); + } + + } + +} + diff --git a/guacamole-common/src/main/java/net/sourceforge/guacamole/net/tunnel/Inbound.java b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/tunnel/Inbound.java new file mode 100644 index 000000000..415d639bf --- /dev/null +++ b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/tunnel/Inbound.java @@ -0,0 +1,59 @@ +package net.sourceforge.guacamole.net.tunnel; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import net.sourceforge.guacamole.GuacamoleException; + +import java.io.Reader; +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.sourceforge.guacamole.net.GuacamoleServlet; + +import net.sourceforge.guacamole.net.GuacamoleSession; + +public class Inbound extends GuacamoleServlet { + + @Override + protected void handleRequest(GuacamoleSession session, HttpServletRequest request, HttpServletResponse response) throws GuacamoleException { + + session.getClient().waitForAuthorization(); + + // Send data + try { + + Reader input = request.getReader(); + char[] buffer = new char[8192]; + + int length; + while ((length = input.read(buffer, 0, buffer.length)) != -1) + session.getClient().write(buffer, 0, length); + + } + catch (IOException e) { + throw new GuacamoleException("I/O Error sending data to server: " + e.getMessage(), e); + } + catch (GuacamoleException e) { + throw new GuacamoleException("Error sending data to server: " + e.getMessage(), e); + } + + } + +} + diff --git a/guacamole-common/src/main/java/net/sourceforge/guacamole/net/tunnel/Outbound.java b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/tunnel/Outbound.java new file mode 100644 index 000000000..76aa34c76 --- /dev/null +++ b/guacamole-common/src/main/java/net/sourceforge/guacamole/net/tunnel/Outbound.java @@ -0,0 +1,99 @@ +package net.sourceforge.guacamole.net.tunnel; + +/* + * Guacamole - Clientless Remote Desktop + * Copyright (C) 2010 Michael Jumper + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.io.Writer; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.concurrent.locks.ReentrantLock; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.sourceforge.guacamole.Client; +import net.sourceforge.guacamole.net.GuacamoleServlet; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.GuacamoleSession; + + +public class Outbound extends GuacamoleServlet { + + @Override + protected void handleRequest(GuacamoleSession session, HttpServletRequest request, HttpServletResponse response) throws GuacamoleException { + + session.getClient().waitForAuthorization(); + + ReentrantLock instructionStreamLock = session.getInstructionStreamLock(); + instructionStreamLock.lock(); + + try { + + response.setContentType("text/plain"); + Writer out = response.getWriter(); + + try { + + // Query new update from server + Client client = session.getClient(); + + // For all messages, until another stream is ready (we send at least one message) + char[] message; + while ((message = client.read()) != null) { + + // Get message output bytes + out.write(message, 0, message.length); + out.flush(); + response.flushBuffer(); + + // No more messages another stream can take over + if (instructionStreamLock.hasQueuedThreads()) + break; + + } + + if (message == null) { + session.disconnect(); + throw new GuacamoleException("Disconnected."); + } + + } + catch (GuacamoleException e) { + out.write("error:" + e.getMessage() + ";"); + out.flush(); + response.flushBuffer(); + } + + // End-of-instructions marker + out.write(';'); + out.flush(); + response.flushBuffer(); + + } + catch (UnsupportedEncodingException e) { + throw new GuacamoleException("UTF-8 not supported by Java.", e); + } + catch (IOException e) { + throw new GuacamoleException("I/O error writing to servlet output stream.", e); + } + finally { + instructionStreamLock.unlock(); + } + + } + +} +