From 23e0eda853a9bfa42a2fa0d50b31eea874a01a9c Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Thu, 4 Jan 2024 13:38:37 -0500 Subject: Add --range-prefix-length option to require the range boundaries to lie within the same subnet of a given size --- src/Makefile.am | 2 +- src/ipaddrcheck.c | 49 ++++++++++++++++++++++++++++----- src/ipaddrcheck_functions.c | 65 ++++++++++++++++++++++++++++++++++++++++---- src/ipaddrcheck_functions.h | 4 +-- src/ipaddrcheck_functions.o | Bin 45080 -> 55552 bytes tests/check_ipaddrcheck.c | 26 ++++++++++++++---- tests/integration_tests.sh | 6 ++++ 7 files changed, 131 insertions(+), 21 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 6098ae5..1c43bae 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,4 @@ -AM_CFLAGS = --pedantic -Wall -Werror -std=c99 -O2 +AM_CFLAGS = --pedantic -Wall -Werror -Wno-error=format-overflow= -std=c99 -O2 AM_LDFLAGS = ipaddrcheck_SOURCES = ipaddrcheck.c ipaddrcheck_functions.c diff --git a/src/ipaddrcheck.c b/src/ipaddrcheck.c index 230c6d6..248c248 100644 --- a/src/ipaddrcheck.c +++ b/src/ipaddrcheck.c @@ -88,6 +88,7 @@ static const struct option options[] = { "is-any-net", no_argument, NULL, 'E' }, { "is-ipv4-range", no_argument, NULL, 'F' }, { "is-ipv6-range", no_argument, NULL, 'G' }, + { "range-prefix-length", required_argument, NULL, 'H' }, { "version", no_argument, NULL, 'z' }, { "help", no_argument, NULL, '?' }, { "verbose", no_argument, NULL, 'V' }, @@ -109,6 +110,7 @@ int main(int argc, char* argv[]) int optc; /* Option character for getopt call */ int allow_loopback = NO_LOOPBACK; /* Allow IPv4 loopback in --is-valid-intf-address */ + int range_prefix_length = 0; int no_action = 0; /* Indicates the option modifies program behaviour but doesn't have its own action */ @@ -124,9 +126,9 @@ int main(int argc, char* argv[]) const char* program_name = argv[0]; /* Program name for use in messages */ - /* Parse options, convert to action codes, store in array */ + /* Parse options, convert to action codes, store in an array. */ - /* Try to allocate memory for the actions array, abort if fail */ + /* Try to allocate memory for the actions array, abort if the attempt fails. */ actions = (int*)calloc(argc, sizeof(int)); if( errno == ENOMEM ) { @@ -134,7 +136,7 @@ int main(int argc, char* argv[]) return(RESULT_INT_ERROR); } - while( (optc = getopt_long(argc, argv, "acdefghijklmnoprstuzABCDEFGV?", options, &option_index)) != -1 ) + while( (optc = getopt_long(argc, argv, "acdefghijklmnoprstuzABCDEFGHV?", options, &option_index)) != -1 ) { switch(optc) { @@ -219,6 +221,26 @@ int main(int argc, char* argv[]) case 'G': ipv6_range_check = 1; break; + case 'H': + errno = 0; + char* endptr = ""; + /* Reminder to the reader on the quirks of strtol: + * errno != 0 --- internal parse error + * endptr == optarg --- no digits found in the string + * *endptr != '\0' --- extra characters after the last digit + */ + range_prefix_length = (int)strtol(optarg, &endptr, 10); + if( (errno != 0) || (endptr == optarg) || (*endptr != '\0') ) + { + fprintf(stderr, "Error: \"%s\" is not a valid prefix length\n", optarg); + return(RESULT_INT_ERROR); + } + if( (range_prefix_length < 0) || (range_prefix_length > 128) ) + { + fprintf(stderr, "Error: \"%s\" is not a valid prefix length\n", optarg); + return(RESULT_INT_ERROR); + } + break; case 'V': verbose = 1; break; @@ -268,8 +290,13 @@ int main(int argc, char* argv[]) /* If the argument is a range, use special functions that can handle it. */ if( ipv4_range_check ) { - int result = is_ipv4_range(address_str, verbose); + if( range_prefix_length > 32 ) + { + fprintf(stderr, "Error: prefix length cannot exceed 32 for IPv4!\n"); + return(RESULT_INT_ERROR); + } + int result = is_ipv4_range(address_str, range_prefix_length, verbose); if( result == RESULT_SUCCESS ) { return(EXIT_SUCCESS); @@ -282,8 +309,13 @@ int main(int argc, char* argv[]) if( ipv6_range_check ) { - int result = is_ipv6_range(address_str, verbose); + if( range_prefix_length > 128 ) + { + fprintf(stderr, "Error: prefix length cannot exceed 32 for IPv4!\n"); + return(RESULT_INT_ERROR); + } + int result = is_ipv6_range(address_str, range_prefix_length, verbose); if( result == RESULT_SUCCESS ) { return(EXIT_SUCCESS); @@ -633,8 +665,11 @@ Address checking options:\n\ --is-ipv6-range Check if STRING is a valid IPv6 address range\n\ \n\ Behavior options:\n\ - --allow-loopback When used with --is-valid-intf-address,\n\ - makes IPv4 loopback addresses pass the check\n\ + --allow-loopback When used with --is-valid-intf-address,\n\ + makes IPv4 loopback addresses pass the check\n\ + --range-prefix-length When used with --is-ipv4-range or --is-ipv6-range,\n\ + requires the range boundaries to lie within\n\ + a prefix of given length\n\ \n\ Other options:\n\ --version Print version information and exit \n\ diff --git a/src/ipaddrcheck_functions.c b/src/ipaddrcheck_functions.c index e3e2c86..2b3cc22 100644 --- a/src/ipaddrcheck_functions.c +++ b/src/ipaddrcheck_functions.c @@ -460,7 +460,7 @@ int is_any_net(CIDR *address) * This function is patently unsafe, * whether it's safe to do what it does should be determined by its callers. */ -void split_range(char* range_str, char* left, char*right) +void split_range(char* range_str, char* left, char* right) { char* ptr = left; int length = strlen(range_str); @@ -487,6 +487,9 @@ void split_range(char* range_str, char* left, char*right) return; } +/* in6_addr fields are byte arrays, so we cannot compare them as numbers + * and needs custom comparison logic + */ int compare_ipv6(struct in6_addr *left, struct in6_addr *right) { int i = 0; @@ -501,7 +504,7 @@ int compare_ipv6(struct in6_addr *left, struct in6_addr *right) } /* Is it a valid IPv4 address range? */ -int is_ipv4_range(char* range_str, int verbose) +int is_ipv4_range(char* range_str, int prefix_length, int verbose) { int result = RESULT_SUCCESS; @@ -553,7 +556,33 @@ int is_ipv4_range(char* range_str, int verbose) if( left_in_addr->s_addr <= right_in_addr->s_addr ) { - result = RESULT_SUCCESS; + /* If non-zero prefix_length is given, + check if the right address is within the network of the first one. */ + if( prefix_length > 0 ) + { + char left_pref_str[19]; + + /* XXX: Prefix length size is checked elsewhere, so it can't be more than 2 characters (32) + and overflow cannot occur. + */ + sprintf(left_pref_str, "%s/%u", left, prefix_length); + CIDR* left_addr_with_pref = cidr_from_str(left_pref_str); + CIDR* left_net = cidr_addr_network(left_addr_with_pref); + if( cidr_contains(left_net, right_addr) == 0 ) + { + result = RESULT_SUCCESS; + } + else + { + result = RESULT_FAILURE; + } + cidr_free(left_addr_with_pref); + cidr_free(left_net); + } + else + { + result = RESULT_SUCCESS; + } } else { @@ -573,7 +602,7 @@ int is_ipv4_range(char* range_str, int verbose) } /* Is it a valid IPv6 address range? */ -int is_ipv6_range(char* range_str, int verbose) +int is_ipv6_range(char* range_str, int prefix_length, int verbose) { int result = RESULT_SUCCESS; @@ -625,7 +654,33 @@ int is_ipv6_range(char* range_str, int verbose) if( compare_ipv6(left_in6_addr, right_in6_addr) <= 0 ) { - result = RESULT_SUCCESS; + /* If non-zero prefix_length is given, + check if the right address is within the network of the first one. */ + if( prefix_length > 0 ) + { + char left_pref_str[44]; + + /* XXX: Prefix length size is checked elsewhere, so it can't be more than 3 characters (128) + and overflow cannot occur. + */ + sprintf(left_pref_str, "%s/%u", left, prefix_length); + CIDR* left_addr_with_pref = cidr_from_str(left_pref_str); + CIDR* left_net = cidr_addr_network(left_addr_with_pref); + if( cidr_contains(left_net, right_addr) == 0 ) + { + result = RESULT_SUCCESS; + } + else + { + result = RESULT_FAILURE; + } + cidr_free(left_addr_with_pref); + cidr_free(left_net); + } + else + { + result = RESULT_SUCCESS; + } } else { diff --git a/src/ipaddrcheck_functions.h b/src/ipaddrcheck_functions.h index c0d8603..9b5e55f 100644 --- a/src/ipaddrcheck_functions.h +++ b/src/ipaddrcheck_functions.h @@ -76,7 +76,7 @@ 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); -int is_ipv6_range(char* range_str, int verbose); +int is_ipv4_range(char* range_str, int prefix_length, int verbose); +int is_ipv6_range(char* range_str, int prefix_length, int verbose); #endif /* IPADDRCHECK_FUNCTIONS_H */ diff --git a/src/ipaddrcheck_functions.o b/src/ipaddrcheck_functions.o index e8e714d..a689dac 100644 Binary files a/src/ipaddrcheck_functions.o and b/src/ipaddrcheck_functions.o differ diff --git a/tests/check_ipaddrcheck.c b/tests/check_ipaddrcheck.c index 3b260c2..ebae1e1 100644 --- a/tests/check_ipaddrcheck.c +++ b/tests/check_ipaddrcheck.c @@ -405,17 +405,31 @@ 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); + ck_assert_int_eq(is_ipv4_range("192.0.2.0-192.0.2.10", 0, 1), RESULT_SUCCESS); + ck_assert_int_eq(is_ipv4_range("192.0.2.-", 0, 1), RESULT_FAILURE); + ck_assert_int_eq(is_ipv4_range("192.0.2.99-192.0.2.11", 0, 1), RESULT_FAILURE); +} +END_TEST + +START_TEST (test_is_ipv4_range_prefix) +{ + ck_assert_int_eq(is_ipv4_range("192.0.2.0-192.0.2.10", 24, 1), RESULT_SUCCESS); + ck_assert_int_eq(is_ipv4_range("10.0.1.1-10.0.2.1", 24, 1), RESULT_FAILURE); } END_TEST START_TEST (test_is_ipv6_range) { - ck_assert_int_eq(is_ipv6_range("2001:db8::1-2001:db8::20", 0), RESULT_SUCCESS); - ck_assert_int_eq(is_ipv6_range("2001:-", 0), RESULT_FAILURE); - ck_assert_int_eq(is_ipv6_range("2001:db8::99-2001:db8:1", 0), RESULT_FAILURE); + ck_assert_int_eq(is_ipv6_range("2001:db8::1-2001:db8::20", 0, 1), RESULT_SUCCESS); + ck_assert_int_eq(is_ipv6_range("2001:-", 0, 1), RESULT_FAILURE); + ck_assert_int_eq(is_ipv6_range("2001:db8::99-2001:db8:1", 0, 1), RESULT_FAILURE); +} +END_TEST + +START_TEST (test_is_ipv6_range_prefix) +{ + ck_assert_int_eq(is_ipv6_range("2001:db8::1-2001:db8::20", 64, 1), RESULT_SUCCESS); + ck_assert_int_eq(is_ipv6_range("2001:db8:aaaa::1-2001:db8:bbbb::1", 64, 1), RESULT_FAILURE); } END_TEST diff --git a/tests/integration_tests.sh b/tests/integration_tests.sh index 8264960..3a1e4ea 100755 --- a/tests/integration_tests.sh +++ b/tests/integration_tests.sh @@ -284,6 +284,9 @@ do assert_raises "$IPADDRCHECK --is-ipv4-range $range" 1 done +assert_raises "$IPADDRCHECK --range-prefix-length 24 --is-ipv4-range 10.0.0.1-10.0.0.10" 0 +assert_raises "$IPADDRCHECK --range-prefix-length 29 --is-ipv4-range 10.0.0.1-10.0.0.10" 1 + # --is-ipv6-range for range in \ ${ipv6_range_positive[*]} @@ -297,4 +300,7 @@ do assert_raises "$IPADDRCHECK --is-ipv6-range $range" 1 done +assert_raises "$IPADDRCHECK --range-prefix-length 64 --is-ipv6-range 2001:db8::1-2001:db8::100" 0 +assert_raises "$IPADDRCHECK --range-prefix-length 64 --is-ipv6-range 2001:db8:aaaa::1-2001:db8:bbbb::1" 1 + assert_end ipaddrcheck_integration -- cgit v1.2.3