summaryrefslogtreecommitdiff
path: root/src/common/cidr.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/cidr.c')
-rw-r--r--src/common/cidr.c708
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;
+}