master
c 474 lines 13.9 KB
Raw
1 /*
2 * D-Bus interface for qemu-vnc standalone VNC server.
3 *
4 * Copyright (C) 2026 Red Hat, Inc.
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9 #include "qemu/osdep.h"
10
11 #include "qemu/cutils.h"
12 #include "qapi-types-trace.h"
13 #include "system/system.h"
14 #include "qapi/qapi-types-ui.h"
15 #include "qapi/qapi-commands-ui.h"
16 #include "qemu-vnc.h"
17 #include "qemu-vnc1.h"
18 #include "qapi/qapi-emit-events.h"
19 #include "qobject/qdict.h"
20 #include "ui/vnc.h"
21 #include "trace.h"
22
23 typedef struct VncDbusClient {
24 QemuVnc1ClientSkeleton *skeleton;
25 char *path;
26 char *host;
27 char *service;
28 unsigned int id;
29 QTAILQ_ENTRY(VncDbusClient) next;
30 } VncDbusClient;
31
32 static QemuVnc1ServerSkeleton *server_skeleton;
33 static GDBusObjectManagerServer *obj_manager;
34 static unsigned int next_client_id;
35
36 static QTAILQ_HEAD(, VncDbusClient)
37 dbus_clients = QTAILQ_HEAD_INITIALIZER(dbus_clients);
38
39 static VncDbusClient *vnc_dbus_find_client(const char *host,
40 const char *service)
41 {
42 VncDbusClient *c;
43
44 QTAILQ_FOREACH(c, &dbus_clients, next) {
45 if (g_str_equal(c->host, host) &&
46 g_str_equal(c->service, service)) {
47 return c;
48 }
49 }
50 return NULL;
51 }
52
53 static void vnc_dbus_update_clients_property(void)
54 {
55 VncDbusClient *c;
56 GPtrArray *paths;
57 const char **strv;
58
59 paths = g_ptr_array_new();
60 QTAILQ_FOREACH(c, &dbus_clients, next) {
61 g_ptr_array_add(paths, c->path);
62 }
63 g_ptr_array_add(paths, NULL);
64
65 strv = (const char **)paths->pdata;
66 qemu_vnc1_server_set_clients(QEMU_VNC1_SERVER(server_skeleton), strv);
67 g_ptr_array_free(paths, TRUE);
68 }
69
70 void vnc_dbus_client_connected(const char *host, const char *service,
71 const char *family, bool websocket)
72 {
73 VncDbusClient *c;
74 g_autoptr(GDBusObjectSkeleton) obj = NULL;
75
76 if (!server_skeleton) {
77 return;
78 }
79
80 c = g_new0(VncDbusClient, 1);
81 c->id = next_client_id++;
82 c->host = g_strdup(host);
83 c->service = g_strdup(service);
84 c->path = g_strdup_printf("/org/qemu/Vnc1/Client_%u", c->id);
85
86 c->skeleton = QEMU_VNC1_CLIENT_SKELETON(qemu_vnc1_client_skeleton_new());
87 qemu_vnc1_client_set_host(QEMU_VNC1_CLIENT(c->skeleton), host);
88 qemu_vnc1_client_set_service(QEMU_VNC1_CLIENT(c->skeleton), service);
89 qemu_vnc1_client_set_family(QEMU_VNC1_CLIENT(c->skeleton), family);
90 qemu_vnc1_client_set_web_socket(QEMU_VNC1_CLIENT(c->skeleton), websocket);
91 qemu_vnc1_client_set_x509_dname(QEMU_VNC1_CLIENT(c->skeleton), "");
92 qemu_vnc1_client_set_sasl_username(QEMU_VNC1_CLIENT(c->skeleton), "");
93
94 obj = g_dbus_object_skeleton_new(c->path);
95 g_dbus_object_skeleton_add_interface(
96 obj, G_DBUS_INTERFACE_SKELETON(c->skeleton));
97 g_dbus_object_manager_server_export(obj_manager, obj);
98
99 QTAILQ_INSERT_TAIL(&dbus_clients, c, next);
100 vnc_dbus_update_clients_property();
101
102 qemu_vnc1_server_emit_client_connected(
103 QEMU_VNC1_SERVER(server_skeleton), c->path);
104 }
105
106 void vnc_dbus_client_initialized(const char *host, const char *service,
107 const char *x509_dname,
108 const char *sasl_username)
109 {
110 VncDbusClient *c;
111
112 if (!server_skeleton) {
113 return;
114 }
115
116 c = vnc_dbus_find_client(host, service);
117 if (!c) {
118 trace_qemu_vnc_client_not_found(host, service);
119 return;
120 }
121
122 if (x509_dname) {
123 qemu_vnc1_client_set_x509_dname(
124 QEMU_VNC1_CLIENT(c->skeleton), x509_dname);
125 }
126 if (sasl_username) {
127 qemu_vnc1_client_set_sasl_username(
128 QEMU_VNC1_CLIENT(c->skeleton), sasl_username);
129 }
130
131 qemu_vnc1_server_emit_client_initialized(
132 QEMU_VNC1_SERVER(server_skeleton), c->path);
133 }
134
135 void vnc_dbus_client_disconnected(const char *host, const char *service)
136 {
137 VncDbusClient *c;
138
139 if (!server_skeleton) {
140 return;
141 }
142
143 c = vnc_dbus_find_client(host, service);
144 if (!c) {
145 trace_qemu_vnc_client_not_found(host, service);
146 return;
147 }
148
149 qemu_vnc1_server_emit_client_disconnected(
150 QEMU_VNC1_SERVER(server_skeleton), c->path);
151
152 g_dbus_object_manager_server_unexport(obj_manager, c->path);
153 QTAILQ_REMOVE(&dbus_clients, c, next);
154 vnc_dbus_update_clients_property();
155
156 g_object_unref(c->skeleton);
157 g_free(c->path);
158 g_free(c->host);
159 g_free(c->service);
160 g_free(c);
161 }
162
163 static gboolean
164 on_set_password(QemuVnc1Server *iface,
165 GDBusMethodInvocation *invocation,
166 const gchar *password,
167 gpointer user_data)
168 {
169 Error *err = NULL;
170
171 if (vnc_display_password("default", password, &err) < 0) {
172 g_dbus_method_invocation_return_error(
173 invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
174 "%s", error_get_pretty(err));
175 error_free(err);
176 return TRUE;
177 }
178
179 qemu_vnc1_server_complete_set_password(iface, invocation);
180 return TRUE;
181 }
182
183 static gboolean
184 on_expire_password(QemuVnc1Server *iface,
185 GDBusMethodInvocation *invocation,
186 const gchar *time_str,
187 gpointer user_data)
188 {
189 time_t when;
190
191 if (g_str_equal(time_str, "now")) {
192 when = 0;
193 } else if (g_str_equal(time_str, "never")) {
194 when = TIME_MAX;
195 } else if (time_str[0] == '+') {
196 int seconds;
197 if (qemu_strtoi(time_str + 1, NULL, 10, &seconds) < 0) {
198 g_dbus_method_invocation_return_error(
199 invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
200 "Invalid time format: %s", time_str);
201 return TRUE;
202 }
203 when = time(NULL) + seconds;
204 } else {
205 int64_t epoch;
206 if (qemu_strtoi64(time_str, NULL, 10, &epoch) < 0) {
207 g_dbus_method_invocation_return_error(
208 invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
209 "Invalid time format: %s", time_str);
210 return TRUE;
211 }
212 when = epoch;
213 }
214
215 if (vnc_display_pw_expire("default", when) < 0) {
216 g_dbus_method_invocation_return_error(
217 invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
218 "Failed to set password expiry");
219 return TRUE;
220 }
221
222 qemu_vnc1_server_complete_expire_password(iface, invocation);
223 return TRUE;
224 }
225
226 static gboolean
227 on_reload_certificates(QemuVnc1Server *iface,
228 GDBusMethodInvocation *invocation,
229 gpointer user_data)
230 {
231 Error *err = NULL;
232
233 if (!vnc_display_reload_certs("default", &err)) {
234 g_dbus_method_invocation_return_error(
235 invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
236 "%s", error_get_pretty(err));
237 error_free(err);
238 return TRUE;
239 }
240
241 qemu_vnc1_server_complete_reload_certificates(iface, invocation);
242 return TRUE;
243 }
244
245 static gboolean
246 on_add_client(QemuVnc1Server *iface,
247 GDBusMethodInvocation *invocation,
248 GUnixFDList *fd_list,
249 GVariant *arg_socket,
250 gboolean skipauth,
251 gpointer user_data)
252 {
253 gint32 handle = g_variant_get_handle(arg_socket);
254 g_autoptr(GError) err = NULL;
255 int fd;
256
257 fd = g_unix_fd_list_get(fd_list, handle, &err);
258 if (fd < 0) {
259 g_dbus_method_invocation_return_error(
260 invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
261 "Failed to get fd: %s", err->message);
262 return TRUE;
263 }
264
265 vnc_display_add_client("default", fd, skipauth);
266
267 qemu_vnc1_server_complete_add_client(iface, invocation, NULL);
268 return TRUE;
269 }
270
271 static void vnc_dbus_add_listeners(VncInfo2 *info)
272 {
273 GVariantBuilder builder;
274 VncServerInfo2List *entry;
275
276 g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}"));
277
278 for (entry = info->server; entry; entry = entry->next) {
279 VncServerInfo2 *s = entry->value;
280 const char *vencrypt_str = "";
281
282 if (s->has_vencrypt) {
283 vencrypt_str = VncVencryptSubAuth_str(s->vencrypt);
284 }
285
286 g_variant_builder_open(&builder, G_VARIANT_TYPE("a{sv}"));
287 g_variant_builder_add(&builder, "{sv}", "Host",
288 g_variant_new_string(s->host));
289 g_variant_builder_add(&builder, "{sv}", "Service",
290 g_variant_new_string(s->service));
291 g_variant_builder_add(&builder, "{sv}", "Family",
292 g_variant_new_string(
293 NetworkAddressFamily_str(s->family)));
294 g_variant_builder_add(&builder, "{sv}", "WebSocket",
295 g_variant_new_boolean(s->websocket));
296 g_variant_builder_add(&builder, "{sv}", "Auth",
297 g_variant_new_string(
298 VncPrimaryAuth_str(s->auth)));
299 g_variant_builder_add(&builder, "{sv}", "VencryptSubAuth",
300 g_variant_new_string(vencrypt_str));
301 g_variant_builder_close(&builder);
302 }
303
304 qemu_vnc1_server_set_listeners(
305 QEMU_VNC1_SERVER(server_skeleton),
306 g_variant_builder_end(&builder));
307 }
308
309 void vnc_dbus_setup(GDBusConnection *bus)
310 {
311 g_autoptr(GDBusObjectSkeleton) server_obj = NULL;
312 g_autoptr(VncInfo2List) info_list = NULL;
313 Error *err = NULL;
314 const char *auth_str = "none";
315 const char *vencrypt_str = "";
316
317 obj_manager = g_dbus_object_manager_server_new("/org/qemu/Vnc1");
318
319 server_skeleton = QEMU_VNC1_SERVER_SKELETON(
320 qemu_vnc1_server_skeleton_new());
321
322 qemu_vnc1_server_set_name(QEMU_VNC1_SERVER(server_skeleton),
323 qemu_name ? qemu_name : "");
324 qemu_vnc1_server_set_clients(QEMU_VNC1_SERVER(server_skeleton), NULL);
325
326 /* Query auth info from the VNC display */
327 info_list = qmp_query_vnc_servers(&err);
328 if (info_list) {
329 VncInfo2 *info = info_list->value;
330 auth_str = VncPrimaryAuth_str(info->auth);
331 if (info->has_vencrypt) {
332 vencrypt_str = VncVencryptSubAuth_str(info->vencrypt);
333 }
334 vnc_dbus_add_listeners(info);
335 }
336
337 qemu_vnc1_server_set_auth(QEMU_VNC1_SERVER(server_skeleton), auth_str);
338 qemu_vnc1_server_set_vencrypt_sub_auth(
339 QEMU_VNC1_SERVER(server_skeleton), vencrypt_str);
340
341 g_signal_connect(server_skeleton, "handle-set-password",
342 G_CALLBACK(on_set_password), NULL);
343 g_signal_connect(server_skeleton, "handle-expire-password",
344 G_CALLBACK(on_expire_password), NULL);
345 g_signal_connect(server_skeleton, "handle-reload-certificates",
346 G_CALLBACK(on_reload_certificates), NULL);
347 g_signal_connect(server_skeleton, "handle-add-client",
348 G_CALLBACK(on_add_client), NULL);
349
350 server_obj = g_dbus_object_skeleton_new("/org/qemu/Vnc1/Server");
351 g_dbus_object_skeleton_add_interface(
352 server_obj, G_DBUS_INTERFACE_SKELETON(server_skeleton));
353 g_dbus_object_manager_server_export(obj_manager, server_obj);
354
355 g_dbus_object_manager_server_set_connection(obj_manager, bus);
356
357 if (g_dbus_connection_get_flags(bus)
358 & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION) {
359 g_bus_own_name_on_connection(
360 bus, "org.qemu.vnc",
361 G_BUS_NAME_OWNER_FLAGS_NONE,
362 NULL, NULL, NULL, NULL);
363 }
364 }
365
366 void vnc_action_shutdown(VncState *vs)
367 {
368 VncDbusClient *c;
369
370 c = vnc_dbus_find_client(vs->info->host, vs->info->service);
371 if (!c) {
372 trace_qemu_vnc_client_not_found(vs->info->host, vs->info->service);
373 return;
374 }
375
376 qemu_vnc1_client_emit_shutdown_request(QEMU_VNC1_CLIENT(c->skeleton));
377 }
378
379 void vnc_action_reset(VncState *vs)
380 {
381 VncDbusClient *c;
382
383 c = vnc_dbus_find_client(vs->info->host, vs->info->service);
384 if (!c) {
385 trace_qemu_vnc_client_not_found(vs->info->host, vs->info->service);
386 return;
387 }
388
389 qemu_vnc1_client_emit_reset_request(QEMU_VNC1_CLIENT(c->skeleton));
390 }
391
392 /*
393 * Override the stub qapi_event_emit() to capture VNC events
394 * and forward them to the D-Bus interface.
395 */
396 void qapi_event_emit(QAPIEvent event, QDict *qdict)
397 {
398 QDict *data, *client;
399 const char *host, *service, *family;
400 bool websocket;
401
402 if (event != QAPI_EVENT_VNC_CONNECTED &&
403 event != QAPI_EVENT_VNC_INITIALIZED &&
404 event != QAPI_EVENT_VNC_DISCONNECTED) {
405 return;
406 }
407
408 data = qdict_get_qdict(qdict, "data");
409 if (!data) {
410 return;
411 }
412
413 client = qdict_get_qdict(data, "client");
414 if (!client) {
415 return;
416 }
417
418 host = qdict_get_str(client, "host");
419 service = qdict_get_str(client, "service");
420 family = qdict_get_str(client, "family");
421 websocket = qdict_get_bool(client, "websocket");
422
423 switch (event) {
424 case QAPI_EVENT_VNC_CONNECTED:
425 vnc_dbus_client_connected(host, service, family, websocket);
426 break;
427 case QAPI_EVENT_VNC_INITIALIZED: {
428 const char *x509_dname = NULL;
429 const char *sasl_username = NULL;
430
431 if (qdict_haskey(client, "x509_dname")) {
432 x509_dname = qdict_get_str(client, "x509_dname");
433 }
434 if (qdict_haskey(client, "sasl_username")) {
435 sasl_username = qdict_get_str(client, "sasl_username");
436 }
437 vnc_dbus_client_initialized(host, service,
438 x509_dname, sasl_username);
439 break;
440 }
441 case QAPI_EVENT_VNC_DISCONNECTED:
442 vnc_dbus_client_disconnected(host, service);
443 break;
444 default:
445 break;
446 }
447 }
448
449 void vnc_dbus_emit_leaving(const char *reason)
450 {
451 if (!server_skeleton) {
452 return;
453 }
454
455 qemu_vnc1_server_emit_leaving(QEMU_VNC1_SERVER(server_skeleton), reason);
456 }
457
458 void vnc_dbus_cleanup(void)
459 {
460 VncDbusClient *c, *next;
461
462 QTAILQ_FOREACH_SAFE(c, &dbus_clients, next, next) {
463 g_dbus_object_manager_server_unexport(obj_manager, c->path);
464 QTAILQ_REMOVE(&dbus_clients, c, next);
465 g_object_unref(c->skeleton);
466 g_free(c->path);
467 g_free(c->host);
468 g_free(c->service);
469 g_free(c);
470 }
471
472 g_clear_object(&server_skeleton);
473 g_clear_object(&obj_manager);
474 }