From 0210b7dc6b1dd76179833aad965c48008b4791fd Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 11 Feb 2017 10:55:45 -0800 Subject: [PATCH 1/3] GUACAMOLE-208: Add protocol status codes representing upstream network errors and session behavior. --- src/libguac/guacamole/protocol-types.h | 41 ++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/libguac/guacamole/protocol-types.h b/src/libguac/guacamole/protocol-types.h index ea76910d..b85d36e7 100644 --- a/src/libguac/guacamole/protocol-types.h +++ b/src/libguac/guacamole/protocol-types.h @@ -35,8 +35,8 @@ * * 0x0000 - 0x00FF: Successful operations. * 0x0100 - 0x01FF: Operations that failed due to implementation status. - * 0x0200 - 0x02FF: Operations that failed due to environmental. - * 0x0300 - 0x03FF: Operations that failed due to user action. + * 0x0200 - 0x02FF: Operations that failed due to remote state/environment. + * 0x0300 - 0x03FF: Operations that failed due to user/client action. * * There is a general correspondence of these status codes with HTTP response * codes. @@ -67,25 +67,25 @@ typedef enum guac_protocol_status { * The operation could not be performed because the upstream server * is not responding. */ - GUAC_PROTOCOL_STATUS_UPSTREAM_TIMEOUT = 0x202, + GUAC_PROTOCOL_STATUS_UPSTREAM_TIMEOUT = 0x0202, /** * The operation was unsuccessful due to an error or otherwise * unexpected condition of the upstream server. */ - GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR = 0x203, + GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR = 0x0203, /** * The operation could not be performed as the requested resource * does not exist. */ - GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND = 0x204, + GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND = 0x0204, /** * The operation could not be performed as the requested resource is * already in use. */ - GUAC_PROTOCOL_STATUS_RESOURCE_CONFLICT = 0x205, + GUAC_PROTOCOL_STATUS_RESOURCE_CONFLICT = 0x0205, /** * The operation could not be performed as the requested resource is now @@ -93,6 +93,35 @@ typedef enum guac_protocol_status { */ GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED = 0x0206, + /** + * The operation could not be performed because the upstream server does + * not appear to exist. + */ + GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND = 0x0207, + + /** + * The operation could not be performed because the upstream server is not + * available to service the request. + */ + GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE = 0x0208, + + /** + * The session within the upstream server has ended because it conflicted + * with another session. + */ + GUAC_PROTOCOL_STATUS_SESSION_CONFLICT = 0x0209, + + /** + * The session within the upstream server has ended because it appeared to + * be inactive. + */ + GUAC_PROTOCOL_STATUS_SESSION_TIMEOUT = 0x020A, + + /** + * The session within the upstream server has been forcibly terminated. + */ + GUAC_PROTOCOL_STATUS_SESSION_CLOSED = 0x020B, + /** * The operation could not be performed because bad parameters were * given. From a78d52e615429f7a8d3fd52e3bc63be340a42587 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 11 Feb 2017 11:53:34 -0800 Subject: [PATCH 2/3] GUACAMOLE-208: Handle RDP disconnect reason codes. --- src/protocols/rdp/Makefile.am | 2 + src/protocols/rdp/error.c | 146 ++++++++++++++++++++++++++++++++++ src/protocols/rdp/error.h | 36 +++++++++ src/protocols/rdp/rdp.c | 17 ++-- 4 files changed, 192 insertions(+), 9 deletions(-) create mode 100644 src/protocols/rdp/error.c create mode 100644 src/protocols/rdp/error.h diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index a190003b..85b5fb57 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -29,6 +29,7 @@ libguac_client_rdp_la_SOURCES = \ audio_input.c \ client.c \ dvc.c \ + error.c \ input.c \ keyboard.c \ ptr_string.c \ @@ -96,6 +97,7 @@ noinst_HEADERS = \ audio_input.h \ client.h \ dvc.h \ + error.h \ input.h \ keyboard.h \ ptr_string.h \ diff --git a/src/protocols/rdp/error.c b/src/protocols/rdp/error.c new file mode 100644 index 00000000..e3403625 --- /dev/null +++ b/src/protocols/rdp/error.c @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "error.h" +#include "rdp.h" + +#include +#include +#include +#include + +void guac_rdp_client_abort(guac_client* client) { + + /* + * NOTE: The RDP status codes translated here are documented within + * [MS-RDPBCGR], section 2.2.5.1.1: "Set Error Info PDU Data", in the + * description of the "errorInfo" field. + * + * https://msdn.microsoft.com/en-us/library/cc240544.aspx + */ + + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + freerdp* rdp_inst = rdp_client->rdp_inst; + + guac_protocol_status status; + const char* message; + + /* Read disconnect reason code from connection */ + int error_info = freerdp_error_info(rdp_inst); + + /* Translate reason code into Guacamole protocol status */ + switch (error_info) { + + /* Normal disconnect */ + case 0x0: /* ERRINFO_SUCCESS */ + status = GUAC_PROTOCOL_STATUS_SUCCESS; + message = "Disconnected."; + break; + + /* Forced disconnect (possibly by admin) */ + case 0x1: /* ERRINFO_RPC_INITIATED_DISCONNECT */ + status = GUAC_PROTOCOL_STATUS_SESSION_CLOSED; + message = "Forcibly disconnected."; + break; + + /* The user was logged off (possibly by admin) */ + case 0x2: /* ERRINFO_RPC_INITIATED_LOGOFF */ + status = GUAC_PROTOCOL_STATUS_SESSION_CLOSED; + message = "Logged off."; + break; + + /* The user was idle long enough that the RDP server disconnected */ + case 0x3: /* ERRINFO_IDLE_TIMEOUT */ + status = GUAC_PROTOCOL_STATUS_SESSION_TIMEOUT; + message = "Idle session time limit exceeded."; + break; + + /* The user's session has been active for too long */ + case 0x4: /* ERRINFO_LOGON_TIMEOUT */ + status = GUAC_PROTOCOL_STATUS_SESSION_CLOSED; + message = "Active session time limit exceeded."; + break; + + /* Another user logged on, disconnecting this user */ + case 0x5: /* ERRINFO_DISCONNECTED_BY_OTHER_CONNECTION */ + status = GUAC_PROTOCOL_STATUS_SESSION_CONFLICT; + message = "Disconnected by other connection."; + break; + + /* The RDP server is refusing to service the connection */ + case 0x6: /* ERRINFO_OUT_OF_MEMORY */ + case 0x7: /* ERRINFO_SERVER_DENIED_CONNECTION */ + status = GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE; + message = "Server refused connection."; + break; + + /* The user does not have permission to connect */ + case 0x9: /* ERRINFO_SERVER_INSUFFICIENT_PRIVILEGES */ + status = GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN; + message = "Insufficient privileges."; + break; + + /* The user's credentials have expired */ + case 0xA: /* ERRINFO_SERVER_FRESH_CREDENTIALS_REQUIRED */ + status = GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN; + message = "Credentials expired."; + break; + + /* The user manually disconnected using an administrative tool within + * the session */ + case 0xB: /* ERRINFO_RPC_INITIATED_DISCONNECT_BYUSER */ + status = GUAC_PROTOCOL_STATUS_SUCCESS; + message = "Manually disconnected."; + break; + + /* The user manually logged off */ + case 0xC: /* ERRINFO_LOGOFF_BY_USER */ + status = GUAC_PROTOCOL_STATUS_SUCCESS; + message = "Manually logged off."; + break; + + /* Unimplemented/unknown disconnect reason code */ + default: + status = GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR; + message = "Upstream error."; + + } + + /* Send error code if an error occurred */ + if (status != GUAC_PROTOCOL_STATUS_SUCCESS) { + guac_protocol_send_error(client->socket, message, status); + guac_socket_flush(client->socket); + } + + /* Log human-readable description of disconnect at info level */ + guac_client_log(client, GUAC_LOG_INFO, "RDP server closed connection: %s", + message); + + /* Log internal disconnect reason code at debug level */ + if (error_info) + guac_client_log(client, GUAC_LOG_DEBUG, "Disconnect reason " + "code: 0x%X.", error_info); + + /* Abort connection */ + guac_client_stop(client); + +} + diff --git a/src/protocols/rdp/error.h b/src/protocols/rdp/error.h new file mode 100644 index 00000000..469bd4a8 --- /dev/null +++ b/src/protocols/rdp/error.h @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_RDP_ERROR_H +#define GUAC_RDP_ERROR_H + +#include + +/** + * Stops the current connection due to the RDP server disconnecting. If the RDP + * server provided a reason for disconnecting, that reason will be logged, and + * an appropriate error code will be sent to the Guacamole client. + * + * @param client + * The Guacamole client to disconnect. + */ +void guac_rdp_client_abort(guac_client* client); + +#endif + diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index 389c7049..8269348b 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -25,6 +25,7 @@ #include "common/display.h" #include "common/recording.h" #include "dvc.h" +#include "error.h" #include "keyboard.h" #include "rdp.h" #include "rdp_bitmap.h" @@ -626,7 +627,7 @@ static int rdp_guac_client_wait_for_messages(guac_client* client, return 0; /* Otherwise, return as error */ - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE, "Error waiting for file descriptor."); return -1; @@ -721,7 +722,7 @@ static int guac_rdp_handle_connection(guac_client* client) { /* Connect to RDP server */ if (!freerdp_connect(rdp_inst)) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND, "Error connecting to RDP server"); return 1; } @@ -763,7 +764,7 @@ static int guac_rdp_handle_connection(guac_client* client) { /* Check the libfreerdp fds */ if (!freerdp_check_fds(rdp_inst)) { guac_client_abort(client, - GUAC_PROTOCOL_STATUS_SERVER_ERROR, + GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE, "Error handling RDP file descriptors"); pthread_mutex_unlock(&(rdp_client->rdp_lock)); return 1; @@ -772,7 +773,7 @@ static int guac_rdp_handle_connection(guac_client* client) { /* Check channel fds */ if (!freerdp_channels_check_fds(channels, rdp_inst)) { guac_client_abort(client, - GUAC_PROTOCOL_STATUS_SERVER_ERROR, + GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE, "Error handling RDP channel file descriptors"); pthread_mutex_unlock(&(rdp_client->rdp_lock)); return 1; @@ -801,9 +802,7 @@ static int guac_rdp_handle_connection(guac_client* client) { /* Handle RDP disconnect */ if (freerdp_shall_disconnect(rdp_inst)) { - guac_client_stop(client); - guac_client_log(client, GUAC_LOG_INFO, - "RDP server closed connection"); + guac_rdp_client_abort(client); pthread_mutex_unlock(&(rdp_client->rdp_lock)); return 1; } @@ -843,7 +842,7 @@ static int guac_rdp_handle_connection(guac_client* client) { /* If an error occurred, fail */ if (wait_result < 0) - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE, "Connection closed."); /* Flush frame */ @@ -995,7 +994,7 @@ void* guac_rdp_client_thread(void* data) { if (rdp_client->sftp_filesystem == NULL) { guac_common_ssh_destroy_session(rdp_client->sftp_session); guac_common_ssh_destroy_user(rdp_client->sftp_user); - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_UNAVAILABLE, "SFTP connection failed."); return NULL; } From 4f4643dd2fe8b1dbc0db3268ac7716d71c021bbc Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 15 Feb 2017 21:37:59 -0800 Subject: [PATCH 3/3] GUACAMOLE-208: Report network failures to connect with UPSTREAM_NOT_FOUND. --- src/common-ssh/guac_ssh.c | 2 +- src/protocols/telnet/telnet.c | 3 ++- src/protocols/vnc/vnc.c | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/common-ssh/guac_ssh.c b/src/common-ssh/guac_ssh.c index 488eea24..97d3bbcd 100644 --- a/src/common-ssh/guac_ssh.c +++ b/src/common-ssh/guac_ssh.c @@ -483,7 +483,7 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, /* If unable to connect to anything, fail */ if (current_address == NULL) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND, "Unable to connect to any addresses."); close(fd); return NULL; diff --git a/src/protocols/telnet/telnet.c b/src/protocols/telnet/telnet.c index b80c959e..56661a43 100644 --- a/src/protocols/telnet/telnet.c +++ b/src/protocols/telnet/telnet.c @@ -352,7 +352,8 @@ static telnet_t* __guac_telnet_create_session(guac_client* client) { /* If unable to connect to anything, fail */ if (current_address == NULL) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to connect to any addresses."); + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND, + "Unable to connect to any addresses."); return NULL; } diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index 9b0bd769..611c84f4 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -202,7 +202,8 @@ void* guac_vnc_client_thread(void* data) { /* If the final connect attempt fails, return error */ if (!rfb_client) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to connect to VNC server."); + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND, + "Unable to connect to VNC server."); return NULL; }