mirror of
https://github.com/morgan9e/grd
synced 2026-04-13 16:04:13 +09:00
518 lines
17 KiB
C
518 lines
17 KiB
C
/*
|
|
* Copyright (C) 2022 Pascal Nowack
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
* 02111-1307, USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "grd-rdp-dvc-camera-enumerator.h"
|
|
|
|
#include <freerdp/server/rdpecam-enumerator.h>
|
|
|
|
#include "grd-rdp-dvc-camera-device.h"
|
|
|
|
#define SERVER_VERSION 2
|
|
#define MAX_DVC_NAME_LEN 256
|
|
|
|
typedef struct _DeviceInfo
|
|
{
|
|
char *device_name;
|
|
char *dvc_name;
|
|
} DeviceInfo;
|
|
|
|
struct _GrdRdpDvcCameraEnumerator
|
|
{
|
|
GrdRdpDvc parent;
|
|
|
|
CamDevEnumServerContext *enumerator_context;
|
|
gboolean channel_opened;
|
|
|
|
GrdRdpDvcHandler *dvc_handler;
|
|
|
|
uint8_t protocol_version;
|
|
gboolean initialized;
|
|
|
|
GHashTable *device_table;
|
|
|
|
GSource *device_source;
|
|
GMutex device_queue_mutex;
|
|
GHashTable *devices_to_add;
|
|
GHashTable *devices_to_remove;
|
|
};
|
|
|
|
G_DEFINE_TYPE (GrdRdpDvcCameraEnumerator, grd_rdp_dvc_camera_enumerator,
|
|
GRD_TYPE_RDP_DVC)
|
|
|
|
static void
|
|
grd_rdp_dvc_camera_enumerator_maybe_init (GrdRdpDvc *dvc)
|
|
{
|
|
GrdRdpDvcCameraEnumerator *enumerator = GRD_RDP_DVC_CAMERA_ENUMERATOR (dvc);
|
|
CamDevEnumServerContext *enumerator_context;
|
|
|
|
if (enumerator->channel_opened)
|
|
return;
|
|
|
|
enumerator_context = enumerator->enumerator_context;
|
|
if (enumerator_context->Open (enumerator_context))
|
|
{
|
|
g_warning ("[RDP.CAM_ENUMERATOR] Failed to open channel. "
|
|
"Terminating protocol");
|
|
grd_rdp_dvc_queue_channel_tear_down (GRD_RDP_DVC (enumerator));
|
|
return;
|
|
}
|
|
enumerator->channel_opened = TRUE;
|
|
}
|
|
|
|
static void
|
|
dvc_creation_status_cb (gpointer user_data,
|
|
int32_t creation_status)
|
|
{
|
|
GrdRdpDvcCameraEnumerator *enumerator = user_data;
|
|
|
|
if (creation_status < 0)
|
|
{
|
|
g_debug ("[RDP.CAM_ENUMERATOR] Failed to open channel "
|
|
"(CreationStatus %i). Terminating protocol", creation_status);
|
|
grd_rdp_dvc_queue_channel_tear_down (GRD_RDP_DVC (enumerator));
|
|
}
|
|
}
|
|
|
|
static BOOL
|
|
enumerator_channel_id_assigned (CamDevEnumServerContext *enumerator_context,
|
|
uint32_t channel_id)
|
|
{
|
|
GrdRdpDvcCameraEnumerator *enumerator = enumerator_context->userdata;
|
|
GrdRdpDvc *dvc = GRD_RDP_DVC (enumerator);
|
|
|
|
g_debug ("[RDP.CAM_ENUMERATOR] DVC channel id assigned to id %u", channel_id);
|
|
|
|
grd_rdp_dvc_subscribe_creation_status (dvc, channel_id,
|
|
dvc_creation_status_cb,
|
|
enumerator);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static uint32_t
|
|
enumerator_select_version_request (CamDevEnumServerContext *enumerator_context,
|
|
const CAM_SELECT_VERSION_REQUEST *select_version_request)
|
|
{
|
|
GrdRdpDvcCameraEnumerator *enumerator = enumerator_context->userdata;
|
|
CAM_SELECT_VERSION_RESPONSE select_version_response = {};
|
|
|
|
if (enumerator->initialized)
|
|
{
|
|
g_warning ("[RDP.CAM_ENUMERATOR] Protocol violation: Received version "
|
|
"request, but protocol is already initialized");
|
|
grd_rdp_dvc_queue_channel_tear_down (GRD_RDP_DVC (enumerator));
|
|
return CHANNEL_RC_INITIALIZATION_ERROR;
|
|
}
|
|
|
|
g_debug ("[RDP.CAM_ENUMERATOR] Client supports protocol version %u",
|
|
select_version_request->Header.Version);
|
|
enumerator->protocol_version = MIN (SERVER_VERSION,
|
|
select_version_request->Header.Version);
|
|
enumerator->initialized = TRUE;
|
|
|
|
select_version_response.Header.Version = enumerator->protocol_version;
|
|
select_version_response.Header.MessageId = CAM_MSG_ID_SelectVersionResponse;
|
|
|
|
return enumerator_context->SelectVersionResponse (enumerator_context,
|
|
&select_version_response);
|
|
}
|
|
|
|
static const char *reserved_svc_names[] =
|
|
{
|
|
"encomsp", /* [MS-RDPEMC] */
|
|
"CLIPRDR", /* [MS-RDPECLIP] */
|
|
"DRDYNVC", /* [MS-RDPEDYC] */
|
|
"RDPDR", /* [MS-RDPEFS] */
|
|
"RDPSND", /* [MS-RDPEA] */
|
|
"RAIL", /* [MS-RDPERP] */
|
|
};
|
|
|
|
static const char *reserved_dvc_names[] =
|
|
{
|
|
"AUDIO_INPUT", /* [MS-RDPEAI] */
|
|
"AUDIO_PLAYBACK_DVC", /* [MS-RDPEA] */
|
|
"AUDIO_PLAYBACK_LOSSY_DVC", /* [MS-RDPEA] */
|
|
"dwmprox", /* [MS-RDPCR2] */
|
|
"ECHO", /* [MS-RDPEECO] */
|
|
"FileRedirectorChannel", /* [MS-RDPEPNP] */
|
|
"Microsoft::Windows::RDS::AuthRedirection", /* [MS-RDPEAR] */
|
|
"Microsoft::Windows::RDS::CoreInput", /* [MS-RDPECI] */
|
|
"Microsoft::Windows::RDS::DisplayControl", /* [MS-RDPEDISP] */
|
|
"Microsoft::Windows::RDS::Geometry::v08.01", /* [MS-RDPEGT] */
|
|
"Microsoft::Windows::RDS::Graphics", /* [MS-RDPEGFX] */
|
|
"Microsoft::Windows::RDS::Input", /* [MS-RDPEI] */
|
|
"Microsoft::Windows::RDS::Location", /* [MS-RDPEL] */
|
|
"Microsoft::Windows::RDS::MouseCursor", /* [MS-RDPEMSC] */
|
|
"Microsoft::Windows::RDS::Telemetry", /* [MS-RDPET] */
|
|
"Microsoft::Windows::RDS::Video::Control::v08.01", /* [MS-RDPEVOR] */
|
|
"Microsoft::Windows::RDS::Video::Data::v08.01", /* [MS-RDPEVOR] */
|
|
"PNPDR", /* [MS-RDPEPNP] */
|
|
"RDCamera_Device_Enumerator", /* [MS-RDPECAM] */
|
|
"TextInput_ServerToClientDVC", /* [MS-RDPETXT] */
|
|
"TextInput_ClientToServerDVC", /* [MS-RDPETXT] */
|
|
"TSMF", /* [MS-RDPEV] */
|
|
"TSVCTKT", /* [MS-RDPEXPS] */
|
|
"URBDRC", /* [MS-RDPEUSB] */
|
|
"WebAuthN_Channel", /* [MS-RDPEWA] */
|
|
"WMSAud", /* [MS-RDPADRV] */
|
|
"WMSDL", /* [MS-RDPADRV] */
|
|
"XPSRD", /* [MS-RDPEXPS] */
|
|
};
|
|
|
|
static gboolean
|
|
is_vc_name_reserved (const char *dvc_name)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (reserved_svc_names); ++i)
|
|
{
|
|
if (strcmp (dvc_name, reserved_svc_names[i]) == 0)
|
|
return TRUE;
|
|
}
|
|
for (i = 0; i < G_N_ELEMENTS (reserved_dvc_names); ++i)
|
|
{
|
|
if (strcmp (dvc_name, reserved_dvc_names[i]) == 0)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static uint32_t
|
|
enumerator_device_added_notification (CamDevEnumServerContext *enumerator_context,
|
|
const CAM_DEVICE_ADDED_NOTIFICATION *device_added_notification)
|
|
{
|
|
GrdRdpDvcCameraEnumerator *enumerator = enumerator_context->userdata;
|
|
g_autofree char *device_name = NULL;
|
|
DeviceInfo *info;
|
|
|
|
if (!enumerator->initialized)
|
|
{
|
|
g_warning ("[RDP.CAM_ENUMERATOR] Protocol violation: Received device "
|
|
"added notification, but no protocol version was negotiated");
|
|
grd_rdp_dvc_queue_channel_tear_down (GRD_RDP_DVC (enumerator));
|
|
return CHANNEL_RC_INITIALIZATION_ERROR;
|
|
}
|
|
|
|
/*
|
|
* The name of a device channel MUST be a null-terminated ANSI encoded
|
|
* character string containing a maximum of 256 characters.
|
|
*/
|
|
if (strlen (device_added_notification->VirtualChannelName) > MAX_DVC_NAME_LEN)
|
|
{
|
|
g_warning ("[RDP.CAM_ENUMERATOR] Protocol violation: Client tried to use "
|
|
"too long DVC name. Ignoring announced camera device");
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
if (is_vc_name_reserved (device_added_notification->VirtualChannelName))
|
|
{
|
|
g_warning ("[RDP.CAM_ENUMERATOR] Client tried to use reserved DVC name "
|
|
"\"%s\". Ignoring announced camera device",
|
|
device_added_notification->VirtualChannelName);
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
device_name = ConvertWCharToUtf8Alloc (device_added_notification->DeviceName,
|
|
NULL);
|
|
if (!device_name)
|
|
{
|
|
g_warning ("[RDP.CAM_ENUMERATOR] Failed to convert device name. "
|
|
"Ignoring announced camera device");
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
g_debug ("[RDP.CAM_ENUMERATOR] Client announced camera device: "
|
|
"DeviceName: \"%s\", DVC name: \"%s\"",
|
|
device_name, device_added_notification->VirtualChannelName);
|
|
|
|
info = g_new0 (DeviceInfo, 1);
|
|
info->device_name = g_strdup (device_name);
|
|
info->dvc_name = g_strdup (device_added_notification->VirtualChannelName);
|
|
|
|
g_mutex_lock (&enumerator->device_queue_mutex);
|
|
g_hash_table_remove (enumerator->devices_to_remove, info->dvc_name);
|
|
g_hash_table_insert (enumerator->devices_to_add, info->dvc_name, info);
|
|
g_mutex_unlock (&enumerator->device_queue_mutex);
|
|
|
|
g_source_set_ready_time (enumerator->device_source, 0);
|
|
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
static uint32_t
|
|
enumerator_device_removed_notification (CamDevEnumServerContext *enumerator_context,
|
|
const CAM_DEVICE_REMOVED_NOTIFICATION *device_removed_notification)
|
|
{
|
|
GrdRdpDvcCameraEnumerator *enumerator = enumerator_context->userdata;
|
|
DeviceInfo *info;
|
|
|
|
if (!enumerator->initialized)
|
|
{
|
|
g_warning ("[RDP.CAM_ENUMERATOR] Protocol violation: Received device "
|
|
"removed notification, but no protocol version was negotiated");
|
|
grd_rdp_dvc_queue_channel_tear_down (GRD_RDP_DVC (enumerator));
|
|
return CHANNEL_RC_INITIALIZATION_ERROR;
|
|
}
|
|
|
|
/*
|
|
* The name of a device channel MUST be a null-terminated ANSI encoded
|
|
* character string containing a maximum of 256 characters.
|
|
*/
|
|
if (strlen (device_removed_notification->VirtualChannelName) > MAX_DVC_NAME_LEN)
|
|
{
|
|
g_warning ("[RDP.CAM_ENUMERATOR] Protocol violation: Client tried to use "
|
|
"too long DVC name. Ignoring announced camera device");
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
g_debug ("[RDP.CAM_ENUMERATOR] Client removed camera device "
|
|
"(channel name: \"%s\")",
|
|
device_removed_notification->VirtualChannelName);
|
|
|
|
info = g_new0 (DeviceInfo, 1);
|
|
info->dvc_name = g_strdup (device_removed_notification->VirtualChannelName);
|
|
|
|
g_mutex_lock (&enumerator->device_queue_mutex);
|
|
g_hash_table_remove (enumerator->devices_to_add, info->dvc_name);
|
|
g_hash_table_insert (enumerator->devices_to_remove, info->dvc_name, info);
|
|
g_mutex_unlock (&enumerator->device_queue_mutex);
|
|
|
|
g_source_set_ready_time (enumerator->device_source, 0);
|
|
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
GrdRdpDvcCameraEnumerator *
|
|
grd_rdp_dvc_camera_enumerator_new (GrdSessionRdp *session_rdp,
|
|
GrdRdpDvcHandler *dvc_handler,
|
|
HANDLE vcm,
|
|
rdpContext *rdp_context)
|
|
{
|
|
GrdRdpDvcCameraEnumerator *enumerator;
|
|
CamDevEnumServerContext *enumerator_context;
|
|
|
|
enumerator = g_object_new (GRD_TYPE_RDP_DVC_CAMERA_ENUMERATOR, NULL);
|
|
enumerator_context = cam_dev_enum_server_context_new (vcm);
|
|
if (!enumerator_context)
|
|
g_error ("[RDP.CAM_ENUMERATOR] Failed to allocate server context (OOM)");
|
|
|
|
enumerator->enumerator_context = enumerator_context;
|
|
enumerator->dvc_handler = dvc_handler;
|
|
|
|
grd_rdp_dvc_initialize_base (GRD_RDP_DVC (enumerator),
|
|
dvc_handler, session_rdp,
|
|
GRD_RDP_CHANNEL_CAMERA_ENUMERATOR);
|
|
|
|
enumerator_context->ChannelIdAssigned = enumerator_channel_id_assigned;
|
|
enumerator_context->SelectVersionRequest = enumerator_select_version_request;
|
|
enumerator_context->DeviceAddedNotification = enumerator_device_added_notification;
|
|
enumerator_context->DeviceRemovedNotification = enumerator_device_removed_notification;
|
|
enumerator_context->rdpcontext = rdp_context;
|
|
enumerator_context->userdata = enumerator;
|
|
|
|
return enumerator;
|
|
}
|
|
|
|
static void
|
|
grd_rdp_dvc_camera_enumerator_dispose (GObject *object)
|
|
{
|
|
GrdRdpDvcCameraEnumerator *enumerator =
|
|
GRD_RDP_DVC_CAMERA_ENUMERATOR (object);
|
|
GrdRdpDvc *dvc = GRD_RDP_DVC (enumerator);
|
|
|
|
g_hash_table_remove_all (enumerator->device_table);
|
|
|
|
if (enumerator->channel_opened)
|
|
{
|
|
enumerator->enumerator_context->Close (enumerator->enumerator_context);
|
|
enumerator->channel_opened = FALSE;
|
|
}
|
|
grd_rdp_dvc_maybe_unsubscribe_creation_status (dvc);
|
|
|
|
if (enumerator->device_source)
|
|
{
|
|
g_source_destroy (enumerator->device_source);
|
|
g_clear_pointer (&enumerator->device_source, g_source_unref);
|
|
}
|
|
|
|
g_clear_pointer (&enumerator->enumerator_context,
|
|
cam_dev_enum_server_context_free);
|
|
|
|
G_OBJECT_CLASS (grd_rdp_dvc_camera_enumerator_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
grd_rdp_dvc_camera_enumerator_finalize (GObject *object)
|
|
{
|
|
GrdRdpDvcCameraEnumerator *enumerator =
|
|
GRD_RDP_DVC_CAMERA_ENUMERATOR (object);
|
|
|
|
g_mutex_clear (&enumerator->device_queue_mutex);
|
|
|
|
g_clear_pointer (&enumerator->devices_to_remove, g_hash_table_unref);
|
|
g_clear_pointer (&enumerator->devices_to_add, g_hash_table_unref);
|
|
|
|
g_assert (g_hash_table_size (enumerator->device_table) == 0);
|
|
g_clear_pointer (&enumerator->device_table, g_hash_table_unref);
|
|
|
|
G_OBJECT_CLASS (grd_rdp_dvc_camera_enumerator_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
device_info_free (gpointer data)
|
|
{
|
|
DeviceInfo *info = data;
|
|
|
|
g_clear_pointer (&info->dvc_name, g_free);
|
|
g_clear_pointer (&info->device_name, g_free);
|
|
|
|
g_free (info);
|
|
}
|
|
|
|
static gboolean
|
|
remove_camera_device (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
GrdRdpDvcCameraEnumerator *enumerator = user_data;
|
|
DeviceInfo *info = value;
|
|
|
|
g_hash_table_remove (enumerator->device_table, info->dvc_name);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
on_device_error (GrdRdpDvcCameraDevice *device,
|
|
GrdRdpDvcCameraEnumerator *enumerator)
|
|
{
|
|
DeviceInfo *info;
|
|
|
|
/* Fake a Device Removed Notification on error */
|
|
info = g_new0 (DeviceInfo, 1);
|
|
info->dvc_name = g_strdup (grd_rdp_dvc_camera_device_get_dvc_name (device));
|
|
|
|
g_mutex_lock (&enumerator->device_queue_mutex);
|
|
g_hash_table_remove (enumerator->devices_to_add, info->dvc_name);
|
|
g_hash_table_insert (enumerator->devices_to_remove, info->dvc_name, info);
|
|
g_mutex_unlock (&enumerator->device_queue_mutex);
|
|
|
|
g_source_set_ready_time (enumerator->device_source, 0);
|
|
}
|
|
|
|
static gboolean
|
|
add_camera_device (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
GrdRdpDvcCameraEnumerator *enumerator = user_data;
|
|
CamDevEnumServerContext *enumerator_context = enumerator->enumerator_context;
|
|
DeviceInfo *info = value;
|
|
GrdRdpDvcCameraDevice *device;
|
|
|
|
/* Camera may be re-added by client */
|
|
g_hash_table_remove (enumerator->device_table, info->dvc_name);
|
|
|
|
device = grd_rdp_dvc_camera_device_new (enumerator->dvc_handler,
|
|
enumerator_context->vcm,
|
|
enumerator_context->rdpcontext,
|
|
enumerator->protocol_version,
|
|
info->dvc_name,
|
|
info->device_name);
|
|
if (!device)
|
|
return TRUE;
|
|
|
|
g_signal_connect (device, "error",
|
|
G_CALLBACK (on_device_error),
|
|
enumerator);
|
|
|
|
g_hash_table_insert (enumerator->device_table,
|
|
g_steal_pointer (&info->dvc_name), device);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
manage_devices (gpointer user_data)
|
|
{
|
|
GrdRdpDvcCameraEnumerator *enumerator = user_data;
|
|
|
|
g_mutex_lock (&enumerator->device_queue_mutex);
|
|
g_hash_table_foreach_remove (enumerator->devices_to_remove,
|
|
remove_camera_device, enumerator);
|
|
g_hash_table_foreach_remove (enumerator->devices_to_add,
|
|
add_camera_device, enumerator);
|
|
g_mutex_unlock (&enumerator->device_queue_mutex);
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static gboolean
|
|
device_source_dispatch (GSource *source,
|
|
GSourceFunc callback,
|
|
gpointer user_data)
|
|
{
|
|
g_source_set_ready_time (source, -1);
|
|
|
|
return callback (user_data);
|
|
}
|
|
|
|
static GSourceFuncs device_source_funcs =
|
|
{
|
|
.dispatch = device_source_dispatch,
|
|
};
|
|
|
|
static void
|
|
grd_rdp_dvc_camera_enumerator_init (GrdRdpDvcCameraEnumerator *enumerator)
|
|
{
|
|
GSource *device_source;
|
|
|
|
enumerator->device_table =
|
|
g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, g_object_unref);
|
|
enumerator->devices_to_add =
|
|
g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
NULL, device_info_free);
|
|
enumerator->devices_to_remove =
|
|
g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
NULL, device_info_free);
|
|
|
|
g_mutex_init (&enumerator->device_queue_mutex);
|
|
|
|
device_source = g_source_new (&device_source_funcs, sizeof (GSource));
|
|
g_source_set_callback (device_source, manage_devices, enumerator, NULL);
|
|
g_source_set_ready_time (device_source, -1);
|
|
g_source_attach (device_source, NULL);
|
|
enumerator->device_source = device_source;
|
|
}
|
|
|
|
static void
|
|
grd_rdp_dvc_camera_enumerator_class_init (GrdRdpDvcCameraEnumeratorClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GrdRdpDvcClass *dvc_class = GRD_RDP_DVC_CLASS (klass);
|
|
|
|
object_class->dispose = grd_rdp_dvc_camera_enumerator_dispose;
|
|
object_class->finalize = grd_rdp_dvc_camera_enumerator_finalize;
|
|
|
|
dvc_class->maybe_init = grd_rdp_dvc_camera_enumerator_maybe_init;
|
|
}
|