Make unix sockets work from modules

This commit is contained in:
Alexey Sokolov
2025-04-20 02:02:59 +01:00
parent 0af3e0705f
commit d3a7f125cf
6 changed files with 192 additions and 32 deletions

View File

@@ -159,6 +159,28 @@ class CSockManager : public TSocketManager<CZNCSock>,
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;

View File

@@ -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,

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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 ///////////////////

View File

@@ -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";