void* ssh_client_thread(void* data) { guac_client* client = (guac_client*) data; ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; guac_socket* socket = client->socket; char buffer[8192]; pthread_t input_thread; /* Init SSH base libraries */ if (guac_common_ssh_init(client)) return NULL; /* Get user and credentials */ client_data->user = guac_ssh_get_user(client); /* Send new name */ char name[1024]; snprintf(name, sizeof(name)-1, "%s@%s", client_data->username, client_data->hostname); guac_protocol_send_name(socket, name); /* Open SSH session */ client_data->session = guac_common_ssh_create_session(client, client_data->hostname, client_data->port, client_data->user); if (client_data->session == NULL) { /* Already aborted within guac_common_ssh_create_session() */ return NULL; } pthread_mutex_init(&client_data->term_channel_lock, NULL); /* Open channel for terminal */ client_data->term_channel = libssh2_channel_open_session(client_data->session->session); if (client_data->term_channel == NULL) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to open terminal channel."); return NULL; } #ifdef ENABLE_SSH_AGENT /* Start SSH agent forwarding, if enabled */ if (client_data->enable_agent) { libssh2_session_callback_set(client_data->session, LIBSSH2_CALLBACK_AUTH_AGENT, (void*) ssh_auth_agent_callback); /* Request agent forwarding */ if (libssh2_channel_request_auth_agent(client_data->term_channel)) guac_client_log(client, GUAC_LOG_ERROR, "Agent forwarding request failed"); else guac_client_log(client, GUAC_LOG_INFO, "Agent forwarding enabled."); } client_data->auth_agent = NULL; #endif /* Start SFTP session as well, if enabled */ if (client_data->enable_sftp) { /* Create SSH session specific for SFTP */ guac_client_log(client, GUAC_LOG_DEBUG, "Reconnecting for SFTP..."); client_data->sftp_session = guac_common_ssh_create_session(client, client_data->hostname, client_data->port, client_data->user); if (client_data->sftp_session == NULL) { /* Already aborted within guac_common_ssh_create_session() */ return NULL; } /* Request SFTP */ client_data->sftp_filesystem = guac_common_ssh_create_sftp_filesystem( client_data->sftp_session, "/"); /* Set generic (non-filesystem) file upload handler */ client->file_handler = guac_sftp_file_handler; /* Init handlers for Guacamole-specific console codes */ client_data->term->upload_path_handler = guac_sftp_set_upload_path; client_data->term->file_download_handler = guac_sftp_download_file; guac_client_log(client, GUAC_LOG_DEBUG, "SFTP session initialized"); } /* Request PTY */ if (libssh2_channel_request_pty_ex(client_data->term_channel, "linux", sizeof("linux")-1, NULL, 0, client_data->term->term_width, client_data->term->term_height, 0, 0)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to allocate PTY."); return NULL; } /* Request shell */ if (libssh2_channel_shell(client_data->term_channel)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to associate shell with PTY."); return NULL; } /* Logged in */ guac_client_log(client, GUAC_LOG_INFO, "SSH connection successful."); /* Start input thread */ if (pthread_create(&(input_thread), NULL, ssh_input_thread, (void*) client)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start input thread"); return NULL; } /* Set non-blocking */ libssh2_session_set_blocking(client_data->session->session, 0); /* While data available, write to terminal */ int bytes_read = 0; for (;;) { /* Track total amount of data read */ int total_read = 0; pthread_mutex_lock(&(client_data->term_channel_lock)); /* Stop reading at EOF */ if (libssh2_channel_eof(client_data->term_channel)) { pthread_mutex_unlock(&(client_data->term_channel_lock)); break; } /* Read terminal data */ bytes_read = libssh2_channel_read(client_data->term_channel, buffer, sizeof(buffer)); pthread_mutex_unlock(&(client_data->term_channel_lock)); /* Attempt to write data received. Exit on failure. */ if (bytes_read > 0) { int written = guac_terminal_write_stdout(client_data->term, buffer, bytes_read); if (written < 0) break; total_read += bytes_read; } else if (bytes_read < 0 && bytes_read != LIBSSH2_ERROR_EAGAIN) break; #ifdef ENABLE_SSH_AGENT /* If agent open, handle any agent packets */ if (client_data->auth_agent != NULL) { bytes_read = ssh_auth_agent_read(client_data->auth_agent); if (bytes_read > 0) total_read += bytes_read; else if (bytes_read < 0 && bytes_read != LIBSSH2_ERROR_EAGAIN) client_data->auth_agent = NULL; } #endif /* Wait for more data if reads turn up empty */ if (total_read == 0) { fd_set fds; struct timeval timeout; FD_ZERO(&fds); FD_SET(client_data->session->fd, &fds); /* Wait for one second */ timeout.tv_sec = 1; timeout.tv_usec = 0; if (select(client_data->session->fd + 1, &fds, NULL, NULL, &timeout) < 0) break; } } /* Kill client and Wait for input thread to die */ guac_client_stop(client); pthread_join(input_thread, NULL); pthread_mutex_destroy(&client_data->term_channel_lock); guac_client_log(client, GUAC_LOG_INFO, "SSH connection ended."); return NULL; }
void* ssh_client_thread(void* data) { guac_client* client = (guac_client*) data; guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; guac_ssh_settings* settings = ssh_client->settings; char buffer[8192]; pthread_t input_thread; /* Init SSH base libraries */ if (guac_common_ssh_init(client)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "SSH library initialization failed"); return NULL; } /* Set up screen recording, if requested */ if (settings->recording_path != NULL) { guac_common_recording_create(client, settings->recording_path, settings->recording_name, settings->create_recording_path); } /* Create terminal */ ssh_client->term = guac_terminal_create(client, settings->font_name, settings->font_size, settings->resolution, settings->width, settings->height, settings->color_scheme); /* Fail if terminal init failed */ if (ssh_client->term == NULL) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Terminal initialization failed"); return NULL; } /* Set up typescript, if requested */ if (settings->typescript_path != NULL) { guac_terminal_create_typescript(ssh_client->term, settings->typescript_path, settings->typescript_name, settings->create_typescript_path); } /* Get user and credentials */ ssh_client->user = guac_ssh_get_user(client); /* Open SSH session */ ssh_client->session = guac_common_ssh_create_session(client, settings->hostname, settings->port, ssh_client->user); if (ssh_client->session == NULL) { /* Already aborted within guac_common_ssh_create_session() */ return NULL; } pthread_mutex_init(&ssh_client->term_channel_lock, NULL); /* Open channel for terminal */ ssh_client->term_channel = libssh2_channel_open_session(ssh_client->session->session); if (ssh_client->term_channel == NULL) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to open terminal channel."); return NULL; } #ifdef ENABLE_SSH_AGENT /* Start SSH agent forwarding, if enabled */ if (ssh_client->enable_agent) { libssh2_session_callback_set(ssh_client->session, LIBSSH2_CALLBACK_AUTH_AGENT, (void*) ssh_auth_agent_callback); /* Request agent forwarding */ if (libssh2_channel_request_auth_agent(ssh_client->term_channel)) guac_client_log(client, GUAC_LOG_ERROR, "Agent forwarding request failed"); else guac_client_log(client, GUAC_LOG_INFO, "Agent forwarding enabled."); } ssh_client->auth_agent = NULL; #endif /* Start SFTP session as well, if enabled */ if (settings->enable_sftp) { /* Create SSH session specific for SFTP */ guac_client_log(client, GUAC_LOG_DEBUG, "Reconnecting for SFTP..."); ssh_client->sftp_session = guac_common_ssh_create_session(client, settings->hostname, settings->port, ssh_client->user); if (ssh_client->sftp_session == NULL) { /* Already aborted within guac_common_ssh_create_session() */ return NULL; } /* Request SFTP */ ssh_client->sftp_filesystem = guac_common_ssh_create_sftp_filesystem( ssh_client->sftp_session, "/"); /* Expose filesystem to connection owner */ guac_client_for_owner(client, guac_common_ssh_expose_sftp_filesystem, ssh_client->sftp_filesystem); /* Init handlers for Guacamole-specific console codes */ ssh_client->term->upload_path_handler = guac_sftp_set_upload_path; ssh_client->term->file_download_handler = guac_sftp_download_file; guac_client_log(client, GUAC_LOG_DEBUG, "SFTP session initialized"); } /* Request PTY */ if (libssh2_channel_request_pty_ex(ssh_client->term_channel, "linux", sizeof("linux")-1, NULL, 0, ssh_client->term->term_width, ssh_client->term->term_height, 0, 0)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to allocate PTY."); return NULL; } /* If a command is specified, run that instead of a shell */ if (settings->command != NULL) { if (libssh2_channel_exec(ssh_client->term_channel, settings->command)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to execute command."); return NULL; } } /* Otherwise, request a shell */ else if (libssh2_channel_shell(ssh_client->term_channel)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to associate shell with PTY."); return NULL; } /* Logged in */ guac_client_log(client, GUAC_LOG_INFO, "SSH connection successful."); /* Start input thread */ if (pthread_create(&(input_thread), NULL, ssh_input_thread, (void*) client)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start input thread"); return NULL; } /* Set non-blocking */ libssh2_session_set_blocking(ssh_client->session->session, 0); /* While data available, write to terminal */ int bytes_read = 0; for (;;) { /* Track total amount of data read */ int total_read = 0; pthread_mutex_lock(&(ssh_client->term_channel_lock)); /* Stop reading at EOF */ if (libssh2_channel_eof(ssh_client->term_channel)) { pthread_mutex_unlock(&(ssh_client->term_channel_lock)); break; } /* Read terminal data */ bytes_read = libssh2_channel_read(ssh_client->term_channel, buffer, sizeof(buffer)); pthread_mutex_unlock(&(ssh_client->term_channel_lock)); /* Attempt to write data received. Exit on failure. */ if (bytes_read > 0) { int written = guac_terminal_write_stdout(ssh_client->term, buffer, bytes_read); if (written < 0) break; total_read += bytes_read; } else if (bytes_read < 0 && bytes_read != LIBSSH2_ERROR_EAGAIN) break; #ifdef ENABLE_SSH_AGENT /* If agent open, handle any agent packets */ if (ssh_client->auth_agent != NULL) { bytes_read = ssh_auth_agent_read(ssh_client->auth_agent); if (bytes_read > 0) total_read += bytes_read; else if (bytes_read < 0 && bytes_read != LIBSSH2_ERROR_EAGAIN) ssh_client->auth_agent = NULL; } #endif /* Wait for more data if reads turn up empty */ if (total_read == 0) { fd_set fds; struct timeval timeout; FD_ZERO(&fds); FD_SET(ssh_client->session->fd, &fds); /* Wait for one second */ timeout.tv_sec = 1; timeout.tv_usec = 0; if (select(ssh_client->session->fd + 1, &fds, NULL, NULL, &timeout) < 0) break; } } /* Kill client and Wait for input thread to die */ guac_client_stop(client); pthread_join(input_thread, NULL); pthread_mutex_destroy(&ssh_client->term_channel_lock); guac_client_log(client, GUAC_LOG_INFO, "SSH connection ended."); return NULL; }