forked from shaba/openuds
Backported guacamole for 2.0 availability
This commit is contained in:
parent
0d28a5b5f7
commit
2eef7222d5
5
guacamole-tunnel/NOTICE
Normal file
5
guacamole-tunnel/NOTICE
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Apache Guacamole
|
||||||
|
Copyright 2016 The Apache Software Foundation
|
||||||
|
|
||||||
|
This product includes software developed at
|
||||||
|
The Apache Software Foundation (http://www.apache.org/).
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel;
|
||||||
|
|
||||||
|
import com.google.inject.Module;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic means of loading a tunnel without adding explicit dependencies within
|
||||||
|
* the main ServletModule, as not all servlet containers may have the classes
|
||||||
|
* required by all tunnel implementations.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public interface TunnelLoader extends Module {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether this type of tunnel is supported by the servlet container.
|
||||||
|
*
|
||||||
|
* @return true if this type of tunnel is supported and can be loaded
|
||||||
|
* without errors, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isSupported();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* This file has been modified from the original, upstream version to facilitate
|
||||||
|
* integration with OpenUDS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel;
|
||||||
|
|
||||||
|
import org.apache.guacamole.tunnel.http.RestrictedGuacamoleHTTPTunnelServlet;
|
||||||
|
import com.google.inject.servlet.ServletModule;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module which loads tunnel implementations.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public class TunnelModule extends ServletModule {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(TunnelModule.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classnames of all implementation-specific WebSocket tunnel modules.
|
||||||
|
*/
|
||||||
|
private static final String[] WEBSOCKET_MODULES = {
|
||||||
|
"org.apache.guacamole.tunnel.websocket.WebSocketTunnelModule",
|
||||||
|
"org.apache.guacamole.tunnel.websocket.jetty8.WebSocketTunnelModule",
|
||||||
|
"org.apache.guacamole.tunnel.websocket.jetty9.WebSocketTunnelModule",
|
||||||
|
"org.apache.guacamole.tunnel.websocket.tomcat.WebSocketTunnelModule"
|
||||||
|
};
|
||||||
|
|
||||||
|
private boolean loadWebSocketModule(String classname) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Attempt to find WebSocket module
|
||||||
|
Class<?> module = Class.forName(classname);
|
||||||
|
|
||||||
|
// Create loader
|
||||||
|
TunnelLoader loader = (TunnelLoader) module.getConstructor().newInstance();
|
||||||
|
|
||||||
|
// Install module, if supported
|
||||||
|
if (loader.isSupported()) {
|
||||||
|
install(loader);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no such class or constructor, etc., then this particular
|
||||||
|
// WebSocket support is not present
|
||||||
|
catch (ClassNotFoundException e) {}
|
||||||
|
catch (NoClassDefFoundError e) {}
|
||||||
|
catch (NoSuchMethodException e) {}
|
||||||
|
|
||||||
|
// Log errors which indicate bugs
|
||||||
|
catch (InstantiationException e) {
|
||||||
|
logger.debug("Error instantiating WebSocket module.", e);
|
||||||
|
}
|
||||||
|
catch (IllegalAccessException e) {
|
||||||
|
logger.debug("Error instantiating WebSocket module.", e);
|
||||||
|
}
|
||||||
|
catch (InvocationTargetException e) {
|
||||||
|
logger.debug("Error instantiating WebSocket module.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load attempt failed
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureServlets() {
|
||||||
|
|
||||||
|
bind(TunnelRequestService.class);
|
||||||
|
|
||||||
|
// Set up HTTP tunnel
|
||||||
|
serve("/tunnel").with(RestrictedGuacamoleHTTPTunnelServlet.class);
|
||||||
|
|
||||||
|
// Try to load each WebSocket tunnel in sequence
|
||||||
|
for (String classname : WEBSOCKET_MODULES) {
|
||||||
|
if (loadWebSocketModule(classname)) {
|
||||||
|
logger.debug("WebSocket module loaded: {}", classname);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn of lack of WebSocket
|
||||||
|
logger.info("WebSocket support NOT present. Only HTTP will be used.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,371 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.guacamole.GuacamoleClientException;
|
||||||
|
import org.apache.guacamole.GuacamoleClientException;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A request object which provides only the functions absolutely required to
|
||||||
|
* retrieve and connect to a tunnel.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public abstract class TunnelRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the request parameter containing the user's authentication
|
||||||
|
* token.
|
||||||
|
*/
|
||||||
|
public static final String AUTH_TOKEN_PARAMETER = "token";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the parameter containing the identifier of the
|
||||||
|
* AuthenticationProvider associated with the UserContext containing the
|
||||||
|
* object to which a tunnel is being requested.
|
||||||
|
*/
|
||||||
|
public static final String AUTH_PROVIDER_IDENTIFIER_PARAMETER = "GUAC_DATA_SOURCE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the parameter specifying the type of object to which a
|
||||||
|
* tunnel is being requested. Currently, this may be "c" for a Guacamole
|
||||||
|
* connection, or "g" for a Guacamole connection group.
|
||||||
|
*/
|
||||||
|
public static final String TYPE_PARAMETER = "GUAC_TYPE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the parameter containing the unique identifier of the object
|
||||||
|
* to which a tunnel is being requested.
|
||||||
|
*/
|
||||||
|
public static final String IDENTIFIER_PARAMETER = "GUAC_ID";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the parameter containing the desired display width, in
|
||||||
|
* pixels.
|
||||||
|
*/
|
||||||
|
public static final String WIDTH_PARAMETER = "GUAC_WIDTH";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the parameter containing the desired display height, in
|
||||||
|
* pixels.
|
||||||
|
*/
|
||||||
|
public static final String HEIGHT_PARAMETER = "GUAC_HEIGHT";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the parameter containing the desired display resolution, in
|
||||||
|
* DPI.
|
||||||
|
*/
|
||||||
|
public static final String DPI_PARAMETER = "GUAC_DPI";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the parameter specifying one supported audio mimetype. This
|
||||||
|
* will normally appear multiple times within a single tunnel request -
|
||||||
|
* once for each mimetype.
|
||||||
|
*/
|
||||||
|
public static final String AUDIO_PARAMETER = "GUAC_AUDIO";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the parameter specifying one supported video mimetype. This
|
||||||
|
* will normally appear multiple times within a single tunnel request -
|
||||||
|
* once for each mimetype.
|
||||||
|
*/
|
||||||
|
public static final String VIDEO_PARAMETER = "GUAC_VIDEO";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the parameter specifying one supported image mimetype. This
|
||||||
|
* will normally appear multiple times within a single tunnel request -
|
||||||
|
* once for each mimetype.
|
||||||
|
*/
|
||||||
|
public static final String IMAGE_PARAMETER = "GUAC_IMAGE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All supported object types that can be used as the destination of a
|
||||||
|
* tunnel.
|
||||||
|
*/
|
||||||
|
public static enum Type {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Guacamole connection.
|
||||||
|
*/
|
||||||
|
CONNECTION("c"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Guacamole connection group.
|
||||||
|
*/
|
||||||
|
CONNECTION_GROUP("g");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parameter value which denotes a destination object of this type.
|
||||||
|
*/
|
||||||
|
final String PARAMETER_VALUE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a Type having the given corresponding parameter value.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The parameter value which denotes a destination object of this
|
||||||
|
* type.
|
||||||
|
*/
|
||||||
|
Type(String value) {
|
||||||
|
PARAMETER_VALUE = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of the parameter having the given name.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* The name of the parameter to return.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The value of the parameter having the given name, or null if no such
|
||||||
|
* parameter was specified.
|
||||||
|
*/
|
||||||
|
public abstract String getParameter(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all values specified for the given parameter.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* The name of the parameter to return.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* All values of the parameter having the given name , or null if no
|
||||||
|
* such parameter was specified.
|
||||||
|
*/
|
||||||
|
public abstract List<String> getParameterValues(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of the parameter having the given name, throwing an
|
||||||
|
* exception if the parameter is missing.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* The name of the parameter to return.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The value of the parameter having the given name.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the parameter is not present in the request.
|
||||||
|
*/
|
||||||
|
public String getRequiredParameter(String name) throws GuacamoleException {
|
||||||
|
|
||||||
|
// Pull requested parameter, aborting if absent
|
||||||
|
String value = getParameter(name);
|
||||||
|
if (value == null)
|
||||||
|
throw new GuacamoleClientException("Parameter \"" + name + "\" is required.");
|
||||||
|
|
||||||
|
return value;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the integer value of the parameter having the given name,
|
||||||
|
* throwing an exception if the parameter cannot be parsed.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* The name of the parameter to return.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The integer value of the parameter having the given name, or null if
|
||||||
|
* the parameter is missing.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the parameter is not a valid integer.
|
||||||
|
*/
|
||||||
|
public Integer getIntegerParameter(String name) throws GuacamoleException {
|
||||||
|
|
||||||
|
// Pull requested parameter
|
||||||
|
String value = getParameter(name);
|
||||||
|
if (value == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Attempt to parse as an integer
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rethrow any parsing error as a GuacamoleClientException
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
throw new GuacamoleClientException("Parameter \"" + name + "\" must be a valid integer.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the authentication token associated with this tunnel request.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The authentication token associated with this tunnel request, or
|
||||||
|
* null if no authentication token is present.
|
||||||
|
*/
|
||||||
|
public String getAuthenticationToken() {
|
||||||
|
return getParameter(AUTH_TOKEN_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the identifier of the AuthenticationProvider associated with the
|
||||||
|
* UserContext from which the connection or connection group is to be
|
||||||
|
* retrieved when the tunnel is created. In the context of the REST API and
|
||||||
|
* the JavaScript side of the web application, this is referred to as the
|
||||||
|
* data source identifier.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The identifier of the AuthenticationProvider associated with the
|
||||||
|
* UserContext from which the connection or connection group is to be
|
||||||
|
* retrieved when the tunnel is created.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the identifier was not present in the request.
|
||||||
|
*/
|
||||||
|
public String getAuthenticationProviderIdentifier()
|
||||||
|
throws GuacamoleException {
|
||||||
|
return getRequiredParameter(AUTH_PROVIDER_IDENTIFIER_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of object for which the tunnel is being requested.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The type of object for which the tunnel is being requested.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the type was not present in the request, or if the type requested
|
||||||
|
* is in the wrong format.
|
||||||
|
*/
|
||||||
|
public Type getType() throws GuacamoleException {
|
||||||
|
|
||||||
|
String type = getRequiredParameter(TYPE_PARAMETER);
|
||||||
|
|
||||||
|
// For each possible object type
|
||||||
|
for (Type possibleType : Type.values()) {
|
||||||
|
|
||||||
|
// Match against defined parameter value
|
||||||
|
if (type.equals(possibleType.PARAMETER_VALUE))
|
||||||
|
return possibleType;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new GuacamoleClientException("Illegal identifier - unknown type.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the identifier of the destination of the tunnel being requested.
|
||||||
|
* As there are multiple types of destination objects available, and within
|
||||||
|
* multiple data sources, the associated object type and data source are
|
||||||
|
* also necessary to determine what this identifier refers to.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The identifier of the destination of the tunnel being requested.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the identifier was not present in the request.
|
||||||
|
*/
|
||||||
|
public String getIdentifier() throws GuacamoleException {
|
||||||
|
return getRequiredParameter(IDENTIFIER_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the display width desired for the Guacamole session over the
|
||||||
|
* tunnel being requested.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The display width desired for the Guacamole session over the tunnel
|
||||||
|
* being requested, or null if no width was given.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the width specified was not a valid integer.
|
||||||
|
*/
|
||||||
|
public Integer getWidth() throws GuacamoleException {
|
||||||
|
return getIntegerParameter(WIDTH_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the display height desired for the Guacamole session over the
|
||||||
|
* tunnel being requested.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The display height desired for the Guacamole session over the tunnel
|
||||||
|
* being requested, or null if no width was given.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the height specified was not a valid integer.
|
||||||
|
*/
|
||||||
|
public Integer getHeight() throws GuacamoleException {
|
||||||
|
return getIntegerParameter(HEIGHT_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the display resolution desired for the Guacamole session over
|
||||||
|
* the tunnel being requested, in DPI.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The display resolution desired for the Guacamole session over the
|
||||||
|
* tunnel being requested, or null if no resolution was given.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the resolution specified was not a valid integer.
|
||||||
|
*/
|
||||||
|
public Integer getDPI() throws GuacamoleException {
|
||||||
|
return getIntegerParameter(DPI_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all audio mimetypes declared as supported within the
|
||||||
|
* tunnel request.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A list of all audio mimetypes declared as supported within the
|
||||||
|
* tunnel request, or null if no mimetypes were specified.
|
||||||
|
*/
|
||||||
|
public List<String> getAudioMimetypes() {
|
||||||
|
return getParameterValues(AUDIO_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all video mimetypes declared as supported within the
|
||||||
|
* tunnel request.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A list of all video mimetypes declared as supported within the
|
||||||
|
* tunnel request, or null if no mimetypes were specified.
|
||||||
|
*/
|
||||||
|
public List<String> getVideoMimetypes() {
|
||||||
|
return getParameterValues(VIDEO_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all image mimetypes declared as supported within the
|
||||||
|
* tunnel request.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A list of all image mimetypes declared as supported within the
|
||||||
|
* tunnel request, or null if no mimetypes were specified.
|
||||||
|
*/
|
||||||
|
public List<String> getImageMimetypes() {
|
||||||
|
return getParameterValues(IMAGE_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* This file has been modified from the original, upstream version to facilitate
|
||||||
|
* integration with OpenUDS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.guacamole.GuacamoleClientException;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.net.GuacamoleSocket;
|
||||||
|
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||||
|
import org.apache.guacamole.net.InetGuacamoleSocket;
|
||||||
|
import org.apache.guacamole.net.SimpleGuacamoleTunnel;
|
||||||
|
import org.apache.guacamole.protocol.ConfiguredGuacamoleSocket;
|
||||||
|
import org.apache.guacamole.protocol.GuacamoleClientInformation;
|
||||||
|
import org.apache.guacamole.protocol.GuacamoleConfiguration;
|
||||||
|
import org.openuds.guacamole.connection.ConnectionService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class that takes a standard request from the Guacamole JavaScript
|
||||||
|
* client and produces the corresponding GuacamoleTunnel. The implementation
|
||||||
|
* of this utility is specific to the form of request used by the upstream
|
||||||
|
* Guacamole web application, and is not necessarily useful to applications
|
||||||
|
* that use purely the Guacamole API.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
* @author Vasily Loginov
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class TunnelRequestService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(TunnelRequestService.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for retrieving remotely-maintained connection information.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private ConnectionService connectionService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hostname of the server hosting guacd.
|
||||||
|
*/
|
||||||
|
private static final String GUACD_HOSTNAME = "127.0.0.1";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The port that guacd will be listening on.
|
||||||
|
*/
|
||||||
|
private static final int GUACD_PORT = 4822;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new tunnel using the parameters and credentials present in
|
||||||
|
* the given request.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* The HttpServletRequest describing the tunnel to create.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The created tunnel, or null if the tunnel could not be created.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If an error occurs while creating the tunnel.
|
||||||
|
*/
|
||||||
|
public GuacamoleTunnel createTunnel(TunnelRequest request) throws GuacamoleException {
|
||||||
|
|
||||||
|
// Pull OpenUDS-specific "data" parameter
|
||||||
|
String data = request.getParameter("data");
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
logger.debug("No ID received in tunnel connect request.");
|
||||||
|
throw new GuacamoleClientException("Connection data not provided.");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Establishing tunnel and connection with data from \"{}\"...", data);
|
||||||
|
|
||||||
|
// Get connection from remote service
|
||||||
|
GuacamoleConfiguration config = connectionService.getConnectionConfiguration(data);
|
||||||
|
if (config == null)
|
||||||
|
throw new GuacamoleClientException("Connection configuration could not be retrieved.");
|
||||||
|
|
||||||
|
// Get client information
|
||||||
|
GuacamoleClientInformation info = new GuacamoleClientInformation();
|
||||||
|
|
||||||
|
// Set width if provided
|
||||||
|
String width = request.getParameter("GUAC_WIDTH");
|
||||||
|
if (width != null)
|
||||||
|
info.setOptimalScreenWidth(Integer.parseInt(width));
|
||||||
|
|
||||||
|
// Set height if provided
|
||||||
|
String height = request.getParameter("GUAC_HEIGHT");
|
||||||
|
if (height != null)
|
||||||
|
info.setOptimalScreenHeight(Integer.parseInt(height));
|
||||||
|
|
||||||
|
// Set resolution if provided
|
||||||
|
String dpi = request.getParameter("GUAC_DPI");
|
||||||
|
if (dpi != null)
|
||||||
|
info.setOptimalResolution(Integer.parseInt(dpi));
|
||||||
|
|
||||||
|
// Add audio mimetypes
|
||||||
|
List<String> audio_mimetypes = request.getParameterValues("GUAC_AUDIO");
|
||||||
|
if (audio_mimetypes != null)
|
||||||
|
info.getAudioMimetypes().addAll(audio_mimetypes);
|
||||||
|
|
||||||
|
// Add video mimetypes
|
||||||
|
List<String> video_mimetypes = request.getParameterValues("GUAC_VIDEO");
|
||||||
|
if (video_mimetypes != null)
|
||||||
|
info.getVideoMimetypes().addAll(video_mimetypes);
|
||||||
|
|
||||||
|
// Add image mimetypes
|
||||||
|
List<String> image_mimetypes = request.getParameterValues("GUAC_IMAGE");
|
||||||
|
if (image_mimetypes != null)
|
||||||
|
info.getImageMimetypes().addAll(image_mimetypes);
|
||||||
|
|
||||||
|
// Connect socket for connection
|
||||||
|
GuacamoleSocket socket;
|
||||||
|
try {
|
||||||
|
socket = new ConfiguredGuacamoleSocket(
|
||||||
|
new InetGuacamoleSocket(GUACD_HOSTNAME, GUACD_PORT),
|
||||||
|
config, info
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log any errors during connection
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
logger.error("Unable to connect to guacd.", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return corresponding tunnel
|
||||||
|
return new SimpleGuacamoleTunnel(socket);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.http;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP-specific implementation of TunnelRequest.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public class HTTPTunnelRequest extends TunnelRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A copy of the parameters obtained from the HttpServletRequest used to
|
||||||
|
* construct the HTTPTunnelRequest.
|
||||||
|
*/
|
||||||
|
private final Map<String, List<String>> parameterMap =
|
||||||
|
new HashMap<String, List<String>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a HTTPTunnelRequest which copies and exposes the parameters
|
||||||
|
* from the given HttpServletRequest.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* The HttpServletRequest to copy parameter values from.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked") // getParameterMap() is defined as returning Map<String, String[]>
|
||||||
|
public HTTPTunnelRequest(HttpServletRequest request) {
|
||||||
|
|
||||||
|
// For each parameter
|
||||||
|
for (Map.Entry<String, String[]> mapEntry : ((Map<String, String[]>)
|
||||||
|
request.getParameterMap()).entrySet()) {
|
||||||
|
|
||||||
|
// Get parameter name and corresponding values
|
||||||
|
String parameterName = mapEntry.getKey();
|
||||||
|
List<String> parameterValues = Arrays.asList(mapEntry.getValue());
|
||||||
|
|
||||||
|
// Store copy of all values in our own map
|
||||||
|
parameterMap.put(
|
||||||
|
parameterName,
|
||||||
|
new ArrayList<String>(parameterValues)
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getParameter(String name) {
|
||||||
|
List<String> values = getParameterValues(name);
|
||||||
|
|
||||||
|
// Return the first value from the list if available
|
||||||
|
if (values != null && !values.isEmpty())
|
||||||
|
return values.get(0);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getParameterValues(String name) {
|
||||||
|
return parameterMap.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* This file has been modified from the original, upstream version to facilitate
|
||||||
|
* integration with OpenUDS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.http;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||||
|
import org.apache.guacamole.servlet.GuacamoleHTTPTunnelServlet;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects users to a tunnel associated with the authorized connection
|
||||||
|
* having the given ID.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class RestrictedGuacamoleHTTPTunnelServlet extends GuacamoleHTTPTunnelServlet {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for handling tunnel requests.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private TunnelRequestService tunnelRequestService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RestrictedGuacamoleHTTPTunnelServlet.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException {
|
||||||
|
|
||||||
|
// Attempt to create HTTP tunnel
|
||||||
|
GuacamoleTunnel tunnel = tunnelRequestService.createTunnel(new HTTPTunnelRequest(request));
|
||||||
|
|
||||||
|
// If successful, warn of lack of WebSocket
|
||||||
|
logger.info("Using HTTP tunnel (not WebSocket). Performance may be sub-optimal.");
|
||||||
|
|
||||||
|
return tunnel;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classes which leverage Guacamole's built-in HTTP tunnel implementation.
|
||||||
|
*/
|
||||||
|
package org.apache.guacamole.tunnel.http;
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classes which are common to all tunnel implementations.
|
||||||
|
*/
|
||||||
|
package org.apache.guacamole.tunnel;
|
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* This file has been modified from the original, upstream version to facilitate
|
||||||
|
* integration with OpenUDS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.websocket;
|
||||||
|
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.websocket.EndpointConfig;
|
||||||
|
import javax.websocket.HandshakeResponse;
|
||||||
|
import javax.websocket.Session;
|
||||||
|
import javax.websocket.server.HandshakeRequest;
|
||||||
|
import javax.websocket.server.ServerEndpointConfig;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||||
|
import org.apache.guacamole.websocket.GuacamoleWebSocketTunnelEndpoint;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tunnel implementation which uses WebSocket as a tunnel backend, rather than
|
||||||
|
* HTTP, properly parsing connection IDs included in the connection request.
|
||||||
|
*/
|
||||||
|
public class RestrictedGuacamoleWebSocketTunnelEndpoint extends GuacamoleWebSocketTunnelEndpoint {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique string which shall be used to store the TunnelRequest
|
||||||
|
* associated with a WebSocket connection.
|
||||||
|
*/
|
||||||
|
private static final String TUNNEL_REQUEST_PROPERTY = "WS_GUAC_TUNNEL_REQUEST";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique string which shall be used to store the TunnelRequestService to
|
||||||
|
* be used for processing TunnelRequests.
|
||||||
|
*/
|
||||||
|
private static final String TUNNEL_REQUEST_SERVICE_PROPERTY = "WS_GUAC_TUNNEL_REQUEST_SERVICE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configurator implementation which stores the requested GuacamoleTunnel
|
||||||
|
* within the user properties. The GuacamoleTunnel will be later retrieved
|
||||||
|
* during the connection process.
|
||||||
|
*/
|
||||||
|
public static class Configurator extends ServerEndpointConfig.Configurator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider which provides instances of a service for handling
|
||||||
|
* tunnel requests.
|
||||||
|
*/
|
||||||
|
private final Provider<TunnelRequestService> tunnelRequestServiceProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Configurator which uses the given tunnel request
|
||||||
|
* service provider to retrieve the necessary service to handle new
|
||||||
|
* connections requests.
|
||||||
|
*
|
||||||
|
* @param tunnelRequestServiceProvider
|
||||||
|
* The tunnel request service provider to use for all new
|
||||||
|
* connections.
|
||||||
|
*/
|
||||||
|
public Configurator(Provider<TunnelRequestService> tunnelRequestServiceProvider) {
|
||||||
|
this.tunnelRequestServiceProvider = tunnelRequestServiceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void modifyHandshake(ServerEndpointConfig config,
|
||||||
|
HandshakeRequest request, HandshakeResponse response) {
|
||||||
|
|
||||||
|
super.modifyHandshake(config, request, response);
|
||||||
|
|
||||||
|
// Store tunnel request and tunnel request service for retrieval
|
||||||
|
// upon WebSocket open
|
||||||
|
Map<String, Object> userProperties = config.getUserProperties();
|
||||||
|
userProperties.clear();
|
||||||
|
userProperties.put(TUNNEL_REQUEST_PROPERTY, new WebSocketTunnelRequest(request));
|
||||||
|
userProperties.put(TUNNEL_REQUEST_SERVICE_PROPERTY, tunnelRequestServiceProvider.get());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GuacamoleTunnel createTunnel(Session session,
|
||||||
|
EndpointConfig config) throws GuacamoleException {
|
||||||
|
|
||||||
|
Map<String, Object> userProperties = config.getUserProperties();
|
||||||
|
|
||||||
|
// Get original tunnel request
|
||||||
|
TunnelRequest tunnelRequest = (TunnelRequest) userProperties.get(TUNNEL_REQUEST_PROPERTY);
|
||||||
|
if (tunnelRequest == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Get tunnel request service
|
||||||
|
TunnelRequestService tunnelRequestService = (TunnelRequestService) userProperties.get(TUNNEL_REQUEST_SERVICE_PROPERTY);
|
||||||
|
if (tunnelRequestService == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Create and return tunnel
|
||||||
|
return tunnelRequestService.createTunnel(tunnelRequest);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* This file has been modified from the original, upstream version to facilitate
|
||||||
|
* integration with OpenUDS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.websocket;
|
||||||
|
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import com.google.inject.servlet.ServletModule;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import javax.websocket.DeploymentException;
|
||||||
|
import javax.websocket.server.ServerContainer;
|
||||||
|
import javax.websocket.server.ServerEndpointConfig;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelLoader;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the JSR-356 WebSocket tunnel implementation.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Attempt to find WebSocket servlet
|
||||||
|
Class.forName("javax.websocket.Endpoint");
|
||||||
|
|
||||||
|
// Support found
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no such servlet class, this particular WebSocket support
|
||||||
|
// is not present
|
||||||
|
catch (ClassNotFoundException e) {}
|
||||||
|
catch (NoClassDefFoundError e) {}
|
||||||
|
|
||||||
|
// Support not found
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureServlets() {
|
||||||
|
|
||||||
|
logger.info("Loading JSR-356 WebSocket support...");
|
||||||
|
|
||||||
|
// Get container
|
||||||
|
ServerContainer container = (ServerContainer) getServletContext().getAttribute("javax.websocket.server.ServerContainer");
|
||||||
|
if (container == null) {
|
||||||
|
logger.warn("ServerContainer attribute required by JSR-356 is missing. Cannot load JSR-356 WebSocket support.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Provider<TunnelRequestService> tunnelRequestServiceProvider = getProvider(TunnelRequestService.class);
|
||||||
|
|
||||||
|
// Build configuration for WebSocket tunnel
|
||||||
|
ServerEndpointConfig config =
|
||||||
|
ServerEndpointConfig.Builder.create(RestrictedGuacamoleWebSocketTunnelEndpoint.class, "/websocket-tunnel")
|
||||||
|
.configurator(new RestrictedGuacamoleWebSocketTunnelEndpoint.Configurator(tunnelRequestServiceProvider))
|
||||||
|
.subprotocols(Arrays.asList(new String[]{"guacamole"}))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Add configuration to container
|
||||||
|
container.addEndpoint(config);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (DeploymentException e) {
|
||||||
|
logger.error("Unable to deploy WebSocket tunnel.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.websocket;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.websocket.server.HandshakeRequest;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket-specific implementation of TunnelRequest.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public class WebSocketTunnelRequest extends TunnelRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All parameters passed via HTTP to the WebSocket handshake.
|
||||||
|
*/
|
||||||
|
private final Map<String, List<String>> handshakeParameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a TunnelRequest implementation which delegates parameter and
|
||||||
|
* session retrieval to the given HandshakeRequest.
|
||||||
|
*
|
||||||
|
* @param request The HandshakeRequest to wrap.
|
||||||
|
*/
|
||||||
|
public WebSocketTunnelRequest(HandshakeRequest request) {
|
||||||
|
this.handshakeParameters = request.getParameterMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getParameter(String name) {
|
||||||
|
|
||||||
|
// Pull list of values, if present
|
||||||
|
List<String> values = getParameterValues(name);
|
||||||
|
if (values == null || values.isEmpty())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Return first parameter value arbitrarily
|
||||||
|
return values.get(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getParameterValues(String name) {
|
||||||
|
return handshakeParameters.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,236 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.websocket.jetty8;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.io.GuacamoleReader;
|
||||||
|
import org.apache.guacamole.io.GuacamoleWriter;
|
||||||
|
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||||
|
import org.eclipse.jetty.websocket.WebSocket;
|
||||||
|
import org.eclipse.jetty.websocket.WebSocket.Connection;
|
||||||
|
import org.eclipse.jetty.websocket.WebSocketServlet;
|
||||||
|
import org.apache.guacamole.GuacamoleClientException;
|
||||||
|
import org.apache.guacamole.GuacamoleConnectionClosedException;
|
||||||
|
import org.apache.guacamole.protocol.GuacamoleInstruction;
|
||||||
|
import org.apache.guacamole.tunnel.http.HTTPTunnelRequest;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||||
|
import org.apache.guacamole.protocol.GuacamoleStatus;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(GuacamoleWebSocketTunnelServlet.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default, minimum buffer size for instructions.
|
||||||
|
*/
|
||||||
|
private static final int BUFFER_SIZE = 8192;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the given status on the given WebSocket connection and closes the
|
||||||
|
* connection.
|
||||||
|
*
|
||||||
|
* @param connection The WebSocket connection to close.
|
||||||
|
* @param guac_status The status to send.
|
||||||
|
*/
|
||||||
|
public static void closeConnection(Connection connection,
|
||||||
|
GuacamoleStatus guac_status) {
|
||||||
|
|
||||||
|
connection.close(guac_status.getWebSocketCode(),
|
||||||
|
Integer.toString(guac_status.getGuacamoleStatusCode()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
|
||||||
|
|
||||||
|
final TunnelRequest tunnelRequest = new HTTPTunnelRequest(request);
|
||||||
|
|
||||||
|
// Return new WebSocket which communicates through tunnel
|
||||||
|
return new WebSocket.OnTextMessage() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GuacamoleTunnel associated with the connected WebSocket. If
|
||||||
|
* the WebSocket has not yet been connected, this will be null.
|
||||||
|
*/
|
||||||
|
private GuacamoleTunnel tunnel = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String string) {
|
||||||
|
|
||||||
|
// Ignore inbound messages if there is no associated tunnel
|
||||||
|
if (tunnel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GuacamoleWriter writer = tunnel.acquireWriter();
|
||||||
|
|
||||||
|
// Write message received
|
||||||
|
try {
|
||||||
|
writer.write(string.toCharArray());
|
||||||
|
}
|
||||||
|
catch (GuacamoleConnectionClosedException e) {
|
||||||
|
logger.debug("Connection to guacd closed.", e);
|
||||||
|
}
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
logger.debug("WebSocket tunnel write failed.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnel.releaseWriter();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpen(final Connection connection) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
tunnel = doConnect(tunnelRequest);
|
||||||
|
}
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
|
||||||
|
logger.debug("Error connecting WebSocket tunnel.", e);
|
||||||
|
closeConnection(connection, e.getStatus());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not start connection if tunnel does not exist
|
||||||
|
if (tunnel == null) {
|
||||||
|
closeConnection(connection, GuacamoleStatus.RESOURCE_NOT_FOUND);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread readThread = new Thread() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
|
||||||
|
GuacamoleReader reader = tunnel.acquireReader();
|
||||||
|
char[] readMessage;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Send tunnel UUID
|
||||||
|
connection.sendMessage(new GuacamoleInstruction(
|
||||||
|
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
|
||||||
|
tunnel.getUUID().toString()
|
||||||
|
).toString());
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Attempt to read
|
||||||
|
while ((readMessage = reader.read()) != null) {
|
||||||
|
|
||||||
|
// Buffer message
|
||||||
|
buffer.append(readMessage);
|
||||||
|
|
||||||
|
// Flush if we expect to wait or buffer is getting full
|
||||||
|
if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
|
||||||
|
connection.sendMessage(buffer.toString());
|
||||||
|
buffer.setLength(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// No more data
|
||||||
|
closeConnection(connection, GuacamoleStatus.SUCCESS);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catch any thrown guacamole exception and attempt
|
||||||
|
// to pass within the WebSocket connection, logging
|
||||||
|
// each error appropriately.
|
||||||
|
catch (GuacamoleClientException e) {
|
||||||
|
logger.info("WebSocket connection terminated: {}", e.getMessage());
|
||||||
|
logger.debug("WebSocket connection terminated due to client error.", e);
|
||||||
|
closeConnection(connection, e.getStatus());
|
||||||
|
}
|
||||||
|
catch (GuacamoleConnectionClosedException e) {
|
||||||
|
logger.debug("Connection to guacd closed.", e);
|
||||||
|
closeConnection(connection, GuacamoleStatus.SUCCESS);
|
||||||
|
}
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
|
||||||
|
logger.debug("Internal error during connection to guacd.", e);
|
||||||
|
closeConnection(connection, e.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
logger.debug("WebSocket tunnel read failed due to I/O error.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
readThread.start();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose(int i, String string) {
|
||||||
|
try {
|
||||||
|
if (tunnel != null)
|
||||||
|
tunnel.close();
|
||||||
|
}
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
logger.debug("Unable to close connection to guacd.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever the JavaScript Guacamole client makes a connection
|
||||||
|
* request. It it up to the implementor of this function to define what
|
||||||
|
* conditions must be met for a tunnel to be configured and returned as a
|
||||||
|
* result of this connection request (whether some sort of credentials must
|
||||||
|
* be specified, for example).
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* The TunnelRequest associated with the connection request received.
|
||||||
|
* Any parameters specified along with the connection request can be
|
||||||
|
* read from this object.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A newly constructed GuacamoleTunnel if successful, null otherwise.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If an error occurs while constructing the GuacamoleTunnel, or if the
|
||||||
|
* conditions required for connection are not met.
|
||||||
|
*/
|
||||||
|
protected abstract GuacamoleTunnel doConnect(TunnelRequest request)
|
||||||
|
throws GuacamoleException;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* This file has been modified from the original, upstream version to facilitate
|
||||||
|
* integration with OpenUDS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.websocket.jetty8;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tunnel servlet implementation which uses WebSocket as a tunnel backend,
|
||||||
|
* rather than HTTP, properly parsing connection IDs included in the connection
|
||||||
|
* request.
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class RestrictedGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for handling tunnel requests.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private TunnelRequestService tunnelRequestService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GuacamoleTunnel doConnect(TunnelRequest request)
|
||||||
|
throws GuacamoleException {
|
||||||
|
return tunnelRequestService.createTunnel(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.websocket.jetty8;
|
||||||
|
|
||||||
|
import com.google.inject.servlet.ServletModule;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelLoader;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the Jetty 8 WebSocket tunnel implementation.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Attempt to find WebSocket servlet
|
||||||
|
Class.forName("org.apache.guacamole.tunnel.websocket.jetty8.RestrictedGuacamoleWebSocketTunnelServlet");
|
||||||
|
|
||||||
|
// Support found
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no such servlet class, this particular WebSocket support
|
||||||
|
// is not present
|
||||||
|
catch (ClassNotFoundException e) {}
|
||||||
|
catch (NoClassDefFoundError e) {}
|
||||||
|
|
||||||
|
// Support not found
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureServlets() {
|
||||||
|
|
||||||
|
logger.info("Loading Jetty 8 WebSocket support...");
|
||||||
|
serve("/websocket-tunnel").with(RestrictedGuacamoleWebSocketTunnelServlet.class);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jetty 8 WebSocket tunnel implementation. The classes here require Jetty 8.
|
||||||
|
*/
|
||||||
|
package org.apache.guacamole.tunnel.websocket.jetty8;
|
@ -0,0 +1,247 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.websocket.jetty9;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.eclipse.jetty.websocket.api.CloseStatus;
|
||||||
|
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
|
||||||
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
|
import org.eclipse.jetty.websocket.api.WebSocketListener;
|
||||||
|
import org.apache.guacamole.GuacamoleClientException;
|
||||||
|
import org.apache.guacamole.GuacamoleConnectionClosedException;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.io.GuacamoleReader;
|
||||||
|
import org.apache.guacamole.io.GuacamoleWriter;
|
||||||
|
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||||
|
import org.apache.guacamole.protocol.GuacamoleInstruction;
|
||||||
|
import org.apache.guacamole.protocol.GuacamoleStatus;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket listener implementation which provides a Guacamole tunnel
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default, minimum buffer size for instructions.
|
||||||
|
*/
|
||||||
|
private static final int BUFFER_SIZE = 8192;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RestrictedGuacamoleWebSocketTunnelServlet.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The underlying GuacamoleTunnel. WebSocket reads/writes will be handled
|
||||||
|
* as reads/writes to this tunnel.
|
||||||
|
*/
|
||||||
|
private GuacamoleTunnel tunnel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the given status on the given WebSocket connection and closes the
|
||||||
|
* connection.
|
||||||
|
*
|
||||||
|
* @param session The outbound WebSocket connection to close.
|
||||||
|
* @param guac_status The status to send.
|
||||||
|
*/
|
||||||
|
private void closeConnection(Session session, GuacamoleStatus guac_status) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
int code = guac_status.getWebSocketCode();
|
||||||
|
String message = Integer.toString(guac_status.getGuacamoleStatusCode());
|
||||||
|
session.close(new CloseStatus(code, message));
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
logger.debug("Unable to close WebSocket connection.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new tunnel for the given session. How this tunnel is created
|
||||||
|
* or retrieved is implementation-dependent.
|
||||||
|
*
|
||||||
|
* @param session The session associated with the active WebSocket
|
||||||
|
* connection.
|
||||||
|
* @return A connected tunnel, or null if no such tunnel exists.
|
||||||
|
* @throws GuacamoleException If an error occurs while retrieving the
|
||||||
|
* tunnel, or if access to the tunnel is denied.
|
||||||
|
*/
|
||||||
|
protected abstract GuacamoleTunnel createTunnel(Session session)
|
||||||
|
throws GuacamoleException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWebSocketConnect(final Session session) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Get tunnel
|
||||||
|
tunnel = createTunnel(session);
|
||||||
|
if (tunnel == null) {
|
||||||
|
closeConnection(session, GuacamoleStatus.RESOURCE_NOT_FOUND);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
|
||||||
|
logger.debug("Error connecting WebSocket tunnel.", e);
|
||||||
|
closeConnection(session, e.getStatus());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare read transfer thread
|
||||||
|
Thread readThread = new Thread() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remote (client) side of this connection
|
||||||
|
*/
|
||||||
|
private final RemoteEndpoint remote = session.getRemote();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
|
||||||
|
GuacamoleReader reader = tunnel.acquireReader();
|
||||||
|
char[] readMessage;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Send tunnel UUID
|
||||||
|
remote.sendString(new GuacamoleInstruction(
|
||||||
|
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
|
||||||
|
tunnel.getUUID().toString()
|
||||||
|
).toString());
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Attempt to read
|
||||||
|
while ((readMessage = reader.read()) != null) {
|
||||||
|
|
||||||
|
// Buffer message
|
||||||
|
buffer.append(readMessage);
|
||||||
|
|
||||||
|
// Flush if we expect to wait or buffer is getting full
|
||||||
|
if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
|
||||||
|
remote.sendString(buffer.toString());
|
||||||
|
buffer.setLength(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// No more data
|
||||||
|
closeConnection(session, GuacamoleStatus.SUCCESS);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catch any thrown guacamole exception and attempt
|
||||||
|
// to pass within the WebSocket connection, logging
|
||||||
|
// each error appropriately.
|
||||||
|
catch (GuacamoleClientException e) {
|
||||||
|
logger.info("WebSocket connection terminated: {}", e.getMessage());
|
||||||
|
logger.debug("WebSocket connection terminated due to client error.", e);
|
||||||
|
closeConnection(session, e.getStatus());
|
||||||
|
}
|
||||||
|
catch (GuacamoleConnectionClosedException e) {
|
||||||
|
logger.debug("Connection to guacd closed.", e);
|
||||||
|
closeConnection(session, GuacamoleStatus.SUCCESS);
|
||||||
|
}
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
|
||||||
|
logger.debug("Internal error during connection to guacd.", e);
|
||||||
|
closeConnection(session, e.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
logger.debug("I/O error prevents further reads.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
readThread.start();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWebSocketText(String message) {
|
||||||
|
|
||||||
|
// Ignore inbound messages if there is no associated tunnel
|
||||||
|
if (tunnel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GuacamoleWriter writer = tunnel.acquireWriter();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Write received message
|
||||||
|
writer.write(message.toCharArray());
|
||||||
|
}
|
||||||
|
catch (GuacamoleConnectionClosedException e) {
|
||||||
|
logger.debug("Connection to guacd closed.", e);
|
||||||
|
}
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
logger.debug("WebSocket tunnel write failed.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnel.releaseWriter();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWebSocketBinary(byte[] payload, int offset, int length) {
|
||||||
|
throw new UnsupportedOperationException("Binary WebSocket messages are not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWebSocketError(Throwable t) {
|
||||||
|
|
||||||
|
logger.debug("WebSocket tunnel closing due to error.", t);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (tunnel != null)
|
||||||
|
tunnel.close();
|
||||||
|
}
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
logger.debug("Unable to close connection to guacd.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWebSocketClose(int statusCode, String reason) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (tunnel != null)
|
||||||
|
tunnel.close();
|
||||||
|
}
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
logger.debug("Unable to close connection to guacd.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* This file has been modified from the original, upstream version to facilitate
|
||||||
|
* integration with OpenUDS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.websocket.jetty9;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||||
|
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
||||||
|
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocketCreator which selects the appropriate WebSocketListener
|
||||||
|
* implementation if the "guacamole" subprotocol is in use.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public class RestrictedGuacamoleWebSocketCreator implements WebSocketCreator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for handling tunnel requests.
|
||||||
|
*/
|
||||||
|
private final TunnelRequestService tunnelRequestService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new WebSocketCreator which uses the given TunnelRequestService
|
||||||
|
* to create new GuacamoleTunnels for inbound requests.
|
||||||
|
*
|
||||||
|
* @param tunnelRequestService The service to use for inbound tunnel
|
||||||
|
* requests.
|
||||||
|
*/
|
||||||
|
public RestrictedGuacamoleWebSocketCreator(TunnelRequestService tunnelRequestService) {
|
||||||
|
this.tunnelRequestService = tunnelRequestService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object createWebSocket(UpgradeRequest request, UpgradeResponse response) {
|
||||||
|
|
||||||
|
// Validate and use "guacamole" subprotocol
|
||||||
|
for (String subprotocol : request.getSubProtocols()) {
|
||||||
|
|
||||||
|
if ("guacamole".equals(subprotocol)) {
|
||||||
|
response.setAcceptedSubProtocol(subprotocol);
|
||||||
|
return new RestrictedGuacamoleWebSocketTunnelListener(tunnelRequestService);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid protocol
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* This file has been modified from the original, upstream version to facilitate
|
||||||
|
* integration with OpenUDS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.websocket.jetty9;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket listener implementation which properly parses connection IDs
|
||||||
|
* included in the connection request.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public class RestrictedGuacamoleWebSocketTunnelListener extends GuacamoleWebSocketTunnelListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for handling tunnel requests.
|
||||||
|
*/
|
||||||
|
private final TunnelRequestService tunnelRequestService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new WebSocketListener which uses the given TunnelRequestService
|
||||||
|
* to create new GuacamoleTunnels for inbound requests.
|
||||||
|
*
|
||||||
|
* @param tunnelRequestService The service to use for inbound tunnel
|
||||||
|
* requests.
|
||||||
|
*/
|
||||||
|
public RestrictedGuacamoleWebSocketTunnelListener(TunnelRequestService tunnelRequestService) {
|
||||||
|
this.tunnelRequestService = tunnelRequestService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GuacamoleTunnel createTunnel(Session session) throws GuacamoleException {
|
||||||
|
return tunnelRequestService.createTunnel(new WebSocketTunnelRequest(session.getUpgradeRequest()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* This file has been modified from the original, upstream version to facilitate
|
||||||
|
* integration with OpenUDS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.websocket.jetty9;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
|
||||||
|
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class RestrictedGuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for handling tunnel requests.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private TunnelRequestService tunnelRequestService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(WebSocketServletFactory factory) {
|
||||||
|
|
||||||
|
// Register WebSocket implementation
|
||||||
|
factory.setCreator(new RestrictedGuacamoleWebSocketCreator(tunnelRequestService));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.websocket.jetty9;
|
||||||
|
|
||||||
|
import com.google.inject.servlet.ServletModule;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelLoader;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the Jetty 9 WebSocket tunnel implementation.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Attempt to find WebSocket servlet
|
||||||
|
Class.forName("org.apache.guacamole.tunnel.websocket.jetty9.RestrictedGuacamoleWebSocketTunnelServlet");
|
||||||
|
|
||||||
|
// Support found
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no such servlet class, this particular WebSocket support
|
||||||
|
// is not present
|
||||||
|
catch (ClassNotFoundException e) {}
|
||||||
|
catch (NoClassDefFoundError e) {}
|
||||||
|
|
||||||
|
// Support not found
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureServlets() {
|
||||||
|
|
||||||
|
logger.info("Loading Jetty 9 WebSocket support...");
|
||||||
|
serve("/websocket-tunnel").with(RestrictedGuacamoleWebSocketTunnelServlet.class);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.websocket.jetty9;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jetty 9 WebSocket-specific implementation of TunnelRequest.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public class WebSocketTunnelRequest extends TunnelRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All parameters passed via HTTP to the WebSocket handshake.
|
||||||
|
*/
|
||||||
|
private final Map<String, String[]> handshakeParameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a TunnelRequest implementation which delegates parameter and
|
||||||
|
* session retrieval to the given UpgradeRequest.
|
||||||
|
*
|
||||||
|
* @param request The UpgradeRequest to wrap.
|
||||||
|
*/
|
||||||
|
public WebSocketTunnelRequest(UpgradeRequest request) {
|
||||||
|
this.handshakeParameters = request.getParameterMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getParameter(String name) {
|
||||||
|
|
||||||
|
// Pull list of values, if present
|
||||||
|
List<String> values = getParameterValues(name);
|
||||||
|
if (values == null || values.isEmpty())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Return first parameter value arbitrarily
|
||||||
|
return values.get(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getParameterValues(String name) {
|
||||||
|
|
||||||
|
String[] values = handshakeParameters.get(name);
|
||||||
|
if (values == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return Arrays.asList(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jetty 9 WebSocket tunnel implementation. The classes here require at least
|
||||||
|
* Jetty 9, prior to Jetty 9.1 (when support for JSR 356 was implemented).
|
||||||
|
*/
|
||||||
|
package org.apache.guacamole.tunnel.websocket.jetty9;
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard WebSocket tunnel implementation. The classes here require a recent
|
||||||
|
* servlet container that supports JSR 356.
|
||||||
|
*/
|
||||||
|
package org.apache.guacamole.tunnel.websocket;
|
@ -0,0 +1,269 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.websocket.tomcat;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.io.GuacamoleReader;
|
||||||
|
import org.apache.guacamole.io.GuacamoleWriter;
|
||||||
|
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||||
|
import org.apache.catalina.websocket.StreamInbound;
|
||||||
|
import org.apache.catalina.websocket.WebSocketServlet;
|
||||||
|
import org.apache.catalina.websocket.WsOutbound;
|
||||||
|
import org.apache.guacamole.GuacamoleClientException;
|
||||||
|
import org.apache.guacamole.GuacamoleConnectionClosedException;
|
||||||
|
import org.apache.guacamole.protocol.GuacamoleInstruction;
|
||||||
|
import org.apache.guacamole.tunnel.http.HTTPTunnelRequest;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||||
|
import org.apache.guacamole.protocol.GuacamoleStatus;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default, minimum buffer size for instructions.
|
||||||
|
*/
|
||||||
|
private static final int BUFFER_SIZE = 8192;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(GuacamoleWebSocketTunnelServlet.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the given status on the given WebSocket connection and closes the
|
||||||
|
* connection.
|
||||||
|
*
|
||||||
|
* @param outbound The outbound WebSocket connection to close.
|
||||||
|
* @param guac_status The status to send.
|
||||||
|
*/
|
||||||
|
public void closeConnection(WsOutbound outbound, GuacamoleStatus guac_status) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] message = Integer.toString(guac_status.getGuacamoleStatusCode()).getBytes("UTF-8");
|
||||||
|
outbound.close(guac_status.getWebSocketCode(), ByteBuffer.wrap(message));
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
logger.debug("Unable to close WebSocket tunnel.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String selectSubProtocol(List<String> subProtocols) {
|
||||||
|
|
||||||
|
// Search for expected protocol
|
||||||
|
for (String protocol : subProtocols)
|
||||||
|
if ("guacamole".equals(protocol))
|
||||||
|
return "guacamole";
|
||||||
|
|
||||||
|
// Otherwise, fail
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreamInbound createWebSocketInbound(String protocol,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
|
||||||
|
final TunnelRequest tunnelRequest = new HTTPTunnelRequest(request);
|
||||||
|
|
||||||
|
// Return new WebSocket which communicates through tunnel
|
||||||
|
return new StreamInbound() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GuacamoleTunnel associated with the connected WebSocket. If
|
||||||
|
* the WebSocket has not yet been connected, this will be null.
|
||||||
|
*/
|
||||||
|
private GuacamoleTunnel tunnel = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onTextData(Reader reader) throws IOException {
|
||||||
|
|
||||||
|
// Ignore inbound messages if there is no associated tunnel
|
||||||
|
if (tunnel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GuacamoleWriter writer = tunnel.acquireWriter();
|
||||||
|
|
||||||
|
// Write all available data
|
||||||
|
try {
|
||||||
|
|
||||||
|
char[] buffer = new char[BUFFER_SIZE];
|
||||||
|
|
||||||
|
int num_read;
|
||||||
|
while ((num_read = reader.read(buffer)) > 0)
|
||||||
|
writer.write(buffer, 0, num_read);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (GuacamoleConnectionClosedException e) {
|
||||||
|
logger.debug("Connection to guacd closed.", e);
|
||||||
|
}
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
logger.debug("WebSocket tunnel write failed.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnel.releaseWriter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpen(final WsOutbound outbound) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
tunnel = doConnect(tunnelRequest);
|
||||||
|
}
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
|
||||||
|
logger.debug("Error connecting WebSocket tunnel.", e);
|
||||||
|
closeConnection(outbound, e.getStatus());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not start connection if tunnel does not exist
|
||||||
|
if (tunnel == null) {
|
||||||
|
closeConnection(outbound, GuacamoleStatus.RESOURCE_NOT_FOUND);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread readThread = new Thread() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
|
||||||
|
GuacamoleReader reader = tunnel.acquireReader();
|
||||||
|
char[] readMessage;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Send tunnel UUID
|
||||||
|
outbound.writeTextMessage(CharBuffer.wrap(new GuacamoleInstruction(
|
||||||
|
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
|
||||||
|
tunnel.getUUID().toString()
|
||||||
|
).toString()));
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Attempt to read
|
||||||
|
while ((readMessage = reader.read()) != null) {
|
||||||
|
|
||||||
|
// Buffer message
|
||||||
|
buffer.append(readMessage);
|
||||||
|
|
||||||
|
// Flush if we expect to wait or buffer is getting full
|
||||||
|
if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
|
||||||
|
outbound.writeTextMessage(CharBuffer.wrap(buffer));
|
||||||
|
buffer.setLength(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// No more data
|
||||||
|
closeConnection(outbound, GuacamoleStatus.SUCCESS);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catch any thrown guacamole exception and attempt
|
||||||
|
// to pass within the WebSocket connection, logging
|
||||||
|
// each error appropriately.
|
||||||
|
catch (GuacamoleClientException e) {
|
||||||
|
logger.info("WebSocket connection terminated: {}", e.getMessage());
|
||||||
|
logger.debug("WebSocket connection terminated due to client error.", e);
|
||||||
|
closeConnection(outbound, e.getStatus());
|
||||||
|
}
|
||||||
|
catch (GuacamoleConnectionClosedException e) {
|
||||||
|
logger.debug("Connection to guacd closed.", e);
|
||||||
|
closeConnection(outbound, GuacamoleStatus.SUCCESS);
|
||||||
|
}
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
|
||||||
|
logger.debug("Internal error during connection to guacd.", e);
|
||||||
|
closeConnection(outbound, e.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
logger.debug("I/O error prevents further reads.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
readThread.start();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose(int i) {
|
||||||
|
try {
|
||||||
|
if (tunnel != null)
|
||||||
|
tunnel.close();
|
||||||
|
}
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
logger.debug("Unable to close connection to guacd.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBinaryData(InputStream in) throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Not supported yet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever the JavaScript Guacamole client makes a connection
|
||||||
|
* request. It it up to the implementor of this function to define what
|
||||||
|
* conditions must be met for a tunnel to be configured and returned as a
|
||||||
|
* result of this connection request (whether some sort of credentials must
|
||||||
|
* be specified, for example).
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* The TunnelRequest associated with the connection request received.
|
||||||
|
* Any parameters specified along with the connection request can be
|
||||||
|
* read from this object.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A newly constructed GuacamoleTunnel if successful, null otherwise.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If an error occurs while constructing the GuacamoleTunnel, or if the
|
||||||
|
* conditions required for connection are not met.
|
||||||
|
*/
|
||||||
|
protected abstract GuacamoleTunnel doConnect(TunnelRequest request)
|
||||||
|
throws GuacamoleException;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* This file has been modified from the original, upstream version to facilitate
|
||||||
|
* integration with OpenUDS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.websocket.tomcat;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tunnel servlet implementation which uses WebSocket as a tunnel backend,
|
||||||
|
* rather than HTTP, properly parsing connection IDs included in the connection
|
||||||
|
* request.
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class RestrictedGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for handling tunnel requests.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private TunnelRequestService tunnelRequestService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GuacamoleTunnel doConnect(TunnelRequest request)
|
||||||
|
throws GuacamoleException {
|
||||||
|
return tunnelRequestService.createTunnel(request);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.guacamole.tunnel.websocket.tomcat;
|
||||||
|
|
||||||
|
import com.google.inject.servlet.ServletModule;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelLoader;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the Jetty 9 WebSocket tunnel implementation.
|
||||||
|
*
|
||||||
|
* @author Michael Jumper
|
||||||
|
*/
|
||||||
|
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Attempt to find WebSocket servlet
|
||||||
|
Class.forName("org.apache.guacamole.tunnel.websocket.tomcat.RestrictedGuacamoleWebSocketTunnelServlet");
|
||||||
|
|
||||||
|
// Support found
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no such servlet class, this particular WebSocket support
|
||||||
|
// is not present
|
||||||
|
catch (ClassNotFoundException e) {}
|
||||||
|
catch (NoClassDefFoundError e) {}
|
||||||
|
|
||||||
|
// Support not found
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureServlets() {
|
||||||
|
|
||||||
|
logger.info("Loading Tomcat 7 WebSocket support...");
|
||||||
|
serve("/websocket-tunnel").with(RestrictedGuacamoleWebSocketTunnelServlet.class);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tomcat WebSocket tunnel implementation. The classes here require at least
|
||||||
|
* Tomcat 7.0, and may change significantly as there is no common WebSocket
|
||||||
|
* API for Java yet.
|
||||||
|
*/
|
||||||
|
package org.apache.guacamole.tunnel.websocket.tomcat;
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Virtual Cable S.L.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.openuds.guacamole;
|
||||||
|
|
||||||
|
import com.google.inject.servlet.ServletModule;
|
||||||
|
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||||
|
import org.openuds.guacamole.config.ConfigurationService;
|
||||||
|
import org.openuds.guacamole.connection.ConnectionService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guice module which binds classes required by the OpenUDS integration of
|
||||||
|
* Apache Guacamole.
|
||||||
|
*/
|
||||||
|
public class UDSModule extends ServletModule {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureServlets() {
|
||||||
|
|
||||||
|
// Serve servlets, etc. with Guice
|
||||||
|
bind(GuiceContainer.class);
|
||||||
|
|
||||||
|
// Bind UDS-specific services
|
||||||
|
bind(ConfigurationService.class);
|
||||||
|
bind(ConnectionService.class);
|
||||||
|
bind(TunnelRequestService.class);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Virtual Cable S.L.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.openuds.guacamole;
|
||||||
|
|
||||||
|
import com.google.inject.Guice;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import com.google.inject.servlet.GuiceServletContextListener;
|
||||||
|
import org.apache.guacamole.tunnel.TunnelModule;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ServletContextListener implementation which initializes Guice and services
|
||||||
|
* specific to OpenUDS.
|
||||||
|
*/
|
||||||
|
public class UDSServletContextListener extends GuiceServletContextListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Injector getInjector() {
|
||||||
|
|
||||||
|
// Create an injector with OpenUDS- and Guacamole-specific services
|
||||||
|
// properly bound
|
||||||
|
return Guice.createInjector(
|
||||||
|
new UDSModule(),
|
||||||
|
new TunnelModule()
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,246 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Virtual Cable S.L.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.openuds.guacamole.config;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.Properties;
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.GuacamoleServerException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that provides access to configuration information stored within
|
||||||
|
* OpenUDS' tunnel.properties file.
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class ConfigurationService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(ConfigurationService.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the property within tunnel.properties which defines the file
|
||||||
|
* whose content dictates the base URL of the service providing connection
|
||||||
|
* configuration information.
|
||||||
|
*/
|
||||||
|
private static final String UDSFILE_PROPERTY = "udsfile";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path beneath the OpenUDS service base URI (scheme + hostname) at
|
||||||
|
* which the connection configuration service can be found. Currently, this
|
||||||
|
* is hard-coded as "/guacamole/".
|
||||||
|
*/
|
||||||
|
private static final String UDS_CONNECTION_PATH = "/guacamole/";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base URI (scheme + hostname) where OpenUDS is being served.
|
||||||
|
*/
|
||||||
|
private final URI udsBaseURI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the contents of the given file, reading the URI of the OpenUDS
|
||||||
|
* service contained therein. The file is expected to define this URI on
|
||||||
|
* the first line, and only the first line is read.
|
||||||
|
*
|
||||||
|
* @param udsFile
|
||||||
|
* The file from which the URI of the OpenUDS service should be read.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The URI of the OpenUDS service.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the file could not be opened or read for any reason, or if the
|
||||||
|
* line read from the file is not a valid URI.
|
||||||
|
*/
|
||||||
|
private URI readServiceURI(String udsFile) throws GuacamoleException {
|
||||||
|
|
||||||
|
// Open UDS file
|
||||||
|
BufferedReader input;
|
||||||
|
try {
|
||||||
|
input = new BufferedReader(new FileReader(udsFile));
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new GuacamoleServerException("Failed to open UDS file.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the first line (and only the first line) assuming it contains
|
||||||
|
// the URL of the OpenUDS service
|
||||||
|
try {
|
||||||
|
return new URI(input.readLine());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rethrow general failure to read from the file
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new GuacamoleServerException("Failed to read UDS service URI from file.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rethrow failure to parse the URL
|
||||||
|
catch (URISyntaxException e) {
|
||||||
|
throw new GuacamoleServerException("Failed to parse UDS service URI from file.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always close the file
|
||||||
|
finally {
|
||||||
|
|
||||||
|
try {
|
||||||
|
input.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
logger.warn("Closure of OpenUDS file failed. Resource may leak.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an arbitrary URI, returns a new URI which contains only the scheme
|
||||||
|
* and host. The path, fragment, etc. of the given URI, if any, are
|
||||||
|
* discarded.
|
||||||
|
*
|
||||||
|
* @param uri
|
||||||
|
* An arbitrary URI from which a base URI should be derived.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A new URI containing only the scheme and host of the provided URI.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the new URI could not be generated because the result is not a
|
||||||
|
* valid URI.
|
||||||
|
*/
|
||||||
|
private URI getBaseURI(URI uri) throws GuacamoleException {
|
||||||
|
|
||||||
|
// Build base URI from only the scheme and host of the given URI
|
||||||
|
try {
|
||||||
|
return new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(),
|
||||||
|
null, null, null);
|
||||||
|
}
|
||||||
|
catch (URISyntaxException e) {
|
||||||
|
throw new GuacamoleServerException("Failed to derive base URI.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ConfigurationService which provides access to the
|
||||||
|
* configuration information stored within the "/WEB-INF/tunnel.properties"
|
||||||
|
* file in the classpath. This file will be parsed immediately, but any
|
||||||
|
* resulting errors will simply be logged. If configuration information
|
||||||
|
* cannot be read, attempts to retrieve this information later through calls
|
||||||
|
* to the getters of this service will fail with appropriate exceptions.
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* The ServletContext associated with the servlet container which is
|
||||||
|
* serving this web application.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
public ConfigurationService(ServletContext context) {
|
||||||
|
|
||||||
|
// Read tunnel.properties
|
||||||
|
Properties config = new Properties();
|
||||||
|
try {
|
||||||
|
config.load(context.getResourceAsStream("/WEB-INF/tunnel.properties"));
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
logger.error("Unable to read tunnel.properties.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse URI from the UDS file (if defined)
|
||||||
|
URI parsedURI = null;
|
||||||
|
String udsFile = config.getProperty(UDSFILE_PROPERTY);
|
||||||
|
if (udsFile != null) {
|
||||||
|
|
||||||
|
// Attempt to parse base URI from the UDS file, logging any failures
|
||||||
|
try {
|
||||||
|
parsedURI = getBaseURI(readServiceURI(udsFile));
|
||||||
|
}
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
logger.error("OpenUDS service URI could not be parsed. This "
|
||||||
|
+ "web application WILL NOT FUNCTION.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no UDS file is defined, web application startup has failed
|
||||||
|
else
|
||||||
|
logger.error("Property \"{}\" not found within tunnel.properties. "
|
||||||
|
+ "This web application WILL NOT FUNCTION.", UDSFILE_PROPERTY);
|
||||||
|
|
||||||
|
// Assign the parsed URI, which may be null
|
||||||
|
udsBaseURI = parsedURI;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the base URI of the OpenUDS service. All services providing data
|
||||||
|
* to this Guacamole integration are hosted beneath this base URI.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The base URI of the OpenUDS service.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the base URI of the OpenUDS service is not defined because the
|
||||||
|
* tunnel.properties file could not be parsed when the web application
|
||||||
|
* started.
|
||||||
|
*/
|
||||||
|
public URI getUDSBaseURI() throws GuacamoleException {
|
||||||
|
|
||||||
|
// Explicitly fail if the configuration was not successfully read
|
||||||
|
if (udsBaseURI == null)
|
||||||
|
throw new GuacamoleServerException("The UDS base URI is not defined.");
|
||||||
|
|
||||||
|
return udsBaseURI;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the path beneath the OpenUDS base URI at which the connection
|
||||||
|
* configuration service can be found. This service is expected to respond
|
||||||
|
* to HTTP GET requests, returning the configuration of requested
|
||||||
|
* connections.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The path beneath the OpenUDS base URI at which the connection
|
||||||
|
* configuration service can be found.
|
||||||
|
*/
|
||||||
|
public String getUDSConnectionPath() {
|
||||||
|
return UDS_CONNECTION_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Virtual Cable S.L.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classes used to retrieve OpenUDS-specific configuration information.
|
||||||
|
*/
|
||||||
|
package org.openuds.guacamole.config;
|
@ -0,0 +1,200 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Virtual Cable S.L.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.openuds.guacamole.connection;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
import org.apache.guacamole.GuacamoleException;
|
||||||
|
import org.apache.guacamole.GuacamoleServerException;
|
||||||
|
import org.apache.guacamole.protocol.GuacamoleConfiguration;
|
||||||
|
import org.openuds.guacamole.config.ConfigurationService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service which communicates with the remote OpenUDS connection service,
|
||||||
|
* providing access to the underlying connection configuration.
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class ConnectionService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(ConnectionService.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the parameter returned by the OpenUDS connection
|
||||||
|
* configuration service which will contain the protocol that Guacamole
|
||||||
|
* should use to initiate the remote desktop connection.
|
||||||
|
*/
|
||||||
|
private static final String PROTOCOL_PARAMETER = "protocol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for retrieving configuration information.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private ConfigurationService configService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes an HTTP GET request to the OpenUDS service running at the given
|
||||||
|
* URI, parsing the response into connection configuration data. The
|
||||||
|
* response MUST be simple text, one line per connection parameter, with the
|
||||||
|
* name of the connection parameter separated from the corresponding value
|
||||||
|
* by a tab character. If the OpenUDS service encounters an error, it is
|
||||||
|
* expected to return the single word "ERROR" on one line. Lines which do
|
||||||
|
* not match these expectations will be skipped.
|
||||||
|
*
|
||||||
|
* @param uri
|
||||||
|
* The URI of the OpenUDS service to which the HTTP GET request should
|
||||||
|
* be made.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A map of all parameter name/value pairs returned by the OpenUDS
|
||||||
|
* service.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the OpenUDS service returns an error, or the response from the
|
||||||
|
* service cannot be read.
|
||||||
|
*/
|
||||||
|
private Map<String, String> readConnectionConfiguration(URI uri)
|
||||||
|
throws GuacamoleException {
|
||||||
|
|
||||||
|
BufferedReader response;
|
||||||
|
|
||||||
|
// Connect to OpenUDS
|
||||||
|
try {
|
||||||
|
URLConnection connection = uri.toURL().openConnection();
|
||||||
|
response = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new GuacamoleServerException("Unable to open connection to OpenUDS service.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> parameters = new HashMap<String, String>();
|
||||||
|
|
||||||
|
// Read and parse each line of the response
|
||||||
|
try {
|
||||||
|
|
||||||
|
String inputLine;
|
||||||
|
while ((inputLine = response.readLine()) != null) {
|
||||||
|
|
||||||
|
// Abort upon error
|
||||||
|
if (inputLine.equals("ERROR"))
|
||||||
|
throw new GuacamoleServerException("OpenUDS service returned an error.");
|
||||||
|
|
||||||
|
// Determine separation between each line's key and value
|
||||||
|
int tab = inputLine.indexOf('\t');
|
||||||
|
if (tab == -1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Add key/value pair from either side of the tab
|
||||||
|
parameters.put(
|
||||||
|
inputLine.substring(0, tab),
|
||||||
|
inputLine.substring(tab + 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rethrow any error which occurs during reading
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new GuacamoleServerException("Failed to read response from OpenUDS service.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always close the stream
|
||||||
|
finally {
|
||||||
|
|
||||||
|
try {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
logger.warn("Closure of connection to OpenUDS failed. Resource may leak.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameters have been successfully parsed
|
||||||
|
return parameters;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries OpenUDS for the connection configuration for the connection
|
||||||
|
* associated with the given data. This data is an opaque value provided
|
||||||
|
* via the "data" parameter to the Guacamole tunnel.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* The OpenUDS-specific data which defines the connection whose
|
||||||
|
* configuration should be retrieved.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The configuration of the connection associated with the provided
|
||||||
|
* OpenUDS-specific data.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the connection configuration could not be retrieved from OpenUDS,
|
||||||
|
* of the response from OpenUDS was missing required information.
|
||||||
|
*/
|
||||||
|
public GuacamoleConfiguration getConnectionConfiguration(String data)
|
||||||
|
throws GuacamoleException {
|
||||||
|
|
||||||
|
// Build URI of remote service from the base URI and given data
|
||||||
|
URI serviceURI = UriBuilder.fromUri(configService.getUDSBaseURI())
|
||||||
|
.path(configService.getUDSConnectionPath())
|
||||||
|
.path(data)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Pull connection configuration from remote service
|
||||||
|
Map<String, String> params = readConnectionConfiguration(serviceURI);
|
||||||
|
|
||||||
|
// Pull the protocol from the parameters
|
||||||
|
String protocol = params.remove(PROTOCOL_PARAMETER);
|
||||||
|
if (protocol == null)
|
||||||
|
throw new GuacamoleServerException("Protocol missing from OpenUDS response.");
|
||||||
|
|
||||||
|
// Create our configuration
|
||||||
|
GuacamoleConfiguration config = new GuacamoleConfiguration();
|
||||||
|
config.setProtocol(protocol);
|
||||||
|
config.setParameters(params);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Virtual Cable S.L.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classes used to communicate with OpenUDS' connection configuration web
|
||||||
|
* service.
|
||||||
|
*/
|
||||||
|
package org.openuds.guacamole.connection;
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Virtual Cable S.L.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classes associated with the OpenUDS integration of Apache Guacamole.
|
||||||
|
*/
|
||||||
|
package org.openuds.guacamole;
|
Loading…
x
Reference in New Issue
Block a user