From: Daniel P. Berrangé <[email protected]>

Hostnames must comply with certain rules about maximum length
and permitted characters. Since these need to be validated in
several places, introduce a helper to centralize the rules
and provide testing.

Signed-off-by: Daniel P. Berrangé <[email protected]>
---
 src/libvirt_private.syms |  1 +
 src/util/virutil.c       | 50 ++++++++++++++++++++++++++++
 src/util/virutil.h       |  2 ++
 tests/utiltest.c         | 72 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 125 insertions(+)

diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 798d2d28c8..66520a3e35 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -3791,6 +3791,7 @@ virGetUserRuntimeDirectory;
 virGetUserShell;
 virHostGetDRMRenderNode;
 virHostHasIOMMU;
+virHostnameValidate;
 virIndexToDiskName;
 virKernelCmdlineMatchParam;
 virKernelCmdlineNextParam;
diff --git a/src/util/virutil.c b/src/util/virutil.c
index 2be9a460ac..75ecb6da5a 100644
--- a/src/util/virutil.c
+++ b/src/util/virutil.c
@@ -1979,3 +1979,53 @@ virPipeNonBlock(int fds[2])
 {
     return virPipeImpl(fds, true, true);
 }
+
+#define VIR_HOSTNAME_VALID_CHARS \
+    "abcdefghijklmnopqrstuvwxyz" \
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+    "0123456789-"
+
+int virHostnameValidate(const char *hostname, bool qualified)
+{
+    g_auto(GStrv) labels = g_strsplit(hostname, ".", -1);
+    size_t i;
+
+    if (!qualified && g_strv_length(labels) > 1) {
+        virReportError(VIR_ERR_INVALID_ARG,
+                       _("Hostname '%1$s' must not be qualified"),
+                       hostname);
+        return -1;
+    }
+    if (strlen(hostname) > 253) {
+        virReportError(VIR_ERR_INVALID_ARG,
+                       _("Hostname '%1$s' must be 253 characters or less"),
+                       hostname);
+        return -1;
+    }
+
+    for (i = 0; labels[i] != NULL; i++) {
+        size_t labellen = strlen(labels[i]);
+        if (labellen == 0) {
+            virReportError(VIR_ERR_INVALID_ARG,
+                           _("Hostname '%1$s' components must be non-empty"),
+                           hostname);
+            return -1;
+        }
+
+        if (labellen > 63) {
+            virReportError(VIR_ERR_INVALID_ARG,
+                           _("Hostname '%1$s' components must be 63 characters 
or less"),
+                           hostname);
+            return -1;
+        }
+
+        if (strspn(labels[i],
+                   VIR_HOSTNAME_VALID_CHARS) != labellen) {
+            virReportError(VIR_ERR_INVALID_ARG,
+                           _("Hostname '%1$s' components must only contain 
a-z, A-Z, 0-9 and -"),
+                           hostname);
+            return -1;
+        }
+    }
+    return 0;
+}
diff --git a/src/util/virutil.h b/src/util/virutil.h
index 5678c339ba..b2718fa1df 100644
--- a/src/util/virutil.h
+++ b/src/util/virutil.h
@@ -230,3 +230,5 @@ int virPipeQuiet(int fds[2]);
  * Returns: -1 on error, 0 on success
  */
 int virPipeNonBlock(int fds[2]);
+
+int virHostnameValidate(const char *hostname, bool qualified);
diff --git a/tests/utiltest.c b/tests/utiltest.c
index 0bef502a84..7e14c0a161 100644
--- a/tests/utiltest.c
+++ b/tests/utiltest.c
@@ -423,6 +423,77 @@ testGetSubIDs(const void *data G_GNUC_UNUSED)
 }
 
 
+struct testHostnamesData
+{
+    const char *name;
+    bool qualified;
+    bool valid;
+};
+
+static const struct testHostnamesData testHostnamesData[] = {
+    { .name = "foo", .qualified = false, .valid = true },
+    { .name = "foo", .qualified = true, .valid = true },
+    { .name = "foo.example.com", .qualified = false, .valid = false },
+    { .name = "foo.example.com", .qualified = true, .valid = true },
+    { .name = "foo ", .qualified = false, .valid = false },
+    { .name = "foo\n", .qualified = false, .valid = false },
+    { .name = "foo\t", .qualified = false, .valid = false },
+    { .name = "0foo", .qualified = false, .valid = true },
+    { .name = "foo\n", .qualified = false, .valid = false },
+    { .name = "foo.example.com\t", .qualified = true, .valid = false },
+    { .name = ".foo", .qualified = false, .valid = false },
+    { .name = "foo.", .qualified = false, .valid = false },
+    { .name = "foo.example.com.", .qualified = false, .valid = false },
+    { .name = "foo..example.com", .qualified = false, .valid = false },
+    { .name = 
"foo1234567foo1234567foo1234567foo1234567foo1234567foo1234567abc",
+      .qualified = false, .valid = true },
+    { .name = 
"foo1234567foo1234567foo1234567foo1234567foo1234567foo1234567abcd",
+      .qualified = false, .valid = false },
+    { .name =
+      "foo1234567foo1234567foo1234567foo1234567foo123456."
+      "foo1234567foo1234567foo1234567foo1234567foo123456."
+      "foo1234567foo1234567foo1234567foo1234567foo123456."
+      "foo1234567foo1234567foo1234567foo1234567foo123456."
+      "foo1234567foo1234567foo1234567foo1234567foo123456."
+      "abc",
+      .qualified = true, .valid = true },
+    { .name =
+      "foo1234567foo1234567foo1234567foo1234567foo123456."
+      "foo1234567foo1234567foo1234567foo1234567foo123456."
+      "foo1234567foo1234567foo1234567foo1234567foo123456."
+      "foo1234567foo1234567foo1234567foo1234567foo123456."
+      "foo1234567foo1234567foo1234567foo1234567foo123456."
+      "abcd",
+      .qualified = true, .valid = false },
+};
+
+
+static int
+testHostnames(const void *data G_GNUC_UNUSED)
+{
+    size_t i;
+    for (i = 0; i < G_N_ELEMENTS(testHostnamesData); i++) {
+        int ret = virHostnameValidate(testHostnamesData[i].name,
+                                      testHostnamesData[i].qualified);
+        if (ret < 0) {
+            if (testHostnamesData[i].valid) {
+                g_printerr("Hostname '%s' should be valid: %s",
+                           testHostnamesData[i].name,
+                           virGetLastErrorMessage());
+                return -1;
+            }
+        } else {
+            if (!testHostnamesData[i].valid) {
+                g_printerr("Hostname '%s' should be invalid",
+                           testHostnamesData[i].name);
+                return -1;
+            }
+        }
+    }
+    return 0;
+}
+
+
 static int
 mymain(void)
 {
@@ -447,6 +518,7 @@ mymain(void)
     DO_TEST(KernelCmdlineNextParam);
     DO_TEST(KernelCmdlineMatchParam);
     DO_TEST(GetSubIDs);
+    DO_TEST(Hostnames);
 
     return result == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
 }
-- 
2.56.0

Reply via email to