diff --git a/include/znc/Socket.h b/include/znc/Socket.h index 249304b5..60173e4a 100644 --- a/include/znc/Socket.h +++ b/include/znc/Socket.h @@ -159,6 +159,28 @@ class CSockManager : public TSocketManager, const CString& sSockName, int iTimeout = 60, bool bSSL = false, const CString& sBindHost = "", CZNCSock* pcSock = nullptr); + bool ListenUnix(const CString& sSockName, const CString& sPath, + CZNCSock* pcSock = nullptr) { + if (pcSock->ListenUnixInternal(sPath)) { + AddSock(pcSock, sSockName); + return true; + } + + delete pcSock; + return false; + } + + bool ConnectUnix(const CString& sSockName, const CString& sPath, + CZNCSock* pcSock = nullptr) { + if (pcSock->ConnectUnixInternal(sPath)) { + AddSock(pcSock, sSockName); + return true; + } + + delete pcSock; + return false; + } + unsigned int GetAnonConnectionCount(const CString& sIP) const; void DelSockByAddr(Csock* pcSock) override; @@ -280,6 +302,10 @@ class CSocket : public CZNCSock { bool bSSL = false, unsigned int uTimeout = 60); //! Ease of use Listen, assigned to the manager and is subsequently tracked bool Listen(unsigned short uPort, bool bSSL, unsigned int uTimeout = 0); + bool ConnectUnix(const CString& sPath); + bool ListenUnix(const CString& sPath); + //! Helper for modperl and modpython, modules don't normally need to call this + CString ConstructSockName(const CString& sPart) const; // Getters CModule* GetModule() const; diff --git a/modules/modperl/startup.pl b/modules/modperl/startup.pl index 8834d436..7bcdbaad 100644 --- a/modules/modperl/startup.pl +++ b/modules/modperl/startup.pl @@ -778,7 +778,7 @@ sub Connect { $self->GetModule->GetManager->Connect( $host, $port, - "perl-socket", + $self->ConstructSockName("Perl-C"), $arg{timeout}//60, $arg{ssl}//0, $arg{bindhost}//'', @@ -786,11 +786,26 @@ sub Connect { ); } +sub ConnectUnix { + my $self = shift; + my $path = shift; + $self->GetModule->GetManager->ConnectUnix( + $self->ConstructSockName("Perl-CU"), + $path, $self->{_csock} + ); +} + sub Listen { my $self = shift; my %arg = @_; my $addrtype = $ZNC::ADDR_ALL; if (defined $arg{addrtype}) { + if ($arg{addrtype} =~ /^unix$/i) { + return $self->GetModule->GetManager->ListenUnix( + $self->ConstructSockName("Perl-LU"), + $arg{path}, $self->{_csock}, + ); + } if ($arg{addrtype} =~ /^ipv4$/i) { $addrtype = $ZNC::ADDR_IPV4ONLY } elsif ($arg{addrtype} =~ /^ipv6$/i) { $addrtype = $ZNC::ADDR_IPV6ONLY } elsif ($arg{addrtype} =~ /^all$/i) { } @@ -799,7 +814,7 @@ sub Listen { if (defined $arg{port}) { return $arg{port} if $self->GetModule->GetManager->ListenHost( $arg{port}, - "perl-socket", + $self->ConstructSockName("Perl-L"), $arg{bindhost}//'', $arg{ssl}//0, $arg{maxconns}//ZNC::_GetSOMAXCONN, @@ -810,7 +825,7 @@ sub Listen { return 0; } $self->GetModule->GetManager->ListenRand( - "perl-socket", + $self->ConstructSockName("Perl-L"), $arg{bindhost}//'', $arg{ssl}//0, $arg{maxconns}//ZNC::_GetSOMAXCONN, diff --git a/modules/modpython/znc.py b/modules/modpython/znc.py index 064dec6f..94ae2d08 100644 --- a/modules/modpython/znc.py +++ b/modules/modpython/znc.py @@ -47,7 +47,12 @@ class Socket: return AsPyModule(self._csock.GetModule()).GetNewPyObj() def Listen(self, addrtype='all', port=None, bindhost='', ssl=False, - maxconns=GetSOMAXCONN(), timeout=0): + maxconns=GetSOMAXCONN(), timeout=0, path=''): + if addrtype == 'unix': + return self.GetModule().GetManager().ListenUnix( + self.ConstructSockName("Py-LU"), + path, self._csock) + try: addr = self.ADDR_MAP[addrtype.lower()] except KeyError: @@ -55,7 +60,7 @@ class Socket: "Specified addrtype [{0}] isn't supported".format(addrtype)) args = ( - "python socket for {0}".format(self.GetModule()), + self.ConstructSockName("Py-L"), bindhost, ssl, maxconns, @@ -76,13 +81,19 @@ class Socket: return self.GetModule().GetManager().Connect( host, port, - 'python conn socket for {0}'.format(self.GetModule()), + self.ConstructSockName("Py-C"), timeout, ssl, bindhost, self._csock ) + def ConnectUnix(self, path): + return self.GetModule().GetManager().ConnectUnix( + self.ConstructSockName("Py-CU"), + path, self._csock + ) + def Write(self, data): if (isinstance(data, str)): return self._csock.Write(data) diff --git a/src/Listener.cpp b/src/Listener.cpp index 2d37589f..bc763fbf 100644 --- a/src/Listener.cpp +++ b/src/Listener.cpp @@ -97,13 +97,8 @@ bool CUnixListener::Listen() { m_pListener = new CRealListener(*this); SetupSSL(); - if (m_pListener->ListenUnix(m_sPath)) { - CZNC::Get().GetManager().AddSock(m_pListener, "UNIX_LISTENER"); - return true; - } - - delete m_pListener; - return false; + return CZNC::Get().GetManager().ListenUnix("UNIX_LISTENER", m_sPath, + m_pListener); } CConfig CUnixListener::ToConfig() const { diff --git a/src/Socket.cpp b/src/Socket.cpp index 1c673035..11242f4f 100644 --- a/src/Socket.cpp +++ b/src/Socket.cpp @@ -539,24 +539,17 @@ bool CSocket::Connect(const CString& sHostname, unsigned short uPort, bool bSSL, } CUser* pUser = m_pModule->GetUser(); - CString sSockName = "MOD::C::" + m_pModule->GetModName(); CString sBindHost; if (pUser) { - sSockName += "::" + pUser->GetUsername(); sBindHost = pUser->GetBindHost(); CIRCNetwork* pNetwork = m_pModule->GetNetwork(); if (pNetwork) { - sSockName += "::" + pNetwork->GetName(); sBindHost = pNetwork->GetBindHost(); } } - // Don't overwrite the socket name if one is already set - if (!GetSockName().empty()) { - sSockName = GetSockName(); - } - + CString sSockName = ConstructSockName("C"); m_pModule->GetManager()->Connect(sHostname, uPort, sSockName, uTimeout, bSSL, sBindHost, this); return true; @@ -570,21 +563,50 @@ bool CSocket::Listen(unsigned short uPort, bool bSSL, unsigned int uTimeout) { return false; } - CUser* pUser = m_pModule->GetUser(); - CString sSockName = "MOD::L::" + m_pModule->GetModName(); - - if (pUser) { - sSockName += "::" + pUser->GetUsername(); - } - // Don't overwrite the socket name if one is already set - if (!GetSockName().empty()) { - sSockName = GetSockName(); - } - + CString sSockName = ConstructSockName("L"); return m_pModule->GetManager()->ListenAll(uPort, sSockName, bSSL, SOMAXCONN, this); } +bool CSocket::ListenUnix(const CString& sPath) { + if (!m_pModule) { + DEBUG( + "ERROR: CSocket::Listen called on instance without m_pModule " + "handle!"); + return false; + } + + CString sSockName = ConstructSockName("LU"); + return m_pModule->GetManager()->ListenUnix(sSockName, sPath, this); +} + +bool CSocket::ConnectUnix(const CString& sPath) { + if (!m_pModule) { + DEBUG( + "ERROR: CSocket::Listen called on instance without m_pModule " + "handle!"); + return false; + } + + CString sSockName = ConstructSockName("CU"); + return m_pModule->GetManager()->ConnectUnix(sSockName, sPath, this); +} + +CString CSocket::ConstructSockName(const CString& sPart) const { + CString sSockName = GetSockName(); + if (!sSockName.empty()) return sSockName; + + sSockName = "MOD::" + sPart + "::" + m_pModule->GetModName(); + + if (CUser* pUser = m_pModule->GetUser()) { + sSockName += "::" + pUser->GetUsername(); + if (CIRCNetwork* pNetwork = m_pModule->GetNetwork()) { + sSockName += "::" + pNetwork->GetName(); + } + } + return sSockName; +} + CModule* CSocket::GetModule() const { return m_pModule; } /////////////////// !CSocket /////////////////// diff --git a/test/integration/tests/scripting.cpp b/test/integration/tests/scripting.cpp index 2bccaa53..afe75b91 100644 --- a/test/integration/tests/scripting.cpp +++ b/test/integration/tests/scripting.cpp @@ -156,6 +156,97 @@ TEST_F(ZNCTest, ModperlSocket) { client.ReadUntil("received 4 bytes"); } +TEST_F(ZNCTest, ModpythonUnixSocket) { +#ifndef WANT_PYTHON + GTEST_SKIP() << "Modpython is disabled"; +#endif + auto znc = Run(); + znc->CanLeak(); + + InstallModule("socktest.py", R"( + import znc + + class acc(znc.Socket): + def OnReadData(self, data): + self.GetModule().PutModule('received {} bytes'.format(len(data))) + self.Close() + + class lis(znc.Socket): + def OnAccepted(self, host, port): + sock = self.GetModule().CreateSocket(acc) + sock.DisableReadLine() + return sock + + class socktest(znc.Module): + def OnLoad(self, args, ret): + listen = self.CreateSocket(lis) + return listen.Listen(addrtype='unix', path=self.GetSavePath() + "/sock") + + def OnModCommand(self, cmd): + sock = self.CreateSocket() + sock.ConnectUnix(self.GetSavePath() + "/sock") + sock.WriteBytes(b'blah') + )"); + + auto ircd = ConnectIRCd(); + auto client = LoginClient(); + client.Write("znc loadmod modpython"); + client.Write("znc loadmod socktest"); + client.Write("PRIVMSG *socktest :foo"); + client.ReadUntil("received 4 bytes"); +} + +TEST_F(ZNCTest, ModperlUnixSocket) { +#ifndef WANT_PERL + GTEST_SKIP() << "Modperl is disabled"; +#endif + auto znc = Run(); + znc->CanLeak(); + + InstallModule("socktest.pm", R"( + package socktest::acc; + use base 'ZNC::Socket'; + sub OnReadData { + my ($self, $data, $len) = @_; + $self->GetModule->PutModule("received $len bytes"); + $self->Close; + } + + package socktest::lis; + use base 'ZNC::Socket'; + sub OnAccepted { + my $self = shift; + return $self->GetModule->CreateSocket('socktest::acc'); + } + + package socktest::conn; + use base 'ZNC::Socket'; + + package socktest; + use base 'ZNC::Module'; + sub OnLoad { + my $self = shift; + my $listen = $self->CreateSocket('socktest::lis'); + $listen->Listen(addrtype=>'unix', path=>$self->GetSavePath . "/sock"); + } + sub OnModCommand { + my ($self, $cmd) = @_; + my $sock = $self->CreateSocket('socktest::conn'); + $sock->ConnectUnix($self->GetSavePath . "/sock"); + $sock->Write('blah'); + } + + 1; + )"); + + auto ircd = ConnectIRCd(); + auto client = LoginClient(); + client.Write("znc loadmod modperl"); + client.Write("znc loadmod socktest"); + client.Write("PRIVMSG *socktest :foo"); + client.ReadUntil("received 4 bytes"); +} + TEST_F(ZNCTest, ModpythonVCString) { #ifndef WANT_PYTHON GTEST_SKIP() << "Modpython is disabled";