Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 44 additions & 40 deletions src/validation_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <string.h>

#include "validation_utils.h"
#include <arpa/inet.h>

char *trim_space(char *str) {
char *end;
Expand All @@ -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) {
Expand All @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/test_files/invalid_rc.conf
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions tests/integration/test_files/valid_rc.conf
Original file line number Diff line number Diff line change
@@ -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"
89 changes: 65 additions & 24 deletions tests/unit/test_validation.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down