From 3f45f0f6c603bf48d6fc585f1ae8cd9d87498aaa Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:12:45 +0100 Subject: [PATCH 1/8] xrdpapi: Add a way to get client connect status --- common/Makefile.am | 1 + common/channel_defs.h | 52 ++++++++++++++++++++++ sesman/chansrv/chansrv.c | 78 +++++++++++++++++++++++++++++++-- xrdpapi/xrdpapi.c | 94 ++++++++++++++++++++++++++++++++++++++-- xrdpapi/xrdpapi.h | 73 +++++++++++++++++++++++++++++++ 5 files changed, 291 insertions(+), 7 deletions(-) create mode 100644 common/channel_defs.h diff --git a/common/Makefile.am b/common/Makefile.am index 4e98f52987..56ecb1b32f 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -47,6 +47,7 @@ libcommon_la_SOURCES = \ arch.h \ base64.h \ base64.c \ + channel_defs.h \ defines.h \ fifo.c \ fifo.h \ diff --git a/common/channel_defs.h b/common/channel_defs.h new file mode 100644 index 0000000000..a416e49072 --- /dev/null +++ b/common/channel_defs.h @@ -0,0 +1,52 @@ +/** + * Copyright (C) 2024 Matt Burt, all xrdp contributors + * + * Licensed 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. + */ + +/** + * @file common/channel_defs.h + * @brief Private xrdp channel definitions + */ + +#if !defined(CHANNEL_DEFS_H) +#define CHANNEL_DEFS_H + +#include + +// Name of private channel between chansrc and xrdpapi for session info +#define CHAN_NAME_XRDP_SESSION_INFO "xrdp/SessInfo" + +/** + * Channel IDs assigned to xrdp private channels. These are chosen to avoid conflicts with + * channel IDs returned by other servers. + */ +enum +{ + CHAN_ID_XRDP_SESSION_INFO = (('x' << 24) | ('r' << 16) | ('d' << 8) | 'p') + // Add further IDs here +}; + +// Defines from xrdpapi.h (exact copies) +#define WM_WTSSESSION_CHANGE 0x02B1 +#define WTS_REMOTE_CONNECT 0x3 +#define WTS_REMOTE_DISCONNECT 0x4 + +struct xrdp_chan_session_info +{ + uint32_t msgno; + intptr_t wparam; + intptr_t lparam; +}; + +#endif /* CHANNEL_DEFS_H */ diff --git a/sesman/chansrv/chansrv.c b/sesman/chansrv/chansrv.c index c75c4f9e8a..3b4c92e818 100644 --- a/sesman/chansrv/chansrv.c +++ b/sesman/chansrv/chansrv.c @@ -44,6 +44,7 @@ #include "chansrv_config.h" #include "xrdp_sockets.h" #include "audin.h" +#include "channel_defs.h" #include "scp.h" #include "scp_sync.h" @@ -56,7 +57,7 @@ static struct trans *g_lis_trans = 0; static struct trans *g_con_trans = 0; static struct trans *g_api_lis_trans = 0; static struct list *g_api_con_trans_list = 0; /* list of apps using api functions */ -static struct chan_item g_chan_items[32]; +static struct chan_item g_chan_items[32 + 8]; // Allow 8 extra for private channels static int g_num_chan_items = 0; static int g_cliprdr_index = -1; static int g_rdpsnd_index = -1; @@ -343,6 +344,57 @@ send_rail_drawing_orders(char *data, int size) return 0; } +/*****************************************************************************/ +/** + * Sends a session-related API event to all listeners (if any) + */ +static void +xrdpapi_send_session_event(unsigned int msgno, intptr_t wparam, intptr_t lparam) +{ + struct xrdp_chan_session_info msg_data = + { + .msgno = msgno, + .wparam = wparam, + .lparam = lparam + }; + + int index; + struct trans *ltran; + struct xrdp_api_data *api_data; + struct stream *ls; + + for (index = 0; index < g_api_con_trans_list->count; index++) + { + ltran = (struct trans *) list_get_item(g_api_con_trans_list, index); + if (ltran != NULL) + { + api_data = (struct xrdp_api_data *) (ltran->callback_data); + if (api_data != NULL) + { + if (api_data->chan_id == CHAN_ID_XRDP_SESSION_INFO) + { + ls = ltran->out_s; + init_stream(ls, (int)sizeof(msg_data)); + out_uint8a(ls, &msg_data, sizeof(msg_data)); + s_mark_end(ls); + (void)trans_write_copy(ltran); + } + } + } + } +} + +/*****************************************************************************/ +static void +xrdpapi_send_session_connect_event(int connect_made) +{ + intptr_t wparam = + (connect_made) ? WTS_REMOTE_CONNECT : WTS_REMOTE_DISCONNECT; + intptr_t lparam = 0; + + xrdpapi_send_session_event(WM_WTSSESSION_CHANGE, wparam, lparam); +} + /*****************************************************************************/ /* returns error */ static int @@ -406,6 +458,13 @@ process_message_channel_setup(struct stream *s) g_num_chan_items++; } + // Add private channels to the static list + ci = &(g_chan_items[g_num_chan_items]); + ci->id = CHAN_ID_XRDP_SESSION_INFO; + ci->flags = 0; + strlcpy(ci->name, CHAN_NAME_XRDP_SESSION_INFO, sizeof(ci->name)); + ++g_num_chan_items; + rv = 0; if (g_cliprdr_index >= 0) @@ -431,6 +490,7 @@ process_message_channel_setup(struct stream *s) } audin_init(); + xrdpapi_send_session_connect_event(1); return rv; } @@ -1190,7 +1250,7 @@ my_api_trans_data_in(struct trans *trans) //LOG_DEVEL(LOG_LEVEL_DEBUG, "my_api_trans_data_in: chan_name %s chan_flags 0x%8.8x", chan_name, ad->chan_flags); if (ad->chan_flags == 0) { - /* SVC */ + /* SVC (and private) */ for (index = 0; index < g_num_chan_items; index++) { if (g_strcasecmp(g_chan_items[index].name, chan_name) == 0) @@ -1259,8 +1319,16 @@ my_api_trans_data_in(struct trans *trans) } if (ad->chan_flags == 0) { - /* SVC */ - rv = send_channel_data(ad->chan_id, s->data, bytes); + if (ad->chan_id == CHAN_ID_XRDP_SESSION_INFO) + { + // Ignore data sent to this channel + rv = 0; + } + else + { + /* SVC */ + rv = send_channel_data(ad->chan_id, s->data, bytes); + } } else { @@ -1513,6 +1581,7 @@ channel_thread_loop(void *in_val) if (g_is_wait_obj_set(g_term_event)) { LOG_DEVEL(LOG_LEVEL_INFO, "channel_thread_loop: g_term_event set"); + xrdpapi_send_session_connect_event(0); clipboard_deinit(); sound_deinit(); devredir_deinit(); @@ -1535,6 +1604,7 @@ channel_thread_loop(void *in_val) { LOG_DEVEL(LOG_LEVEL_INFO, "channel_thread_loop: " "trans_check_wait_objs error resetting"); + xrdpapi_send_session_connect_event(0); clipboard_deinit(); sound_deinit(); devredir_deinit(); diff --git a/xrdpapi/xrdpapi.c b/xrdpapi/xrdpapi.c index b8ce4ff244..97114c775e 100644 --- a/xrdpapi/xrdpapi.c +++ b/xrdpapi/xrdpapi.c @@ -37,6 +37,7 @@ #include "log.h" #include "xrdp_sockets.h" #include "string_calls.h" +#include "channel_defs.h" #include "xrdpapi.h" struct wts_obj @@ -45,6 +46,16 @@ struct wts_obj int display_num; }; +/** + * Data we store for each server + */ +struct wts_server +{ + struct wts_obj *info_obj; // Object to get session notifications +}; + +static struct wts_server wts_current_server; + /* helper functions used by WTSxxx API - do not invoke directly */ static int can_send(int sck, int millis, int restart); @@ -72,8 +83,8 @@ free_wts(struct wts_obj *wts) * Opens a handle to the server end of a specified virtual channel - this * call is deprecated - use WTSVirtualChannelOpenEx() instead * - * @param hServer - * @param SessionId - current session ID; *must* be WTS_CURRENT_SERVER_HANDLE + * @param hServer - *must* be WTS_CURRENT_SERVER_HANDLE + * @param SessionId - current session ID; *must* be WTS_CURRENT_SESSION * @param pVirtualName - virtual channel name when using SVC * - name of endpoint listener when using DVC * @@ -94,7 +105,7 @@ WTSVirtualChannelOpen(void *hServer, unsigned int SessionId, /* * Opens a handle to the server end of a specified virtual channel * - * @param SessionId - current session ID; *must* be WTS_CURRENT_SERVER_HANDLE + * @param SessionId - current session ID; *must* be WTS_CURRENT_SESSION * @param pVirtualName - virtual channel name when using SVC * - name of endpoint listener when using DVC * @param flags - type of channel and channel priority if DVC @@ -118,6 +129,7 @@ WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName, LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: bad SessionId"); return 0; } + wts = (struct wts_obj *) calloc(1, sizeof(struct wts_obj)); if (wts == NULL) { @@ -541,3 +553,79 @@ can_recv(int sck, int millis, int restart) return rv; } + +/*****************************************************************************/ +int WTSRegisterSessionNotificationEx(char *hServer, + int *fd_ptr, + int dwFlags) +{ + (void)dwFlags; // Unused parameter + if (hServer != WTS_CURRENT_SERVER_HANDLE) + { + LOG(LOG_LEVEL_ERROR, "WTSRegisterSessionNotificationEx: bad hServer"); + return 0; + } + + if (wts_current_server.info_obj == NULL) + { + // Open a private channel for session info messages + // The flags arg is ignored for xrdp private channels + wts_current_server.info_obj = + (struct wts_obj *) + WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, + CHAN_NAME_XRDP_SESSION_INFO, 0); + } + if (wts_current_server.info_obj == NULL) + { + *fd_ptr = -1; + return 0; + } + *fd_ptr = wts_current_server.info_obj->fd; + return 1; +} + +/*****************************************************************************/ +int WTSUnRegisterSessionNotificationEx(char *hServer, + int fd) +{ + (void)fd; // Unused parameter + if (hServer != WTS_CURRENT_SERVER_HANDLE) + { + LOG(LOG_LEVEL_ERROR, "WTSUnRegisterSessionNotificationEx: bad hServer"); + return 0; + } + + free_wts(wts_current_server.info_obj); + return 1; +} + +/*****************************************************************************/ +int +WTSGetDispatchMessage(void *cbdata, WNDPROC wndproc, LRESULT *lResult) +{ + LRESULT result = 0; + struct xrdp_chan_session_info msg_data; + + /* get response */ + if (wts_current_server.info_obj == NULL) + { + LOG(LOG_LEVEL_ERROR, "WTSGetDispatchMessage: No notification channel was opened"); + } + else if (!can_recv(wts_current_server.info_obj->fd, 0, 1)) + { + // No message available - nothing to log + } + else if (myrecv(wts_current_server.info_obj->fd, + &msg_data, sizeof(msg_data)) != sizeof(msg_data)) + { + LOG(LOG_LEVEL_ERROR, "WTSGetDispatchMessage: An incomplete message was received"); + } + else + { + *lResult = wndproc(cbdata, msg_data.msgno, + msg_data.wparam, msg_data.lparam); + result = 1; + } + + return result; +} diff --git a/xrdpapi/xrdpapi.h b/xrdpapi/xrdpapi.h index b818d93e02..1486b43849 100644 --- a/xrdpapi/xrdpapi.h +++ b/xrdpapi/xrdpapi.h @@ -26,6 +26,8 @@ #if !defined(XRDPAPI_H_) #define XRDPAPI_H_ +#include + #ifdef __cplusplus extern "C" { #endif @@ -41,6 +43,16 @@ extern "C" { #define WTS_CHANNEL_OPTION_DYNAMIC_PRI_HIGH 0x00000003 #define WTS_CHANNEL_OPTION_DYNAMIC_PRI_REAL 0x00000004 +#define WM_WTSSESSION_CHANGE 0x02B1 + +/* + * codes passed in WPARAM for WM_WTSSESSION_CHANGE + * Unlisted codes are not yet implemented. + */ +#define WTS_REMOTE_CONNECT 0x3 +#define WTS_REMOTE_DISCONNECT 0x4 + + typedef enum _WTS_VIRTUAL_CLASS { WTSVirtualClientData, @@ -48,6 +60,18 @@ typedef enum _WTS_VIRTUAL_CLASS } WTS_VIRTUAL_CLASS; +// Win32 basic types +// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types +typedef uint32_t UINT; +typedef intptr_t UINT_PTR; +typedef intptr_t LONG_PTR; +typedef UINT_PTR WPARAM; +typedef LONG_PTR LPARAM; +typedef LONG_PTR LRESULT; + +// WNDPROC emulation for WTSRegisterSessionNotificationEx() +typedef LRESULT WNDPROC(void *cbdata, UINT msg, WPARAM wParam, LPARAM lParam); + /* * Reference: * http://msdn.microsoft.com/en-us/library/windows/desktop/aa383464(v=vs.85).aspx @@ -73,6 +97,55 @@ int WTSVirtualChannelQuery(void *hChannelHandle, WTS_VIRTUAL_CLASS WtsVirtualCla void WTSFreeMemory(void *pMemory); +/** + * This function is similar to, but not the same as the Win32 + * function of the same name. + * + * The purpose of it is to allow an application to receive + * WM_WTSSESSION_CHANGE messages. + * + * @param hServer set to WTS_CURRENT_SERVER_HANDLE + * @param[out] fd_ptr File descriptor to check for notification messages + * @param dwFlags ignored + * @return true for success + * + * The fd_ptr replaces the hWnd parameter in the Win32 call. + * + * After a successful call, the location pointed-to by fd_ptr will contain a + * file descriptor which the caller can poll for session change messages. + * When the file descriptor becomes readable, a call to + * WTSGetDispatchMessage() will process a single session change message. + * + * The caller must do nothing with the returned file descriptor except poll it. + */ +int WTSRegisterSessionNotificationEx(char *hServer, + int *fd_ptr, + int dwFlags); + +/** + * This function is similar to, but not the same as the Win32 + * function of the same name. + * + * The purpose of it is to deallocate resources associated with + * WTSRegisterSessionNotificationEx() + * + * @param hServer set to WTS_CURRENT_SERVER_HANDLE + * @param fd File descriptor from WTSRegisterSessionNotificationEx + */ +int WTSUnRegisterSessionNotificationEx(char *hServer, + int fd); + + +/** Replaces Win32 GetMessage() / DispatchMessage() + * + * @param cbdata callback data to pass in to WNDPROC + * @param wndproc WNDPROC to call + * @param[out] lResult Result of WNDPROC if call is successful + * @return != 0 if a WNDPROC was successfully called + */ +int +WTSGetDispatchMessage(void *cbdata, WNDPROC wndproc, LRESULT *lResult); + #ifdef __cplusplus } #endif From 56deb56168623e4f6884cc3ab35b0bc8102b30d2 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:02:35 +0100 Subject: [PATCH 2/8] xrdpapi: Fix memory leak in WTSVirtualChannelOpenEx() --- xrdpapi/xrdpapi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xrdpapi/xrdpapi.c b/xrdpapi/xrdpapi.c index 97114c775e..c1afbb7f02 100644 --- a/xrdpapi/xrdpapi.c +++ b/xrdpapi/xrdpapi.c @@ -261,6 +261,8 @@ WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName, return NULL; } + free(connect_data); + return wts; } From 09c99472150cd62d6b44c14e0e14b878fa15648b Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:03:09 +0100 Subject: [PATCH 3/8] xrdpapi: Add demo program for session events --- xrdpapi/Makefile.am | 15 ++++- xrdpapi/connectmon.c | 144 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 xrdpapi/connectmon.c diff --git a/xrdpapi/Makefile.am b/xrdpapi/Makefile.am index 266b07a52f..a980163504 100644 --- a/xrdpapi/Makefile.am +++ b/xrdpapi/Makefile.am @@ -1,5 +1,6 @@ EXTRA_DIST = \ simple.c \ + connectmon.c \ vrplayer.c \ vrplayer.mk @@ -17,8 +18,18 @@ libxrdpapi_la_SOURCES = \ libxrdpapi_la_LIBADD = \ $(top_builddir)/common/libcommon.la -# Build the 'simple' example program, so it's added to the CI -noinst_PROGRAMS = xrdp-xrdpapi-simple +# Build the 'simple' example programs, so they are added to the CI +noinst_PROGRAMS = \ + xrdp-xrdpapi-connectmon \ + xrdp-xrdpapi-simple + +xrdp_xrdpapi_connectmon_SOURCES = \ + connectmon.c + +# If you change this, update the standalone build instructions in connectmon.c +xrdp_xrdpapi_connectmon_LDADD = \ + libxrdpapi.la \ + $(top_builddir)/common/libcommon.la xrdp_xrdpapi_simple_SOURCES = \ simple.c diff --git a/xrdpapi/connectmon.c b/xrdpapi/connectmon.c new file mode 100644 index 0000000000..190c835bef --- /dev/null +++ b/xrdpapi/connectmon.c @@ -0,0 +1,144 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2012-2013 + * Copyright (C) Laxmikant Rashinkar 2012-2013 + * + * Licensed 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. + * + * sample program to demonstrate use of xrdpapi connection monitoring + * + */ + +/* + * build instructions: + * gcc connectmon.c -o connectmon \ + * -I.. -I../common -L./.libs -L../common/.libs \ + * -DHAVE_CONFIG_H -lxrdpapi -lcommon + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include "xrdpapi.h" +#include "log.h" + +#include +#include +#include +#include + +/******************************************************************************/ +static LRESULT +callback (void *cbdata, UINT msg, WPARAM wParam, LPARAM lParam) +{ + LRESULT result = 1; + int *countptr = (int *)cbdata; + + switch (wParam) + { + case WTS_REMOTE_CONNECT: + ++*countptr; + printf("State change %d : Connect to session\n", *countptr); + break; + + case WTS_REMOTE_DISCONNECT: + ++*countptr; + printf("State change %d : Disconnect from session\n", *countptr); + break; + + default: + printf("** Unexpected callback reason %ld\n", (long)wParam); + result = 0; + } + + return result; +} + +/******************************************************************************/ +int +main(int argc, char **argv) +{ + int result = 0; + int changes = 10; + int fd; + struct log_config *lc; + + if ((lc = log_config_init_for_console(LOG_LEVEL_DEBUG, NULL)) != NULL) + { + log_start_from_param(lc); + } + + if (argc > 1 && atoi(argv[1]) > 0) + { + changes = atoi(argv[1]); + } + printf("Connection monitor : exiting after %d state changes\n", changes); + + if (!WTSRegisterSessionNotificationEx(WTS_CURRENT_SERVER_HANDLE, &fd, 0)) + { + result = 1; // Error occurred (should be logged) + } + else + { + int count = 0; + while (count < changes && result == 0) + { + struct pollfd pollarg = + { + .fd = fd, + .events = (POLLIN | POLLERR), + .revents = 0 + }; + int pollstat; + LRESULT cbresult; + + if ((pollstat = poll(&pollarg, 1, -1)) < 0) + { + printf("** Error from poll() : %s\n", strerror(errno)); + result = 1; + } + else if (pollstat == 0) + { + printf("** Timeout from poll() !?\n"); + result = 1; + } + else if ((pollarg.revents & POLLERR)) + { + printf("** File descriptor was closed\n"); + result = 1; + } + else if (!WTSGetDispatchMessage(&count, callback, &cbresult)) + { + printf("** Unexpected faiure to dispatch message\n"); + result = 1; + } + else if (!cbresult) + { + printf("** Callback failed\n"); + result = 1; + } + } + + (void)WTSUnRegisterSessionNotificationEx(WTS_CURRENT_SERVER_HANDLE, fd); + } + + if (lc != NULL) + { + log_config_free(lc); + log_end(); + } + + return result; +} From d63edba0aad31d0947b679c316fd0bfacaca7d84 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:05:15 +0000 Subject: [PATCH 4/8] xrdpapi: Fix possible memory leak --- xrdpapi/xrdpapi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/xrdpapi/xrdpapi.c b/xrdpapi/xrdpapi.c index c1afbb7f02..e0b8eba963 100644 --- a/xrdpapi/xrdpapi.c +++ b/xrdpapi/xrdpapi.c @@ -598,6 +598,7 @@ int WTSUnRegisterSessionNotificationEx(char *hServer, } free_wts(wts_current_server.info_obj); + wts_current_server.info_obj = NULL; return 1; } From 2999372dc4edf129160c2ea41c54f85d0d7fb24d Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:28:57 +0000 Subject: [PATCH 5/8] xrdpapi: Make the session info channel private The xrdp private channels are no longer in the same namespace as standard xrdp channels, making it impossible for the user to open them with the API in xrdp/xrdpapi.h For readability, a single struct is now passed from xrdpapi to chansrv for the connect operation. --- common/channel_defs.h | 28 ++++++-- common/parse.h | 8 +++ sesman/chansrv/chansrv.c | 77 ++++++++++------------ xrdpapi/xrdpapi.c | 138 ++++++++++++++++++--------------------- 4 files changed, 129 insertions(+), 122 deletions(-) diff --git a/common/channel_defs.h b/common/channel_defs.h index a416e49072..c4d03738e8 100644 --- a/common/channel_defs.h +++ b/common/channel_defs.h @@ -24,17 +24,16 @@ #include -// Name of private channel between chansrc and xrdpapi for session info -#define CHAN_NAME_XRDP_SESSION_INFO "xrdp/SessInfo" - /** * Channel IDs assigned to xrdp private channels. These are chosen to avoid conflicts with * channel IDs returned by other servers. */ enum { - CHAN_ID_XRDP_SESSION_INFO = (('x' << 24) | ('r' << 16) | ('d' << 8) | 'p') - // Add further IDs here + CHAN_ID_XRDP_BASE = (('x' << 24) | ('r' << 16) | ('d' << 8) | 'p'), + CHAN_ID_XRDP_SESSION_INFO, + // Add further IDs here + CHAN_ID_XRDP_MAX }; // Defines from xrdpapi.h (exact copies) @@ -42,6 +41,25 @@ enum #define WTS_REMOTE_CONNECT 0x3 #define WTS_REMOTE_DISCONNECT 0x4 +// Max length of a DVC name. This is taken from the specification of +// WTSVirtualChannelOpenEx() which limits the length of a virtual channel +// to Windows MAX_PATH +#define MAX_DVC_NAME_LEN 260 + +/** + * Information to connect to a channel using xrdpapi + */ +#define XRDPAPI_CONNECT_PDU_LEN 384 // Connect PDU is always this length +#define XRDPAPI_CONNECT_PDU_VERSION 1 // Current connnect PDU version + +struct xrdp_chan_connect +{ + uint32_t version; + uint32_t private_chan; // 0 for a standard channel + uint32_t flags; + char name[MAX_DVC_NAME_LEN + 1]; // null-terminated +}; + struct xrdp_chan_session_info { uint32_t msgno; diff --git a/common/parse.h b/common/parse.h index 2b3596d947..dccdb4415c 100644 --- a/common/parse.h +++ b/common/parse.h @@ -528,6 +528,14 @@ in_utf16_le_terminated_as_utf8_length(struct stream *s); (s)->p++; \ } while (0) +/******************************************************************************/ +// Platform-endian out_uint32 +#if defined(B_ENDIAN) +#define out_uint32_pe(s, v) out_uint32_be(s, v) +#else +#define out_uint32_pe(s, v) out_uint32_le(s, v) +#endif + /******************************************************************************/ #define in_uint8p(s, v, n) do \ { \ diff --git a/sesman/chansrv/chansrv.c b/sesman/chansrv/chansrv.c index 3b4c92e818..87a0f810ac 100644 --- a/sesman/chansrv/chansrv.c +++ b/sesman/chansrv/chansrv.c @@ -51,13 +51,11 @@ #include "ms-rdpbcgr.h" -#define MAX_PATH 260 - static struct trans *g_lis_trans = 0; static struct trans *g_con_trans = 0; static struct trans *g_api_lis_trans = 0; static struct list *g_api_con_trans_list = 0; /* list of apps using api functions */ -static struct chan_item g_chan_items[32 + 8]; // Allow 8 extra for private channels +static struct chan_item g_chan_items[32]; static int g_num_chan_items = 0; static int g_cliprdr_index = -1; static int g_rdpsnd_index = -1; @@ -458,13 +456,6 @@ process_message_channel_setup(struct stream *s) g_num_chan_items++; } - // Add private channels to the static list - ci = &(g_chan_items[g_num_chan_items]); - ci->id = CHAN_ID_XRDP_SESSION_INFO; - ci->flags = 0; - strlcpy(ci->name, CHAN_NAME_XRDP_SESSION_INFO, sizeof(ci->name)); - ++g_num_chan_items; - rv = 0; if (g_cliprdr_index >= 0) @@ -1125,7 +1116,7 @@ my_api_open_response(int chan_id, int creation_status) { return 1; } - out_uint32_le(s, creation_status); + out_uint32_pe(s, creation_status); s_mark_end(s); if (trans_write_copy(trans) != 0) { @@ -1197,7 +1188,7 @@ my_api_data(int chan_id, char *data, int bytes) } /* - * called when WTSVirtualChannelWrite() is invoked in xrdpapi.c + * called when VirtualChannelOpen() is invoked in xrdpapi.c * ******************************************************************************/ static int @@ -1209,14 +1200,8 @@ my_api_trans_data_in(struct trans *trans) int index; int rv; int bytes; - int ver; struct chansrv_drdynvc_procs procs; - /* - * Name is limited to CHANNEL_NAME_LEN for an SVC, or MAX_PATH - * bytes for a DVC - */ - char chan_name[MAX(CHANNEL_NAME_LEN, MAX_PATH) + 1]; - unsigned int channel_name_bytes; + struct xrdp_chan_connect connect_data; //LOG_DEVEL(LOG_LEVEL_DEBUG, "my_api_trans_data_in: extra_flags %d", trans->extra_flags); rv = 0; @@ -1224,40 +1209,44 @@ my_api_trans_data_in(struct trans *trans) s = trans_get_in_s(trans); if (trans->extra_flags == 0) { - in_uint32_le(s, bytes); - in_uint32_le(s, ver); - //LOG_DEVEL(LOG_LEVEL_DEBUG, "my_api_trans_data_in: bytes %d ver %d", bytes, ver); - if (ver != 0) - { - return 1; - } - trans->header_size = bytes; + trans->header_size = XRDPAPI_CONNECT_PDU_LEN; // Need more data trans->extra_flags = 1; } else if (trans->extra_flags == 1) { rv = 1; - in_uint32_le(s, channel_name_bytes); - //LOG_DEVEL(LOG_LEVEL_DEBUG, "my_api_trans_data_in: channel_name_bytes %d", channel_name_bytes); - if (channel_name_bytes > (sizeof(chan_name) - 1)) + + in_uint8a(s, &connect_data, sizeof(connect_data)); + connect_data.name[MAX_DVC_NAME_LEN] = '\0'; + //LOG_DEVEL(LOG_LEVEL_DEBUG, "my_api_trans_data_in: chan_name %s chan_flags 0x%8.8x", connect_data.name, connect_data.flags); + if (connect_data.version != XRDPAPI_CONNECT_PDU_VERSION) { return 1; } - in_uint8a(s, chan_name, channel_name_bytes); - chan_name[channel_name_bytes] = '\0'; + ad->chan_flags = connect_data.flags; - in_uint32_le(s, ad->chan_flags); - //LOG_DEVEL(LOG_LEVEL_DEBUG, "my_api_trans_data_in: chan_name %s chan_flags 0x%8.8x", chan_name, ad->chan_flags); - if (ad->chan_flags == 0) + if (connect_data.flags == 0 || connect_data.private_chan != 0) { - /* SVC (and private) */ - for (index = 0; index < g_num_chan_items; index++) + /* SVC (or private) */ + if (connect_data.private_chan >= CHAN_ID_XRDP_BASE && + connect_data.private_chan < CHAN_ID_XRDP_MAX) { - if (g_strcasecmp(g_chan_items[index].name, chan_name) == 0) + /* Valid private channel */ + ad->chan_id = connect_data.private_chan; + rv = 0; + } + else if (connect_data.private_chan == 0) + { + /* Check SVC names */ + for (index = 0; index < g_num_chan_items; index++) { - ad->chan_id = g_chan_items[index].id; - rv = 0; - break; + if (g_strcasecmp(g_chan_items[index].name, + connect_data.name) == 0) + { + ad->chan_id = g_chan_items[index].id; + rv = 0; + break; + } } } if (rv == 0) @@ -1268,7 +1257,7 @@ my_api_trans_data_in(struct trans *trans) { return 1; } - out_uint32_le(out_s, 0); + out_uint32_pe(out_s, 0); s_mark_end(out_s); if (trans_write_copy(trans) != 0) { @@ -1283,7 +1272,7 @@ my_api_trans_data_in(struct trans *trans) { return 1; } - out_uint32_le(out_s, 1); + out_uint32_pe(out_s, 1); s_mark_end(out_s); if (trans_write_copy(trans) != 0) { @@ -1299,7 +1288,7 @@ my_api_trans_data_in(struct trans *trans) procs.close_response = my_api_close_response; procs.data_first = my_api_data_first; procs.data = my_api_data; - rv = chansrv_drdynvc_open(chan_name, ad->chan_flags, + rv = chansrv_drdynvc_open(connect_data.name, ad->chan_flags, &procs, &(ad->chan_id)); //LOG_DEVEL(LOG_LEVEL_DEBUG, "my_api_trans_data_in: chansrv_drdynvc_open rv %d " // "chan_id %d", rv, ad->chan_id); diff --git a/xrdpapi/xrdpapi.c b/xrdpapi/xrdpapi.c index e0b8eba963..548a42b2ee 100644 --- a/xrdpapi/xrdpapi.c +++ b/xrdpapi/xrdpapi.c @@ -79,29 +79,6 @@ free_wts(struct wts_obj *wts) } } -/* - * Opens a handle to the server end of a specified virtual channel - this - * call is deprecated - use WTSVirtualChannelOpenEx() instead - * - * @param hServer - *must* be WTS_CURRENT_SERVER_HANDLE - * @param SessionId - current session ID; *must* be WTS_CURRENT_SESSION - * @param pVirtualName - virtual channel name when using SVC - * - name of endpoint listener when using DVC - * - * @return a valid pointer on success, NULL on error - ******************************************************************************/ -void * -WTSVirtualChannelOpen(void *hServer, unsigned int SessionId, - const char *pVirtualName) -{ - if (hServer != WTS_CURRENT_SERVER_HANDLE) - { - return 0; - } - - return WTSVirtualChannelOpenEx(SessionId, pVirtualName, 0); -} - /* * Opens a handle to the server end of a specified virtual channel * @@ -109,19 +86,29 @@ WTSVirtualChannelOpen(void *hServer, unsigned int SessionId, * @param pVirtualName - virtual channel name when using SVC * - name of endpoint listener when using DVC * @param flags - type of channel and channel priority if DVC + * @param private_chan - If != 0, this is a private channel defined + * in channel_defs.h * * @return a valid pointer on success, NULL on error ******************************************************************************/ -void * -WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName, - unsigned int flags) +static void * +VirtualChannelOpen(unsigned int SessionId, const char *pVirtualName, + unsigned int flags, + unsigned int private_chan) { struct wts_obj *wts; int bytes; unsigned long long1; struct sockaddr_un s; - char *connect_data; - int chan_name_bytes; + // Pad the connect data out to a larger size to allow for + // changes to struct xrdp_chan_connect + union + { + char pad[XRDPAPI_CONNECT_PDU_LEN]; + struct xrdp_chan_connect connect_data; + } cd = {0}; + + uint32_t connect_result; int lerrno; if (SessionId != WTS_CURRENT_SESSION) @@ -193,44 +180,12 @@ WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName, return NULL; } - chan_name_bytes = strlen(pVirtualName); - bytes = 4 + 4 + 4 + chan_name_bytes + 4; - - LOG_DEVEL(LOG_LEVEL_DEBUG, - "WTSVirtualChannelOpenEx: chan_name_bytes %d bytes %d pVirtualName %s", - chan_name_bytes, bytes, pVirtualName); - - connect_data = (char *) calloc(bytes, 1); - if (connect_data == NULL) - { - LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: calloc failed"); - free_wts(wts); - return NULL; - } - - connect_data[0] = (bytes >> 0) & 0xFF; - connect_data[1] = (bytes >> 8) & 0xFF; - connect_data[2] = (bytes >> 16) & 0xFF; - connect_data[3] = (bytes >> 24) & 0xFF; - - /* version here(4-7), just leave 0 */ - - connect_data[8] = (chan_name_bytes >> 0) & 0xFF; - connect_data[9] = (chan_name_bytes >> 8) & 0xFF; - connect_data[10] = (chan_name_bytes >> 16) & 0xFF; - connect_data[11] = (chan_name_bytes >> 24) & 0xFF; - - memcpy(connect_data + 12, pVirtualName, chan_name_bytes); - - connect_data[4 + 4 + 4 + chan_name_bytes + 0] = (flags >> 0) & 0xFF; - connect_data[4 + 4 + 4 + chan_name_bytes + 1] = (flags >> 8) & 0xFF; - connect_data[4 + 4 + 4 + chan_name_bytes + 2] = (flags >> 16) & 0xFF; - connect_data[4 + 4 + 4 + chan_name_bytes + 3] = (flags >> 24) & 0xFF; + cd.connect_data.version = XRDPAPI_CONNECT_PDU_VERSION; + cd.connect_data.private_chan = private_chan; + cd.connect_data.flags = flags; + strlcpy(cd.connect_data.name, pVirtualName, sizeof(cd.connect_data.name)); - LOG_DEVEL(LOG_LEVEL_DEBUG, - "WTSVirtualChannelOpenEx: calling mysend with %d bytes", bytes); - - if (mysend(wts->fd, connect_data, bytes) != bytes) + if (mysend(wts->fd, &cd, sizeof(cd)) != sizeof(cd)) { LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: mysend failed"); free_wts(wts); @@ -246,26 +201,63 @@ WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName, } /* get response */ - if (myrecv(wts->fd, connect_data, 4) != 4) + if (myrecv(wts->fd, &connect_result, sizeof(connect_result)) != + sizeof(connect_result)) { LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: myrecv failed"); free_wts(wts); return NULL; } - if ((connect_data[0] != 0) || (connect_data[1] != 0) || - (connect_data[2] != 0) || (connect_data[3] != 0)) + if (connect_result != 0) { LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: connect_data not ok"); free_wts(wts); return NULL; } - free(connect_data); - return wts; } +/* + * Opens a handle to the server end of a specified virtual channel - this + * call is deprecated - use WTSVirtualChannelOpenEx() instead + * + * @param hServer - *must* be WTS_CURRENT_SERVER_HANDLE + * @param SessionId - current session ID; *must* be WTS_CURRENT_SESSION + * @param pVirtualName - virtual channel name when using SVC + * - name of endpoint listener when using DVC + * + * @return a valid pointer on success, NULL on error + ******************************************************************************/ +void * +WTSVirtualChannelOpen(void *hServer, unsigned int SessionId, + const char *pVirtualName) +{ + if (hServer != WTS_CURRENT_SERVER_HANDLE) + { + return 0; + } + return VirtualChannelOpen(SessionId, pVirtualName, 0, 0 ); +} + +/* + * Opens a handle to the server end of a specified virtual channel + * + * @param SessionId - current session ID; *must* be WTS_CURRENT_SESSION + * @param pVirtualName - virtual channel name when using SVC + * - name of endpoint listener when using DVC + * @param flags - type of channel and channel priority if DVC + * + * @return a valid pointer on success, NULL on error + ******************************************************************************/ +void * +WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName, + unsigned int flags) +{ + return VirtualChannelOpen(SessionId, pVirtualName, flags, 0 ); +} + /* * Prevent receiving SIGPIPE on disconnect using either MSG_NOSIGNAL (Linux) * or SO_NOSIGPIPE (Mac OS X) @@ -571,11 +563,11 @@ int WTSRegisterSessionNotificationEx(char *hServer, if (wts_current_server.info_obj == NULL) { // Open a private channel for session info messages - // The flags arg is ignored for xrdp private channels + // The name and flags args are ignored for xrdp private channels wts_current_server.info_obj = (struct wts_obj *) - WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, - CHAN_NAME_XRDP_SESSION_INFO, 0); + VirtualChannelOpen(WTS_CURRENT_SESSION, + "", 0, CHAN_ID_XRDP_SESSION_INFO); } if (wts_current_server.info_obj == NULL) { From a2c68386d5e705a39df9c2bf9d29a00e16334425 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 10 Nov 2025 16:01:19 +0000 Subject: [PATCH 6/8] xrdpapi: Return error from notification calls Allos the user to determine whether a call to WTSRegisterSessionNotificationEx() can be retried or not. --- xrdpapi/connectmon.c | 5 +++-- xrdpapi/xrdpapi.c | 49 ++++++++++++++++++++++++++++++++++++++------ xrdpapi/xrdpapi.h | 24 ++++++++++++++++++++-- 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/xrdpapi/connectmon.c b/xrdpapi/connectmon.c index 190c835bef..dc99ad0e58 100644 --- a/xrdpapi/connectmon.c +++ b/xrdpapi/connectmon.c @@ -86,7 +86,7 @@ main(int argc, char **argv) } printf("Connection monitor : exiting after %d state changes\n", changes); - if (!WTSRegisterSessionNotificationEx(WTS_CURRENT_SERVER_HANDLE, &fd, 0)) + if (!WTSRegisterSessionNotificationEx(WTS_CURRENT_SERVER_HANDLE, &fd, 0, NULL)) { result = 1; // Error occurred (should be logged) } @@ -131,7 +131,8 @@ main(int argc, char **argv) } } - (void)WTSUnRegisterSessionNotificationEx(WTS_CURRENT_SERVER_HANDLE, fd); + (void)WTSUnRegisterSessionNotificationEx(WTS_CURRENT_SERVER_HANDLE, + fd, NULL); } if (lc != NULL) diff --git a/xrdpapi/xrdpapi.c b/xrdpapi/xrdpapi.c index 548a42b2ee..34da05af0e 100644 --- a/xrdpapi/xrdpapi.c +++ b/xrdpapi/xrdpapi.c @@ -88,13 +88,16 @@ free_wts(struct wts_obj *wts) * @param flags - type of channel and channel priority if DVC * @param private_chan - If != 0, this is a private channel defined * in channel_defs.h + * @param[out] errcode - Indication for the user of a possible + * error. Cannot be defaulted. Is only set on error. * * @return a valid pointer on success, NULL on error ******************************************************************************/ static void * VirtualChannelOpen(unsigned int SessionId, const char *pVirtualName, unsigned int flags, - unsigned int private_chan) + unsigned int private_chan, + enum wts_errcode *errcode) { struct wts_obj *wts; int bytes; @@ -114,6 +117,7 @@ VirtualChannelOpen(unsigned int SessionId, const char *pVirtualName, if (SessionId != WTS_CURRENT_SESSION) { LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: bad SessionId"); + *errcode = WTS_E_BAD_SESSION_ID; return 0; } @@ -121,6 +125,7 @@ VirtualChannelOpen(unsigned int SessionId, const char *pVirtualName, if (wts == NULL) { LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: calloc failed"); + *errcode = WTS_E_RESOURCE_ERROR; return 0; } wts->fd = -1; @@ -128,6 +133,7 @@ VirtualChannelOpen(unsigned int SessionId, const char *pVirtualName, if (wts->display_num < 0) { LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: fatal error; invalid DISPLAY"); + *errcode = WTS_E_RESOURCE_ERROR; free_wts(wts); return NULL; } @@ -136,6 +142,7 @@ VirtualChannelOpen(unsigned int SessionId, const char *pVirtualName, if ((wts->fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: socket failed"); + *errcode = WTS_E_RESOURCE_ERROR; free_wts(wts); return NULL; } @@ -167,6 +174,7 @@ VirtualChannelOpen(unsigned int SessionId, const char *pVirtualName, else { LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: connect failed"); + *errcode = WTS_E_CHANSRV_NOT_UP; free_wts(wts); return NULL; } @@ -176,6 +184,7 @@ VirtualChannelOpen(unsigned int SessionId, const char *pVirtualName, if (!can_send(wts->fd, 500, 1)) { LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: can_send failed"); + *errcode = WTS_E_CHANSRV_NOT_UP; free_wts(wts); return NULL; } @@ -188,6 +197,7 @@ VirtualChannelOpen(unsigned int SessionId, const char *pVirtualName, if (mysend(wts->fd, &cd, sizeof(cd)) != sizeof(cd)) { LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: mysend failed"); + *errcode = WTS_E_RESOURCE_ERROR; free_wts(wts); return NULL; } @@ -196,6 +206,7 @@ VirtualChannelOpen(unsigned int SessionId, const char *pVirtualName, if (!can_recv(wts->fd, 500, 1)) { LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: can_recv failed"); + *errcode = WTS_E_RESOURCE_ERROR; free_wts(wts); return NULL; } @@ -205,6 +216,7 @@ VirtualChannelOpen(unsigned int SessionId, const char *pVirtualName, sizeof(connect_result)) { LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: myrecv failed"); + *errcode = WTS_E_RESOURCE_ERROR; free_wts(wts); return NULL; } @@ -212,6 +224,7 @@ VirtualChannelOpen(unsigned int SessionId, const char *pVirtualName, if (connect_result != 0) { LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: connect_data not ok"); + *errcode = WTS_E_RESOURCE_ERROR; free_wts(wts); return NULL; } @@ -234,11 +247,13 @@ void * WTSVirtualChannelOpen(void *hServer, unsigned int SessionId, const char *pVirtualName) { + enum wts_errcode errcode_dummy = WTS_E_NO_ERROR; + if (hServer != WTS_CURRENT_SERVER_HANDLE) { return 0; } - return VirtualChannelOpen(SessionId, pVirtualName, 0, 0 ); + return VirtualChannelOpen(SessionId, pVirtualName, 0, 0, &errcode_dummy); } /* @@ -255,7 +270,9 @@ void * WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName, unsigned int flags) { - return VirtualChannelOpen(SessionId, pVirtualName, flags, 0 ); + enum wts_errcode errcode_dummy = WTS_E_NO_ERROR; + return VirtualChannelOpen(SessionId, pVirtualName, + flags, 0, &errcode_dummy); } /* @@ -551,12 +568,21 @@ can_recv(int sck, int millis, int restart) /*****************************************************************************/ int WTSRegisterSessionNotificationEx(char *hServer, int *fd_ptr, - int dwFlags) + int dwFlags, + enum wts_errcode *errcode) { (void)dwFlags; // Unused parameter + enum wts_errcode errcode_dummy; + if (errcode == NULL) + { + errcode = &errcode_dummy; + } + *errcode = WTS_E_NO_ERROR; + if (hServer != WTS_CURRENT_SERVER_HANDLE) { LOG(LOG_LEVEL_ERROR, "WTSRegisterSessionNotificationEx: bad hServer"); + *errcode = WTS_E_BAD_SERVER; return 0; } @@ -567,7 +593,9 @@ int WTSRegisterSessionNotificationEx(char *hServer, wts_current_server.info_obj = (struct wts_obj *) VirtualChannelOpen(WTS_CURRENT_SESSION, - "", 0, CHAN_ID_XRDP_SESSION_INFO); + "", 0, + CHAN_ID_XRDP_SESSION_INFO, + errcode); } if (wts_current_server.info_obj == NULL) { @@ -580,12 +608,21 @@ int WTSRegisterSessionNotificationEx(char *hServer, /*****************************************************************************/ int WTSUnRegisterSessionNotificationEx(char *hServer, - int fd) + int fd, + enum wts_errcode *errcode) { (void)fd; // Unused parameter + enum wts_errcode errcode_dummy; + if (errcode == NULL) + { + errcode = &errcode_dummy; + } + *errcode = WTS_E_NO_ERROR; + if (hServer != WTS_CURRENT_SERVER_HANDLE) { LOG(LOG_LEVEL_ERROR, "WTSUnRegisterSessionNotificationEx: bad hServer"); + *errcode = WTS_E_BAD_SERVER; return 0; } diff --git a/xrdpapi/xrdpapi.h b/xrdpapi/xrdpapi.h index 1486b43849..f1ca11685b 100644 --- a/xrdpapi/xrdpapi.h +++ b/xrdpapi/xrdpapi.h @@ -60,6 +60,21 @@ typedef enum _WTS_VIRTUAL_CLASS } WTS_VIRTUAL_CLASS; +// Enumerated type for an error code from some calls. This is not +// compatible with the Windows API. +enum wts_errcode +{ + WTS_E_NO_ERROR = 0, + // Retryable errors + WTS_E_CHANSRV_NOT_UP, + // Fatal errors + WTS_E_BAD_SERVER = 32, + WTS_E_BAD_SESSION_ID, + WTS_E_RESOURCE_ERROR, +}; + +#define WTS_ERRCODE_FATAL(errcode) ((int)(errcode) >= (int)WTS_E_BAD_SERVER) + // Win32 basic types // See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types typedef uint32_t UINT; @@ -107,6 +122,7 @@ void WTSFreeMemory(void *pMemory); * @param hServer set to WTS_CURRENT_SERVER_HANDLE * @param[out] fd_ptr File descriptor to check for notification messages * @param dwFlags ignored + * @param[out] errcode Status of operation if false returned. Can be NULL. * @return true for success * * The fd_ptr replaces the hWnd parameter in the Win32 call. @@ -120,7 +136,8 @@ void WTSFreeMemory(void *pMemory); */ int WTSRegisterSessionNotificationEx(char *hServer, int *fd_ptr, - int dwFlags); + int dwFlags, + enum wts_errcode *errcode); /** * This function is similar to, but not the same as the Win32 @@ -131,9 +148,12 @@ int WTSRegisterSessionNotificationEx(char *hServer, * * @param hServer set to WTS_CURRENT_SERVER_HANDLE * @param fd File descriptor from WTSRegisterSessionNotificationEx + * @param[out] errcode Status of operation if false returned. Can be NULL. + * @return true for success */ int WTSUnRegisterSessionNotificationEx(char *hServer, - int fd); + int fd, + enum wts_errcode *errcode); /** Replaces Win32 GetMessage() / DispatchMessage() From 396bfd50548be6822f726e9be8ebfea82ba535b2 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Mon, 10 Nov 2025 19:48:18 +0000 Subject: [PATCH 7/8] xrdpapi: Implement WTSQuerySessionInformationA Very limited function to check the connection status of a client --- common/channel_defs.h | 13 ++- sesman/chansrv/chansrv.c | 196 ++++++++++++++++++++++----------------- xrdpapi/connectmon.c | 35 +++++++ xrdpapi/xrdpapi.c | 113 ++++++++++++++++++++-- xrdpapi/xrdpapi.h | 54 ++++++++++- 5 files changed, 312 insertions(+), 99 deletions(-) diff --git a/common/channel_defs.h b/common/channel_defs.h index c4d03738e8..f550ef7e08 100644 --- a/common/channel_defs.h +++ b/common/channel_defs.h @@ -31,7 +31,7 @@ enum { CHAN_ID_XRDP_BASE = (('x' << 24) | ('r' << 16) | ('d' << 8) | 'p'), - CHAN_ID_XRDP_SESSION_INFO, + CHAN_ID_XRDP_SESSION_INFO = CHAN_ID_XRDP_BASE, // Add further IDs here CHAN_ID_XRDP_MAX }; @@ -60,7 +60,16 @@ struct xrdp_chan_connect char name[MAX_DVC_NAME_LEN + 1]; // null-terminated }; -struct xrdp_chan_session_info +// Events received on the CHAN_ID_XRDP_SESSION_INFO channel + +// Status event immediately after a channel comes up +struct xrdp_chan_session_status_event +{ + uint32_t is_connected; // Is the session connected? +}; + +// Info events received afterwards +struct xrdp_chan_session_info_event { uint32_t msgno; intptr_t wparam; diff --git a/sesman/chansrv/chansrv.c b/sesman/chansrv/chansrv.c index 87a0f810ac..c1e108a344 100644 --- a/sesman/chansrv/chansrv.c +++ b/sesman/chansrv/chansrv.c @@ -349,7 +349,7 @@ send_rail_drawing_orders(char *data, int size) static void xrdpapi_send_session_event(unsigned int msgno, intptr_t wparam, intptr_t lparam) { - struct xrdp_chan_session_info msg_data = + struct xrdp_chan_session_info_event msg_data = { .msgno = msgno, .wparam = wparam, @@ -1188,114 +1188,138 @@ my_api_data(int chan_id, char *data, int bytes) } /* - * called when VirtualChannelOpen() is invoked in xrdpapi.c + * Handles an incoming channel connect request on an xrdpapi channel * ******************************************************************************/ static int -my_api_trans_data_in(struct trans *trans) +handle_xrdpapi_connection_request(struct trans *trans) { - struct stream *s; - struct stream *out_s; - struct xrdp_api_data *ad; - int index; - int rv; - int bytes; - struct chansrv_drdynvc_procs procs; + struct xrdp_api_data *ad = (struct xrdp_api_data *)(trans->callback_data); + struct stream *s = trans_get_in_s(trans); + int rv = 1; // Assume failure struct xrdp_chan_connect connect_data; - //LOG_DEVEL(LOG_LEVEL_DEBUG, "my_api_trans_data_in: extra_flags %d", trans->extra_flags); - rv = 0; - ad = (struct xrdp_api_data *) (trans->callback_data); - s = trans_get_in_s(trans); - if (trans->extra_flags == 0) + in_uint8a(s, &connect_data, sizeof(connect_data)); + connect_data.name[MAX_DVC_NAME_LEN] = '\0'; + //LOG_DEVEL(LOG_LEVEL_DEBUG, "my_api_trans_data_in: chan_name %s chan_flags 0x%8.8x", connect_data.name, connect_data.flags); + if (connect_data.version != XRDPAPI_CONNECT_PDU_VERSION) { - trans->header_size = XRDPAPI_CONNECT_PDU_LEN; // Need more data - trans->extra_flags = 1; + return 1; } - else if (trans->extra_flags == 1) + ad->chan_flags = connect_data.flags; + + if (connect_data.flags == 0 || connect_data.private_chan != 0) { - rv = 1; + /* SVC (or private) */ + if (connect_data.private_chan >= CHAN_ID_XRDP_BASE && + connect_data.private_chan < CHAN_ID_XRDP_MAX) + { + /* Valid private channel */ + ad->chan_id = connect_data.private_chan; + ad->chan_flags = 0; + rv = 0; + } + else if (connect_data.private_chan == 0) + { + /* Check SVC names */ + int index; + for (index = 0; index < g_num_chan_items; index++) + { + if (g_strcasecmp(g_chan_items[index].name, + connect_data.name) == 0) + { + ad->chan_id = g_chan_items[index].id; + rv = 0; + break; + } + } + } - in_uint8a(s, &connect_data, sizeof(connect_data)); - connect_data.name[MAX_DVC_NAME_LEN] = '\0'; - //LOG_DEVEL(LOG_LEVEL_DEBUG, "my_api_trans_data_in: chan_name %s chan_flags 0x%8.8x", connect_data.name, connect_data.flags); - if (connect_data.version != XRDPAPI_CONNECT_PDU_VERSION) + /* Send the status back to the caller */ + struct stream *out_s = trans_get_out_s(trans, 8192); + if (out_s == NULL) + { + return 1; + } + out_uint32_pe(out_s, rv); + s_mark_end(out_s); + if (trans_write_copy(trans) != 0) { return 1; } - ad->chan_flags = connect_data.flags; - if (connect_data.flags == 0 || connect_data.private_chan != 0) + /* Channel-specific processing */ + if (rv == 0 && connect_data.private_chan != 0) { - /* SVC (or private) */ - if (connect_data.private_chan >= CHAN_ID_XRDP_BASE && - connect_data.private_chan < CHAN_ID_XRDP_MAX) - { - /* Valid private channel */ - ad->chan_id = connect_data.private_chan; - rv = 0; - } - else if (connect_data.private_chan == 0) + switch (connect_data.private_chan) { - /* Check SVC names */ - for (index = 0; index < g_num_chan_items; index++) + case CHAN_ID_XRDP_SESSION_INFO: { - if (g_strcasecmp(g_chan_items[index].name, - connect_data.name) == 0) + struct xrdp_chan_session_status_event status = { - ad->chan_id = g_chan_items[index].id; - rv = 0; - break; + .is_connected = (g_con_trans != NULL) + }; + init_stream(out_s, sizeof(status)); + out_uint8a(out_s, &status, sizeof(status)); + s_mark_end(out_s); + if (trans_write_copy(trans) != 0) + { + rv = 1; } + break; } + default: + break; } - if (rv == 0) - { - /* open ok */ - out_s = trans_get_out_s(trans, 8192); - if (out_s == NULL) - { - return 1; - } - out_uint32_pe(out_s, 0); - s_mark_end(out_s); - if (trans_write_copy(trans) != 0) - { - return 1; - } - } - else - { - /* open failed */ - out_s = trans_get_out_s(trans, 8192); - if (out_s == NULL) - { - return 1; - } - out_uint32_pe(out_s, 1); - s_mark_end(out_s); - if (trans_write_copy(trans) != 0) - { - return 1; - } - } - } - else - { - /* DVS */ - g_memset(&procs, 0, sizeof(procs)); - procs.open_response = my_api_open_response; - procs.close_response = my_api_close_response; - procs.data_first = my_api_data_first; - procs.data = my_api_data; - rv = chansrv_drdynvc_open(connect_data.name, ad->chan_flags, - &procs, &(ad->chan_id)); - //LOG_DEVEL(LOG_LEVEL_DEBUG, "my_api_trans_data_in: chansrv_drdynvc_open rv %d " - // "chan_id %d", rv, ad->chan_id); - g_drdynvcs[ad->chan_id].xrdp_api_trans = trans; } + } + else + { + /* DVS */ + struct chansrv_drdynvc_procs procs; + g_memset(&procs, 0, sizeof(procs)); + procs.open_response = my_api_open_response; + procs.close_response = my_api_close_response; + procs.data_first = my_api_data_first; + procs.data = my_api_data; + rv = chansrv_drdynvc_open(connect_data.name, ad->chan_flags, + &procs, &(ad->chan_id)); + //LOG_DEVEL(LOG_LEVEL_DEBUG, "my_api_trans_data_in: chansrv_drdynvc_open rv %d " + // "chan_id %d", rv, ad->chan_id); + g_drdynvcs[ad->chan_id].xrdp_api_trans = trans; + } + + return rv; +} + +/* + * called when VirtualChannelOpen() is invoked in xrdpapi.c, and later, + * when the channel is written to + * + ******************************************************************************/ +static int +my_api_trans_data_in(struct trans *trans) +{ + struct stream *s; + struct xrdp_api_data *ad; + int rv; + int bytes; + + //LOG_DEVEL(LOG_LEVEL_DEBUG, "my_api_trans_data_in: extra_flags %d", trans->extra_flags); + rv = 0; + ad = (struct xrdp_api_data *) (trans->callback_data); + s = trans_get_in_s(trans); + if (trans->extra_flags == 0) + { + trans->header_size = XRDPAPI_CONNECT_PDU_LEN; // Need more data + trans->extra_flags = 1; + } + else if (trans->extra_flags == 1) + { + // We've got a complete connection request on the channel */ + handle_xrdpapi_connection_request(trans); init_stream(s, 0); - trans->extra_flags = 2; + trans->extra_flags = 2; // Mark the connection phase as complete trans->header_size = 0; } else diff --git a/xrdpapi/connectmon.c b/xrdpapi/connectmon.c index dc99ad0e58..38b5dfc082 100644 --- a/xrdpapi/connectmon.c +++ b/xrdpapi/connectmon.c @@ -39,6 +39,36 @@ #include #include +/******************************************************************************/ +static void +print_current_connection_state(void) +{ + WTS_CONNECTSTATE_CLASS connect_state; + char buff[64]; + const char *statestr = "unavailable"; + if (WTSQuerySessionInformationA(WTS_CURRENT_SERVER_HANDLE, + WTS_CURRENT_SESSION, + WTSConnectState, + &connect_state, NULL, NULL)) + { + if (connect_state == WTSConnected) + { + statestr = "connected"; + } + else if (connect_state == WTSDisconnected) + { + statestr = "disconnected"; + } + else + { + snprintf(buff, sizeof(buff), + "unrecognised val %d", (int)connect_state); + statestr = buff; + } + } + printf("Current connect state is %s\n", statestr); +} + /******************************************************************************/ static LRESULT callback (void *cbdata, UINT msg, WPARAM wParam, LPARAM lParam) @@ -51,11 +81,13 @@ callback (void *cbdata, UINT msg, WPARAM wParam, LPARAM lParam) case WTS_REMOTE_CONNECT: ++*countptr; printf("State change %d : Connect to session\n", *countptr); + print_current_connection_state(); break; case WTS_REMOTE_DISCONNECT: ++*countptr; printf("State change %d : Disconnect from session\n", *countptr); + print_current_connection_state(); break; default: @@ -86,6 +118,9 @@ main(int argc, char **argv) } printf("Connection monitor : exiting after %d state changes\n", changes); + print_current_connection_state(); + + if (!WTSRegisterSessionNotificationEx(WTS_CURRENT_SERVER_HANDLE, &fd, 0, NULL)) { result = 1; // Error occurred (should be logged) diff --git a/xrdpapi/xrdpapi.c b/xrdpapi/xrdpapi.c index 34da05af0e..2ed3b9c8f1 100644 --- a/xrdpapi/xrdpapi.c +++ b/xrdpapi/xrdpapi.c @@ -52,6 +52,7 @@ struct wts_obj struct wts_server { struct wts_obj *info_obj; // Object to get session notifications + int client_is_connected; // Do we have a client? }; static struct wts_server wts_current_server; @@ -566,7 +567,68 @@ can_recv(int sck, int millis, int restart) } /*****************************************************************************/ -int WTSRegisterSessionNotificationEx(char *hServer, +int WTSQuerySessionInformationA(void *hServer, + unsigned int SessionId, + WTS_INFO_CLASS WTSInfoClass, + void *ppBuffer, + DWORD *pBytesReturned, + enum wts_errcode *errcode) +{ + int rv = 0; + enum wts_errcode errcode_dummy; + if (errcode == NULL) + { + errcode = &errcode_dummy; + } + *errcode = WTS_E_NO_ERROR; + + if (hServer != WTS_CURRENT_SERVER_HANDLE) + { + LOG(LOG_LEVEL_ERROR, "WTSQuerySessionInformationA: bad hServer"); + *errcode = WTS_E_BAD_SERVER; + } + else if (SessionId != WTS_CURRENT_SESSION) + { + LOG(LOG_LEVEL_ERROR, "WTSQuerySessionInformationA: bad SessionId"); + *errcode = WTS_E_BAD_SESSION_ID; + } + else if (WTSInfoClass != WTSConnectState) + { + LOG(LOG_LEVEL_ERROR, + "WTSQuerySessionInformationA: unsupported WTSInfoClass"); + *errcode = WTS_E_BAD_INFO_CLASS; + } + else + { + rv = 1; // Assume success + if (wts_current_server.info_obj == NULL) + { + // We don't have a current connection for server state events. + // Set one up to update our cached values, then tear it down again. + int fd; + + rv = WTSRegisterSessionNotificationEx(hServer, &fd, 0, errcode) && + WTSUnRegisterSessionNotificationEx(hServer, fd, errcode); + } + + if (rv == 1) + { + *(WTS_CONNECTSTATE_CLASS *)ppBuffer = + (wts_current_server.client_is_connected) + ? WTSConnected + : WTSDisconnected; + if (pBytesReturned != NULL) + { + *pBytesReturned = sizeof(WTS_CONNECTSTATE_CLASS); + } + } + } + + return rv; +} + +/*****************************************************************************/ +int WTSRegisterSessionNotificationEx(void *hServer, int *fd_ptr, int dwFlags, enum wts_errcode *errcode) @@ -590,12 +652,38 @@ int WTSRegisterSessionNotificationEx(char *hServer, { // Open a private channel for session info messages // The name and flags args are ignored for xrdp private channels - wts_current_server.info_obj = - (struct wts_obj *) - VirtualChannelOpen(WTS_CURRENT_SESSION, - "", 0, - CHAN_ID_XRDP_SESSION_INFO, - errcode); + struct wts_obj *wts = (struct wts_obj *) + VirtualChannelOpen(WTS_CURRENT_SESSION, + "", 0, + CHAN_ID_XRDP_SESSION_INFO, + errcode); + if (wts != NULL) + { + // Server will pass its current state now + struct xrdp_chan_session_status_event server_status; + if (!can_recv(wts->fd, 1000, 1)) + { + LOG(LOG_LEVEL_ERROR, + "WTSRegisterSessionNotificationEx: can_recv failed"); + *errcode = WTS_E_RESOURCE_ERROR; + free_wts(wts); + } + /* get server_status */ + else if (myrecv(wts->fd, &server_status, sizeof(server_status)) != + sizeof(server_status)) + { + LOG(LOG_LEVEL_ERROR, + "WTSRegisterSessionNotificationEx: myrecv failed"); + *errcode = WTS_E_RESOURCE_ERROR; + free_wts(wts); + } + else + { + wts_current_server.info_obj = wts; + wts_current_server.client_is_connected = + server_status.is_connected; + } + } } if (wts_current_server.info_obj == NULL) { @@ -607,7 +695,7 @@ int WTSRegisterSessionNotificationEx(char *hServer, } /*****************************************************************************/ -int WTSUnRegisterSessionNotificationEx(char *hServer, +int WTSUnRegisterSessionNotificationEx(void *hServer, int fd, enum wts_errcode *errcode) { @@ -636,7 +724,7 @@ int WTSGetDispatchMessage(void *cbdata, WNDPROC wndproc, LRESULT *lResult) { LRESULT result = 0; - struct xrdp_chan_session_info msg_data; + struct xrdp_chan_session_info_event msg_data; /* get response */ if (wts_current_server.info_obj == NULL) @@ -654,6 +742,13 @@ WTSGetDispatchMessage(void *cbdata, WNDPROC wndproc, LRESULT *lResult) } else { + if (msg_data.msgno == WM_WTSSESSION_CHANGE) + { + // Update our cached copy of the client connected status before + // invoking the callback. + wts_current_server.client_is_connected = + (msg_data.wparam == WTS_REMOTE_CONNECT); + } *lResult = wndproc(cbdata, msg_data.msgno, msg_data.wparam, msg_data.lparam); result = 1; diff --git a/xrdpapi/xrdpapi.h b/xrdpapi/xrdpapi.h index f1ca11685b..55593aadbd 100644 --- a/xrdpapi/xrdpapi.h +++ b/xrdpapi/xrdpapi.h @@ -52,6 +52,32 @@ extern "C" { #define WTS_REMOTE_CONNECT 0x3 #define WTS_REMOTE_DISCONNECT 0x4 +typedef enum _WTS_INFO_CLASS +{ + //WTSInitialProgram, // Not yet implemented + //WTSApplicationName, // Not yet implemented + //WTSWorkingDirectory, // Not yet implemented + //WTSOEMId, // Not yet implemented + //WTSSessionId, // Not yet implemented + //WTSUserName, // Not yet implemented + //WTSWinStationName, // Not yet implemented + //WTSDomainName, // Not yet implemented + WTSConnectState = 8 +} WTS_INFO_CLASS; + +typedef enum _WTS_CONNECTSTATE_CLASS +{ + // WTSActive, + WTSConnected = 1, + // WTSConnectQuery, + // WTSShadow, + WTSDisconnected = 4, + // WTSIdle, + // WTSListen, + // WTSReset, + // WTSDown, + // WTSInit +} WTS_CONNECTSTATE_CLASS; typedef enum _WTS_VIRTUAL_CLASS { @@ -71,12 +97,14 @@ enum wts_errcode WTS_E_BAD_SERVER = 32, WTS_E_BAD_SESSION_ID, WTS_E_RESOURCE_ERROR, + WTS_E_BAD_INFO_CLASS }; #define WTS_ERRCODE_FATAL(errcode) ((int)(errcode) >= (int)WTS_E_BAD_SERVER) // Win32 basic types // See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types +typedef uint32_t DWORD; typedef uint32_t UINT; typedef intptr_t UINT_PTR; typedef intptr_t LONG_PTR; @@ -112,6 +140,28 @@ int WTSVirtualChannelQuery(void *hChannelHandle, WTS_VIRTUAL_CLASS WtsVirtualCla void WTSFreeMemory(void *pMemory); +/** + * This function is similar to, but not the same as the Win32 + * function of the same name. + * + * The purpose of it is to allow an application to find out + * (rather limited) information about the session + * + * @param hServer set to WTS_CURRENT_SERVER_HANDLE + * @param SessionId current session ID; *must* be WTS_CURRENT_SESSION + * @param WTSInfoClass parameter to query + * @param ppBuffer pointer for result + * @param pBytesReturned size of result + * @param[out] errcode Status of operation if false returned. Can be NULL. + * @return true for success + */ +int WTSQuerySessionInformationA(void *hServer, + unsigned int SessionId, + WTS_INFO_CLASS WTSInfoClass, + void *ppBuffer, + DWORD *pBytesReturned, + enum wts_errcode *errcode); + /** * This function is similar to, but not the same as the Win32 * function of the same name. @@ -134,7 +184,7 @@ void WTSFreeMemory(void *pMemory); * * The caller must do nothing with the returned file descriptor except poll it. */ -int WTSRegisterSessionNotificationEx(char *hServer, +int WTSRegisterSessionNotificationEx(void *hServer, int *fd_ptr, int dwFlags, enum wts_errcode *errcode); @@ -151,7 +201,7 @@ int WTSRegisterSessionNotificationEx(char *hServer, * @param[out] errcode Status of operation if false returned. Can be NULL. * @return true for success */ -int WTSUnRegisterSessionNotificationEx(char *hServer, +int WTSUnRegisterSessionNotificationEx(void *hServer, int fd, enum wts_errcode *errcode); From cd2df87a994ec879158bf0a1d184c85e9aaa7140 Mon Sep 17 00:00:00 2001 From: matt335672 <30179339+matt335672@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:42:21 +0000 Subject: [PATCH 8/8] xrdpapi: Simplify interface from chansrv to xrdpapi Windows definitions are removed from the chansrv, as they are not needed here. --- common/channel_defs.h | 22 +++------- sesman/chansrv/chansrv.c | 85 +++++++++++++++++++------------------ xrdpapi/xrdpapi.c | 90 +++++++++++++++++++++++++++++++--------- 3 files changed, 122 insertions(+), 75 deletions(-) diff --git a/common/channel_defs.h b/common/channel_defs.h index f550ef7e08..f3df89193c 100644 --- a/common/channel_defs.h +++ b/common/channel_defs.h @@ -22,7 +22,7 @@ #if !defined(CHANNEL_DEFS_H) #define CHANNEL_DEFS_H -#include +#include /** * Channel IDs assigned to xrdp private channels. These are chosen to avoid conflicts with @@ -36,11 +36,6 @@ enum CHAN_ID_XRDP_MAX }; -// Defines from xrdpapi.h (exact copies) -#define WM_WTSSESSION_CHANGE 0x02B1 -#define WTS_REMOTE_CONNECT 0x3 -#define WTS_REMOTE_DISCONNECT 0x4 - // Max length of a DVC name. This is taken from the specification of // WTSVirtualChannelOpenEx() which limits the length of a virtual channel // to Windows MAX_PATH @@ -62,18 +57,13 @@ struct xrdp_chan_connect // Events received on the CHAN_ID_XRDP_SESSION_INFO channel -// Status event immediately after a channel comes up -struct xrdp_chan_session_status_event +// Update on all the session state data held by chansrv +// +// If more information is needed to be exported by xrdpapi, it can be +// added to this event +struct xrdp_chan_session_state { uint32_t is_connected; // Is the session connected? }; -// Info events received afterwards -struct xrdp_chan_session_info_event -{ - uint32_t msgno; - intptr_t wparam; - intptr_t lparam; -}; - #endif /* CHANNEL_DEFS_H */ diff --git a/sesman/chansrv/chansrv.c b/sesman/chansrv/chansrv.c index c1e108a344..f611457179 100644 --- a/sesman/chansrv/chansrv.c +++ b/sesman/chansrv/chansrv.c @@ -112,6 +112,12 @@ struct xrdp_api_data int chan_id; }; +// Session state passed to xrdpapi +// Whenever a single member of this struct changes, the whole +// block is sent to xrdpapi. This simplifies the interface, but +// complicates xrdpapi somewhat +static struct xrdp_chan_session_state g_session_state; + struct timeout_obj { tui32 mstime; @@ -344,22 +350,31 @@ send_rail_drawing_orders(char *data, int size) /*****************************************************************************/ /** - * Sends a session-related API event to all listeners (if any) + * Sends a session API state event to one listener */ -static void -xrdpapi_send_session_event(unsigned int msgno, intptr_t wparam, intptr_t lparam) +static int +xrdpapi_send_session_state_event_single(struct trans *t) { - struct xrdp_chan_session_info_event msg_data = - { - .msgno = msgno, - .wparam = wparam, - .lparam = lparam - }; + struct stream *s = t->out_s; + init_stream(s, (int)sizeof(g_session_state)); + out_uint8a(s, &g_session_state, sizeof(g_session_state)); + s_mark_end(s); + return trans_write_copy(t); +} +/*****************************************************************************/ +/** + * Sends a session API state event to all listeners (if any) + * + * This should be called after updating g_session_state. The xrdpapi + * module will generate any appropriate 'windows' events from the message + */ +static void +xrdpapi_send_session_state_event_all(void) +{ int index; struct trans *ltran; struct xrdp_api_data *api_data; - struct stream *ls; for (index = 0; index < g_api_con_trans_list->count; index++) { @@ -371,28 +386,13 @@ xrdpapi_send_session_event(unsigned int msgno, intptr_t wparam, intptr_t lparam) { if (api_data->chan_id == CHAN_ID_XRDP_SESSION_INFO) { - ls = ltran->out_s; - init_stream(ls, (int)sizeof(msg_data)); - out_uint8a(ls, &msg_data, sizeof(msg_data)); - s_mark_end(ls); - (void)trans_write_copy(ltran); + (void)xrdpapi_send_session_state_event_single(ltran); } } } } } -/*****************************************************************************/ -static void -xrdpapi_send_session_connect_event(int connect_made) -{ - intptr_t wparam = - (connect_made) ? WTS_REMOTE_CONNECT : WTS_REMOTE_DISCONNECT; - intptr_t lparam = 0; - - xrdpapi_send_session_event(WM_WTSSESSION_CHANGE, wparam, lparam); -} - /*****************************************************************************/ /* returns error */ static int @@ -481,7 +481,6 @@ process_message_channel_setup(struct stream *s) } audin_init(); - xrdpapi_send_session_connect_event(1); return rv; } @@ -1255,17 +1254,7 @@ handle_xrdpapi_connection_request(struct trans *trans) { case CHAN_ID_XRDP_SESSION_INFO: { - struct xrdp_chan_session_status_event status = - { - .is_connected = (g_con_trans != NULL) - }; - init_stream(out_s, sizeof(status)); - out_uint8a(out_s, &status, sizeof(status)); - s_mark_end(out_s); - if (trans_write_copy(trans) != 0) - { - rv = 1; - } + rv = xrdpapi_send_session_state_event_single(trans); break; } default: @@ -1382,6 +1371,12 @@ my_trans_conn_in(struct trans *trans, struct trans *new_trans) g_con_trans = new_trans; g_con_trans->trans_data_in = my_trans_data_in; g_con_trans->header_size = 8; + + /* Tell any xrdpapi listeners the session is connected. The + * previous state is guaranteed to be 'disconnected' at this point */ + g_session_state.is_connected = 1; + xrdpapi_send_session_state_event_all(); + /* stop listening */ trans_delete(g_lis_trans); g_lis_trans = 0; @@ -1594,7 +1589,6 @@ channel_thread_loop(void *in_val) if (g_is_wait_obj_set(g_term_event)) { LOG_DEVEL(LOG_LEVEL_INFO, "channel_thread_loop: g_term_event set"); - xrdpapi_send_session_connect_event(0); clipboard_deinit(); sound_deinit(); devredir_deinit(); @@ -1617,7 +1611,7 @@ channel_thread_loop(void *in_val) { LOG_DEVEL(LOG_LEVEL_INFO, "channel_thread_loop: " "trans_check_wait_objs error resetting"); - xrdpapi_send_session_connect_event(0); + clipboard_deinit(); sound_deinit(); devredir_deinit(); @@ -1625,6 +1619,11 @@ channel_thread_loop(void *in_val) /* delete g_con_trans */ trans_delete(g_con_trans); g_con_trans = 0; + /* Tell any xrdpapi listeners the session is + * disconnected. The previous state is guaranteed + * to be 'disconnected' at this point */ + g_session_state.is_connected = 0; + (void)xrdpapi_send_session_state_event_all(); /* create new listener */ error = setup_listen(); @@ -1675,6 +1674,12 @@ channel_thread_loop(void *in_val) g_lis_trans = 0; trans_delete(g_con_trans); g_con_trans = 0; + /* Tell any xrdpapi listeners the session is disconnected */ + if (g_session_state.is_connected) + { + g_session_state.is_connected = 0; + (void)xrdpapi_send_session_state_event_all(); + } trans_delete(g_api_lis_trans); g_api_lis_trans = 0; api_con_trans_list_remove_all(); diff --git a/xrdpapi/xrdpapi.c b/xrdpapi/xrdpapi.c index 2ed3b9c8f1..5c0373eb15 100644 --- a/xrdpapi/xrdpapi.c +++ b/xrdpapi/xrdpapi.c @@ -52,7 +52,7 @@ struct wts_obj struct wts_server { struct wts_obj *info_obj; // Object to get session notifications - int client_is_connected; // Do we have a client? + struct xrdp_chan_session_state session_state; // session state }; static struct wts_server wts_current_server; @@ -66,6 +66,8 @@ static int mysend(int sck, const void *adata, int bytes); static int myrecv(int sck, void *adata, int bytes); +static int +mypeek(int sck, void *adata, int bytes); static void free_wts(struct wts_obj *wts) @@ -344,6 +346,25 @@ myrecv(int sck, void *adata, int bytes) return recd; } +/*****************************************************************************/ +static int +mypeek(int sck, void *adata, int bytes) +{ + int error; + +#if defined(SO_NOSIGPIPE) + const int on = 1; + setsockopt(sck, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on)); +#endif + + error = 0; + if (can_recv(sck, 100, 0)) + { + error = recv(sck, adata, bytes, MSG_NOSIGNAL | MSG_PEEK); + } + return error; +} + /* * write data to client connection * @@ -614,7 +635,7 @@ int WTSQuerySessionInformationA(void *hServer, if (rv == 1) { *(WTS_CONNECTSTATE_CLASS *)ppBuffer = - (wts_current_server.client_is_connected) + (wts_current_server.session_state.is_connected) ? WTSConnected : WTSDisconnected; if (pBytesReturned != NULL) @@ -659,8 +680,8 @@ int WTSRegisterSessionNotificationEx(void *hServer, errcode); if (wts != NULL) { - // Server will pass its current state now - struct xrdp_chan_session_status_event server_status; + // Server will pass the current session state now + struct xrdp_chan_session_state sess_state; if (!can_recv(wts->fd, 1000, 1)) { LOG(LOG_LEVEL_ERROR, @@ -669,8 +690,8 @@ int WTSRegisterSessionNotificationEx(void *hServer, free_wts(wts); } /* get server_status */ - else if (myrecv(wts->fd, &server_status, sizeof(server_status)) != - sizeof(server_status)) + else if (myrecv(wts->fd, &sess_state, sizeof(sess_state)) != + sizeof(sess_state)) { LOG(LOG_LEVEL_ERROR, "WTSRegisterSessionNotificationEx: myrecv failed"); @@ -680,8 +701,7 @@ int WTSRegisterSessionNotificationEx(void *hServer, else { wts_current_server.info_obj = wts; - wts_current_server.client_is_connected = - server_status.is_connected; + wts_current_server.session_state = sess_state; } } } @@ -724,7 +744,7 @@ int WTSGetDispatchMessage(void *cbdata, WNDPROC wndproc, LRESULT *lResult) { LRESULT result = 0; - struct xrdp_chan_session_info_event msg_data; + struct xrdp_chan_session_state new_state; /* get response */ if (wts_current_server.info_obj == NULL) @@ -735,23 +755,55 @@ WTSGetDispatchMessage(void *cbdata, WNDPROC wndproc, LRESULT *lResult) { // No message available - nothing to log } - else if (myrecv(wts_current_server.info_obj->fd, - &msg_data, sizeof(msg_data)) != sizeof(msg_data)) + else if (mypeek(wts_current_server.info_obj->fd, + &new_state, sizeof(new_state)) != sizeof(new_state)) { LOG(LOG_LEVEL_ERROR, "WTSGetDispatchMessage: An incomplete message was received"); } else { - if (msg_data.msgno == WM_WTSSESSION_CHANGE) + /* We've peeked a message. Find a SINGLE difference with our + * own state, update our state, and issue a callback. If there + * are more differences, the application will call us back and + * we can process the next one. When all the differences are + * accounted for, we can clear the message from the queue, and + * the fd will no longer be readable */ + UINT msgno = 0; // 0 means 'no message' + WPARAM wParam = 0; + LPARAM lParam = 0; + // Look for a single difference in the state we have, and the + // current state from the server + struct xrdp_chan_session_state *curr_state; + curr_state = &wts_current_server.session_state; + + if (curr_state->is_connected != new_state.is_connected) + { + curr_state->is_connected = new_state.is_connected; + msgno = WM_WTSSESSION_CHANGE; + wParam = (new_state.is_connected) + ? WTS_REMOTE_CONNECT : WTS_REMOTE_DISCONNECT; + } + + // If we found a difference, activate the callback + if (msgno != 0) { - // Update our cached copy of the client connected status before - // invoking the callback. - wts_current_server.client_is_connected = - (msg_data.wparam == WTS_REMOTE_CONNECT); + *lResult = wndproc(cbdata, msgno, wParam, lParam); + result = 1; + } + + // If we've exhausted all the differences between the old and + // the new state, purge the message from the queue + if (curr_state->is_connected == new_state.is_connected && + /* Add further checks here in the future */ + 1) + { + /* Sanity check on the fd */ + if (wts_current_server.info_obj->fd >= 0) + { + (void)myrecv(wts_current_server.info_obj->fd, + &new_state, sizeof(new_state)); + } } - *lResult = wndproc(cbdata, msg_data.msgno, - msg_data.wparam, msg_data.lparam); - result = 1; } return result;