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
+336 -71
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,31 +209,75 @@ 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;
}
free(connect_data);
return wts;
}
/*
* Opens a handle to the server end of a specified virtual channel - this
* call is deprecated - use WTSVirtualChannelOpenEx() instead
*
* @param hServer - *must* be WTS_CURRENT_SERVER_HANDLE
* @param SessionId - current session ID; *must* be WTS_CURRENT_SESSION
* @param pVirtualName - virtual channel name when using SVC
* - name of endpoint listener when using DVC
*
* @return a valid pointer on success, NULL on error
******************************************************************************/
void *
WTSVirtualChannelOpen(void *hServer, unsigned int SessionId,
const char *pVirtualName)
{
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)
@@ -322,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
*
@@ -543,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