client.c (10022B)
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 "client.h" 21#include "channels/audio-input/audio-buffer.h" 22#include "channels/cliprdr.h" 23#include "channels/disp.h" 24#include "channels/pipe-svc.h" 25#include "config.h" 26#include "fs.h" 27#include "log.h" 28#include "rdp.h" 29#include "settings.h" 30#include "user.h" 31 32#ifdef ENABLE_COMMON_SSH 33#include "common-ssh/sftp.h" 34#include "common-ssh/ssh.h" 35#include "common-ssh/user.h" 36#endif 37 38#include <guacamole/audio.h> 39#include <guacamole/client.h> 40#include <guacamole/mem.h> 41#include <guacamole/recording.h> 42 43#include <dirent.h> 44#include <errno.h> 45#include <fcntl.h> 46#include <pthread.h> 47#include <pwd.h> 48#include <stdlib.h> 49#include <string.h> 50#include <sys/types.h> 51#include <unistd.h> 52 53/** 54 * Tests whether the given path refers to a directory which the current user 55 * can write to. If the given path is not a directory, is not writable, or is 56 * not a link pointing to a writable directory, this test will fail, and 57 * errno will be set appropriately. 58 * 59 * @param path 60 * The path to test. 61 * 62 * @return 63 * Non-zero if the given path is (or points to) a writable directory, zero 64 * otherwise. 65 */ 66static int is_writable_directory(const char* path) { 67 68 /* Verify path is writable */ 69 if (faccessat(AT_FDCWD, path, W_OK, 0)) 70 return 0; 71 72 /* If writable, verify path is actually a directory */ 73 DIR* dir = opendir(path); 74 if (!dir) 75 return 0; 76 77 /* Path is both writable and a directory */ 78 closedir(dir); 79 return 1; 80 81} 82 83/** 84 * Add the provided user to the provided audio stream. 85 * 86 * @param user 87 * The pending user who should be added to the audio stream. 88 * 89 * @param data 90 * The audio stream that the user should be added to. 91 * 92 * @return 93 * Always NULL. 94 */ 95static void* guac_rdp_sync_pending_user_audio(guac_user* user, void* data) { 96 97 /* Add the user to the stream */ 98 guac_audio_stream* audio = (guac_audio_stream*) data; 99 guac_audio_stream_add_user(audio, user); 100 101 return NULL; 102 103} 104 105/** 106 * A pending join handler implementation that will synchronize the connection 107 * state for all pending users prior to them being promoted to full user. 108 * 109 * @param client 110 * The client whose pending users are about to be promoted. 111 * 112 * @return 113 * Always zero. 114 */ 115static int guac_rdp_join_pending_handler(guac_client* client) { 116 117 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 118 guac_socket* broadcast_socket = client->pending_socket; 119 120 /* Synchronize any audio stream for each pending user */ 121 if (rdp_client->audio) 122 guac_client_foreach_pending_user( 123 client, guac_rdp_sync_pending_user_audio, rdp_client->audio); 124 125 /* Bring user up to date with any registered static channels */ 126 guac_rdp_pipe_svc_send_pipes(client, broadcast_socket); 127 128 /* Synchronize with current display */ 129 if (rdp_client->display != NULL) { 130 guac_common_display_dup(rdp_client->display, client, broadcast_socket); 131 guac_socket_flush(broadcast_socket); 132 } 133 134 return 0; 135 136} 137 138int guac_client_init(guac_client* client, int argc, char** argv) { 139 140 /* Automatically set HOME environment variable if unset (FreeRDP's 141 * initialization process will fail within freerdp_settings_new() if this 142 * is unset) */ 143 const char* current_home = getenv("HOME"); 144 if (current_home == NULL) { 145 146 /* Warn if the correct home directory cannot be determined */ 147 struct passwd* passwd = getpwuid(getuid()); 148 if (passwd == NULL) 149 guac_client_log(client, GUAC_LOG_WARNING, "FreeRDP initialization " 150 "may fail: The \"HOME\" environment variable is unset and " 151 "its correct value could not be automatically determined: " 152 "%s", strerror(errno)); 153 154 /* Warn if the correct home directory could be determined but can't be 155 * assigned */ 156 else if (setenv("HOME", passwd->pw_dir, 1)) 157 guac_client_log(client, GUAC_LOG_WARNING, "FreeRDP initialization " 158 "may fail: The \"HOME\" environment variable is unset " 159 "and its correct value (detected as \"%s\") could not be " 160 "assigned: %s", passwd->pw_dir, strerror(errno)); 161 162 /* HOME has been successfully set */ 163 else { 164 guac_client_log(client, GUAC_LOG_DEBUG, "\"HOME\" " 165 "environment variable was unset and has been " 166 "automatically set to \"%s\"", passwd->pw_dir); 167 current_home = passwd->pw_dir; 168 } 169 170 } 171 172 /* Verify that detected home directory is actually writable and actually a 173 * directory, as FreeRDP initialization will mysteriously fail otherwise */ 174 if (current_home != NULL && !is_writable_directory(current_home)) { 175 if (errno == EACCES) 176 guac_client_log(client, GUAC_LOG_WARNING, "FreeRDP initialization " 177 "may fail: The current user's home directory (\"%s\") is " 178 "not writable, but FreeRDP generally requires a writable " 179 "home directory for storage of configuration files and " 180 "certificates.", current_home); 181 else if (errno == ENOTDIR) 182 guac_client_log(client, GUAC_LOG_WARNING, "FreeRDP initialization " 183 "may fail: The current user's home directory (\"%s\") is " 184 "not actually a directory, but FreeRDP generally requires " 185 "a writable home directory for storage of configuration " 186 "files and certificates.", current_home); 187 else 188 guac_client_log(client, GUAC_LOG_WARNING, "FreeRDP initialization " 189 "may fail: Writability of the current user's home " 190 "directory (\"%s\") could not be determined: %s", 191 current_home, strerror(errno)); 192 } 193 194 /* Set client args */ 195 client->args = GUAC_RDP_CLIENT_ARGS; 196 197 /* Alloc client data */ 198 guac_rdp_client* rdp_client = guac_mem_zalloc(sizeof(guac_rdp_client)); 199 client->data = rdp_client; 200 201 /* Init clipboard */ 202 rdp_client->clipboard = guac_rdp_clipboard_alloc(client); 203 204 /* Init display update module */ 205 rdp_client->disp = guac_rdp_disp_alloc(client); 206 207 /* Init multi-touch support module (RDPEI) */ 208 rdp_client->rdpei = guac_rdp_rdpei_alloc(client); 209 210 /* Redirect FreeRDP log messages to guac_client_log() */ 211 guac_rdp_redirect_wlog(client); 212 213 /* Recursive attribute for locks */ 214 pthread_mutexattr_init(&(rdp_client->attributes)); 215 pthread_mutexattr_settype(&(rdp_client->attributes), 216 PTHREAD_MUTEX_RECURSIVE); 217 218 /* Init required locks */ 219 pthread_rwlock_init(&(rdp_client->lock), NULL); 220 pthread_mutex_init(&(rdp_client->message_lock), &(rdp_client->attributes)); 221 222 /* Set handlers */ 223 client->join_handler = guac_rdp_user_join_handler; 224 client->join_pending_handler = guac_rdp_join_pending_handler; 225 client->free_handler = guac_rdp_client_free_handler; 226 client->leave_handler = guac_rdp_user_leave_handler; 227 228#ifdef ENABLE_COMMON_SSH 229 guac_common_ssh_init(client); 230#endif 231 232 return 0; 233 234} 235 236int guac_rdp_client_free_handler(guac_client* client) { 237 238 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 239 240 /* Wait for client thread */ 241 pthread_join(rdp_client->client_thread, NULL); 242 243 /* Free parsed settings */ 244 if (rdp_client->settings != NULL) 245 guac_rdp_settings_free(rdp_client->settings); 246 247 /* Clean up clipboard */ 248 guac_rdp_clipboard_free(rdp_client->clipboard); 249 250 /* Free display update module */ 251 guac_rdp_disp_free(rdp_client->disp); 252 253 /* Free multi-touch support module (RDPEI) */ 254 guac_rdp_rdpei_free(rdp_client->rdpei); 255 256 /* Clean up filesystem, if allocated */ 257 if (rdp_client->filesystem != NULL) 258 guac_rdp_fs_free(rdp_client->filesystem); 259 260 /* End active print job, if any */ 261 guac_rdp_print_job* job = (guac_rdp_print_job*) rdp_client->active_job; 262 if (job != NULL) { 263 guac_rdp_print_job_kill(job); 264 guac_rdp_print_job_free(job); 265 rdp_client->active_job = NULL; 266 } 267 268#ifdef ENABLE_COMMON_SSH 269 /* Free SFTP filesystem, if loaded */ 270 if (rdp_client->sftp_filesystem) 271 guac_common_ssh_destroy_sftp_filesystem(rdp_client->sftp_filesystem); 272 273 /* Free SFTP session */ 274 if (rdp_client->sftp_session) 275 guac_common_ssh_destroy_session(rdp_client->sftp_session); 276 277 /* Free SFTP user */ 278 if (rdp_client->sftp_user) 279 guac_common_ssh_destroy_user(rdp_client->sftp_user); 280 281 guac_common_ssh_uninit(); 282#endif 283 284 /* Clean up recording, if in progress */ 285 if (rdp_client->recording != NULL) 286 guac_recording_free(rdp_client->recording); 287 288 /* Clean up audio stream, if allocated */ 289 if (rdp_client->audio != NULL) 290 guac_audio_stream_free(rdp_client->audio); 291 292 /* Clean up audio input buffer, if allocated */ 293 if (rdp_client->audio_input != NULL) 294 guac_rdp_audio_buffer_free(rdp_client->audio_input); 295 296 pthread_rwlock_destroy(&(rdp_client->lock)); 297 pthread_mutex_destroy(&(rdp_client->message_lock)); 298 299 /* Free client data */ 300 guac_mem_free(rdp_client); 301 302 return 0; 303 304} 305