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..f3df89193c --- /dev/null +++ b/common/channel_defs.h @@ -0,0 +1,69 @@ +/** + * 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 + +/** + * Channel IDs assigned to xrdp private channels. These are chosen to avoid conflicts with + * channel IDs returned by other servers. + */ +enum +{ + CHAN_ID_XRDP_BASE = (('x' << 24) | ('r' << 16) | ('d' << 8) | 'p'), + CHAN_ID_XRDP_SESSION_INFO = CHAN_ID_XRDP_BASE, + // Add further IDs here + CHAN_ID_XRDP_MAX +}; + +// 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 +}; + +// Events received on the CHAN_ID_XRDP_SESSION_INFO channel + +// 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? +}; + +#endif /* CHANNEL_DEFS_H */ 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 c75c4f9e8a..f611457179 100644 --- a/sesman/chansrv/chansrv.c +++ b/sesman/chansrv/chansrv.c @@ -44,14 +44,13 @@ #include "chansrv_config.h" #include "xrdp_sockets.h" #include "audin.h" +#include "channel_defs.h" #include "scp.h" #include "scp_sync.h" #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; @@ -113,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; @@ -343,6 +348,51 @@ send_rail_drawing_orders(char *data, int size) return 0; } +/*****************************************************************************/ +/** + * Sends a session API state event to one listener + */ +static int +xrdpapi_send_session_state_event_single(struct trans *t) +{ + 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; + + 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) + { + (void)xrdpapi_send_session_state_event_single(ltran); + } + } + } + } +} + /*****************************************************************************/ /* returns error */ static int @@ -1065,7 +1115,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) { @@ -1137,116 +1187,128 @@ my_api_data(int chan_id, char *data, int bytes) } /* - * called when WTSVirtualChannelWrite() 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; - 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_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) { - 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->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; - 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)) + /* SVC (or private) */ + if (connect_data.private_chan >= CHAN_ID_XRDP_BASE && + connect_data.private_chan < CHAN_ID_XRDP_MAX) { - return 1; + /* Valid private channel */ + ad->chan_id = connect_data.private_chan; + ad->chan_flags = 0; + rv = 0; } - in_uint8a(s, chan_name, channel_name_bytes); - chan_name[channel_name_bytes] = '\0'; - - 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) + else if (connect_data.private_chan == 0) { - /* SVC */ + /* Check SVC names */ + int index; for (index = 0; index < g_num_chan_items; index++) { - if (g_strcasecmp(g_chan_items[index].name, chan_name) == 0) + 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) - { - /* open ok */ - out_s = trans_get_out_s(trans, 8192); - if (out_s == NULL) - { - return 1; - } - out_uint32_le(out_s, 0); - s_mark_end(out_s); - if (trans_write_copy(trans) != 0) - { - return 1; - } - } - else + } + + /* 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; + } + + /* Channel-specific processing */ + if (rv == 0 && connect_data.private_chan != 0) + { + switch (connect_data.private_chan) { - /* open failed */ - out_s = trans_get_out_s(trans, 8192); - if (out_s == NULL) + case CHAN_ID_XRDP_SESSION_INFO: { - return 1; - } - out_uint32_le(out_s, 1); - s_mark_end(out_s); - if (trans_write_copy(trans) != 0) - { - return 1; + rv = xrdpapi_send_session_state_event_single(trans); + break; } + default: + break; } } - 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(chan_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 @@ -1259,8 +1321,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 { @@ -1301,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; @@ -1535,6 +1611,7 @@ channel_thread_loop(void *in_val) { LOG_DEVEL(LOG_LEVEL_INFO, "channel_thread_loop: " "trans_check_wait_objs error resetting"); + clipboard_deinit(); sound_deinit(); devredir_deinit(); @@ -1542,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(); @@ -1592,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/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..38b5dfc082 --- /dev/null +++ b/xrdpapi/connectmon.c @@ -0,0 +1,180 @@ +/** + * 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 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) +{ + LRESULT result = 1; + int *countptr = (int *)cbdata; + + switch (wParam) + { + 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: + 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); + + print_current_connection_state(); + + + if (!WTSRegisterSessionNotificationEx(WTS_CURRENT_SERVER_HANDLE, &fd, 0, NULL)) + { + 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, NULL); + } + + if (lc != NULL) + { + log_config_free(lc); + log_end(); + } + + return result; +} diff --git a/xrdpapi/xrdpapi.c b/xrdpapi/xrdpapi.c index b8ce4ff244..5c0373eb15 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,17 @@ 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 + struct xrdp_chan_session_state session_state; // session state +}; + +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); @@ -54,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) @@ -68,60 +82,53 @@ 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 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 * - * @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 + * @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 ******************************************************************************/ -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, + enum wts_errcode *errcode) { 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) { LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: bad SessionId"); + *errcode = WTS_E_BAD_SESSION_ID; return 0; } + wts = (struct wts_obj *) calloc(1, sizeof(struct wts_obj)); if (wts == NULL) { LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: calloc failed"); + *errcode = WTS_E_RESOURCE_ERROR; return 0; } wts->fd = -1; @@ -129,6 +136,7 @@ WTSVirtualChannelOpenEx(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; } @@ -137,6 +145,7 @@ WTSVirtualChannelOpenEx(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; } @@ -168,6 +177,7 @@ WTSVirtualChannelOpenEx(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; } @@ -177,50 +187,20 @@ WTSVirtualChannelOpenEx(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; } - chan_name_bytes = strlen(pVirtualName); - bytes = 4 + 4 + 4 + chan_name_bytes + 4; + 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: 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; - - 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"); + *errcode = WTS_E_RESOURCE_ERROR; free_wts(wts); return NULL; } @@ -229,22 +209,25 @@ WTSVirtualChannelOpenEx(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; } /* 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"); + *errcode = WTS_E_RESOURCE_ERROR; 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"); + *errcode = WTS_E_RESOURCE_ERROR; free_wts(wts); return NULL; } @@ -252,6 +235,49 @@ WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName, 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) +{ + enum wts_errcode errcode_dummy = WTS_E_NO_ERROR; + + if (hServer != WTS_CURRENT_SERVER_HANDLE) + { + return 0; + } + return VirtualChannelOpen(SessionId, pVirtualName, 0, 0, &errcode_dummy); +} + +/* + * 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) +{ + enum wts_errcode errcode_dummy = WTS_E_NO_ERROR; + return VirtualChannelOpen(SessionId, pVirtualName, + flags, 0, &errcode_dummy); +} + /* * Prevent receiving SIGPIPE on disconnect using either MSG_NOSIGNAL (Linux) * or SO_NOSIGPIPE (Mac OS X) @@ -320,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 * @@ -541,3 +586,225 @@ can_recv(int sck, int millis, int restart) return rv; } + +/*****************************************************************************/ +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.session_state.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) +{ + (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; + } + + if (wts_current_server.info_obj == NULL) + { + // Open a private channel for session info messages + // The name and flags args are ignored for xrdp private channels + struct wts_obj *wts = (struct wts_obj *) + VirtualChannelOpen(WTS_CURRENT_SESSION, + "", 0, + CHAN_ID_XRDP_SESSION_INFO, + errcode); + if (wts != NULL) + { + // 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, + "WTSRegisterSessionNotificationEx: can_recv failed"); + *errcode = WTS_E_RESOURCE_ERROR; + free_wts(wts); + } + /* get server_status */ + else if (myrecv(wts->fd, &sess_state, sizeof(sess_state)) != + sizeof(sess_state)) + { + 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.session_state = sess_state; + } + } + } + if (wts_current_server.info_obj == NULL) + { + *fd_ptr = -1; + return 0; + } + *fd_ptr = wts_current_server.info_obj->fd; + return 1; +} + +/*****************************************************************************/ +int WTSUnRegisterSessionNotificationEx(void *hServer, + 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; + } + + free_wts(wts_current_server.info_obj); + wts_current_server.info_obj = NULL; + return 1; +} + +/*****************************************************************************/ +int +WTSGetDispatchMessage(void *cbdata, WNDPROC wndproc, LRESULT *lResult) +{ + LRESULT result = 0; + struct xrdp_chan_session_state new_state; + + /* 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 (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 + { + /* 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) + { + *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)); + } + } + } + + return result; +} diff --git a/xrdpapi/xrdpapi.h b/xrdpapi/xrdpapi.h index b818d93e02..55593aadbd 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,42 @@ 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_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 { WTSVirtualClientData, @@ -48,6 +86,35 @@ 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, + 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; +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 +140,82 @@ 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. + * + * 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 + * @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. + * + * 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(void *hServer, + int *fd_ptr, + int dwFlags, + enum wts_errcode *errcode); + +/** + * 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 + * @param[out] errcode Status of operation if false returned. Can be NULL. + * @return true for success + */ +int WTSUnRegisterSessionNotificationEx(void *hServer, + int fd, + enum wts_errcode *errcode); + + +/** 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