Merge pull request #3640 from matt335672/xrdpapi_connect_status

xrdpapi: Add a way to monitor connect/disconnect events
This commit is contained in:
matt335672
2025-12-28 11:39:07 +00:00
committed by GitHub
9 changed files with 933 additions and 166 deletions
+1
View File
@@ -47,6 +47,7 @@ libcommon_la_SOURCES = \
arch.h \
base64.h \
base64.c \
channel_defs.h \
defines.h \
fifo.c \
fifo.h \
+69
View File
@@ -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 <stdint.h>
/**
* 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 */
+8
View File
@@ -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 \
{ \
+182 -94
View File
@@ -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,26 +1187,112 @@ 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
handle_xrdpapi_connection_request(struct trans *trans)
{
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;
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;
}
ad->chan_flags = connect_data.flags;
if (connect_data.flags == 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;
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;
}
}
}
/* 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)
{
case CHAN_ID_XRDP_SESSION_INFO:
{
rv = xrdpapi_send_session_state_event_single(trans);
break;
}
default:
break;
}
}
}
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 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;
//LOG_DEVEL(LOG_LEVEL_DEBUG, "my_api_trans_data_in: extra_flags %d", trans->extra_flags);
rv = 0;
@@ -1164,89 +1300,15 @@ 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))
{
return 1;
}
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)
{
/* SVC */
for (index = 0; index < g_num_chan_items; index++)
{
if (g_strcasecmp(g_chan_items[index].name, chan_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
{
/* open failed */
out_s = trans_get_out_s(trans, 8192);
if (out_s == NULL)
{
return 1;
}
out_uint32_le(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(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;
}
// 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 'connected' 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();
+13 -2
View File
@@ -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
+180
View File
@@ -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 <config_ac.h>
#endif
#include "xrdpapi.h"
#include "log.h"
#include <stdlib.h>
#include <stdio.h>
#include <poll.h>
#include <errno.h>
/******************************************************************************/
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;
}
+1 -1
View File
@@ -158,7 +158,7 @@ run_echo_test(void)
/* validate the echo */
for (i = 0; i < bytes_read; i++, rd_ptr++)
{
if (*rd_ptr != (unsigned char) in_buf[i])
if (*rd_ptr != in_buf[i])
{
printf("### data mismatch: expected 0x%x got 0x%x\n",
(unsigned char) *rd_ptr, (unsigned char) in_buf[i]);
+336 -69
View File
@@ -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;
}
+143
View File
@@ -26,6 +26,8 @@
#if !defined(XRDPAPI_H_)
#define XRDPAPI_H_
#include <stdint.h>
#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