diff --git a/modules/bouncedcc.cpp b/modules/bouncedcc.cpp index 72a647df..b11bdfce 100644 --- a/modules/bouncedcc.cpp +++ b/modules/bouncedcc.cpp @@ -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()) { diff --git a/test/integration/tests/modules.cpp b/test/integration/tests/modules.cpp index c90efd86..c09b97b3 100644 --- a/test/integration/tests/modules.cpp +++ b/test/integration/tests/modules.cpp @@ -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");