From c81c5080f02a3e9bdfe6ee087b159e3f2287d763 Mon Sep 17 00:00:00 2001 From: James Muehlner Date: Fri, 28 Jul 2023 22:45:25 +0000 Subject: [PATCH] GUACAMOLE-1841: Create a Cygwin build option. --- configure.ac | 54 ++- src/common-ssh/Makefile.am | 3 +- src/guacd/Makefile.am | 6 + src/guacd/connection.c | 228 +++++++++++- src/guacd/connection.h | 17 + src/guacd/log.c | 19 +- src/guacd/move-pipe.c | 108 ++++++ src/guacd/move-pipe.h | 74 ++++ src/guacd/proc.c | 70 ++++ src/guacenc/encode.c | 14 +- src/guaclog/interpret.c | 13 +- src/libguac/Makefile.am | 12 +- src/libguac/client.c | 2 +- src/libguac/error.c | 242 ++++++++++-- src/libguac/guacamole/error-types.h | 12 + src/libguac/guacamole/error.h | 44 +-- src/libguac/guacamole/handle-helpers.h | 78 ++++ src/libguac/{ => guacamole}/id.h | 6 + src/libguac/guacamole/plugin-constants.h | 18 + src/libguac/guacamole/socket-handle.h | 50 +++ src/libguac/guacamole/wait-handle.h | 44 +++ src/libguac/handle-helpers.c | 103 ++++++ src/libguac/id.c | 14 +- src/libguac/socket-handle.c | 451 +++++++++++++++++++++++ src/libguac/tests/id/generate.c | 2 +- src/libguac/user-handshake.c | 16 +- src/libguac/user.c | 2 +- src/libguac/wait-handle.c | 67 ++++ src/protocols/kubernetes/Makefile.am | 3 +- src/protocols/rdp/Makefile.am | 10 +- src/protocols/rdp/channels/rdpgfx.c | 11 + src/protocols/ssh/Makefile.am | 4 +- src/protocols/telnet/Makefile.am | 3 +- src/protocols/vnc/Makefile.am | 3 +- src/pulse/Makefile.am | 3 +- src/terminal/Makefile.am | 3 +- src/terminal/color-scheme.c | 18 + src/terminal/named-colors.c | 9 + 38 files changed, 1709 insertions(+), 127 deletions(-) create mode 100755 src/guacd/move-pipe.c create mode 100755 src/guacd/move-pipe.h create mode 100755 src/libguac/guacamole/handle-helpers.h rename src/libguac/{ => guacamole}/id.h (89%) create mode 100755 src/libguac/guacamole/socket-handle.h create mode 100644 src/libguac/guacamole/wait-handle.h create mode 100755 src/libguac/handle-helpers.c create mode 100644 src/libguac/socket-handle.c create mode 100644 src/libguac/wait-handle.c diff --git a/configure.ac b/configure.ac index d4db3b6d7b..6d3a563985 100644 --- a/configure.ac +++ b/configure.ac @@ -40,9 +40,28 @@ AC_PROG_LIBTOOL # Headers AC_CHECK_HEADERS([fcntl.h stdlib.h string.h sys/socket.h time.h sys/time.h syslog.h unistd.h cairo/cairo.h pngstruct.h]) +# Cygwin build +CYWGIN_LDFLAGS= +AC_ARG_WITH([cygwin], + [AS_HELP_STRING([--with-cygwin], + [use Cygwin to build under Windows @<:@default=no@:>@])], + [with_cygwin=yes], [with_cygwin=no]) + +AM_CONDITIONAL([CYGWIN_BUILD], [test "x${with_cygwin}" = "xyes"]) + +if test "x$with_cygwin" = "xyes" +then + CYWGIN_LDFLAGS=-no-undefined + AC_DEFINE([CYGWIN_BUILD],,[Build against Cygwin on Windows]) +fi + # Source characteristics AC_DEFINE([_XOPEN_SOURCE], [700], [Uses X/Open and POSIX APIs]) -AC_DEFINE([__BSD_VISIBLE], [1], [Uses BSD-specific APIs (if available)]) + +if test "x$with_cygwin" = "xno" +then + AC_DEFINE([__BSD_VISIBLE], [1], [Uses BSD-specific APIs (if available)]) +fi # Check for whether math library is required AC_CHECK_LIB([m], [cos], @@ -131,6 +150,7 @@ AC_SUBST(CAIRO_LIBS) AC_SUBST(PTHREAD_LIBS) AC_SUBST(UUID_LIBS) AC_SUBST(CUNIT_LIBS) +AC_SUBST(CYWGIN_LDFLAGS) # Library functions AC_CHECK_FUNCS([clock_gettime gettimeofday memmove memset select strdup nanosleep]) @@ -357,20 +377,24 @@ AC_SUBST(SSL_LIBS) have_winsock=disabled WINSOCK_LIBS= -AC_ARG_WITH([winsock], - [AS_HELP_STRING([--with-winsock], - [support Windows Sockets API @<:@default=check@:>@])], - [], - [with_winsock=check]) -if test "x$with_winsock" != "xno" +if test "x$with_cygwin" = "xno" then - have_winsock=yes - AC_CHECK_LIB([wsock32], [main], - [WINSOCK_LIBS="-lwsock32"] - [AC_DEFINE([ENABLE_WINSOCK],, - [Whether Windows Socket API support is enabled])], - [have_winsock=no]) + AC_ARG_WITH([winsock], + [AS_HELP_STRING([--with-winsock], + [support Windows Sockets API @<:@default=check@:>@])], + [], + [with_winsock=check]) + + if test "x$with_winsock" != "xno" + then + have_winsock=yes + AC_CHECK_LIB([wsock32], [main], + [WINSOCK_LIBS="-lwsock32"] + [AC_DEFINE([ENABLE_WINSOCK],, + [Whether Windows Socket API support is enabled])], + [have_winsock=no]) + fi fi AM_CONDITIONAL([ENABLE_WINSOCK], [test "x${have_winsock}" = "xyes"]) @@ -513,7 +537,7 @@ then # Whether libvncserver was built against libgcrypt AC_CHECK_DECL([LIBVNCSERVER_WITH_CLIENT_GCRYPT], - [AC_CHECK_HEADER(gcrypt.h,, + [AC_CHECK_HEADER(gcrypt.h, [VNC_LIBS="$VNC_LIBS -lgcrypt"], [AC_MSG_WARN([ -------------------------------------------- libvncserver appears to be built against @@ -940,7 +964,7 @@ then # Whether libssh2 was built against libgcrypt AC_CHECK_LIB([ssh2], [gcry_control], [AC_CHECK_HEADER(gcrypt.h, - [AC_DEFINE([LIBSSH2_USES_GCRYPT],, + [AC_DEFINE([LIBSSH2_USES_GCRYPT],[SSH_LIBS="$SSH_LIBS -lgcrypt"], [Whether libssh2 was built against libgcrypt])], [AC_MSG_WARN([ -------------------------------------------- diff --git a/src/common-ssh/Makefile.am b/src/common-ssh/Makefile.am index 5d1a88d153..50abb2c132 100644 --- a/src/common-ssh/Makefile.am +++ b/src/common-ssh/Makefile.am @@ -54,5 +54,6 @@ libguac_common_ssh_la_LIBADD = \ libguac_common_ssh_la_LDFLAGS = \ @PTHREAD_LIBS@ \ @SSH_LIBS@ \ - @SSL_LIBS@ + @SSL_LIBS@ \ + @CYWGIN_LDFLAGS@ diff --git a/src/guacd/Makefile.am b/src/guacd/Makefile.am index 356f72f234..42a445845f 100644 --- a/src/guacd/Makefile.am +++ b/src/guacd/Makefile.am @@ -53,6 +53,12 @@ guacd_SOURCES = \ proc.c \ proc-map.c +# Cygwin build +if CYGWIN_BUILD +noinst_HEADERS += move-pipe.h +guacd_SOURCES += move-pipe.c +endif + guacd_CFLAGS = \ -Werror -Wall -pedantic \ @COMMON_INCLUDE@ \ diff --git a/src/guacd/connection.c b/src/guacd/connection.c index a5b98adfa2..51f014b7d4 100644 --- a/src/guacd/connection.c +++ b/src/guacd/connection.c @@ -21,12 +21,12 @@ #include "connection.h" #include "log.h" -#include "move-fd.h" #include "proc.h" #include "proc-map.h" #include #include +#include #include #include #include @@ -45,6 +45,58 @@ #include #include +#ifdef CYGWIN_BUILD + +#include +#include +#include + +#include +#include + +#include "move-pipe.h" + +/** + * Behaves exactly as write(), but writes as much as possible, returning + * successfully only if the entire buffer was written. If the write fails for + * any reason, a negative value is returned. + * + * @param handle + * The file handle to write to. + * + * @param buffer + * The buffer containing the data to be written. + * + * @param length + * The number of bytes in the buffer to write. + * + * @return + * The number of bytes written, or -1 if an error occurs. As this function + * is guaranteed to write ALL bytes, this will always be the number of + * bytes specified by length unless an error occurs. + */ +static int __write_all(HANDLE handle, char* buffer, int length) { + + /* Repeatedly write to the handle until all data is written */ + while (length > 0) { + + DWORD written; + if (guac_write_to_handle(handle, buffer, length, &written)) + return -1; + + length -= written; + buffer += written; + + } + + return length; + +} + +#else + +#include "move-fd.h" + /** * Behaves exactly as write(), but writes as much as possible, returning * successfully only if the entire buffer was written. If the write fails for @@ -82,10 +134,12 @@ static int __write_all(int fd, char* buffer, int length) { } +#endif + /** * Continuously reads from a guac_socket, writing all data read to a file - * descriptor. Any data already buffered from that guac_socket by a given - * guac_parser is read first, prior to reading further data from the + * descriptor or handle. Any data already buffered from that guac_socket by a + * given guac_parser is read first, prior to reading further data from the * guac_socket. The provided guac_parser will be freed once its buffers have * been emptied, but the guac_socket will not. * @@ -110,7 +164,13 @@ static void* guacd_connection_write_thread(void* data) { /* Read all buffered data from parser first */ while ((length = guac_parser_shift(params->parser, buffer, sizeof(buffer))) > 0) { + +#ifdef CYGWIN_BUILD + if (__write_all(params->handle, buffer, length) < 0) +#else if (__write_all(params->fd, buffer, length) < 0) +#endif + break; } @@ -119,7 +179,13 @@ static void* guacd_connection_write_thread(void* data) { /* Transfer data from file descriptor to socket */ while ((length = guac_socket_read(params->socket, buffer, sizeof(buffer))) > 0) { + +#ifdef CYGWIN_BUILD + if (__write_all(params->handle, buffer, length) < 0) +#else if (__write_all(params->fd, buffer, length) < 0) +#endif + break; } @@ -132,11 +198,27 @@ void* guacd_connection_io_thread(void* data) { guacd_connection_io_thread_params* params = (guacd_connection_io_thread_params*) data; char buffer[8192]; - int length; - pthread_t write_thread; pthread_create(&write_thread, NULL, guacd_connection_write_thread, params); +#ifdef CYGWIN_BUILD + + /* Transfer data from file handle to socket */ + while (1) { + + DWORD bytes_read; + if (guac_read_from_handle(params->handle, buffer, sizeof(buffer), &bytes_read)) + break; + + if (guac_socket_write(params->socket, buffer, bytes_read)) + break; + + guac_socket_flush(params->socket); + + } + +#else + /* Transfer data from file descriptor to socket */ while ((length = read(params->fd, buffer, sizeof(buffer))) > 0) { if (guac_socket_write(params->socket, buffer, length)) @@ -144,12 +226,14 @@ void* guacd_connection_io_thread(void* data) { guac_socket_flush(params->socket); } +#endif + /* Wait for write thread to die */ pthread_join(write_thread, NULL); /* Clean up */ guac_socket_free(params->socket); - close(params->fd); + CloseHandle(params->handle); free(params); return NULL; @@ -181,6 +265,129 @@ void* guacd_connection_io_thread(void* data) { */ static int guacd_add_user(guacd_proc* proc, guac_parser* parser, guac_socket* socket) { +#ifdef CYGWIN_BUILD + + SECURITY_ATTRIBUTES attributes = { 0 }; + attributes.nLength = sizeof(SECURITY_ATTRIBUTES); + + /* + * Attempt to create a Windows security descriptor that grants access only + * to the owner of this process. + */ + if (!ConvertStringSecurityDescriptorToSecurityDescriptor( + + /* + * An SDDL string that uses DACL to grant the General Access (GA) + * permission, only to the owner (OW). For more, see + * https://learn.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-string-format. + */ + "D:P(A;;GA;;;OW)", + SDDL_REVISION_1, + + /* The populated security descriptor output */ + &(attributes.lpSecurityDescriptor), + + /* There's no need to capture the descriptor size */ + NULL + + )) { + guacd_log(GUAC_LOG_ERROR, "Unable to initialize named pipe security descriptor."); + return 1; + } + + char pipe_name[GUAC_PIPE_NAME_LENGTH]; + + /* Required pipe name prefix */ + memcpy(pipe_name, PIPE_NAME_PREFIX, strlen(PIPE_NAME_PREFIX)); + + /* UUID to ensure the pipe name is unique */ + char* uuid = guac_generate_id('G'); + if (uuid == NULL) { + guacd_log(GUAC_LOG_ERROR, "Unable to generate UUID for pipe name."); + return 1; + } + + memcpy(pipe_name + strlen(PIPE_NAME_PREFIX), uuid, GUAC_UUID_LEN); + + /* Null terminator */ + pipe_name[GUAC_PIPE_NAME_LENGTH - 1] = '\0'; + + /* + * Set up a named pipe for communication with the user. For more, see + * https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea + */ + HANDLE pipe_handle = CreateNamedPipe( + pipe_name, + + /* + * Read/write and "overlapped" (async) modes. PIPE_WAIT ensures + * that completion actions do not occur until data is actually + * ready, i.e. it's actually possible to wait for data. + */ + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + + /* Allow only one instance of this named pipe to be opened. + * PIPE_WAIT ensures that completion actions do not occur until data + * is actually ready, i.e. it's actually possible to wait for data. + * Also, allow only connections from the local machine. + */ + PIPE_TYPE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, + + /* Only this one instance of this named pipe is needed */ + 1, + + /* Output and input buffer sizes */ + 8192, 8192, + + /* Use the default timeout for the unused function WaitNamedPipe() */ + 0, + + /* Set our custom security descriptor to allow only owner usage */ + &attributes + + ); + + LocalFree(attributes.lpSecurityDescriptor); + + if (pipe_handle == INVALID_HANDLE_VALUE) { + guacd_log(GUAC_LOG_ERROR, "Unable to create named pipe for IPC."); + return 1; + } + + /* If pipe creation failed, the error will already have been logged */ + if (pipe_handle == NULL) + return 1; + + /* Send pipe name to process so it can connect to the pipe */ + if (!guacd_send_pipe(proc->fd_socket, pipe_name)) { + CloseHandle(pipe_handle); + guacd_log(GUAC_LOG_ERROR, "Unable to add user."); + return 1; + } + + /* Wait for the other end of the pipe to connect before attempting IO */ + HANDLE event = CreateEvent(NULL, FALSE, FALSE, NULL); + OVERLAPPED overlapped = { 0 }; + overlapped.hEvent = event; + ConnectNamedPipe(pipe_handle, &overlapped); + + /* Wait for 1 second for the other end to be connected */ + DWORD result = WaitForSingleObject(event, 1000); + if (result == WAIT_FAILED) { + + /* + * If the wait failed for any reason other than the pipe being + * already connected + */ + if (GetLastError() != ERROR_PIPE_CONNECTED) { + guacd_log(GUAC_LOG_ERROR, "Named pipe connection not established."); + return 1; + } + + } + +#else + int sockets[2]; /* Set up socket pair */ @@ -201,10 +408,17 @@ static int guacd_add_user(guacd_proc* proc, guac_parser* parser, guac_socket* so /* Close our end of the process file descriptor */ close(proc_fd); +#endif + guacd_connection_io_thread_params* params = malloc(sizeof(guacd_connection_io_thread_params)); params->parser = parser; params->socket = socket; + +#ifdef CYGWIN_BUILD + params->handle = pipe_handle; +#else params->fd = user_fd; +#endif /* Start I/O thread */ pthread_t io_thread; @@ -389,7 +603,7 @@ void* guacd_connection_thread(void* data) { #else /* Open guac_socket */ - socket = guac_socket_open(connected_socket_fd); + socket = guac_socket_open(connected_handle); #endif /* Route connection according to Guacamole, creating a new process if needed */ diff --git a/src/guacd/connection.h b/src/guacd/connection.h index 08a75266dc..42e09d3602 100644 --- a/src/guacd/connection.h +++ b/src/guacd/connection.h @@ -28,6 +28,10 @@ #include #endif +#ifdef CYGWIN_BUILD +#include +#endif + /** * Parameters required by each connection thread. */ @@ -92,12 +96,25 @@ typedef struct guacd_connection_io_thread_params { */ guac_socket* socket; + +#ifdef CYGWIN_BUILD + + /** + * The named pipe handle which is being handled by a guac_socket within the + * connection-specific process. + */ + HANDLE handle; + +#else + /** * The file descriptor which is being handled by a guac_socket within the * connection-specific process. */ int fd; +#endif + } guacd_connection_io_thread_params; /** diff --git a/src/guacd/log.c b/src/guacd/log.c index 81c313b86f..9bda2adf0c 100644 --- a/src/guacd/log.c +++ b/src/guacd/log.c @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -117,10 +118,11 @@ void guacd_log_guac_error(guac_client_log_level level, const char* message) { guac_error_message); /* Otherwise just log with standard status string */ - else - guacd_log(level, "%s: %s", - message, - guac_status_string(guac_error)); + else { + char* status_string = guac_status_string(guac_error); + guacd_log(level, "%s: %s", message, status_string); + free(status_string); + } } @@ -140,10 +142,13 @@ void guacd_log_handshake_failure() { "Guacamole protocol violation. Perhaps the version of " "guacamole-client is incompatible with this version of " "guacd?"); - else + else { + + char* status_string = guac_status_string(guac_error); guacd_log(GUAC_LOG_WARNING, - "Guacamole handshake failed: %s", - guac_status_string(guac_error)); + "Guacamole handshake failed: %s", status_string); + free(status_string); + } } diff --git a/src/guacd/move-pipe.c b/src/guacd/move-pipe.c new file mode 100755 index 0000000000..4ff901482e --- /dev/null +++ b/src/guacd/move-pipe.c @@ -0,0 +1,108 @@ +/* + * 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 "log.h" +#include "move-pipe.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Windows headers */ +#include +#include +#include +#include +#include + +int guacd_send_pipe(int sock, char* pipe_name) { + + /* Assign data buffer */ + struct iovec io_vector[1]; + io_vector[0].iov_base = pipe_name; + io_vector[0].iov_len = GUAC_PIPE_NAME_LENGTH; + + struct msghdr message = {0}; + message.msg_iov = io_vector; + message.msg_iovlen = 1; + + /* Send pipe name */ + return (sendmsg(sock, &message, 0) == GUAC_PIPE_NAME_LENGTH); + +} + +HANDLE guacd_recv_pipe(int sock) { + + /* Assign data buffer */ + char pipe_name[GUAC_PIPE_NAME_LENGTH]; + struct iovec io_vector[1]; + io_vector[0].iov_base = pipe_name; + io_vector[0].iov_len = GUAC_PIPE_NAME_LENGTH; + + struct msghdr message = {0}; + message.msg_iov = io_vector; + message.msg_iovlen = 1; + + /* Receive file descriptor */ + if (recvmsg(sock, &message, 0) == GUAC_PIPE_NAME_LENGTH) { + + /* + * Make sure the value is always null-terminated, even if an invalid + * name was sent. + */ + pipe_name[GUAC_PIPE_NAME_LENGTH - 1] = '\0'; + + return CreateFile( + + pipe_name, + + /* Desired access level */ + GENERIC_READ | GENERIC_WRITE, + + /* Sharing level - do not allow any other usage of this pipe */ + 0, + + /* Default security mode - do not allow child processes to inherir */ + NULL, + + /* Open the existing pipe; don't try to create a new one */ + OPEN_EXISTING, + + /* Open in "overlapped" (async) mode */ + FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, + + /* Ignored for existing pipes */ + NULL + ); + + } /* end if recvmsg() success */ + + /* Failed to get the pipe */ + return NULL; + +} + diff --git a/src/guacd/move-pipe.h b/src/guacd/move-pipe.h new file mode 100755 index 0000000000..1107a26dd5 --- /dev/null +++ b/src/guacd/move-pipe.h @@ -0,0 +1,74 @@ +/* + * 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 GUACD_MOVE_HANDLE_H +#define GUACD_MOVE_HANDLE_H + +#include +#include + +/* + * The required prefix for all pipe names in Windows. + */ +#define PIPE_NAME_PREFIX "\\\\.\\pipe\\" + +/* + * The length of a named pipe as used by guacamole. Every pipe name will consist + * of PIPE_NAME_PREFIX, plus a the length of a UUID as returned from + * guac_generate_id(), plus a null-terminator. + */ +#define GUAC_PIPE_NAME_LENGTH (strlen(PIPE_NAME_PREFIX) + GUAC_UUID_LEN + 1) + +/** + * Sends the given pipe name along the given socket. Returns non-zero on success, + * zero on error, just as a normal call to sendmsg() would. If an error does occur, + * GetLastError() will return the appropriate error. + * + * @param sock + * The file descriptor of an open UNIX domain socket along which the pipe + * name specified by pipe_name should be sent. + * + * @param pipe_name + * The null-terminated name of the pipe to send across the socket. The name + * MUST be GUAC_PIPE_NAME_LENGTH characters long, and end with a null + * terminator. + * + * @return + * Non-zero if the send operation succeeded, zero on error. + */ +int guacd_send_pipe(int sock, char* pipe_name); + +/** + * Waits for a pipe name on the given socket, returning a handle to the client + * end of the named pipe with that name. The pipe name must have been sent via + * guacd_send_pipe_name. If an error occurs, NULL is returned, and GetLastError() + * will return the appropriate error. + * + * @param sock + * The file descriptor of an open UNIX domain socket along which the file + * handle will be sent (by guacd_send_handle()). + * + * @return + * The handle to the client end of the named pipe if the operation succeeded, + * NULL otherwise. + */ +HANDLE guacd_recv_pipe(int sock); + +#endif + diff --git a/src/guacd/proc.c b/src/guacd/proc.c index 9641ac05a0..6af6be0b06 100644 --- a/src/guacd/proc.c +++ b/src/guacd/proc.c @@ -43,6 +43,13 @@ #include #include +#ifdef CYGWIN_BUILD +#include "move-pipe.h" +#include "guacamole/socket-handle.h" +#include +#include +#endif + /** * Parameters for the user thread. */ @@ -53,11 +60,22 @@ typedef struct guacd_user_thread_params { */ guacd_proc* proc; +#ifdef CYGWIN_BUILD + + /** + * The file handle for communicating with the joining user. + */ + HANDLE handle; + +#else + /** * The file descriptor of the joining user's socket. */ int fd; +#endif + /** * Whether the joining user is the connection owner. */ @@ -84,7 +102,11 @@ static void* guacd_user_thread(void* data) { guac_client* client = proc->client; /* Get guac_socket for user's file descriptor */ +#ifdef CYGWIN_BUILD + guac_socket* socket = guac_socket_open_handle(params->handle); +#else guac_socket* socket = guac_socket_open(params->fd); +#endif if (socket == NULL) return NULL; @@ -112,6 +134,39 @@ static void* guacd_user_thread(void* data) { } +#ifdef CYGWIN_BUILD + +/** + * Begins a new user connection under a given process, using the given file + * handle. The connection will be managed by a separate and detached thread + * which is started by this function. + * + * @param proc + * The process that the user is being added to. + + * @param handle + * The handle associated with the user's connection to guacd. + * + * @param owner + * Non-zero if the user is the owner of the connection being joined (they + * are the first user to join), or zero otherwise. + */ +static void guacd_proc_add_user(guacd_proc* proc, HANDLE handle, int owner) { + + guacd_user_thread_params* params = malloc(sizeof(guacd_user_thread_params)); + params->proc = proc; + params->handle = handle; + params->owner = owner; + + /* Start user thread */ + pthread_t user_thread; + pthread_create(&user_thread, NULL, guacd_user_thread, params); + pthread_detach(user_thread); + +} + +#else + /** * Begins a new user connection under a given process, using the given file * descriptor. The connection will be managed by a separate and detached thread @@ -142,6 +197,8 @@ static void guacd_proc_add_user(guacd_proc* proc, int fd, int owner) { } +#endif + /** * Forcibly kills all processes within the current process group, including the * current process and all child processes. This function is only safe to call @@ -333,12 +390,25 @@ static void guacd_exec_proc(guacd_proc* proc, const char* protocol) { /* Enable keep alive on the broadcast socket */ guac_socket_require_keep_alive(client->socket); + +#ifdef CYGWIN_BUILD + + /* Add each received file handle as a new user */ + HANDLE handle; + while ((handle = guacd_recv_pipe(proc->fd_socket)) != NULL) { + + guacd_proc_add_user(proc, handle, owner); + +#else + /* Add each received file descriptor as a new user */ int received_fd; while ((received_fd = guacd_recv_fd(proc->fd_socket)) != -1) { guacd_proc_add_user(proc, received_fd, owner); +#endif + /* Future file descriptors are not owners */ owner = 0; diff --git a/src/guacenc/encode.c b/src/guacenc/encode.c index 3de9d796eb..e68b540365 100644 --- a/src/guacenc/encode.c +++ b/src/guacenc/encode.c @@ -72,8 +72,11 @@ static int guacenc_read_instructions(guacenc_display* display, /* Fail on read/parse error */ if (guac_error != GUAC_STATUS_CLOSED) { - guacenc_log(GUAC_LOG_ERROR, "%s: %s", - path, guac_status_string(guac_error)); + + char* status_string = guac_status_string(guac_error); + guacenc_log(GUAC_LOG_ERROR, "%s: %s", path, status_string); + free(status_string); + guac_parser_free(parser); return 1; } @@ -132,8 +135,11 @@ int guacenc_encode(const char* path, const char* out_path, const char* codec, /* Obtain guac_socket wrapping file descriptor */ guac_socket* socket = guac_socket_open(fd); if (socket == NULL) { - guacenc_log(GUAC_LOG_ERROR, "%s: %s", path, - guac_status_string(guac_error)); + + char* status_string = guac_status_string(guac_error); + guacenc_log(GUAC_LOG_ERROR, "%s: %s", path, status_string); + free(status_string); + close(fd); guacenc_display_free(display); return 1; diff --git a/src/guaclog/interpret.c b/src/guaclog/interpret.c index d996c24551..8c35521b04 100644 --- a/src/guaclog/interpret.c +++ b/src/guaclog/interpret.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -69,8 +70,9 @@ static int guaclog_read_instructions(guaclog_state* state, /* Fail on read/parse error */ if (guac_error != GUAC_STATUS_CLOSED) { - guaclog_log(GUAC_LOG_ERROR, "%s: %s", - path, guac_status_string(guac_error)); + char* status_string = guac_status_string(guac_error); + guaclog_log(GUAC_LOG_ERROR, "%s: %s", path, status_string); + free(status_string); guac_parser_free(parser); return 1; } @@ -127,8 +129,11 @@ int guaclog_interpret(const char* path, const char* out_path, bool force) { /* Obtain guac_socket wrapping file descriptor */ guac_socket* socket = guac_socket_open(fd); if (socket == NULL) { - guaclog_log(GUAC_LOG_ERROR, "%s: %s", path, - guac_status_string(guac_error)); + + char* status_string = guac_status_string(guac_error); + guaclog_log(GUAC_LOG_ERROR, "%s: %s", path, status_string); + free(status_string); + close(fd); guaclog_state_free(state); return 1; diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index 0c8680561d..f1cc9fb67c 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -46,6 +46,7 @@ libguacinc_HEADERS = \ guacamole/error-types.h \ guacamole/fips.h \ guacamole/hash.h \ + guacamole/id.h \ guacamole/layer.h \ guacamole/layer-types.h \ guacamole/object.h \ @@ -79,7 +80,6 @@ libguacinc_HEADERS = \ guacamole/wol-constants.h noinst_HEADERS = \ - id.h \ encode-jpeg.h \ encode-png.h \ palette.h \ @@ -135,6 +135,16 @@ libguac_la_SOURCES += socket-wsa.c libguacinc_HEADERS += guacamole/socket-wsa.h endif +# Cygwin build +if CYGWIN_BUILD +libguac_la_SOURCES += handle-helpers.c +libguac_la_SOURCES += socket-handle.c +libguac_la_SOURCES += wait-handle.c +libguacinc_HEADERS += guacamole/handle-helpers.h +libguacinc_HEADERS += guacamole/socket-handle.h +libguacinc_HEADERS += guacamole/wait-handle.h +endif + libguac_la_CFLAGS = \ -Werror -Wall -pedantic diff --git a/src/libguac/client.c b/src/libguac/client.c index 46291b46d8..622f687e09 100644 --- a/src/libguac/client.c +++ b/src/libguac/client.c @@ -33,7 +33,7 @@ #include "guacamole/string.h" #include "guacamole/timestamp.h" #include "guacamole/user.h" -#include "id.h" +#include #include #include diff --git a/src/libguac/error.c b/src/libguac/error.c index 5561b1781f..35a9095d10 100644 --- a/src/libguac/error.c +++ b/src/libguac/error.c @@ -29,6 +29,12 @@ #include #endif +#ifdef CYGWIN_BUILD +#include +#include +#include +#endif + /* * Error strings */ @@ -58,109 +64,214 @@ const char* __GUAC_STATUS_REFUSED_STR = "Operation refused"; const char* __GUAC_STATUS_TOO_MANY_STR = "Insufficient resources"; const char* __GUAC_STATUS_WOULD_BLOCK_STR = "Operation would block"; -const char* guac_status_string(guac_status status) { +#ifdef CYGWIN_BUILD + +/* + * A format for a backup message in case Windows can't produce one. + */ +#define FALLBACK_ERROR_FORMAT "Failed to get Windows error for code %ul" + +/* + * The size of newly constructed fallback error buffers. Large enough to + * contain the longest possible DWORD and a null terminator. + */ +#define FALLBACK_ERROR_SIZE 64 + +/** + * Get the error message associated with the most recent Windows error. This + * message is allocated fresh every time this function is called, and must be + * freed by the caller. + * + */ +static LPTSTR get_last_error_message() { + + /* This will be set to point to the buffer allocated by FormatMessage */ + LPTSTR error_buffer = NULL; + + /* Attempt to format message using system error message tables */ + DWORD length = FormatMessage( + + /* Fetch error messages from system message tables */ + FORMAT_MESSAGE_FROM_SYSTEM + + /* Allocate a buffer to hold the message text */ + |FORMAT_MESSAGE_ALLOCATE_BUFFER + + /* We're not supplying our own text inserts */ + |FORMAT_MESSAGE_IGNORE_INSERTS, + + /* Don't pass any custom message tables */ + NULL, + + /* Pass the error code in to get the error message */ + guac_windows_error_code, + + /* Use default language settings */ + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + + /* + * Cast the address of the buffer back to a LPTSTR, even though it's + * really a LPTSTR*. The function will internally treat it as a LPTSTR*, + * populating `errorText` with the message text. + */ + (LPTSTR)&error_buffer, + + /* Minimum output buffer size */ + 0, + + /* No extra arguments */ + NULL + ); + + /* If Windows failed to generate an error message */ + if (!length) { + + /* Allocate and format the fallback error message */ + error_buffer = malloc(FALLBACK_ERROR_SIZE * sizeof(TCHAR)); + + /* Format the message */ + sprintf(error_buffer, FALLBACK_ERROR_FORMAT, guac_windows_error_code); + } + + return error_buffer; + +} + +#endif + +/** + * Allocate and return a copy of the provided null-terminated string constant. + * + * @param string_constant + * The null-terminated string constant to duplicated. + * + * @return + * A newly allocated copy of the provided string constant. + */ +static char* guac_status_duplicate_constant(const char* string_constant) { + + /* Create a buffer to hold the message plus null terminator */ + char* string_copy = malloc((strlen(string_constant) + 1) * sizeof(char)); + + /* Copy the constant into the buffer */ + strcpy(string_copy, string_constant); + + /* A newly-allocated copy of the constant */ + return string_copy; + +} + +char* guac_status_string(guac_status status) { switch (status) { /* No error */ case GUAC_STATUS_SUCCESS: - return __GUAC_STATUS_SUCCESS_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_SUCCESS_STR); /* Out of memory */ case GUAC_STATUS_NO_MEMORY: - return __GUAC_STATUS_NO_MEMORY_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_NO_MEMORY_STR); /* End of stream */ case GUAC_STATUS_CLOSED: - return __GUAC_STATUS_CLOSED_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_CLOSED_STR); /* Timeout */ case GUAC_STATUS_TIMEOUT: - return __GUAC_STATUS_TIMEOUT_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_TIMEOUT_STR); /* Further information in errno */ case GUAC_STATUS_SEE_ERRNO: - return strerror(errno); + return guac_status_duplicate_constant(strerror(errno)); + +#ifdef CYGWIN_BUILD + + /* Further information available from the Windows API. */ + case GUAC_STATUS_SEE_WINDOWS_ERROR: + return get_last_error_message(); + +#endif /* Input/output error */ case GUAC_STATUS_IO_ERROR: - return __GUAC_STATUS_IO_ERROR_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_IO_ERROR_STR); /* Invalid argument */ case GUAC_STATUS_INVALID_ARGUMENT: - return __GUAC_STATUS_INVALID_ARGUMENT_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_INVALID_ARGUMENT_STR); /* Internal error */ case GUAC_STATUS_INTERNAL_ERROR: - return __GUAC_STATUS_INTERNAL_ERROR_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_INTERNAL_ERROR_STR); /* Out of space */ case GUAC_STATUS_NO_SPACE: - return __GUAC_STATUS_NO_SPACE_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_NO_SPACE_STR); /* Input too large */ case GUAC_STATUS_INPUT_TOO_LARGE: - return __GUAC_STATUS_INPUT_TOO_LARGE_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_INPUT_TOO_LARGE_STR); /* Result too large */ case GUAC_STATUS_RESULT_TOO_LARGE: - return __GUAC_STATUS_RESULT_TOO_LARGE_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_RESULT_TOO_LARGE_STR); /* Permission denied */ case GUAC_STATUS_PERMISSION_DENIED: - return __GUAC_STATUS_PERMISSION_DENIED_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_PERMISSION_DENIED_STR); /* Resource is busy */ case GUAC_STATUS_BUSY: - return __GUAC_STATUS_BUSY_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_BUSY_STR); /* Resource not available */ case GUAC_STATUS_NOT_AVAILABLE: - return __GUAC_STATUS_NOT_AVAILABLE_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_NOT_AVAILABLE_STR); /* Not supported */ case GUAC_STATUS_NOT_SUPPORTED: - return __GUAC_STATUS_NOT_SUPPORTED_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_NOT_SUPPORTED_STR); /* Not implemented */ case GUAC_STATUS_NOT_INPLEMENTED: - return __GUAC_STATUS_NOT_INPLEMENTED_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_NOT_INPLEMENTED_STR); /* Temporary failure */ case GUAC_STATUS_TRY_AGAIN: - return __GUAC_STATUS_TRY_AGAIN_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_TRY_AGAIN_STR); /* Guacamole protocol error */ case GUAC_STATUS_PROTOCOL_ERROR: - return __GUAC_STATUS_PROTOCOL_ERROR_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_PROTOCOL_ERROR_STR); /* Resource not found */ case GUAC_STATUS_NOT_FOUND: - return __GUAC_STATUS_NOT_FOUND_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_NOT_FOUND_STR); /* Operation canceled */ case GUAC_STATUS_CANCELED: - return __GUAC_STATUS_CANCELED_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_CANCELED_STR); /* Value out of range */ case GUAC_STATUS_OUT_OF_RANGE: - return __GUAC_STATUS_OUT_OF_RANGE_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_OUT_OF_RANGE_STR); /* Operation refused */ case GUAC_STATUS_REFUSED: - return __GUAC_STATUS_REFUSED_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_REFUSED_STR); /* Too many resource in use */ case GUAC_STATUS_TOO_MANY: - return __GUAC_STATUS_TOO_MANY_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_TOO_MANY_STR); /* Operation would block */ case GUAC_STATUS_WOULD_BLOCK: - return __GUAC_STATUS_WOULD_BLOCK_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_WOULD_BLOCK_STR); /* Unknown status code */ default: - return __GUAC_STATUS_UNKNOWN_STATUS_STR; + return guac_status_duplicate_constant(__GUAC_STATUS_UNKNOWN_STATUS_STR); } @@ -218,10 +329,10 @@ guac_status* __guac_error() { } -const char** __guac_error_message() { +char** __guac_error_message() { /* Pointer for thread-local data */ - const char** message; + char** message; /* Init error message key, if not already initialized */ pthread_once( @@ -230,11 +341,11 @@ const char** __guac_error_message() { ); /* Retrieve thread-local message variable */ - message = (const char**) pthread_getspecific(__guac_error_message_key); + message = (char**) pthread_getspecific(__guac_error_message_key); /* Allocate thread-local message variable if not already allocated */ if (message == NULL) { - message = malloc(sizeof(const char*)); + message = malloc(sizeof(char*)); pthread_setspecific(__guac_error_message_key, message); } @@ -242,22 +353,89 @@ const char** __guac_error_message() { } +#ifdef CYGWIN_BUILD + +/** + * PThread key for the thread local Windows error code associated with + * the current guac_status, if the status is GUAC_STATUS_SEE_WINDOWS_ERROR. + */ +static pthread_key_t guac_windows_code_key; + +/** + * A static pthread_once_t instance to ensure that the Windows error code + * thread local is only created once. + */ +static pthread_once_t guac_windows_code_key_init = PTHREAD_ONCE_INIT; + +/** + * Allocate the PThread key for the Windows error code associated with the + * current error. + */ +static void guac_alloc_windows_code_key() { + + /* Create key, destroy any allocated variable on thread exit */ + pthread_key_create(&guac_windows_code_key, __guac_free_pointer); + +} + +DWORD* __guac_windows_code() { + + /* Pointer for thread-local data */ + DWORD* windows_code; + + /* Init error code key, if not already initialized */ + pthread_once(&guac_windows_code_key_init, guac_alloc_windows_code_key); + + /* Get the value of the current thread local */ + windows_code = (DWORD*) pthread_getspecific(guac_windows_code_key); + + /* Allocate thread-local error code variable if not already allocated */ + if (windows_code == NULL) { + windows_code = malloc(sizeof(DWORD)); + pthread_setspecific(guac_windows_code_key, windows_code); + } + + return windows_code; + +} + +#endif + #else /* Default (not-threadsafe) implementation */ static guac_status __guac_error_unsafe_storage; -static const char** __guac_error_message_unsafe_storage; +static char** __guac_error_message_unsafe_storage; guac_status* __guac_error() { return &__guac_error_unsafe_storage; } -const char** __guac_error_message() { +char** __guac_error_message() { return &__guac_error_message_unsafe_storage; } /* Warn about threadsafety */ #warn No threadsafe implementation of __guac_error exists for your platform, so a default non-threadsafe implementation has been used instead. This may lead to incorrect status codes being reported for failures. Please consider adding support for your platform, or filing a bug report with the Guacamole project. +#ifdef CYGWIN_BUILD + +/** + * Non-threadsafe storage for the Windows error code. + */ +static DWORD __guac_windows_error_unsafe_storage; + +/** + * Return non-threadsafe Windows error code. + * + * @return + * The non-threadsafe Windows error code. + */ +DWORD* __guac_windows_code() { + return &__guac_windows_error_unsafe_storage; +} + +#endif + #endif diff --git a/src/libguac/guacamole/error-types.h b/src/libguac/guacamole/error-types.h index 4cf3499eaf..7622fb35eb 100644 --- a/src/libguac/guacamole/error-types.h +++ b/src/libguac/guacamole/error-types.h @@ -20,6 +20,8 @@ #ifndef _GUAC_ERROR_TYPES_H #define _GUAC_ERROR_TYPES_H +#include "config.h" + /** * Type definitions related to return values and errors. * @@ -57,6 +59,16 @@ typedef enum guac_status { * stored in errno. */ GUAC_STATUS_SEE_ERRNO, + +#ifdef CYGWIN_BUILD + + /** + * An error occurred, and further information about the error can be + * retrieved using the GetLastError function. + */ + GUAC_STATUS_SEE_WINDOWS_ERROR, + +#endif /** * An I/O error prevented the operation from succeeding. diff --git a/src/libguac/guacamole/error.h b/src/libguac/guacamole/error.h index a511d315ff..46a4fb38fd 100644 --- a/src/libguac/guacamole/error.h +++ b/src/libguac/guacamole/error.h @@ -30,10 +30,15 @@ #include "error-types.h" +#ifdef CYGWIN_BUILD +#include +#endif + /** - * Returns a human-readable explanation of the status code given. + * Returns a newly-allocated, null-terminated, and human-readable explanation + * of the status code given. */ -const char* guac_status_string(guac_status status); +char* guac_status_string(guac_status status); /** * Returns the status code associated with the error which occurred during the @@ -58,37 +63,24 @@ guac_status* __guac_error(); */ #define guac_error_message (*__guac_error_message()) -const char** __guac_error_message(); +char** __guac_error_message(); -/** - * Returns a human-readable explanation of the status code given. - */ -const char* guac_status_string(guac_status status); +#ifdef CYGWIN_BUILD /** - * Returns the status code associated with the error which occurred during the - * last function call. This value will only be set by functions documented to - * use it (most libguac functions), and is undefined if no error occurred. + * Returns an error code describing the Windows error that occured when + * attempting the Windows function call that induced the guac_error status + * being set to GUAC_STATUS_SEE_WINDOWS_ERROR. This value is meaningless if + * any other guac status is set. * - * The storage of this value is thread-local. Assignment of a status code to - * guac_error in one thread will not affect its value in another thread. + * The storage of this value is thread-local. Assignment of an error code in + * one thread will not affect its value in another thread. */ -#define guac_error (*__guac_error()) +#define guac_windows_error_code (*__guac_windows_code()) -guac_status* __guac_error(); +DWORD* __guac_windows_code(); -/** - * Returns a message describing the error which occurred during the last - * function call. If an error occurred, but no message is associated with it, - * NULL is returned. This value is undefined if no error occurred. - * - * The storage of this value is thread-local. Assignment of a message to - * guac_error_message in one thread will not affect its value in another - * thread. - */ -#define guac_error_message (*__guac_error_message()) - -const char** __guac_error_message(); +#endif #endif diff --git a/src/libguac/guacamole/handle-helpers.h b/src/libguac/guacamole/handle-helpers.h new file mode 100755 index 0000000000..c7131d26e2 --- /dev/null +++ b/src/libguac/guacamole/handle-helpers.h @@ -0,0 +1,78 @@ +/* + * 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 + +/** + * Attempt to read the provided count of bytes from the provided handle, into the + * provided buffer. If the read is successful, 0 will be returned, and the actual + * number of bytes written to the buffer will be saved to the provided + * num_bytes_read pointer. If an error occurs while attempting to read from the + * handle, or while waiting on the results of the read attempt, the error code + * (as returned by GetLastError()) will be returned. + * + * @param handle + * The handle to read from. This handle MUST have been opened in overlapped + * mode. + * + * @param buffer + * The buffer to write the data into. It must be at least `count` bytes. + * + * @param count + * The maximum number of bytes to read from the handle. + * + * @param num_bytes_read + * The actual number of bytes read from the handle. This value is valid only + * if this function returns successfully. This value may be less than `count`. + * + * @return + * Zero on success, or the failure code (as returned by GetLastError()) if + * the read attempt, or the wait on that read attempt fails. + */ +int guac_read_from_handle( + HANDLE handle, void* buffer, DWORD count, DWORD* num_bytes_read); + +/** + * Attempt to wrtie the provided count of bytes to the provided handle, from the + * provided buffer. If the write is successful, 0 will be returned, and the actual + * number of bytes written to the handle will be saved to the provided + * num_bytes_written pointer. If an error occurs while attempting to write to the + * handle, or while waiting on the results of the write attempt, the error code + * (as returned by GetLastError()) will be returned. + * + * @param handle + * The handle to write to. This handle MUST have been opened in overlapped + * mode. + * + * @param buffer + * The buffer to write to the handle. It must be at least `count` bytes. + * + * @param count + * The maximum numer of bytes to write to the handle. + * + * @param num_bytes_written + * The actual number of bytes written to the handle. This value is valid only + * if this function returns successfully. This value may be less than `count`. + * + * @return + * Zero on success, or the failure code (as returned by GetLastError()) if + * the write attempt, or the wait on that write attempt fails. + */ +int guac_write_to_handle( + HANDLE handle, const void* buffer, DWORD count, DWORD* num_bytes_written); \ No newline at end of file diff --git a/src/libguac/id.h b/src/libguac/guacamole/id.h similarity index 89% rename from src/libguac/id.h rename to src/libguac/guacamole/id.h index 9b64d4108d..762dc29987 100644 --- a/src/libguac/id.h +++ b/src/libguac/guacamole/id.h @@ -20,6 +20,12 @@ #ifndef __GUAC_ID_H #define __GUAC_ID_H +/** + * The length of a guac UUID in bytes. All UUIDs returned by guac_generate_id() + * are guaranteed to be 37 1-byte characters long. + */ +#define GUAC_UUID_LEN 37 + /** * Generates a guaranteed-unique identifier which is a total of 37 characters * long, having the given single-character prefix. The resulting identifier diff --git a/src/libguac/guacamole/plugin-constants.h b/src/libguac/guacamole/plugin-constants.h index a67f5f54cb..7aeef251f7 100644 --- a/src/libguac/guacamole/plugin-constants.h +++ b/src/libguac/guacamole/plugin-constants.h @@ -20,12 +20,28 @@ #ifndef _GUAC_PLUGIN_CONSTANTS_H #define _GUAC_PLUGIN_CONSTANTS_H +#include "config.h" + /** * Constants related to client plugins. * * @file plugin-constants.h */ +#ifdef CYGWIN_BUILD + +/** + * String prefix which begins the library filename of all client plugins. + */ +#define GUAC_PROTOCOL_LIBRARY_PREFIX "cygguac-client-" + +/** + * String suffix which ends the library filename of all client plugins. + */ +#define GUAC_PROTOCOL_LIBRARY_SUFFIX "-0.dll" + +#else + /** * String prefix which begins the library filename of all client plugins. */ @@ -36,6 +52,8 @@ */ #define GUAC_PROTOCOL_LIBRARY_SUFFIX ".so" +#endif + /** * The maximum number of characters (COUNTING NULL TERMINATOR) to allow * for protocol names within the library filename of client plugins. diff --git a/src/libguac/guacamole/socket-handle.h b/src/libguac/guacamole/socket-handle.h new file mode 100755 index 0000000000..56c3c0c773 --- /dev/null +++ b/src/libguac/guacamole/socket-handle.h @@ -0,0 +1,50 @@ +/* + * 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_SOCKET_HANDLE_H +#define GUAC_SOCKET_HANDLE_H + +/** + * Provides an implementation of guac_socket specific to the Windows file handle + * API. This header willonly be available if libguac was built with Cgywin. + * + * @file socket-handle.h + */ + +#include "socket-types.h" + +#include + +/** + * Creates a new guac_socket which will use the Windows handle API for all + * communication. Freeing this guac_socket will automatically close the + * associated handle. + * + * @param handle + * The handle to use for the communicating with the connection underlying + * the created guac_socket. + * + * @return + * A newly-allocated guac_socket which will transparently use the Windows + * handle API for all communication. + */ +guac_socket* guac_socket_open_handle(HANDLE handle); + +#endif + diff --git a/src/libguac/guacamole/wait-handle.h b/src/libguac/guacamole/wait-handle.h new file mode 100644 index 0000000000..7e135f4b5b --- /dev/null +++ b/src/libguac/guacamole/wait-handle.h @@ -0,0 +1,44 @@ +/* + * 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_WAIT_HANDLE_H +#define GUAC_WAIT_HANDLE_H + +#include + +/** + * Waits for data to be available for reading on a given file handle. Returns + * zero if data is available, a negative value if the wait timed out without + * data being available, or a positive Windows error code if the wait failed. + * + * @param handle + * The file handle to wait for. + * + * @param usec_timeout + * The maximum number of microseconds to wait for data, or -1 to + * potentially wait forever. + * + * @return + * Zero if data is available for reading, negative if the timeout elapsed + * and no data is available, or a positive Windows error code if an error + * occurs. + */ +int guac_wait_for_handle(HANDLE handle, int usec_timeout); + +#endif diff --git a/src/libguac/handle-helpers.c b/src/libguac/handle-helpers.c new file mode 100755 index 0000000000..6c878336f5 --- /dev/null +++ b/src/libguac/handle-helpers.c @@ -0,0 +1,103 @@ +/* + * 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 "guacamole/error.h" +#include "guacamole/handle-helpers.h" + +#include +#include +#include +#include + +#include + +int guac_read_from_handle( + HANDLE handle, void* buffer, DWORD count, DWORD* num_bytes_read) { + + /* + * Overlapped structure and associated event for waiting on async call. + * The event isn't used by this function, but is required in order to + * reliably wait on an async operation anyway - see + * https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getoverlappedresult#remarks. + */ + OVERLAPPED overlapped = { 0 }; + overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + + /* Attempt to start the async read operation */ + if (!ReadFile(handle, buffer, count, NULL, &overlapped)) { + + DWORD error = GetLastError(); + + /* + * If an error other than the expected ERROR_IO_PENDING happens, + * return it as the error code immediately. + */ + if (error != ERROR_IO_PENDING) { + return error; + } + + } + + /* + * Wait on the result of the read. If any error occurs when waiting, + * return the error. + */ + if (!GetOverlappedResult(handle, &overlapped, num_bytes_read, TRUE)) + return GetLastError(); + + /* No errors occured, so the read was successful */ + return 0; + +} + +int guac_write_to_handle( + HANDLE handle, const void* buffer, DWORD count, DWORD* num_bytes_written) { + + /* + * Overlapped structure and associated event for waiting on async call. + */ + OVERLAPPED overlapped = { 0 }; + overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + + /* Attempt to start the async write operation */ + if (!WriteFile(handle, buffer, count, NULL, &overlapped)) { + + DWORD error = GetLastError(); + + /* + * If an error other than the expected ERROR_IO_PENDING happens, + * return it as the error code immediately. + */ + if (error != ERROR_IO_PENDING) + return error; + + } + + /* + * Wait on the result of the write. If any error occurs when waiting, + * return the error. + */ + if (!GetOverlappedResult(handle, &overlapped, num_bytes_written, TRUE)) + return GetLastError(); + + /* No errors occured, so the write was successful */ + return 0; + +} \ No newline at end of file diff --git a/src/libguac/id.c b/src/libguac/id.c index e627f89d83..610f836d55 100644 --- a/src/libguac/id.c +++ b/src/libguac/id.c @@ -20,7 +20,7 @@ #include "config.h" #include "guacamole/error.h" -#include "id.h" +#include #if defined(HAVE_LIBUUID) #include @@ -32,12 +32,6 @@ #include -/** - * The length of a UUID in bytes. All UUIDs are guaranteed to be 36 1-byte - * characters long. - */ -#define GUAC_UUID_LEN 36 - char* guac_generate_id(char prefix) { char* buffer; @@ -68,7 +62,7 @@ char* guac_generate_id(char prefix) { #endif /* Allocate buffer for future formatted ID */ - buffer = malloc(GUAC_UUID_LEN + 2); + buffer = malloc(GUAC_UUID_LEN + 1); if (buffer == NULL) { #ifndef HAVE_LIBUUID uuid_destroy(uuid); @@ -84,7 +78,7 @@ char* guac_generate_id(char prefix) { #ifdef HAVE_LIBUUID uuid_unparse_lower(uuid, identifier); #else - size_t identifier_length = GUAC_UUID_LEN + 1; + size_t identifier_length = GUAC_UUID_LEN; if (uuid_export(uuid, UUID_FMT_STR, &identifier, &identifier_length) != UUID_RC_OK) { free(buffer); uuid_destroy(uuid); @@ -98,7 +92,7 @@ char* guac_generate_id(char prefix) { #endif buffer[0] = prefix; - buffer[GUAC_UUID_LEN + 1] = '\0'; + buffer[GUAC_UUID_LEN] = '\0'; return buffer; } diff --git a/src/libguac/socket-handle.c b/src/libguac/socket-handle.c new file mode 100644 index 0000000000..dce0291ccb --- /dev/null +++ b/src/libguac/socket-handle.c @@ -0,0 +1,451 @@ +/* + * 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 "guacamole/error.h" +#include "guacamole/handle-helpers.h" +#include "guacamole/socket.h" +#include "guacamole/wait-handle.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/** + * Data associated with an open socket which writes to a file handle. + */ +typedef struct guac_socket_handle_data { + + /** + * The associated file handle. + */ + HANDLE handle; + + /** + * The number of bytes currently in the main write buffer. + */ + int written; + + /** + * The main write buffer. Bytes written go here before being flushed + * to the open file descriptor. + */ + char out_buf[GUAC_SOCKET_OUTPUT_BUFFER_SIZE]; + + /** + * Lock which is acquired when an instruction is being written, and + * released when the instruction is finished being written. + */ + pthread_mutex_t socket_lock; + + /** + * Lock which protects access to the internal buffer of this socket, + * guaranteeing atomicity of writes and flushes. + */ + pthread_mutex_t buffer_lock; + +} guac_socket_handle_data; + +/** + * Writes the entire contents of the given buffer to the file handle + * associated with the given socket, retrying as necessary until the whole + * buffer is written, and aborting if an error occurs. + * + * @param socket + * The guac_socket associated with the file handle to which the given + * buffer should be written. + * + * @param buf + * The buffer of data to write to the given guac_socket. + * + * @param count + * The number of bytes within the given buffer. + * + * @return + * The number of bytes written, which will be exactly the size of the given + * buffer, or a negative value if an error occurs. + */ +static ssize_t guac_socket_handle_write(guac_socket* socket, + const void* buf, size_t count) { + + guac_socket_handle_data* data = (guac_socket_handle_data*) socket->data; + const char* buffer = buf; + + /* Write until completely written */ + while (count > 0) { + + DWORD bytes_written; + + DWORD error = guac_write_to_handle(data->handle, buffer, count, &bytes_written); + + if (error) { + guac_error = GUAC_STATUS_SEE_WINDOWS_ERROR; + guac_windows_error_code = error; + guac_error_message = "Error writing data to handle"; + return -1; + } + + /* Advance buffer to next chunk */ + buffer += bytes_written; + count -= bytes_written; + + } + + return 0; + +} + +/** + * Attempts to read from the underlying file handle of the given + * guac_socket, populating the given buffer. + * + * @param socket + * The guac_socket being read from. + * + * @param buf + * The arbitrary buffer which we must populate with data. + * + * @param count + * The maximum number of bytes to read into the buffer. + * + * @return + * The number of bytes read, or -1 if an error occurs. + */ +static ssize_t guac_socket_handle_read_handler(guac_socket* socket, + void* buf, size_t count) { + + guac_socket_handle_data* data = (guac_socket_handle_data*) socket->data; + + DWORD bytes_read; + do { + + DWORD error = guac_read_from_handle(data->handle, buf, count, &bytes_read); + if (error) { + guac_error = GUAC_STATUS_SEE_WINDOWS_ERROR; + guac_windows_error_code = error; + guac_error_message = "Error reading data from handle"; + return -1; + } + + } while(bytes_read == 0); + + return bytes_read; + +} + +/** + * Flushes the contents of the output buffer of the given socket immediately, + * without first locking access to the output buffer. This function must ONLY + * be called if the buffer lock has already been acquired. + * + * @param socket + * The guac_socket to flush. + * + * @return + * Zero if the flush operation was successful, non-zero otherwise. + */ +static ssize_t guac_socket_handle_flush(guac_socket* socket) { + + guac_socket_handle_data* data = (guac_socket_handle_data*) socket->data; + + /* Flush remaining bytes in buffer */ + if (data->written > 0) { + + /* Write ALL bytes in buffer immediately */ + if (guac_socket_handle_write(socket, data->out_buf, data->written)) + return 1; + + data->written = 0; + } + + return 0; + +} + +/** + * Flushes the internal buffer of the given guac_socket, writing all data + * to the underlying file handle. + * + * @param socket + * The guac_socket to flush. + * + * @return + * Zero if the flush operation was successful, non-zero otherwise. + */ +static ssize_t guac_socket_handle_flush_handler(guac_socket* socket) { + + int retval; + guac_socket_handle_data* data = (guac_socket_handle_data*) socket->data; + + /* Acquire exclusive access to buffer */ + pthread_mutex_lock(&(data->buffer_lock)); + + /* Flush contents of buffer */ + retval = guac_socket_handle_flush(socket); + + /* Relinquish exclusive access to buffer */ + pthread_mutex_unlock(&(data->buffer_lock)); + + return retval; + +} + +/** + * Writes the contents of the buffer to the output buffer of the given socket, + * flushing the output buffer as necessary, without first locking access to the + * output buffer. This function must ONLY be called if the buffer lock has + * already been acquired. + * + * @param socket + * The guac_socket to write the given buffer to. + * + * @param buf + * The buffer to write to the given socket. + * + * @param count + * The number of bytes in the given buffer. + * + * @return + * The number of bytes written, or a negative value if an error occurs + * during write. + */ +static ssize_t guac_socket_handle_write_buffered(guac_socket* socket, + const void* buf, size_t count) { + + size_t original_count = count; + const char* current = buf; + guac_socket_handle_data* data = (guac_socket_handle_data*) socket->data; + + /* Append to buffer, flush if necessary */ + while (count > 0) { + + int chunk_size; + int remaining = sizeof(data->out_buf) - data->written; + + /* If no space left in buffer, flush and retry */ + if (remaining == 0) { + + /* Abort if error occurs during flush */ + if (guac_socket_handle_flush(socket)) + return -1; + + /* Retry buffer append */ + continue; + + } + + /* Calculate size of chunk to be written to buffer */ + chunk_size = count; + if (chunk_size > remaining) + chunk_size = remaining; + + /* Update output buffer */ + memcpy(data->out_buf + data->written, current, chunk_size); + data->written += chunk_size; + + /* Update provided buffer */ + current += chunk_size; + count -= chunk_size; + + } + + /* All bytes have been written, possibly some to the internal buffer */ + return original_count; + +} + +/** + * Appends the provided data to the internal buffer for future writing. The + * actual write attempt will occur only upon flush, or when the internal buffer + * is full. + * + * @param socket + * The guac_socket being write to. + * + * @param buf + * The arbitrary buffer containing the data to be written. + * + * @param count + * The number of bytes contained within the buffer. + * + * @return + * The number of bytes written, or -1 if an error occurs. + */ +static ssize_t guac_socket_handle_write_handler(guac_socket* socket, + const void* buf, size_t count) { + + int retval; + guac_socket_handle_data* data = (guac_socket_handle_data*) socket->data; + + /* Acquire exclusive access to buffer */ + pthread_mutex_lock(&(data->buffer_lock)); + + /* Write provided data to buffer */ + retval = guac_socket_handle_write_buffered(socket, buf, count); + + /* Relinquish exclusive access to buffer */ + pthread_mutex_unlock(&(data->buffer_lock)); + + return retval; + +} + +/** + * Waits for data on the underlying file handle of the given socket to + * become available such that the next read operation will not block. + * + * @param socket + * The guac_socket to wait for. + * + * @param usec_timeout + * The maximum amount of time to wait for data, in microseconds, or -1 to + * potentially wait forever. + * + * @return + * A positive value on success, zero if the timeout elapsed and no data is + * available, or a negative value if an error occurs. + */ +static int guac_socket_handle_select_handler(guac_socket* socket, + int usec_timeout) { + + /* Wait for data on socket */ + guac_socket_handle_data* data = (guac_socket_handle_data*) socket->data; + + int retval = guac_wait_for_handle(data->handle, usec_timeout); + + /* Properly set guac_error */ + if (retval > 0) { + guac_error = GUAC_STATUS_SEE_WINDOWS_ERROR; + guac_windows_error_code = retval; + guac_error_message = "Error while waiting for data on handle"; + return -1; + } + + else if (retval < 0) { + guac_error = GUAC_STATUS_TIMEOUT; + guac_error_message = "Timeout while waiting for data on handle"; + return 0; + } + + /* Data is ready */ + return 1; + +} + +/** + * Frees all implementation-specific data associated with the given socket, but + * not the socket object itself. + * + * @param socket + * The guac_socket whose associated data should be freed. + * + * @return + * Zero if the data was successfully freed, non-zero otherwise. This + * implementation always succeeds, and will always return zero. + */ +static int guac_socket_handle_free_handler(guac_socket* socket) { + + guac_socket_handle_data* data = (guac_socket_handle_data*) socket->data; + + /* Destroy locks */ + pthread_mutex_destroy(&(data->socket_lock)); + pthread_mutex_destroy(&(data->buffer_lock)); + + /* Close file handles */ + CloseHandle(data->handle); + + free(data); + return 0; + +} + +/** + * Acquires exclusive access to the given socket. + * + * @param socket + * The guac_socket to which exclusive access is required. + */ +static void guac_socket_handle_lock_handler(guac_socket* socket) { + + guac_socket_handle_data* data = (guac_socket_handle_data*) socket->data; + + /* Acquire exclusive access to socket */ + pthread_mutex_lock(&(data->socket_lock)); + +} + +/** + * Relinquishes exclusive access to the given socket. + * + * @param socket + * The guac_socket to which exclusive access is no longer required. + */ +static void guac_socket_handle_unlock_handler(guac_socket* socket) { + + guac_socket_handle_data* data = (guac_socket_handle_data*) socket->data; + + /* Relinquish exclusive access to socket */ + pthread_mutex_unlock(&(data->socket_lock)); + +} + +guac_socket* guac_socket_open_handle(HANDLE handle) { + + pthread_mutexattr_t lock_attributes; + + /* Allocate socket and associated data */ + guac_socket* socket = guac_socket_alloc(); + guac_socket_handle_data* data = malloc(sizeof(guac_socket_handle_data)); + + /* Store file handle as socket data */ + data->handle = handle; + data->written = 0; + socket->data = data; + + pthread_mutexattr_init(&lock_attributes); + pthread_mutexattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED); + + /* Init locks */ + pthread_mutex_init(&(data->socket_lock), &lock_attributes); + pthread_mutex_init(&(data->buffer_lock), &lock_attributes); + + /* Set read/write handlers */ + socket->read_handler = guac_socket_handle_read_handler; + socket->write_handler = guac_socket_handle_write_handler; + socket->select_handler = guac_socket_handle_select_handler; + socket->lock_handler = guac_socket_handle_lock_handler; + socket->unlock_handler = guac_socket_handle_unlock_handler; + socket->flush_handler = guac_socket_handle_flush_handler; + socket->free_handler = guac_socket_handle_free_handler; + + return socket; + +} + diff --git a/src/libguac/tests/id/generate.c b/src/libguac/tests/id/generate.c index 3142a6f10e..88c2f59e2a 100644 --- a/src/libguac/tests/id/generate.c +++ b/src/libguac/tests/id/generate.c @@ -17,7 +17,7 @@ * under the License. */ -#include "id.h" +#include #include #include diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index 0863325f71..1b6895ca40 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -78,9 +78,11 @@ static void guac_user_log_guac_error(guac_user* user, guac_error_message); /* Otherwise just log with standard status string */ - else - guac_user_log(user, level, "%s: %s", message, - guac_status_string(guac_error)); + else { + char* status_string = guac_status_string(guac_error); + guac_user_log(user, level, "%s: %s", message, status_string); + free(status_string); + } } @@ -107,10 +109,12 @@ static void guac_user_log_handshake_failure(guac_user* user) { "Guacamole protocol violation. Perhaps the version of " "guacamole-client is incompatible with this version of " "libguac?"); - else + else { + char* status_string = guac_status_string(guac_error); guac_user_log(user, GUAC_LOG_WARNING, - "Guacamole handshake failed: %s", - guac_status_string(guac_error)); + "Guacamole handshake failed: %s", status_string); + free(status_string); + } } diff --git a/src/libguac/user.c b/src/libguac/user.c index d16f43b93e..c2c7807b39 100644 --- a/src/libguac/user.c +++ b/src/libguac/user.c @@ -30,7 +30,7 @@ #include "guacamole/stream.h" #include "guacamole/timestamp.h" #include "guacamole/user.h" -#include "id.h" +#include #include "user-handlers.h" #include diff --git a/src/libguac/wait-handle.c b/src/libguac/wait-handle.c new file mode 100644 index 0000000000..0e31338687 --- /dev/null +++ b/src/libguac/wait-handle.c @@ -0,0 +1,67 @@ +/* + * 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 + +#include +#include +#include +#include + +int guac_wait_for_handle(HANDLE handle, int usec_timeout) { + + HANDLE event = CreateEvent(NULL, FALSE, FALSE, NULL); + OVERLAPPED overlapped = { 0 }; + + /* Set the event to be used to signal comm events */ + overlapped.hEvent = event; + + /* Request to wait for new data to be available */ + char buff[1]; + if (!ReadFile(handle, &buff, 0, NULL, &overlapped)) { + + DWORD error = GetLastError(); + + /* ERROR_IO_PENDING is expected in overlapped mode */ + if (error != ERROR_IO_PENDING) + return error; + + } + + int millis = (usec_timeout + 999) / 1000; + + DWORD result = WaitForSingleObject(event, millis); + + /* The wait attempt failed */ + if (result == WAIT_FAILED) + return GetLastError(); + + /* The event was signalled, which should indicate data is ready */ + else if (result == WAIT_OBJECT_0) + return 0; + + /* + * If the event didn't trigger and the wait didn't fail, data just isn't + * ready yet. + */ + return -1; + +} diff --git a/src/protocols/kubernetes/Makefile.am b/src/protocols/kubernetes/Makefile.am index e9316ce49b..e488a83d95 100644 --- a/src/protocols/kubernetes/Makefile.am +++ b/src/protocols/kubernetes/Makefile.am @@ -69,5 +69,6 @@ libguac_client_kubernetes_la_LDFLAGS = \ -version-info 0:0:0 \ @PTHREAD_LIBS@ \ @SSL_LIBS@ \ - @WEBSOCKETS_LIBS@ + @WEBSOCKETS_LIBS@ \ + @CYWGIN_LDFLAGS@ diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 1c393c30d8..b8dd53479e 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -144,7 +144,8 @@ libguac_client_rdp_la_LDFLAGS = \ -version-info 0:0:0 \ @CAIRO_LIBS@ \ @PTHREAD_LIBS@ \ - @RDP_LIBS@ + @RDP_LIBS@ \ + @CYWGIN_LDFLAGS@ libguac_client_rdp_la_LIBADD = \ @COMMON_LTLIB@ \ @@ -174,7 +175,8 @@ libguac_common_svc_client_la_CFLAGS = \ libguac_common_svc_client_la_LDFLAGS = \ -module -avoid-version -shared \ - @RDP_LIBS@ + @RDP_LIBS@ \ + @CYWGIN_LDFLAGS@ libguac_common_svc_client_la_LIBADD = \ @LIBGUAC_LTLIB@ @@ -199,7 +201,8 @@ libguacai_client_la_CFLAGS = \ libguacai_client_la_LDFLAGS = \ -module -avoid-version -shared \ @PTHREAD_LIBS@ \ - @RDP_LIBS@ + @RDP_LIBS@ \ + @CYWGIN_LDFLAGS@ libguacai_client_la_LIBADD = \ @COMMON_LTLIB@ \ @@ -264,4 +267,3 @@ EXTRA_DIST = \ $(rdp_keymaps) \ keymaps/generate.pl \ plugins/generate-entry-wrappers.pl - diff --git a/src/protocols/rdp/channels/rdpgfx.c b/src/protocols/rdp/channels/rdpgfx.c index 0fae972f63..a8b6da6cd7 100644 --- a/src/protocols/rdp/channels/rdpgfx.c +++ b/src/protocols/rdp/channels/rdpgfx.c @@ -17,6 +17,8 @@ * under the License. */ +#include "config.h" + #include "channels/rdpgfx.h" #include "plugins/channels.h" #include "rdp.h" @@ -62,10 +64,19 @@ static void guac_rdp_rdpgfx_channel_connected(rdpContext* context, RdpgfxClientContext* rdpgfx = (RdpgfxClientContext*) args->pInterface; rdpGdi* gdi = context->gdi; +#ifdef CYGWIN_BUILD + + /* Return type is void under cygwin */ + gdi_graphics_pipeline_init(gdi, rdpgfx); + +#else + if (!gdi_graphics_pipeline_init(gdi, rdpgfx)) guac_client_log(client, GUAC_LOG_WARNING, "Rendering backend for RDPGFX " "channel could not be loaded. Graphics may not render at all!"); else + +#endif guac_client_log(client, GUAC_LOG_DEBUG, "RDPGFX channel will be used for " "the RDP Graphics Pipeline Extension."); diff --git a/src/protocols/ssh/Makefile.am b/src/protocols/ssh/Makefile.am index 7bb1e3498c..d586315fba 100644 --- a/src/protocols/ssh/Makefile.am +++ b/src/protocols/ssh/Makefile.am @@ -74,5 +74,5 @@ libguac_client_ssh_la_LDFLAGS = \ -version-info 0:0:0 \ @PTHREAD_LIBS@ \ @SSH_LIBS@ \ - @SSL_LIBS@ - + @SSL_LIBS@ \ + @CYWGIN_LDFLAGS@ diff --git a/src/protocols/telnet/Makefile.am b/src/protocols/telnet/Makefile.am index d0264f674c..1b78b13938 100644 --- a/src/protocols/telnet/Makefile.am +++ b/src/protocols/telnet/Makefile.am @@ -61,5 +61,6 @@ libguac_client_telnet_la_LIBADD = \ libguac_client_telnet_la_LDFLAGS = \ -version-info 0:0:0 \ @PTHREAD_LIBS@ \ - @TELNET_LIBS@ + @TELNET_LIBS@ \ + @CYWGIN_LDFLAGS@ diff --git a/src/protocols/vnc/Makefile.am b/src/protocols/vnc/Makefile.am index 64e8147e66..b19d123ab4 100644 --- a/src/protocols/vnc/Makefile.am +++ b/src/protocols/vnc/Makefile.am @@ -64,7 +64,8 @@ libguac_client_vnc_la_CFLAGS = \ libguac_client_vnc_la_LDFLAGS = \ -version-info 0:0:0 \ @CAIRO_LIBS@ \ - @VNC_LIBS@ + @VNC_LIBS@ \ + @CYWGIN_LDFLAGS@ libguac_client_vnc_la_LIBADD = \ @COMMON_LTLIB@ \ diff --git a/src/pulse/Makefile.am b/src/pulse/Makefile.am index 3cb2d44cba..7ed891f109 100644 --- a/src/pulse/Makefile.am +++ b/src/pulse/Makefile.am @@ -42,5 +42,6 @@ libguac_pulse_la_LIBADD = \ @LIBGUAC_LTLIB@ libguac_pulse_la_LDFLAGS = \ - @PULSE_LIBS@ + @PULSE_LIBS@ \ + @CYWGIN_LDFLAGS@ diff --git a/src/terminal/Makefile.am b/src/terminal/Makefile.am index 38baedbadc..89122b9301 100644 --- a/src/terminal/Makefile.am +++ b/src/terminal/Makefile.am @@ -83,5 +83,6 @@ libguac_terminal_la_LDFLAGS = \ @MATH_LIBS@ \ @PANGO_LIBS@ \ @PANGOCAIRO_LIBS@ \ - @PTHREAD_LIBS@ + @PTHREAD_LIBS@ \ + @CYWGIN_LDFLAGS@ diff --git a/src/terminal/color-scheme.c b/src/terminal/color-scheme.c index c1a9e3ff74..60e68ad8cd 100644 --- a/src/terminal/color-scheme.c +++ b/src/terminal/color-scheme.c @@ -17,6 +17,7 @@ * under the License. */ +#include "config.h" #include "terminal/color-scheme.h" #include "terminal/palette.h" @@ -71,6 +72,20 @@ static int guac_terminal_color_scheme_compare_token(const char* str_start, */ static void guac_terminal_color_scheme_strip_spaces(const char** str_start, const char** str_end) { + +#ifdef CYGWIN_BUILD + + /* Cast to unsigned below to avoid error: array subscript has type 'char' */ + + /* Strip leading spaces. */ + while (*str_start < *str_end && isspace((const unsigned char) (**str_start))) + (*str_start)++; + + /* Strip trailing spaces. */ + while (*str_end > *str_start && isspace((const unsigned char) *(*str_end - 1))) + (*str_end)--; + +#else /* Strip leading spaces. */ while (*str_start < *str_end && isspace(**str_start)) @@ -79,6 +94,9 @@ static void guac_terminal_color_scheme_strip_spaces(const char** str_start, /* Strip trailing spaces. */ while (*str_end > *str_start && isspace(*(*str_end - 1))) (*str_end)--; + +#endif + } /** diff --git a/src/terminal/named-colors.c b/src/terminal/named-colors.c index 948982afe3..1ed23614ee 100644 --- a/src/terminal/named-colors.c +++ b/src/terminal/named-colors.c @@ -17,6 +17,8 @@ * under the License. */ +#include "config.h" + #include "terminal/palette.h" #include @@ -758,7 +760,14 @@ static int guac_terminal_named_color_search(const void* a, const void* b) { for (; *key && *name; key++, name++) { /* Skip any spaces in key (name will never have spaces) */ + +#ifdef CYGWIN_BUILD + + /* Cast to unsigned to avoid error: array subscript has type 'char' */ + while (*key && isspace((unsigned char) *key)) key++; +#else while (*key && isspace(*key)) key++; +#endif /* Treat semi-colon as string terminator, to support parsing color names within a larger string (e.g. within the terminal color-scheme