xrdpapi: Add a way to get client connect status

Functions are added to xrdpapi to allows the connection status
to be determimed. These functions are modelled on the Windows API
functions, but are not compatible with them. In particular, the error
handling is different.

A way for an application to receive events is also provided. At present,
only connect/disconnected events are implemented.
This commit is contained in:
matt335672
2025-10-13 15:12:45 +01:00
parent d90b9d9316
commit 6a5d858dce
6 changed files with 739 additions and 165 deletions
+1
View File
@@ -47,6 +47,7 @@ libcommon_la_SOURCES = \
arch.h \ arch.h \
base64.h \ base64.h \
base64.c \ base64.c \
channel_defs.h \
defines.h \ defines.h \
fifo.c \ fifo.c \
fifo.h \ 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++; \ (s)->p++; \
} while (0) } 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 \ #define in_uint8p(s, v, n) do \
{ \ { \
+180 -92
View File
@@ -44,14 +44,13 @@
#include "chansrv_config.h" #include "chansrv_config.h"
#include "xrdp_sockets.h" #include "xrdp_sockets.h"
#include "audin.h" #include "audin.h"
#include "channel_defs.h"
#include "scp.h" #include "scp.h"
#include "scp_sync.h" #include "scp_sync.h"
#include "ms-rdpbcgr.h" #include "ms-rdpbcgr.h"
#define MAX_PATH 260
static struct trans *g_lis_trans = 0; static struct trans *g_lis_trans = 0;
static struct trans *g_con_trans = 0; static struct trans *g_con_trans = 0;
static struct trans *g_api_lis_trans = 0; static struct trans *g_api_lis_trans = 0;
@@ -113,6 +112,12 @@ struct xrdp_api_data
int chan_id; 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 struct timeout_obj
{ {
tui32 mstime; tui32 mstime;
@@ -343,6 +348,51 @@ send_rail_drawing_orders(char *data, int size)
return 0; 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 */ /* returns error */
static int static int
@@ -1065,7 +1115,7 @@ my_api_open_response(int chan_id, int creation_status)
{ {
return 1; return 1;
} }
out_uint32_le(s, creation_status); out_uint32_pe(s, creation_status);
s_mark_end(s); s_mark_end(s);
if (trans_write_copy(trans) != 0) 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 static int
my_api_trans_data_in(struct trans *trans) my_api_trans_data_in(struct trans *trans)
{ {
struct stream *s; struct stream *s;
struct stream *out_s;
struct xrdp_api_data *ad; struct xrdp_api_data *ad;
int index;
int rv; int rv;
int bytes; 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); //LOG_DEVEL(LOG_LEVEL_DEBUG, "my_api_trans_data_in: extra_flags %d", trans->extra_flags);
rv = 0; rv = 0;
@@ -1164,89 +1300,15 @@ my_api_trans_data_in(struct trans *trans)
s = trans_get_in_s(trans); s = trans_get_in_s(trans);
if (trans->extra_flags == 0) if (trans->extra_flags == 0)
{ {
in_uint32_le(s, bytes); trans->header_size = XRDPAPI_CONNECT_PDU_LEN; // Need more data
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; trans->extra_flags = 1;
} }
else if (trans->extra_flags == 1) else if (trans->extra_flags == 1)
{ {
rv = 1; // We've got a complete connection request on the channel */
in_uint32_le(s, channel_name_bytes); handle_xrdpapi_connection_request(trans);
//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;
}
init_stream(s, 0); init_stream(s, 0);
trans->extra_flags = 2; trans->extra_flags = 2; // Mark the connection phase as complete
trans->header_size = 0; trans->header_size = 0;
} }
else else
@@ -1258,10 +1320,18 @@ my_api_trans_data_in(struct trans *trans)
return 1; return 1;
} }
if (ad->chan_flags == 0) if (ad->chan_flags == 0)
{
if (ad->chan_id == CHAN_ID_XRDP_SESSION_INFO)
{
// Ignore data sent to this channel
rv = 0;
}
else
{ {
/* SVC */ /* SVC */
rv = send_channel_data(ad->chan_id, s->data, bytes); rv = send_channel_data(ad->chan_id, s->data, bytes);
} }
}
else else
{ {
/* DVS */ /* DVS */
@@ -1301,6 +1371,12 @@ my_trans_conn_in(struct trans *trans, struct trans *new_trans)
g_con_trans = new_trans; g_con_trans = new_trans;
g_con_trans->trans_data_in = my_trans_data_in; g_con_trans->trans_data_in = my_trans_data_in;
g_con_trans->header_size = 8; 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 */ /* stop listening */
trans_delete(g_lis_trans); trans_delete(g_lis_trans);
g_lis_trans = 0; g_lis_trans = 0;
@@ -1535,6 +1611,7 @@ channel_thread_loop(void *in_val)
{ {
LOG_DEVEL(LOG_LEVEL_INFO, "channel_thread_loop: " LOG_DEVEL(LOG_LEVEL_INFO, "channel_thread_loop: "
"trans_check_wait_objs error resetting"); "trans_check_wait_objs error resetting");
clipboard_deinit(); clipboard_deinit();
sound_deinit(); sound_deinit();
devredir_deinit(); devredir_deinit();
@@ -1542,6 +1619,11 @@ channel_thread_loop(void *in_val)
/* delete g_con_trans */ /* delete g_con_trans */
trans_delete(g_con_trans); trans_delete(g_con_trans);
g_con_trans = 0; 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 */ /* create new listener */
error = setup_listen(); error = setup_listen();
@@ -1592,6 +1674,12 @@ channel_thread_loop(void *in_val)
g_lis_trans = 0; g_lis_trans = 0;
trans_delete(g_con_trans); trans_delete(g_con_trans);
g_con_trans = 0; 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); trans_delete(g_api_lis_trans);
g_api_lis_trans = 0; g_api_lis_trans = 0;
api_con_trans_list_remove_all(); api_con_trans_list_remove_all();
+336 -71
View File
@@ -37,6 +37,7 @@
#include "log.h" #include "log.h"
#include "xrdp_sockets.h" #include "xrdp_sockets.h"
#include "string_calls.h" #include "string_calls.h"
#include "channel_defs.h"
#include "xrdpapi.h" #include "xrdpapi.h"
struct wts_obj struct wts_obj
@@ -45,6 +46,17 @@ struct wts_obj
int display_num; 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 */ /* helper functions used by WTSxxx API - do not invoke directly */
static int static int
can_send(int sck, int millis, int restart); can_send(int sck, int millis, int restart);
@@ -54,6 +66,8 @@ static int
mysend(int sck, const void *adata, int bytes); mysend(int sck, const void *adata, int bytes);
static int static int
myrecv(int sck, void *adata, int bytes); myrecv(int sck, void *adata, int bytes);
static int
mypeek(int sck, void *adata, int bytes);
static void static void
free_wts(struct wts_obj *wts) 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 * 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 * @param pVirtualName - virtual channel name when using SVC
* - name of endpoint listener when using DVC * - name of endpoint listener when using DVC
* @param flags - type of channel and channel priority if 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 * @return a valid pointer on success, NULL on error
******************************************************************************/ ******************************************************************************/
void * static void *
WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName, VirtualChannelOpen(unsigned int SessionId, const char *pVirtualName,
unsigned int flags) unsigned int flags,
unsigned int private_chan,
enum wts_errcode *errcode)
{ {
struct wts_obj *wts; struct wts_obj *wts;
int bytes; int bytes;
unsigned long long1; unsigned long long1;
struct sockaddr_un s; struct sockaddr_un s;
char *connect_data; // Pad the connect data out to a larger size to allow for
int chan_name_bytes; // 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; int lerrno;
if (SessionId != WTS_CURRENT_SESSION) if (SessionId != WTS_CURRENT_SESSION)
{ {
LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: bad SessionId"); LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: bad SessionId");
*errcode = WTS_E_BAD_SESSION_ID;
return 0; return 0;
} }
wts = (struct wts_obj *) calloc(1, sizeof(struct wts_obj)); wts = (struct wts_obj *) calloc(1, sizeof(struct wts_obj));
if (wts == NULL) if (wts == NULL)
{ {
LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: calloc failed"); LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: calloc failed");
*errcode = WTS_E_RESOURCE_ERROR;
return 0; return 0;
} }
wts->fd = -1; wts->fd = -1;
@@ -129,6 +136,7 @@ WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName,
if (wts->display_num < 0) if (wts->display_num < 0)
{ {
LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: fatal error; invalid DISPLAY"); LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: fatal error; invalid DISPLAY");
*errcode = WTS_E_RESOURCE_ERROR;
free_wts(wts); free_wts(wts);
return NULL; return NULL;
} }
@@ -137,6 +145,7 @@ WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName,
if ((wts->fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) if ((wts->fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
{ {
LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: socket failed"); LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: socket failed");
*errcode = WTS_E_RESOURCE_ERROR;
free_wts(wts); free_wts(wts);
return NULL; return NULL;
} }
@@ -168,6 +177,7 @@ WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName,
else else
{ {
LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: connect failed"); LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: connect failed");
*errcode = WTS_E_CHANSRV_NOT_UP;
free_wts(wts); free_wts(wts);
return NULL; return NULL;
} }
@@ -177,50 +187,20 @@ WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName,
if (!can_send(wts->fd, 500, 1)) if (!can_send(wts->fd, 500, 1))
{ {
LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: can_send failed"); LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: can_send failed");
*errcode = WTS_E_CHANSRV_NOT_UP;
free_wts(wts); free_wts(wts);
return NULL; return NULL;
} }
chan_name_bytes = strlen(pVirtualName); cd.connect_data.version = XRDPAPI_CONNECT_PDU_VERSION;
bytes = 4 + 4 + 4 + chan_name_bytes + 4; 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, if (mysend(wts->fd, &cd, sizeof(cd)) != sizeof(cd))
"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)
{ {
LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: mysend failed"); LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: mysend failed");
*errcode = WTS_E_RESOURCE_ERROR;
free_wts(wts); free_wts(wts);
return NULL; return NULL;
} }
@@ -229,31 +209,75 @@ WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName,
if (!can_recv(wts->fd, 500, 1)) if (!can_recv(wts->fd, 500, 1))
{ {
LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: can_recv failed"); LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: can_recv failed");
*errcode = WTS_E_RESOURCE_ERROR;
free_wts(wts); free_wts(wts);
return NULL; return NULL;
} }
/* get response */ /* 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"); LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: myrecv failed");
*errcode = WTS_E_RESOURCE_ERROR;
free_wts(wts); free_wts(wts);
return NULL; return NULL;
} }
if ((connect_data[0] != 0) || (connect_data[1] != 0) || if (connect_result != 0)
(connect_data[2] != 0) || (connect_data[3] != 0))
{ {
LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: connect_data not ok"); LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: connect_data not ok");
*errcode = WTS_E_RESOURCE_ERROR;
free_wts(wts); free_wts(wts);
return NULL; return NULL;
} }
free(connect_data);
return wts; 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) * Prevent receiving SIGPIPE on disconnect using either MSG_NOSIGNAL (Linux)
* or SO_NOSIGPIPE (Mac OS X) * or SO_NOSIGPIPE (Mac OS X)
@@ -322,6 +346,25 @@ myrecv(int sck, void *adata, int bytes)
return recd; 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 * write data to client connection
* *
@@ -543,3 +586,225 @@ can_recv(int sck, int millis, int restart)
return rv; 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_) #if !defined(XRDPAPI_H_)
#define XRDPAPI_H_ #define XRDPAPI_H_
#include <stdint.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@@ -41,6 +43,42 @@ extern "C" {
#define WTS_CHANNEL_OPTION_DYNAMIC_PRI_HIGH 0x00000003 #define WTS_CHANNEL_OPTION_DYNAMIC_PRI_HIGH 0x00000003
#define WTS_CHANNEL_OPTION_DYNAMIC_PRI_REAL 0x00000004 #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 typedef enum _WTS_VIRTUAL_CLASS
{ {
WTSVirtualClientData, WTSVirtualClientData,
@@ -48,6 +86,35 @@ typedef enum _WTS_VIRTUAL_CLASS
} }
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: * Reference:
* http://msdn.microsoft.com/en-us/library/windows/desktop/aa383464(v=vs.85).aspx * 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); 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 #ifdef __cplusplus
} }
#endif #endif