bouncedcc: only let the user's own clients connect to an IRC-originated bounce

When the DCC request came from IRC, ZNC connects out to the remote peer
and the thing connecting to the bounce listener should be one of the
user's clients. In GetSockObj, compare the connecting address with the
remote IP of each currently connected client and refuse anyone else,
closing the accepted socket straight away while leaving the listener up
for the real client. The user-originated path is unchanged, since there
it is the remote IRC peer that is expected to connect.
This commit is contained in:
Alhuda Khan committed 2026-10-23 13:00:06 +05:30
1 parent 8aef9873fc
commit d5375e58f8
2 files changed
+34

No files matched your search

+25
View File
@@ -163,6 +163,13 @@ class CBounceDCCMod : public CModule {
bool UseClientIP() { return GetNV("UseClientIP").ToBool(); }
bool IsClientIP(const CString& sIP) {
for (CClient* pClient : GetUser()->GetAllClients()) {
if (pClient->GetRemoteIP().Equals(sIP)) return true;
}
return false;
}
// The address in a DCC request from IRC is chosen by the remote peer, and
// ZNC is the one connecting to it, so don't let it point at ZNC's own
// host or at link-local services.
@@ -506,6 +513,24 @@ void CDCCBounce::Shutdown() {
}
Csock* CDCCBounce::GetSockObj(const CString& sHost, unsigned short uPort) {
// m_sRemoteIP is only known up front when the request came from IRC. In
// that case ZNC connects out to the remote peer itself, so the only thing
// that should be connecting here is one of the user's own clients. Hand
// Csocket a throwaway socket that is closed straight away and keep
// listening for the real client.
if (!m_sRemoteIP.empty() && !m_pModule->IsClientIP(sHost)) {
DEBUG(GetSockName() << " == GetSockObj(" << sHost << ") refused");
CString sType = m_bIsChat ? t_s("Chat", "type") : t_s("Xfer", "type");
m_pModule->PutModule(
t_f("DCC {1} Bounce ({2}): Refused connection from {3}, which is "
"not one of your clients")(sType, m_sRemoteNick, sHost));
CDCCBounce* pRefused =
new CDCCBounce(m_pModule, sHost, uPort, m_sRemoteNick, m_sRemoteIP,
m_sFileName, m_bIsChat);
pRefused->Close();
return pRefused;
}
Close();
if (m_sRemoteIP.empty()) {
+9
View File
@@ -766,6 +766,15 @@ TEST_F(ZNCTest, BounceDCCModule) {
client.ReadUntilAndGet(":friend!user@host PRIVMSG nick :\001DCC CHAT chat", line2);
EXPECT_THAT(line2.toStdString(), Not(HasSubstr("3232235521 54321")));
// Only the user's own clients may connect to that bounce port. The test
// client is on a unix socket, so a TCP connection from anywhere is refused.
QByteArray bouncePort = line2.mid(line2.lastIndexOf(' ') + 1);
bouncePort.chop(1); // trailing \001
QTcpSocket outsider;
outsider.connectToHost("127.0.0.1", bouncePort.toUShort());
outsider.waitForConnected();
client.ReadUntil("Refused connection from 127.0.0.1");
// Peer must not be able to make ZNC connect to its own loopback
ircd.Write(":friend!user@host PRIVMSG nick :\001DCC CHAT chat 2130706433 6379\001");
client.ReadUntil("refused: bad address 2130706433");