New API: inspect-get-hostname to return the hostname of the guest.
authorRichard W.M. Jones <rjones@redhat.com>
Tue, 16 Nov 2010 12:57:36 +0000 (12:57 +0000)
committerRichard W.M. Jones <rjones@redhat.com>
Tue, 16 Nov 2010 13:27:01 +0000 (13:27 +0000)
This returns the hostname of the guest.  Tested on RHEL, Fedora,
Debian 5, Ubuntu 10.10, FreeBSD 8, Windows 7.

generator/generator_actions.ml
src/guestfs-internal.h
src/guestfs.pod
src/inspect.c

index 34a82bd..b409938 100644 (file)
@@ -1276,6 +1276,21 @@ If unavailable this is returned as an empty string C<\"\">.
 
 Please read L<guestfs(3)/INSPECTION> for more details.");
 
+  ("inspect_get_hostname", (RString "hostname", [Device "root"], []), -1, [],
+   [],
+   "get hostname of the operating system",
+   "\
+This function should only be called with a root device string
+as returned by C<guestfs_inspect_os>.
+
+This function returns the hostname of the operating system
+as found by inspection of the guest's configuration files.
+
+If the hostname could not be determined, then the
+string C<unknown> is returned.
+
+Please read L<guestfs(3)/INSPECTION> for more details.");
+
 ]
 
 (* daemon_functions are any functions which cause some action
index abcd456..e3c5b1f 100644 (file)
@@ -219,6 +219,7 @@ struct inspect_fs {
   int major_version;
   int minor_version;
   char *arch;
+  char *hostname;
   char *windows_systemroot;
   struct inspect_fstab_entry *fstab;
   size_t nr_fstab;
index 8370982..fc58f4f 100644 (file)
@@ -1095,6 +1095,14 @@ data.  Callers should be careful to escape these before printing them
 to a structured file (for example, use HTML escaping if creating a web
 page).
 
+Guest configuration may be altered in unusual ways by the
+administrator of the virtual machine, and may not reflect reality
+(particularly for untrusted or actively malicious guests).  For
+example we parse the hostname from configuration files like
+C</etc/sysconfig/network> that we find in the guest, but the guest
+administrator can easily manipulate these files to provide the wrong
+hostname.
+
 The inspection API parses guest configuration using two external
 libraries: Augeas (Linux configuration) and hivex (Windows Registry).
 Both are designed to be robust in the face of malicious data, although
index 2006bbd..baf4b3a 100644 (file)
@@ -195,10 +195,14 @@ static int check_filesystem (guestfs_h *g, const char *device);
 static int check_linux_root (guestfs_h *g, struct inspect_fs *fs);
 static int check_freebsd_root (guestfs_h *g, struct inspect_fs *fs);
 static void check_architecture (guestfs_h *g, struct inspect_fs *fs);
+static int check_hostname_unix (guestfs_h *g, struct inspect_fs *fs);
+static int check_hostname_redhat (guestfs_h *g, struct inspect_fs *fs);
+static int check_hostname_freebsd (guestfs_h *g, struct inspect_fs *fs);
 static int check_fstab (guestfs_h *g, struct inspect_fs *fs);
 static int check_windows_root (guestfs_h *g, struct inspect_fs *fs);
 static int check_windows_arch (guestfs_h *g, struct inspect_fs *fs);
-static int check_windows_registry (guestfs_h *g, struct inspect_fs *fs);
+static int check_windows_software_registry (guestfs_h *g, struct inspect_fs *fs);
+static int check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs);
 static char *resolve_windows_path_silently (guestfs_h *g, const char *);
 static int extend_fses (guestfs_h *g);
 static int parse_unsigned_int (guestfs_h *g, const char *str);
@@ -594,6 +598,10 @@ check_linux_root (guestfs_h *g, struct inspect_fs *fs)
   if (inspect_with_augeas (g, fs, "/etc/fstab", check_fstab) == -1)
     return -1;
 
+  /* Determine hostname. */
+  if (check_hostname_unix (g, fs) == -1)
+    return -1;
+
   return 0;
 }
 
@@ -625,6 +633,10 @@ check_freebsd_root (guestfs_h *g, struct inspect_fs *fs)
   if (inspect_with_augeas (g, fs, "/etc/fstab", check_fstab) == -1)
     return -1;
 
+  /* Determine hostname. */
+  if (check_hostname_unix (g, fs) == -1)
+    return -1;
+
   return 0;
 }
 
@@ -654,6 +666,124 @@ check_architecture (guestfs_h *g, struct inspect_fs *fs)
   }
 }
 
+/* Try several methods to determine the hostname from a Linux or
+ * FreeBSD guest.  Note that type and distro have been set, so we can
+ * use that information to direct the search.
+ */
+static int
+check_hostname_unix (guestfs_h *g, struct inspect_fs *fs)
+{
+  char **lines;
+
+  switch (fs->type) {
+  case OS_TYPE_LINUX:
+    /* Red Hat-derived would be in /etc/sysconfig/network, and
+     * Debian-derived in the file /etc/hostname.  Very old Debian and
+     * SUSE use /etc/HOSTNAME.  It's best to just look for each of
+     * these files in turn, rather than try anything clever based on
+     * distro.
+     */
+    if (guestfs_is_file (g, "/etc/HOSTNAME")) {
+      fs->hostname = first_line_of_file (g, "/etc/HOSTNAME");
+      if (fs->hostname == NULL)
+        return -1;
+    }
+    else if (guestfs_is_file (g, "/etc/hostname")) {
+      fs->hostname = first_line_of_file (g, "/etc/hostname");
+      if (fs->hostname == NULL)
+        return -1;
+    }
+    else if (guestfs_is_file (g, "/etc/sysconfig/network")) {
+      if (inspect_with_augeas (g, fs, "/etc/sysconfig/network",
+                               check_hostname_redhat) == -1)
+        return -1;
+    }
+    break;
+
+  case OS_TYPE_FREEBSD:
+    /* /etc/rc.conf contains the hostname, but there is no Augeas lens
+     * for this file.
+     */
+    if (guestfs_is_file (g, "/etc/rc.conf")) {
+      if (check_hostname_freebsd (g, fs) == -1)
+        return -1;
+    }
+    break;
+
+  case OS_TYPE_WINDOWS: /* not here, see check_windows_system_registry */
+  case OS_TYPE_UNKNOWN:
+  default:
+    /* nothing, keep GCC warnings happy */;
+  }
+
+  return 0;
+}
+
+/* Parse the hostname from /etc/sysconfig/network.  This must be called
+ * from the inspect_with_augeas wrapper.
+ */
+static int
+check_hostname_redhat (guestfs_h *g, struct inspect_fs *fs)
+{
+  char *hostname;
+
+  hostname = guestfs_aug_get (g, "/files/etc/sysconfig/network/HOSTNAME");
+  if (!hostname)
+    return -1;
+
+  fs->hostname = hostname;  /* freed by guestfs___free_inspect_info */
+  return 0;
+}
+
+/* Parse the hostname from /etc/rc.conf.  On FreeBSD this file
+ * contains comments, blank lines and:
+ *   hostname="freebsd8.example.com"
+ *   ifconfig_re0="DHCP"
+ *   keymap="uk.iso"
+ *   sshd_enable="YES"
+ */
+static int
+check_hostname_freebsd (guestfs_h *g, struct inspect_fs *fs)
+{
+  const char *filename = "/etc/rc.conf";
+  int64_t size;
+  char **lines;
+  size_t i;
+
+  /* Don't trust guestfs_read_lines not to break with very large files.
+   * Check the file size is something reasonable first.
+   */
+  size = guestfs_filesize (g, filename);
+  if (size == -1)
+    /* guestfs_filesize failed and has already set error in handle */
+    return -1;
+  if (size > 1000000) {
+    error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"),
+           filename, size);
+    return -1;
+  }
+
+  lines = guestfs_read_lines (g, filename);
+  if (lines == NULL)
+    return -1;
+
+  for (i = 0; lines[i] != NULL; ++i) {
+    if (STRPREFIX (lines[i], "hostname=\"") ||
+        STRPREFIX (lines[i], "hostname='")) {
+      size_t len = strlen (lines[i]) - 10 - 1;
+      fs->hostname = safe_strndup (g, &lines[i][10], len);
+      break;
+    } else if (STRPREFIX (lines[i], "hostname=")) {
+      size_t len = strlen (lines[i]) - 9;
+      fs->hostname = safe_strndup (g, &lines[i][9], len);
+      break;
+    }
+  }
+
+  guestfs___free_string_list (lines);
+  return 0;
+}
+
 static int
 check_fstab (guestfs_h *g, struct inspect_fs *fs)
 {
@@ -891,12 +1021,17 @@ check_windows_root (guestfs_h *g, struct inspect_fs *fs)
   if (check_windows_arch (g, fs) == -1)
     return -1;
 
-  if (check_windows_registry (g, fs) == -1)
+  /* Product name and version. */
+  if (check_windows_software_registry (g, fs) == -1)
     return -1;
 
   check_package_format (g, fs);
   check_package_management (g, fs);
 
+  /* Hostname. */
+  if (check_windows_system_registry (g, fs) == -1)
+    return -1;
+
   return 0;
 }
 
@@ -925,7 +1060,7 @@ check_windows_arch (guestfs_h *g, struct inspect_fs *fs)
  * registry fields available to callers.
  */
 static int
-check_windows_registry (guestfs_h *g, struct inspect_fs *fs)
+check_windows_software_registry (guestfs_h *g, struct inspect_fs *fs)
 {
   TMP_TEMPLATE_ON_STACK (software_local);
 
@@ -1032,6 +1167,90 @@ check_windows_registry (guestfs_h *g, struct inspect_fs *fs)
   return ret;
 }
 
+static int
+check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs)
+{
+  TMP_TEMPLATE_ON_STACK (system_local);
+
+  size_t len = strlen (fs->windows_systemroot) + 64;
+  char system[len];
+  snprintf (system, len, "%s/system32/config/system",
+            fs->windows_systemroot);
+
+  char *system_path = resolve_windows_path_silently (g, system);
+  if (!system_path)
+    /* If the system hive doesn't exist, just accept that we cannot
+     * find hostname etc.
+     */
+    return 0;
+
+  int ret = -1;
+  hive_h *h = NULL;
+  hive_value_h *values = NULL;
+
+  if (download_to_tmp (g, system_path, system_local, 100000000) == -1)
+    goto out;
+
+  h = hivex_open (system_local, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
+  if (h == NULL) {
+    perrorf (g, "hivex_open");
+    goto out;
+  }
+
+  hive_node_h node = hivex_root (h);
+  /* XXX Don't hard-code ControlSet001.  The current control set would
+   * be another good thing to expose up through the inspection API.
+   */
+  const char *hivepath[] =
+    { "ControlSet001", "Services", "Tcpip", "Parameters" };
+  size_t i;
+  for (i = 0;
+       node != 0 && i < sizeof hivepath / sizeof hivepath[0];
+       ++i) {
+    node = hivex_node_get_child (h, node, hivepath[i]);
+  }
+
+  if (node == 0) {
+    perrorf (g, "hivex: cannot locate HKLM\\SYSTEM\\ControlSet001\\Services\\Tcpip\\Parameters");
+    goto out;
+  }
+
+  values = hivex_node_values (h, node);
+
+  for (i = 0; values[i] != 0; ++i) {
+    char *key = hivex_value_key (h, values[i]);
+    if (key == NULL) {
+      perrorf (g, "hivex_value_key");
+      goto out;
+    }
+
+    if (STRCASEEQ (key, "Hostname")) {
+      fs->hostname = hivex_value_string (h, values[i]);
+      if (!fs->hostname) {
+        perrorf (g, "hivex_value_string");
+        free (key);
+        goto out;
+      }
+    }
+    /* many other interesting fields here ... */
+
+    free (key);
+  }
+
+  ret = 0;
+
+ out:
+  if (h) hivex_close (h);
+  free (values);
+  free (system_path);
+
+  /* Free up the temporary file. */
+  unlink (system_local);
+#undef system_local_len
+
+  return ret;
+}
+
 static char *
 resolve_windows_path_silently (guestfs_h *g, const char *path)
 {
@@ -1432,6 +1651,16 @@ guestfs__inspect_get_package_management (guestfs_h *g, const char *root)
   return ret;
 }
 
+char *
+guestfs__inspect_get_hostname (guestfs_h *g, const char *root)
+{
+  struct inspect_fs *fs = search_for_root (g, root);
+  if (!fs)
+    return NULL;
+
+  return safe_strdup (g, fs->hostname ? : "unknown");
+}
+
 static struct guestfs_application_list *list_applications_rpm (guestfs_h *g, struct inspect_fs *fs);
 static struct guestfs_application_list *list_applications_deb (guestfs_h *g, struct inspect_fs *fs);
 static struct guestfs_application_list *list_applications_windows (guestfs_h *g, struct inspect_fs *fs);
@@ -2095,6 +2324,12 @@ guestfs__inspect_get_package_management (guestfs_h *g, const char *root)
   NOT_IMPL(NULL);
 }
 
+char *
+guestfs__inspect_get_hostname (guestfs_h *g, const char *root)
+{
+  NOT_IMPL(NULL);
+}
+
 struct guestfs_application_list *
 guestfs__inspect_list_applications (guestfs_h *g, const char *root)
 {