1 /*
2 * Virt Viewer: A virtual machine console viewer
3 *
4 * Copyright (C) 2007-2012 Red Hat, Inc.
5 * Copyright (C) 2009-2012 Daniel P. Berrange
6 * Copyright (C) 2010 Marc-André Lureau
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 *
22 * Author: Daniel P. Berrange <berrange@redhat.com>
23 */
24
25 #include <config.h>
26
27 #include <gdk/gdkkeysyms.h>
28 #include <gtk/gtk.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <locale.h>
35 #include <gio/gio.h>
36 #include <glib/gprintf.h>
37 #include <glib/gi18n.h>
38
39 #include <libvirt/libvirt.h>
40 #include <libvirt/virterror.h>
41 #include <libvirt-glib/libvirt-glib.h>
42 #include <libxml/xpath.h>
43 #include <libxml/uri.h>
44
45 #ifndef G_OS_WIN32
46 #include <sys/socket.h>
47 #endif
48
49 #include "virt-viewer.h"
50 #include "virt-viewer-app.h"
51 #include "virt-viewer-vm-connection.h"
52 #include "virt-viewer-auth.h"
53 #include "virt-viewer-util.h"
54
55 #ifdef HAVE_SPICE_GTK
56 #include "virt-viewer-session-spice.h"
57 #endif
58
59 struct _VirtViewer {
60 VirtViewerApp parent;
61 char *uri;
62 virConnectPtr conn;
63 virDomainPtr dom;
64 char *domkey;
65 gboolean waitvm;
66 gboolean reconnect;
67 gboolean auth_cancelled;
68 gint domain_event;
69 guint reconnect_poll; /* source id */
70 };
71
72 G_DEFINE_TYPE(VirtViewer, virt_viewer, VIRT_VIEWER_TYPE_APP)
73
74 static gboolean virt_viewer_initial_connect(VirtViewerApp *self, GError **error);
75 static gboolean virt_viewer_open_connection(VirtViewerApp *self, int *fd);
76 static void virt_viewer_deactivated(VirtViewerApp *self, gboolean connect_error);
77 static gboolean virt_viewer_start(VirtViewerApp *self, GError **error);
78 static void virt_viewer_dispose (GObject *object);
79 static int virt_viewer_connect(VirtViewerApp *app, GError **error);
80
81 static gchar **opt_args = NULL;
82 static gchar *opt_uri = NULL;
83 static gboolean opt_direct = FALSE;
84 static gboolean opt_attach = FALSE;
85 static gboolean opt_waitvm = FALSE;
86 static gboolean opt_reconnect = FALSE;
87 static gboolean opt_shared = FALSE;
88
89 typedef enum {
90 DOMAIN_SELECTION_ID = (1 << 0),
91 DOMAIN_SELECTION_UUID = (1 << 1),
92 DOMAIN_SELECTION_NAME = (1 << 2),
93 DOMAIN_SELECTION_DEFAULT = DOMAIN_SELECTION_ID | DOMAIN_SELECTION_UUID | DOMAIN_SELECTION_NAME,
94 } DomainSelection;
95
96 static const gchar* domain_selection_to_opt[] = {
97 [DOMAIN_SELECTION_ID] = "--id",
98 [DOMAIN_SELECTION_UUID] = "--uuid",
99 [DOMAIN_SELECTION_NAME] = "--domain-name",
100 };
101
102 static DomainSelection domain_selection_type = DOMAIN_SELECTION_DEFAULT;
103
104 static gboolean
opt_domain_selection_cb(const gchar * option_name,const gchar * value G_GNUC_UNUSED,gpointer data G_GNUC_UNUSED,GError ** error)105 opt_domain_selection_cb(const gchar *option_name,
106 const gchar *value G_GNUC_UNUSED,
107 gpointer data G_GNUC_UNUSED,
108 GError **error)
109 {
110 guint i;
111 if (domain_selection_type != DOMAIN_SELECTION_DEFAULT) {
112 g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
113 "selection type has been already set");
114 return FALSE;
115 }
116
117 for (i = DOMAIN_SELECTION_ID; i < G_N_ELEMENTS(domain_selection_to_opt); i++) {
118 if (g_strcmp0(option_name, domain_selection_to_opt[i]) == 0) {
119 domain_selection_type = i;
120 return TRUE;
121 }
122 }
123
124 g_assert_not_reached();
125 return FALSE;
126 }
127
128 static void
virt_viewer_add_option_entries(VirtViewerApp * self,GOptionContext * context,GOptionGroup * group)129 virt_viewer_add_option_entries(VirtViewerApp *self, GOptionContext *context, GOptionGroup *group)
130 {
131 static const GOptionEntry options[] = {
132 { "direct", 'd', 0, G_OPTION_ARG_NONE, &opt_direct,
133 N_("Direct connection with no automatic tunnels"), NULL },
134 { "attach", 'a', 0, G_OPTION_ARG_NONE, &opt_attach,
135 N_("Attach to the local display using libvirt"), NULL },
136 { "connect", 'c', 0, G_OPTION_ARG_STRING, &opt_uri,
137 N_("Connect to hypervisor"), "URI"},
138 { "wait", 'w', 0, G_OPTION_ARG_NONE, &opt_waitvm,
139 N_("Wait for domain to start"), NULL },
140 { "reconnect", 'r', 0, G_OPTION_ARG_NONE, &opt_reconnect,
141 N_("Reconnect to domain upon restart"), NULL },
142 { "domain-name", '\0', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_domain_selection_cb,
143 N_("Select the virtual machine only by its name"), NULL },
144 { "id", '\0', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_domain_selection_cb,
145 N_("Select the virtual machine only by its ID"), NULL },
146 { "uuid", '\0', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_domain_selection_cb,
147 N_("Select the virtual machine only by its UUID"), NULL },
148 { "shared", 's', 0, G_OPTION_ARG_NONE, &opt_shared,
149 N_("Share client session"), NULL },
150 { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_STRING_ARRAY, &opt_args,
151 NULL, "-- ID|UUID|DOMAIN-NAME" },
152 { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
153 };
154
155 VIRT_VIEWER_APP_CLASS(virt_viewer_parent_class)->add_option_entries(self, context, group);
156 g_option_context_set_summary(context, _("Virtual machine graphical console"));
157 g_option_group_add_entries(group, options);
158 }
159
160 static gboolean
virt_viewer_local_command_line(GApplication * gapp,gchar *** args,int * status)161 virt_viewer_local_command_line (GApplication *gapp,
162 gchar ***args,
163 int *status)
164 {
165 gboolean ret = FALSE;
166 VirtViewer *self = VIRT_VIEWER(gapp);
167 VirtViewerApp *app = VIRT_VIEWER_APP(gapp);
168
169 ret = G_APPLICATION_CLASS(virt_viewer_parent_class)->local_command_line(gapp, args, status);
170 if (ret)
171 goto end;
172
173 if (opt_args) {
174 if (g_strv_length(opt_args) != 1) {
175 g_printerr(_("\nUsage: %s [OPTIONS] [ID|UUID|DOMAIN-NAME]\n\n"), PACKAGE);
176 ret = TRUE;
177 *status = 1;
178 goto end;
179 }
180
181 self->domkey = g_strdup(opt_args[0]);
182 }
183
184
185 if (opt_waitvm || domain_selection_type != DOMAIN_SELECTION_DEFAULT) {
186 if (!self->domkey) {
187 g_printerr(_("\nNo ID|UUID|DOMAIN-NAME was specified for '%s'\n\n"),
188 opt_waitvm ? "--wait" : domain_selection_to_opt[domain_selection_type]);
189 ret = TRUE;
190 *status = 1;
191 goto end;
192 }
193
194 self->waitvm = opt_waitvm;
195 }
196
197 virt_viewer_app_set_direct(app, opt_direct);
198 virt_viewer_app_set_attach(app, opt_attach);
199 virt_viewer_app_set_shared(app, opt_shared);
200 self->reconnect = opt_reconnect;
201 self->uri = g_strdup(opt_uri);
202
203 end:
204 if (ret && *status)
205 g_printerr(_("Run '%s --help' to see a full list of available command line options\n"), g_get_prgname());
206
207 g_strfreev(opt_args);
208 g_free(opt_uri);
209 return ret;
210 }
211
212 static void
virt_viewer_class_init(VirtViewerClass * klass)213 virt_viewer_class_init (VirtViewerClass *klass)
214 {
215 GObjectClass *object_class = G_OBJECT_CLASS (klass);
216 VirtViewerAppClass *app_class = VIRT_VIEWER_APP_CLASS (klass);
217 GApplicationClass *g_app_class = G_APPLICATION_CLASS(klass);
218
219 object_class->dispose = virt_viewer_dispose;
220
221 app_class->initial_connect = virt_viewer_initial_connect;
222 app_class->deactivated = virt_viewer_deactivated;
223 app_class->open_connection = virt_viewer_open_connection;
224 app_class->start = virt_viewer_start;
225 app_class->add_option_entries = virt_viewer_add_option_entries;
226
227 g_app_class->local_command_line = virt_viewer_local_command_line;
228 }
229
230 static void
virt_viewer_init(VirtViewer * self)231 virt_viewer_init(VirtViewer *self)
232 {
233 self->domain_event = -1;
234 }
235
236 static gboolean
virt_viewer_connect_timer(void * opaque)237 virt_viewer_connect_timer(void *opaque)
238 {
239 VirtViewer *self = VIRT_VIEWER(opaque);
240 VirtViewerApp *app = VIRT_VIEWER_APP(self);
241
242 g_debug("Connect timer fired");
243
244 if (!virt_viewer_app_is_active(app) &&
245 !virt_viewer_app_initial_connect(app, NULL))
246 g_application_quit(G_APPLICATION(app));
247
248 if (virt_viewer_app_is_active(app)) {
249 self->reconnect_poll = 0;
250 return FALSE;
251 }
252
253 return TRUE;
254 }
255
256 static void
virt_viewer_start_reconnect_poll(VirtViewer * self)257 virt_viewer_start_reconnect_poll(VirtViewer *self)
258 {
259 g_debug("reconnect_poll: %u", self->reconnect_poll);
260
261 if (self->reconnect_poll != 0)
262 return;
263
264 self->reconnect_poll = g_timeout_add(500, virt_viewer_connect_timer, self);
265 }
266
267 static void
virt_viewer_stop_reconnect_poll(VirtViewer * self)268 virt_viewer_stop_reconnect_poll(VirtViewer *self)
269 {
270 g_debug("reconnect_poll: %u", self->reconnect_poll);
271
272 if (self->reconnect_poll == 0)
273 return;
274
275 g_source_remove(self->reconnect_poll);
276 self->reconnect_poll = 0;
277 }
278
279 static void
virt_viewer_deactivated(VirtViewerApp * app,gboolean connect_error)280 virt_viewer_deactivated(VirtViewerApp *app, gboolean connect_error)
281 {
282 VirtViewer *self = VIRT_VIEWER(app);
283
284 if (self->dom) {
285 virDomainFree(self->dom);
286 self->dom = NULL;
287 }
288
289 if (self->reconnect && !virt_viewer_app_get_session_cancelled(app)) {
290 if (self->domain_event < 0) {
291 g_debug("No domain events, falling back to polling");
292 virt_viewer_start_reconnect_poll(self);
293 }
294
295 virt_viewer_app_show_status(app, _("Waiting for guest domain to re-start"));
296 virt_viewer_app_trace(app, "Guest %s display has disconnected, waiting to reconnect", self->domkey);
297 } else {
298 VIRT_VIEWER_APP_CLASS(virt_viewer_parent_class)->deactivated(app, connect_error);
299 }
300 }
301
302 static int
virt_viewer_parse_uuid(const char * name,unsigned char * uuid)303 virt_viewer_parse_uuid(const char *name,
304 unsigned char *uuid)
305 {
306 int i;
307
308 const char *cur = name;
309 for (i = 0;i < 16;) {
310 uuid[i] = 0;
311 if (*cur == 0)
312 return -1;
313 if ((*cur == '-') || (*cur == ' ')) {
314 cur++;
315 continue;
316 }
317 if ((*cur >= '0') && (*cur <= '9'))
318 uuid[i] = *cur - '0';
319 else if ((*cur >= 'a') && (*cur <= 'f'))
320 uuid[i] = *cur - 'a' + 10;
321 else if ((*cur >= 'A') && (*cur <= 'F'))
322 uuid[i] = *cur - 'A' + 10;
323 else
324 return -1;
325 uuid[i] *= 16;
326 cur++;
327 if (*cur == 0)
328 return -1;
329 if ((*cur >= '0') && (*cur <= '9'))
330 uuid[i] += *cur - '0';
331 else if ((*cur >= 'a') && (*cur <= 'f'))
332 uuid[i] += *cur - 'a' + 10;
333 else if ((*cur >= 'A') && (*cur <= 'F'))
334 uuid[i] += *cur - 'A' + 10;
335 else
336 return -1;
337 i++;
338 cur++;
339 }
340
341 return 0;
342 }
343
344
345 static virDomainPtr
virt_viewer_lookup_domain(VirtViewer * self)346 virt_viewer_lookup_domain(VirtViewer *self)
347 {
348 char *end;
349 virDomainPtr dom = NULL;
350
351 if (self->domkey == NULL) {
352 return NULL;
353 }
354
355 if (domain_selection_type & DOMAIN_SELECTION_ID) {
356 long int id = strtol(self->domkey, &end, 10);
357 if (id >= 0 && end && !*end) {
358 dom = virDomainLookupByID(self->conn, id);
359 }
360 }
361
362 if (domain_selection_type & DOMAIN_SELECTION_UUID) {
363 unsigned char uuid[16];
364 if (dom == NULL && virt_viewer_parse_uuid(self->domkey, uuid) == 0) {
365 dom = virDomainLookupByUUID(self->conn, uuid);
366 }
367 }
368
369 if (domain_selection_type & DOMAIN_SELECTION_NAME) {
370 if (dom == NULL) {
371 dom = virDomainLookupByName(self->conn, self->domkey);
372 }
373 }
374
375 return dom;
376 }
377
378 static int
virt_viewer_matches_domain(VirtViewer * self,virDomainPtr dom)379 virt_viewer_matches_domain(VirtViewer *self,
380 virDomainPtr dom)
381 {
382 char *end;
383 const char *name;
384 int id = strtol(self->domkey, &end, 10);
385 unsigned char wantuuid[16];
386 unsigned char domuuid[16];
387
388 if (id >= 0 && end && !*end) {
389 if (virDomainGetID(dom) == id)
390 return 1;
391 }
392 if (virt_viewer_parse_uuid(self->domkey, wantuuid) == 0) {
393 virDomainGetUUID(dom, domuuid);
394 if (memcmp(wantuuid, domuuid, VIR_UUID_BUFLEN) == 0)
395 return 1;
396 }
397
398 name = virDomainGetName(dom);
399 if (strcmp(name, self->domkey) == 0)
400 return 1;
401
402 return 0;
403 }
404
405 static char *
virt_viewer_extract_xpath_string(const gchar * xmldesc,const gchar * xpath)406 virt_viewer_extract_xpath_string(const gchar *xmldesc,
407 const gchar *xpath)
408 {
409 xmlDocPtr xml = NULL;
410 xmlParserCtxtPtr pctxt = NULL;
411 xmlXPathContextPtr ctxt = NULL;
412 xmlXPathObjectPtr obj = NULL;
413 char *port = NULL;
414
415 pctxt = xmlNewParserCtxt();
416 if (!pctxt || !pctxt->sax)
417 goto error;
418
419 xml = xmlCtxtReadDoc(pctxt, (const xmlChar *)xmldesc, "domain.xml", NULL,
420 XML_PARSE_NOENT | XML_PARSE_NONET |
421 XML_PARSE_NOWARNING);
422 if (!xml)
423 goto error;
424
425 ctxt = xmlXPathNewContext(xml);
426 if (!ctxt)
427 goto error;
428
429 obj = xmlXPathEval((const xmlChar *)xpath, ctxt);
430 if (!obj || obj->type != XPATH_STRING || !obj->stringval || !obj->stringval[0])
431 goto error;
432 if (!strcmp((const char*)obj->stringval, "-1"))
433 goto error;
434
435 port = g_strdup((const char*)obj->stringval);
436 xmlXPathFreeObject(obj);
437 obj = NULL;
438
439 error:
440 xmlXPathFreeObject(obj);
441 xmlXPathFreeContext(ctxt);
442 xmlFreeDoc(xml);
443 xmlFreeParserCtxt(pctxt);
444 return port;
445 }
446
447
448 static gboolean
virt_viewer_replace_host(const gchar * host)449 virt_viewer_replace_host(const gchar *host)
450 {
451 GInetAddress *addr;
452 gboolean ret;
453
454 if (!host)
455 return TRUE;
456
457 addr = g_inet_address_new_from_string(host);
458
459 if (!addr) /* Parsing error means it was probably a hostname */
460 return FALSE;
461
462 ret = g_inet_address_get_is_any(addr);
463 g_object_unref(addr);
464
465 return ret;
466 }
467
468
469 static gboolean
virt_viewer_is_loopback(const char * host)470 virt_viewer_is_loopback(const char *host)
471 {
472 GInetAddress *addr = NULL;
473 gboolean is_loopback = FALSE;
474
475 g_return_val_if_fail(host != NULL, FALSE);
476
477 addr = g_inet_address_new_from_string(host);
478 if (!addr) /* Parsing error means it was probably a hostname */
479 return (strcmp(host, "localhost") == 0);
480
481 is_loopback = g_inet_address_get_is_loopback(addr);
482 g_object_unref(addr);
483
484 return is_loopback;
485 }
486
487
488 static gboolean
virt_viewer_is_reachable(const gchar * host,const char * transport,const char * transport_host,gboolean direct)489 virt_viewer_is_reachable(const gchar *host,
490 const char *transport,
491 const char *transport_host,
492 gboolean direct)
493 {
494 gboolean host_is_loopback;
495 gboolean transport_is_loopback;
496
497 if (!host)
498 return FALSE;
499
500 if (!transport)
501 return TRUE;
502
503 if (strcmp(transport, "ssh") == 0 && !direct)
504 return TRUE;
505
506 if (strcmp(transport, "unix") == 0)
507 return TRUE;
508
509 host_is_loopback = virt_viewer_is_loopback(host);
510 transport_is_loopback = virt_viewer_is_loopback(transport_host);
511
512 if (transport_is_loopback && host_is_loopback)
513 return TRUE;
514 else
515 return !host_is_loopback;
516 }
517
518
519 static gboolean
virt_viewer_extract_connect_info(VirtViewer * self,virDomainPtr dom,GError ** error)520 virt_viewer_extract_connect_info(VirtViewer *self,
521 virDomainPtr dom,
522 GError **error)
523 {
524 char *type = NULL;
525 char *xpath = NULL;
526 gboolean retval = FALSE;
527 char *xmldesc = virDomainGetXMLDesc(dom, 0);
528 VirtViewerApp *app = VIRT_VIEWER_APP(self);
529 gchar *gport = NULL;
530 gchar *gtlsport = NULL;
531 gchar *ghost = NULL;
532 gchar *unixsock = NULL;
533 gchar *host = NULL;
534 gchar *transport = NULL;
535 gchar *user = NULL;
536 gint port = 0;
537 gchar *uri = NULL;
538 gboolean direct = virt_viewer_app_get_direct(app);
539
540 virt_viewer_app_free_connect_info(app);
541
542 if ((type = virt_viewer_extract_xpath_string(xmldesc, "string(/domain/devices/graphics/@type)")) == NULL) {
543 g_set_error(error,
544 VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED,
545 _("Cannot determine the graphic type for the guest %s"), self->domkey);
546
547 goto cleanup;
548 }
549
550 if (!virt_viewer_app_create_session(app, type, error))
551 goto cleanup;
552
553 xpath = g_strdup_printf("string(/domain/devices/graphics[@type='%s']/@port)", type);
554 gport = virt_viewer_extract_xpath_string(xmldesc, xpath);
555 g_free(xpath);
556 if (g_str_equal(type, "spice")) {
557 xpath = g_strdup_printf("string(/domain/devices/graphics[@type='%s']/@tlsPort)", type);
558 gtlsport = virt_viewer_extract_xpath_string(xmldesc, xpath);
559 g_free(xpath);
560 }
561
562 if (gport || gtlsport) {
563 xpath = g_strdup_printf("string(/domain/devices/graphics[@type='%s']/listen/@address)", type);
564 ghost = virt_viewer_extract_xpath_string(xmldesc, xpath);
565 if (ghost == NULL) { /* try old xml format - listen attribute in the graphics node */
566 g_free(xpath);
567 xpath = g_strdup_printf("string(/domain/devices/graphics[@type='%s']/@listen)", type);
568 ghost = virt_viewer_extract_xpath_string(xmldesc, xpath);
569 }
570 } else {
571 xpath = g_strdup_printf("string(/domain/devices/graphics[@type='%s']/listen/@socket)", type);
572 unixsock = virt_viewer_extract_xpath_string(xmldesc, xpath);
573 if (unixsock == NULL) { /* try old xml format - socket attribute in the graphics node */
574 g_free(xpath);
575 xpath = g_strdup_printf("string(/domain/devices/graphics[@type='%s']/@socket)", type);
576 unixsock = virt_viewer_extract_xpath_string(xmldesc, xpath);
577 }
578 }
579
580 if (ghost && gport) {
581 g_debug("Guest graphics address is %s:%s", ghost, gport);
582 } else if (unixsock) {
583 g_debug("Guest graphics address is %s", unixsock);
584 } else {
585 g_debug("Using direct libvirt connection");
586 retval = TRUE;
587 goto cleanup;
588 }
589
590 uri = virConnectGetURI(self->conn);
591 if (virt_viewer_util_extract_host(uri, NULL, &host, &transport, &user, &port) < 0) {
592 g_set_error(error,
593 VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED,
594 _("Cannot determine the host for the guest %s"), self->domkey);
595
596 goto cleanup;
597 }
598
599 /* If the XML listen attribute shows a wildcard address, we need to
600 * throw that away since you obviously can't 'connect(2)' to that
601 * from a remote host. Instead we fallback to the hostname used in
602 * the libvirt URI. This isn't perfect but it is better than nothing.
603 * If the transport is SSH, fallback to localhost as the connection
604 * will be made from the remote end of the ssh connection.
605 */
606 if (virt_viewer_replace_host(ghost)) {
607 gchar *replacement_host = NULL;
608 if ((g_strcmp0(transport, "ssh") == 0) && !direct) {
609 replacement_host = g_strdup("localhost");
610 } else {
611 replacement_host = g_strdup(host);
612 }
613 g_debug("Guest graphics listen '%s' is NULL or a wildcard, replacing with '%s'",
614 ghost ? ghost : "", replacement_host);
615 g_free(ghost);
616 ghost = replacement_host;
617 }
618
619 if (!virt_viewer_is_reachable(ghost, transport, host, direct)) {
620 g_set_error(error,
621 VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED,
622 _("Guest '%s' is not reachable"), self->domkey);
623
624 g_debug("graphics listen '%s' is not reachable from this machine",
625 ghost ? ghost : "");
626
627 goto cleanup;
628 }
629
630 virt_viewer_app_set_connect_info(app, host, ghost, gport, gtlsport,transport, unixsock, user, port, NULL);
631
632 retval = TRUE;
633
634 cleanup:
635 g_free(gport);
636 g_free(gtlsport);
637 g_free(ghost);
638 g_free(unixsock);
639 g_free(host);
640 g_free(transport);
641 g_free(user);
642 g_free(type);
643 g_free(xpath);
644 g_free(xmldesc);
645 g_free(uri);
646 return retval;
647 }
648
649 static gboolean
virt_viewer_update_display(VirtViewer * self,virDomainPtr dom,GError ** error)650 virt_viewer_update_display(VirtViewer *self, virDomainPtr dom, GError **error)
651 {
652 VirtViewerApp *app = VIRT_VIEWER_APP(self);
653
654 if (self->dom)
655 virDomainFree(self->dom);
656 self->dom = dom;
657 virDomainRef(self->dom);
658
659 virt_viewer_app_trace(app, "Guest %s is running, determining display",
660 self->domkey);
661
662 if (virt_viewer_app_has_session(app))
663 return TRUE;
664
665 return virt_viewer_extract_connect_info(self, dom, error);
666 }
667
668 static gboolean
virt_viewer_open_connection(VirtViewerApp * viewer,int * fd)669 virt_viewer_open_connection(VirtViewerApp *viewer, int *fd)
670 {
671 VirtViewer *self = VIRT_VIEWER(viewer);
672 virErrorPtr err;
673 #ifndef G_OS_WIN32
674 int pair[2];
675 #endif
676 *fd = -1;
677
678 if (!self->dom)
679 return TRUE;
680
681 if ((*fd = virDomainOpenGraphicsFD(self->dom, 0,
682 VIR_DOMAIN_OPEN_GRAPHICS_SKIPAUTH)) >= 0)
683 return TRUE;
684
685 err = virGetLastError();
686 if (err && err->code != VIR_ERR_NO_SUPPORT) {
687 g_debug("Error %s", err->message ? err->message : "Unknown");
688 return TRUE;
689 }
690
691 #ifndef G_OS_WIN32
692 if (socketpair(PF_UNIX, SOCK_STREAM, 0, pair) < 0)
693 return FALSE;
694
695 if (virDomainOpenGraphics(self->dom, 0, pair[0],
696 VIR_DOMAIN_OPEN_GRAPHICS_SKIPAUTH) < 0) {
697 err = virGetLastError();
698 g_debug("Error %s", err && err->message ? err->message : "Unknown");
699 close(pair[0]);
700 close(pair[1]);
701 return TRUE;
702 }
703 close(pair[0]);
704 *fd = pair[1];
705 #endif
706 return TRUE;
707 }
708
709 static int
virt_viewer_domain_event(virConnectPtr conn G_GNUC_UNUSED,virDomainPtr dom,int event,int detail G_GNUC_UNUSED,void * opaque)710 virt_viewer_domain_event(virConnectPtr conn G_GNUC_UNUSED,
711 virDomainPtr dom,
712 int event,
713 int detail G_GNUC_UNUSED,
714 void *opaque)
715 {
716 VirtViewer *self = opaque;
717 VirtViewerApp *app = VIRT_VIEWER_APP(self);
718 VirtViewerSession *session;
719 GError *error = NULL;
720
721 g_debug("Got domain event %d %d", event, detail);
722
723 if (!virt_viewer_matches_domain(self, dom))
724 return 0;
725
726 switch (event) {
727 case VIR_DOMAIN_EVENT_STOPPED:
728 session = virt_viewer_app_get_session(app);
729 #ifdef HAVE_SPICE_GTK
730 /* do not disconnect due to migration */
731 if (detail == VIR_DOMAIN_EVENT_STOPPED_MIGRATED &&
732 VIRT_VIEWER_IS_SESSION_SPICE(session)) {
733 break;
734 }
735 #endif
736 if (session != NULL)
737 virt_viewer_session_close(session);
738 break;
739
740 case VIR_DOMAIN_EVENT_STARTED:
741 virt_viewer_update_display(self, dom, &error);
742 if (error) {
743 virt_viewer_app_simple_message_dialog(app, "%s", error->message);
744 g_clear_error(&error);
745 }
746
747 virt_viewer_app_activate(app, &error);
748 if (error) {
749 /* we may want to consolidate error reporting in
750 app_activate() instead */
751 g_warning("%s", error->message);
752 g_clear_error(&error);
753 }
754 break;
755 }
756
757 return 0;
758 }
759
760 static void
virt_viewer_conn_event(virConnectPtr conn G_GNUC_UNUSED,int reason,void * opaque)761 virt_viewer_conn_event(virConnectPtr conn G_GNUC_UNUSED,
762 int reason,
763 void *opaque)
764 {
765 VirtViewer *self = opaque;
766
767 g_debug("Got connection event %d", reason);
768
769 virConnectClose(self->conn);
770 self->conn = NULL;
771
772 virt_viewer_start_reconnect_poll(self);
773 }
774
775 static void
virt_viewer_dispose(GObject * object)776 virt_viewer_dispose (GObject *object)
777 {
778 VirtViewer *self = VIRT_VIEWER(object);
779
780 if (self->conn) {
781 if (self->domain_event >= 0) {
782 virConnectDomainEventDeregisterAny(self->conn,
783 self->domain_event);
784 self->domain_event = -1;
785 }
786 virConnectUnregisterCloseCallback(self->conn,
787 virt_viewer_conn_event);
788 virConnectClose(self->conn);
789 self->conn = NULL;
790 }
791 if (self->dom) {
792 virDomainFree(self->dom);
793 self->dom = NULL;
794 }
795 g_free(self->uri);
796 self->uri = NULL;
797 g_free(self->domkey);
798 self->domkey = NULL;
799 G_OBJECT_CLASS(virt_viewer_parent_class)->dispose (object);
800 }
801
802 static virDomainPtr
choose_vm(GtkWindow * main_window,char ** vm_name,virConnectPtr conn,GError ** error)803 choose_vm(GtkWindow *main_window,
804 char **vm_name,
805 virConnectPtr conn,
806 GError **error)
807 {
808 GtkListStore *model;
809 GtkTreeIter iter;
810 virDomainPtr *domains, dom = NULL;
811 int i, vms_running;
812 unsigned int flags = VIR_CONNECT_LIST_DOMAINS_RUNNING;
813
814 g_return_val_if_fail(vm_name != NULL, NULL);
815 free(*vm_name);
816
817 /* UI name , key , tooltip */
818 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
819
820 vms_running = virConnectListAllDomains(conn, &domains, flags);
821 for (i = 0; i < vms_running; i++) {
822 const char *name = virDomainGetName(domains[i]);
823 char *title = virDomainGetMetadata(domains[i], VIR_DOMAIN_METADATA_TITLE, NULL, 0);
824 char *description = virDomainGetMetadata(domains[i], VIR_DOMAIN_METADATA_DESCRIPTION, NULL, 0);
825 gtk_list_store_append(model, &iter);
826 gtk_list_store_set(model, &iter, 0, title ? title : name, 1, name, -1);
827 if (description)
828 gtk_list_store_set(model, &iter, 2, description, -1);
829 virDomainFree(domains[i]);
830 free(title);
831 free(description);
832 }
833 free(domains);
834
835 *vm_name = virt_viewer_vm_connection_choose_name_dialog(main_window,
836 GTK_TREE_MODEL(model),
837 error);
838 g_object_unref(G_OBJECT(model));
839 if (*vm_name == NULL)
840 return NULL;
841
842 dom = virDomainLookupByName(conn, *vm_name);
843 if (dom == NULL) {
844 virErrorPtr err = virGetLastError();
845 g_set_error_literal(error,
846 VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED,
847 err && err->message ? err->message : "unknown libvirt error");
848 } else if (virDomainGetState(dom, &i, NULL, 0) < 0 || i != VIR_DOMAIN_RUNNING) {
849 g_set_error(error,
850 VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED,
851 _("Virtual machine %s is not running"), *vm_name);
852 virDomainFree(dom);
853 dom = NULL;
854 }
855
856 return dom;
857 }
858
859 static gboolean
virt_viewer_initial_connect(VirtViewerApp * app,GError ** error)860 virt_viewer_initial_connect(VirtViewerApp *app, GError **error)
861 {
862 virDomainPtr dom = NULL;
863 virDomainInfo info;
864 gboolean ret = FALSE;
865 VirtViewer *self = VIRT_VIEWER(app);
866 char uuid_string[VIR_UUID_STRING_BUFLEN];
867 const char *guest_name;
868 char *title;
869 GError *err = NULL;
870
871 g_debug("initial connect");
872
873 if (!self->conn &&
874 virt_viewer_connect(app, &err) < 0) {
875 virt_viewer_app_show_status(app, _("Waiting for libvirt to start"));
876 goto wait;
877 }
878
879 virt_viewer_app_show_status(app, _("Finding guest domain"));
880 dom = virt_viewer_lookup_domain(self);
881 if (!dom) {
882 if (self->waitvm) {
883 virt_viewer_app_show_status(app, _("Waiting for guest domain to be created"));
884 goto wait;
885 } else {
886 VirtViewerWindow *main_window = virt_viewer_app_get_main_window(app);
887 if (self->domkey != NULL)
888 g_debug("Cannot find guest %s", self->domkey);
889 dom = choose_vm(virt_viewer_window_get_window(main_window),
890 &self->domkey,
891 self->conn,
892 &err);
893 if (dom == NULL) {
894 goto cleanup;
895 }
896 }
897 }
898
899 if (virDomainGetUUIDString(dom, uuid_string) < 0) {
900 g_debug("Couldn't get uuid from libvirt");
901 } else {
902 g_object_set(app, "uuid", uuid_string, NULL);
903 }
904 guest_name = virDomainGetName(dom);
905 if (guest_name != NULL) {
906 g_object_set(app, "guest-name", guest_name, NULL);
907 }
908
909 title = virDomainGetMetadata(dom, VIR_DOMAIN_METADATA_TITLE, NULL, 0);
910 if (title != NULL) {
911 g_object_set(app, "title", title, NULL);
912 free(title);
913 }
914
915 virt_viewer_app_show_status(app, _("Checking guest domain status"));
916 if (virDomainGetInfo(dom, &info) < 0) {
917 g_set_error_literal(&err, VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED,
918 _("Cannot get guest state"));
919 g_debug("%s", err->message);
920 goto cleanup;
921 }
922
923 if (info.state == VIR_DOMAIN_SHUTOFF) {
924 virt_viewer_app_show_status(app, _("Waiting for guest domain to start"));
925 goto wait;
926 }
927
928 if (!virt_viewer_update_display(self, dom, &err))
929 goto cleanup;
930
931 ret = VIRT_VIEWER_APP_CLASS(virt_viewer_parent_class)->initial_connect(app, &err);
932 if (ret || err)
933 goto cleanup;
934
935 wait:
936 virt_viewer_app_trace(app, "Guest %s has not activated its display yet, waiting "
937 "for it to start", self->domkey);
938 ret = TRUE;
939
940 cleanup:
941 if (err != NULL)
942 g_propagate_error(error, err);
943 if (dom)
944 virDomainFree(dom);
945 return ret;
946 }
947
948 static void
virt_viewer_error_func(void * data G_GNUC_UNUSED,virErrorPtr error G_GNUC_UNUSED)949 virt_viewer_error_func (void *data G_GNUC_UNUSED,
950 virErrorPtr error G_GNUC_UNUSED)
951 {
952 /* nothing */
953 }
954
955
956
957 static int
virt_viewer_auth_libvirt_credentials(virConnectCredentialPtr cred,unsigned int ncred,void * cbdata)958 virt_viewer_auth_libvirt_credentials(virConnectCredentialPtr cred,
959 unsigned int ncred,
960 void *cbdata)
961 {
962 char **username = NULL, **password = NULL;
963 VirtViewer *self = cbdata;
964 int i;
965 int ret = 0;
966
967 g_debug("Got libvirt credential request for %u credential(s)", ncred);
968
969 for (i = 0 ; i < ncred ; i++) {
970 switch (cred[i].type) {
971 case VIR_CRED_USERNAME:
972 case VIR_CRED_AUTHNAME:
973 username = &cred[i].result;
974 break;
975 case VIR_CRED_PASSPHRASE:
976 password = &cred[i].result;
977 break;
978 default:
979 g_debug("Unsupported libvirt credential %d", cred[i].type);
980 return -1;
981 }
982 }
983
984 if (username || password) {
985 VirtViewerWindow *vwin = virt_viewer_app_get_main_window(VIRT_VIEWER_APP(self));
986 GtkWindow *win = virt_viewer_window_get_window(vwin);
987 VirtViewerAuth *auth = virt_viewer_auth_new(win);
988
989 if (username && (*username == NULL || **username == '\0'))
990 *username = g_strdup(g_get_user_name());
991
992 self->auth_cancelled = !virt_viewer_auth_collect_credentials(auth,
993 "libvirt",
994 self->uri,
995 username, password);
996 gtk_widget_destroy(GTK_WIDGET(auth));
997 if (self->auth_cancelled) {
998 ret = -1;
999 goto cleanup;
1000 }
1001 }
1002
1003 static const char * const cred_type_to_str[] = {
1004 [VIR_CRED_USERNAME] = "Identity to act as",
1005 [VIR_CRED_AUTHNAME] = "Identify to authorize as",
1006 [VIR_CRED_PASSPHRASE] = "Passphrase secret",
1007 };
1008
1009 for (i = 0 ; i < ncred ; i++) {
1010 switch (cred[i].type) {
1011 case VIR_CRED_AUTHNAME:
1012 case VIR_CRED_USERNAME:
1013 case VIR_CRED_PASSPHRASE:
1014 if (cred[i].result)
1015 cred[i].resultlen = strlen(cred[i].result);
1016 else
1017 cred[i].resultlen = 0;
1018 g_debug("Got %s '%s' %d",
1019 cred_type_to_str[cred[i].type],
1020 /* hide password */
1021 (cred[i].type == VIR_CRED_PASSPHRASE) ? "*****" : cred[i].result,
1022 cred[i].type);
1023 break;
1024 }
1025 }
1026
1027 cleanup:
1028 g_debug("Return %d", ret);
1029 return ret;
1030 }
1031
1032 static gchar *
virt_viewer_get_error_message_from_vir_error(VirtViewer * self,virErrorPtr error)1033 virt_viewer_get_error_message_from_vir_error(VirtViewer *self,
1034 virErrorPtr error)
1035 {
1036 const gchar *error_details = NULL;
1037 gchar *detailed_error_message = NULL;
1038 gchar *error_message = g_strdup_printf(_("Unable to connect to libvirt with URI: %s."),
1039 self->uri ? self->uri : _("[none]"));
1040
1041 g_debug("Error: %s", error->message);
1042
1043 /* For now we are only treating authentication errors. */
1044 switch (error->code) {
1045 case VIR_ERR_AUTH_FAILED:
1046 error_details = _("Authentication failed.");
1047 break;
1048 default:
1049 break;
1050 }
1051
1052 if (error_details != NULL) {
1053 detailed_error_message = g_strdup_printf("%s\n%s", error_message, error_details);
1054 g_free(error_message);
1055 return detailed_error_message;
1056 }
1057
1058 return error_message;
1059 }
1060
1061 static int
virt_viewer_connect(VirtViewerApp * app,GError ** err)1062 virt_viewer_connect(VirtViewerApp *app, GError **err)
1063 {
1064 VirtViewer *self = VIRT_VIEWER(app);
1065 int cred_types[] =
1066 { VIR_CRED_AUTHNAME, VIR_CRED_PASSPHRASE };
1067 virConnectAuth auth_libvirt = {
1068 .credtype = cred_types,
1069 .ncredtype = G_N_ELEMENTS(cred_types),
1070 .cb = virt_viewer_auth_libvirt_credentials,
1071 .cbdata = app,
1072 };
1073 int oflags = 0;
1074 GError *error = NULL;
1075
1076 if (!virt_viewer_app_get_attach(app))
1077 oflags |= VIR_CONNECT_RO;
1078
1079 g_debug("connecting ...");
1080
1081 virt_viewer_app_trace(app, "Opening connection to libvirt with URI %s",
1082 self->uri ? self->uri : "<null>");
1083 self->conn = virConnectOpenAuth(self->uri,
1084 //virConnectAuthPtrDefault,
1085 &auth_libvirt,
1086 oflags);
1087 if (!self->conn) {
1088 if (!self->auth_cancelled) {
1089 gchar *error_message = virt_viewer_get_error_message_from_vir_error(self, virGetLastError());
1090 g_set_error_literal(&error,
1091 VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED,
1092 error_message);
1093
1094 g_free(error_message);
1095 } else {
1096 g_set_error_literal(&error,
1097 VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_CANCELLED,
1098 _("Authentication was cancelled"));
1099 }
1100 g_propagate_error(err, error);
1101 return -1;
1102 }
1103
1104 if (!virt_viewer_app_initial_connect(app, &error)) {
1105 g_propagate_prefixed_error(err, error, _("Failed to connect: "));
1106 return -1;
1107 }
1108
1109 self->domain_event = virConnectDomainEventRegisterAny(self->conn,
1110 self->dom,
1111 VIR_DOMAIN_EVENT_ID_LIFECYCLE,
1112 VIR_DOMAIN_EVENT_CALLBACK(virt_viewer_domain_event),
1113 self,
1114 NULL);
1115 if (self->domain_event < 0 &&
1116 !virt_viewer_app_is_active(app)) {
1117 g_debug("No domain events, falling back to polling");
1118 virt_viewer_start_reconnect_poll(self);
1119 } else {
1120 /* we may be polling if we lost the libvirt connection and are trying
1121 * to reconnect */
1122 virt_viewer_stop_reconnect_poll(self);
1123 }
1124
1125 if (virConnectRegisterCloseCallback(self->conn,
1126 virt_viewer_conn_event,
1127 self,
1128 NULL) < 0) {
1129 g_debug("Unable to register close callback on libvirt connection");
1130 }
1131
1132 if (virConnectSetKeepAlive(self->conn, 5, 3) < 0) {
1133 g_debug("Unable to set keep alive");
1134 }
1135
1136 return 0;
1137 }
1138
1139 static gboolean
virt_viewer_start(VirtViewerApp * app,GError ** error)1140 virt_viewer_start(VirtViewerApp *app, GError **error)
1141 {
1142 gvir_event_register();
1143
1144 virSetErrorFunc(NULL, virt_viewer_error_func);
1145
1146 if (virt_viewer_connect(app, error) < 0)
1147 return FALSE;
1148
1149 return VIRT_VIEWER_APP_CLASS(virt_viewer_parent_class)->start(app, error);
1150 }
1151
1152 VirtViewer *
virt_viewer_new(void)1153 virt_viewer_new(void)
1154 {
1155 return g_object_new(VIRT_VIEWER_TYPE,
1156 "application-id", "org.virt-manager.virt-viewer",
1157 "flags", G_APPLICATION_NON_UNIQUE,
1158 NULL);
1159 }
1160