mirror of
https://github.com/znc/znc.git
synced 2026-03-28 17:42:41 +01:00
@@ -65,7 +65,7 @@ class CUser {
|
||||
bool AddAllowedHost(const CString& sHostMask);
|
||||
bool RemAllowedHost(const CString& sHostMask);
|
||||
void ClearAllowedHosts();
|
||||
bool IsHostAllowed(const CString& sHostMask) const;
|
||||
bool IsHostAllowed(const CString& sHost) const;
|
||||
bool IsValid(CString& sErrMsg, bool bSkipPass = false) const;
|
||||
static bool IsValidUserName(const CString& sUserName);
|
||||
static CString MakeCleanUserName(const CString& sUserName);
|
||||
|
||||
@@ -83,6 +83,11 @@ class CUtils {
|
||||
static timeval ParseServerTime(const CString& sTime);
|
||||
static SCString GetTimezones();
|
||||
static SCString GetEncodings();
|
||||
/** CIDR notation checker, e.g. "192.0.2.0/24" or "2001:db8::/32"
|
||||
*
|
||||
* For historical reasons also allows wildcards, e.g. "192.168.*"
|
||||
*/
|
||||
static bool CheckCIDR(const CString& sIP, const CString& sRange);
|
||||
|
||||
/// @deprecated Use CMessage instead
|
||||
static MCString GetMessageTags(const CString& sLine);
|
||||
|
||||
@@ -163,7 +163,7 @@ void CHTTPSock::ReadLine(const CString& sData) {
|
||||
// check if sIP is trusted proxy
|
||||
bool bTrusted = false;
|
||||
for (const CString& sTrustedProxy : vsTrustedProxies) {
|
||||
if (sIP.WildCmp(sTrustedProxy)) {
|
||||
if (CUtils::CheckCIDR(sIP, sTrustedProxy)) {
|
||||
bTrusted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
104
src/User.cpp
104
src/User.cpp
@@ -849,112 +849,14 @@ bool CUser::RemAllowedHost(const CString& sHostMask) {
|
||||
}
|
||||
void CUser::ClearAllowedHosts() { m_ssAllowedHosts.clear(); }
|
||||
|
||||
bool CUser::IsHostAllowed(const CString& sHostMask) const {
|
||||
bool CUser::IsHostAllowed(const CString& sHost) const {
|
||||
if (m_ssAllowedHosts.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const CString& sHost : m_ssAllowedHosts) {
|
||||
if (sHostMask.WildCmp(sHost)) {
|
||||
for (const CString& sAllowedHost : m_ssAllowedHosts) {
|
||||
if (CUtils::CheckCIDR(sHost, sAllowedHost)) {
|
||||
return true;
|
||||
} else {
|
||||
// CIDR notation checker, e.g. "192.0.2.0/24" or "2001:db8::/32"
|
||||
|
||||
// Try to split the string into an IP and routing prefix
|
||||
VCString vsSplitCIDR;
|
||||
if (sHost.Split("/", vsSplitCIDR, false) != 2) continue;
|
||||
const CString sRoutingPrefix = vsSplitCIDR.back();
|
||||
const int iRoutingPrefix = sRoutingPrefix.ToInt();
|
||||
if (iRoutingPrefix < 0 || iRoutingPrefix > 128) continue;
|
||||
|
||||
// If iRoutingPrefix is 0, it could be due to ToInt() failing, so
|
||||
// sRoutingPrefix needs to be all zeroes
|
||||
if (iRoutingPrefix == 0 && sRoutingPrefix != "0") continue;
|
||||
|
||||
// Convert each IP from a numeric string to an addrinfo
|
||||
addrinfo aiHints;
|
||||
memset(&aiHints, 0, sizeof(addrinfo));
|
||||
aiHints.ai_flags = AI_NUMERICHOST;
|
||||
|
||||
addrinfo* aiHost;
|
||||
int iIsHostValid =
|
||||
getaddrinfo(sHostMask.c_str(), nullptr, &aiHints, &aiHost);
|
||||
if (iIsHostValid != 0) continue;
|
||||
|
||||
aiHints.ai_family = aiHost->ai_family; // Host and range must be in
|
||||
// the same address family
|
||||
|
||||
addrinfo* aiRange;
|
||||
int iIsRangeValid = getaddrinfo(vsSplitCIDR.front().c_str(),
|
||||
nullptr, &aiHints, &aiRange);
|
||||
if (iIsRangeValid != 0) {
|
||||
freeaddrinfo(aiHost);
|
||||
continue;
|
||||
}
|
||||
|
||||
// "/0" allows all IPv[4|6] addresses
|
||||
if (iRoutingPrefix == 0) {
|
||||
freeaddrinfo(aiHost);
|
||||
freeaddrinfo(aiRange);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If both IPs are valid and of the same type, make a bit field mask
|
||||
// from the routing prefix, AND it to the host and range, and see if
|
||||
// they match
|
||||
bool bIsHostInRange = false;
|
||||
if (aiHost->ai_family == AF_INET) {
|
||||
if (iRoutingPrefix > 32) {
|
||||
freeaddrinfo(aiHost);
|
||||
freeaddrinfo(aiRange);
|
||||
continue;
|
||||
}
|
||||
|
||||
const sockaddr_in* saHost = (sockaddr_in*)(aiHost->ai_addr);
|
||||
const sockaddr_in* saRange = (sockaddr_in*)(aiRange->ai_addr);
|
||||
|
||||
// Make IPv4 bitmask
|
||||
const in_addr_t inBitmask =
|
||||
htonl((~0u) << (32 - iRoutingPrefix));
|
||||
|
||||
// Compare masked IPv4s
|
||||
bIsHostInRange = ((inBitmask & saHost->sin_addr.s_addr) ==
|
||||
(inBitmask & saRange->sin_addr.s_addr));
|
||||
} else if (aiHost->ai_family == AF_INET6) {
|
||||
// Make IPv6 bitmask
|
||||
in6_addr in6aBitmask;
|
||||
memset(&in6aBitmask, 0, sizeof(in6aBitmask));
|
||||
|
||||
for (int i = 0, iBitsLeft = iRoutingPrefix; iBitsLeft > 0;
|
||||
++i, iBitsLeft -= 8) {
|
||||
if (iBitsLeft >= 8) {
|
||||
in6aBitmask.s6_addr[i] = (uint8_t)(~0u);
|
||||
} else {
|
||||
in6aBitmask.s6_addr[i] = (uint8_t)(~0u)
|
||||
<< (8 - iBitsLeft);
|
||||
}
|
||||
}
|
||||
|
||||
// Compare masked IPv6s
|
||||
bIsHostInRange = true;
|
||||
const sockaddr_in6* sa6Host = (sockaddr_in6*)(aiHost->ai_addr);
|
||||
const sockaddr_in6* sa6Range =
|
||||
(sockaddr_in6*)(aiRange->ai_addr);
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if ((in6aBitmask.s6_addr[i] &
|
||||
sa6Host->sin6_addr.s6_addr[i]) !=
|
||||
(in6aBitmask.s6_addr[i] &
|
||||
sa6Range->sin6_addr.s6_addr[i])) {
|
||||
bIsHostInRange = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(aiHost);
|
||||
freeaddrinfo(aiRange);
|
||||
|
||||
if (bIsHostInRange) return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,10 @@
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#ifdef HAVE_TCSETATTR
|
||||
#include <termios.h>
|
||||
#endif
|
||||
@@ -585,6 +589,101 @@ SCString CUtils::GetEncodings() {
|
||||
return ssResult;
|
||||
}
|
||||
|
||||
bool CUtils::CheckCIDR(const CString& sIP, const CString& sRange) {
|
||||
if (sIP.WildCmp(sRange)) {
|
||||
return true;
|
||||
}
|
||||
// Try to split the string into an IP and routing prefix
|
||||
VCString vsSplitCIDR;
|
||||
if (sRange.Split("/", vsSplitCIDR, false) != 2) return false;
|
||||
const CString sRoutingPrefix = vsSplitCIDR.back();
|
||||
const int iRoutingPrefix = sRoutingPrefix.ToInt();
|
||||
if (iRoutingPrefix < 0 || iRoutingPrefix > 128) return false;
|
||||
|
||||
// If iRoutingPrefix is 0, it could be due to ToInt() failing, so
|
||||
// sRoutingPrefix needs to be all zeroes
|
||||
if (iRoutingPrefix == 0 && sRoutingPrefix != "0") return false;
|
||||
|
||||
// Convert each IP from a numeric string to an addrinfo
|
||||
addrinfo aiHints;
|
||||
memset(&aiHints, 0, sizeof(addrinfo));
|
||||
aiHints.ai_flags = AI_NUMERICHOST;
|
||||
|
||||
addrinfo* aiHost;
|
||||
int iIsHostValid = getaddrinfo(sIP.c_str(), nullptr, &aiHints, &aiHost);
|
||||
if (iIsHostValid != 0) return false;
|
||||
|
||||
aiHints.ai_family = aiHost->ai_family; // Host and range must be in
|
||||
// the same address family
|
||||
|
||||
addrinfo* aiRange;
|
||||
int iIsRangeValid =
|
||||
getaddrinfo(vsSplitCIDR.front().c_str(), nullptr, &aiHints, &aiRange);
|
||||
if (iIsRangeValid != 0) {
|
||||
freeaddrinfo(aiHost);
|
||||
return false;
|
||||
}
|
||||
|
||||
// "/0" allows all IPv[4|6] addresses
|
||||
if (iRoutingPrefix == 0) {
|
||||
freeaddrinfo(aiHost);
|
||||
freeaddrinfo(aiRange);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If both IPs are valid and of the same type, make a bit field mask
|
||||
// from the routing prefix, AND it to the host and range, and see if
|
||||
// they match
|
||||
bool bIsHostInRange = false;
|
||||
if (aiHost->ai_family == AF_INET) {
|
||||
if (iRoutingPrefix > 32) {
|
||||
freeaddrinfo(aiHost);
|
||||
freeaddrinfo(aiRange);
|
||||
return false;
|
||||
}
|
||||
|
||||
const sockaddr_in* saHost = (sockaddr_in*)(aiHost->ai_addr);
|
||||
const sockaddr_in* saRange = (sockaddr_in*)(aiRange->ai_addr);
|
||||
|
||||
// Make IPv4 bitmask
|
||||
const in_addr_t inBitmask = htonl((~0u) << (32 - iRoutingPrefix));
|
||||
|
||||
// Compare masked IPv4s
|
||||
bIsHostInRange = ((inBitmask & saHost->sin_addr.s_addr) ==
|
||||
(inBitmask & saRange->sin_addr.s_addr));
|
||||
} else if (aiHost->ai_family == AF_INET6) {
|
||||
// Make IPv6 bitmask
|
||||
in6_addr in6aBitmask;
|
||||
memset(&in6aBitmask, 0, sizeof(in6aBitmask));
|
||||
|
||||
for (int i = 0, iBitsLeft = iRoutingPrefix; iBitsLeft > 0;
|
||||
++i, iBitsLeft -= 8) {
|
||||
if (iBitsLeft >= 8) {
|
||||
in6aBitmask.s6_addr[i] = (uint8_t)(~0u);
|
||||
} else {
|
||||
in6aBitmask.s6_addr[i] = (uint8_t)(~0u) << (8 - iBitsLeft);
|
||||
}
|
||||
}
|
||||
|
||||
// Compare masked IPv6s
|
||||
bIsHostInRange = true;
|
||||
const sockaddr_in6* sa6Host = (sockaddr_in6*)(aiHost->ai_addr);
|
||||
const sockaddr_in6* sa6Range = (sockaddr_in6*)(aiRange->ai_addr);
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if ((in6aBitmask.s6_addr[i] & sa6Host->sin6_addr.s6_addr[i]) !=
|
||||
(in6aBitmask.s6_addr[i] & sa6Range->sin6_addr.s6_addr[i])) {
|
||||
bIsHostInRange = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(aiHost);
|
||||
freeaddrinfo(aiRange);
|
||||
|
||||
return bIsHostInRange;
|
||||
}
|
||||
|
||||
MCString CUtils::GetMessageTags(const CString& sLine) {
|
||||
if (sLine.StartsWith("@")) {
|
||||
return CMessage(sLine).GetTags();
|
||||
|
||||
@@ -60,7 +60,8 @@ add_executable(unittest_bin EXCLUDE_FROM_ALL
|
||||
"${GMOCK_ROOT}/src/gmock-all.cc"
|
||||
"ThreadTest.cpp" "NickTest.cpp" "ClientTest.cpp" "NetworkTest.cpp"
|
||||
"MessageTest.cpp" "ModulesTest.cpp" "IRCSockTest.cpp" "QueryTest.cpp"
|
||||
"StringTest.cpp" "ConfigTest.cpp" "BufferTest.cpp" "UtilsTest.cpp")
|
||||
"StringTest.cpp" "ConfigTest.cpp" "BufferTest.cpp" "UtilsTest.cpp"
|
||||
"UserTest.cpp")
|
||||
target_link_libraries(unittest_bin PRIVATE znclib)
|
||||
target_include_directories(unittest_bin PRIVATE
|
||||
"${GTEST_ROOT}" "${GTEST_ROOT}/include"
|
||||
|
||||
@@ -27,7 +27,7 @@ class UserTest : public ::testing::Test {
|
||||
|
||||
TEST_F(UserTest, IsHostAllowed) {
|
||||
struct hostTest {
|
||||
CString sTestHost;
|
||||
CString sMask;
|
||||
CString sIP;
|
||||
bool bExpectedResult;
|
||||
};
|
||||
@@ -108,12 +108,14 @@ TEST_F(UserTest, IsHostAllowed) {
|
||||
|
||||
{"::2/00000000000", "::1", false},
|
||||
{"::2/0a", "::1", false},
|
||||
|
||||
{"192.168.*", "192.168.0.1", true},
|
||||
};
|
||||
|
||||
for (const hostTest& h : aHostTests) {
|
||||
CUser user("user");
|
||||
user.AddAllowedHost(h.sTestHost);
|
||||
user.AddAllowedHost(h.sMask);
|
||||
EXPECT_EQ(h.bExpectedResult, user.IsHostAllowed(h.sIP))
|
||||
<< "Allow-host is " << h.sTestHost;
|
||||
<< "Allow-host is " << h.sMask;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user