Coverage Report

Created: 2026-10-15 06:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/channels/client/generic_dynvc.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * Dynamic channel
4
 *
5
 * Copyright 2022 David Fort <contact@hardening-consulting.com>
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
20
#include <freerdp/config.h>
21
#include <freerdp/log.h>
22
#include <freerdp/client/channels.h>
23
24
#define TAG FREERDP_TAG("genericdynvc")
25
26
static BOOL generic_update_connected_state(GENERIC_DYNVC_PLUGIN* plugin, BOOL val)
27
0
{
28
0
  if (!plugin || !plugin->dynvc_name)
29
0
    return FALSE;
30
0
  const size_t len = strlen(plugin->dynvc_name);
31
0
  plugin->dynvc_name[len + 1] = (char)val;
32
0
  return TRUE;
33
0
}
34
35
WINPR_ATTR_NODISCARD
36
static BOOL generic_get_connected_state(GENERIC_DYNVC_PLUGIN* plugin)
37
0
{
38
0
  if (!plugin || !plugin->dynvc_name)
39
0
    return FALSE;
40
0
  const size_t len = strlen(plugin->dynvc_name);
41
0
  return plugin->dynvc_name[len + 1];
42
0
}
43
44
static UINT generic_on_close(IWTSVirtualChannelCallback* pChannelCallback)
45
0
{
46
0
  GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
47
0
  WINPR_ASSERT(callback);
48
49
0
  GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)callback->plugin;
50
0
  WINPR_ASSERT(plugin);
51
52
  /* We're wrapping the originally set callback to update the channel state.
53
   * call the originally intended OnClose from the plugin
54
   */
55
0
  WINPR_ASSERT(plugin->channel_callbacks);
56
0
  const UINT rc =
57
0
      IFCALLRESULT(CHANNEL_RC_OK, plugin->channel_callbacks->OnClose, pChannelCallback);
58
0
  generic_update_connected_state(plugin, FALSE);
59
60
0
  return rc;
61
0
}
62
63
static UINT generic_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
64
                                              IWTSVirtualChannel* pChannel,
65
                                              WINPR_ATTR_UNUSED BYTE* Data,
66
                                              WINPR_ATTR_UNUSED BOOL* pbAccept,
67
                                              IWTSVirtualChannelCallback** ppCallback)
68
0
{
69
0
  GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback;
70
71
0
  if (!listener_callback || !listener_callback->plugin)
72
0
    return ERROR_INTERNAL_ERROR;
73
74
0
  GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)listener_callback->plugin;
75
0
  WLog_Print(plugin->log, WLOG_TRACE, "...");
76
77
0
  if (generic_get_connected_state(plugin))
78
0
  {
79
0
    WLog_Print(plugin->log, WLOG_ERROR, "Channel already connected, terminating.");
80
0
    return ERROR_DEVICE_ALREADY_ATTACHED;
81
0
  }
82
83
0
  GENERIC_CHANNEL_CALLBACK* callback =
84
0
      (GENERIC_CHANNEL_CALLBACK*)calloc(1, plugin->channelCallbackSize);
85
0
  if (!callback)
86
0
  {
87
0
    WLog_Print(plugin->log, WLOG_ERROR, "calloc failed!");
88
0
    return CHANNEL_RC_NO_MEMORY;
89
0
  }
90
91
  /* implant configured channel callbacks */
92
0
  callback->iface = *plugin->channel_callbacks;
93
0
  callback->iface.OnClose = generic_on_close;
94
95
0
  callback->plugin = listener_callback->plugin;
96
0
  callback->channel_mgr = listener_callback->channel_mgr;
97
0
  callback->channel = pChannel;
98
99
0
  listener_callback->channel_callback = callback;
100
0
  listener_callback->channel = pChannel;
101
102
0
  *ppCallback = &callback->iface;
103
0
  generic_update_connected_state(plugin, TRUE);
104
0
  return CHANNEL_RC_OK;
105
0
}
106
107
static UINT generic_dynvc_plugin_initialize(IWTSPlugin* pPlugin,
108
                                            IWTSVirtualChannelManager* pChannelMgr)
109
0
{
110
0
  UINT rc = 0;
111
0
  GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin;
112
113
0
  if (!plugin)
114
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
115
116
0
  if (!pChannelMgr)
117
0
    return ERROR_INVALID_PARAMETER;
118
119
0
  if (plugin->initialized)
120
0
  {
121
0
    WLog_ERR(TAG, "[%s] channel initialized twice, aborting", plugin->dynvc_name);
122
0
    return ERROR_INVALID_DATA;
123
0
  }
124
125
0
  WLog_Print(plugin->log, WLOG_TRACE, "...");
126
0
  GENERIC_LISTENER_CALLBACK* listener_callback =
127
0
      (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK));
128
0
  if (!listener_callback)
129
0
  {
130
0
    WLog_Print(plugin->log, WLOG_ERROR, "calloc failed!");
131
0
    return CHANNEL_RC_NO_MEMORY;
132
0
  }
133
134
0
  plugin->listener_callback = listener_callback;
135
0
  listener_callback->iface.OnNewChannelConnection = generic_on_new_channel_connection;
136
0
  listener_callback->plugin = pPlugin;
137
0
  listener_callback->channel_mgr = pChannelMgr;
138
0
  rc = pChannelMgr->CreateListener(pChannelMgr, plugin->dynvc_name, 0, &listener_callback->iface,
139
0
                                   &plugin->listener);
140
141
0
  plugin->listener->pInterface = plugin->iface.pInterface;
142
0
  plugin->initialized = (rc == CHANNEL_RC_OK);
143
0
  return rc;
144
0
}
145
146
static UINT generic_plugin_terminated(IWTSPlugin* pPlugin)
147
0
{
148
0
  GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin;
149
0
  UINT error = CHANNEL_RC_OK;
150
151
0
  if (!plugin)
152
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
153
154
0
  WLog_Print(plugin->log, WLOG_TRACE, "...");
155
156
  /* some channels (namely rdpei), look at initialized to see if they should continue to run */
157
0
  plugin->initialized = FALSE;
158
159
0
  if (plugin->terminatePluginFn)
160
0
    plugin->terminatePluginFn(plugin);
161
162
0
  if (plugin->listener_callback)
163
0
  {
164
0
    IWTSVirtualChannelManager* mgr = plugin->listener_callback->channel_mgr;
165
0
    if (mgr)
166
0
      IFCALL(mgr->DestroyListener, mgr, plugin->listener);
167
0
  }
168
169
0
  free(plugin->listener_callback);
170
0
  free(plugin->dynvc_name);
171
0
  free(plugin);
172
0
  return error;
173
0
}
174
175
static UINT generic_dynvc_plugin_attached(IWTSPlugin* pPlugin)
176
0
{
177
0
  GENERIC_DYNVC_PLUGIN* pluginn = (GENERIC_DYNVC_PLUGIN*)pPlugin;
178
0
  UINT error = CHANNEL_RC_OK;
179
180
0
  if (!pluginn)
181
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
182
183
0
  pluginn->attached = TRUE;
184
0
  return error;
185
0
}
186
187
static UINT generic_dynvc_plugin_detached(IWTSPlugin* pPlugin)
188
0
{
189
0
  GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin;
190
0
  UINT error = CHANNEL_RC_OK;
191
192
0
  if (!plugin)
193
0
    return CHANNEL_RC_BAD_CHANNEL_HANDLE;
194
195
0
  plugin->attached = FALSE;
196
0
  return error;
197
0
}
198
199
UINT freerdp_generic_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints, const char* logTag,
200
                                    const char* name, size_t pluginSize, size_t channelCallbackSize,
201
                                    const IWTSVirtualChannelCallback* channel_callbacks,
202
                                    DYNVC_PLUGIN_INIT_FN initPluginFn,
203
                                    DYNVC_PLUGIN_TERMINATE_FN terminatePluginFn)
204
0
{
205
0
  GENERIC_DYNVC_PLUGIN* plugin = nullptr;
206
0
  UINT error = CHANNEL_RC_INITIALIZATION_ERROR;
207
208
0
  WINPR_ASSERT(pEntryPoints);
209
0
  WINPR_ASSERT(pEntryPoints->GetPlugin);
210
0
  WINPR_ASSERT(logTag);
211
0
  WINPR_ASSERT(name);
212
0
  WINPR_ASSERT(pluginSize >= sizeof(*plugin));
213
0
  WINPR_ASSERT(channelCallbackSize >= sizeof(GENERIC_CHANNEL_CALLBACK));
214
215
0
  plugin = (GENERIC_DYNVC_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, name);
216
0
  if (plugin != nullptr)
217
0
    return CHANNEL_RC_ALREADY_INITIALIZED;
218
219
0
  plugin = (GENERIC_DYNVC_PLUGIN*)calloc(1, pluginSize);
220
0
  if (!plugin)
221
0
  {
222
0
    WLog_ERR(TAG, "calloc failed!");
223
0
    return CHANNEL_RC_NO_MEMORY;
224
0
  }
225
226
0
  plugin->log = WLog_Get(logTag);
227
0
  plugin->attached = TRUE;
228
0
  plugin->channel_callbacks = channel_callbacks;
229
0
  plugin->channelCallbackSize = channelCallbackSize;
230
0
  plugin->iface.Initialize = generic_dynvc_plugin_initialize;
231
0
  plugin->iface.Connected = nullptr;
232
0
  plugin->iface.Disconnected = nullptr;
233
0
  plugin->iface.Terminated = generic_plugin_terminated;
234
0
  plugin->iface.Attached = generic_dynvc_plugin_attached;
235
0
  plugin->iface.Detached = generic_dynvc_plugin_detached;
236
0
  plugin->terminatePluginFn = terminatePluginFn;
237
238
0
  if (initPluginFn)
239
0
  {
240
0
    rdpSettings* settings = pEntryPoints->GetRdpSettings(pEntryPoints);
241
0
    rdpContext* context = pEntryPoints->GetRdpContext(pEntryPoints);
242
243
0
    error = initPluginFn(plugin, context, settings);
244
0
    if (error != CHANNEL_RC_OK)
245
0
      goto error;
246
0
  }
247
248
  /* Since we do not have a private struct append the connected state after the name of the
249
   * channel */
250
0
  const size_t len = strlen(name);
251
0
  plugin->dynvc_name = calloc(len + 2, sizeof(char));
252
0
  if (!plugin->dynvc_name)
253
0
    goto error;
254
0
  strncpy(plugin->dynvc_name, name, len);
255
256
0
  error = pEntryPoints->RegisterPlugin(pEntryPoints, name, &plugin->iface);
257
0
  if (error == CHANNEL_RC_OK)
258
0
    return error;
259
260
0
error:
261
0
  generic_plugin_terminated(&plugin->iface);
262
0
  return error;
263
0
}