/* * ipaddrcheck_functions.c: IPv4/IPv6 validation functions for ipaddrcheck * * Copyright (C) 2013 Daniil Baturin * Copyright (C) 2018-2024 VyOS maintainers and contributors * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include "ipaddrcheck_functions.h" /* * Address string functions * * Note that they perform format check only * and must not be used to deermine if it's * a valid address, only what type of address * format it is. * * The only reason they exist is that libcidr * is very liberal on its input format and * doesn't provide any information on what * the format was. */ int regex_matches(const char* regex, const char* str) { int offsets[1]; pcre *re; int rc; const char *error; int erroffset; 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 RESULT_SUCCESS; } else { return RESULT_FAILURE; } } /* Does it contain more than one double colon? IPv6 addresses allow replacing no more than one group of zeros with a '::' shortcut. */ int duplicate_double_colons(char* address_str) { return regex_matches(".*(::).*\\1", address_str); } /* Is it an IPv4 address with prefix length (e.g., 192.0.2.1/24)? */ int is_ipv4_cidr(char* address_str) { 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) { 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 (e.g., 2001:db8::1/64)? */ int is_ipv6_cidr(char* address_str) { 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) { return regex_matches("^(([0-9a-fA-F\\:])+)$", address_str); } /* Is it a CIDR-formatted IPv4 or IPv6 address? */ int is_any_cidr(char* address_str) { int result; if( (is_ipv4_cidr(address_str) == RESULT_SUCCESS) || (is_ipv6_cidr(address_str) == RESULT_SUCCESS) ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Is it a single IPv4 or IPv6 address? */ int is_any_single(char* address_str) { int result; if( (is_ipv4_single(address_str) == RESULT_SUCCESS) || (is_ipv6_single(address_str) == RESULT_SUCCESS) ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* * Address checking functions that rely on libcidr */ /* Does it look like a valid address of any protocol? */ int is_valid_address(CIDR *address) { int result; if( cidr_get_proto(address) != INVALID_PROTO ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Is it a correct IPv4 host or subnet address with or without net mask */ int is_ipv4(CIDR *address) { int result; if( cidr_get_proto(address) == CIDR_IPV4 ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Is it a correct IPv4 host address (i.e., not a network address)? */ int is_ipv4_host(CIDR *address) { int result; if( (cidr_get_proto(address) == CIDR_IPV4) && ((cidr_equals(address, cidr_addr_network(address)) < 0) || (cidr_get_pflen(address) >= 31)) ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Is it a correct IPv4 network address? */ int is_ipv4_net(CIDR *address) { int result; if( (cidr_get_proto(address) == CIDR_IPV4) && (cidr_equals(address, cidr_addr_network(address)) == 0) ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Is it an IPv4 broadcast address? */ int is_ipv4_broadcast(CIDR *address) { int result; /* The very concept of broadcast address doesn't apply to IPv6 and point-to-point (/31) or isolated (/32) IPv4 addresses. */ if( (cidr_get_proto(address) == CIDR_IPV4) && (cidr_equals(address, cidr_addr_broadcast(address)) == 0 ) && (cidr_get_pflen(address) < 31) ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Is it an IPv4 multicast address? */ int is_ipv4_multicast(CIDR *address) { int result; if( (cidr_get_proto(address) == CIDR_IPV4) && (cidr_contains(cidr_from_str(IPV4_MULTICAST), address) == 0) ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Is it an IPv4 loopback address? */ int is_ipv4_loopback(CIDR *address) { int result; if( (cidr_get_proto(address) == CIDR_IPV4) && (cidr_contains(cidr_from_str(IPV4_LOOPBACK), address) == 0) ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Is it an IPv4 link-local address? */ int is_ipv4_link_local(CIDR *address) { int result; if( (cidr_get_proto(address) == CIDR_IPV4) && (cidr_contains(cidr_from_str(IPV4_LINKLOCAL), address) == 0) ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Is it a private (RFC 1918) IPv4 address? */ int is_ipv4_rfc1918(CIDR *address) { int result; if( (cidr_get_proto(address) == CIDR_IPV4) && ( (cidr_contains(cidr_from_str(IPV4_RFC1918_A), address) == 0) || (cidr_contains(cidr_from_str(IPV4_RFC1918_B), address) == 0) || (cidr_contains(cidr_from_str(IPV4_RFC1918_C), address) == 0) ) ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* is it a correct IPv6 host or a subnet address, with or without network mask? */ int is_ipv6(CIDR *address) { int result; if( cidr_get_proto(address) == CIDR_IPV6 ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Is it a correct IPv6 host address? */ int is_ipv6_host(CIDR *address) { int result; if( (cidr_get_proto(address) == CIDR_IPV6) && ((cidr_equals(address, cidr_addr_network(address)) < 0) || (cidr_get_pflen(address) >= 127)) ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Is it a correct IPv6 network address? */ int is_ipv6_net(CIDR *address) { int result; if( (cidr_get_proto(address) == CIDR_IPV6) && (cidr_equals(address, cidr_addr_network(address)) == 0) ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Is it an IPv6 multicast address? */ int is_ipv6_multicast(CIDR *address) { int result; if( (cidr_get_proto(address) == CIDR_IPV6) && (cidr_contains(cidr_from_str(IPV6_MULTICAST), address) == 0) ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Is it an IPv6 link-local address? */ int is_ipv6_link_local(CIDR *address) { int result; if( (cidr_get_proto(address) == CIDR_IPV6) && (cidr_contains(cidr_from_str(IPV6_LINKLOCAL), address) == 0) ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Is it an address that can be assigned to a network interface? (i.e., is it a host address that is not reserved for any special use) */ int is_valid_intf_address(CIDR *address, char* address_str, int allow_loopback) { int result; if( (is_ipv4_broadcast(address) == RESULT_FAILURE) && (is_ipv4_multicast(address) == RESULT_FAILURE) && (is_ipv6_multicast(address) == RESULT_FAILURE) && ((is_ipv4_loopback(address) == RESULT_FAILURE) || (allow_loopback == LOOPBACK_ALLOWED)) && (cidr_equals(address, cidr_from_str(IPV6_LOOPBACK)) != 0) && (cidr_equals(address, cidr_from_str(IPV4_UNSPECIFIED)) != 0) && (cidr_contains(cidr_from_str(IPV4_THIS), address) != 0) && (cidr_equals(address, cidr_from_str(IPV4_LIMITED_BROADCAST)) != 0) && (is_any_host(address) == RESULT_SUCCESS) && (is_any_cidr(address_str) == RESULT_SUCCESS) ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Is it an IPv4 or IPv6 host address? */ int is_any_host(CIDR *address) { int result; if( (is_ipv4_host(address) == RESULT_SUCCESS) || (is_ipv6_host(address) == RESULT_SUCCESS) ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Is it an IPv4 or IPv6 network address? */ int is_any_net(CIDR *address) { int result; if( (is_ipv4_net(address) == RESULT_SUCCESS) || (is_ipv6_net(address) == RESULT_SUCCESS) ) { result = RESULT_SUCCESS; } else { result = RESULT_FAILURE; } return(result); } /* Split a hyphen-separated range into its left and right components. * 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) { 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'; 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; for( i = 0; i < 16; i++ ) { if (left->s6_addr[i] < right->s6_addr[i]) return -1; else if (left->s6_addr[i] > right->s6_addr[i]) return 1; } return 0; } /* Is it a valid IPv4 address range? */ int is_ipv4_range(char* range_str, int prefix_length, 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. */ /* Allocate memory for the components of the range. We need at most 15 characters for an IPv4 address, plus space for the terminating 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. */ split_range(range_str, left, right); 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 ) { /* 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 { 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); } /* Is it a valid IPv6 address range? */ int is_ipv6_range(char* range_str, int prefix_length, int verbose) { int result = RESULT_SUCCESS; int regex_check_res = regex_matches("^([0-9a-fA-F:]+\\-[0-9a-fA-F:]+)$", range_str); if( !regex_check_res ) { if( verbose ) { fprintf(stderr, "Malformed range %s: must be a pair of hyphen-separated IPv6 addresses\n", range_str); } result = RESULT_FAILURE; } else { /* Extract sub-components from the range string. */ /* Allocate memory for the components of the range. We need at most 39 characters for an IPv6 address, plus space for the terminating null byte. */ char left[40]; char right[40]; /* Split the string at the hyphen. If the regex check succeeded, we know the hyphen is there. */ split_range(range_str, left, right); if( !is_ipv6_single(left) ) { if( verbose ) { fprintf(stderr, "Malformed range %s: %s is not a valid IPv6 address\n", range_str, left); } result = RESULT_FAILURE; } else if( !is_ipv6_single(right) ) { if( verbose ) { fprintf(stderr, "Malformed range %s: %s is not a valid IPv6 address\n", range_str, right); } result = RESULT_FAILURE; } else { CIDR* left_addr = cidr_from_str(left); CIDR* right_addr = cidr_from_str(right); struct in6_addr* left_in6_addr = cidr_to_in6addr(left_addr, NULL); struct in6_addr* right_in6_addr = cidr_to_in6addr(right_addr, NULL); if( compare_ipv6(left_in6_addr, right_in6_addr) <= 0 ) { /* 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 { if( verbose ) { fprintf(stderr, "Malformed IPv6 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); }