enforce allowed hosts when an auth module accepts the login

The Allow list was only checked in CZNC::AuthUser() after the core
password check, so logins accepted by imapauth, cyrusauth or certauth
(including SASL EXTERNAL) ignored it. Check it in CAuthBase::AcceptLogin()
and CClient::AcceptSASLLogin() instead.
This commit is contained in:
Alhuda Khan committed 2026-10-23 20:48:13 +05:30
1 parent bfba882ccc
commit 064fa95318
4 files changed
+56 -9

No files matched your search

+12
View File
@@ -420,6 +420,13 @@ void CAuthBase::Invalidate() { m_pSock = nullptr; }
void CAuthBase::AcceptLogin(CUser& User) {
if (m_pSock) {
// Auth modules call this directly, so check allowed hosts here rather
// than only in CZNC::AuthUser()
CString sHost = GetRemoteIP();
if (!User.IsHostAllowed(sHost)) {
RefuseLogin("Your host [" + sHost + "] is not allowed");
return;
}
AcceptedLogin(User);
}
Invalidate();
@@ -1340,6 +1347,11 @@ void CClient::RefuseSASLLogin(const CString& sReason) {
}
void CClient::AcceptSASLLogin(CUser& User) {
if (!User.IsHostAllowed(GetRemoteIP())) {
RefuseSASLLogin("Your host [" + GetRemoteIP() + "] is not allowed");
return;
}
PutClient(":irc.znc.in 900 " + GetNick() + " " + GetNick() + "!" +
User.GetIdent() + "@" + GetRemoteIP() + " " + User.GetUsername() +
" :You are now logged in as " + User.GetUsername());
+1 -9
View File
@@ -1978,8 +1978,6 @@ CZNC::TrafficStatsMap CZNC::GetNetworkTrafficStats(const CString& sUsername,
}
void CZNC::AuthUser(std::shared_ptr<CAuthBase> AuthClass) {
// TODO unless the auth module calls it, CUser::IsHostAllowed() is not
// honoured
bool bReturn = false;
GLOBALMODULECALL(OnLoginAttempt(AuthClass), &bReturn);
if (bReturn) return;
@@ -1991,13 +1989,7 @@ void CZNC::AuthUser(std::shared_ptr<CAuthBase> AuthClass) {
return;
}
CString sHost = AuthClass->GetRemoteIP();
if (!pUser->IsHostAllowed(sHost)) {
AuthClass->RefuseLogin("Your host [" + sHost + "] is not allowed");
return;
}
// CAuthBase::AcceptLogin() checks allowed hosts
AuthClass->AcceptLogin(*pUser);
}
+8
View File
@@ -390,3 +390,11 @@ TEST_F(ClientTest, OnSendToClientMessage) {
EXPECT_THAT(m_pTestSock->vsLines, ElementsAre(msg.ToString()));
m_pTestModule->bSendHooks = false;
}
TEST_F(ClientTest, AcceptSASLLoginAllowedHosts) {
TestClient client;
m_pTestUser->AddAllowedHost("203.0.113.0/24");
client.AcceptSASLLogin(*m_pTestUser);
EXPECT_THAT(client.vsLines,
ElementsAre(":irc.znc.in 904 me :Your host [] is not allowed"));
}
+35
View File
@@ -589,6 +589,41 @@ TEST_F(ZNCTest, SaslAuthPlainImapAuthLineBreak) {
EXPECT_FALSE(imap.hasPendingConnections());
}
TEST_F(ZNCTest, ImapAuthAllowedHosts) {
QTcpServer imap;
ASSERT_TRUE(imap.listen(QHostAddress::LocalHost)) << imap.errorString().toStdString();
QFile conf(m_dir.path() + "/configs/znc.conf");
ASSERT_TRUE(conf.open(QIODevice::Append | QIODevice::Text));
QTextStream(&conf) << R"(
<User foo>
Pass = plain#hunter2
Allow = 203.0.113.0/24
</User>
)";
conf.close();
auto znc = Run();
auto ircd = ConnectIRCd();
auto client = LoginClient();
client.Write(QStringLiteral("znc loadmod imapauth 127.0.0.1 %1")
.arg(imap.serverPort())
.toUtf8());
client.ReadUntil("Loaded");
auto client2 = ConnectClient();
client2.Write("PASS foo:hunter2");
client2.Write("NICK foo");
client2.Write("USER foo x x :x");
ASSERT_TRUE(imap.waitForNewConnection(30000 /* msec */));
auto imapsock = WrapIO(imap.nextPendingConnection());
imapsock.Write("* OK IMAP4rev1 Service Ready");
imapsock.ReadUntil("AUTH LOGIN \"foo\" \"hunter2\"");
imapsock.Write("AUTH OK");
client2.ReadUntil(":irc.znc.in 464 foo :Your host [");
client2.ReadUntil("] is not allowed");
}
TEST_F(ZNCTest, SaslAuthExternal) {
#ifndef HAVE_LIBSSL
GTEST_SKIP() << "SSL is disabled";