/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 char magic[32] = "**GENERIC_DYNVC_PLUGIN_PRIVATE*"; |
27 | | static const size_t maxPrivateOffset = 0x100000; /* 1MB should not be reached. */ |
28 | | |
29 | | typedef struct |
30 | | { |
31 | | char magic[32]; |
32 | | size_t pluginSize; |
33 | | DWORD flags; |
34 | | BOOL connected; |
35 | | } GENERIC_DYNVC_PLUGIN_PRIVATE; |
36 | | |
37 | | WINPR_ATTR_NODISCARD |
38 | | static GENERIC_DYNVC_PLUGIN_PRIVATE* getFromGeneric(GENERIC_DYNVC_PLUGIN* plugin) |
39 | 10.9k | { |
40 | 10.9k | if (!plugin) |
41 | 0 | return nullptr; |
42 | | |
43 | | /* We do not know the exact size used to allocate this struct (+ some channel custom data |
44 | | * afterward) but we know that the private segment starts with a certain magic. |
45 | | * |
46 | | * So search for the magic from the end of GENERIC_DYNVC_PLUGIN until found. |
47 | | */ |
48 | 10.9k | char* ptr = (char*)(&plugin[1]); |
49 | | |
50 | 2.24G | for (size_t count = 0; count <= maxPrivateOffset; count++, ptr++) |
51 | 2.24G | { |
52 | | /* Only bytes matching the first element of the magic pattern get the full memcmp. A |
53 | | * byte that matches it alone is skipped like any other. */ |
54 | 2.24G | if ((*ptr == magic[0]) && (memcmp(ptr, magic, sizeof(magic)) == 0)) |
55 | 10.9k | return WINPR_PACKED_ALIGN_CAST(GENERIC_DYNVC_PLUGIN_PRIVATE*, ptr); |
56 | 2.24G | } |
57 | | |
58 | 0 | WLog_ERR(TAG, |
59 | 0 | "Failed to find GENERIC_DYNVC_PLUGIN_PRIVATE offset in the first %" PRIuz |
60 | 0 | " bytes after GENERIC_DYNVC_PLUGIN, giving up.", |
61 | 0 | maxPrivateOffset); |
62 | 0 | WINPR_ASSERT(FALSE); |
63 | 0 | return nullptr; |
64 | 10.9k | } |
65 | | |
66 | | WINPR_ATTR_NODISCARD |
67 | | static BOOL generic_connected_allow_multiple(GENERIC_DYNVC_PLUGIN* plugin) |
68 | 2.73k | { |
69 | 2.73k | GENERIC_DYNVC_PLUGIN_PRIVATE* priv = getFromGeneric(plugin); |
70 | 2.73k | if (!priv) |
71 | 0 | return FALSE; |
72 | 2.73k | return (priv->flags & GENERIC_CHANNEL_ENTRY_OPTION_MULTI_INSTANCE) != 0; |
73 | 2.73k | } |
74 | | |
75 | | static BOOL generic_update_connected_state(GENERIC_DYNVC_PLUGIN* plugin, BOOL val) |
76 | 5.46k | { |
77 | 5.46k | GENERIC_DYNVC_PLUGIN_PRIVATE* priv = getFromGeneric(plugin); |
78 | 5.46k | if (!priv) |
79 | 0 | return FALSE; |
80 | 5.46k | priv->connected = val; |
81 | 5.46k | return TRUE; |
82 | 5.46k | } |
83 | | |
84 | | WINPR_ATTR_NODISCARD |
85 | | static BOOL generic_get_connected_state(GENERIC_DYNVC_PLUGIN* plugin) |
86 | 2.73k | { |
87 | 2.73k | GENERIC_DYNVC_PLUGIN_PRIVATE* priv = getFromGeneric(plugin); |
88 | 2.73k | if (!priv) |
89 | 0 | return FALSE; |
90 | 2.73k | return priv->connected; |
91 | 2.73k | } |
92 | | |
93 | | static UINT generic_on_close(IWTSVirtualChannelCallback* pChannelCallback) |
94 | 2.73k | { |
95 | 2.73k | GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; |
96 | 2.73k | WINPR_ASSERT(callback); |
97 | | |
98 | 2.73k | GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)callback->plugin; |
99 | 2.73k | WINPR_ASSERT(plugin); |
100 | | |
101 | | /* We're wrapping the originally set callback to update the channel state. |
102 | | * call the originally intended OnClose from the plugin |
103 | | */ |
104 | 2.73k | WINPR_ASSERT(plugin->channel_callbacks); |
105 | 2.73k | const UINT rc = |
106 | 2.73k | IFCALLRESULT(CHANNEL_RC_OK, plugin->channel_callbacks->OnClose, pChannelCallback); |
107 | 2.73k | generic_update_connected_state(plugin, FALSE); |
108 | | |
109 | 2.73k | return rc; |
110 | 2.73k | } |
111 | | |
112 | | static UINT generic_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, |
113 | | IWTSVirtualChannel* pChannel, |
114 | | WINPR_ATTR_UNUSED BYTE* Data, |
115 | | WINPR_ATTR_UNUSED BOOL* pbAccept, |
116 | | IWTSVirtualChannelCallback** ppCallback) |
117 | 2.73k | { |
118 | 2.73k | GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback; |
119 | | |
120 | 2.73k | if (!listener_callback || !listener_callback->plugin) |
121 | 0 | return ERROR_INTERNAL_ERROR; |
122 | | |
123 | 2.73k | GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)listener_callback->plugin; |
124 | 2.73k | WLog_Print(plugin->log, WLOG_TRACE, "..."); |
125 | | |
126 | 2.73k | if (!generic_connected_allow_multiple(plugin) && generic_get_connected_state(plugin)) |
127 | 0 | { |
128 | 0 | WLog_Print(plugin->log, WLOG_ERROR, "Channel already connected, terminating."); |
129 | 0 | return ERROR_DEVICE_ALREADY_ATTACHED; |
130 | 0 | } |
131 | | |
132 | 2.73k | GENERIC_CHANNEL_CALLBACK* callback = |
133 | 2.73k | (GENERIC_CHANNEL_CALLBACK*)calloc(1, plugin->channelCallbackSize); |
134 | 2.73k | if (!callback) |
135 | 0 | { |
136 | 0 | WLog_Print(plugin->log, WLOG_ERROR, "calloc failed!"); |
137 | 0 | return CHANNEL_RC_NO_MEMORY; |
138 | 0 | } |
139 | | |
140 | | /* implant configured channel callbacks */ |
141 | 2.73k | callback->iface = *plugin->channel_callbacks; |
142 | 2.73k | callback->iface.OnClose = generic_on_close; |
143 | | |
144 | 2.73k | callback->plugin = listener_callback->plugin; |
145 | 2.73k | callback->channel_mgr = listener_callback->channel_mgr; |
146 | 2.73k | callback->channel = pChannel; |
147 | | |
148 | 2.73k | listener_callback->channel_callback = callback; |
149 | 2.73k | listener_callback->channel = pChannel; |
150 | | |
151 | 2.73k | *ppCallback = &callback->iface; |
152 | 2.73k | generic_update_connected_state(plugin, TRUE); |
153 | 2.73k | return CHANNEL_RC_OK; |
154 | 2.73k | } |
155 | | |
156 | | static UINT generic_dynvc_plugin_initialize(IWTSPlugin* pPlugin, |
157 | | IWTSVirtualChannelManager* pChannelMgr) |
158 | 2.73k | { |
159 | 2.73k | UINT rc = 0; |
160 | 2.73k | GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin; |
161 | | |
162 | 2.73k | if (!plugin) |
163 | 0 | return CHANNEL_RC_BAD_CHANNEL_HANDLE; |
164 | | |
165 | 2.73k | if (!pChannelMgr) |
166 | 0 | return ERROR_INVALID_PARAMETER; |
167 | | |
168 | 2.73k | if (plugin->initialized) |
169 | 0 | { |
170 | 0 | WLog_ERR(TAG, "[%s] channel initialized twice, aborting", plugin->dynvc_name); |
171 | 0 | return ERROR_INVALID_DATA; |
172 | 0 | } |
173 | | |
174 | 2.73k | WLog_Print(plugin->log, WLOG_TRACE, "..."); |
175 | 2.73k | GENERIC_LISTENER_CALLBACK* listener_callback = |
176 | 2.73k | (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK)); |
177 | 2.73k | if (!listener_callback) |
178 | 0 | { |
179 | 0 | WLog_Print(plugin->log, WLOG_ERROR, "calloc failed!"); |
180 | 0 | return CHANNEL_RC_NO_MEMORY; |
181 | 0 | } |
182 | | |
183 | 2.73k | plugin->listener_callback = listener_callback; |
184 | 2.73k | listener_callback->iface.OnNewChannelConnection = generic_on_new_channel_connection; |
185 | 2.73k | listener_callback->plugin = pPlugin; |
186 | 2.73k | listener_callback->channel_mgr = pChannelMgr; |
187 | 2.73k | rc = pChannelMgr->CreateListener(pChannelMgr, plugin->dynvc_name, 0, &listener_callback->iface, |
188 | 2.73k | &plugin->listener); |
189 | | |
190 | 2.73k | plugin->listener->pInterface = plugin->iface.pInterface; |
191 | 2.73k | plugin->initialized = (rc == CHANNEL_RC_OK); |
192 | 2.73k | return rc; |
193 | 2.73k | } |
194 | | |
195 | | static UINT generic_plugin_terminated(IWTSPlugin* pPlugin) |
196 | 2.73k | { |
197 | 2.73k | GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin; |
198 | 2.73k | UINT error = CHANNEL_RC_OK; |
199 | | |
200 | 2.73k | if (!plugin) |
201 | 0 | return CHANNEL_RC_BAD_CHANNEL_HANDLE; |
202 | | |
203 | 2.73k | WLog_Print(plugin->log, WLOG_TRACE, "..."); |
204 | | |
205 | | /* some channels (namely rdpei), look at initialized to see if they should continue to run */ |
206 | 2.73k | plugin->initialized = FALSE; |
207 | | |
208 | 2.73k | if (plugin->terminatePluginFn) |
209 | 2.73k | plugin->terminatePluginFn(plugin); |
210 | | |
211 | 2.73k | if (plugin->listener_callback) |
212 | 2.73k | { |
213 | 2.73k | IWTSVirtualChannelManager* mgr = plugin->listener_callback->channel_mgr; |
214 | 2.73k | if (mgr) |
215 | 2.73k | IFCALL(mgr->DestroyListener, mgr, plugin->listener); |
216 | 2.73k | } |
217 | | |
218 | 2.73k | free(plugin->listener_callback); |
219 | 2.73k | free(plugin->dynvc_name); |
220 | 2.73k | free(plugin); |
221 | 2.73k | return error; |
222 | 2.73k | } |
223 | | |
224 | | static UINT generic_dynvc_plugin_attached(IWTSPlugin* pPlugin) |
225 | 0 | { |
226 | 0 | GENERIC_DYNVC_PLUGIN* pluginn = (GENERIC_DYNVC_PLUGIN*)pPlugin; |
227 | 0 | UINT error = CHANNEL_RC_OK; |
228 | |
|
229 | 0 | if (!pluginn) |
230 | 0 | return CHANNEL_RC_BAD_CHANNEL_HANDLE; |
231 | | |
232 | 0 | pluginn->attached = TRUE; |
233 | 0 | return error; |
234 | 0 | } |
235 | | |
236 | | static UINT generic_dynvc_plugin_detached(IWTSPlugin* pPlugin) |
237 | 0 | { |
238 | 0 | GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin; |
239 | 0 | UINT error = CHANNEL_RC_OK; |
240 | |
|
241 | 0 | if (!plugin) |
242 | 0 | return CHANNEL_RC_BAD_CHANNEL_HANDLE; |
243 | | |
244 | 0 | plugin->attached = FALSE; |
245 | 0 | return error; |
246 | 0 | } |
247 | | |
248 | | UINT freerdp_generic_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints, const char* logTag, |
249 | | const char* name, size_t pluginSize, size_t channelCallbackSize, |
250 | | const IWTSVirtualChannelCallback* channel_callbacks, |
251 | | DYNVC_PLUGIN_INIT_FN initPluginFn, |
252 | | DYNVC_PLUGIN_TERMINATE_FN terminatePluginFn) |
253 | 2.73k | { |
254 | 2.73k | return freerdp_generic_DVCPluginEntryWithOptions(pEntryPoints, logTag, name, pluginSize, |
255 | 2.73k | channelCallbackSize, channel_callbacks, |
256 | 2.73k | initPluginFn, terminatePluginFn, 0); |
257 | 2.73k | } |
258 | | |
259 | | UINT freerdp_generic_DVCPluginEntryWithOptions( |
260 | | IDRDYNVC_ENTRY_POINTS* pEntryPoints, const char* logTag, const char* name, size_t pluginSize, |
261 | | size_t channelCallbackSize, const IWTSVirtualChannelCallback* channel_callbacks, |
262 | | DYNVC_PLUGIN_INIT_FN initPluginFn, DYNVC_PLUGIN_TERMINATE_FN terminatePluginFn, UINT32 flags) |
263 | 2.73k | { |
264 | 2.73k | UINT error = CHANNEL_RC_INITIALIZATION_ERROR; |
265 | | |
266 | 2.73k | WINPR_ASSERT(pEntryPoints); |
267 | 2.73k | WINPR_ASSERT(pEntryPoints->GetPlugin); |
268 | 2.73k | WINPR_ASSERT(logTag); |
269 | 2.73k | WINPR_ASSERT(name); |
270 | 2.73k | WINPR_ASSERT(pluginSize >= sizeof(GENERIC_DYNVC_PLUGIN)); |
271 | 2.73k | WINPR_ASSERT(channelCallbackSize >= sizeof(GENERIC_CHANNEL_CALLBACK)); |
272 | | |
273 | 2.73k | GENERIC_DYNVC_PLUGIN* plugin = |
274 | 2.73k | (GENERIC_DYNVC_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, name); |
275 | 2.73k | if (plugin != nullptr) |
276 | 0 | return CHANNEL_RC_ALREADY_INITIALIZED; |
277 | | |
278 | | /* Ensure 32 byte alignment. |
279 | | */ |
280 | 2.73k | size_t size = pluginSize; |
281 | 2.73k | if ((size % 32) != 0) |
282 | 2.73k | size += 32 - size % 32; |
283 | 2.73k | size += sizeof(GENERIC_DYNVC_PLUGIN_PRIVATE); |
284 | 2.73k | plugin = (GENERIC_DYNVC_PLUGIN*)calloc(1, size); |
285 | 2.73k | if (!plugin) |
286 | 0 | { |
287 | 0 | WLog_ERR(TAG, "calloc failed!"); |
288 | 0 | return CHANNEL_RC_NO_MEMORY; |
289 | 0 | } |
290 | | |
291 | 2.73k | BYTE* ptr = &(((BYTE*)plugin)[size - sizeof(GENERIC_DYNVC_PLUGIN_PRIVATE)]); |
292 | 2.73k | GENERIC_DYNVC_PLUGIN_PRIVATE* priv = |
293 | 2.73k | WINPR_PACKED_ALIGN_CAST(GENERIC_DYNVC_PLUGIN_PRIVATE*, ptr); |
294 | 2.73k | if (!priv) |
295 | 0 | goto error; |
296 | | |
297 | 2.73k | WINPR_STATIC_ASSERT(sizeof(magic) == sizeof(priv->magic)); |
298 | 2.73k | memcpy(priv->magic, magic, sizeof(priv->magic)); |
299 | 2.73k | priv->pluginSize = pluginSize; |
300 | 2.73k | priv->flags = flags; |
301 | | |
302 | 2.73k | plugin->log = WLog_Get(logTag); |
303 | 2.73k | plugin->attached = TRUE; |
304 | 2.73k | plugin->channel_callbacks = channel_callbacks; |
305 | 2.73k | plugin->channelCallbackSize = channelCallbackSize; |
306 | 2.73k | plugin->iface.Initialize = generic_dynvc_plugin_initialize; |
307 | 2.73k | plugin->iface.Connected = nullptr; |
308 | 2.73k | plugin->iface.Disconnected = nullptr; |
309 | 2.73k | plugin->iface.Terminated = generic_plugin_terminated; |
310 | 2.73k | plugin->iface.Attached = generic_dynvc_plugin_attached; |
311 | 2.73k | plugin->iface.Detached = generic_dynvc_plugin_detached; |
312 | 2.73k | plugin->terminatePluginFn = terminatePluginFn; |
313 | | |
314 | 2.73k | if (initPluginFn) |
315 | 2.73k | { |
316 | 2.73k | rdpSettings* settings = pEntryPoints->GetRdpSettings(pEntryPoints); |
317 | 2.73k | rdpContext* context = pEntryPoints->GetRdpContext(pEntryPoints); |
318 | | |
319 | 2.73k | error = initPluginFn(plugin, context, settings); |
320 | 2.73k | if (error != CHANNEL_RC_OK) |
321 | 0 | goto error; |
322 | 2.73k | } |
323 | | |
324 | 2.73k | plugin->dynvc_name = _strdup(name); |
325 | 2.73k | if (!plugin->dynvc_name) |
326 | 0 | goto error; |
327 | | |
328 | 2.73k | error = pEntryPoints->RegisterPlugin(pEntryPoints, name, &plugin->iface); |
329 | 2.73k | if (error == CHANNEL_RC_OK) |
330 | 2.73k | return error; |
331 | | |
332 | 0 | error: |
333 | 0 | generic_plugin_terminated(&plugin->iface); |
334 | 0 | return error; |
335 | 2.73k | } |