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
