user-handshake.c (12367B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20#include "config.h" 21 22#include "guacamole/mem.h" 23#include "guacamole/client.h" 24#include "guacamole/error.h" 25#include "guacamole/parser.h" 26#include "guacamole/protocol.h" 27#include "guacamole/socket.h" 28#include "guacamole/user.h" 29#include "user-handlers.h" 30 31#include <pthread.h> 32#include <stdlib.h> 33#include <string.h> 34 35/** 36 * Parameters required by the user input thread. 37 */ 38typedef struct guac_user_input_thread_params { 39 40 /** 41 * The parser which will be used throughout the user's session. 42 */ 43 guac_parser* parser; 44 45 /** 46 * A reference to the connected user. 47 */ 48 guac_user* user; 49 50 /** 51 * The number of microseconds to wait for instructions from a connected 52 * user before closing the connection with an error. 53 */ 54 int usec_timeout; 55 56} guac_user_input_thread_params; 57 58/** 59 * Prints an error message using the logging facilities of the given user, 60 * automatically including any information present in guac_error. 61 * 62 * @param user 63 * The guac_user associated with the error that occurred. 64 * 65 * @param level 66 * The level at which to log this message. 67 * 68 * @param message 69 * The message to log. 70 */ 71static void guac_user_log_guac_error(guac_user* user, 72 guac_client_log_level level, const char* message) { 73 74 if (guac_error != GUAC_STATUS_SUCCESS) { 75 76 /* If error message provided, include in log */ 77 if (guac_error_message != NULL) 78 guac_user_log(user, level, "%s: %s", message, 79 guac_error_message); 80 81 /* Otherwise just log with standard status string */ 82 else 83 guac_user_log(user, level, "%s: %s", message, 84 guac_status_string(guac_error)); 85 86 } 87 88 /* Just log message if no status code */ 89 else 90 guac_user_log(user, level, "%s", message); 91 92} 93 94/** 95 * Logs a reasonable explanatory message regarding handshake failure based on 96 * the current value of guac_error. 97 * 98 * @param user 99 * The guac_user associated with the failed Guacamole protocol handshake. 100 */ 101static void guac_user_log_handshake_failure(guac_user* user) { 102 103 if (guac_error == GUAC_STATUS_CLOSED) 104 guac_user_log(user, GUAC_LOG_DEBUG, 105 "Guacamole connection closed during handshake"); 106 else if (guac_error == GUAC_STATUS_PROTOCOL_ERROR) 107 guac_user_log(user, GUAC_LOG_ERROR, 108 "Guacamole protocol violation. Perhaps the version of " 109 "guacamole-client is incompatible with this version of " 110 "libguac?"); 111 else 112 guac_user_log(user, GUAC_LOG_WARNING, 113 "Guacamole handshake failed: %s", 114 guac_status_string(guac_error)); 115 116} 117 118/** 119 * The thread which handles all user input, calling event handlers for received 120 * instructions. 121 * 122 * @param data 123 * A pointer to a guac_user_input_thread_params structure describing the 124 * user whose input is being handled and the guac_parser with which to 125 * handle it. 126 * 127 * @return 128 * Always NULL. 129 */ 130static void* guac_user_input_thread(void* data) { 131 132 guac_user_input_thread_params* params = 133 (guac_user_input_thread_params*) data; 134 135 int usec_timeout = params->usec_timeout; 136 guac_user* user = params->user; 137 guac_parser* parser = params->parser; 138 guac_client* client = user->client; 139 guac_socket* socket = user->socket; 140 141 /* Guacamole user input loop */ 142 while (client->state == GUAC_CLIENT_RUNNING && user->active) { 143 144 /* Read instruction, stop on error */ 145 if (guac_parser_read(parser, socket, usec_timeout)) { 146 147 if (guac_error == GUAC_STATUS_TIMEOUT) 148 guac_user_abort(user, GUAC_PROTOCOL_STATUS_CLIENT_TIMEOUT, "User is not responding."); 149 150 else { 151 if (guac_error != GUAC_STATUS_CLOSED) 152 guac_user_log_guac_error(user, GUAC_LOG_WARNING, 153 "Guacamole connection failure"); 154 guac_user_stop(user); 155 } 156 157 return NULL; 158 } 159 160 /* Reset guac_error and guac_error_message (user/client handlers are not 161 * guaranteed to set these) */ 162 guac_error = GUAC_STATUS_SUCCESS; 163 guac_error_message = NULL; 164 165 /* Call handler, stop on error */ 166 if (__guac_user_call_opcode_handler(__guac_instruction_handler_map, 167 user, parser->opcode, parser->argc, parser->argv)) { 168 169 /* Log error */ 170 guac_user_log_guac_error(user, GUAC_LOG_WARNING, 171 "User connection aborted"); 172 173 /* Log handler details */ 174 guac_user_log(user, GUAC_LOG_DEBUG, "Failing instruction handler in user was \"%s\"", parser->opcode); 175 176 guac_user_stop(user); 177 return NULL; 178 } 179 180 } 181 182 return NULL; 183 184} 185 186/** 187 * Starts the input/output threads of a new user. This function will block 188 * until the user disconnects. If an error prevents the input/output threads 189 * from starting, guac_user_stop() will be invoked on the given user. 190 * 191 * @param parser 192 * The guac_parser to use to handle all input from the given user. 193 * 194 * @param user 195 * The user whose associated I/O transfer threads should be started. 196 * 197 * @param usec_timeout 198 * The number of microseconds to wait for instructions from the given 199 * user before closing the connection with an error. 200 * 201 * @return 202 * Zero if the I/O threads started successfully and user has disconnected, 203 * or non-zero if the I/O threads could not be started. 204 */ 205static int guac_user_start(guac_parser* parser, guac_user* user, 206 int usec_timeout) { 207 208 guac_user_input_thread_params params = { 209 .parser = parser, 210 .user = user, 211 .usec_timeout = usec_timeout 212 }; 213 214 pthread_t input_thread; 215 216 if (pthread_create(&input_thread, NULL, guac_user_input_thread, (void*) ¶ms)) { 217 guac_user_log(user, GUAC_LOG_ERROR, "Unable to start input thread"); 218 guac_user_stop(user); 219 return -1; 220 } 221 222 /* Wait for I/O threads */ 223 pthread_join(input_thread, NULL); 224 225 /* Explicitly signal disconnect */ 226 guac_protocol_send_disconnect(user->socket); 227 guac_socket_flush(user->socket); 228 229 /* Done */ 230 return 0; 231 232} 233 234/** 235 * This function loops through the received instructions during the handshake 236 * with the client attempting to join the connection, and runs the handlers 237 * for each of the opcodes, ending when the connect instruction is received. 238 * Returns zero if the handshake completes successfully with the connect opcode, 239 * or a non-zero value if an error occurs. 240 * 241 * @param user 242 * The guac_user attempting to join the connection. 243 * 244 * @param parser 245 * The parser used to examine the received data. 246 * 247 * @param usec_timeout 248 * The timeout, in microseconds, for reading the instructions. 249 * 250 * @return 251 * Zero if the handshake completes successfully with the connect opcode, 252 * or non-zero if an error occurs. 253 */ 254static int __guac_user_handshake(guac_user* user, guac_parser* parser, 255 int usec_timeout) { 256 257 guac_socket* socket = user->socket; 258 259 /* Handle each of the opcodes. */ 260 while (guac_parser_read(parser, socket, usec_timeout) == 0) { 261 262 /* If we receive the connect opcode, we're done. */ 263 if (strcmp(parser->opcode, "connect") == 0) 264 return 0; 265 266 guac_user_log(user, GUAC_LOG_DEBUG, "Processing instruction: %s", 267 parser->opcode); 268 269 /* Run instruction handler for opcode with arguments. */ 270 if (__guac_user_call_opcode_handler(__guac_handshake_handler_map, user, 271 parser->opcode, parser->argc, parser->argv)) { 272 273 guac_user_log_handshake_failure(user); 274 guac_user_log_guac_error(user, GUAC_LOG_DEBUG, 275 "Error handling instruction during handshake."); 276 guac_user_log(user, GUAC_LOG_DEBUG, "Failed opcode: %s", 277 parser->opcode); 278 279 guac_parser_free(parser); 280 return 1; 281 282 } 283 284 } 285 286 /* If we get here it's because we never got the connect instruction. */ 287 guac_user_log(user, GUAC_LOG_ERROR, 288 "Handshake failed, \"connect\" instruction was not received."); 289 return 1; 290} 291 292int guac_user_handle_connection(guac_user* user, int usec_timeout) { 293 294 guac_socket* socket = user->socket; 295 guac_client* client = user->client; 296 297 user->info.audio_mimetypes = NULL; 298 user->info.image_mimetypes = NULL; 299 user->info.video_mimetypes = NULL; 300 user->info.name = NULL; 301 user->info.timezone = NULL; 302 303 /* Count number of arguments. */ 304 int num_args; 305 for (num_args = 0; client->args[num_args] != NULL; num_args++); 306 307 /* Send args */ 308 if (guac_protocol_send_args(socket, client->args) 309 || guac_socket_flush(socket)) { 310 311 /* Log error */ 312 guac_user_log_handshake_failure(user); 313 guac_user_log_guac_error(user, GUAC_LOG_DEBUG, 314 "Error sending \"args\" to new user"); 315 316 return 1; 317 } 318 319 guac_parser* parser = guac_parser_alloc(); 320 321 /* Perform the handshake with the client. */ 322 if (__guac_user_handshake(user, parser, usec_timeout)) { 323 guac_parser_free(parser); 324 return 1; 325 } 326 327 /* Acknowledge connection availability */ 328 guac_protocol_send_ready(socket, client->connection_id); 329 guac_socket_flush(socket); 330 331 /* Verify argument count. */ 332 if (parser->argc != (num_args + 1)) { 333 guac_client_log(client, GUAC_LOG_ERROR, "Client did not return the " 334 "expected number of arguments."); 335 return 1; 336 } 337 338 /* Attempt to join user to connection. */ 339 if (guac_client_add_user(client, user, (parser->argc - 1), parser->argv + 1)) 340 guac_client_log(client, GUAC_LOG_ERROR, "User \"%s\" could NOT " 341 "join connection \"%s\"", user->user_id, client->connection_id); 342 343 /* Begin user connection if join successful */ 344 else { 345 346 guac_client_log(client, GUAC_LOG_INFO, "User \"%s\" joined connection " 347 "\"%s\" (%i users now present)", user->user_id, 348 client->connection_id, client->connected_users); 349 if (strcmp(parser->argv[0],"") != 0) { 350 guac_client_log(client, GUAC_LOG_DEBUG, "Client is using protocol " 351 "version \"%s\"", parser->argv[0]); 352 user->info.protocol_version = guac_protocol_string_to_version(parser->argv[0]); 353 } 354 else { 355 guac_client_log(client, GUAC_LOG_DEBUG, "Client has not defined " 356 "its protocol version."); 357 user->info.protocol_version = GUAC_PROTOCOL_VERSION_1_0_0; 358 } 359 360 /* Handle user I/O, wait for connection to terminate */ 361 guac_user_start(parser, user, usec_timeout); 362 363 /* Remove/free user */ 364 guac_client_remove_user(client, user); 365 guac_client_log(client, GUAC_LOG_INFO, "User \"%s\" disconnected (%i " 366 "users remain)", user->user_id, client->connected_users); 367 368 } 369 370 /* Free mimetype character arrays. */ 371 guac_free_mimetypes((char **) user->info.audio_mimetypes); 372 guac_free_mimetypes((char **) user->info.image_mimetypes); 373 guac_free_mimetypes((char **) user->info.video_mimetypes); 374 375 /* Free name and timezone info. */ 376 guac_mem_free_const(user->info.name); 377 guac_mem_free_const(user->info.timezone); 378 379 guac_parser_free(parser); 380 381 /* Successful disconnect */ 382 return 0; 383 384} 385