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