/* $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 #include #include /* required for inet_aton() */ #include #include #include #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: : and processes it like: * -N 0.0.0.0/0: -N 0.0.0.0/0: * 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; }