Remove use of X-Guacamole-Error-Message header, return null rather than throwing...
[guacamole.git] / src / main / java / net / sourceforge / guacamole / net / basic / BasicGuacamoleTunnelServlet.java
1 package net.sourceforge.guacamole.net.basic;
2
3 /*
4  *  Guacamole - Clientless Remote Desktop
5  *  Copyright (C) 2010  Michael Jumper
6  *
7  *  This program is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU Affero General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU Affero General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Affero General Public License
18  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20
21 import java.io.IOException;
22 import java.util.Collection;
23 import java.util.Map;
24 import javax.servlet.ServletException;
25 import javax.servlet.http.HttpServletRequest;
26 import javax.servlet.http.HttpServletResponse;
27 import javax.servlet.http.HttpSession;
28 import net.sourceforge.guacamole.GuacamoleException;
29 import net.sourceforge.guacamole.net.InetGuacamoleSocket;
30 import net.sourceforge.guacamole.protocol.GuacamoleConfiguration;
31 import net.sourceforge.guacamole.properties.GuacamoleProperties;
32 import net.sourceforge.guacamole.net.GuacamoleSocket;
33 import net.sourceforge.guacamole.net.GuacamoleTunnel;
34 import net.sourceforge.guacamole.net.auth.Credentials;
35 import net.sourceforge.guacamole.net.basic.event.SessionListenerCollection;
36 import net.sourceforge.guacamole.net.event.TunnelCloseEvent;
37 import net.sourceforge.guacamole.net.event.TunnelConnectEvent;
38 import net.sourceforge.guacamole.net.event.listener.TunnelCloseListener;
39 import net.sourceforge.guacamole.net.event.listener.TunnelConnectListener;
40 import net.sourceforge.guacamole.protocol.ConfiguredGuacamoleSocket;
41 import net.sourceforge.guacamole.servlet.GuacamoleHTTPTunnelServlet;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * Connects users to a tunnel associated with the authorized configuration
47  * having the given ID.
48  * 
49  * @author Michael Jumper
50  */
51 public class BasicGuacamoleTunnelServlet extends AuthenticatingHttpServlet {
52
53     private Logger logger = LoggerFactory.getLogger(BasicGuacamoleTunnelServlet.class);
54
55     @Override
56     protected void authenticatedService(
57             Map<String, GuacamoleConfiguration> configs,
58             HttpServletRequest request, HttpServletResponse response)
59     throws IOException, ServletException {
60         
61         // If authenticated, respond as tunnel
62         tunnelServlet.service(request, response);
63         
64     }
65
66     /**
67      * Notifies all listeners in the given collection that a tunnel has been
68      * connected.
69      * 
70      * @param listeners A collection of all listeners that should be notified.
71      * @param credentials The credentials associated with the authentication
72      *                    request that connected the tunnel.
73      * @return true if all listeners are allowing the tunnel to connect,
74      *         or if there are no listeners, and false if any listener is
75      *         canceling the connection. Note that once one listener cancels,
76      *         no other listeners will run.
77      * @throws GuacamoleException If any listener throws an error while being
78      *                            notified. Note that if any listener throws an
79      *                            error, the connect is canceled, and no other
80      *                            listeners will run.
81      */
82     private boolean notifyConnect(Collection listeners,
83             Credentials credentials, GuacamoleTunnel tunnel)
84             throws GuacamoleException {
85         
86         // Build event for auth success
87         TunnelConnectEvent event = new TunnelConnectEvent(credentials, tunnel);
88         
89         // Notify all listeners
90         for (Object listener : listeners) {
91             if (listener instanceof TunnelConnectListener) {
92
93                 // Cancel immediately if hook returns false
94                 if (!((TunnelConnectListener) listener).tunnelConnected(event))
95                     return false;
96                 
97             }
98         }
99
100         return true;
101         
102     }
103
104     /**
105      * Notifies all listeners in the given collection that a tunnel has been
106      * closed.
107      * 
108      * @param listeners A collection of all listeners that should be notified.
109      * @param credentials The credentials associated with the authentication
110      *                    request that closed the tunnel.
111      * @return true if all listeners are allowing the tunnel to close,
112      *         or if there are no listeners, and false if any listener is
113      *         canceling the close. Note that once one listener cancels,
114      *         no other listeners will run.
115      * @throws GuacamoleException If any listener throws an error while being
116      *                            notified. Note that if any listener throws an
117      *                            error, the close is canceled, and no other
118      *                            listeners will run.
119      */
120     private boolean notifyClose(Collection listeners,
121             Credentials credentials, GuacamoleTunnel tunnel)
122             throws GuacamoleException {
123         
124         // Build event for auth success
125         TunnelCloseEvent event = new TunnelCloseEvent(credentials, tunnel);
126         
127         // Notify all listeners
128         for (Object listener : listeners) {
129             if (listener instanceof TunnelCloseListener) {
130
131                 // Cancel immediately if hook returns false
132                 if (!((TunnelCloseListener) listener).tunnelClosed(event))
133                     return false;
134                 
135             }
136         }
137
138         return true;
139         
140     }
141
142     /**
143      * Wrapped GuacamoleHTTPTunnelServlet which will handle all authenticated
144      * requests.
145      */
146     private GuacamoleHTTPTunnelServlet tunnelServlet = new GuacamoleHTTPTunnelServlet() {
147
148         @Override
149         protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException {
150
151             HttpSession httpSession = request.getSession(true);
152             
153             // Get listeners
154             final SessionListenerCollection listeners;
155             try {
156                 listeners = new SessionListenerCollection(httpSession);
157             }
158             catch (GuacamoleException e) {
159                 logger.error("Failed to retrieve listeners. Authentication canceled.", e);
160                 throw e;
161             }
162
163             // Get ID of connection
164             String id = request.getParameter("id");
165             
166             // Get credentials
167             final Credentials credentials = getCredentials(httpSession);
168             
169             // Get authorized configs
170             Map<String, GuacamoleConfiguration> configs = getConfigurations(httpSession);
171
172             // If no configs/credentials in session, not authorized
173             if (credentials == null || configs == null)
174                 throw new GuacamoleException("Cannot connect - user not logged in.");
175
176             // Get authorized config
177             GuacamoleConfiguration config = configs.get(id);
178             if (config == null) {
179                 logger.error("Configuration id={} not found.", id);
180                 return null;
181             }
182             
183             logger.info("Successful connection from {} to \"{}\".", request.getRemoteAddr(), id);
184
185             // Configure and connect socket
186             String hostname = GuacamoleProperties.getProperty(GuacamoleProperties.GUACD_HOSTNAME);
187             int port = GuacamoleProperties.getProperty(GuacamoleProperties.GUACD_PORT);
188
189             GuacamoleSocket socket = new ConfiguredGuacamoleSocket(
190                     new InetGuacamoleSocket(hostname, port),
191                     config
192             );
193
194             // Associate socket with tunnel
195             GuacamoleTunnel tunnel = new GuacamoleTunnel(socket) {
196
197                 @Override
198                 public void close() throws GuacamoleException {
199
200                     // Only close if not canceled
201                     if (!notifyClose(listeners, credentials, this))
202                         throw new GuacamoleException("Tunnel close canceled by listener.");
203                     
204                     // Close if no exception due to listener
205                     super.close();
206                     
207                 }
208                 
209             };
210
211             // Notify listeners about connection
212             if (!notifyConnect(listeners, credentials, tunnel)) {
213                 logger.info("Connection canceled by listener.");
214                 return null;
215             }
216             
217             return tunnel;
218
219         }
220
221     };
222
223 }
224