diff --git a/doc/20-library-reference.md b/doc/20-library-reference.md index 409013cea..ecb3d7509 100644 --- a/doc/20-library-reference.md +++ b/doc/20-library-reference.md @@ -6,6 +6,7 @@ Function | Description --------------------------------|----------------------- regex(pattern, text) | Returns true if the regex pattern matches the text, false otherwise. match(pattern, text) | Returns true if the wildcard pattern matches the text, false otherwise. +cidr_match(pattern, ip) | Returns true if the CIDR pattern matches the IP address, false otherwise. IPv4 addresses are converted to IPv4-mapped IPv6 addresses before being matched against the pattern. len(value) | Returns the length of the value, i.e. the number of elements for an array or dictionary, or the length of the string in bytes. union(array, array, ...) | Returns an array containing all unique elements from the specified arrays. intersection(array, array, ...) | Returns an array containing all unique elements which are common to all specified arrays. diff --git a/lib/base/scriptutils.cpp b/lib/base/scriptutils.cpp index f9b5429bf..374f910e0 100644 --- a/lib/base/scriptutils.cpp +++ b/lib/base/scriptutils.cpp @@ -39,6 +39,7 @@ using namespace icinga; REGISTER_SCRIPTFUNCTION(regex, &ScriptUtils::Regex); REGISTER_SCRIPTFUNCTION(match, &Utility::Match); +REGISTER_SCRIPTFUNCTION(cidr_match, &Utility::CidrMatch); REGISTER_SCRIPTFUNCTION(len, &ScriptUtils::Len); REGISTER_SCRIPTFUNCTION(union, &ScriptUtils::Union); REGISTER_SCRIPTFUNCTION(intersection, &ScriptUtils::Intersection); diff --git a/lib/base/utility.cpp b/lib/base/utility.cpp index 6708a1e6c..4eaf70a12 100644 --- a/lib/base/utility.cpp +++ b/lib/base/utility.cpp @@ -143,6 +143,81 @@ bool Utility::Match(const String& pattern, const String& text) return (match(pattern.CStr(), text.CStr()) == 0); } +static void ParseIpMask(const String& ip, char mask[16], int *bits) +{ + String::SizeType slashp = ip.FindFirstOf("/"); + String uip; + + if (slashp == String::NPos) { + uip = ip; + *bits = 0; + } else { + uip = ip.SubStr(0, slashp); + *bits = Convert::ToLong(ip.SubStr(slashp + 1)); + } + + /* IPv4-mapped IPv6 address (::ffff:) */ + memset(mask, 0, 10); + memset(mask + 10, 0xff, 2); + if (inet_pton(AF_INET, uip.CStr(), mask + 12) < 1) { + if (inet_pton(AF_INET6, uip.CStr(), mask) < 1) { + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid IP address specified.")); + } + } else + *bits += 96; + + if (slashp == String::NPos) + *bits = 128; + + if (*bits > 128) + BOOST_THROW_EXCEPTION(std::invalid_argument("Mask must not be greater than 128.")); + + /* TODO: validate mask */ + for (int i = 0; i < 16; i++) { + int lbits = *bits - i * 8; + + if (lbits >= 8) + continue; + + if (mask[i] & (0xff >> lbits)) + BOOST_THROW_EXCEPTION(std::invalid_argument("Masked-off bits must all be zero.")); + } +} + +static bool IpMaskCheck(char addr[16], char mask[16], int bits) +{ + for (int i = 0; i < 16; i++) { + if (bits < 8) + return !((addr[i] ^ mask[i]) >> (8 - bits)); + + if (mask[i] != addr[i]) + return false; + + bits -= 8; + + if (bits == 0) + return true; + } + + return true; +} + +bool Utility::CidrMatch(const String& pattern, const String& ip) +{ + char mask[16], addr[16]; + int bits; + + try { + ParseIpMask(ip, addr, &bits); + } catch (const std::exception&) { + return false; + } + + ParseIpMask(pattern, mask, &bits); + + return IpMaskCheck(addr, mask, bits); +} + /** * Returns the directory component of a path. See dirname(3) for details. * diff --git a/lib/base/utility.hpp b/lib/base/utility.hpp index d13a5545b..4fc31990a 100644 --- a/lib/base/utility.hpp +++ b/lib/base/utility.hpp @@ -65,6 +65,7 @@ public: static String GetSymbolName(const void *addr); static bool Match(const String& pattern, const String& text); + static bool CidrMatch(const String& pattern, const String& ip); static String DirName(const String& path); static String BaseName(const String& path);