diff options
Diffstat (limited to 'src/common/cidr.c')
| -rw-r--r-- | src/common/cidr.c | 708 |
1 files changed, 708 insertions, 0 deletions
diff --git a/src/common/cidr.c b/src/common/cidr.c new file mode 100644 index 0000000..ef917ee --- /dev/null +++ b/src/common/cidr.c @@ -0,0 +1,708 @@ +/* $Id: cidr.c 2423 2010-03-13 07:09:49Z aturner $ */ + +/* + * Copyright (c) 2001-2010 Aaron Turner. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#include "config.h" +#include "defines.h" +#include "common.h" +#include "lib/strlcpy.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +/* required for inet_aton() */ +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + + +#ifdef DEBUG +extern int debug; +#endif + +static tcpr_cidr_t *cidr2cidr(char *); + +/** + * prints to the given fd all the entries in mycidr + */ +void +print_cidr(tcpr_cidr_t * mycidr) +{ + tcpr_cidr_t *cidr_ptr; + + fprintf(stderr, "Cidr List: "); + + cidr_ptr = mycidr; + while (cidr_ptr != NULL) { + /* print it */ + fprintf(stderr, "%s/%d, ", get_cidr2name(cidr_ptr, RESOLVE), + cidr_ptr->masklen); + + /* go to the next */ + if (cidr_ptr->next != NULL) { + cidr_ptr = cidr_ptr->next; + } + else { + break; + } + } + fprintf(stderr, "\n"); +} + +/** + * deletes all entries in a cidr and destroys the datastructure + */ +void +destroy_cidr(tcpr_cidr_t * cidr) +{ + + if (cidr != NULL) + if (cidr->next != NULL) + destroy_cidr(cidr->next); + + safe_free(cidr); + return; + +} + +/** + * adds a new tcpr_cidr_t entry to cidrdata + */ +void +add_cidr(tcpr_cidr_t ** cidrdata, tcpr_cidr_t ** newcidr) +{ + tcpr_cidr_t *cidr_ptr; + dbg(1, "Running new_cidr()"); + + if (*cidrdata == NULL) { + *cidrdata = *newcidr; + } else { + cidr_ptr = *cidrdata; + + while (cidr_ptr->next != NULL) + cidr_ptr = cidr_ptr->next; + + cidr_ptr->next = *newcidr; + } +} + +/** + * takes in an IP and masklen, and returns a string in + * cidr format: x.x.x.x/y. This malloc's memory. + */ +u_char * +ip2cidr(const unsigned long ip, const int masklen) +{ + u_char *network; + char mask[3]; + + network = (u_char *)safe_malloc(20); + + strlcpy((char *)network, (char *)get_addr2name4(ip, RESOLVE), + sizeof(network)); + + strcat((char *)network, "/"); + if (masklen < 10) { + snprintf(mask, 1, "%d", masklen); + strncat((char *)network, mask, 1); + } else { + snprintf(mask, 2, "%d", masklen); + strncat((char *)network, mask, 2); + } + + return (network); +} + +/** + * Mallocs and sets to sane defaults a tcpr_cidr_t structure + */ + +tcpr_cidr_t * +new_cidr(void) +{ + tcpr_cidr_t *newcidr; + + newcidr = (tcpr_cidr_t *)safe_malloc(sizeof(tcpr_cidr_t)); + + memset(newcidr, '\0', sizeof(tcpr_cidr_t)); + newcidr->masklen = 99; + newcidr->next = NULL; + + return (newcidr); +} + +/** + * Creates a new tcpr_cidrmap_t structure. Malloc's memory + */ +tcpr_cidrmap_t * +new_cidr_map(void) +{ + tcpr_cidrmap_t *new; + + new = (tcpr_cidrmap_t *)safe_malloc(sizeof(tcpr_cidrmap_t)); + + memset(new, '\0', sizeof(tcpr_cidrmap_t)); + new->next = NULL; + + return (new); +} + + +/** + * Converts a single cidr (string) in the form of x.x.x.x/y into a + * tcpr_cidr_t structure. Will malloc the tcpr_cidr_t structure. + */ +static tcpr_cidr_t * +cidr2cidr(char *cidr) +{ + int count = 0; + unsigned int octets[4]; /* used in sscanf */ + tcpr_cidr_t *newcidr; + char networkip[16], tempoctet[4], ebuf[EBUF_SIZE]; + int family; + char* p; + + assert(cidr); + assert(strlen(cidr) <= EBUF_SIZE); + + newcidr = new_cidr(); + + for (p = cidr; *p; ++p) { + if (*p == '#') { + *p = ':'; + } else if (*p == ']') { + *p = 0; + break; + } + } + + /* + * scan it, and make sure it scanned correctly, also copy over the + * masklen + */ + count = sscanf(cidr, "%u.%u.%u.%u/%d", &octets[0], &octets[1], + &octets[2], &octets[3], &newcidr->masklen); + + if (count == 4) { + newcidr->masklen = 32; + family = AF_INET; + } else if (count == 5) { + family = AF_INET; + } else { + p = strstr(cidr, "/"); + if (p) { + *p = 0; + ++p; + count = sscanf(p, "%d", &newcidr->masklen); + } else { + newcidr->masklen = 128; + } + + if (newcidr->masklen < 0 || newcidr->masklen > 128) + goto error; + + /* skip past the opening [ */ + if (*cidr == '[') + cidr ++; + + if (get_name2addr6(cidr, RESOLVE, &newcidr->u.network6) > 0) { + family = AF_INET6; + } else { + goto error; + } + } + + if (family == AF_INET) { + /* masklen better be 0 =< masklen <= 32 */ + if (newcidr->masklen > 32) + goto error; + + /* copy in the ip address */ + memset(networkip, '\0', 16); + for (count = 0; count < 4; count++) { + if (octets[count] > 255) + goto error; + + snprintf(tempoctet, sizeof(octets[count]), "%d", octets[count]); + strcat(networkip, tempoctet); + /* we don't want a '.' at the end of the last octet */ + if (count < 3) + strcat(networkip, "."); + } + + /* copy over the network address and return */ +#ifdef HAVE_INET_ATON + inet_aton(networkip, (struct in_addr *)&newcidr->u.network); +#elif HAVE_INET_ADDR + newcidr->network = inet_addr(networkip); +#endif + } else if (family == AF_INET6) { + /* Everything's done */ + } else { + goto error; + } + + newcidr->family = family; + return (newcidr); + + /* we only get here on error parsing input */ +error: + memset(ebuf, '\0', EBUF_SIZE); + strcpy(ebuf, "Unable to parse as a vaild CIDR: "); + strlcat(ebuf, cidr, EBUF_SIZE); + errx(-1, "%s", ebuf); + return NULL; +} + +static void +mask_cidr6(char **cidrin, char* delim) +{ + char *p; + + if (**cidrin == '[' && *delim == ':') { + ++*cidrin; + /* make strtok happy */ + for (p = *cidrin; *p && *p != ']'; ++p) { + if (*p == ':') { + *p = '#'; + } + } + } +} + +/** + * parses a list of tcpr_cidr_t's input from the user which should be in the form + * of x.x.x.x/y,x.x.x.x/y... + * returns 1 for success, or fails to return on failure (exit 1) + * since we use strtok to process cidr, it gets zeroed out. + */ +int +parse_cidr(tcpr_cidr_t ** cidrdata, char *cidrin, char *delim) +{ + tcpr_cidr_t *cidr_ptr; /* ptr to current cidr record */ + char *network = NULL; + char *token = NULL; + + mask_cidr6(&cidrin, delim); + + /* first itteration of input using strtok */ + network = strtok_r(cidrin, delim, &token); + + *cidrdata = cidr2cidr(network); + cidr_ptr = *cidrdata; + + /* do the same with the rest of the input */ + while (1) { + if (token) + mask_cidr6(&token, delim); + + network = strtok_r(NULL, delim, &token); + /* if that was the last CIDR, then kickout */ + if (network == NULL) + break; + + /* next record */ + cidr_ptr->next = cidr2cidr(network); + cidr_ptr = cidr_ptr->next; + } + return 1; + +} + +/** + * parses a pair of IP addresses: <IP1>:<IP2> and processes it like: + * -N 0.0.0.0/0:<IP1> -N 0.0.0.0/0:<IP2> + * returns 1 for success or returns 0 on failure + * since we use strtok to process optarg, it gets zeroed out + */ +int +parse_endpoints(tcpr_cidrmap_t ** cidrmap1, tcpr_cidrmap_t ** cidrmap2, const char *optarg) +{ +#define NEWMAP_LEN (INET6_ADDRSTRLEN * 2) + char *map = NULL, newmap[NEWMAP_LEN]; + char *token = NULL; + char *string; + char *p; + + string = safe_strdup(optarg); + + if (*string == '[') { + /* ipv6 mode */ + memset(newmap, '\0', NEWMAP_LEN); + p = strstr(string, "]:["); + if (!p) + return 0; + + *p = 0; + strlcpy(newmap, "[::/0]:", NEWMAP_LEN); + strlcat(newmap, string, NEWMAP_LEN); + strlcat(newmap, "]", NEWMAP_LEN); + + if (! parse_cidr_map(cidrmap1, newmap)) + return 0; + + /* do again with the second IP */ + memset(newmap, '\0', NEWMAP_LEN); + strlcpy(newmap, "[::/0]:", NEWMAP_LEN); + strlcat(newmap, p + 2, NEWMAP_LEN); + + if (! parse_cidr_map(cidrmap2, newmap)) + return 0; + + } else { + /* ipv4 mode */ + memset(newmap, '\0', NEWMAP_LEN); + map = strtok_r(string, ":", &token); + + strlcpy(newmap, "0.0.0.0/0:", NEWMAP_LEN); + strlcat(newmap, map, NEWMAP_LEN); + if (! parse_cidr_map(cidrmap1, newmap)) + return 0; + + /* do again with the second IP */ + memset(newmap, '\0', NEWMAP_LEN); + map = strtok_r(NULL, ":", &token); + + strlcpy(newmap, "0.0.0.0/0:", NEWMAP_LEN); + strlcat(newmap, map, NEWMAP_LEN); + if (! parse_cidr_map(cidrmap2, newmap)) + return 0; + } + + safe_free(string); + return 1; /* success */ +} + + +/** + * parses a list of tcpr_cidrmap_t's input from the user which should be in the form + * of x.x.x.x/y:x.x.x.x/y,... + * IPv6 syntax: [addr/y]:[addr/y],... + * returns 1 for success, or returns 0 on failure + * since we use strtok to process optarg, it gets zeroed out. + */ +int +parse_cidr_map(tcpr_cidrmap_t **cidrmap, const char *optarg) +{ + tcpr_cidr_t *cidr = NULL; + char *map = NULL; + char *token = NULL, *string = NULL; + tcpr_cidrmap_t *ptr; + + string = safe_strdup(optarg); + + /* first iteration */ + map = strtok_r(string, ",", &token); + if (! parse_cidr(&cidr, map, ":")) + return 0; + + /* must return a linked list of two */ + if (cidr->next == NULL) + return 0; + + /* copy over */ + *cidrmap = new_cidr_map(); + ptr = *cidrmap; + + ptr->from = cidr; + ptr->to = cidr->next; + ptr->from->next = NULL; + + /* do the same with the reset of the input */ + while(1) { + map = strtok_r(NULL, ",", &token); + if (map == NULL) + break; + + if (! parse_cidr(&cidr, map, ":")) + return 0; + + /* must return a linked list of two */ + if (cidr->next == NULL) + return 0; + + /* copy over */ + ptr->next = new_cidr_map(); + ptr = ptr->next; + ptr->from = cidr; + ptr->to = cidr->next; + ptr->from->next = NULL; + } + + safe_free(string); + return 1; /* success */ +} + +/** + * checks to see if the ip address is in the cidr + * returns 1 for true, 0 for false + */ +int +ip_in_cidr(const tcpr_cidr_t * mycidr, const unsigned long ip) +{ + unsigned long ipaddr = 0, network = 0, mask = 0; + int ret = 0; +#ifdef DEBUG + char netstr[20]; +#endif + + if (mycidr->family != AF_INET) + return 0; + + /* always return 1 if 0.0.0.0/0 */ + if (mycidr->masklen == 0 && mycidr->u.network == 0) + return 1; + + mask = ~0; /* turn on all the bits */ + + /* shift over by the correct number of bits */ + mask = mask << (32 - mycidr->masklen); + + /* apply the mask to the network and ip */ + ipaddr = ntohl(ip) & mask; + + network = htonl(mycidr->u.network) & mask; + + +#ifdef DEBUG + /* copy this for debug purposes, since it's not re-entrant */ + strlcpy(netstr, get_addr2name4(htonl(mycidr->u.network), RESOLVE), 20); +#endif + + /* if they're the same, then ip is in network */ + if (network == ipaddr) { +#ifdef DEBUG + dbgx(1, "The ip %s is inside of %s/%d", + get_addr2name4(ip, RESOLVE), netstr, mycidr->masklen); +#endif + ret = 1; + } else { +#ifdef DEBUG + dbgx(1, "The ip %s is not inside of %s/%d", + get_addr2name4(ip, RESOLVE), netstr, mycidr->masklen); +#endif + ret = 0; + } + return ret; + +} + +static int +ip6_addr_is_unspec(const struct tcpr_in6_addr *addr) +{ + return addr->tcpr_s6_addr32[0] == 0 && addr->tcpr_s6_addr32[1] == 0 && + addr->tcpr_s6_addr32[2] == 0 && addr->tcpr_s6_addr32[3] == 0; +} + +int +ip6_in_cidr(const tcpr_cidr_t * mycidr, const struct tcpr_in6_addr *addr) +{ + int ret = 0; +#ifdef DEBUG + char netstr[INET6_ADDRSTRLEN]; +#endif + int i, j, k; + + if (mycidr->family != AF_INET6) + return 0; + + /* always return 1 if ::/0 */ + if (mycidr->masklen == 0 && ip6_addr_is_unspec(addr)) + return 1; + + j = mycidr->masklen / 8; + + for (i = 0; i < j; i++) { + if (addr->tcpr_s6_addr[i] != mycidr->u.network6.tcpr_s6_addr[i]) { + ret = 0; + goto out; + } + } + + if ((k = mycidr->masklen % 8) == 0) { + ret = 1; + goto out; + } + + k = ~0 << (8 - k); + i = addr->tcpr_s6_addr[j] & k; + j = mycidr->u.network6.tcpr_s6_addr[j] & k; + ret = i == j; +out: + +#ifdef DEBUG + /* copy this for debug purposes, since it's not re-entrant */ + strlcpy(netstr, get_addr2name6(&mycidr->u.network6, RESOLVE), INET6_ADDRSTRLEN); +#endif + + /* if they're the same, then ip is in network */ + if (ret) { +#ifdef DEBUG + dbgx(1, "The ip %s is inside of %s/%d", + get_addr2name6(addr, RESOLVE), netstr, mycidr->masklen); +#endif + } else { +#ifdef DEBUG + dbgx(1, "The ip %s is not inside of %s/%d", + get_addr2name6(addr, RESOLVE), netstr, mycidr->masklen); +#endif + } + return ret; + +} + + +/** + * iterates over cidrdata to find if a given ip matches + * returns 1 for true, 0 for false + */ + +int +check_ip_cidr(tcpr_cidr_t * cidrdata, const unsigned long ip) +{ + tcpr_cidr_t *mycidr; + + /* if we have no cidrdata, of course it isn't in there + * this actually should happen occasionally, so don't put an assert here + */ + if (cidrdata == NULL) + return 1; + + mycidr = cidrdata; + + /* loop through cidr */ + while (1) { + + /* if match, return 1 */ + if (ip_in_cidr(mycidr, ip)) { + dbgx(3, "Found %s in cidr", get_addr2name4(ip, RESOLVE)); + return 1; + } + + /* check for next record */ + if (mycidr->next != NULL) { + mycidr = mycidr->next; + } else { + break; + } + } + + /* if we get here, no match */ + dbgx(3, "Didn't find %s in cidr", get_addr2name4(ip, RESOLVE)); + return 0; +} + +int +check_ip6_cidr(tcpr_cidr_t * cidrdata, const struct tcpr_in6_addr *addr) +{ + tcpr_cidr_t *mycidr; + + /* if we have no cidrdata, of course it isn't in there + * this actually should happen occasionally, so don't put an assert here + */ + if (cidrdata == NULL) { + return 1; + } + + mycidr = cidrdata; + + /* loop through cidr */ + while (1) { + + /* if match, return 1 */ + if (ip6_in_cidr(mycidr, addr)) { + dbgx(3, "Found %s in cidr", get_addr2name6(addr, RESOLVE)); + return 1; + } + + /* check for next record */ + if (mycidr->next != NULL) { + mycidr = mycidr->next; + } else { + break; + } + } + + /* if we get here, no match */ + dbgx(3, "Didn't find %s in cidr", get_addr2name6(addr, RESOLVE)); + return 0; +} + + +/** + * cidr2ip takes a tcpr_cidr_t and a delimiter + * and returns a string which lists all the IP addresses in the cidr + * deliminated by the given char + */ +char * +cidr2iplist(tcpr_cidr_t * cidr, char delim) +{ + char *list = NULL; + char ipaddr[16]; + u_int32_t size, addr, first, last, numips; + struct in_addr in; + + /* + * 16 bytes per IP + delim + * # of IP's = 2^(32-masklen) + */ + numips = 2; + for (int i = 2; i <= (32 - cidr->masklen); i++) + numips *= 2; + + size = 16 * numips; + + list = (char *)safe_malloc(size); + + memset(list, 0, size); + + /* first and last should not include network or broadcast */ + first = ntohl(cidr->u.network) + 1; + last = first + numips - 3; + + dbgx(1, "First: %u\t\tLast: %u", first, last); + + /* loop through all but the last one */ + for (addr = first; addr < last; addr++) { + in.s_addr = htonl(addr); + snprintf(ipaddr, 17, "%s%c", inet_ntoa(in), delim); + dbgx(2, "%s", ipaddr); + strlcat(list, ipaddr, size); + } + + /* last is a special case, end in \0 */ + in.s_addr = htonl(addr); + snprintf(ipaddr, 16, "%s", inet_ntoa(in)); + strlcat(list, ipaddr, size); + + return list; +} |
