From 6fd22cc8000e4e5c034617cd02cbd432d673c446 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Tue, 6 Sep 2022 18:19:01 +0100 Subject: T4472: add an option for verifying IPv4 ranges (#6) --- src/ipaddrcheck.c | 30 ++++++- src/ipaddrcheck_functions.c | 208 ++++++++++++++++++++++++-------------------- src/ipaddrcheck_functions.h | 1 + src/ipaddrcheck_functions.o | Bin 0 -> 45080 bytes tests/check_ipaddrcheck.c | 9 ++ tests/integration_tests.sh | 23 +++++ 6 files changed, 175 insertions(+), 96 deletions(-) create mode 100644 src/ipaddrcheck_functions.o diff --git a/src/ipaddrcheck.c b/src/ipaddrcheck.c index 92250d8..8e9c782 100644 --- a/src/ipaddrcheck.c +++ b/src/ipaddrcheck.c @@ -49,6 +49,8 @@ #define ALLOW_LOOPBACK 250 #define IS_ANY_HOST 260 #define IS_ANY_NET 270 +#define IS_IPV4_RANGE 280 +#define IS_IPV6_RANGE 290 #define NO_ACTION 500 static const struct option options[] = @@ -77,6 +79,8 @@ static const struct option options[] = { "allow-loopback", no_argument, NULL, 'C' }, { "is-any-host", no_argument, NULL, 'D' }, { "is-any-net", no_argument, NULL, 'E' }, + { "is-ipv4-range", no_argument, NULL, 'F' }, + { "is-ipv6-range", no_argument, NULL, 'G' }, { "version", no_argument, NULL, 'z' }, { "help", no_argument, NULL, '?' }, { "verbose", no_argument, NULL, 'V' }, @@ -102,6 +106,9 @@ int main(int argc, char* argv[]) int no_action = 0; /* Indicates the option modifies program behaviour but doesn't have its own action */ + int ipv4_range_check = 0; /* Disabled quick validity check for the argment string, + since it needs to be split into components first. */ + int verbose = 0; const char* program_name = argv[0]; /* Program name for use in messages */ @@ -117,7 +124,7 @@ int main(int argc, char* argv[]) return(RESULT_INT_ERROR); } - while( (optc = getopt_long(argc, argv, "acdefghijklmnoprstuzABCDEV?", options, &option_index)) != -1 ) + while( (optc = getopt_long(argc, argv, "acdefghijklmnoprstuzABCDEFGV?", options, &option_index)) != -1 ) { switch(optc) { @@ -196,6 +203,12 @@ int main(int argc, char* argv[]) case 'E': action = IS_ANY_NET; break; + case 'F': + ipv4_range_check = 1; + break; + case 'G': + action = IS_IPV6_RANGE; + break; case 'V': verbose = 1; break; @@ -242,8 +255,23 @@ int main(int argc, char* argv[]) return(RESULT_INT_ERROR); } + if( ipv4_range_check ) + { + int result = is_ipv4_range(address_str, verbose); + + if( result == RESULT_SUCCESS ) + { + return(EXIT_SUCCESS); + } + else + { + return(EXIT_FAILURE); + } + } + CIDR *address; address = cidr_from_str(address_str); + int result = RESULT_SUCCESS; /* Check if the address is valid and well-formatted at all, diff --git a/src/ipaddrcheck_functions.c b/src/ipaddrcheck_functions.c index d98ceda..269981f 100644 --- a/src/ipaddrcheck_functions.c +++ b/src/ipaddrcheck_functions.c @@ -20,6 +20,9 @@ * */ +#include +#include + #include "ipaddrcheck_functions.h" /* @@ -36,134 +39,59 @@ * the format was. */ - -/* Does it contain double colons? This is not allowed in IPv6 addresses */ -int duplicate_double_colons(char* address_str) { +int regex_matches(const char* regex, const char* str) +{ int offsets[1]; pcre *re; int rc; const char *error; int erroffset; - re = pcre_compile(".*(::).*\\1", 0, &error, &erroffset, NULL); - rc = pcre_exec(re, NULL, address_str, strlen(address_str), 0, 0, offsets, 1); + re = pcre_compile(regex, 0, &error, &erroffset, NULL); + assert(re != NULL); + + rc = pcre_exec(re, NULL, str, strlen(str), 0, 0, offsets, 1); if( rc >= 0) { - return(1); + return RESULT_SUCCESS; } else { - return(0); + return RESULT_FAILURE; } } + +/* Does it contain double colons? This is not allowed in IPv6 addresses */ +int duplicate_double_colons(char* address_str) { + return regex_matches(".*(::).*\\1", address_str); +} + /* Does it look like IPv4 CIDR (e.g. 192.0.2.1/24)? */ int is_ipv4_cidr(char* address_str) { - int result; - - int offsets[1]; - pcre *re; - int rc; - const char *error; - int erroffset; - - re = pcre_compile("^((([1-9]\\d{0,2}|0)\\.){3}([1-9]\\d{0,2}|0)\\/([1-9]\\d*|0))$", - 0, &error, &erroffset, NULL); - rc = pcre_exec(re, NULL, address_str, strlen(address_str), 0, 0, offsets, 1); - - if( rc < 0 ) - { - result = RESULT_FAILURE; - } - else - { - result = RESULT_SUCCESS; - } - - return(result); + return regex_matches("^((([1-9]\\d{0,2}|0)\\.){3}([1-9]\\d{0,2}|0)\\/([1-9]\\d*|0))$", + address_str); } /* Is it a single dotted decimal address? */ int is_ipv4_single(char* address_str) { - int result; - - int offsets[1]; - pcre *re; - int rc; - const char *error; - int erroffset; - - re = pcre_compile("^((([1-9]\\d{0,2}|0)\\.){3}([1-9]\\d{0,2}|0))$", - 0, &error, &erroffset, NULL); - rc = pcre_exec(re, NULL, address_str, strlen(address_str), 0, 0, offsets, 1); - - if( rc < 0 ) - { - result = RESULT_FAILURE; - } - else - { - result = RESULT_SUCCESS; - } - - return(result); + return regex_matches("^((([1-9]\\d{0,2}|0)\\.){3}([1-9]\\d{0,2}|0))$", + address_str); } /* Is it an IPv6 address with prefix length? */ int is_ipv6_cidr(char* address_str) { - int result; - - int offsets[1]; - pcre *re; - int rc; - const char *error; - int erroffset; - - re = pcre_compile("^((([0-9a-fA-F\\:])+)(\\/\\d{1,3}))$", - 0, &error, &erroffset, NULL); - rc = pcre_exec(re, NULL, address_str, strlen(address_str), 0, 0, offsets, 1); - - if( rc < 0 ) - { - result = RESULT_FAILURE; - } - else - { - result = RESULT_SUCCESS; - } - - return(result); + return regex_matches("^((([0-9a-fA-F\\:])+)(\\/\\d{1,3}))$", address_str); } /* Is it a single IPv6 address? */ int is_ipv6_single(char* address_str) { - int result; - - int offsets[1]; - pcre *re; - int rc; - const char *error; - int erroffset; - - re = pcre_compile("^(([0-9a-fA-F\\:])+)$", - 0, &error, &erroffset, NULL); - rc = pcre_exec(re, NULL, address_str, strlen(address_str), 0, 0, offsets, 1); - - if( rc < 0 ) - { - result = RESULT_FAILURE; - } - else - { - result = RESULT_SUCCESS; - } - - return(result); + return regex_matches("^(([0-9a-fA-F\\:])+)$", address_str); } /* Is it a CIDR-formatted IPv4 or IPv6 address? */ @@ -527,3 +455,93 @@ int is_any_net(CIDR *address) } +int is_ipv4_range(char* range_str, int verbose) +{ + int result = RESULT_SUCCESS; + + int regex_check_res = regex_matches("^([0-9\\.]+\\-[0-9\\.]+)$", range_str); + + if( !regex_check_res ) + { + if( verbose ) + { + fprintf(stderr, "Malformed range %s: must be a pair of hyphen-separated IPv4 addresses\n", range_str); + } + result = RESULT_FAILURE; + } + else + { + /* Extract sub-components from the range string. */ + + /* Alocate memory for the components. + We know that an IPv4 address is always 15 characters or less, plus a null byte. */ + char left[16]; + char right[16]; + + /* Split the string at the hyphen. + If the regex check succeeded, we know the hyphen is there. */ + char* ptr = left; + int length = strlen(range_str); + int pos = 0; + int index = 0; + while(pos < length) + { + if( range_str[pos] == '-' ) + { + ptr[index] = '\0'; + ptr = right; + index = 0; + } + else + { + ptr[index] = range_str[pos]; + index++; + } + + pos++; + } + ptr[index] = '\0'; + + if( !is_ipv4_single(left) ) + { + if( verbose ) + { + fprintf(stderr, "Malformed range %s: %s is not a valid IPv4 address\n", range_str, left); + } + result = RESULT_FAILURE; + } + else if( !is_ipv4_single(right) ) + { + if( verbose ) + { + fprintf(stderr, "Malformed range %s: %s is not a valid IPv4 address\n", range_str, right); + } + result = RESULT_FAILURE; + } + else + { + CIDR* left_addr = cidr_from_str(left); + CIDR* right_addr = cidr_from_str(right); + struct in_addr* left_in_addr = cidr_to_inaddr(left_addr, NULL); + struct in_addr* right_in_addr = cidr_to_inaddr(right_addr, NULL); + + if( left_in_addr->s_addr < right_in_addr->s_addr ) + { + result = RESULT_SUCCESS; + } + else + { + if( verbose ) + { + fprintf(stderr, "Malformed IPv4 range %s: its first address is greater than the last\n", range_str); + } + result = RESULT_FAILURE; + } + + cidr_free(left_addr); + cidr_free(right_addr); + } + } + + return(result); +} diff --git a/src/ipaddrcheck_functions.h b/src/ipaddrcheck_functions.h index 73dd246..05a19c3 100644 --- a/src/ipaddrcheck_functions.h +++ b/src/ipaddrcheck_functions.h @@ -76,5 +76,6 @@ int is_ipv6_link_local(CIDR *address); int is_valid_intf_address(CIDR *address, char* address_str, int allow_loopback); int is_any_host(CIDR *address); int is_any_net(CIDR *address); +int is_ipv4_range(char* range_str, int verbose); #endif /* IPADDRCHECK_FUNCTIONS_H */ diff --git a/src/ipaddrcheck_functions.o b/src/ipaddrcheck_functions.o new file mode 100644 index 0000000..e8e714d Binary files /dev/null and b/src/ipaddrcheck_functions.o differ diff --git a/tests/check_ipaddrcheck.c b/tests/check_ipaddrcheck.c index ad2b673..feb5a41 100644 --- a/tests/check_ipaddrcheck.c +++ b/tests/check_ipaddrcheck.c @@ -403,6 +403,14 @@ START_TEST (test_is_any_net) } END_TEST +START_TEST (test_is_ipv4_range) +{ + ck_assert_int_eq(is_ipv4_range("192.0.2.0-192.0.2.10", 0), RESULT_SUCCESS); + ck_assert_int_eq(is_ipv4_range("192.0.2.-", 0), RESULT_FAILURE); + ck_assert_int_eq(is_ipv4_range("192.0.2.99-192.0.2.11", 0), RESULT_FAILURE); +} +END_TEST + Suite *ipaddrcheck_suite(void) { @@ -433,6 +441,7 @@ Suite *ipaddrcheck_suite(void) tcase_add_test(tc_core, test_is_valid_intf_address); tcase_add_test(tc_core, test_is_any_host); tcase_add_test(tc_core, test_is_any_net); + tcase_add_test(tc_core, test_is_ipv4_range); suite_add_tcase(s, tc_core); diff --git a/tests/integration_tests.sh b/tests/integration_tests.sh index ccc7e3a..a679ea0 100755 --- a/tests/integration_tests.sh +++ b/tests/integration_tests.sh @@ -48,6 +48,16 @@ ipv4_cidr_negative=( 192.0.2.666/32 ) +ipv4_range_positive=( + 192.0.2.0-192.0.2.100 +) + +ipv4_range_negative=( + 192.0.2.-192.0.2.100 + 192.0.2.0- + 192.0.2.200-192.0.2.100 +) + ipv6_single_positive=( 2001:0db8:0000:0000:0000:ff00:0042:8329 2001:db8:0:0:0:ff00:42:8329 @@ -251,4 +261,17 @@ done # --is-ipv6-link-local # --is-valid-intf-address +# --is-ipv4-range +for range in \ + ${ipv4_range_positive[*]} +do + assert_raises "$IPADDRCHECK --is-ipv4-range $range" 0 +done + +for range in \ + ${ipv4_range_negative[*]} +do + assert_raises "$IPADDRCHECK --is-ipv4-range $range" 1 +done + assert_end ipaddrcheck_integration -- cgit v1.2.3