From 931f21e3bf5a9de0da7f3fa40d69e5129cadfa5e Mon Sep 17 00:00:00 2001 From: Vitor Lobo Date: Wed, 23 Jul 2025 15:31:21 -0300 Subject: [PATCH] feat: allow hostname and 'no' for defaultrouter --- src/validation_utils.c | 84 +++++++++--------- tests/integration/test_files/invalid_rc.conf | 4 +- tests/integration/test_files/valid_rc.conf | 4 +- tests/unit/test_validation.c | 89 ++++++++++++++------ 4 files changed, 113 insertions(+), 68 deletions(-) diff --git a/src/validation_utils.c b/src/validation_utils.c index f191aae..a6078c1 100644 --- a/src/validation_utils.c +++ b/src/validation_utils.c @@ -3,6 +3,7 @@ #include #include "validation_utils.h" +#include char *trim_space(char *str) { char *end; @@ -28,48 +29,43 @@ static bool is_valid_ipv4(const char *value) { return a <= 255 && b <= 255 && c <= 255 && d <= 255; } -static bool is_valid_ipv6(const char *value) { - const char *s = value; - int colons = 0; - bool double_colon = false; - unsigned int val; - - // Handle empty string - if (!*s) return false; - - // Handle starting with : - if (*s == ':') { - if (s[1] != ':') return false; - double_colon = true; - s += 2; - colons++; +static bool is_valid_hostname(const char *value) { + size_t len = strlen(value); + if (len == 0 || len > 253) return false; + if (value[0] == '.' || value[len - 1] == '.' || + value[0] == '-' || value[len - 1] == '-') + return false; + bool has_alnum = false; + for (size_t i = 0; i < len; i++) { + char c = value[i]; + if (!(isalnum((unsigned char)c) || c == '-' || c == '.')) return false; + if (isalnum((unsigned char)c)) has_alnum = true; } + return has_alnum; +} - while (*s) { - if (*s == ':') { - colons++; - if (s[1] == ':') { - if (double_colon) return false; // Only one :: allowed - double_colon = true; - s++; - } - s++; - continue; - } - - // Read up to 4 hex digits - val = 0; - for (int i = 0; i < 4 && isxdigit((unsigned char)*s); i++, s++) { - val = (val << 4) | (isdigit(*s) ? *s - '0' : - (tolower(*s) - 'a' + 10)); +static bool is_valid_ipv6_address(const char *value) { + unsigned char buf[16]; + return inet_pton(AF_INET6, value, buf) == 1; +} + +static bool is_valid_ipv6(const char *value) { + const char *percent = strchr(value, '%'); + char addr[128]; + if (percent) { + size_t len = percent - value; + if (len >= sizeof(addr)) return false; + memcpy(addr, value, len); + addr[len] = '\0'; + value = addr; + const char *iface = percent + 1; + if (*iface == '\0') return false; + for (const char *p = iface; *p; p++) { + if (!(isalnum((unsigned char)*p) || *p == '_' || *p == '-' || *p == '.')) + return false; } - if (val > 0xffff) return false; - - if (*s && *s != ':') return false; } - - // Check number of segments - return double_colon ? colons <= 7 : colons == 7; + return is_valid_ipv6_address(value); } bool validate_option(const char *key, char *value) { @@ -86,9 +82,17 @@ bool validate_option(const char *key, char *value) { case TYPE_STRING: // Special handling for router options if (strcmp(key, "defaultrouter") == 0) { - return value[0] == '\0' || is_valid_ipv4(value); + if (value[0] == '\0' || strcasecmp(value, "no") == 0) + return true; + if (is_valid_ipv4(value)) return true; + if (strspn(value, "0123456789.") == strlen(value)) + return false; + return is_valid_hostname(value); } else if (strcmp(key, "ipv6_defaultrouter") == 0) { - return value[0] == '\0' || is_valid_ipv6(value); + if (value[0] == '\0' || strcasecmp(value, "no") == 0) + return true; + if (is_valid_ipv6(value)) return true; + return is_valid_hostname(value); } return true; case TYPE_INT: diff --git a/tests/integration/test_files/invalid_rc.conf b/tests/integration/test_files/invalid_rc.conf index 8f601a3..d1d0ad2 100644 --- a/tests/integration/test_files/invalid_rc.conf +++ b/tests/integration/test_files/invalid_rc.conf @@ -1,7 +1,7 @@ # Invalid rc.conf configuration for testing hostname="testserver.local" -defaultrouter="300.168.1.1" # Invalid IPv4 -ipv6_defaultrouter="2001:xyz::1" # Invalid IPv6 +defaultrouter="bad host!" # Invalid hostname +ipv6_defaultrouter="2001:db8::g" # Invalid IPv6 sshd_enable="enabled" # Invalid boolean ntpd_enable="1" # Invalid boolean rcshutdown_timeout="fast" # Invalid integer \ No newline at end of file diff --git a/tests/integration/test_files/valid_rc.conf b/tests/integration/test_files/valid_rc.conf index 5881500..c0429df 100644 --- a/tests/integration/test_files/valid_rc.conf +++ b/tests/integration/test_files/valid_rc.conf @@ -1,7 +1,7 @@ # Valid rc.conf configuration for testing hostname="testserver.local" -defaultrouter="192.168.1.1" -ipv6_defaultrouter="2001:db8::1" +defaultrouter="router.example.com" +ipv6_defaultrouter="fe80::1%em0" sshd_enable="YES" ntpd_enable="NO" rcshutdown_timeout="30" \ No newline at end of file diff --git a/tests/unit/test_validation.c b/tests/unit/test_validation.c index 0c8e069..9109730 100644 --- a/tests/unit/test_validation.c +++ b/tests/unit/test_validation.c @@ -4,42 +4,83 @@ #include "validation_utils.h" MU_TEST(test_ipv4_validation) { - mu_check(validate_option("defaultrouter", "192.168.1.1") == true); - mu_check(validate_option("defaultrouter", "10.0.0.1") == true); - mu_check(validate_option("defaultrouter", "256.1.2.3") == false); - mu_check(validate_option("defaultrouter", "192.168.1") == false); - mu_check(validate_option("defaultrouter", "192.168.1.1.1") == false); - mu_check(validate_option("defaultrouter", "") == true); // Empty is valid + char v1[] = "192.168.1.1"; + mu_check(validate_option("defaultrouter", v1) == true); + char v2[] = "10.0.0.1"; + mu_check(validate_option("defaultrouter", v2) == true); + char v3[] = "256.1.2.3"; + mu_check(validate_option("defaultrouter", v3) == false); + char v4[] = "192.168.1"; + mu_check(validate_option("defaultrouter", v4) == false); + char v5[] = "192.168.1.1.1"; + mu_check(validate_option("defaultrouter", v5) == false); + char v6[] = ""; + mu_check(validate_option("defaultrouter", v6) == true); // Empty is valid + char v7[] = "no"; + mu_check(validate_option("defaultrouter", v7) == true); // 'no' disables route + char v8[] = "router.example.com"; + mu_check(validate_option("defaultrouter", v8) == true); // Hostname allowed + char v9[] = "bad host!"; + mu_check(validate_option("defaultrouter", v9) == false); // Invalid hostname + return NULL; } MU_TEST(test_ipv6_validation) { - mu_check(validate_option("ipv6_defaultrouter", "2001:db8::1") == true); - mu_check(validate_option("ipv6_defaultrouter", "::1") == true); - mu_check(validate_option("ipv6_defaultrouter", "2001:db8::g") == false); - mu_check(validate_option("ipv6_defaultrouter", "2001:db8:::1") == false); - mu_check(validate_option("ipv6_defaultrouter", "") == true); // Empty is valid + char i1[] = "2001:db8::1"; + mu_check(validate_option("ipv6_defaultrouter", i1) == true); + char i2[] = "::1"; + mu_check(validate_option("ipv6_defaultrouter", i2) == true); + char i3[] = "2001:db8::g"; + mu_check(validate_option("ipv6_defaultrouter", i3) == false); + char i4[] = "2001:db8:::1"; + mu_check(validate_option("ipv6_defaultrouter", i4) == false); + char i5[] = ""; + mu_check(validate_option("ipv6_defaultrouter", i5) == true); // Empty is valid + char i6[] = "no"; + mu_check(validate_option("ipv6_defaultrouter", i6) == true); + char i7[] = "fe80::1%em0"; + mu_check(validate_option("ipv6_defaultrouter", i7) == true); + char i8[] = "gateway.example.com"; + mu_check(validate_option("ipv6_defaultrouter", i8) == true); + return NULL; } MU_TEST(test_boolean_validation) { - mu_check(validate_option("sshd_enable", "YES") == true); - mu_check(validate_option("sshd_enable", "NO") == true); - mu_check(validate_option("sshd_enable", "yes") == true); // Case insensitive - mu_check(validate_option("sshd_enable", "no") == true); // Case insensitive - mu_check(validate_option("sshd_enable", "true") == false); - mu_check(validate_option("sshd_enable", "1") == false); + char b1[] = "YES"; + mu_check(validate_option("sshd_enable", b1) == true); + char b2[] = "NO"; + mu_check(validate_option("sshd_enable", b2) == true); + char b3[] = "yes"; + mu_check(validate_option("sshd_enable", b3) == true); // Case insensitive + char b4[] = "no"; + mu_check(validate_option("sshd_enable", b4) == true); // Case insensitive + char b5[] = "true"; + mu_check(validate_option("sshd_enable", b5) == false); + char b6[] = "1"; + mu_check(validate_option("sshd_enable", b6) == false); + return NULL; } MU_TEST(test_integer_validation) { - mu_check(validate_option("rcshutdown_timeout", "30") == true); - mu_check(validate_option("rcshutdown_timeout", "-1") == true); - mu_check(validate_option("rcshutdown_timeout", "abc") == false); - mu_check(validate_option("rcshutdown_timeout", "1.5") == false); + char i1[] = "30"; + mu_check(validate_option("rcshutdown_timeout", i1) == true); + char i2[] = "-1"; + mu_check(validate_option("rcshutdown_timeout", i2) == true); + char i3[] = "abc"; + mu_check(validate_option("rcshutdown_timeout", i3) == false); + char i4[] = "1.5"; + mu_check(validate_option("rcshutdown_timeout", i4) == false); + return NULL; } MU_TEST(test_string_validation) { - mu_check(validate_option("hostname", "myserver.example.com") == true); - mu_check(validate_option("hostname", "") == true); - mu_check(validate_option("hostname", "server#1") == true); + char s1[] = "myserver.example.com"; + mu_check(validate_option("hostname", s1) == true); + char s2[] = ""; + mu_check(validate_option("hostname", s2) == true); + char s3[] = "server#1"; + mu_check(validate_option("hostname", s3) == true); + return NULL; } MU_TEST_SUITE(test_suite) {