|  |  |  | @@ -19,28 +19,12 @@ package org.glyptodon.guacamole.net.basic; | 
		
	
		
			
				|  |  |  |  |  */ | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | import java.io.IOException; | 
		
	
		
			
				|  |  |  |  | import java.util.Arrays; | 
		
	
		
			
				|  |  |  |  | import java.util.Collection; | 
		
	
		
			
				|  |  |  |  | import javax.servlet.ServletException; | 
		
	
		
			
				|  |  |  |  | import javax.servlet.http.HttpServletRequest; | 
		
	
		
			
				|  |  |  |  | import javax.servlet.http.HttpServletResponse; | 
		
	
		
			
				|  |  |  |  | import javax.servlet.http.HttpSession; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.GuacamoleClientException; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.GuacamoleException; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.GuacamoleSecurityException; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.net.GuacamoleSocket; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.net.GuacamoleTunnel; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.net.auth.Connection; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.net.auth.ConnectionGroup; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.net.auth.Credentials; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.net.auth.Directory; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.net.auth.UserContext; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.net.basic.event.SessionListenerCollection; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.net.event.TunnelCloseEvent; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.net.event.TunnelConnectEvent; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.net.event.listener.TunnelCloseListener; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.net.event.listener.TunnelConnectListener; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.protocol.GuacamoleClientInformation; | 
		
	
		
			
				|  |  |  |  | import org.glyptodon.guacamole.servlet.GuacamoleHTTPTunnelServlet; | 
		
	
		
			
				|  |  |  |  | import org.slf4j.Logger; | 
		
	
		
			
				|  |  |  |  | import org.slf4j.LoggerFactory; | 
		
	
	
		
			
				
					
					|  |  |  | @@ -139,230 +123,6 @@ public class BasicGuacamoleTunnelServlet extends AuthenticatingHttpServlet { | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     /** | 
		
	
		
			
				|  |  |  |  |      * Notifies all listeners in the given collection that a tunnel has been | 
		
	
		
			
				|  |  |  |  |      * connected. | 
		
	
		
			
				|  |  |  |  |      * | 
		
	
		
			
				|  |  |  |  |      * @param listeners A collection of all listeners that should be notified. | 
		
	
		
			
				|  |  |  |  |      * @param context The UserContext associated with the current session. | 
		
	
		
			
				|  |  |  |  |      * @param credentials The credentials associated with the current session. | 
		
	
		
			
				|  |  |  |  |      * @param tunnel The tunnel being connected. | 
		
	
		
			
				|  |  |  |  |      * @return true if all listeners are allowing the tunnel to connect, | 
		
	
		
			
				|  |  |  |  |      *         or if there are no listeners, and false if any listener is | 
		
	
		
			
				|  |  |  |  |      *         canceling the connection. Note that once one listener cancels, | 
		
	
		
			
				|  |  |  |  |      *         no other listeners will run. | 
		
	
		
			
				|  |  |  |  |      * @throws GuacamoleException If any listener throws an error while being | 
		
	
		
			
				|  |  |  |  |      *                            notified. Note that if any listener throws an | 
		
	
		
			
				|  |  |  |  |      *                            error, the connect is canceled, and no other | 
		
	
		
			
				|  |  |  |  |      *                            listeners will run. | 
		
	
		
			
				|  |  |  |  |      */ | 
		
	
		
			
				|  |  |  |  |     public static boolean notifyConnect(Collection listeners, UserContext context, | 
		
	
		
			
				|  |  |  |  |             Credentials credentials, GuacamoleTunnel tunnel) | 
		
	
		
			
				|  |  |  |  |             throws GuacamoleException { | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Build event for auth success | 
		
	
		
			
				|  |  |  |  |         TunnelConnectEvent event = new TunnelConnectEvent(context, | 
		
	
		
			
				|  |  |  |  |                 credentials, tunnel); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Notify all listeners | 
		
	
		
			
				|  |  |  |  |         for (Object listener : listeners) { | 
		
	
		
			
				|  |  |  |  |             if (listener instanceof TunnelConnectListener) { | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |                 // Cancel immediately if hook returns false | 
		
	
		
			
				|  |  |  |  |                 if (!((TunnelConnectListener) listener).tunnelConnected(event)) | 
		
	
		
			
				|  |  |  |  |                     return false; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |             } | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         return true; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     /** | 
		
	
		
			
				|  |  |  |  |      * Notifies all listeners in the given collection that a tunnel has been | 
		
	
		
			
				|  |  |  |  |      * closed. | 
		
	
		
			
				|  |  |  |  |      * | 
		
	
		
			
				|  |  |  |  |      * @param listeners A collection of all listeners that should be notified. | 
		
	
		
			
				|  |  |  |  |      * @param context The UserContext associated with the current session. | 
		
	
		
			
				|  |  |  |  |      * @param credentials The credentials associated with the current session. | 
		
	
		
			
				|  |  |  |  |      * @param tunnel The tunnel being closed. | 
		
	
		
			
				|  |  |  |  |      * @return true if all listeners are allowing the tunnel to close, | 
		
	
		
			
				|  |  |  |  |      *         or if there are no listeners, and false if any listener is | 
		
	
		
			
				|  |  |  |  |      *         canceling the close. Note that once one listener cancels, | 
		
	
		
			
				|  |  |  |  |      *         no other listeners will run. | 
		
	
		
			
				|  |  |  |  |      * @throws GuacamoleException If any listener throws an error while being | 
		
	
		
			
				|  |  |  |  |      *                            notified. Note that if any listener throws an | 
		
	
		
			
				|  |  |  |  |      *                            error, the close is canceled, and no other | 
		
	
		
			
				|  |  |  |  |      *                            listeners will run. | 
		
	
		
			
				|  |  |  |  |      */ | 
		
	
		
			
				|  |  |  |  |     public static boolean notifyClose(Collection listeners, UserContext context, | 
		
	
		
			
				|  |  |  |  |             Credentials credentials, GuacamoleTunnel tunnel) | 
		
	
		
			
				|  |  |  |  |             throws GuacamoleException { | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Build event for auth success | 
		
	
		
			
				|  |  |  |  |         TunnelCloseEvent event = new TunnelCloseEvent(context, | 
		
	
		
			
				|  |  |  |  |                 credentials, tunnel); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Notify all listeners | 
		
	
		
			
				|  |  |  |  |         for (Object listener : listeners) { | 
		
	
		
			
				|  |  |  |  |             if (listener instanceof TunnelCloseListener) { | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |                 // Cancel immediately if hook returns false | 
		
	
		
			
				|  |  |  |  |                 if (!((TunnelCloseListener) listener).tunnelClosed(event)) | 
		
	
		
			
				|  |  |  |  |                     return false; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |             } | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         return true; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     /** | 
		
	
		
			
				|  |  |  |  |      * 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 static GuacamoleTunnel createTunnel(HttpServletRequest request) | 
		
	
		
			
				|  |  |  |  |             throws GuacamoleException { | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         HttpSession httpSession = request.getSession(true); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Get listeners | 
		
	
		
			
				|  |  |  |  |         final SessionListenerCollection listeners; | 
		
	
		
			
				|  |  |  |  |         try { | 
		
	
		
			
				|  |  |  |  |             listeners = new SessionListenerCollection(httpSession); | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |         catch (GuacamoleException e) { | 
		
	
		
			
				|  |  |  |  |             logger.error("Failed to retrieve listeners. Authentication canceled.", e); | 
		
	
		
			
				|  |  |  |  |             throw e; | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Get ID of connection | 
		
	
		
			
				|  |  |  |  |         String id = request.getParameter("id"); | 
		
	
		
			
				|  |  |  |  |         IdentifierType id_type = IdentifierType.getType(id); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Do not continue if unable to determine type | 
		
	
		
			
				|  |  |  |  |         if (id_type == null) | 
		
	
		
			
				|  |  |  |  |             throw new GuacamoleClientException("Illegal identifier - unknown type."); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Remove prefix | 
		
	
		
			
				|  |  |  |  |         id = id.substring(id_type.PREFIX.length()); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Get credentials | 
		
	
		
			
				|  |  |  |  |         final Credentials credentials = getCredentials(httpSession); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Get context | 
		
	
		
			
				|  |  |  |  |         final UserContext context = getUserContext(httpSession); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // If no context or no credentials, not logged in | 
		
	
		
			
				|  |  |  |  |         if (context == null || credentials == null) | 
		
	
		
			
				|  |  |  |  |             throw new GuacamoleSecurityException("Cannot connect - user not logged in."); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Get client information | 
		
	
		
			
				|  |  |  |  |         GuacamoleClientInformation info = new GuacamoleClientInformation(); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Set width if provided | 
		
	
		
			
				|  |  |  |  |         String width  = request.getParameter("width"); | 
		
	
		
			
				|  |  |  |  |         if (width != null) | 
		
	
		
			
				|  |  |  |  |             info.setOptimalScreenWidth(Integer.parseInt(width)); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Set height if provided | 
		
	
		
			
				|  |  |  |  |         String height = request.getParameter("height"); | 
		
	
		
			
				|  |  |  |  |         if (height != null) | 
		
	
		
			
				|  |  |  |  |             info.setOptimalScreenHeight(Integer.parseInt(height)); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Add audio mimetypes | 
		
	
		
			
				|  |  |  |  |         String[] audio_mimetypes = request.getParameterValues("audio"); | 
		
	
		
			
				|  |  |  |  |         if (audio_mimetypes != null) | 
		
	
		
			
				|  |  |  |  |             info.getAudioMimetypes().addAll(Arrays.asList(audio_mimetypes)); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Add video mimetypes | 
		
	
		
			
				|  |  |  |  |         String[] video_mimetypes = request.getParameterValues("video"); | 
		
	
		
			
				|  |  |  |  |         if (video_mimetypes != null) | 
		
	
		
			
				|  |  |  |  |             info.getVideoMimetypes().addAll(Arrays.asList(video_mimetypes)); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Create connected socket from identifier | 
		
	
		
			
				|  |  |  |  |         GuacamoleSocket socket; | 
		
	
		
			
				|  |  |  |  |         switch (id_type) { | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |             // Connection identifiers | 
		
	
		
			
				|  |  |  |  |             case CONNECTION: { | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |                 // Get connection directory | 
		
	
		
			
				|  |  |  |  |                 Directory<String, Connection> directory = | 
		
	
		
			
				|  |  |  |  |                     context.getRootConnectionGroup().getConnectionDirectory(); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |                 // Get authorized connection | 
		
	
		
			
				|  |  |  |  |                 Connection connection = directory.get(id); | 
		
	
		
			
				|  |  |  |  |                 if (connection == null) { | 
		
	
		
			
				|  |  |  |  |                     logger.warn("Connection id={} not found.", id); | 
		
	
		
			
				|  |  |  |  |                     throw new GuacamoleSecurityException("Requested connection is not authorized."); | 
		
	
		
			
				|  |  |  |  |                 } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |                 // Connect socket | 
		
	
		
			
				|  |  |  |  |                 socket = connection.connect(info); | 
		
	
		
			
				|  |  |  |  |                 logger.info("Successful connection from {} to \"{}\".", request.getRemoteAddr(), id); | 
		
	
		
			
				|  |  |  |  |                 break; | 
		
	
		
			
				|  |  |  |  |             } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |             // Connection group identifiers | 
		
	
		
			
				|  |  |  |  |             case CONNECTION_GROUP: { | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |                 // Get connection group directory | 
		
	
		
			
				|  |  |  |  |                 Directory<String, ConnectionGroup> directory = | 
		
	
		
			
				|  |  |  |  |                     context.getRootConnectionGroup().getConnectionGroupDirectory(); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |                 // Get authorized connection group | 
		
	
		
			
				|  |  |  |  |                 ConnectionGroup group = directory.get(id); | 
		
	
		
			
				|  |  |  |  |                 if (group == null) { | 
		
	
		
			
				|  |  |  |  |                     logger.warn("Connection group id={} not found.", id); | 
		
	
		
			
				|  |  |  |  |                     throw new GuacamoleSecurityException("Requested connection group is not authorized."); | 
		
	
		
			
				|  |  |  |  |                 } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |                 // Connect socket | 
		
	
		
			
				|  |  |  |  |                 socket = group.connect(info); | 
		
	
		
			
				|  |  |  |  |                 logger.info("Successful connection from {} to group \"{}\".", request.getRemoteAddr(), id); | 
		
	
		
			
				|  |  |  |  |                 break; | 
		
	
		
			
				|  |  |  |  |             } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |             // Fail if unsupported type | 
		
	
		
			
				|  |  |  |  |             default: | 
		
	
		
			
				|  |  |  |  |                 throw new GuacamoleClientException("Connection not supported for provided identifier type."); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Associate socket with tunnel | 
		
	
		
			
				|  |  |  |  |         GuacamoleTunnel tunnel = new GuacamoleTunnel(socket) { | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |             @Override | 
		
	
		
			
				|  |  |  |  |             public void close() throws GuacamoleException { | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |                 // Only close if not canceled | 
		
	
		
			
				|  |  |  |  |                 if (!notifyClose(listeners, context, credentials, this)) | 
		
	
		
			
				|  |  |  |  |                     throw new GuacamoleException("Tunnel close canceled by listener."); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |                 // Close if no exception due to listener | 
		
	
		
			
				|  |  |  |  |                 super.close(); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |             } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         }; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         // Notify listeners about connection | 
		
	
		
			
				|  |  |  |  |         if (!notifyConnect(listeners, context, credentials, tunnel)) { | 
		
	
		
			
				|  |  |  |  |             logger.info("Connection canceled by listener."); | 
		
	
		
			
				|  |  |  |  |             return null; | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         return tunnel; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     /** | 
		
	
		
			
				|  |  |  |  |      * Wrapped GuacamoleHTTPTunnelServlet which will handle all authenticated | 
		
	
		
			
				|  |  |  |  |      * requests. | 
		
	
	
		
			
				
					
					|  |  |  | @@ -371,7 +131,7 @@ public class BasicGuacamoleTunnelServlet extends AuthenticatingHttpServlet { | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |         @Override | 
		
	
		
			
				|  |  |  |  |         protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException { | 
		
	
		
			
				|  |  |  |  |             return createTunnel(request); | 
		
	
		
			
				|  |  |  |  |             return BasicTunnelRequestUtility.createTunnel(request); | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     }; | 
		
	
	
		
			
				
					
					|  |  |  |   |