/* * ipaddrcheck.c: an IPv4/IPv6 validator * * Copyright (C) 2013 Daniil Baturin * Copyright (C) 2018-2024 VyOS maintainers and contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 or later as * published by the Free Software Foundation. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include "config.h" #include "ipaddrcheck_functions.h" /* Option codes */ #define IS_VALID 10 #define IS_IPV4 20 #define IS_IPV4_CIDR 30 #define IS_IPV4_SINGLE 40 #define IS_IPV4_HOST 50 #define IS_IPV4_NET 60 #define IS_IPV4_BROADCAST 70 #define IS_IPV4_UNICAST 80 #define IS_IPV4_MULTICAST 90 #define IS_IPV4_RFC1918 100 #define IS_IPV4_LOOPBACK 110 #define IS_IPV4_LINKLOCAL 120 #define IS_IPV6 130 #define IS_IPV6_CIDR 140 #define IS_IPV6_SINGLE 150 #define IS_IPV6_HOST 160 #define IS_IPV6_NET 170 #define IS_IPV6_UNICAST 180 #define IS_IPV6_MULTICAST 190 #define IS_IPV6_LINKLOCAL 200 #define IS_VALID_INTF_ADDR 220 #define IS_ANY_CIDR 230 #define IS_ANY_SINGLE 240 #define ALLOW_LOOPBACK 250 #define IS_ANY_HOST 260 #define IS_ANY_NET 270 /* XXX: These options are handled outside of the main switch * because they the main switch was design to handle * only single addresses directly parseable by libcidr. * Ideally, we should refactor that at some point in the future... */ #define IS_IPV4_RANGE 280 #define IS_IPV6_RANGE 290 #define NO_ACTION 500 static const struct option options[] = { { "is-valid", no_argument, NULL, 'a' }, { "is-ipv4", no_argument, NULL, 'c' }, { "is-ipv4-cidr", no_argument, NULL, 'd' }, { "is-ipv4-single", no_argument, NULL, 'e' }, { "is-any-cidr", no_argument, NULL, 'A' }, { "is-any-single", no_argument, NULL, 'B' }, { "is-ipv4-host", no_argument, NULL, 'f' }, { "is-ipv4-net", no_argument, NULL, 'g' }, { "is-ipv4-broadcast", no_argument, NULL, 'h' }, { "is-ipv4-multicast", no_argument, NULL, 'i' }, { "is-ipv4-loopback", no_argument, NULL, 'j' }, { "is-ipv4-link-local", no_argument, NULL, 'k' }, { "is-ipv4-rfc1918", no_argument, NULL, 'l' }, { "is-ipv6", no_argument, NULL, 'm' }, { "is-ipv6-cidr", no_argument, NULL, 'n' }, { "is-ipv6-single", no_argument, NULL, 'o' }, { "is-ipv6-host", no_argument, NULL, 'p' }, { "is-ipv6-net", no_argument, NULL, 'r' }, { "is-ipv6-multicast", no_argument, NULL, 's' }, { "is-ipv6-link-local", no_argument, NULL, 't' }, { "is-valid-intf-address", no_argument, NULL, 'u' }, { "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' }, { "range-prefix-length", required_argument, NULL, 'H' }, { "version", no_argument, NULL, 'z' }, { "help", no_argument, NULL, '?' }, { "verbose", no_argument, NULL, 'V' }, { NULL, no_argument, NULL, 0 } }; /* Auxiliary functions */ static void print_help(const char* program_name); static void print_version(void); int main(int argc, char* argv[]) { char *address_str = ""; /* IP address string obtained from arguments */ int action = 0; /* Action associated with given check option */ int* actions; /* Array of all given actions */ int action_count = 0; /* Actions array size */ int option_index = 0; /* Number of the current option for getopt call */ 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 */ /* Range checks are handled separately now, without a quick sanity check, * since their argument is not a single address and needs to be split first. */ int ipv4_range_check = 0; int ipv6_range_check = 0; int verbose = 0; const char* program_name = argv[0]; /* Program name for use in messages */ /* Parse options, convert to action codes, store in an array. */ /* Try to allocate memory for the actions array, abort if the attempt fails. */ actions = (int*)calloc(argc, sizeof(int)); if( errno == ENOMEM ) { fprintf(stderr, "Error: could not allocate memory!\n"); return(RESULT_INT_ERROR); } while( (optc = getopt_long(argc, argv, "acdefghijklmnoprstuzABCDEFGHV?", options, &option_index)) != -1 ) { switch(optc) { case 'a': action = IS_VALID; break; case 'b': break; case 'c': action = IS_IPV4; break; case 'd': action = IS_IPV4_CIDR; break; case 'e': action = IS_IPV4_SINGLE; break; case 'f': action = IS_IPV4_HOST; break; case 'g': action = IS_IPV4_NET; break; case 'h': action = IS_IPV4_BROADCAST; break; case 'i': action = IS_IPV4_MULTICAST; break; case 'j': action = IS_IPV4_LOOPBACK; break; case 'k': action = IS_IPV4_LINKLOCAL; break; case 'l': action = IS_IPV4_RFC1918; break; case 'm': action = IS_IPV6; break; case 'n': action = IS_IPV6_CIDR; break; case 'o': action = IS_IPV6_SINGLE; break; case 'p': action = IS_IPV6_HOST; break; case 'r': action = IS_IPV6_NET; break; case 's': action = IS_IPV6_MULTICAST; break; case 't': action = IS_IPV6_LINKLOCAL; break; case 'u': action = IS_VALID_INTF_ADDR; break; case 'A': action = IS_ANY_CIDR; break; case 'B': action = IS_ANY_SINGLE; break; case 'C': allow_loopback = LOOPBACK_ALLOWED; no_action = NO_ACTION; break; case 'D': action = IS_ANY_HOST; break; case 'E': action = IS_ANY_NET; break; case 'F': ipv4_range_check = 1; break; 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; case '?': print_help(program_name); return(EXIT_SUCCESS); case 'z': print_version(); return(EXIT_SUCCESS); default: fprintf(stderr, "Error: invalid option\n"); print_help(program_name); return(RESULT_INT_ERROR); } if( no_action != NO_ACTION ) { action_count = optind-2; actions[action_count] = action; } else { no_action = 0; /* Reset no_action */ } } /* Exit if no option given */ if( optind < 2 ) { fprintf(stderr, "Error: at least one option expected!\n"); print_help(program_name); return(RESULT_INT_ERROR); } /* Get non-option arguments */ if( (argc - optind) == 1 ) { address_str = argv[optind]; } else { fprintf(stderr, "Error: wrong number of arguments, one argument required!\n"); print_help(program_name); return(RESULT_INT_ERROR); } /* If the argument is a range, use special functions that can handle it. */ if( ipv4_range_check ) { 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); } else { return(EXIT_FAILURE); } } if( ipv6_range_check ) { 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); } else { return(EXIT_FAILURE); } } /* If ipaddrcheck is called with options other than --is-ipv4-range or --is-ipv6-range, * the argument is a single address that we can parse beforehand and pass to various checking functions. */ CIDR *address; address = cidr_from_str(address_str); int result = RESULT_SUCCESS; /* Check if the address is valid and well-formatted at all, if not there is no point in going further */ if( !( (is_valid_address(address) == RESULT_SUCCESS) && ((is_any_cidr(address_str) == RESULT_SUCCESS) || (is_any_single(address_str) == RESULT_SUCCESS)) ) ) { if( verbose ) { printf("Malformed address %s\n", address_str); } return(EXIT_FAILURE); } /* FIXUP: libcidr allows more than one double colon, but RFC 4291 does not! */ if( duplicate_double_colons(address_str) ) { if( verbose ) { printf("More than one \"::\" is not allowed in IPv6 addresses\n"); } return(EXIT_FAILURE); } while( (action_count >= 0) && (result == RESULT_SUCCESS) ) { switch(actions[action_count]) { case IS_VALID: result = is_valid_address(address); break; case IS_IPV4: result = is_ipv4(address); break; case IS_IPV4_CIDR: result = is_ipv4_cidr(address_str); break; case IS_IPV4_SINGLE: result = is_ipv4_single(address_str); break; case IS_IPV4_HOST: /* Host vs. network address check only makes sense if prefix length is given */ if( !(cidr_get_proto(address) == CIDR_IPV4) ) { if( verbose ) { printf("%s is not a valid IPv4 address\n", address_str); } result = RESULT_FAILURE; break; } if( !is_ipv4_cidr(address_str) ) { if( verbose ) { printf("Cannot check if %s is a valid host address: missing prefix length\n", address_str); } result = RESULT_FAILURE; } else { result = is_ipv4_host(address); if( (result == RESULT_FAILURE) && verbose ) { if( ((cidr_equals(address, cidr_addr_network(address)) >= 0) && (cidr_get_pflen(address) != 32)) ) { printf("%s is an IPv4 network address, not a host address\n", address_str); } } } break; case IS_IPV4_NET: /* Host vs. network address check only makes sense if prefix length is given */ if( !(cidr_get_proto(address) == CIDR_IPV4) ) { if( verbose ) { printf("%s is not a valid IPv4 address\n", address_str); } result = RESULT_FAILURE; break; } if( !is_ipv4_cidr(address_str) ) { if( verbose ) { printf("Cannot check if %s is a valid network address: missing prefix length\n", address_str); } result = RESULT_FAILURE; } else { result = is_ipv4_net(address); if( (result == RESULT_FAILURE) && verbose ) { if( ((cidr_equals(address, cidr_addr_network(address)) < 0) && (cidr_get_pflen(address) != 32)) ) { char* network_addr = cidr_to_str(cidr_addr_network(address), 0); printf("%s is an IPv4 host address, not a network address. Did you mean %s?\n", address_str, network_addr); } } } break; case IS_IPV4_BROADCAST: /* Broadcast address check only makes sense if prefix length is given */ if( !is_ipv4_cidr(address_str) ) { if( verbose ) { printf("Cannot check if %s is a broadcast address: missing prefix length\n", address_str); } result = RESULT_FAILURE; } else { result = is_ipv4_broadcast(address); } break; case IS_IPV4_MULTICAST: result = is_ipv4_multicast(address); break; case IS_IPV4_LOOPBACK: result = is_ipv4_loopback(address); break; case IS_IPV4_LINKLOCAL: result = is_ipv4_link_local(address); break; case IS_IPV4_RFC1918: result = is_ipv4_rfc1918(address); break; case IS_IPV6: result = is_ipv6(address); break; case IS_IPV6_CIDR: result = is_ipv6_cidr(address_str); break; case IS_IPV6_SINGLE: result = is_ipv6_single(address_str); break; case IS_IPV6_HOST: /* Host vs. network address check only makes sense if prefix length is given */ if( !(cidr_get_proto(address) == CIDR_IPV6) ) { if( verbose ) { printf("%s is not a valid IPv6 address\n", address_str); } result = RESULT_FAILURE; break; } if( !is_ipv6_cidr(address_str) ) { if( verbose ) { printf("Cannot check if %s is a valid IPv6 host address: missing prefix length\n", address_str); } result = RESULT_FAILURE; } else { result = is_ipv6_host(address); if( (result == RESULT_FAILURE) && verbose ) { if( ((cidr_equals(address, cidr_addr_network(address)) >= 0) && (cidr_get_pflen(address) != 128)) ) { printf("%s is an IPv6 network address, not a host address\n", address_str); } } } break; case IS_IPV6_NET: /* Host vs. network address check only makes sense if prefix length is given */ if( !(cidr_get_proto(address) == CIDR_IPV6) ) { if( verbose ) { printf("%s is not a valid IPv6 address\n", address_str); } result = RESULT_FAILURE; break; } if( !is_ipv6_cidr(address_str) ) { if( verbose ) { printf("Cannot check if %s is a valid IPv6 network address: missing prefix length\n", address_str); } result = RESULT_FAILURE; } else { result = is_ipv6_net(address); if( (result == RESULT_FAILURE) && verbose ) { if( ((cidr_equals(address, cidr_addr_network(address)) < 0) && (cidr_get_pflen(address) != 128)) ) { char* network_addr = cidr_to_str(cidr_addr_network(address), 0); printf("%s is an IPv6 host address, not a network address. Did you mean %s?\n", address_str, network_addr); } } } break; case IS_IPV6_MULTICAST: result = is_ipv6_multicast(address); break; case IS_IPV6_LINKLOCAL: result = is_ipv6_link_local(address); break; case IS_ANY_CIDR: result = is_any_cidr(address_str); break; case IS_ANY_SINGLE: result = is_any_single(address_str); break; case IS_VALID_INTF_ADDR: result = is_valid_intf_address(address, address_str, allow_loopback); break; case NO_ACTION: break; case IS_ANY_HOST: /* Host vs. network address check only makes sense if prefix length is given */ if( !is_any_cidr(address_str) ) { if( verbose ) { printf("Cannot check if %s is a valid host address: missing prefix length\n", address_str); } result = RESULT_FAILURE; } else { result = is_any_host(address); if( (result == RESULT_FAILURE) && verbose ) { if( ((cidr_equals(address, cidr_addr_network(address)) >= 0) && (cidr_get_pflen(address) != 32) && (cidr_get_pflen(address) != 128)) ) { printf("%s is a network address, not a host address\n", address_str); } } } break; case IS_ANY_NET: /* Host vs. network address check only makes sense if prefix length is given */ if( !is_any_cidr(address_str) ) { if( verbose ) { printf("Cannot check if %s is a valid network address: missing prefix length\n", address_str); } result = RESULT_FAILURE; } else { result = is_any_net(address); if( (result == RESULT_FAILURE) && verbose ) { if( ((cidr_equals(address, cidr_addr_network(address)) < 0) && (cidr_get_pflen(address) != 128) && (cidr_get_pflen(address) != 32)) ) { char* network_addr = cidr_to_str(cidr_addr_network(address), 0); printf("%s is a host address, not a network address. Did you mean %s?\n", address_str, network_addr); } } } break; default: break; } action_count--; } /* Clean up */ free(actions); cidr_free(address); if( result == RESULT_SUCCESS ) { return(EXIT_SUCCESS); } else { return(EXIT_FAILURE); } } /* * Print help, no other side effects */ void print_help(const char* program_name) { printf("Usage: %s [STRING]\n", program_name); printf("\ Address checking options:\n\ --is-valid Check if STRING is a valid IPv4 or IPv6 address\n\ with or without prefix length\n\ --is-any-cidr Check if STRING is a valid IPv4 or IPv6 address\n\ with prefix length\n\ --is-any-single Check if STRING is a valid single IPv4 or IPv6 address\n\ --is-any-host Check if STRING is a valid IPv4 or IPv6 host address\n\ --is-any-net Check if STRING is a valid IPv4 or IPv6 network address\n\ --is-ipv4 Check if STRING is a valid IPv4 address with mask \n\ --is-ipv4-cidr Check if STRING is a valid CIDR-formatted address \n\ --is-ipv4-single Check if STRING is a valid single address\n\ (i.e. with no mask)\n\ --is-ipv4-host Check if STRING is a host address \n\ --is-ipv4-net Check if STRING is a network address \n\ --is-ipv4-broadcast Check if STRING is a broadcast address \n\ --is-ipv4-multicast Check if STRING is a multicast address \n\ --is-ipv4-loopback Check if STRING is a loopback address \n\ --is-ipv4-link-local Check if STRING is a link-local address \n\ --is-ipv4-rfc1918 Check if STRING is a private (RFC1918) address \n\ --is-ipv6 Check if STRING is a valid IPv6 address \n\ --is-ipv6-cidr Check if STRING is a CIDR-formatted IPv6 address \n\ --is-ipv6-single Check if STRING is an IPv6 address with no mask \n\ --is-ipv6-host Check if STRING is an IPv6 host address \n\ --is-ipv6-net Check if STRING is an IPv6 network address \n\ --is-ipv6-multicast Check if STRING is an IPv6 multicast address \n\ --is-ipv6-link-local Check if STRING is an IPv6 link-local address \n\ --is-valid-intf-address Check if STRING is an IPv4 or IPv6 address that \n\ can be assigned to a network interface \n\ --is-ipv4-range Check if STRING is a valid IPv4 address range\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\ --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\ --help Print help message and exit\n\ \n\ Exit codes:\n\ 0 if check passed,\n\ 1 if check failed,\n\ 2 if a problem occured (wrong option, internal error etc.)\n"); } /* * Print version information, no other side effects */ void print_version(void) { printf("%s %s\n\n", PACKAGE_NAME, PACKAGE_VERSION); printf("Copyright (C) 2024 VyOS maintainers and contributors.\n\ License GPLv2+: GNU GPL version 2 or later \n\ This is free software: you are free to change and redistribute it.\n\ There is NO WARRANTY, to the extent permitted by law.\n"); }