mirror of
https://github.com/znc/znc.git
synced 2026-08-03 07:23:19 +02:00
+1
-1
@@ -59,7 +59,7 @@ BIN_SRCS := src/main.cpp
|
||||
LIB_OBJS := $(patsubst %cpp,%o,$(LIB_SRCS))
|
||||
BIN_OBJS := $(patsubst %cpp,%o,$(BIN_SRCS))
|
||||
TESTS := StringTest ConfigTest UtilsTest ThreadTest NickTest ClientTest NetworkTest \
|
||||
MessageTest ModulesTest IRCSockTest QueryTest BufferTest
|
||||
MessageTest ModulesTest IRCSockTest QueryTest BufferTest UserTest
|
||||
TESTS := $(addprefix test/,$(addsuffix .o,$(TESTS)))
|
||||
CLEAN := znc src/*.o test/*.o core core.* .version_extra .depend modules/.depend \
|
||||
unittest $(LIBZNC)
|
||||
|
||||
@@ -852,6 +852,92 @@ bool CUser::IsHostAllowed(const CString& sHostMask) const {
|
||||
for (const CString& sHost : m_ssAllowedHosts) {
|
||||
if (sHostMask.WildCmp(sHost)) {
|
||||
return true;
|
||||
} else {
|
||||
// CIDR notation checker, e.g. "192.0.2.0/24" or "2001:db8::/32"
|
||||
addrinfo *aiHost, *aiRange;
|
||||
addrinfo aiHints;
|
||||
in6_addr in6aBitmask;
|
||||
|
||||
memset(&in6aBitmask, 0, sizeof(in6aBitmask));
|
||||
memset(&aiHints, 0, sizeof(addrinfo));
|
||||
|
||||
aiHints.ai_family = AF_UNSPEC; // Allow any address family
|
||||
aiHints.ai_socktype = 0; // Any socket
|
||||
aiHints.ai_flags = AI_NUMERICHOST | AI_ADDRCONFIG;
|
||||
aiHints.ai_protocol = 0; // Any protocol
|
||||
aiHints.ai_canonname = NULL;
|
||||
aiHints.ai_addr = NULL;
|
||||
aiHints.ai_next = NULL;
|
||||
|
||||
// Try to split the string into an IP and routing prefix
|
||||
VCString vsSplitCIDR;
|
||||
const int iSplitCIDRCount = sHost.Split("/", vsSplitCIDR, false);
|
||||
const int iRoutingPrefix = vsSplitCIDR.back().ToInt();
|
||||
|
||||
if (iSplitCIDRCount != 2 || iRoutingPrefix < 0) continue;
|
||||
|
||||
int iIsHostValid, iIsRangeValid;
|
||||
iIsHostValid =
|
||||
getaddrinfo(sHostMask.c_str(), NULL, &aiHints, &aiHost);
|
||||
if (iIsHostValid != 0) continue;
|
||||
|
||||
aiHints.ai_family = aiHost->ai_family; // Host and range must be in
|
||||
// the same address family
|
||||
|
||||
iIsRangeValid = getaddrinfo(vsSplitCIDR.front().c_str(), NULL,
|
||||
&aiHints, &aiRange);
|
||||
if (iIsRangeValid != 0) continue;
|
||||
|
||||
// "/0" allows all IPv[4|6] addresses
|
||||
if (iRoutingPrefix == 0) 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) {
|
||||
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
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2004-2015 ZNC, see the NOTICE file for details.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <znc/User.h>
|
||||
#include <znc/znc.h>
|
||||
|
||||
class UserTest : public ::testing::Test {
|
||||
protected:
|
||||
UserTest() { CZNC::CreateInstance(); }
|
||||
~UserTest() { CZNC::DestroyInstance(); }
|
||||
};
|
||||
|
||||
TEST_F(UserTest, IsHostAllowed) {
|
||||
CUser* m_pTestUser;
|
||||
|
||||
struct hostTest {
|
||||
CString sTestHost;
|
||||
CString sIP;
|
||||
bool bExpectedResult;
|
||||
};
|
||||
|
||||
hostTest aHostTests[] = {
|
||||
{"127.0.0.1", "127.0.0.1", true},
|
||||
{"127.0.0.20", "127.0.0.1", false},
|
||||
{"127.0.0.20/24", "127.0.0.1", true},
|
||||
{"127.0.0.20/0", "127.0.0.1", true},
|
||||
{"127.0.0.20/32", "127.0.0.1", false},
|
||||
|
||||
{"127.0.0.1", "127.0.0.0", false},
|
||||
{"127.0.0.20", "127.0.0.0", false},
|
||||
{"127.0.0.20/24", "127.0.0.0", true},
|
||||
{"127.0.0.20/0", "127.0.0.0", true},
|
||||
{"127.0.0.20/32", "127.0.0.0", false},
|
||||
|
||||
{"127.0.0.1", "127.0.0.255", false},
|
||||
{"127.0.0.20", "127.0.0.255", false},
|
||||
{"127.0.0.20/24", "127.0.0.255", true},
|
||||
{"127.0.0.20/0", "127.0.0.255", true},
|
||||
{"127.0.0.20/32", "127.0.0.255", false},
|
||||
|
||||
{"127.0.0.1", "127.0.1.1", false},
|
||||
{"127.0.0.20", "127.0.1.1", false},
|
||||
{"127.0.0.20/24", "127.0.1.1", false},
|
||||
{"127.0.0.20/16", "127.0.1.1", true},
|
||||
{"127.0.0.20/0", "127.0.1.1", true},
|
||||
{"127.0.0.20/32", "127.0.1.1", false},
|
||||
|
||||
{"127.0.0.1", "0.0.0.0", false},
|
||||
{"127.0.0.20", "0.0.0.0", false},
|
||||
{"127.0.0.20/24", "0.0.0.0", false},
|
||||
{"127.0.0.20/16", "0.0.0.0", false},
|
||||
{"127.0.0.20/0", "0.0.0.0", true},
|
||||
{"127.0.0.20/32", "0.0.0.0", false},
|
||||
|
||||
{"127.0.0.1", "255.255.255.255", false},
|
||||
{"127.0.0.20", "255.255.255.255", false},
|
||||
{"127.0.0.20/24", "255.255.255.255", false},
|
||||
{"127.0.0.20/16", "255.255.255.255", false},
|
||||
{"127.0.0.20/0", "255.255.255.255", true},
|
||||
{"127.0.0.20/32", "255.255.255.255", false},
|
||||
|
||||
{"127.0.0.1", "::1", false},
|
||||
{"::1", "::1", true},
|
||||
{"::20/120", "::1", true},
|
||||
{"::20/120", "::ffff", false},
|
||||
{"::ff/0", "::1", true},
|
||||
{"::ff/0", "127.0.0.1", false},
|
||||
{"127.0.0.20/0", "::1", false},
|
||||
|
||||
{"127.0.0.1/-1", "127.0.0.1", false},
|
||||
{"::0/-1", "::0", false},
|
||||
{"127.0.0.a/0", "127.0.0.1", false},
|
||||
{"::g/0", "::0", false},
|
||||
{"127.0.0.1/0", "127.0.0.a", false},
|
||||
{"::0/0", "::g", false},
|
||||
|
||||
{"::0/0/0", "::0", false},
|
||||
{"2001:db8::/33", "2001:db9:0::", false},
|
||||
{"2001:db8::/32", "2001:db8:8000::", true},
|
||||
{"2001:db8::/33", "2001:db8:8000::", false},
|
||||
};
|
||||
for (int i = 0; i < (int)(sizeof(aHostTests) / sizeof(aHostTests[0]));
|
||||
++i) {
|
||||
m_pTestUser = new CUser("user");
|
||||
hostTest* h = aHostTests + i;
|
||||
m_pTestUser->AddAllowedHost(h->sTestHost);
|
||||
CString should = h->bExpectedResult ? "" : " not";
|
||||
CString error = "Allow-host string \"" + h->sTestHost + "\" should" +
|
||||
should + " allow host \"" + h->sIP + "\"";
|
||||
EXPECT_EQ(m_pTestUser->IsHostAllowed(aHostTests[i].sIP),
|
||||
aHostTests[i].bExpectedResult)
|
||||
<< error;
|
||||
delete m_pTestUser;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user