[Libguestfs] [PATCH] New APIs: Model libvirt authentication events through the API.

Richard W.M. Jones rjones at redhat.com
Sat Oct 13 19:23:27 UTC 2012


From: "Richard W.M. Jones" <rjones at redhat.com>

This commit models libvirt authentication events through the API,
adding one new event (GUESTFS_EVENT_LIBVIRT_AUTH) and several new
APIs:

  guestfs_set_libvirt_supported_credentials
  guestfs_get_libvirt_requested_credentials
  guestfs_get_libvirt_requested_credential_prompt
  guestfs_get_libvirt_requested_credential_challenge
  guestfs_get_libvirt_requested_credential_defresult
  guestfs_set_libvirt_requested_credential

See the documentation and example which shows how to use the new API.

This commit also changes existing calls to virConnectOpen* within the
library so that the new API is used.

Also included is an example (but not a test, because it's hard to see
how to automatically test the libvirt API).
---
 .gitignore                    |   1 +
 examples/Makefile.am          |  13 +-
 examples/libvirt_auth.c       | 167 +++++++++++++++++++
 generator/actions.ml          | 101 ++++++++++++
 generator/events.ml           |   2 +
 ocaml/t/guestfs_400_events.ml |   3 +-
 po/POTFILES                   |   1 +
 src/Makefile.am               |   1 +
 src/guestfs-internal.h        |  15 ++
 src/guestfs.pod               | 136 ++++++++++++++++
 src/launch-libvirt.c          |   3 +-
 src/libvirt-auth.c            | 369 ++++++++++++++++++++++++++++++++++++++++++
 src/libvirt-domain.c          |   2 +-
 13 files changed, 809 insertions(+), 5 deletions(-)
 create mode 100644 examples/libvirt_auth.c
 create mode 100644 src/libvirt-auth.c

diff --git a/.gitignore b/.gitignore
index 75fa7ab..6a59aec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -99,6 +99,7 @@ Makefile.in
 /examples/guestfs-recipes.1
 /examples/guestfs-testing.1
 /examples/inspect_vm
+/examples/libvirt_auth
 /examples/mount_local
 /examples/stamp-guestfs-examples.pod
 /examples/stamp-guestfs-faq.pod
diff --git a/examples/Makefile.am b/examples/Makefile.am
index a7c9903..bf2db45 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -32,7 +32,7 @@ CLEANFILES = \
 
 noinst_PROGRAMS = create_disk display_icon inspect_vm
 if HAVE_LIBVIRT
-noinst_PROGRAMS += copy_over
+noinst_PROGRAMS += copy_over libvirt_auth
 endif
 if HAVE_HIVEX
 noinst_PROGRAMS += virt-dhcp-address
@@ -52,6 +52,17 @@ copy_over_CFLAGS = \
 copy_over_LDADD = \
 	$(top_builddir)/src/libguestfs.la \
 	$(LIBVIRT_LIBS)
+
+libvirt_auth_SOURCES = libvirt_auth.c
+libvirt_auth_CFLAGS = \
+	-DGUESTFS_WARN_DEPRECATED=1 \
+	-I$(top_srcdir)/src -I$(top_builddir)/src \
+	$(LIBVIRT_CFLAGS) \
+	-pthread \
+	$(WARN_CFLAGS) $(WERROR_CFLAGS)
+libvirt_auth_LDADD = \
+	$(top_builddir)/src/libguestfs.la \
+	$(LIBVIRT_LIBS)
 endif
 
 create_disk_SOURCES = create_disk.c
diff --git a/examples/libvirt_auth.c b/examples/libvirt_auth.c
new file mode 100644
index 0000000..f0b8bbb
--- /dev/null
+++ b/examples/libvirt_auth.c
@@ -0,0 +1,167 @@
+/* Example of using the libvirt authentication event-driven API. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <guestfs.h>
+
+static void
+usage (void)
+{
+  fprintf (stderr,
+    "Usage:\n"
+    "\n"
+    "  libvirt_auth URI domain\n"
+    "\n"
+    "where:\n"
+    "\n"
+    "  URI     is the libvirt URI, eg. qemu+libssh2://USER at localhost/system\n"
+    "  domain  is the name of the guest\n"
+    "\n"
+    "Example:\n"
+    "\n"
+    "  libvirt_auth 'qemu+libssh2://USER at localhost/system' 'foo'\n"
+    "\n"
+    "would connect (read-only) to libvirt URI given and open the guest\n"
+    "called 'foo' and list some information about its filesystems.\n"
+    "\n"
+    "The important point of this example is that any libvirt authentication\n"
+    "required to connect to the server should be done.\n"
+    "\n");
+}
+
+static void auth_callback (guestfs_h *g, void *opaque, uint64_t event, int event_handle, int flags, const char *buf, size_t buf_len, const uint64_t *array, size_t array_len);
+
+int
+main (int argc, char *argv[])
+{
+  const char *uri, *dom;
+  guestfs_h *g;
+  const char *creds[] = { "authname", "passphrase",
+                          "echoprompt", "noechoprompt", NULL };
+  int r, eh;
+  char **filesystems;
+  size_t i;
+
+  if (argc != 3) {
+    usage ();
+    exit (EXIT_FAILURE);
+  }
+  uri = argv[1];
+  dom = argv[2];
+
+  g = guestfs_create ();
+  if (!g)
+    exit (EXIT_FAILURE);
+
+  r = guestfs_set_libvirt_supported_credentials (g, (char **) creds);
+  if (r == -1)
+    exit (EXIT_FAILURE);
+
+  /* Set up the event handler. */
+  eh = guestfs_set_event_callback (g, auth_callback,
+                                   GUESTFS_EVENT_LIBVIRT_AUTH, 0, NULL);
+  if (eh == -1)
+    exit (EXIT_FAILURE);
+
+  /* Add the named domain. */
+  r = guestfs_add_domain (g, dom,
+                          GUESTFS_ADD_DOMAIN_LIBVIRTURI, uri,
+                          -1);
+  if (r == -1)
+    exit (EXIT_FAILURE);
+
+  /* Launch and do some simple inspection. */
+  r = guestfs_launch (g);
+  if (r == -1)
+    exit (EXIT_FAILURE);
+
+  filesystems = guestfs_list_filesystems (g);
+
+  for (i = 0; filesystems[i] != NULL; i += 2) {
+    printf ("%s:%s is a %s filesystem\n",
+            dom, filesystems[i], filesystems[i+1]);
+    free (filesystems[i]);
+    free (filesystems[i+1]);
+  }
+  free (filesystems);
+
+  exit (EXIT_SUCCESS);
+}
+
+static void
+auth_callback (guestfs_h *g,
+               void *opaque,
+               uint64_t event,
+               int event_handle,
+               int flags,
+               const char *buf, size_t buf_len,
+               const uint64_t *array, size_t array_len)
+{
+  char **creds;
+  size_t i;
+  char *prompt;
+  char *reply = NULL;
+  size_t replylen;
+  char *pass;
+  ssize_t len;
+  int r;
+
+  printf ("libvirt_auth.c: authentication required for libvirt URI '%s'\n\n",
+          buf);
+
+  /* Ask libguestfs what credentials libvirt is demanding. */
+  creds = guestfs_get_libvirt_requested_credentials (g);
+  if (creds == NULL)
+    exit (EXIT_FAILURE);
+
+  /* Now ask the user for answers. */
+  for (i = 0; creds[i] != NULL; ++i)
+  {
+    printf ("libvirt_auth.c: credential '%s'\n", creds[i]);
+
+    if (strcmp (creds[i], "authname") == 0 ||
+        strcmp (creds[i], "echoprompt") == 0) {
+      prompt = guestfs_get_libvirt_requested_credential_prompt (g, i);
+      if (prompt && strcmp (prompt, "") != 0)
+        printf ("%s: ", prompt);
+      free (prompt);
+
+      len = getline (&reply, &replylen, stdin);
+      if (len == -1) {
+        perror ("getline");
+        exit (EXIT_FAILURE);
+      }
+      if (len > 0 && reply[len-1] == '\n')
+        reply[--len] = '\0';
+
+      r = guestfs_set_libvirt_requested_credential (g, i, reply, len);
+      if (r == -1)
+        exit (EXIT_FAILURE);
+    } else if (strcmp (creds[i], "passphrase") == 0 ||
+               strcmp (creds[i], "noechoprompt") == 0) {
+      prompt = guestfs_get_libvirt_requested_credential_prompt (g, i);
+      if (prompt && strcmp (prompt, "") != 0)
+        printf ("%s: ", prompt);
+      free (prompt);
+
+      pass = getpass ("");
+      if (pass == NULL) {
+        perror ("getpass");
+        exit (EXIT_FAILURE);
+      }
+      len = strlen (pass);
+
+      r = guestfs_set_libvirt_requested_credential (g, i, pass, len);
+      if (r == -1)
+        exit (EXIT_FAILURE);
+    }
+
+    free (creds[i]);
+  }
+
+  free (reply);
+  free (creds);
+}
diff --git a/generator/actions.ml b/generator/actions.ml
index 13e54f3..ac8e354 100644
--- a/generator/actions.ml
+++ b/generator/actions.ml
@@ -2379,6 +2379,107 @@ unplug the drive: see L<guestfs(3)/HOTPLUGGING>.  The disk B<must not>
 be in use (eg. mounted) when you do this.  We try to detect if the
 disk is in use and stop you from doing this." };
 
+  { defaults with
+    name = "set_libvirt_supported_credentials";
+    style = RErr, [StringList "creds"], [];
+    tests = [];
+    shortdesc = "set libvirt credentials supported by calling program";
+    longdesc = "\
+Call this function before setting an event handler for
+C<GUESTFS_EVENT_LIBVIRT_AUTH>, to supply the list of credential types
+that the program knows how to process.
+
+The C<creds> list must be a non-empty list of strings.
+Possible strings are:
+
+=over 4
+
+=item C<username>
+
+=item C<authname>
+
+=item C<language>
+
+=item C<cnonce>
+
+=item C<passphrase>
+
+=item C<echoprompt>
+
+=item C<noechoprompt>
+
+=item C<realm>
+
+=item C<external>
+
+=back
+
+See libvirt documentation for the meaning of these credential types.
+
+See L<guestfs(3)/LIBVIRT AUTHENTICATION> for documentation and example code." };
+
+  { defaults with
+    name = "get_libvirt_requested_credentials";
+    style = RStringList "creds", [], [];
+    tests = [];
+    shortdesc = "get list of credentials requested by libvirt";
+    longdesc = "\
+This should only be called during the event callback
+for events of type C<GUESTFS_EVENT_LIBVIRT_AUTH>.
+
+Return the list of credentials requested by libvirt.  Possible
+values are a subset of the strings provided when you called
+C<guestfs_set_libvirt_supported_credentials>.
+
+See L<guestfs(3)/LIBVIRT AUTHENTICATION> for documentation and example code." };
+
+  { defaults with
+    name = "get_libvirt_requested_credential_prompt";
+    style = RString "prompt", [Int "index"], [];
+    tests = [];
+    shortdesc = "prompt of i'th requested credential";
+    longdesc = "\
+Get the prompt (provided by libvirt) for the C<index>'th
+requested credential.  If libvirt did not provide a prompt,
+this returns the empty string C<\"\">.
+
+See L<guestfs(3)/LIBVIRT AUTHENTICATION> for documentation and example code." };
+
+  { defaults with
+    name = "get_libvirt_requested_credential_challenge";
+    style = RString "challenge", [Int "index"], [];
+    tests = [];
+    shortdesc = "challenge of i'th requested credential";
+    longdesc = "\
+Get the challenge (provided by libvirt) for the C<index>'th
+requested credential.  If libvirt did not provide a challenge,
+this returns the empty string C<\"\">.
+
+See L<guestfs(3)/LIBVIRT AUTHENTICATION> for documentation and example code." };
+
+  { defaults with
+    name = "get_libvirt_requested_credential_defresult";
+    style = RString "defresult", [Int "index"], [];
+    tests = [];
+    shortdesc = "default result of i'th requested credential";
+    longdesc = "\
+Get the default result (provided by libvirt) for the C<index>'th
+requested credential.  If libvirt did not provide a default result,
+this returns the empty string C<\"\">.
+
+See L<guestfs(3)/LIBVIRT AUTHENTICATION> for documentation and example code." };
+
+  { defaults with
+    name = "set_libvirt_requested_credential";
+    style = RErr, [Int "index"; BufferIn "cred"], [];
+    tests = [];
+    shortdesc = "pass requested credential back to libvirt";
+    longdesc = "\
+After requesting the C<index>'th credential from the user,
+call this function to pass the answer back to libvirt.
+
+See L<guestfs(3)/LIBVIRT AUTHENTICATION> for documentation and example code." };
+
 ]
 
 (* daemon_functions are any functions which cause some action
diff --git a/generator/events.ml b/generator/events.ml
index d025f57..1062302 100644
--- a/generator/events.ml
+++ b/generator/events.ml
@@ -37,6 +37,8 @@ let events = [
   "trace";                              (* call trace messages *)
 
   "enter";                              (* enter a function *)
+
+  "libvirt_auth";                       (* libvirt authentication request *)
 ]
 
 let events = mapi (fun i name -> name, 1 lsl i) events
diff --git a/ocaml/t/guestfs_400_events.ml b/ocaml/t/guestfs_400_events.ml
index 4585a09..be40608 100644
--- a/ocaml/t/guestfs_400_events.ml
+++ b/ocaml/t/guestfs_400_events.ml
@@ -28,7 +28,8 @@ let log g ev eh buf array =
     | Guestfs.EVENT_APPLIANCE -> "appliance"
     | Guestfs.EVENT_LIBRARY -> "library"
     | Guestfs.EVENT_TRACE -> "trace"
-    | Guestfs.EVENT_ENTER -> "enter" in
+    | Guestfs.EVENT_ENTER -> "enter"
+    | Guestfs.EVENT_LIBVIRT_AUTH -> "libvirt_auth" in
 
   let eh : int = Obj.magic eh in
 
diff --git a/po/POTFILES b/po/POTFILES
index 3705e74..815b541 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -236,6 +236,7 @@ src/launch-appliance.c
 src/launch-libvirt.c
 src/launch-unix.c
 src/launch.c
+src/libvirt-auth.c
 src/libvirt-domain.c
 src/listfs.c
 src/match.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 9b57716..b189c4a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -142,6 +142,7 @@ libguestfs_la_SOURCES = \
 	launch-appliance.c \
 	launch-libvirt.c \
 	launch-unix.c \
+	libvirt-auth.c \
 	libvirt-domain.c \
 	listfs.c \
 	match.c \
diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h
index 784f762..8090613 100644
--- a/src/guestfs-internal.h
+++ b/src/guestfs-internal.h
@@ -293,6 +293,16 @@ struct guestfs_h
   int ml_debug_calls;        /* Extra debug info on each FUSE call. */
 #endif
 
+#ifdef HAVE_LIBVIRT
+  /* Used by src/libvirt-auth.c. */
+#define NR_CREDENTIAL_TYPES 9
+  unsigned int nr_supported_credentials;
+  int supported_credentials[NR_CREDENTIAL_TYPES];
+  const char *saved_libvirt_uri; /* Doesn't need to be freed. */
+  unsigned int nr_requested_credentials;
+  virConnectCredentialPtr requested_credentials;
+#endif
+
   /**** Private data for attach-methods. ****/
   /* NB: This cannot be a union because of a pathological case where
    * the user changes attach-method while reusing the handle to launch
@@ -567,4 +577,9 @@ extern int guestfs___read_db_dump (guestfs_h *g, const char *dumpfile, void *opa
 extern void guestfs___free_fuse (guestfs_h *g);
 #endif
 
+/* libvirt-auth.c */
+#ifdef HAVE_LIBVIRT
+extern virConnectPtr guestfs___open_libvirt_connection (guestfs_h *g, const char *uri, unsigned int flags);
+#endif
+
 #endif /* GUESTFS_INTERNAL_H_ */
diff --git a/src/guestfs.pod b/src/guestfs.pod
index 151c7ad..7002f46 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -2232,6 +2232,16 @@ do not generate this event.
 
 If no callback is registered: the event is ignored.
 
+=item GUESTFS_EVENT_LIBVIRT_AUTH
+(payload type: libvirt URI)
+
+For any API function that opens a libvirt connection, this
+event may be generated to indicate that libvirt demands
+authentication information.  See L</LIBVIRT AUTHENTICATION> below.
+
+If no callback is registered: C<virConnectAuthPtrDefault> is
+used (suitable for command-line programs only).
+
 =back
 
 =head2 EVENT API
@@ -2351,6 +2361,132 @@ this example, messages are directed to syslog:
      syslog (priority, "event 0x%lx: %s", event, buf);
  }
 
+=head2 LIBVIRT AUTHENTICATION
+
+Some libguestfs API calls can open libvirt connections.  Currently the
+only ones are L</guestfs_add_domain>; and L</guestfs_launch> if the
+libvirt attach-method has been selected.  Libvirt connections may
+require authentication, for example if they need to access a remote
+server or to access root services from non-root.  Libvirt
+authentication happens via a callback mechanism, see
+L<http://libvirt.org/guide/html/Application_Development_Guide-Connections.html>
+
+You may provide libvirt authentication data by registering a callback
+for events of type C<GUESTFS_EVENT_LIBVIRT_AUTH>.
+
+If no such event is registered, then libguestfs uses a libvirt
+function that provides command-line prompts
+(C<virConnectAuthPtrDefault>).  This is only suitable for command-line
+libguestfs programs.
+
+To provide authentication, first call
+L</guestfs_set_libvirt_supported_credentials> with the list of
+credentials your program knows how to provide.  Second, register a
+callback for the C<GUESTFS_EVENT_LIBVIRT_AUTH> event.  The event
+handler will be called when libvirt is requesting authentication
+information.
+
+In the event handler, call
+L</guestfs_get_libvirt_requested_credentials> to get a list of the
+credentials that libvirt is asking for.  You then need to ask (eg. the
+user) for each credential, and call
+L</guestfs_set_libvirt_requested_credential> with the answer.  Note
+that for each credential, additional information may be available
+via the calls
+L</guestfs_get_libvirt_requested_credential_prompt>,
+L</guestfs_get_libvirt_requested_credential_challenge> or
+L</guestfs_get_libvirt_requested_credential_defresult>.
+
+The example program below should make this clearer.
+
+There is also a more substantial working example program supplied with
+the libguestfs sources, called C<libvirt_auth.c>.
+
+ main ()
+ {
+   guestfs_h *g;
+   char *creds[] = { "authname", "passphrase", NULL };
+   int r, eh;
+ 
+   g = guestfs_create ();
+   if (!g) exit (EXIT_FAILURE);
+ 
+   /* Tell libvirt what credentials the program supports. */
+   r = guestfs_set_libvirt_supported_credentials (g, creds);
+   if (r == -1)
+     exit (EXIT_FAILURE);
+ 
+   /* Set up the event handler. */
+   eh = guestfs_set_event_callback (
+       g, do_auth,
+       GUESTFS_EVENT_LIBVIRT_AUTH, 0, NULL);
+   if (eh == -1)
+     exit (EXIT_FAILURE);
+ 
+   /* An example of a call that may ask for credentials. */
+   r = guestfs_add_domain (
+       g, "dom",
+       GUESTFS_ADD_DOMAIN_LIBVIRTURI, "qemu:///system",
+       -1);
+   if (r == -1)
+     exit (EXIT_FAILURE);
+ 
+   exit (EXIT_SUCCESS);
+ }
+ 
+ static void
+ do_auth (guestfs_h *g,
+          void *opaque,
+          uint64_t event,
+          int event_handle,
+          int flags,
+          const char *buf, size_t buf_len,
+          const uint64_t *array, size_t array_len)
+ {
+   char **creds;
+   size_t i;
+   char *prompt;
+   char *reply;
+   size_t replylen;
+   int r;
+ 
+   // buf will be the libvirt URI.  buf_len may be ignored.
+   printf ("Authentication required for libvirt conn '%s'\n",
+           buf);
+ 
+   // Ask libguestfs what credentials libvirt is demanding.
+   creds = guestfs_get_libvirt_requested_credentials (g);
+   if (creds == NULL)
+     exit (EXIT_FAILURE);
+ 
+   // Now ask the user for answers.
+   for (i = 0; creds[i] != NULL; ++i)
+   {
+     if (strcmp (creds[i], "authname") == 0 ||
+         strcmp (creds[i], "passphrase") == 0)
+     {
+       prompt =
+         guestfs_get_libvirt_requested_credential_prompt (g, i);
+       if (prompt && strcmp (prompt, "") != 0)
+         printf ("%s: ", prompt);
+       free (prompt);
+ 
+       // Some code here to ask for the credential.
+       // ...
+       // Put the reply in 'reply', length 'replylen' (bytes).
+ 
+      r = guestfs_set_libvirt_requested_credential (g, i,
+          reply, replylen);
+      if (r == -1)
+        exit (EXIT_FAILURE);
+     }
+ 
+     free (creds[i]);
+   }
+ 
+   free (creds);
+ }
+
 =head1 CANCELLING LONG TRANSFERS
 
 Some operations can be cancelled by the caller while they are in
diff --git a/src/launch-libvirt.c b/src/launch-libvirt.c
index 4f015e3..b7a75d3 100644
--- a/src/launch-libvirt.c
+++ b/src/launch-libvirt.c
@@ -157,8 +157,7 @@ launch_libvirt (guestfs_h *g, const char *libvirt_uri)
     guestfs___print_timestamped_message (g, "connect to libvirt");
 
   /* Connect to libvirt, get capabilities. */
-  /* XXX Support libvirt authentication in the future. */
-  conn = virConnectOpen (libvirt_uri);
+  conn = guestfs___open_libvirt_connection (g, libvirt_uri, 0);
   if (!conn) {
     libvirt_error (g, _("could not connect to libvirt (URI = %s)"),
            libvirt_uri ? : "NULL");
diff --git a/src/libvirt-auth.c b/src/libvirt-auth.c
new file mode 100644
index 0000000..c3e3ddc
--- /dev/null
+++ b/src/libvirt-auth.c
@@ -0,0 +1,369 @@
+/* libguestfs
+ * Copyright (C) 2012 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#ifdef HAVE_LIBVIRT
+#include <libvirt/libvirt.h>
+#include <libvirt/virterror.h>
+#endif
+
+#ifdef HAVE_LIBXML2
+#include <libxml/xpath.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#endif
+
+#include "guestfs.h"
+#include "guestfs-internal.h"
+#include "guestfs-internal-actions.h"
+#include "guestfs_protocol.h"
+
+#if defined(HAVE_LIBVIRT) && defined(HAVE_LIBXML2)
+
+static struct {
+  int credtype;
+  const char *credname;
+} libvirt_credential_types[NR_CREDENTIAL_TYPES] = {
+  { VIR_CRED_USERNAME,     "username" },
+  { VIR_CRED_AUTHNAME,     "authname" },
+  { VIR_CRED_LANGUAGE,     "language" },
+  { VIR_CRED_CNONCE,       "cnonce" },
+  { VIR_CRED_PASSPHRASE,   "passphrase" },
+  { VIR_CRED_ECHOPROMPT,   "echoprompt" },
+  { VIR_CRED_NOECHOPROMPT, "noechoprompt" },
+  { VIR_CRED_REALM,        "realm" },
+  { VIR_CRED_EXTERNAL,     "external" },
+};
+
+static int
+get_credtype_from_string (const char *name)
+{
+  size_t i;
+
+  for (i = 0; i < NR_CREDENTIAL_TYPES; ++i)
+    if (STREQ (name, libvirt_credential_types[i].credname))
+      return libvirt_credential_types[i].credtype;
+
+  return -1;
+}
+
+static const char *
+get_string_of_credtype (int credtype)
+{
+  size_t i;
+
+  for (i = 0; i < NR_CREDENTIAL_TYPES; ++i)
+    if (credtype == libvirt_credential_types[i].credtype)
+      return libvirt_credential_types[i].credname;
+
+  return NULL;
+}
+
+/* Note to callers: Should it be possible to say that you don't
+ * support any libvirt credential types at all?  Not clear if that's
+ * an error or not, so don't depend on the current behaviour.
+ */
+int
+guestfs__set_libvirt_supported_credentials (guestfs_h *g, char *const *creds)
+{
+  size_t i;
+  int credtype;
+
+  /* Try to make this call atomic so it either completely succeeds
+   * or if it fails it leaves the current state intact.
+   */
+  unsigned int ncredtypes = 0;
+  int credtypes[NR_CREDENTIAL_TYPES];
+
+  for (i = 0; creds[i] != NULL; ++i) {
+    credtype = get_credtype_from_string (creds[i]);
+    if (credtype == -1) {
+      error (g, _("unknown credential type '%s'"), creds[i]);
+      return -1;
+    }
+
+    if (ncredtypes >= NR_CREDENTIAL_TYPES) {
+      error (g, _("list of supported credentials is too long"));
+      return -1;
+    }
+
+    credtypes[ncredtypes++] = credtype;
+  }
+
+  g->nr_supported_credentials = ncredtypes;
+  memcpy (g->supported_credentials, credtypes, sizeof g->supported_credentials);
+
+  return 0;
+}
+
+/* This function is called back from libvirt.  In turn it generates a
+ * libguestfs event to collect the desired credentials.
+ */
+static int
+libvirt_auth_callback (virConnectCredentialPtr cred,
+                       unsigned int ncred,
+                       void *gv)
+{
+  guestfs_h *g = gv;
+  size_t i;
+
+  if (cred == NULL || ncred == 0)
+    return 0;
+
+  /* libvirt probably does this already, but no harm in checking. */
+  for (i = 0; i < ncred; ++i)
+    cred[i].result = NULL;
+
+  g->requested_credentials = cred;
+  g->nr_requested_credentials = ncred;
+
+  guestfs___call_callbacks_message (g, GUESTFS_EVENT_LIBVIRT_AUTH,
+                                    g->saved_libvirt_uri,
+                                    strlen (g->saved_libvirt_uri));
+
+  /* libvirt documentation says: "Returns: 0 if all interactions were
+   * filled, or -1 upon error" However it also says "If an interaction
+   * cannot be filled, fill in NULL and 0".  Does that mean it's OK
+   * (not an error) to leave a field NULL?  libguestfs events cannot
+   * return errors, so that we would never have any other reason to
+   * return -1 here.  XXX
+   */
+  for (i = 0; i < ncred; ++i)
+    if (cred[i].result == NULL)
+      goto error;
+  return 0;
+
+error:
+  for (i = 0; i < ncred; ++i) {
+    free (cred[i].result);
+    cred[i].result = NULL;
+    cred[i].resultlen = 0;
+  }
+  return -1;
+}
+
+static int
+exists_libvirt_auth_event (guestfs_h *g)
+{
+  size_t i;
+
+  for (i = 0; i < g->nr_events; ++i)
+    if ((g->events[i].event_bitmask & GUESTFS_EVENT_LIBVIRT_AUTH) != 0)
+      return 1;
+
+  return 0;
+}
+
+/* Open a libvirt connection (called from other parts of the library). */
+virConnectPtr
+guestfs___open_libvirt_connection (guestfs_h *g, const char *uri,
+                                   unsigned int flags)
+{
+  virConnectAuth authdata;
+  virConnectAuthPtr authdataptr;
+  virConnectPtr conn;
+
+  /* Did the caller register a GUESTFS_EVENT_LIBVIRT_AUTH event and
+   * call guestfs_set_libvirt_supported_credentials?
+   */
+  if (g->nr_supported_credentials > 0 && exists_libvirt_auth_event (g)) {
+    memset (&authdata, 0, sizeof authdata);
+    authdata.credtype = g->supported_credentials;
+    authdata.ncredtype = g->nr_supported_credentials;
+    authdata.cb = libvirt_auth_callback;
+    authdata.cbdata = g;
+    authdataptr = &authdata;
+    g->saved_libvirt_uri = uri;
+  }
+  else
+    authdataptr = virConnectAuthPtrDefault;
+
+  conn = virConnectOpenAuth (uri, authdataptr, flags);
+
+  /* Restore handle fields to "outside event handler" state. */
+  g->saved_libvirt_uri = NULL;
+  g->nr_requested_credentials = 0;
+  g->requested_credentials = NULL;
+
+  return conn;
+}
+
+/* The calls below are meant to be called recursively from
+ * the GUESTFS_EVENT_LIBVIRT_AUTH event.
+ */
+#define CHECK_IN_EVENT_HANDLER(r)                                       \
+  if (g->nr_requested_credentials == 0) {                               \
+    error (g, _("%s should only be called from within the GUESTFS_EVENT_LIBVIRT_AUTH event handler"), \
+           __func__);                                                   \
+    return r;                                                           \
+  }
+
+char **
+guestfs__get_libvirt_requested_credentials (guestfs_h *g)
+{
+  char **ret;
+  size_t i;
+
+  CHECK_IN_EVENT_HANDLER (NULL);
+
+  /* Convert the requested_credentials types to a list of strings. */
+  ret = safe_malloc (g, sizeof (char *) * (g->nr_requested_credentials+1));
+  for (i = 0; i < g->nr_requested_credentials; ++i) {
+    ret[i] = safe_strdup (g,
+      get_string_of_credtype (g->requested_credentials[i].type));
+  }
+  ret[i] = NULL;
+
+  return ret;                   /* caller frees */
+}
+
+char *
+guestfs__get_libvirt_requested_credential_prompt (guestfs_h *g, int index)
+{
+  size_t i;
+
+  CHECK_IN_EVENT_HANDLER (NULL);
+
+  if (index >= 0 && (unsigned int) index < g->nr_requested_credentials)
+    i = (size_t) index;
+  else {
+    error (g, _("credential index out of range"));
+    return NULL;
+  }
+
+  if (g->requested_credentials[i].prompt)
+    return safe_strdup (g, g->requested_credentials[i].prompt);
+  else
+    return safe_strdup (g, "");
+}
+
+char *
+guestfs__get_libvirt_requested_credential_challenge (guestfs_h *g, int index)
+{
+  size_t i;
+
+  CHECK_IN_EVENT_HANDLER (NULL);
+
+  if (index >= 0 && (unsigned int) index < g->nr_requested_credentials)
+    i = (size_t) index;
+  else {
+    error (g, _("credential index out of range"));
+    return NULL;
+  }
+
+  if (g->requested_credentials[i].challenge)
+    return safe_strdup (g, g->requested_credentials[i].challenge);
+  else
+    return safe_strdup (g, "");
+}
+
+char *
+guestfs__get_libvirt_requested_credential_defresult (guestfs_h *g, int index)
+{
+  size_t i;
+
+  CHECK_IN_EVENT_HANDLER (NULL);
+
+  if (index >= 0 && (unsigned int) index < g->nr_requested_credentials)
+    i = (size_t) index;
+  else {
+    error (g, _("credential index out of range"));
+    return NULL;
+  }
+
+  if (g->requested_credentials[i].defresult)
+    return safe_strdup (g, g->requested_credentials[i].defresult);
+  else
+    return safe_strdup (g, "");
+}
+
+int
+guestfs__set_libvirt_requested_credential (guestfs_h *g, int index,
+                                           const char *cred, size_t cred_size)
+{
+  size_t i;
+
+  CHECK_IN_EVENT_HANDLER (-1);
+
+  if (index >= 0 && (unsigned int) index < g->nr_requested_credentials)
+    i = (size_t) index;
+  else {
+    error (g, _("credential index out of range"));
+    return -1;
+  }
+
+  /* All the evidence is that libvirt will free this. */
+  g->requested_credentials[i].result = safe_malloc (g, cred_size+1 /* sic */);
+  memcpy (g->requested_credentials[i].result, cred, cred_size);
+  /* Some libvirt drivers are buggy (eg. libssh2), and they expect
+   * that the cred field will be \0 terminated.  To avoid surprises,
+   * add a \0 at the end.
+   */
+  g->requested_credentials[i].result[cred_size] = 0;
+  g->requested_credentials[i].resultlen = cred_size;
+  return 0;
+}
+
+#else /* no libvirt or libxml2 at compile time */
+
+#define NOT_IMPL(r)                                                     \
+  error (g, _("libvirt authentication APIs not available since this version of libguestfs was compiled without libvirt or libxml2")); \
+  return r
+
+int
+guestfs__set_libvirt_supported_credentials (guestfs_h *g, char *const *creds)
+{
+  NOT_IMPL(-1);
+}
+
+char **
+guestfs__get_libvirt_requested_credentials (guestfs_h *g)
+{
+  NOT_IMPL(NULL);
+}
+
+char *
+guestfs__get_libvirt_requested_credential_prompt (guestfs_h *g, int index)
+{
+  NOT_IMPL(NULL);
+}
+
+char *
+guestfs__get_libvirt_requested_credential_challenge (guestfs_h *g, int index)
+{
+  NOT_IMPL(NULL);
+}
+
+char *
+guestfs__get_libvirt_requested_credential_defresult (guestfs_h *g, int index)
+{
+  NOT_IMPL(NULL);
+}
+
+int
+guestfs__set_libvirt_requested_credential (guestfs_h *g, int index, const char *cred, size_t cred_size)
+{
+  NOT_IMPL(-1);
+}
+
+#endif /* no libvirt or libxml2 at compile time */
diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c
index eecea26..f65686c 100644
--- a/src/libvirt-domain.c
+++ b/src/libvirt-domain.c
@@ -99,7 +99,7 @@ guestfs__add_domain (guestfs_h *g, const char *domain_name,
   }
 
   /* Connect to libvirt, find the domain. */
-  conn = virConnectOpenReadOnly (libvirturi);
+  conn = guestfs___open_libvirt_connection (g, libvirturi, VIR_CONNECT_RO);
   if (!conn) {
     err = virGetLastError ();
     error (g, _("could not connect to libvirt (code %d, domain %d): %s"),
-- 
1.7.11.4




More information about the Libguestfs mailing list