From b68fbc64a247256cbc873394239eb21aca9e7974 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Thu, 12 Jan 2017 10:31:32 +0100 Subject: [PATCH 01/35] Move listener config writing into CListener Signed-off-by: Uli Schlachter --- include/znc/Listener.h | 2 ++ src/Listener.cpp | 25 +++++++++++++++++++++++++ src/znc.cpp | 23 +---------------------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/include/znc/Listener.h b/include/znc/Listener.h index e8d8111a..a6b05353 100644 --- a/include/znc/Listener.h +++ b/include/znc/Listener.h @@ -22,6 +22,7 @@ // Forward Declarations class CRealListener; +class CConfig; // !Forward Declarations class CListener { @@ -60,6 +61,7 @@ class CListener { bool Listen(); void ResetRealListener(); + CConfig ToConfig() const; private: protected: diff --git a/src/Listener.cpp b/src/Listener.cpp index 93633b7e..50023018 100644 --- a/src/Listener.cpp +++ b/src/Listener.cpp @@ -15,6 +15,7 @@ */ #include +#include #include CListener::~CListener() { @@ -48,6 +49,30 @@ bool CListener::Listen() { m_pListener, 0, m_eAddr); } +CConfig CListener::ToConfig() const { + CConfig listenerConfig; + + listenerConfig.AddKeyValuePair("Host", GetBindHost()); + listenerConfig.AddKeyValuePair("URIPrefix", GetURIPrefix() + "/"); + listenerConfig.AddKeyValuePair("Port", CString(GetPort())); + + listenerConfig.AddKeyValuePair( + "IPv4", CString(GetAddrType() != ADDR_IPV6ONLY)); + listenerConfig.AddKeyValuePair( + "IPv6", CString(GetAddrType() != ADDR_IPV4ONLY)); + + listenerConfig.AddKeyValuePair("SSL", CString(IsSSL())); + + listenerConfig.AddKeyValuePair( + "AllowIRC", + CString(GetAcceptType() != CListener::ACCEPT_HTTP)); + listenerConfig.AddKeyValuePair( + "AllowWeb", + CString(GetAcceptType() != CListener::ACCEPT_IRC)); + + return listenerConfig; +} + void CListener::ResetRealListener() { m_pListener = nullptr; } CRealListener::~CRealListener() { m_Listener.ResetRealListener(); } diff --git a/src/znc.cpp b/src/znc.cpp index f58800b8..810f065d 100644 --- a/src/znc.cpp +++ b/src/znc.cpp @@ -497,29 +497,8 @@ bool CZNC::WriteConfig() { unsigned int l = 0; for (CListener* pListener : m_vpListeners) { - CConfig listenerConfig; - - listenerConfig.AddKeyValuePair("Host", pListener->GetBindHost()); - listenerConfig.AddKeyValuePair("URIPrefix", - pListener->GetURIPrefix() + "/"); - listenerConfig.AddKeyValuePair("Port", CString(pListener->GetPort())); - - listenerConfig.AddKeyValuePair( - "IPv4", CString(pListener->GetAddrType() != ADDR_IPV6ONLY)); - listenerConfig.AddKeyValuePair( - "IPv6", CString(pListener->GetAddrType() != ADDR_IPV4ONLY)); - - listenerConfig.AddKeyValuePair("SSL", CString(pListener->IsSSL())); - - listenerConfig.AddKeyValuePair( - "AllowIRC", - CString(pListener->GetAcceptType() != CListener::ACCEPT_HTTP)); - listenerConfig.AddKeyValuePair( - "AllowWeb", - CString(pListener->GetAcceptType() != CListener::ACCEPT_IRC)); - config.AddSubConfig("Listener", "listener" + CString(l++), - listenerConfig); + pListener->ToConfig()); } config.AddKeyValuePair("ConnectDelay", CString(m_uiConnectDelay)); From 35da5784ca39936981d3c9f7767fcb2fcfd191ed Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Thu, 12 Jan 2017 10:45:54 +0100 Subject: [PATCH 02/35] Split CTCPListener from CListener This splits up CListener into a TCP-specific class and a general class. The intention is to later add another class inheriting from CListener that listens on unix sockets. Signed-off-by: Uli Schlachter --- include/znc/Listener.h | 48 ++++++++++++++++++++++++++------------- modules/webadmin.cpp | 51 ++++++++++++++++++++++++------------------ src/ClientCommand.cpp | 30 ++++++++++++++++--------- src/Listener.cpp | 7 ++++-- src/znc.cpp | 12 +++++----- 5 files changed, 93 insertions(+), 55 deletions(-) diff --git a/include/znc/Listener.h b/include/znc/Listener.h index a6b05353..92d2f581 100644 --- a/include/znc/Listener.h +++ b/include/znc/Listener.h @@ -29,27 +29,19 @@ class CListener { public: typedef enum { ACCEPT_IRC, ACCEPT_HTTP, ACCEPT_ALL } EAcceptType; - CListener(unsigned short uPort, const CString& sBindHost, - const CString& sURIPrefix, bool bSSL, EAddrType eAddr, - EAcceptType eAccept) + CListener(const CString& sURIPrefix, bool bSSL, EAcceptType eAccept) : m_bSSL(bSSL), - m_eAddr(eAddr), - m_uPort(uPort), - m_sBindHost(sBindHost), m_sURIPrefix(sURIPrefix), m_pListener(nullptr), m_eAcceptType(eAccept) {} - ~CListener(); + virtual ~CListener(); CListener(const CListener&) = delete; CListener& operator=(const CListener&) = delete; // Getters bool IsSSL() const { return m_bSSL; } - EAddrType GetAddrType() const { return m_eAddr; } - unsigned short GetPort() const { return m_uPort; } - const CString& GetBindHost() const { return m_sBindHost; } CRealListener* GetRealListener() const { return m_pListener; } const CString& GetURIPrefix() const { return m_sURIPrefix; } EAcceptType GetAcceptType() const { return m_eAcceptType; } @@ -59,21 +51,47 @@ class CListener { // except this one, so don't add other setters! void SetAcceptType(EAcceptType eType) { m_eAcceptType = eType; } - bool Listen(); + virtual bool Listen() = 0; void ResetRealListener(); - CConfig ToConfig() const; + virtual CConfig ToConfig() const = 0; private: protected: bool m_bSSL; - EAddrType m_eAddr; - unsigned short m_uPort; - CString m_sBindHost; CString m_sURIPrefix; CRealListener* m_pListener; EAcceptType m_eAcceptType; }; +class CTCPListener : public CListener { + public: + CTCPListener(unsigned short uPort, const CString& sBindHost, + const CString& sURIPrefix, bool bSSL, EAddrType eAddr, + EAcceptType eAccept) + : CListener(sURIPrefix, bSSL, eAccept), + m_eAddr(eAddr), + m_uPort(uPort), + m_sBindHost(sBindHost) {} + ~CTCPListener(); + + CTCPListener(const CTCPListener&) = delete; + CTCPListener& operator=(const CTCPListener&) = delete; + + // Getters + EAddrType GetAddrType() const { return m_eAddr; } + unsigned short GetPort() const { return m_uPort; } + const CString& GetBindHost() const { return m_sBindHost; } + // !Getters + + bool Listen() override; + CConfig ToConfig() const override; + + protected: + EAddrType m_eAddr; + unsigned short m_uPort; + CString m_sBindHost; +}; + class CRealListener : public CZNCSock { public: CRealListener(CListener& listener) : CZNCSock(), m_Listener(listener) {} diff --git a/modules/webadmin.cpp b/modules/webadmin.cpp index b44342a6..722c45a6 100644 --- a/modules/webadmin.cpp +++ b/modules/webadmin.cpp @@ -161,7 +161,7 @@ class CWebAdminMod : public CModule { } // Now turn that into a listener instance - CListener* pListener = new CListener( + CListener* pListener = new CTCPListener( uPort, sListenHost, sURIPrefix, bSSL, (!bIPv6 ? ADDR_IPV4ONLY : ADDR_ALL), CListener::ACCEPT_HTTP); @@ -1883,8 +1883,18 @@ class CWebAdminMod : public CModule { for (const CListener* pListener : vpListeners) { CTemplate& l = Tmpl.AddRow("ListenLoop"); - l["Port"] = CString(pListener->GetPort()); - l["BindHost"] = pListener->GetBindHost(); + const CTCPListener* pTCPListener = dynamic_cast(pListener); + if (pTCPListener != nullptr) { + l["Port"] = CString(pTCPListener->GetPort()); + l["BindHost"] = pTCPListener->GetBindHost(); + + // simple protection for user from shooting his own foot + // TODO check also for hosts/families + // such check is only here, user still can forge HTTP request to + // delete web port + l["SuggestDeletion"] = + CString(pTCPListener->GetPort() != WebSock.GetLocalPort()); + } l["IsHTTP"] = CString(pListener->GetAcceptType() != CListener::ACCEPT_IRC); @@ -1893,13 +1903,6 @@ class CWebAdminMod : public CModule { l["URIPrefix"] = pListener->GetURIPrefix() + "/"; - // simple protection for user from shooting his own foot - // TODO check also for hosts/families - // such check is only here, user still can forge HTTP request to - // delete web port - l["SuggestDeletion"] = - CString(pListener->GetPort() != WebSock.GetLocalPort()); - #ifdef HAVE_LIBSSL if (pListener->IsSSL()) { l["IsSSL"] = "true"; @@ -1907,20 +1910,24 @@ class CWebAdminMod : public CModule { #endif #ifdef HAVE_IPV6 - switch (pListener->GetAddrType()) { - case ADDR_IPV4ONLY: - l["IsIPV4"] = "true"; - break; - case ADDR_IPV6ONLY: - l["IsIPV6"] = "true"; - break; - case ADDR_ALL: - l["IsIPV4"] = "true"; - l["IsIPV6"] = "true"; - break; + if (pTCPListener != nullptr) { + switch (pTCPListener->GetAddrType()) { + case ADDR_IPV4ONLY: + l["IsIPV4"] = "true"; + break; + case ADDR_IPV6ONLY: + l["IsIPV6"] = "true"; + break; + case ADDR_ALL: + l["IsIPV4"] = "true"; + l["IsIPV6"] = "true"; + break; + } } #else - l["IsIPV4"] = "true"; + if (pTCPListener != nullptr) { + l["IsIPV4"] = "true"; + } #endif } diff --git a/src/ClientCommand.cpp b/src/ClientCommand.cpp index fa728d66..cdd8c9e0 100644 --- a/src/ClientCommand.cpp +++ b/src/ClientCommand.cpp @@ -1543,17 +1543,25 @@ void CClient::UserPortCommand(CString& sLine) { for (const CListener* pListener : vpListeners) { Table.AddRow(); - Table.SetCell("Port", CString(pListener->GetPort())); - Table.SetCell("BindHost", (pListener->GetBindHost().empty() - ? CString("*") - : pListener->GetBindHost())); - Table.SetCell("SSL", CString(pListener->IsSSL())); - EAddrType eAddr = pListener->GetAddrType(); - Table.SetCell("Proto", - (eAddr == ADDR_ALL - ? "All" - : (eAddr == ADDR_IPV4ONLY ? "IPv4" : "IPv6"))); + const CTCPListener* pTCPListener = dynamic_cast(pListener); + + if (pTCPListener != nullptr) { + Table.SetCell("Port", CString(pTCPListener->GetPort())); + Table.SetCell("BindHost", (pTCPListener->GetBindHost().empty() + ? CString("*") + : pTCPListener->GetBindHost())); + + EAddrType eAddr = pTCPListener->GetAddrType(); + Table.SetCell("Proto", + (eAddr == ADDR_ALL + ? "All" + : (eAddr == ADDR_IPV4ONLY ? "IPv4" : "IPv6"))); + } else { + Table.SetCell("Port", "unknown"); + } + + Table.SetCell("SSL", CString(pListener->IsSSL())); CListener::EAcceptType eAccept = pListener->GetAcceptType(); Table.SetCell( @@ -1608,7 +1616,7 @@ void CClient::UserPortCommand(CString& sLine) { const CString sBindHost = sLine.Token(4); const CString sURIPrefix = sLine.Token(5); - CListener* pListener = new CListener(uPort, sBindHost, sURIPrefix, + CListener* pListener = new CTCPListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept); if (!pListener->Listen()) { diff --git a/src/Listener.cpp b/src/Listener.cpp index 50023018..a8cce98a 100644 --- a/src/Listener.cpp +++ b/src/Listener.cpp @@ -22,7 +22,10 @@ CListener::~CListener() { if (m_pListener) CZNC::Get().GetManager().DelSockByAddr(m_pListener); } -bool CListener::Listen() { +CTCPListener::~CTCPListener() { +} + +bool CTCPListener::Listen() { if (!m_uPort || m_pListener) { errno = EINVAL; return false; @@ -49,7 +52,7 @@ bool CListener::Listen() { m_pListener, 0, m_eAddr); } -CConfig CListener::ToConfig() const { +CConfig CTCPListener::ToConfig() const { CConfig listenerConfig; listenerConfig.AddKeyValuePair("Host", GetBindHost()); diff --git a/src/znc.cpp b/src/znc.cpp index 810f065d..1f6de301 100644 --- a/src/znc.cpp +++ b/src/znc.cpp @@ -669,7 +669,7 @@ bool CZNC::WriteNewConfig(const CString& sConfigFile) { // Don't ask for listen host, it may be configured later if needed. CUtils::PrintAction("Verifying the listener"); - CListener* pListener = new CListener( + CListener* pListener = new CTCPListener( (unsigned short int)uListenPort, sListenHost, sURIPrefix, bListenSSL, b6 ? ADDR_ALL : ADDR_IPV4ONLY, CListener::ACCEPT_ALL); if (!pListener->Listen()) { @@ -1568,9 +1568,11 @@ bool CZNC::AddUser(CUser* pUser, CString& sErrorRet, bool bStartup) { CListener* CZNC::FindListener(u_short uPort, const CString& sBindHost, EAddrType eAddr) { for (CListener* pListener : m_vpListeners) { - if (pListener->GetPort() != uPort) continue; - if (pListener->GetBindHost() != sBindHost) continue; - if (pListener->GetAddrType() != eAddr) continue; + CTCPListener* pTCPListener = dynamic_cast(pListener); + if (!pTCPListener) continue; + if (pTCPListener->GetPort() != uPort) continue; + if (pTCPListener->GetBindHost() != sBindHost) continue; + if (pTCPListener->GetAddrType() != eAddr) continue; return pListener; } return nullptr; @@ -1700,7 +1702,7 @@ bool CZNC::AddListener(unsigned short uPort, const CString& sBindHost, } CListener* pListener = - new CListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept); + new CTCPListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept); if (!pListener->Listen()) { sError = FormatBindError(); From b8d99f2674a3dc5dccb7cf70d8e2cad4fc9ea391 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Thu, 12 Jan 2017 11:00:37 +0100 Subject: [PATCH 03/35] Implement CListener::ToConfig() This will later reduce some code duplication. Signed-off-by: Uli Schlachter --- include/znc/Listener.h | 2 +- src/Listener.cpp | 29 ++++++++++++++++++----------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/include/znc/Listener.h b/include/znc/Listener.h index 92d2f581..c16ef1c1 100644 --- a/include/znc/Listener.h +++ b/include/znc/Listener.h @@ -53,7 +53,7 @@ class CListener { virtual bool Listen() = 0; void ResetRealListener(); - virtual CConfig ToConfig() const = 0; + virtual CConfig ToConfig() const; private: protected: diff --git a/src/Listener.cpp b/src/Listener.cpp index a8cce98a..40b8e388 100644 --- a/src/Listener.cpp +++ b/src/Listener.cpp @@ -22,6 +22,23 @@ CListener::~CListener() { if (m_pListener) CZNC::Get().GetManager().DelSockByAddr(m_pListener); } +CConfig CListener::ToConfig() const { + CConfig listenerConfig; + + listenerConfig.AddKeyValuePair("URIPrefix", GetURIPrefix() + "/"); + + listenerConfig.AddKeyValuePair("SSL", CString(IsSSL())); + + listenerConfig.AddKeyValuePair( + "AllowIRC", + CString(GetAcceptType() != CListener::ACCEPT_HTTP)); + listenerConfig.AddKeyValuePair( + "AllowWeb", + CString(GetAcceptType() != CListener::ACCEPT_IRC)); + + return listenerConfig; +} + CTCPListener::~CTCPListener() { } @@ -53,10 +70,9 @@ bool CTCPListener::Listen() { } CConfig CTCPListener::ToConfig() const { - CConfig listenerConfig; + CConfig listenerConfig = CListener::ToConfig(); listenerConfig.AddKeyValuePair("Host", GetBindHost()); - listenerConfig.AddKeyValuePair("URIPrefix", GetURIPrefix() + "/"); listenerConfig.AddKeyValuePair("Port", CString(GetPort())); listenerConfig.AddKeyValuePair( @@ -64,15 +80,6 @@ CConfig CTCPListener::ToConfig() const { listenerConfig.AddKeyValuePair( "IPv6", CString(GetAddrType() != ADDR_IPV4ONLY)); - listenerConfig.AddKeyValuePair("SSL", CString(IsSSL())); - - listenerConfig.AddKeyValuePair( - "AllowIRC", - CString(GetAcceptType() != CListener::ACCEPT_HTTP)); - listenerConfig.AddKeyValuePair( - "AllowWeb", - CString(GetAcceptType() != CListener::ACCEPT_IRC)); - return listenerConfig; } From cb94756ec5e6019da421b4eead8d7962e22a5037 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Thu, 2 Nov 2017 11:49:17 +0100 Subject: [PATCH 04/35] Update Csocket submodule Signed-off-by: Uli Schlachter --- third_party/Csocket | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/Csocket b/third_party/Csocket index 94e21a83..34d902bf 160000 --- a/third_party/Csocket +++ b/third_party/Csocket @@ -1 +1 @@ -Subproject commit 94e21a832d15dde65cab89429b727e9806d54cd5 +Subproject commit 34d902bfc80f8ecb18b0821159edac628ce0c891 From 811f453efb31ce70ae5ef4302aeb824c1a263930 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Thu, 12 Jan 2017 12:02:51 +0100 Subject: [PATCH 05/35] Add support for listening on a unix domain socket So far this is not integrated with ClientCommand.cpp or webadmin.cpp, so the only way to actually use this is to hand-editing the config with a section like the following: Path = /tmp/listen SSL = false So far this received only very basic testing. I did not even test SSL support. Signed-off-by: Uli Schlachter --- include/znc/Listener.h | 22 ++++++++ include/znc/znc.h | 2 + src/Listener.cpp | 29 +++++++++++ src/znc.cpp | 113 +++++++++++++++++++++++++++++++++-------- 4 files changed, 145 insertions(+), 21 deletions(-) diff --git a/include/znc/Listener.h b/include/znc/Listener.h index c16ef1c1..002a3098 100644 --- a/include/znc/Listener.h +++ b/include/znc/Listener.h @@ -92,6 +92,28 @@ class CTCPListener : public CListener { CString m_sBindHost; }; +class CUnixListener : public CListener { + public: + CUnixListener(const CString& sPath, const CString& sURIPrefix, bool bSSL, + EAcceptType eAccept) + : CListener(sURIPrefix, bSSL, eAccept), + m_sPath(sPath) {} + ~CUnixListener(); + + CUnixListener(const CUnixListener&) = delete; + CUnixListener& operator=(const CUnixListener&) = delete; + + // Getters + const CString& GetPath() const { return m_sPath; } + // !Getters + + bool Listen() override; + CConfig ToConfig() const override; + + protected: + CString m_sPath; +}; + class CRealListener : public CZNCSock { public: CRealListener(CListener& listener) : CZNCSock(), m_Listener(listener) {} diff --git a/include/znc/znc.h b/include/znc/znc.h index 80720d3d..d81aa392 100644 --- a/include/znc/znc.h +++ b/include/znc/znc.h @@ -204,6 +204,8 @@ class CZNC { bool AddListener(unsigned short uPort, const CString& sBindHost, const CString& sURIPrefix, bool bSSL, EAddrType eAddr, CListener::EAcceptType eAccept, CString& sError); + bool AddListener(const CString& sPath, const CString& sURIPrefix, bool bSSL, + CListener::EAcceptType eAccept, CString& sError); bool DelListener(CListener*); // Message of the Day diff --git a/src/Listener.cpp b/src/Listener.cpp index 40b8e388..d5513982 100644 --- a/src/Listener.cpp +++ b/src/Listener.cpp @@ -83,6 +83,35 @@ CConfig CTCPListener::ToConfig() const { return listenerConfig; } +CUnixListener::~CUnixListener() { +} + +bool CUnixListener::Listen() { + CString sName = "unix:" + m_sPath; + + m_pListener = new CRealListener(*this); + +#ifdef HAVE_LIBSSL + if (IsSSL()) { + m_pListener->SetSSL(true); + m_pListener->SetPemLocation(CZNC::Get().GetPemLocation()); + m_pListener->SetKeyLocation(CZNC::Get().GetKeyLocation()); + m_pListener->SetDHParamLocation(CZNC::Get().GetDHParamLocation()); + } +#endif + + CZNC::Get().GetManager().AddSock(m_pListener, sName); + return m_pListener->ListenUnix(m_sPath); +} + +CConfig CUnixListener::ToConfig() const { + CConfig listenerConfig = CListener::ToConfig(); + + listenerConfig.AddKeyValuePair("Path", GetPath()); + + return listenerConfig; +} + void CListener::ResetRealListener() { m_pListener = nullptr; } CRealListener::~CRealListener() { m_Listener.ResetRealListener(); } diff --git a/src/znc.cpp b/src/znc.cpp index 1f6de301..9db0fef4 100644 --- a/src/znc.cpp +++ b/src/znc.cpp @@ -1717,9 +1717,70 @@ bool CZNC::AddListener(unsigned short uPort, const CString& sBindHost, return true; } +bool CZNC::AddListener(const CString& sPath, const CString& sURIPrefixRaw, + bool bSSL, CListener::EAcceptType eAccept, + CString& sError) { + CUtils::PrintAction("Binding to path [" + sPath + "]"); + +#ifndef HAVE_LIBSSL + if (bSSL) { + sError = "SSL is not enabled"; + CUtils::PrintStatus(false, sError); + return false; + } +#else + CString sPemFile = GetPemLocation(); + + if (bSSL && !CFile::Exists(sPemFile)) { + sError = "Unable to locate pem file: [" + sPemFile + "]"; + CUtils::PrintStatus(false, sError); + + // If stdin is e.g. /dev/null and we call GetBoolInput(), + // we are stuck in an endless loop! + if (isatty(0) && + CUtils::GetBoolInput("Would you like to create a new pem file?", + true)) { + sError.clear(); + WritePemFile(); + } else { + return false; + } + + CUtils::PrintAction("Binding to path [" + sPath + "]"); + } +#endif + + // URIPrefix must start with a slash and end without one. + CString sURIPrefix = CString(sURIPrefixRaw); + if (!sURIPrefix.empty()) { + if (!sURIPrefix.StartsWith("/")) { + sURIPrefix = "/" + sURIPrefix; + } + if (sURIPrefix.EndsWith("/")) { + sURIPrefix.TrimRight("/"); + } + } + + CListener* pListener = + new CUnixListener(sPath, sURIPrefix, bSSL, eAccept); + + if (!pListener->Listen()) { + sError = FormatBindError(); + CUtils::PrintStatus(false, sError); + delete pListener; + return false; + } + + m_vpListeners.push_back(pListener); + CUtils::PrintStatus(true); + + return true; +} + bool CZNC::AddListener(CConfig* pConfig, CString& sError) { CString sBindHost; CString sURIPrefix; + CString sPath; bool bSSL; bool b4; #ifdef HAVE_IPV6 @@ -1730,32 +1791,22 @@ bool CZNC::AddListener(CConfig* pConfig, CString& sError) { bool bIRC; bool bWeb; unsigned short uPort; + bool bTcpListener = true; + if (!pConfig->FindUShortEntry("port", uPort)) { - sError = "No port given"; - CUtils::PrintError(sError); - return false; + bTcpListener = false; + if (!pConfig->FindStringEntry("path", sPath)) { + sError = "No port and no path given"; + CUtils::PrintError(sError); + return false; + } } - pConfig->FindStringEntry("host", sBindHost); + pConfig->FindBoolEntry("ssl", bSSL, false); - pConfig->FindBoolEntry("ipv4", b4, true); - pConfig->FindBoolEntry("ipv6", b6, b6); pConfig->FindBoolEntry("allowirc", bIRC, true); pConfig->FindBoolEntry("allowweb", bWeb, true); pConfig->FindStringEntry("uriprefix", sURIPrefix); - EAddrType eAddr; - if (b4 && b6) { - eAddr = ADDR_ALL; - } else if (b4 && !b6) { - eAddr = ADDR_IPV4ONLY; - } else if (!b4 && b6) { - eAddr = ADDR_IPV6ONLY; - } else { - sError = "No address family given"; - CUtils::PrintError(sError); - return false; - } - CListener::EAcceptType eAccept; if (bIRC && bWeb) { eAccept = CListener::ACCEPT_ALL; @@ -1769,8 +1820,28 @@ bool CZNC::AddListener(CConfig* pConfig, CString& sError) { return false; } - return AddListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept, - sError); + if (bTcpListener) { + pConfig->FindStringEntry("host", sBindHost); + pConfig->FindBoolEntry("ipv4", b4, true); + pConfig->FindBoolEntry("ipv6", b6, b6); + + EAddrType eAddr; + if (b4 && b6) { + eAddr = ADDR_ALL; + } else if (b4 && !b6) { + eAddr = ADDR_IPV4ONLY; + } else if (!b4 && b6) { + eAddr = ADDR_IPV6ONLY; + } else { + sError = "No address family given"; + CUtils::PrintError(sError); + return false; + } + + return AddListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept, + sError); + } + return AddListener(sPath, sURIPrefix, bSSL, eAccept, sError); } bool CZNC::AddListener(CListener* pListener) { From b5d77a8adefd02441106ff318bbd07cccb06908e Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Thu, 2 Nov 2017 12:03:08 +0100 Subject: [PATCH 06/35] CListener: Split out common code to setup ssl Signed-off-by: Uli Schlachter --- include/znc/Listener.h | 2 ++ src/Listener.cpp | 29 ++++++++++++++--------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/include/znc/Listener.h b/include/znc/Listener.h index 002a3098..e42aab73 100644 --- a/include/znc/Listener.h +++ b/include/znc/Listener.h @@ -57,6 +57,8 @@ class CListener { private: protected: + void setupSSL(CRealListener* listener) const; + bool m_bSSL; CString m_sURIPrefix; CRealListener* m_pListener; diff --git a/src/Listener.cpp b/src/Listener.cpp index d5513982..e9584dfc 100644 --- a/src/Listener.cpp +++ b/src/Listener.cpp @@ -39,6 +39,17 @@ CConfig CListener::ToConfig() const { return listenerConfig; } +void CListener::setupSSL(CRealListener* listener) const { +#ifdef HAVE_LIBSSL + if (IsSSL()) { + m_pListener->SetSSL(true); + m_pListener->SetPemLocation(CZNC::Get().GetPemLocation()); + m_pListener->SetKeyLocation(CZNC::Get().GetKeyLocation()); + m_pListener->SetDHParamLocation(CZNC::Get().GetDHParamLocation()); + } +#endif +} + CTCPListener::~CTCPListener() { } @@ -50,14 +61,10 @@ bool CTCPListener::Listen() { m_pListener = new CRealListener(*this); + setupSSL(m_pListener); bool bSSL = false; #ifdef HAVE_LIBSSL - if (IsSSL()) { - bSSL = true; - m_pListener->SetPemLocation(CZNC::Get().GetPemLocation()); - m_pListener->SetKeyLocation(CZNC::Get().GetKeyLocation()); - m_pListener->SetDHParamLocation(CZNC::Get().GetDHParamLocation()); - } + bSSL = IsSSL(); #endif // If e.g. getaddrinfo() fails, the following might not set errno. @@ -90,15 +97,7 @@ bool CUnixListener::Listen() { CString sName = "unix:" + m_sPath; m_pListener = new CRealListener(*this); - -#ifdef HAVE_LIBSSL - if (IsSSL()) { - m_pListener->SetSSL(true); - m_pListener->SetPemLocation(CZNC::Get().GetPemLocation()); - m_pListener->SetKeyLocation(CZNC::Get().GetKeyLocation()); - m_pListener->SetDHParamLocation(CZNC::Get().GetDHParamLocation()); - } -#endif + setupSSL(m_pListener); CZNC::Get().GetManager().AddSock(m_pListener, sName); return m_pListener->ListenUnix(m_sPath); From 29a646b7730187cc614733a39e6bae266debe515 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Thu, 12 Jan 2017 12:04:31 +0100 Subject: [PATCH 07/35] WIP: Add ToDos to some places that display listeners and should handle CUnixListener Signed-off-by: Uli Schlachter --- modules/webadmin.cpp | 1 + src/ClientCommand.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/modules/webadmin.cpp b/modules/webadmin.cpp index 722c45a6..725309bf 100644 --- a/modules/webadmin.cpp +++ b/modules/webadmin.cpp @@ -1895,6 +1895,7 @@ class CWebAdminMod : public CModule { l["SuggestDeletion"] = CString(pTCPListener->GetPort() != WebSock.GetLocalPort()); } + // TODO: Handle CUnixListener l["IsHTTP"] = CString(pListener->GetAcceptType() != CListener::ACCEPT_IRC); diff --git a/src/ClientCommand.cpp b/src/ClientCommand.cpp index cdd8c9e0..f01f8e47 100644 --- a/src/ClientCommand.cpp +++ b/src/ClientCommand.cpp @@ -1558,6 +1558,7 @@ void CClient::UserPortCommand(CString& sLine) { ? "All" : (eAddr == ADDR_IPV4ONLY ? "IPv4" : "IPv6"))); } else { + // TODO: Handle CUnixListener Table.SetCell("Port", "unknown"); } From 7621a31af5af5ecb1da466657a0efd80576f661b Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Mon, 6 Nov 2017 15:29:44 +0100 Subject: [PATCH 08/35] Rename AddListener() variants into AddTCPListener() and AddUnixListener Signed-off-by: Uli Schlachter --- include/znc/znc.h | 16 ++++++++++++---- src/znc.cpp | 14 +++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/include/znc/znc.h b/include/znc/znc.h index d81aa392..85b07125 100644 --- a/include/znc/znc.h +++ b/include/znc/znc.h @@ -201,12 +201,20 @@ class CZNC { CListener* FindListener(u_short uPort, const CString& BindHost, EAddrType eAddr); bool AddListener(CListener*); + bool AddTCPListener(unsigned short uPort, const CString& sBindHost, + const CString& sURIPrefix, bool bSSL, EAddrType eAddr, + CListener::EAcceptType eAccept, CString& sError); + bool AddUnixListener(const CString& sPath, const CString& sURIPrefix, bool bSSL, + CListener::EAcceptType eAccept, CString& sError); + bool DelListener(CListener*); + + // For backwards-compatibility TODO: Remove + /// @deprecated use AddTCPListener bool AddListener(unsigned short uPort, const CString& sBindHost, const CString& sURIPrefix, bool bSSL, EAddrType eAddr, - CListener::EAcceptType eAccept, CString& sError); - bool AddListener(const CString& sPath, const CString& sURIPrefix, bool bSSL, - CListener::EAcceptType eAccept, CString& sError); - bool DelListener(CListener*); + CListener::EAcceptType eAccept, CString& sError) { + return AddTCPListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept, sError); + } // Message of the Day void SetMotd(const CString& sMessage) { diff --git a/src/znc.cpp b/src/znc.cpp index 9db0fef4..bb022c8a 100644 --- a/src/znc.cpp +++ b/src/znc.cpp @@ -1623,7 +1623,7 @@ bool CZNC::AddListener(const CString& sLine, CString& sError) { sError); } -bool CZNC::AddListener(unsigned short uPort, const CString& sBindHost, +bool CZNC::AddTCPListener(unsigned short uPort, const CString& sBindHost, const CString& sURIPrefixRaw, bool bSSL, EAddrType eAddr, CListener::EAcceptType eAccept, CString& sError) { CString sHostComment; @@ -1717,9 +1717,9 @@ bool CZNC::AddListener(unsigned short uPort, const CString& sBindHost, return true; } -bool CZNC::AddListener(const CString& sPath, const CString& sURIPrefixRaw, - bool bSSL, CListener::EAcceptType eAccept, - CString& sError) { +bool CZNC::AddUnixListener(const CString& sPath, const CString& sURIPrefixRaw, + bool bSSL, CListener::EAcceptType eAccept, + CString& sError) { CUtils::PrintAction("Binding to path [" + sPath + "]"); #ifndef HAVE_LIBSSL @@ -1838,10 +1838,10 @@ bool CZNC::AddListener(CConfig* pConfig, CString& sError) { return false; } - return AddListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept, - sError); + return AddTCPListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, + eAccept, sError); } - return AddListener(sPath, sURIPrefix, bSSL, eAccept, sError); + return AddUnixListener(sPath, sURIPrefix, bSSL, eAccept, sError); } bool CZNC::AddListener(CListener* pListener) { From 48dc52a0da31d6b3105a737073c85ef73230d254 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Mon, 6 Nov 2017 16:26:34 +0100 Subject: [PATCH 09/35] Add an integration test for unix sockets Signed-off-by: Uli Schlachter --- test/integration/main.cpp | 42 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test/integration/main.cpp b/test/integration/main.cpp index f5edf19b..ee65d449 100644 --- a/test/integration/main.cpp +++ b/test/integration/main.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -151,6 +152,7 @@ class IO { // Need to flush QTcpSocket, and QIODevice doesn't have flush at all... static void FlushIfCan(QIODevice*) {} static void FlushIfCan(QTcpSocket* sock) { sock->flush(); } + static void FlushIfCan(QLocalSocket* sock) { sock->flush(); } Device* m_device; bool m_verbose; @@ -901,4 +903,44 @@ TEST_F(ZNCTest, ModuleCrypt) { Z; } +TEST(UnixSocketTest, Connect) { + // Create a config + QTemporaryDir tempDir; + QDir dir(tempDir.path()); + QString socketName = dir.filePath("socket"); + dir.mkdir("configs"); + QFile config(dir.filePath("configs/znc.conf")); + ASSERT_TRUE(config.open(QIODevice::WriteOnly | QIODevice::Text)); + Z; + + QTextStream out(&config); + // FIXME: Hardcoding a version is bad + out << "Version = 1.7.x\n\nPath = "; + out << socketName; + out << "\nSSL = false\n\n"; + out << "\nPass = test\nAdmin = true\n\n"; + config.close(); + Z; + + // Start znc + Process p(ZNC_BIN_DIR "/znc", QStringList() << "--debug" + << "--datadir" << dir.path()); + p.ReadUntil("https://znc.in"); + Z; + + // Connect to it and log in + QLocalSocket socket; + socket.connectToServer(socketName); + ASSERT_TRUE(socket.waitForConnected()) + << socket.errorString().toStdString(); + Z; + + auto wrapper = IO(&socket, true); + wrapper.Write("PASS :test:test"); + wrapper.Write("NICK test"); + wrapper.Write("USER user/test x x :x"); + Z; + wrapper.ReadUntil("Welcome to ZNC"); +} + } // namespace From b952502eae79794c5cce6e301834910590fb86b7 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Sat, 19 Apr 2025 20:16:46 +0100 Subject: [PATCH 10/35] Dedup code between TCP and Unix listener --- include/znc/Listener.h | 2 +- include/znc/znc.h | 2 + src/Listener.cpp | 27 ++++---- src/znc.cpp | 139 ++++++++++++++--------------------------- 4 files changed, 66 insertions(+), 104 deletions(-) diff --git a/include/znc/Listener.h b/include/znc/Listener.h index d8e43185..383547a2 100644 --- a/include/znc/Listener.h +++ b/include/znc/Listener.h @@ -57,7 +57,7 @@ class CListener { private: protected: - void setupSSL(CRealListener* listener) const; + void SetupSSL() const; bool m_bSSL; CString m_sURIPrefix; diff --git a/include/znc/znc.h b/include/znc/znc.h index ef3eb2dd..27b851de 100644 --- a/include/znc/znc.h +++ b/include/znc/znc.h @@ -277,6 +277,8 @@ class CZNC : private CCoreTranslationMixin { CString MakeConfigHeader(); bool AddListener(const CString& sLine, CString& sError); bool AddListener(CConfig* pConfig, CString& sError); + bool CheckSslAndPemFile(bool bSSL, CString& sError); + bool FinishAddingListener(CListener* pListener, CString& sError); protected: time_t m_TimeStarted; diff --git a/src/Listener.cpp b/src/Listener.cpp index 71bdbb18..2d37589f 100644 --- a/src/Listener.cpp +++ b/src/Listener.cpp @@ -39,7 +39,7 @@ CConfig CListener::ToConfig() const { return listenerConfig; } -void CListener::setupSSL(CRealListener* listener) const { +void CListener::SetupSSL() const { #ifdef HAVE_LIBSSL if (IsSSL()) { m_pListener->SetSSL(true); @@ -60,19 +60,14 @@ bool CTCPListener::Listen() { } m_pListener = new CRealListener(*this); - - setupSSL(m_pListener); - bool bSSL = false; -#ifdef HAVE_LIBSSL - bSSL = IsSSL(); -#endif + SetupSSL(); // If e.g. getaddrinfo() fails, the following might not set errno. // Make sure there is a consistent error message, not something random // which might even be "Error: Success". errno = EINVAL; return CZNC::Get().GetManager().ListenHost(m_uPort, "_LISTENER", - m_sBindHost, bSSL, SOMAXCONN, + m_sBindHost, IsSSL(), SOMAXCONN, m_pListener, 0, m_eAddr); } @@ -94,13 +89,21 @@ CUnixListener::~CUnixListener() { } bool CUnixListener::Listen() { - CString sName = "unix:" + m_sPath; + if (m_pListener) { + errno = EINVAL; + return false; + } m_pListener = new CRealListener(*this); - setupSSL(m_pListener); + SetupSSL(); - CZNC::Get().GetManager().AddSock(m_pListener, sName); - return m_pListener->ListenUnix(m_sPath); + if (m_pListener->ListenUnix(m_sPath)) { + CZNC::Get().GetManager().AddSock(m_pListener, "UNIX_LISTENER"); + return true; + } + + delete m_pListener; + return false; } CConfig CUnixListener::ToConfig() const { diff --git a/src/znc.cpp b/src/znc.cpp index 30cae265..8c233ff6 100644 --- a/src/znc.cpp +++ b/src/znc.cpp @@ -1620,8 +1620,37 @@ bool CZNC::AddListener(const CString& sLine, CString& sError) { sError); } +bool CZNC::CheckSslAndPemFile(bool bSSL, CString& sError) { +#ifndef HAVE_LIBSSL + if (bSSL) { + sError = t_s("SSL is not enabled"); + CUtils::PrintStatus(false, sError); + return false; + } +#else + CString sPemFile = GetPemLocation(); + + if (bSSL && !CFile::Exists(sPemFile)) { + sError = t_f("Unable to locate pem file: {1}")(sPemFile); + CUtils::PrintStatus(false, sError); + + // If stdin is e.g. /dev/null and we call GetBoolInput(), + // we are stuck in an endless loop! + if (isatty(0) && + CUtils::GetBoolInput("Would you like to create a new pem file?", + true)) { + sError.clear(); + WritePemFile(); + } else { + return false; + } + } +#endif + return true; +} + bool CZNC::AddTCPListener(unsigned short uPort, const CString& sBindHost, - const CString& sURIPrefixRaw, bool bSSL, EAddrType eAddr, + const CString& sURIPrefix, bool bSSL, EAddrType eAddr, CListener::EAcceptType eAccept, CString& sError) { CString sHostComment; @@ -1653,114 +1682,32 @@ bool CZNC::AddTCPListener(unsigned short uPort, const CString& sBindHost, } #endif -#ifndef HAVE_LIBSSL - if (bSSL) { - sError = t_s("SSL is not enabled"); - CUtils::PrintStatus(false, sError); - return false; - } -#else - CString sPemFile = GetPemLocation(); + if (!CheckSslAndPemFile(bSSL, sError)) return false; - if (bSSL && !CFile::Exists(sPemFile)) { - sError = t_f("Unable to locate pem file: {1}")(sPemFile); - CUtils::PrintStatus(false, sError); - - // If stdin is e.g. /dev/null and we call GetBoolInput(), - // we are stuck in an endless loop! - if (isatty(0) && - CUtils::GetBoolInput("Would you like to create a new pem file?", - true)) { - sError.clear(); - WritePemFile(); - } else { - return false; - } - - CUtils::PrintAction("Binding to port [+" + CString(uPort) + "]" + - sHostComment + sIPV6Comment); - } -#endif if (!uPort) { sError = t_s("Invalid port"); CUtils::PrintStatus(false, sError); return false; } - // URIPrefix must start with a slash and end without one. - CString sURIPrefix = CString(sURIPrefixRaw); - if (!sURIPrefix.empty()) { - if (!sURIPrefix.StartsWith("/")) { - sURIPrefix = "/" + sURIPrefix; - } - if (sURIPrefix.EndsWith("/")) { - sURIPrefix.TrimRight("/"); - } - } - CListener* pListener = new CTCPListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept); - - if (!pListener->Listen()) { - sError = FormatBindError(); - CUtils::PrintStatus(false, sError); - delete pListener; - return false; - } - - m_vpListeners.push_back(pListener); - CUtils::PrintStatus(true); - - return true; + return FinishAddingListener(pListener, sError); } -bool CZNC::AddUnixListener(const CString& sPath, const CString& sURIPrefixRaw, +bool CZNC::AddUnixListener(const CString& sPath, const CString& sURIPrefix, bool bSSL, CListener::EAcceptType eAccept, CString& sError) { - CUtils::PrintAction("Binding to path [" + sPath + "]"); + CUtils::PrintAction("Binding to path [" + sPath + "]" + (bSSL ? " with SSL" : "")); -#ifndef HAVE_LIBSSL - if (bSSL) { - sError = "SSL is not enabled"; - CUtils::PrintStatus(false, sError); - return false; - } -#else - CString sPemFile = GetPemLocation(); - - if (bSSL && !CFile::Exists(sPemFile)) { - sError = "Unable to locate pem file: [" + sPemFile + "]"; - CUtils::PrintStatus(false, sError); - - // If stdin is e.g. /dev/null and we call GetBoolInput(), - // we are stuck in an endless loop! - if (isatty(0) && - CUtils::GetBoolInput("Would you like to create a new pem file?", - true)) { - sError.clear(); - WritePemFile(); - } else { - return false; - } - - CUtils::PrintAction("Binding to path [" + sPath + "]"); - } -#endif - - // URIPrefix must start with a slash and end without one. - CString sURIPrefix = CString(sURIPrefixRaw); - if (!sURIPrefix.empty()) { - if (!sURIPrefix.StartsWith("/")) { - sURIPrefix = "/" + sURIPrefix; - } - if (sURIPrefix.EndsWith("/")) { - sURIPrefix.TrimRight("/"); - } - } + if (!CheckSslAndPemFile(bSSL, sError)) return false; CListener* pListener = new CUnixListener(sPath, sURIPrefix, bSSL, eAccept); + return FinishAddingListener(pListener, sError); +} +bool CZNC::FinishAddingListener(CListener* pListener, CString& sError) { if (!pListener->Listen()) { sError = FormatBindError(); CUtils::PrintStatus(false, sError); @@ -1817,6 +1764,16 @@ bool CZNC::AddListener(CConfig* pConfig, CString& sError) { return false; } + // URIPrefix must start with a slash and end without one. + if (!sURIPrefix.empty()) { + if (!sURIPrefix.StartsWith("/")) { + sURIPrefix = "/" + sURIPrefix; + } + if (sURIPrefix.EndsWith("/")) { + sURIPrefix.TrimRight("/"); + } + } + if (bTcpListener) { pConfig->FindStringEntry("host", sBindHost); pConfig->FindBoolEntry("ipv4", b4, true); From 3348de5b974ccd026d083a4c2219e7e518c9143f Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Sat, 19 Apr 2025 20:23:08 +0100 Subject: [PATCH 11/35] Remove old compatibility code in webadmin Previously it was creating its own listener, but http support migrated to core ages ago --- modules/webadmin.cpp | 76 -------------------------------------------- 1 file changed, 76 deletions(-) diff --git a/modules/webadmin.cpp b/modules/webadmin.cpp index 84f8ad11..fcfa027c 100644 --- a/modules/webadmin.cpp +++ b/modules/webadmin.cpp @@ -99,82 +99,6 @@ class CWebAdminMod : public CModule { ~CWebAdminMod() override {} - bool OnLoad(const CString& sArgStr, CString& sMessage) override { - if (sArgStr.empty() || CModInfo::GlobalModule != GetType()) return true; - - // We don't accept any arguments, but for backwards - // compatibility we have to do some magic here. - sMessage = "Arguments converted to new syntax"; - - bool bSSL = false; - bool bIPv6 = false; - bool bShareIRCPorts = true; - unsigned short uPort = 8080; - CString sArgs(sArgStr); - CString sPort; - CString sListenHost; - CString sURIPrefix; - - while (sArgs.Left(1) == "-") { - CString sOpt = sArgs.Token(0); - sArgs = sArgs.Token(1, true); - - if (sOpt.Equals("-IPV6")) { - bIPv6 = true; - } else if (sOpt.Equals("-IPV4")) { - bIPv6 = false; - } else if (sOpt.Equals("-noircport")) { - bShareIRCPorts = false; - } else { - // Uhm... Unknown option? Let's just ignore all - // arguments, older versions would have returned - // an error and denied loading - return true; - } - } - - // No arguments left: Only port sharing - if (sArgs.empty() && bShareIRCPorts) return true; - - if (sArgs.find(" ") != CString::npos) { - sListenHost = sArgs.Token(0); - sPort = sArgs.Token(1, true); - } else { - sPort = sArgs; - } - - if (sPort.Left(1) == "+") { - sPort.TrimLeft("+"); - bSSL = true; - } - - if (!sPort.empty()) { - uPort = sPort.ToUShort(); - } - - if (!bShareIRCPorts) { - // Make all existing listeners IRC-only - const vector& vListeners = CZNC::Get().GetListeners(); - for (CListener* pListener : vListeners) { - pListener->SetAcceptType(CListener::ACCEPT_IRC); - } - } - - // Now turn that into a listener instance - CListener* pListener = new CTCPListener( - uPort, sListenHost, sURIPrefix, bSSL, - (!bIPv6 ? ADDR_IPV4ONLY : ADDR_ALL), CListener::ACCEPT_HTTP); - - if (!pListener->Listen()) { - sMessage = "Failed to add backwards-compatible listener"; - return false; - } - CZNC::Get().AddListener(pListener); - - SetArgs(""); - return true; - } - CUser* GetNewUser(CWebSock& WebSock, CUser* pUser) { std::shared_ptr spSession = WebSock.GetSession(); CString sUsername = WebSock.GetParam("newuser"); From 18af9b089578aa7d335fe1d154018c6672a28eb2 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Sat, 19 Apr 2025 20:44:39 +0100 Subject: [PATCH 12/35] More cleanup of listeners --- modules/webadmin.cpp | 6 ++---- src/ClientCommand.cpp | 18 +++++++++--------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/modules/webadmin.cpp b/modules/webadmin.cpp index fcfa027c..5f81f6d9 100644 --- a/modules/webadmin.cpp +++ b/modules/webadmin.cpp @@ -1994,8 +1994,8 @@ class CWebAdminMod : public CModule { } #endif -#ifdef HAVE_IPV6 if (pTCPListener != nullptr) { +#ifdef HAVE_IPV6 switch (pTCPListener->GetAddrType()) { case ADDR_IPV4ONLY: l["IsIPV4"] = "true"; @@ -2008,12 +2008,10 @@ class CWebAdminMod : public CModule { l["IsIPV6"] = "true"; break; } - } #else - if (pTCPListener != nullptr) { l["IsIPV4"] = "true"; - } #endif + } } vector vDirs; diff --git a/src/ClientCommand.cpp b/src/ClientCommand.cpp index a681294e..1cae443a 100644 --- a/src/ClientCommand.cpp +++ b/src/ClientCommand.cpp @@ -1636,18 +1636,14 @@ void CClient::UserPortCommand(CString& sLine) { for (const CListener* pListener : vpListeners) { Table.AddRow(); - const CTCPListener* pTCPListener = - dynamic_cast(pListener); - if (pTCPListener != nullptr) { + if (const CTCPListener* pTCPListener = + dynamic_cast(pListener)) { Table.SetCell(t_s("Port", "listports"), CString(pTCPListener->GetPort())); Table.SetCell(t_s("BindHost", "listports"), (pTCPListener->GetBindHost().empty() ? CString("*") : pTCPListener->GetBindHost())); - Table.SetCell(t_s("SSL", "listports"), - pListener->IsSSL() ? t_s("yes", "listports|ssl") - : t_s("no", "listports|ssl")); EAddrType eAddr = pTCPListener->GetAddrType(); Table.SetCell( @@ -1656,10 +1652,14 @@ void CClient::UserPortCommand(CString& sLine) { ? t_s("IPv4 and IPv6", "listports") : (eAddr == ADDR_IPV4ONLY ? t_s("IPv4", "listports") : t_s("IPv6", "listports"))); - } else { - // TODO: Handle CUnixListener - Table.SetCell("Port", "unknown"); + } else if (const CUnixListener* pUnixListener = + dynamic_cast(pListener)) { + Table.SetCell(t_s("BindHost", "listports"), + pUnixListener->GetPath()); } + Table.SetCell(t_s("SSL", "listports"), + pListener->IsSSL() ? t_s("yes", "listports|ssl") + : t_s("no", "listports|ssl")); CListener::EAcceptType eAccept = pListener->GetAcceptType(); Table.SetCell(t_s("IRC", "listports"), From dab1127090fc149e464653d651e235bc20467e25 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Sat, 19 Apr 2025 22:17:30 +0100 Subject: [PATCH 13/35] Add unix listener support to *status addport, delport --- include/znc/znc.h | 1 + src/ClientCommand.cpp | 119 ++++++++++++++++++++++++++---------------- src/znc.cpp | 10 ++++ 3 files changed, 86 insertions(+), 44 deletions(-) diff --git a/include/znc/znc.h b/include/znc/znc.h index 27b851de..e6da2a2e 100644 --- a/include/znc/znc.h +++ b/include/znc/znc.h @@ -202,6 +202,7 @@ class CZNC : private CCoreTranslationMixin { // Listener yummy CListener* FindListener(u_short uPort, const CString& BindHost, EAddrType eAddr); + CListener* FindUnixListener(const CString& sPath); bool AddListener(CListener*); bool AddTCPListener(unsigned short uPort, const CString& sBindHost, const CString& sURIPrefix, bool bSSL, EAddrType eAddr, diff --git a/src/ClientCommand.cpp b/src/ClientCommand.cpp index 1cae443a..40557c30 100644 --- a/src/ClientCommand.cpp +++ b/src/ClientCommand.cpp @@ -1620,6 +1620,10 @@ void CClient::UserCommand(CString& sLine) { } } +namespace { +struct PortCommandUsage {}; +} + void CClient::UserPortCommand(CString& sLine) { const CString sCommand = sLine.Token(0); @@ -1654,7 +1658,7 @@ void CClient::UserPortCommand(CString& sLine) { : t_s("IPv6", "listports"))); } else if (const CUnixListener* pUnixListener = dynamic_cast(pListener)) { - Table.SetCell(t_s("BindHost", "listports"), + Table.SetCell(t_s("Port", "listports"), pUnixListener->GetPath()); } Table.SetCell(t_s("SSL", "listports"), @@ -1680,68 +1684,92 @@ void CClient::UserPortCommand(CString& sLine) { return; } + auto ParseEAddr = [](const CString& sAddr) { + if (sAddr.Equals("IPV4")) { + return ADDR_IPV4ONLY; + } else if (sAddr.Equals("IPV6")) { + return ADDR_IPV6ONLY; + } else if (sAddr.Equals("ALL")) { + return ADDR_ALL; + } else { + throw PortCommandUsage{}; + } + }; + + auto ParseEAccept = [](const CString& sAccept) { + if (sAccept.Equals("WEB")) { + return CListener::ACCEPT_HTTP; + } else if (sAccept.Equals("IRC")) { + return CListener::ACCEPT_IRC; + } else if (sAccept.Equals("ALL")) { + return CListener::ACCEPT_ALL; + } else { + throw PortCommandUsage{}; + } + }; + CString sPort = sLine.Token(1); - CString sAddr = sLine.Token(2); - EAddrType eAddr = ADDR_ALL; - - if (sAddr.Equals("IPV4")) { - eAddr = ADDR_IPV4ONLY; - } else if (sAddr.Equals("IPV6")) { - eAddr = ADDR_IPV6ONLY; - } else if (sAddr.Equals("ALL")) { - eAddr = ADDR_ALL; - } else { - sAddr.clear(); - } - unsigned short uPort = sPort.ToUShort(); if (sCommand.Equals("ADDPORT")) { - CListener::EAcceptType eAccept = CListener::ACCEPT_ALL; - CString sAccept = sLine.Token(3); + try { + if (sPort.empty()) { + throw PortCommandUsage{}; + } - if (sAccept.Equals("WEB")) { - eAccept = CListener::ACCEPT_HTTP; - } else if (sAccept.Equals("IRC")) { - eAccept = CListener::ACCEPT_IRC; - } else if (sAccept.Equals("ALL")) { - eAccept = CListener::ACCEPT_ALL; - } else { - sAccept.clear(); - } + std::unique_ptr pListener; + if (sPort.TrimPrefix("unix:")) { + bool bSSL = sPort.TrimPrefix("+"); + const CString& sPath = sPort; + CListener::EAcceptType eAccept = ParseEAccept(sLine.Token(2)); + CString sURIPrefix = sLine.Token(3); - if (sPort.empty() || sAddr.empty() || sAccept.empty()) { - PutStatus( - t_s("Usage: AddPort <[+]port> " - "[bindhost [uriprefix]]")); - } else { - bool bSSL = (sPort.StartsWith("+")); - const CString sBindHost = sLine.Token(4); - const CString sURIPrefix = sLine.Token(5); + pListener.reset(new CUnixListener(sPath, sURIPrefix, bSSL, eAccept)); + } else { + bool bSSL = sPort.StartsWith("+"); + EAddrType eAddr = ParseEAddr(sLine.Token(2)); + CListener::EAcceptType eAccept = ParseEAccept(sLine.Token(3)); + const CString sBindHost = sLine.Token(4); + const CString sURIPrefix = sLine.Token(5); - CListener* pListener = new CTCPListener(uPort, sBindHost, sURIPrefix, - bSSL, eAddr, eAccept); + pListener.reset(new CTCPListener(uPort, sBindHost, sURIPrefix, + bSSL, eAddr, eAccept)); + } if (!pListener->Listen()) { auto e = errno; - delete pListener; PutStatus(t_f("Unable to bind: {1}")(CString(strerror(e)))); } else { - if (CZNC::Get().AddListener(pListener)) { + if (CZNC::Get().AddListener(pListener.release())) { PutStatus(t_s("Port added")); } else { PutStatus(t_s("Couldn't add port")); } } + } catch (PortCommandUsage) { + PutStatus( + t_s("Usage: AddPort <[+]port> " + "[bindhost [uriprefix]]")); + PutStatus( + t_s("Or: AddPort unix:[+]/path/to/socket " + "[uriprefix]")); + PutStatus(t_s("+ means SSL")); } } else if (sCommand.Equals("DELPORT")) { - if (sPort.empty() || sAddr.empty()) { - PutStatus(t_s("Usage: DelPort [bindhost]")); - } else { - const CString sBindHost = sLine.Token(3); - - CListener* pListener = - CZNC::Get().FindListener(uPort, sBindHost, eAddr); + try { + if (sPort.empty()) { + throw PortCommandUsage{}; + } + CListener* pListener; + if (sPort.TrimPrefix("unix:")) { + sPort.TrimPrefix("+"); + pListener = CZNC::Get().FindUnixListener(sPort); + } else { + CString sAddr = sLine.Token(2); + CString sBindHost = sLine.Token(3); + pListener = CZNC::Get().FindListener( + uPort, sBindHost, ParseEAddr(sAddr)); + } if (pListener) { CZNC::Get().DelListener(pListener); @@ -1749,6 +1777,9 @@ void CClient::UserPortCommand(CString& sLine) { } else { PutStatus(t_s("Unable to find a matching port")); } + } catch (PortCommandUsage) { + PutStatus(t_s("Usage: DelPort [bindhost]")); + PutStatus(t_s("Or: DelPort unix:/path/to/socket")); } } } diff --git a/src/znc.cpp b/src/znc.cpp index 8c233ff6..a7dc3ebc 100644 --- a/src/znc.cpp +++ b/src/znc.cpp @@ -1575,6 +1575,16 @@ CListener* CZNC::FindListener(u_short uPort, const CString& sBindHost, return nullptr; } +CListener* CZNC::FindUnixListener(const CString& sPath) { + for (CListener* pListener : m_vpListeners) { + CUnixListener* pUnixListener = dynamic_cast(pListener); + if (!pUnixListener) continue; + if (pUnixListener->GetPath() != sPath) continue; + return pListener; + } + return nullptr; +} + bool CZNC::AddListener(const CString& sLine, CString& sError) { CString sName = sLine.Token(0); CString sValue = sLine.Token(1, true); From 0af3e0705f8e486ec871ffe48669b2f2e2793045 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Sat, 19 Apr 2025 22:59:09 +0100 Subject: [PATCH 14/35] Configure unix listeners in webadmin --- modules/data/webadmin/tmpl/settings.tmpl | 32 ++++- modules/webadmin.cpp | 146 +++++++++++++---------- 2 files changed, 112 insertions(+), 66 deletions(-) diff --git a/modules/data/webadmin/tmpl/settings.tmpl b/modules/data/webadmin/tmpl/settings.tmpl index cb08d87e..4822cd7d 100644 --- a/modules/data/webadmin/tmpl/settings.tmpl +++ b/modules/data/webadmin/tmpl/settings.tmpl @@ -12,9 +12,9 @@ - + @@ -24,17 +24,21 @@ + - -
checked="checked"/>
-
checked="checked"/>
checked="checked"/>
+ + unix: + + +
checked="checked"/>
+
checked="checked"/>
@@ -46,10 +50,15 @@
+ + + + + "/>
@@ -61,11 +70,24 @@
+ -
+
+
+
+ + "/> +
+ + +
+ + + unix: +
diff --git a/modules/webadmin.cpp b/modules/webadmin.cpp index 5f81f6d9..692f125c 100644 --- a/modules/webadmin.cpp +++ b/modules/webadmin.cpp @@ -1844,33 +1844,10 @@ class CWebAdminMod : public CModule { } bool AddListener(CWebSock& WebSock, CTemplate& Tmpl) { - unsigned short uPort = WebSock.GetParam("port").ToUShort(); - CString sHost = WebSock.GetParam("host"); CString sURIPrefix = WebSock.GetParam("uriprefix"); - if (sHost == "*") sHost = ""; bool bSSL = WebSock.GetParam("ssl").ToBool(); - bool bIPv4 = WebSock.GetParam("ipv4").ToBool(); - bool bIPv6 = WebSock.GetParam("ipv6").ToBool(); bool bIRC = WebSock.GetParam("irc").ToBool(); bool bWeb = WebSock.GetParam("web").ToBool(); - - EAddrType eAddr = ADDR_ALL; - if (bIPv4) { - if (bIPv6) { - eAddr = ADDR_ALL; - } else { - eAddr = ADDR_IPV4ONLY; - } - } else { - if (bIPv6) { - eAddr = ADDR_IPV6ONLY; - } else { - WebSock.GetSession()->AddError( - t_s("Choose either IPv4 or IPv6 or both.")); - return SettingsPage(WebSock, Tmpl); - } - } - CListener::EAcceptType eAccept; if (bIRC) { if (bWeb) { @@ -1889,8 +1866,39 @@ class CWebAdminMod : public CModule { } CString sMessage; - if (CZNC::Get().AddListener(uPort, sHost, sURIPrefix, bSSL, eAddr, - eAccept, sMessage)) { + bool bResult; + if (WebSock.GetParam("type") == "TCP") { + unsigned short uPort = WebSock.GetParam("port").ToUShort(); + CString sHost = WebSock.GetParam("host"); + if (sHost == "*") sHost = ""; + bool bIPv4 = WebSock.GetParam("ipv4").ToBool(); + bool bIPv6 = WebSock.GetParam("ipv6").ToBool(); + + EAddrType eAddr = ADDR_ALL; + if (bIPv4) { + if (bIPv6) { + eAddr = ADDR_ALL; + } else { + eAddr = ADDR_IPV4ONLY; + } + } else { + if (bIPv6) { + eAddr = ADDR_IPV6ONLY; + } else { + WebSock.GetSession()->AddError( + t_s("Choose either IPv4 or IPv6 or both.")); + return SettingsPage(WebSock, Tmpl); + } + } + bResult = CZNC::Get().AddListener(uPort, sHost, sURIPrefix, bSSL, + eAddr, eAccept, sMessage); + } else { + CString sPath = WebSock.GetParam("path"); + bResult = CZNC::Get().AddUnixListener(sPath, sURIPrefix, bSSL, + eAccept, sMessage); + } + + if (bResult) { if (!sMessage.empty()) { WebSock.GetSession()->AddSuccess(sMessage); } @@ -1906,28 +1914,34 @@ class CWebAdminMod : public CModule { } bool DelListener(CWebSock& WebSock, CTemplate& Tmpl) { - unsigned short uPort = WebSock.GetParam("port").ToUShort(); - CString sHost = WebSock.GetParam("host"); - bool bIPv4 = WebSock.GetParam("ipv4").ToBool(); - bool bIPv6 = WebSock.GetParam("ipv6").ToBool(); + CListener* pListener; - EAddrType eAddr = ADDR_ALL; - if (bIPv4) { - if (bIPv6) { - eAddr = ADDR_ALL; + if (WebSock.GetParam("type") == "TCP") { + unsigned short uPort = WebSock.GetParam("port").ToUShort(); + CString sHost = WebSock.GetParam("host"); + bool bIPv4 = WebSock.GetParam("ipv4").ToBool(); + bool bIPv6 = WebSock.GetParam("ipv6").ToBool(); + + EAddrType eAddr = ADDR_ALL; + if (bIPv4) { + if (bIPv6) { + eAddr = ADDR_ALL; + } else { + eAddr = ADDR_IPV4ONLY; + } } else { - eAddr = ADDR_IPV4ONLY; + if (bIPv6) { + eAddr = ADDR_IPV6ONLY; + } else { + WebSock.GetSession()->AddError(t_s("Invalid request.")); + return SettingsPage(WebSock, Tmpl); + } } + + pListener = CZNC::Get().FindListener(uPort, sHost, eAddr); } else { - if (bIPv6) { - eAddr = ADDR_IPV6ONLY; - } else { - WebSock.GetSession()->AddError(t_s("Invalid request.")); - return SettingsPage(WebSock, Tmpl); - } + pListener = CZNC::Get().FindUnixListener(WebSock.GetParam("path")); } - - CListener* pListener = CZNC::Get().FindListener(uPort, sHost, eAddr); if (pListener) { CZNC::Get().DelListener(pListener); if (!CZNC::Get().WriteConfig()) { @@ -1967,8 +1981,9 @@ class CWebAdminMod : public CModule { for (const CListener* pListener : vpListeners) { CTemplate& l = Tmpl.AddRow("ListenLoop"); - const CTCPListener* pTCPListener = dynamic_cast(pListener); - if (pTCPListener != nullptr) { + if (const CTCPListener* pTCPListener = + dynamic_cast(pListener)) { + l["Type"] = "TCP"; l["Port"] = CString(pTCPListener->GetPort()); l["BindHost"] = pTCPListener->GetBindHost(); @@ -1978,23 +1993,6 @@ class CWebAdminMod : public CModule { // delete web port l["SuggestDeletion"] = CString(pTCPListener->GetPort() != WebSock.GetLocalPort()); - } - // TODO: Handle CUnixListener - - l["IsHTTP"] = CString(pListener->GetAcceptType() != - CListener::ACCEPT_IRC); - l["IsIRC"] = CString(pListener->GetAcceptType() != - CListener::ACCEPT_HTTP); - - l["URIPrefix"] = pListener->GetURIPrefix() + "/"; - -#ifdef HAVE_LIBSSL - if (pListener->IsSSL()) { - l["IsSSL"] = "true"; - } -#endif - - if (pTCPListener != nullptr) { #ifdef HAVE_IPV6 switch (pTCPListener->GetAddrType()) { case ADDR_IPV4ONLY: @@ -2012,6 +2010,32 @@ class CWebAdminMod : public CModule { l["IsIPV4"] = "true"; #endif } + if (const CUnixListener* pUnixListener = + dynamic_cast(pListener)) { + l["Type"] = "Unix"; + l["Path"] = pUnixListener->GetPath(); + // We can't determine whether it's the same port, as it's + // always "localhost". Just assume the user knows what he's + // doing. Unix sockets are advanced topic anyway. + l["SuggestDeletion"] = "true"; + } + + l["IsHTTP"] = CString(pListener->GetAcceptType() != + CListener::ACCEPT_IRC); + l["IsIRC"] = CString(pListener->GetAcceptType() != + CListener::ACCEPT_HTTP); + + CString sURIPrefix = pListener->GetURIPrefix(); + if (!sURIPrefix.EndsWith("/")) { + sURIPrefix += "/"; + } + l["URIPrefix"] = sURIPrefix; + +#ifdef HAVE_LIBSSL + if (pListener->IsSSL()) { + l["IsSSL"] = "true"; + } +#endif } vector vDirs; From d3a7f125cf87a667a796e5b8e3a9c2619ccde1d1 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Sun, 20 Apr 2025 02:02:59 +0100 Subject: [PATCH 15/35] Make unix sockets work from modules --- include/znc/Socket.h | 26 ++++++++ modules/modperl/startup.pl | 21 ++++++- modules/modpython/znc.py | 17 +++++- src/Listener.cpp | 9 +-- src/Socket.cpp | 60 ++++++++++++------ test/integration/tests/scripting.cpp | 91 ++++++++++++++++++++++++++++ 6 files changed, 192 insertions(+), 32 deletions(-) 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"; From 66b17926cc8488f798f935f261ce6d03b262dd61 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Sun, 20 Apr 2025 08:54:25 +0100 Subject: [PATCH 16/35] update csocket --- third_party/Csocket | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/Csocket b/third_party/Csocket index 81d27e61..cf9a613c 160000 --- a/third_party/Csocket +++ b/third_party/Csocket @@ -1 +1 @@ -Subproject commit 81d27e6137334905fe563af483da284465065028 +Subproject commit cf9a613c2b963ab0e2946580e9f0493cbc46d524 From 63d10ccb176dd241c62898b7d39739b8dc5e0dd5 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Sun, 20 Apr 2025 22:20:52 +0100 Subject: [PATCH 17/35] Add support to connect to server via unix socket The syntax for AddServer command and config is chosen to be unix:/path or unix:ssl:/path For security reasons, only admins can add such servers, to prevent users from poking around the file system. --- include/znc/IRCNetwork.h | 2 + include/znc/Server.h | 11 ++- modules/controlpanel.cpp | 17 +++- modules/data/webadmin/files/webadmin.js | 56 +++++++++---- .../data/webadmin/tmpl/add_edit_network.tmpl | 1 + modules/webadmin.cpp | 22 ++++- src/ClientCommand.cpp | 33 +++++--- src/IRCNetwork.cpp | 81 +++++++++---------- src/Server.cpp | 68 ++++++++++++++-- third_party/Csocket | 2 +- 10 files changed, 212 insertions(+), 81 deletions(-) diff --git a/include/znc/IRCNetwork.h b/include/znc/IRCNetwork.h index 63dc5447..bc3affd6 100644 --- a/include/znc/IRCNetwork.h +++ b/include/znc/IRCNetwork.h @@ -114,6 +114,8 @@ class CIRCNetwork : private CCoreTranslationMixin { CServer* FindServer(const CString& sName) const; bool DelServer(const CString& sName, unsigned short uPort, const CString& sPass); + bool DelServer(const CServer& Server); + bool AddServer(CServer Server); bool AddServer(const CString& sName); bool AddServer(const CString& sName, unsigned short uPort, const CString& sPass = "", bool bSSL = false); diff --git a/include/znc/Server.h b/include/znc/Server.h index 4598666f..9f237ab2 100644 --- a/include/znc/Server.h +++ b/include/znc/Server.h @@ -23,13 +23,21 @@ class CServer { public: CServer(const CString& sName, unsigned short uPort = 6667, - const CString& sPass = "", bool bSSL = false); + const CString& sPass = "", bool bSSL = false, + bool bUnixSocket = false); ~CServer(); + // TODO: use C++20's =default and <=> + bool operator==(const CServer&) const; + bool operator<(const CServer&) const; + + static CServer Parse(CString sLine); + const CString& GetName() const; unsigned short GetPort() const; const CString& GetPass() const; bool IsSSL() const; + bool IsUnixSocket() const; CString GetString(bool bIncludePassword = true) const; static bool IsValidHostName(const CString& sHostName); @@ -39,6 +47,7 @@ class CServer { unsigned short m_uPort; CString m_sPass; bool m_bSSL; + bool m_bUnixSocket; }; #endif // !ZNC_SERVER_H diff --git a/modules/controlpanel.cpp b/modules/controlpanel.cpp index a8c3f121..ae23945c 100644 --- a/modules/controlpanel.cpp +++ b/modules/controlpanel.cpp @@ -21,6 +21,7 @@ #include #include #include +#include "znc/Server.h" using std::map; using std::vector; @@ -1249,6 +1250,10 @@ class CAdminMod : public CModule { PutModule( t_s("Usage: AddServer [[+]port] " "[password]")); + if (GetUser()->IsAdmin()) { + PutModule(t_s("Or: AddServer unix:[ssl:]/path/to/socket")); + } + PutModule(t_s("+ means SSL")); return; } @@ -1265,7 +1270,13 @@ class CAdminMod : public CModule { return; } - if (pNetwork->AddServer(sServer)) + CServer Server = CServer::Parse(sServer); + if (Server.IsUnixSocket() && !GetUser()->IsAdmin()) { + PutModule(t_s("Access denied!")); + return; + } + + if (pNetwork->AddServer(std::move(Server))) PutModule(t_f("Added IRC Server {1} to network {2} for user {3}.")( sServer, pNetwork->GetName(), pUser->GetUsername())); else @@ -1278,8 +1289,6 @@ class CAdminMod : public CModule { CString sUsername = sLine.Token(1); CString sNetwork = sLine.Token(2); CString sServer = sLine.Token(3, true); - unsigned short uPort = sLine.Token(4).ToUShort(); - CString sPass = sLine.Token(5); if (sServer.empty()) { PutModule( @@ -1301,7 +1310,7 @@ class CAdminMod : public CModule { return; } - if (pNetwork->DelServer(sServer, uPort, sPass)) + if (pNetwork->DelServer(CServer::Parse(sServer))) PutModule( t_f("Deleted IRC Server {1} from network {2} for user {3}.")( sServer, pNetwork->GetName(), pUser->GetUsername())); diff --git a/modules/data/webadmin/files/webadmin.js b/modules/data/webadmin/files/webadmin.js index 0fb776be..7611d4a6 100644 --- a/modules/data/webadmin/files/webadmin.js +++ b/modules/data/webadmin/files/webadmin.js @@ -106,9 +106,11 @@ function serverlist_init($) { var pass = $(".servers_row_pass", $(this)).val(); if (host.length == 0) return; text += host; - text += " "; - if (ssl) text += "+"; - text += port; + if (!host.startsWith("unix:")) { + text += " "; + if (ssl) text += "+"; + text += port; + } text += " "; text += pass; text += "\n"; @@ -122,14 +124,15 @@ function serverlist_init($) { serialize(); } if (NetworkEdit) { + var disable = host.startsWith("unix:") && !EditUnixSockets; row.append( - $("").append($("").attr({"type":"text"}) + $("").append($("").attr({"type":"text","disabled":disable}) .addClass("servers_row_host").val(host)), - $("").append($("").attr({"type":"number"}) + $("").append($("").attr({"type":"number","disabled":disable}) .addClass("servers_row_port").val(port)), - $("").append($("").attr({"type":"checkbox"}) + $("").append($("").attr({"type":"checkbox","disabled":disable}) .addClass("servers_row_ssl").prop("checked", ssl)), - $("").append($("").attr({"type":"text"}) + $("").append($("").attr({"type":"text","disabled":disable}) .addClass("servers_row_pass").val(pass)), $("").append($("").attr({"type":"button"}) .val("X").click(delete_row)) @@ -147,6 +150,25 @@ function serverlist_init($) { ); } $("input", row).change(serialize); + $("input.servers_row_host", row).change(function (ev) { + var host = ev.target.value; + if (host.startsWith("unix:")) { + $("input.servers_row_ssl", row)[0].checked = host.startsWith("unix:ssl:"); + } + }); + $("input.servers_row_ssl", row).change(function (ev) { + var host = $("input.servers_row_host", row).val(); + if (host.startsWith("unix:")) { + if (ev.target.checked != host.startsWith("unix:ssl:")) { + if (host.startsWith("unix:ssl:")) { + host = host.substr(9); + } else { + host = host.substr(5); + } + $("input.servers_row_host", row).val("unix:" + (ev.target.checked ? "ssl:" : "") + host); + } + } + }); $("#servers_tbody").append(row); } @@ -157,14 +179,20 @@ function serverlist_init($) { if (line.length == 0) return; line = line.split(" "); var host = line[0]; - var port = line[1] || "6667"; - var pass = line[2] || ""; - var ssl; - if (port.match(/^\+/)) { - ssl = true; - port = port.substr(1); + var unix = host.startsWith("unix:"); + var port = "0"; + var pass = line[unix ? 1 : 2] || ""; + var ssl = false; + if (unix) { + if (host.startsWith("unix:ssl:")) { + ssl = true; + } } else { - ssl = false; + port = line[1] || "6667"; + if (port.match(/^\+/)) { + ssl = true; + port = port.substr(1); + } } add_row(host, port, ssl, pass); }); diff --git a/modules/data/webadmin/tmpl/add_edit_network.tmpl b/modules/data/webadmin/tmpl/add_edit_network.tmpl index a0239711..c2f0ddc7 100644 --- a/modules/data/webadmin/tmpl/add_edit_network.tmpl +++ b/modules/data/webadmin/tmpl/add_edit_network.tmpl @@ -5,6 +5,7 @@
diff --git a/modules/webadmin.cpp b/modules/webadmin.cpp index 692f125c..feacc452 100644 --- a/modules/webadmin.cpp +++ b/modules/webadmin.cpp @@ -972,6 +972,7 @@ class CWebAdminMod : public CModule { Tmpl["NetworkEdit"] = spSession->IsAdmin() || !spSession->GetUser()->DenySetNetwork() ? "true" : "false"; + Tmpl["EditUnixSockets"] = spSession->IsAdmin() ? "true" : "false"; Tmpl["FloodProtection"] = CString(CIRCSock::IsFloodProtected(pNetwork->GetFloodRate())); @@ -1147,9 +1148,22 @@ class CWebAdminMod : public CModule { VCString vsArgs; if (spSession->IsAdmin() || !spSession->GetUser()->DenySetNetwork()) { + std::set vAllowedUnixServers; + for (const CServer* pServer : pNetwork->GetServers()) { + if (pServer->IsUnixSocket()) { + vAllowedUnixServers.insert(*pServer); + } + } pNetwork->DelServers(); WebSock.GetRawParam("servers").Split("\n", vsArgs); for (const CString& sServer : vsArgs) { + CServer Server = CServer::Parse(sServer); + if (Server.IsUnixSocket() && !spSession->IsAdmin() && + vAllowedUnixServers.count(Server) == 0) { + // For non-admins, allow unix sockets only if they had these + // exact servers before. + continue; + } pNetwork->AddServer(sServer.Trim_n()); } } @@ -1404,9 +1418,11 @@ class CWebAdminMod : public CModule { l["IRCNick"] = pNetwork->GetIRCNick().GetNick(); CServer* pServer = pNetwork->GetCurrentServer(); if (pServer) { - l["Server"] = pServer->GetName() + ":" + - (pServer->IsSSL() ? "+" : "") + - CString(pServer->GetPort()); + l["Server"] = pServer->IsUnixSocket() + ? "unix:" + pServer->GetName() + : pServer->GetName() + ":" + + (pServer->IsSSL() ? "+" : "") + + CString(pServer->GetPort()); } } diff --git a/src/ClientCommand.cpp b/src/ClientCommand.cpp index 40557c30..d20a23d7 100644 --- a/src/ClientCommand.cpp +++ b/src/ClientCommand.cpp @@ -817,7 +817,7 @@ void CClient::UserCommand(CString& sLine) { return; } - CString sServer = sLine.Token(1); + CString sServer = sLine.Token(1, true); if (!m_pNetwork) { PutStatus(t_s( @@ -827,10 +827,20 @@ void CClient::UserCommand(CString& sLine) { if (sServer.empty()) { PutStatus(t_s("Usage: AddServer [[+]port] [pass]")); + if (m_pUser->IsAdmin()) { + PutStatus(t_s("Or: AddServer unix:[ssl:]/path/to/socket")); + } + PutStatus(t_s("+ means SSL")); return; } - if (m_pNetwork->AddServer(sLine.Token(1, true))) { + CServer Server = CServer::Parse(sServer); + if (Server.IsUnixSocket() && !m_pUser->IsAdmin()) { + PutStatus(t_s("Access denied!")); + return; + } + + if (m_pNetwork->AddServer(std::move(Server))) { PutStatus(t_s("Server added")); } else { PutStatus( @@ -849,11 +859,9 @@ void CClient::UserCommand(CString& sLine) { return; } - CString sServer = sLine.Token(1); - unsigned short uPort = sLine.Token(2).ToUShort(); - CString sPass = sLine.Token(3); + CServer Server = CServer::Parse(sLine.Token(1, true)); - if (sServer.empty()) { + if (Server.GetName().empty()) { PutStatus(t_s("Usage: DelServer [port] [pass]")); return; } @@ -863,7 +871,9 @@ void CClient::UserCommand(CString& sLine) { return; } - if (m_pNetwork->DelServer(sServer, uPort, sPass)) { + // Unix sockets can be removed with "unix:" prefix and without, both + // work. + if (m_pNetwork->DelServer(Server)) { PutStatus(t_s("Server removed")); } else { PutStatus(t_s("No such server")); @@ -888,9 +898,12 @@ void CClient::UserCommand(CString& sLine) { Table.AddRow(); Table.SetCell( t_s("Host", "listservers"), - pServer->GetName() + (pServer == pCurServ ? "*" : "")); - Table.SetCell(t_s("Port", "listservers"), - CString(pServer->GetPort())); + (pServer->IsUnixSocket() ? pServer->GetString(false) + : pServer->GetName()) + + (pServer == pCurServ ? "*" : "")); + if (!pServer->IsUnixSocket()) + Table.SetCell(t_s("Port", "listservers"), + CString(pServer->GetPort())); Table.SetCell( t_s("SSL", "listservers"), (pServer->IsSSL()) ? t_s("SSL", "listservers|cell") : ""); diff --git a/src/IRCNetwork.cpp b/src/IRCNetwork.cpp index 70511704..2f097ce2 100644 --- a/src/IRCNetwork.cpp +++ b/src/IRCNetwork.cpp @@ -215,8 +215,7 @@ void CIRCNetwork::Clone(const CIRCNetwork& Network, bool bCloneName) { DelServers(); for (CServer* pServer : vServers) { - AddServer(pServer->GetName(), pServer->GetPort(), pServer->GetPass(), - pServer->IsSSL()); + AddServer(*pServer); } m_uServerIdx = 0; @@ -1155,6 +1154,11 @@ bool CIRCNetwork::DelServer(const CString& sName, unsigned short uPort, return false; } + CServer Server(sName, uPort, sPass); + return DelServer(Server); +} + +bool CIRCNetwork::DelServer(const CServer& Server) { unsigned int a = 0; bool bSawCurrentServer = false; CServer* pCurServer = GetCurrentServer(); @@ -1165,11 +1169,16 @@ bool CIRCNetwork::DelServer(const CString& sName, unsigned short uPort, if (pServer == pCurServer) bSawCurrentServer = true; - if (!pServer->GetName().Equals(sName)) continue; + // Unix sockets can be removed with "unix:" prefix and without, both + // work - that's not part of GetName() + if (!pServer->GetName().Equals(Server.GetName())) continue; - if (uPort != 0 && pServer->GetPort() != uPort) continue; + // But it makes no sense to remove TCP server via "unix:hostname.com" + if (!pServer->IsUnixSocket() && Server.IsUnixSocket()) continue; - if (!sPass.empty() && pServer->GetPass() != sPass) continue; + if (Server.GetPort() != 6667 && pServer->GetPort() != Server.GetPort()) continue; + + if (!Server.GetPass().empty() && pServer->GetPass() != Server.GetPass()) continue; m_vServers.erase(it); @@ -1205,21 +1214,23 @@ bool CIRCNetwork::AddServer(const CString& sName) { return false; } - bool bSSL = false; - CString sLine = sName; - sLine.Trim(); + return AddServer(CServer::Parse(sName)); +} - CString sHost = sLine.Token(0); - CString sPort = sLine.Token(1); +bool CIRCNetwork::AddServer(CServer Server) { + if (Server.GetName().empty()) return false; +#ifndef HAVE_LIBSSL + if (Server.IsSSL()) return false; +#endif - if (sPort.TrimPrefix("+")) { - bSSL = true; + // Check if server is already added + for (CServer* pServer : m_vServers) { + if (*pServer == Server) return false; } - unsigned short uPort = sPort.ToUShort(); - CString sPass = sLine.Token(2, true); - - return AddServer(sHost, uPort, sPass, bSSL); + m_vServers.push_back(new CServer(std::move(Server))); + CheckIRCConnect(); + return true; } bool CIRCNetwork::AddServer(const CString& sName, unsigned short uPort, @@ -1234,30 +1245,7 @@ bool CIRCNetwork::AddServer(const CString& sName, unsigned short uPort, return false; } - if (!uPort) { - uPort = 6667; - } - - // Check if server is already added - for (CServer* pServer : m_vServers) { - if (!sName.Equals(pServer->GetName())) continue; - - if (uPort != pServer->GetPort()) continue; - - if (sPass != pServer->GetPass()) continue; - - if (bSSL != pServer->IsSSL()) continue; - - // Server is already added - return false; - } - - CServer* pServer = new CServer(sName, uPort, sPass, bSSL); - m_vServers.push_back(pServer); - - CheckIRCConnect(); - - return true; + return AddServer(CServer(sName, uPort, sPass, bSSL)); } CServer* CIRCNetwork::GetNextServer(bool bAdvance) { @@ -1374,9 +1362,16 @@ bool CIRCNetwork::Connect() { } CString sSockName = "IRC::" + m_pUser->GetUsername() + "::" + m_sName; - CZNC::Get().GetManager().Connect(pServer->GetName(), pServer->GetPort(), - sSockName, 120, bSSL, GetBindHost(), - pIRCSock); + + if (pServer->IsUnixSocket()) { + pIRCSock->SetSSL(bSSL); + CZNC::Get().GetManager().ConnectUnix(sSockName, pServer->GetName(), + pIRCSock); + } else { + CZNC::Get().GetManager().Connect(pServer->GetName(), pServer->GetPort(), + sSockName, 120, bSSL, GetBindHost(), + pIRCSock); + } return true; } diff --git a/src/Server.cpp b/src/Server.cpp index 53dc0e09..bebb3f39 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -17,11 +17,12 @@ #include CServer::CServer(const CString& sName, unsigned short uPort, - const CString& sPass, bool bSSL) + const CString& sPass, bool bSSL, bool bUnixSocket) : m_sName(sName), m_uPort((uPort) ? uPort : (unsigned short)6667), m_sPass(sPass), - m_bSSL(bSSL) {} + m_bSSL(bSSL), + m_bUnixSocket(bUnixSocket) {} CServer::~CServer() {} @@ -33,9 +34,66 @@ const CString& CServer::GetName() const { return m_sName; } unsigned short CServer::GetPort() const { return m_uPort; } const CString& CServer::GetPass() const { return m_sPass; } bool CServer::IsSSL() const { return m_bSSL; } +bool CServer::IsUnixSocket() const { return m_bUnixSocket; } CString CServer::GetString(bool bIncludePassword) const { - return m_sName + " " + CString(m_bSSL ? "+" : "") + CString(m_uPort) + - CString(bIncludePassword ? (m_sPass.empty() ? "" : " " + m_sPass) - : ""); + CString sResult; + if (m_bUnixSocket) { + sResult = "unix:" + CString(m_bSSL ? "ssl:" : "") + m_sName; + } else { + sResult = m_sName + " " + CString(m_bSSL ? "+" : "") + CString(m_uPort); + } + sResult += + CString(bIncludePassword ? (m_sPass.empty() ? "" : " " + m_sPass) : ""); + return sResult; +} + +CServer CServer::Parse(CString sLine) { + bool bSSL = false; + sLine.Trim(); + + if (sLine.TrimPrefix("unix:")) { + if (sLine.TrimPrefix("ssl:")) { + bSSL = true; + } + + CString sPath = sLine.Token(0); + CString sPass = sLine.Token(1, true); + return CServer(sPath, 0, sPass, bSSL, true); + } + + CString sHost = sLine.Token(0); + CString sPort = sLine.Token(1); + + if (sPort.TrimPrefix("+")) { + bSSL = true; + } + + unsigned short uPort = sPort.ToUShort(); + CString sPass = sLine.Token(2, true); + + return CServer(sHost, uPort, sPass, bSSL, false); +} + +bool CServer::operator==(const CServer& o) const { + if (m_sName != o.m_sName) return false; + if (m_uPort != o.m_uPort) return false; + if (m_sPass != o.m_sPass) return false; + if (m_bSSL != o.m_bSSL) return false; + if (m_bUnixSocket != o.m_bUnixSocket) return false; + return true; +} + +bool CServer::operator<(const CServer& o) const { + if (m_sName < o.m_sName) return true; + if (m_sName > o.m_sName) return false; + if (m_uPort < o.m_uPort) return true; + if (m_uPort > o.m_uPort) return false; + if (m_sPass < o.m_sPass) return true; + if (m_sPass > o.m_sPass) return false; + if (m_bSSL < o.m_bSSL) return true; + if (m_bSSL > o.m_bSSL) return false; + if (m_bUnixSocket < o.m_bUnixSocket) return true; + if (m_bUnixSocket > o.m_bUnixSocket) return false; + return false; } diff --git a/third_party/Csocket b/third_party/Csocket index cf9a613c..83993952 160000 --- a/third_party/Csocket +++ b/third_party/Csocket @@ -1 +1 @@ -Subproject commit cf9a613c2b963ab0e2946580e9f0493cbc46d524 +Subproject commit 839939522b07986d81239dcc253e3155fe45d71b From b642d92ce71e5cd416c006e099049f1ab795fb1b Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Mon, 21 Apr 2025 00:00:14 +0100 Subject: [PATCH 18/35] Switch integration test to mostly use unix sockets By not using the same hardcoded number for every test, we can parallelize the test now. There are several cases remaining where we can't easily use unix sockets (e.g. QSslSocket or imapauth module), for that ask kernel what port number is currently free to use. This is a bit racy though. --- src/znc.cpp | 94 +++++++++++++++----------- test/integration/framework/base.cpp | 11 ++- test/integration/framework/base.h | 11 ++- test/integration/framework/znctest.cpp | 23 ++++--- test/integration/framework/znctest.h | 11 +-- test/integration/tests/core.cpp | 36 ++++++++-- test/integration/tests/modules.cpp | 47 +++++++------ test/integration/tests/scripting.cpp | 4 +- 8 files changed, 152 insertions(+), 85 deletions(-) diff --git a/src/znc.cpp b/src/znc.cpp index a7dc3ebc..9dc9daea 100644 --- a/src/znc.cpp +++ b/src/znc.cpp @@ -625,49 +625,57 @@ bool CZNC::WriteNewConfig(const CString& sConfigFile) { unsigned int uListenPort = 0; bool bSuccess; - do { - bSuccess = true; - while (true) { - if (!CUtils::GetNumInput("Listen on port", uListenPort, 1025, - 65534)) { - continue; - } - if (uListenPort == 6667 || uListenPort == 6697) { - CUtils::PrintStatus(false, - "WARNING: Some web browsers reject ports " - "6667 and 6697. If you intend to"); - CUtils::PrintStatus(false, - "use ZNC's web interface, you might want " - "to use another port."); - if (!CUtils::GetBoolInput("Proceed anyway?", - true)) { + // Unix sockets are not exposed in --makeconf by default, but it's possible + // to trigger this using env var. This is mostly useful for the integration + // test. + char* szListenUnixSocket = getenv("ZNC_LISTEN_UNIX_SOCKET"); + if (!szListenUnixSocket) { + do { + bSuccess = true; + while (true) { + if (!CUtils::GetNumInput("Listen on port", uListenPort, 1025, + 65534)) { continue; } + if (uListenPort == 6667 || uListenPort == 6697) { + CUtils::PrintStatus( + false, + "WARNING: Some web browsers reject ports " + "6667 and 6697. If you intend to"); + CUtils::PrintStatus( + false, + "use ZNC's web interface, you might want " + "to use another port."); + if (!CUtils::GetBoolInput("Proceed anyway?", true)) { + continue; + } + } + break; } - break; - } #ifdef HAVE_LIBSSL - bListenSSL = CUtils::GetBoolInput("Listen using SSL", bListenSSL); + bListenSSL = CUtils::GetBoolInput("Listen using SSL", bListenSSL); #endif #ifdef HAVE_IPV6 - b6 = CUtils::GetBoolInput("Listen using both IPv4 and IPv6", b6); + b6 = CUtils::GetBoolInput("Listen using both IPv4 and IPv6", b6); #endif - // Don't ask for listen host, it may be configured later if needed. + // Don't ask for listen host, it may be configured later if needed. - CUtils::PrintAction("Verifying the listener"); - CListener* pListener = new CTCPListener( - (unsigned short int)uListenPort, sListenHost, sURIPrefix, - bListenSSL, b6 ? ADDR_ALL : ADDR_IPV4ONLY, CListener::ACCEPT_ALL); - if (!pListener->Listen()) { - CUtils::PrintStatus(false, FormatBindError()); - bSuccess = false; - } else - CUtils::PrintStatus(true); - delete pListener; - } while (!bSuccess); + CUtils::PrintAction("Verifying the listener"); + CListener* pListener = new CTCPListener( + (unsigned short int)uListenPort, sListenHost, sURIPrefix, + bListenSSL, b6 ? ADDR_ALL : ADDR_IPV4ONLY, + CListener::ACCEPT_ALL); + if (!pListener->Listen()) { + CUtils::PrintStatus(false, FormatBindError()); + bSuccess = false; + } else + CUtils::PrintStatus(true); + delete pListener; + } while (!bSuccess); + } #ifdef HAVE_LIBSSL CString sPemFile = GetPemLocation(); @@ -679,9 +687,13 @@ bool CZNC::WriteNewConfig(const CString& sConfigFile) { #endif vsLines.push_back(""); - vsLines.push_back("\tPort = " + CString(uListenPort)); - vsLines.push_back("\tIPv4 = true"); - vsLines.push_back("\tIPv6 = " + CString(b6)); + if (szListenUnixSocket) { + vsLines.push_back("\tPath = " + CString(szListenUnixSocket)); + } else { + vsLines.push_back("\tPort = " + CString(uListenPort)); + vsLines.push_back("\tIPv4 = true"); + vsLines.push_back("\tIPv6 = " + CString(b6)); + } vsLines.push_back("\tSSL = " + CString(bListenSSL)); if (!sListenHost.empty()) { vsLines.push_back("\tHost = " + sListenHost); @@ -788,12 +800,16 @@ bool CZNC::WriteNewConfig(const CString& sConfigFile) { bSSL = CUtils::GetBoolInput("Server uses SSL?", bSSL); #endif while (!CUtils::GetNumInput("Server port", uServerPort, 1, 65535, - bSSL ? 6697 : 6667)) - ; + bSSL ? 6697 : 6667)); CUtils::GetInput("Server password (probably empty)", sPass); - vsLines.push_back("\t\tServer = " + sHost + ((bSSL) ? " +" : " ") + - CString(uServerPort) + " " + sPass); + if (sHost.StartsWith("unix:")) { + vsLines.push_back("\t\tServer = " + sHost + " " + sPass); + } else { + vsLines.push_back("\t\tServer = " + sHost + + ((bSSL) ? " +" : " ") + CString(uServerPort) + + " " + sPass); + } CString sChans; if (CUtils::GetInput("Initial channels", sChans)) { diff --git a/test/integration/framework/base.cpp b/test/integration/framework/base.cpp index ee012143..1890564a 100644 --- a/test/integration/framework/base.cpp +++ b/test/integration/framework/base.cpp @@ -14,9 +14,12 @@ * limitations under the License. */ -#include #include "base.h" +#include + +#include + using testing::AnyOf; using testing::Eq; @@ -58,4 +61,10 @@ Process::~Process() { } } +int PickPortNumber() { + QTcpServer tcp; + tcp.listen(QHostAddress::LocalHost); + return tcp.serverPort(); +} + } // namespace znc_inttest diff --git a/test/integration/framework/base.h b/test/integration/framework/base.h index f3441c2e..71098fd7 100644 --- a/test/integration/framework/base.h +++ b/test/integration/framework/base.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -49,6 +50,7 @@ class IO { // Need to flush QTcpSocket, and QIODevice doesn't have flush at all... static void FlushIfCan(QIODevice*) {} static void FlushIfCan(QTcpSocket* sock) { sock->flush(); } + static void FlushIfCan(QLocalSocket* sock) { sock->flush(); } Device* m_device; bool m_verbose; @@ -60,7 +62,7 @@ IO WrapIO(Device* d) { return IO(d); } -using Socket = IO; +using Socket = IO; class Process : public IO { public: @@ -202,6 +204,9 @@ void IO::Write(QByteArray s, bool new_line) { FlushIfCan(m_device); } +inline void DisconnectFromServer(QTcpSocket* s) { s->disconnectFromHost(); } +inline void DisconnectFromServer(QLocalSocket* s) { s->disconnectFromServer(); } + template void IO::Close() { #ifdef __CYGWIN__ @@ -209,8 +214,10 @@ void IO::Close() { // without this line sleep(1); #endif - m_device->disconnectFromHost(); + DisconnectFromServer(m_device); } +int PickPortNumber(); + } // namespace znc_inttest diff --git a/test/integration/framework/znctest.cpp b/test/integration/framework/znctest.cpp index 77628c3e..ffc8d625 100644 --- a/test/integration/framework/znctest.cpp +++ b/test/integration/framework/znctest.cpp @@ -15,6 +15,7 @@ */ #include +#include #include "znctest.h" #ifndef ZNC_BIN_DIR @@ -24,13 +25,15 @@ namespace znc_inttest { void WriteConfig(QString path) { + Process p(ZNC_BIN_DIR "/znc", + QStringList() << "--debug" << "--datadir" << path << "--makeconf", + [=](QProcess* p) { + auto env = p->processEnvironment(); + env.insert("ZNC_LISTEN_UNIX_SOCKET", + path + "/inttest.znc"); + p->setProcessEnvironment(env); + }); // clang-format off - Process p(ZNC_BIN_DIR "/znc", QStringList() << "--debug" - << "--datadir" << path - << "--makeconf"); - p.ReadUntil("Listen on port"); p.Write("12345"); - p.ReadUntil("Listen using SSL"); p.Write(); - p.ReadUntil("IPv6"); p.Write(); p.ReadUntil("Username"); p.Write("user"); p.ReadUntil("password"); p.Write("hunter2", false); p.ReadUntil("Confirm"); p.Write("hunter2", false); @@ -41,7 +44,7 @@ void WriteConfig(QString path) { p.ReadUntil("Bind host"); p.Write(); p.ReadUntil("Set up a network?"); p.Write(); p.ReadUntil("Name [libera]"); p.Write("test"); - p.ReadUntil("Server host (host only)"); p.Write("127.0.0.1"); + p.ReadUntil("Server host (host only)"); p.Write("unix:" + path.toUtf8() + "/inttest.ircd"); p.ReadUntil("Server uses SSL?"); p.Write(); p.ReadUntil("6667"); p.Write(); p.ReadUntil("password"); p.Write(); @@ -61,7 +64,7 @@ void WriteConfig(QString path) { void ZNCTest::SetUp() { WriteConfig(m_dir.path()); - ASSERT_TRUE(m_server.listen(QHostAddress::LocalHost, 6667)) + ASSERT_TRUE(m_server.listen(m_dir.path() + "/inttest.ircd")) << m_server.errorString().toStdString(); } @@ -72,8 +75,8 @@ Socket ZNCTest::ConnectIRCd() { Socket ZNCTest::ConnectClient() { m_clients.emplace_back(); - QTcpSocket& sock = m_clients.back(); - sock.connectToHost("127.0.0.1", 12345); + QLocalSocket& sock = m_clients.back(); + sock.connectToServer(m_dir.path() + "/inttest.znc"); [&] { ASSERT_TRUE(sock.waitForConnected()) << sock.errorString().toStdString(); diff --git a/test/integration/framework/znctest.h b/test/integration/framework/znctest.h index ba12d369..36352c09 100644 --- a/test/integration/framework/znctest.h +++ b/test/integration/framework/znctest.h @@ -16,17 +16,18 @@ #pragma once -#include "base.h" +#include #include -#include #include -#include +#include #include #include #include #include #include +#include "base.h" + namespace znc_inttest { void WriteConfig(QString path); @@ -52,8 +53,8 @@ class ZNCTest : public testing::Test { App m_app; QNetworkAccessManager m_network; QTemporaryDir m_dir; - QTcpServer m_server; - std::list m_clients; + QLocalServer m_server; + std::list m_clients; }; } // namespace znc_inttest diff --git a/test/integration/tests/core.cpp b/test/integration/tests/core.cpp index f1a0768e..9f056b97 100644 --- a/test/integration/tests/core.cpp +++ b/test/integration/tests/core.cpp @@ -90,7 +90,14 @@ TEST_F(ZNCTest, Channel) { TEST_F(ZNCTest, HTTP) { auto znc = Run(); auto ircd = ConnectIRCd(); - auto reply = HttpGet(QNetworkRequest(QUrl("http://127.0.0.1:12345/"))); + + auto client = LoginClient(); + int port = PickPortNumber(); + client.Write(QStringLiteral("znc addport %1 all all").arg(port).toUtf8()); + client.ReadUntil(":Port added"); + + auto reply = HttpGet(QNetworkRequest( + QUrl(QStringLiteral("http://127.0.0.1:%1/").arg(port)))); EXPECT_THAT(reply->rawHeader("Server").toStdString(), HasSubstr("ZNC")); } @@ -102,10 +109,17 @@ TEST_F(ZNCTest, FixCVE20149403) { ircd.Write(":server PING :1"); ircd.ReadUntil("PONG 1"); + auto client = LoginClient(); + int port = PickPortNumber(); + client.Write(QStringLiteral("znc addport %1 all all").arg(port).toUtf8()); + client.ReadUntil(":Port added"); + QNetworkRequest request; request.setRawHeader("Authorization", "Basic " + QByteArray("user:hunter2").toBase64()); - request.setUrl(QUrl("http://127.0.0.1:12345/mods/global/webadmin/addchan")); + request.setUrl( + QUrl(QStringLiteral("http://127.0.0.1:%1/mods/global/webadmin/addchan") + .arg(port))); HttpPost(request, { {"user", "user"}, {"network", "test"}, @@ -136,10 +150,18 @@ TEST_F(ZNCTest, FixFixOfCVE20149403) { ircd.Write(":server PING :12345"); ircd.ReadUntil("PONG 12345"); + auto client = LoginClient(); + int port = PickPortNumber(); + client.Write(QStringLiteral("znc addport %1 all all").arg(port).toUtf8()); + client.ReadUntil(":Port added"); + QNetworkRequest request; request.setRawHeader("Authorization", "Basic " + QByteArray("user:hunter2").toBase64()); - request.setUrl(QUrl("http://127.0.0.1:12345/mods/global/webadmin/addchan")); + request.setUrl( + QUrl(QStringLiteral("http://127.0.0.1:%1/mods/global/webadmin/addchan") + .arg(port) + .toUtf8())); auto reply = HttpPost(request, { {"user", "user"}, {"network", "test"}, @@ -968,13 +990,13 @@ TEST_F(ZNCTest, SpacedServerPassword) { auto znc = Run(); auto ircd = ConnectIRCd(); auto client = LoginClient(); - client.Write("znc delserver 127.0.0.1"); - client.Write("znc addserver 127.0.0.1 6667 a b"); + client.Write(("znc delserver unix:" + m_dir.path() + "/inttest.ircd").toUtf8()); + client.Write(("znc addserver unix:" + m_dir.path() + "/inttest.ircd a b").toUtf8()); client.Write("znc jump"); auto ircd2 = ConnectIRCd(); ircd2.ReadUntil("PASS :a b"); - client.Write("znc delserver 127.0.0.1"); - client.Write("znc addserver 127.0.0.1 6667 a"); + client.Write(("znc delserver unix:" + m_dir.path() + "/inttest.ircd").toUtf8()); + client.Write(("znc addserver unix:" + m_dir.path() + "/inttest.ircd a").toUtf8()); client.Write("znc jump"); auto ircd3 = ConnectIRCd(); // No : diff --git a/test/integration/tests/modules.cpp b/test/integration/tests/modules.cpp index 3b01147b..f000ecb5 100644 --- a/test/integration/tests/modules.cpp +++ b/test/integration/tests/modules.cpp @@ -20,6 +20,7 @@ #include "znctest.h" #include +#include using testing::HasSubstr; using testing::Not; @@ -38,23 +39,23 @@ TEST_F(ZNCTest, NotifyConnectModule) { client2.Write("PASS :hunter2"); client2.Write("NICK nick"); client2.Write("USER user/test x x :x"); - client.ReadUntil("NOTICE nick :*** user attached from 127.0.0.1"); + client.ReadUntil("NOTICE nick :*** user attached from localhost"); auto client3 = ConnectClient(); client3.Write("PASS :hunter2"); client3.Write("NICK nick"); client3.Write("USER user@identifier/test x x :x"); client.ReadUntil( - "NOTICE nick :*** user@identifier attached from 127.0.0.1"); + "NOTICE nick :*** user@identifier attached from localhost"); client2.ReadUntil( - "NOTICE nick :*** user@identifier attached from 127.0.0.1"); + "NOTICE nick :*** user@identifier attached from localhost"); client2.Write("QUIT"); - client.ReadUntil("NOTICE nick :*** user detached from 127.0.0.1"); + client.ReadUntil("NOTICE nick :*** user detached from localhost"); client3.Close(); client.ReadUntil( - "NOTICE nick :*** user@identifier detached from 127.0.0.1"); + "NOTICE nick :*** user@identifier detached from localhost"); } TEST_F(ZNCTest, ClientNotifyModule) { @@ -70,35 +71,35 @@ TEST_F(ZNCTest, ClientNotifyModule) { }; auto client2 = LoginClient(); - client.ReadUntil(":Another client (127.0.0.1) authenticated as your user. Use the 'ListClients' command to see all 2 clients."); + client.ReadUntil(":Another client (localhost) authenticated as your user. Use the 'ListClients' command to see all 2 clients."); auto client3 = LoginClient(); - client.ReadUntil(":Another client (127.0.0.1) authenticated as your user. Use the 'ListClients' command to see all 3 clients."); + client.ReadUntil(":Another client (localhost) authenticated as your user. Use the 'ListClients' command to see all 3 clients."); // disable notifications for every message client.Write("PRIVMSG *clientnotify :NewOnly on"); // check that we do not ge a notification after connecting from a know ip auto client4 = LoginClient(); - check_not_sent(client, ":Another client (127.0.0.1) authenticated as your user. Use the 'ListClients' command to see all 4 clients."); + check_not_sent(client, ":Another client (localhost) authenticated as your user. Use the 'ListClients' command to see all 4 clients."); // choose to notify only on new client ids client.Write("PRIVMSG *clientnotify :NotifyOnNewID on"); auto client5 = LoginClient("identifier123"); - client.ReadUntil(":Another client (127.0.0.1 / identifier123) authenticated as your user. Use the 'ListClients' command to see all 5 clients."); + client.ReadUntil(":Another client (localhost / identifier123) authenticated as your user. Use the 'ListClients' command to see all 5 clients."); auto client6 = LoginClient("identifier123"); - check_not_sent(client, ":Another client (127.0.0.1 / identifier123) authenticated as your user. Use the 'ListClients' command to see all 6 clients."); + check_not_sent(client, ":Another client (localhost / identifier123) authenticated as your user. Use the 'ListClients' command to see all 6 clients."); auto client7 = LoginClient("not_identifier123"); - client.ReadUntil(":Another client (127.0.0.1 / not_identifier123) authenticated as your user. Use the 'ListClients' command to see all 7 clients."); + client.ReadUntil(":Another client (localhost / not_identifier123) authenticated as your user. Use the 'ListClients' command to see all 7 clients."); // choose to notify from both clientids and new IPs client.Write("PRIVMSG *clientnotify :NotifyOnNewIP on"); auto client8 = LoginClient(); - check_not_sent(client, ":Another client (127.0.0.1 / identifier123) authenticated as your user. Use the 'ListClients' command to see all 8 clients."); + check_not_sent(client, ":Another client (localhost / identifier123) authenticated as your user. Use the 'ListClients' command to see all 8 clients."); auto client9 = LoginClient("definitely_not_identifier123"); - client.ReadUntil(":Another client (127.0.0.1 / definitely_not_identifier123) authenticated as your user. Use the 'ListClients' command to see all 9 clients."); + client.ReadUntil(":Another client (localhost / definitely_not_identifier123) authenticated as your user. Use the 'ListClients' command to see all 9 clients."); } TEST_F(ZNCTest, ShellModule) { @@ -241,13 +242,16 @@ TEST_F(ZNCTest, KeepNickModule) { } TEST_F(ZNCTest, ModuleCSRFOverride) { + int port = PickPortNumber(); auto znc = Run(); auto ircd = ConnectIRCd(); auto client = LoginClient(); + client.Write(QStringLiteral("znc addport %1 all all").arg(port).toUtf8()); client.Write("znc loadmod samplewebapi"); client.ReadUntil("Loaded module"); auto request = QNetworkRequest( - QUrl("http://127.0.0.1:12345/mods/global/samplewebapi/")); + QUrl(QStringLiteral("http://127.0.0.1:%1/mods/global/samplewebapi/") + .arg(port))); auto reply = HttpPost(request, {{"text", "ipsum"}})->readAll().toStdString(); EXPECT_THAT(reply, HasSubstr("ipsum")); @@ -352,9 +356,12 @@ TEST_F(ZNCTest, SaslAuthPlainImapAuth) { auto znc = Run(); auto ircd = ConnectIRCd(); QTcpServer imap; - ASSERT_TRUE(imap.listen(QHostAddress::LocalHost, 12346)) << imap.errorString().toStdString(); + ASSERT_TRUE(imap.listen(QHostAddress::LocalHost)) << imap.errorString().toStdString(); auto client = LoginClient(); - client.Write("znc loadmod imapauth 127.0.0.1 12346 %@mail.test.com"); + client.Write( + QStringLiteral("znc loadmod imapauth 127.0.0.1 %1 %@mail.test.com") + .arg(imap.serverPort()) + .toUtf8()); client.ReadUntil("Loaded"); auto client2 = ConnectClient(); @@ -375,11 +382,13 @@ TEST_F(ZNCTest, SaslAuthPlainImapAuth) { } TEST_F(ZNCTest, SaslAuthExternal) { + int port = PickPortNumber(); + auto znc = Run(); auto ircd = ConnectIRCd(); ircd.Write(":server 001 nick :Hello"); auto client = LoginClient(); - client.Write("znc addport +12346 all all"); + client.Write(QStringLiteral("znc addport +%1 all all").arg(port).toUtf8()); client.ReadUntil(":Port added"); client.Write("znc loadmod certauth"); client.ReadUntil("Loaded"); @@ -390,7 +399,7 @@ TEST_F(ZNCTest, SaslAuthExternal) { sock.setLocalCertificate(m_dir.path() + "/znc.pem"); sock.setPrivateKey(m_dir.path() + "/znc.pem"); sock.setPeerVerifyMode(QSslSocket::VerifyNone); - sock.connectToHostEncrypted("127.0.0.1", 12346); + sock.connectToHostEncrypted("127.0.0.1", port); ASSERT_TRUE(sock.waitForConnected()) << sock.errorString().toStdString(); ASSERT_TRUE(sock.waitForEncrypted()) << sock.errorString().toStdString(); auto client2 = WrapIO(&sock); @@ -404,7 +413,7 @@ TEST_F(ZNCTest, SaslAuthExternal) { client2.Close(); ASSERT_TRUE(sock.state() == QAbstractSocket::UnconnectedState || sock.waitForDisconnected()) << sock.errorString().toStdString(); - sock.connectToHostEncrypted("127.0.0.1", 12346); + sock.connectToHostEncrypted("127.0.0.1", port); ASSERT_TRUE(sock.waitForConnected()) << sock.errorString().toStdString(); ASSERT_TRUE(sock.waitForEncrypted()) diff --git a/test/integration/tests/scripting.cpp b/test/integration/tests/scripting.cpp index afe75b91..fbba2f3d 100644 --- a/test/integration/tests/scripting.cpp +++ b/test/integration/tests/scripting.cpp @@ -491,7 +491,7 @@ TEST_F(ZNCTest, ModpythonSaslAuth) { client2.ReadUntil("AUTHENTICATE " + QByteArrayLiteral("Welcome").toBase64()); client2.Write("AUTHENTICATE +"); client2.ReadUntil( - ":irc.znc.in 900 nick nick!user@127.0.0.1 user :You are now logged in " + ":irc.znc.in 900 nick nick!user@localhost user :You are now logged in " "as user"); } @@ -548,7 +548,7 @@ TEST_F(ZNCTest, ModperlSaslAuth) { client2.ReadUntil("AUTHENTICATE " + QByteArrayLiteral("Welcome").toBase64()); client2.Write("AUTHENTICATE +"); client2.ReadUntil( - ":irc.znc.in 900 nick nick!user@127.0.0.1 user :You are now logged in " + ":irc.znc.in 900 nick nick!user@localhost user :You are now logged in " "as user"); } From b1a03fa1cc4f89b0fc90c3a666a8c28651eb92a1 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Mon, 21 Apr 2025 00:52:29 +0100 Subject: [PATCH 19/35] CI: parallelize integration test I'm not sure whether 'make inttest' should do this or not --- .appveyor.yml | 4 +++- .github/build.sh | 5 ++++- Jenkinsfile | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 6df0cd1d..80ecfa06 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -27,4 +27,6 @@ build_script: - c:\cygwin-root\bin\sh -lc "rebase -s -v $(cat /tmp/files-to-rebase)" test_script: - c:\cygwin-root\bin\sh -lc "cd $APPVEYOR_BUILD_FOLDER/build; make VERBOSE=1 unittest < /dev/null" - - c:\cygwin-root\bin\sh -lc "cd $APPVEYOR_BUILD_FOLDER/build; env ZNC_QT_VER=5 make VERBOSE=1 inttest < /dev/null" + - c:\cygwin-root\bin\sh -lc "cd $APPVEYOR_BUILD_FOLDER/build; env ZNC_QT_VER=5 make VERBOSE=1 inttest_bin < /dev/null" + - c:\cygwin-root\bin\sh -lc "cd $APPVEYOR_BUILD_FOLDER/build; git clone --depth=1 https://github.com/google/gtest-parallel" + - c:\cygwin-root\bin\sh -lc "cd $APPVEYOR_BUILD_FOLDER/build; gtest-parallel/gtest-parallel test/integration/inttest" diff --git a/.github/build.sh b/.github/build.sh index 33a56ed3..e89e21e4 100644 --- a/.github/build.sh +++ b/.github/build.sh @@ -31,8 +31,11 @@ env LLVM_PROFILE_FILE="$PWD/unittest.profraw" make VERBOSE=1 unittest sudo make install /usr/local/bin/znc --version +git clone --depth=1 https://github.com/google/gtest-parallel +make VERBOSE=1 inttest_bin + # TODO: use DEVEL_COVER_OPTIONS for https://metacpan.org/pod/Devel::Cover -env LLVM_PROFILE_FILE="$PWD/inttest.profraw" ZNC_MODPERL_COVERAGE_OPTS="-db,$PWD/cover_db" PYTHONWARNINGS=error make VERBOSE=1 inttest +env LLVM_PROFILE_FILE="$PWD/inttest.profraw" ZNC_MODPERL_COVERAGE_OPTS="-db,$PWD/cover_db" PYTHONWARNINGS=error gtest-parallel/gtest-parallel test/integration/inttest ls -lRa ~/perl5/bin/cover --no-gcov --report=clover diff --git a/Jenkinsfile b/Jenkinsfile index 1d8f594b..f2eba994 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -26,7 +26,9 @@ timestamps { stage('Integration test') { withEnv(['GTEST_OUTPUT=xml:integration-test.xml']) { sh 'make install' - sh 'make inttest' + sh 'make inttest_bin' + sh 'git clone --depth=1 https://github.com/google/gtest-parallel' + sh 'gtest-parallel/gtest-parallel test/integration/inttest' } } junit '**/*test.xml' From 81e3c908add5caeda4a4d49bd806a74434c4b854 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Mon, 21 Apr 2025 01:01:42 +0100 Subject: [PATCH 20/35] Fix include --- modules/controlpanel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/controlpanel.cpp b/modules/controlpanel.cpp index ae23945c..e76d0a68 100644 --- a/modules/controlpanel.cpp +++ b/modules/controlpanel.cpp @@ -21,7 +21,7 @@ #include #include #include -#include "znc/Server.h" +#include using std::map; using std::vector; From 4c8e780a60bdaf3b267ffff8a772d7eb7362e896 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Mon, 21 Apr 2025 09:06:23 +0100 Subject: [PATCH 21/35] Revert "CI: parallelize integration test" This reverts commit b1a03fa1cc4f89b0fc90c3a666a8c28651eb92a1. Let's plug it as part of 'make inttest' --- .appveyor.yml | 4 +--- .github/build.sh | 5 +---- Jenkinsfile | 4 +--- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 80ecfa06..6df0cd1d 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -27,6 +27,4 @@ build_script: - c:\cygwin-root\bin\sh -lc "rebase -s -v $(cat /tmp/files-to-rebase)" test_script: - c:\cygwin-root\bin\sh -lc "cd $APPVEYOR_BUILD_FOLDER/build; make VERBOSE=1 unittest < /dev/null" - - c:\cygwin-root\bin\sh -lc "cd $APPVEYOR_BUILD_FOLDER/build; env ZNC_QT_VER=5 make VERBOSE=1 inttest_bin < /dev/null" - - c:\cygwin-root\bin\sh -lc "cd $APPVEYOR_BUILD_FOLDER/build; git clone --depth=1 https://github.com/google/gtest-parallel" - - c:\cygwin-root\bin\sh -lc "cd $APPVEYOR_BUILD_FOLDER/build; gtest-parallel/gtest-parallel test/integration/inttest" + - c:\cygwin-root\bin\sh -lc "cd $APPVEYOR_BUILD_FOLDER/build; env ZNC_QT_VER=5 make VERBOSE=1 inttest < /dev/null" diff --git a/.github/build.sh b/.github/build.sh index e89e21e4..33a56ed3 100644 --- a/.github/build.sh +++ b/.github/build.sh @@ -31,11 +31,8 @@ env LLVM_PROFILE_FILE="$PWD/unittest.profraw" make VERBOSE=1 unittest sudo make install /usr/local/bin/znc --version -git clone --depth=1 https://github.com/google/gtest-parallel -make VERBOSE=1 inttest_bin - # TODO: use DEVEL_COVER_OPTIONS for https://metacpan.org/pod/Devel::Cover -env LLVM_PROFILE_FILE="$PWD/inttest.profraw" ZNC_MODPERL_COVERAGE_OPTS="-db,$PWD/cover_db" PYTHONWARNINGS=error gtest-parallel/gtest-parallel test/integration/inttest +env LLVM_PROFILE_FILE="$PWD/inttest.profraw" ZNC_MODPERL_COVERAGE_OPTS="-db,$PWD/cover_db" PYTHONWARNINGS=error make VERBOSE=1 inttest ls -lRa ~/perl5/bin/cover --no-gcov --report=clover diff --git a/Jenkinsfile b/Jenkinsfile index f2eba994..1d8f594b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -26,9 +26,7 @@ timestamps { stage('Integration test') { withEnv(['GTEST_OUTPUT=xml:integration-test.xml']) { sh 'make install' - sh 'make inttest_bin' - sh 'git clone --depth=1 https://github.com/google/gtest-parallel' - sh 'gtest-parallel/gtest-parallel test/integration/inttest' + sh 'make inttest' } } junit '**/*test.xml' From fc15b8ec5eaf8f9e3cce55e08bb60e8befc58c5d Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Mon, 21 Apr 2025 09:31:08 +0100 Subject: [PATCH 22/35] Make 'make inttest' run in parallel even outside CI --- .gitmodules | 3 +++ NOTICE | 1 + make-tarball.sh | 2 ++ test/CMakeLists.txt | 1 + third_party/gtest-parallel | 1 + 5 files changed, 8 insertions(+) create mode 160000 third_party/gtest-parallel diff --git a/.gitmodules b/.gitmodules index e21f1e3b..893c11a9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "third_party/cctz"] path = third_party/cctz url = https://github.com/google/cctz +[submodule "third_party/gtest-parallel"] + path = third_party/gtest-parallel + url = https://github.com/google/gtest-parallel diff --git a/NOTICE b/NOTICE index e08eeaa5..7d87122a 100644 --- a/NOTICE +++ b/NOTICE @@ -17,6 +17,7 @@ ZNC includes code from Selectize (http://brianreavis.github.io/selectize.js/), l ZNC includes modified code from CMakeFindFrameworks.cmake by Kitware, Inc., licensed under BSD License. ZNC includes modified code from TestLargeFiles.cmake, licensed under Boost Software License, Version 1.0. ZNC includes code from cctz (https://github.com/google/cctz), licensed under the Apache License 2.0. +ZNC includes code from gtest-parallel (https://github.com/google/gtest-parallel), licensed under the Apache License 2.0. ZNC is developed by these people: diff --git a/make-tarball.sh b/make-tarball.sh index a96a1970..a4cdba2f 100755 --- a/make-tarball.sh +++ b/make-tarball.sh @@ -45,6 +45,8 @@ mkdir -p --mode=0755 $TMPDIR/$ZNCDIR/third_party/Csocket cp -p third_party/Csocket/Csocket.cc third_party/Csocket/Csocket.h $TMPDIR/$ZNCDIR/third_party/Csocket/ mkdir -p --mode=0755 $TMPDIR/$ZNCDIR/third_party/cctz cp -Rp third_party/cctz/src third_party/cctz/include third_party/cctz/LICENSE.txt $TMPDIR/$ZNCDIR/third_party/cctz/ +mkdir -p --mode=0755 $TMPDIR/$ZNCDIR/third_party/gtest-parallel +cp -p third_party/gtest-parallel/LICENSE third_party/gtest-parallel/gtest-parallel third_party/gtest-parallel/gtest-parallel.py $TMPDIR/$ZNCDIR/third_party/gtest-parallel/ ( cd $TMPDIR2 cmake $TMPDIR/$ZNCDIR -DWANT_PERL=yes -DWANT_PYTHON=yes diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 07b64d27..780a7fb6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -102,5 +102,6 @@ add_custom_target(inttest COMMAND # znc-buildmod should use the correct compiler. # https://bugs.gentoo.org/699258 is an example of how it can go wrong. ${CMAKE_COMMAND} -E env MAKEFLAGS= CXX=${CMAKE_CXX_COMPILER} + "${PROJECT_SOURCE_DIR}/third_party/gtest-parallel/gtest-parallel" "${CMAKE_CURRENT_BINARY_DIR}/integration/inttest") add_dependencies(inttest inttest_bin) diff --git a/third_party/gtest-parallel b/third_party/gtest-parallel new file mode 160000 index 00000000..96f4f904 --- /dev/null +++ b/third_party/gtest-parallel @@ -0,0 +1 @@ +Subproject commit 96f4f904922f9bf66689e749c40f314845baaac8 From 2af8cef4c20109a6edbf28784a2ccf11dac40f9e Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Mon, 21 Apr 2025 11:22:24 +0100 Subject: [PATCH 23/35] update csocket --- third_party/Csocket | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/Csocket b/third_party/Csocket index 83993952..3f536e9a 160000 --- a/third_party/Csocket +++ b/third_party/Csocket @@ -1 +1 @@ -Subproject commit 839939522b07986d81239dcc253e3155fe45d71b +Subproject commit 3f536e9a609bb058a5649cbe938b2e4cfde32d53 From ce0a1584ba7ec961cd700a411692d0d289264a1c Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Mon, 21 Apr 2025 15:47:21 +0100 Subject: [PATCH 24/35] Cygwin: try a horrible hack to fix integration test with unix sockets --- test/integration/CMakeLists.txt | 7 ++ test/integration/framework/cygwin.cpp | 99 ++++++++++++++++++++++++++ test/integration/framework/cygwin.h | 11 +++ test/integration/framework/znctest.cpp | 8 ++- test/integration/tests/modules.cpp | 2 + 5 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 test/integration/framework/cygwin.cpp create mode 100644 test/integration/framework/cygwin.h diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt index af4679cb..495ec762 100644 --- a/test/integration/CMakeLists.txt +++ b/test/integration/CMakeLists.txt @@ -56,3 +56,10 @@ target_include_directories(inttest PUBLIC "${GMOCK_ROOT}" "${GMOCK_ROOT}/include") target_compile_definitions(inttest PRIVATE "ZNC_BIN_DIR=\"${ZNC_BIN_DIR}\"") + +if(CYGWIN) + # This workaround contains a sizeable modified copypaste of Qt's qlocalsocket_unix.cpp, which is LGPL, so has to be in a separate shared library. + add_library(inttest_cygwin SHARED framework/cygwin.cpp) + target_link_libraries(inttest_cygwin Qt${ZNC_QT_VER}::NetworkPrivate) + target_link_libraries(inttest inttest_cygwin) +endif() diff --git a/test/integration/framework/cygwin.cpp b/test/integration/framework/cygwin.cpp new file mode 100644 index 00000000..da4746de --- /dev/null +++ b/test/integration/framework/cygwin.cpp @@ -0,0 +1,99 @@ +// This file is LGPL, as it's based on Qt's qlocalsocket_unix.cpp +// The original header follows: + +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "cygwin.h" + +#include +#include +#include +#include + +namespace znc_inttest_cygwin { +// https://stackoverflow.com/questions/424104/can-i-access-private-members-from-outside-the-class-without-using-friends +template +struct Rob { + friend typename Tag::type get(Tag) { return M; } +}; +struct A_member { + using type = QScopedPointer QObject::*; + friend type get(A_member); +}; +template struct Rob; + +// This function is inspired by QLocalSocket::connectToServer() and QLocalSocketPrivate::_q_connectToSocket() +void CygwinWorkaroundLocalConnect(QLocalSocket& sock) { + QObjectData* o = (sock.*get(A_member())).get(); + QLocalSocketPrivate* d = reinterpret_cast(o); + d->unixSocket.setSocketState(QAbstractSocket::ConnectingState); + d->state = QLocalSocket::ConnectingState; + sock.stateChanged(d->state); + + if ((d->connectingSocket = qt_safe_socket(PF_UNIX, SOCK_STREAM, 0, 0)) == + -1) { + sock.errorOccurred(QLocalSocket::UnsupportedSocketOperationError); + return; + } + + d->fullServerName = d->serverName; + + const QByteArray encodedConnectingPathName = + QFile::encodeName(d->serverName); + struct sockaddr_un name; + name.sun_family = PF_UNIX; + ::memcpy(name.sun_path, encodedConnectingPathName.constData(), + encodedConnectingPathName.size() + 1); + if (qt_safe_connect(d->connectingSocket, (struct sockaddr*)&name, + sizeof(name)) == -1) { + sock.errorOccurred(QLocalSocket::UnknownSocketError); + return; + } + + ::fcntl(d->connectingSocket, F_SETFL, + ::fcntl(d->connectingSocket, F_GETFL) | O_NONBLOCK); + d->unixSocket.setSocketDescriptor(d->connectingSocket, + QAbstractSocket::ConnectedState); + sock.QIODevice::open(QLocalSocket::ReadWrite | QLocalSocket::Unbuffered); + sock.connected(); + d->connectingSocket = -1; + d->connectingName.clear(); +} +} // namespace znc_inttest_cygwin diff --git a/test/integration/framework/cygwin.h b/test/integration/framework/cygwin.h new file mode 100644 index 00000000..ff42e65b --- /dev/null +++ b/test/integration/framework/cygwin.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace znc_inttest_cygwin { +// Qt uses non-blocking sockets for unix sockets, but cygwin emulates them via +// AF_INET sockets, so connect() fails. This function connects the socket by +// reaching into private parts of QLocalSocket, and sets it non-blocking only +// after connect(). +void CygwinWorkaroundLocalConnect(QLocalSocket& sock); +} diff --git a/test/integration/framework/znctest.cpp b/test/integration/framework/znctest.cpp index ffc8d625..e52732fd 100644 --- a/test/integration/framework/znctest.cpp +++ b/test/integration/framework/znctest.cpp @@ -17,6 +17,7 @@ #include #include #include "znctest.h" +#include "cygwin.h" #ifndef ZNC_BIN_DIR #define ZNC_BIN_DIR "" @@ -76,7 +77,12 @@ Socket ZNCTest::ConnectIRCd() { Socket ZNCTest::ConnectClient() { m_clients.emplace_back(); QLocalSocket& sock = m_clients.back(); - sock.connectToServer(m_dir.path() + "/inttest.znc"); + sock.setServerName(m_dir.path() + "/inttest.znc"); +#ifdef __CYGWIN__ + znc_inttest_cygwin::CygwinWorkaroundLocalConnect(sock); +#else + sock.connectToServer(); +#endif [&] { ASSERT_TRUE(sock.waitForConnected()) << sock.errorString().toStdString(); diff --git a/test/integration/tests/modules.cpp b/test/integration/tests/modules.cpp index f000ecb5..5815c088 100644 --- a/test/integration/tests/modules.cpp +++ b/test/integration/tests/modules.cpp @@ -242,6 +242,8 @@ TEST_F(ZNCTest, KeepNickModule) { } TEST_F(ZNCTest, ModuleCSRFOverride) { + // TODO: Qt 6.8 introduced QNetworkRequest::FullLocalServerNameAttribute to + // let it connect to unix socket int port = PickPortNumber(); auto znc = Run(); auto ircd = ConnectIRCd(); From 6b6614cffcfeceedf6081626b51c020dff8d7b2d Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Mon, 21 Apr 2025 16:35:31 +0100 Subject: [PATCH 25/35] Adapt cygwin workaround to ancient cygwin's qt version --- test/integration/framework/cygwin.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/integration/framework/cygwin.cpp b/test/integration/framework/cygwin.cpp index da4746de..df67e0bd 100644 --- a/test/integration/framework/cygwin.cpp +++ b/test/integration/framework/cygwin.cpp @@ -42,10 +42,13 @@ #include "cygwin.h" +#define _GNU_SOURCE +#include +#include +#include #include #include #include -#include namespace znc_inttest_cygwin { // https://stackoverflow.com/questions/424104/can-i-access-private-members-from-outside-the-class-without-using-friends @@ -61,7 +64,7 @@ template struct Rob; // This function is inspired by QLocalSocket::connectToServer() and QLocalSocketPrivate::_q_connectToSocket() void CygwinWorkaroundLocalConnect(QLocalSocket& sock) { - QObjectData* o = (sock.*get(A_member())).get(); + QObjectData* o = (sock.*get(A_member())).data(); QLocalSocketPrivate* d = reinterpret_cast(o); d->unixSocket.setSocketState(QAbstractSocket::ConnectingState); d->state = QLocalSocket::ConnectingState; @@ -69,7 +72,7 @@ void CygwinWorkaroundLocalConnect(QLocalSocket& sock) { if ((d->connectingSocket = qt_safe_socket(PF_UNIX, SOCK_STREAM, 0, 0)) == -1) { - sock.errorOccurred(QLocalSocket::UnsupportedSocketOperationError); + qDebug() << "CygwinWorkaroundLocalConnect: qt_safe_socket errored"; return; } @@ -83,14 +86,17 @@ void CygwinWorkaroundLocalConnect(QLocalSocket& sock) { encodedConnectingPathName.size() + 1); if (qt_safe_connect(d->connectingSocket, (struct sockaddr*)&name, sizeof(name)) == -1) { - sock.errorOccurred(QLocalSocket::UnknownSocketError); + qDebug() << "CygwinWorkaroundLocalConnect: qt_safe_connect errored"; return; } ::fcntl(d->connectingSocket, F_SETFL, ::fcntl(d->connectingSocket, F_GETFL) | O_NONBLOCK); - d->unixSocket.setSocketDescriptor(d->connectingSocket, - QAbstractSocket::ConnectedState); + if (!d->unixSocket.setSocketDescriptor(d->connectingSocket, + QAbstractSocket::ConnectedState)) { + qDebug() << "CygwinWorkaroundLocalConnect: setSocketDescriptor errored"; + return; + } sock.QIODevice::open(QLocalSocket::ReadWrite | QLocalSocket::Unbuffered); sock.connected(); d->connectingSocket = -1; From 174a57a6ab46a5949aa647588c77c6c50192e21e Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Mon, 21 Apr 2025 16:38:33 +0100 Subject: [PATCH 26/35] fix typo --- make-tarball.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make-tarball.sh b/make-tarball.sh index a4cdba2f..17ea90d8 100755 --- a/make-tarball.sh +++ b/make-tarball.sh @@ -46,7 +46,7 @@ cp -p third_party/Csocket/Csocket.cc third_party/Csocket/Csocket.h $TMPDIR/$ZNCD mkdir -p --mode=0755 $TMPDIR/$ZNCDIR/third_party/cctz cp -Rp third_party/cctz/src third_party/cctz/include third_party/cctz/LICENSE.txt $TMPDIR/$ZNCDIR/third_party/cctz/ mkdir -p --mode=0755 $TMPDIR/$ZNCDIR/third_party/gtest-parallel -cp -p third_party/gtest-parallel/LICENSE third_party/gtest-parallel/gtest-parallel third_party/gtest-parallel/gtest-parallel.py $TMPDIR/$ZNCDIR/third_party/gtest-parallel/ +cp -p third_party/gtest-parallel/LICENSE third_party/gtest-parallel/gtest-parallel third_party/gtest-parallel/gtest_parallel.py $TMPDIR/$ZNCDIR/third_party/gtest-parallel/ ( cd $TMPDIR2 cmake $TMPDIR/$ZNCDIR -DWANT_PERL=yes -DWANT_PYTHON=yes From e084af4b7df5bc40133f9e2e03c85b9982ab39df Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Mon, 21 Apr 2025 17:33:35 +0100 Subject: [PATCH 27/35] more hacks to the cygwin hack --- test/integration/framework/cygwin.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/integration/framework/cygwin.cpp b/test/integration/framework/cygwin.cpp index df67e0bd..deee497b 100644 --- a/test/integration/framework/cygwin.cpp +++ b/test/integration/framework/cygwin.cpp @@ -42,10 +42,12 @@ #include "cygwin.h" -#define _GNU_SOURCE #include #include #include + +#include +#undef QT_THREADSAFE_CLOEXEC #include #include #include From d8234a1a71f515ec63e90a948a0f34b50770e343 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Tue, 22 Apr 2025 00:09:55 +0100 Subject: [PATCH 28/35] Fix the IP in SASL This is wrong PR for that, but I noticed the issue while debugging failed tests here --- src/Client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.cpp b/src/Client.cpp index f279d81c..e0a48681 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -1325,7 +1325,7 @@ void CClient::RefuseSASLLogin(const CString& sReason) { void CClient::AcceptSASLLogin(CUser& User) { PutClient(":irc.znc.in 900 " + GetNick() + " " + GetNick() + "!" + - User.GetIdent() + "@" + GetHostName() + " " + User.GetUsername() + + User.GetIdent() + "@" + GetRemoteIP() + " " + User.GetUsername() + " :You are now logged in as " + User.GetUsername()); PutClient(":irc.znc.in 903 " + GetNick() + " :SASL authentication successful"); From 951c39479f69ee2f549bce2b692ded419b3fe23c Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Tue, 22 Apr 2025 01:10:22 +0100 Subject: [PATCH 29/35] Update several tests to accept empty string or "localhost" For unix sockets the behavior of getpeername() is different on different OS This required to add support of regex to the integration test framework --- test/integration/framework/base.h | 39 ++++++++++++++++++++++++---- test/integration/tests/modules.cpp | 37 ++++++++++++++------------ test/integration/tests/scripting.cpp | 12 ++++----- 3 files changed, 61 insertions(+), 27 deletions(-) diff --git a/test/integration/framework/base.h b/test/integration/framework/base.h index 71098fd7..1663f121 100644 --- a/test/integration/framework/base.h +++ b/test/integration/framework/base.h @@ -20,12 +20,11 @@ #include #include -#include #include +#include +#include #include -#include - namespace znc_inttest { template @@ -35,6 +34,7 @@ class IO { : m_device(device), m_verbose(verbose) {} virtual ~IO() {} void ReadUntil(QByteArray pattern); + void ReadUntilRe(QString pattern); /* * Reads from Device until pattern is matched and returns this pattern * up to and excluding the first newline. Pattern itself can contain a newline. @@ -116,7 +116,36 @@ void IO::ReadUntil(QByteArray pattern) { } const int timeout_ms = QDateTime::currentDateTime().msecsTo(deadline); - ASSERT_GT(timeout_ms, 0) << "Wanted:" << pattern.toStdString(); + ASSERT_GT(timeout_ms, 0) << "Wanted: " << pattern.toStdString(); + ASSERT_TRUE(m_device->waitForReadyRead(timeout_ms)) + << "Wanted: " << pattern.toStdString(); + QByteArray chunk = m_device->readAll(); + if (m_verbose) { + std::cout << chunk.toStdString() << std::flush; + } + m_readed += chunk; + } +} + +template +void IO::ReadUntilRe(QString pattern) { + QRegularExpression expr(pattern); + auto deadline = QDateTime::currentDateTime().addSecs(60); + while (true) { + QRegularExpressionMatch match = + expr.match(QString::fromUtf8(m_readed), 0, + QRegularExpression::PartialPreferCompleteMatch); + if (match.hasMatch()) { + m_readed.remove(0, match.capturedEnd()); + return; + } + if (!match.hasPartialMatch()) { + m_readed.clear(); + } + const int timeout_ms = + QDateTime::currentDateTime().msecsTo(deadline); + ASSERT_GT(timeout_ms, 0) + << "Wanted: " << pattern.toStdString(); ASSERT_TRUE(m_device->waitForReadyRead(timeout_ms)) << "Wanted: " << pattern.toStdString(); QByteArray chunk = m_device->readAll(); @@ -158,7 +187,7 @@ void IO::ReadUntilAndGet(QByteArray pattern, QByteArray& match) { } const int timeout_ms = QDateTime::currentDateTime().msecsTo(deadline); - ASSERT_GT(timeout_ms, 0) << "Wanted:" << pattern.toStdString(); + ASSERT_GT(timeout_ms, 0) << "Wanted: " << pattern.toStdString(); ASSERT_TRUE(m_device->waitForReadyRead(timeout_ms)) << "Wanted: " << pattern.toStdString(); QByteArray chunk = m_device->readAll(); diff --git a/test/integration/tests/modules.cpp b/test/integration/tests/modules.cpp index 5815c088..652fccb4 100644 --- a/test/integration/tests/modules.cpp +++ b/test/integration/tests/modules.cpp @@ -39,23 +39,23 @@ TEST_F(ZNCTest, NotifyConnectModule) { client2.Write("PASS :hunter2"); client2.Write("NICK nick"); client2.Write("USER user/test x x :x"); - client.ReadUntil("NOTICE nick :*** user attached from localhost"); + client.ReadUntil("NOTICE nick :*** user attached from "); auto client3 = ConnectClient(); client3.Write("PASS :hunter2"); client3.Write("NICK nick"); client3.Write("USER user@identifier/test x x :x"); client.ReadUntil( - "NOTICE nick :*** user@identifier attached from localhost"); + "NOTICE nick :*** user@identifier attached from "); client2.ReadUntil( - "NOTICE nick :*** user@identifier attached from localhost"); + "NOTICE nick :*** user@identifier attached from "); client2.Write("QUIT"); - client.ReadUntil("NOTICE nick :*** user detached from localhost"); + client.ReadUntil("NOTICE nick :*** user detached from "); client3.Close(); client.ReadUntil( - "NOTICE nick :*** user@identifier detached from localhost"); + "NOTICE nick :*** user@identifier detached from "); } TEST_F(ZNCTest, ClientNotifyModule) { @@ -65,41 +65,46 @@ TEST_F(ZNCTest, ClientNotifyModule) { client.Write("znc loadmod clientnotify"); client.ReadUntil("Loaded module"); - auto check_not_sent = [](Socket& client, std::string wrongAnswer){ - auto result = QString{client.ReadRemainder()}.toStdString(); - EXPECT_THAT(result, Not(HasSubstr((wrongAnswer)))) << "Got an answer from the ClientNotifyModule even though we didnt want one with the given configuration"; + auto check_not_sent = [](Socket& client, QString wrongAnswer) { + QString result = QString::fromUtf8(client.ReadRemainder()); + QRegularExpression expr(wrongAnswer); + QRegularExpressionMatch match = expr.match(result); + EXPECT_FALSE(match.hasMatch()) + << "Got an answer from the ClientNotifyModule even though we didnt " + "want one with the given configuration: " + << wrongAnswer.toStdString() << result.toStdString(); }; auto client2 = LoginClient(); - client.ReadUntil(":Another client (localhost) authenticated as your user. Use the 'ListClients' command to see all 2 clients."); + client.ReadUntilRe(R"(:Another client \((localhost)?\) authenticated as your user. Use the 'ListClients' command to see all 2 clients.)"); auto client3 = LoginClient(); - client.ReadUntil(":Another client (localhost) authenticated as your user. Use the 'ListClients' command to see all 3 clients."); + client.ReadUntilRe(R"(:Another client \((localhost)?\) authenticated as your user. Use the 'ListClients' command to see all 3 clients.)"); // disable notifications for every message client.Write("PRIVMSG *clientnotify :NewOnly on"); // check that we do not ge a notification after connecting from a know ip auto client4 = LoginClient(); - check_not_sent(client, ":Another client (localhost) authenticated as your user. Use the 'ListClients' command to see all 4 clients."); + check_not_sent(client, ":Another client (.*) authenticated as your user. Use the 'ListClients' command to see all 4 clients."); // choose to notify only on new client ids client.Write("PRIVMSG *clientnotify :NotifyOnNewID on"); auto client5 = LoginClient("identifier123"); - client.ReadUntil(":Another client (localhost / identifier123) authenticated as your user. Use the 'ListClients' command to see all 5 clients."); + client.ReadUntilRe(R"(:Another client \((localhost)? / identifier123\) authenticated as your user. Use the 'ListClients' command to see all 5 clients.)"); auto client6 = LoginClient("identifier123"); - check_not_sent(client, ":Another client (localhost / identifier123) authenticated as your user. Use the 'ListClients' command to see all 6 clients."); + check_not_sent(client, ":Another client (.* / identifier123) authenticated as your user. Use the 'ListClients' command to see all 6 clients."); auto client7 = LoginClient("not_identifier123"); - client.ReadUntil(":Another client (localhost / not_identifier123) authenticated as your user. Use the 'ListClients' command to see all 7 clients."); + client.ReadUntilRe(R"(:Another client \((localhost)? / not_identifier123\) authenticated as your user. Use the 'ListClients' command to see all 7 clients.)"); // choose to notify from both clientids and new IPs client.Write("PRIVMSG *clientnotify :NotifyOnNewIP on"); auto client8 = LoginClient(); - check_not_sent(client, ":Another client (localhost / identifier123) authenticated as your user. Use the 'ListClients' command to see all 8 clients."); + check_not_sent(client, ":Another client (.* / identifier123) authenticated as your user. Use the 'ListClients' command to see all 8 clients."); auto client9 = LoginClient("definitely_not_identifier123"); - client.ReadUntil(":Another client (localhost / definitely_not_identifier123) authenticated as your user. Use the 'ListClients' command to see all 9 clients."); + client.ReadUntilRe(R"(:Another client \((localhost)? / definitely_not_identifier123\) authenticated as your user. Use the 'ListClients' command to see all 9 clients.)"); } TEST_F(ZNCTest, ShellModule) { diff --git a/test/integration/tests/scripting.cpp b/test/integration/tests/scripting.cpp index fbba2f3d..afeeaeb7 100644 --- a/test/integration/tests/scripting.cpp +++ b/test/integration/tests/scripting.cpp @@ -490,9 +490,9 @@ TEST_F(ZNCTest, ModpythonSaslAuth) { client2.Write("AUTHENTICATE FOO"); client2.ReadUntil("AUTHENTICATE " + QByteArrayLiteral("Welcome").toBase64()); client2.Write("AUTHENTICATE +"); - client2.ReadUntil( - ":irc.znc.in 900 nick nick!user@localhost user :You are now logged in " - "as user"); + client2.ReadUntilRe( + ":irc.znc.in 900 nick nick!user@(localhost)? user :You are now logged " + "in as user"); } TEST_F(ZNCTest, ModperlSaslAuth) { @@ -547,9 +547,9 @@ TEST_F(ZNCTest, ModperlSaslAuth) { client2.Write("AUTHENTICATE FOO"); client2.ReadUntil("AUTHENTICATE " + QByteArrayLiteral("Welcome").toBase64()); client2.Write("AUTHENTICATE +"); - client2.ReadUntil( - ":irc.znc.in 900 nick nick!user@localhost user :You are now logged in " - "as user"); + client2.ReadUntilRe( + ":irc.znc.in 900 nick nick!user@(localhost)? user :You are now logged " + "in as user"); } } // namespace From 1ac3571c605edbbc7271cff68832b4d7d2b0a101 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Tue, 22 Apr 2025 01:15:23 +0100 Subject: [PATCH 30/35] Add modified Qt to NOTICE --- NOTICE | 1 + 1 file changed, 1 insertion(+) diff --git a/NOTICE b/NOTICE index 7d87122a..83b6f8a1 100644 --- a/NOTICE +++ b/NOTICE @@ -18,6 +18,7 @@ ZNC includes modified code from CMakeFindFrameworks.cmake by Kitware, Inc., lice ZNC includes modified code from TestLargeFiles.cmake, licensed under Boost Software License, Version 1.0. ZNC includes code from cctz (https://github.com/google/cctz), licensed under the Apache License 2.0. ZNC includes code from gtest-parallel (https://github.com/google/gtest-parallel), licensed under the Apache License 2.0. +ZNC integration test includes modified code from Qt, licensed under LGPL. ZNC is developed by these people: From 81e7fd69de607fc1c420ef0e253c5731c70acc68 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Mon, 28 Apr 2025 00:26:02 +0100 Subject: [PATCH 31/35] WIP debugging failures on mac and cygwin... --- test/integration/tests/scripting.cpp | 2 ++ third_party/Csocket | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/integration/tests/scripting.cpp b/test/integration/tests/scripting.cpp index afeeaeb7..b58c6ba4 100644 --- a/test/integration/tests/scripting.cpp +++ b/test/integration/tests/scripting.cpp @@ -243,6 +243,7 @@ TEST_F(ZNCTest, ModperlUnixSocket) { auto client = LoginClient(); client.Write("znc loadmod modperl"); client.Write("znc loadmod socktest"); + sleep(1); client.Write("PRIVMSG *socktest :foo"); client.ReadUntil("received 4 bytes"); } @@ -267,6 +268,7 @@ TEST_F(ZNCTest, ModpythonVCString) { auto client = LoginClient(); client.Write("znc loadmod modpython"); client.Write("znc loadmod test"); + sleep(1); client.Write("PRIVMSG *test :foo"); client.ReadUntil("'*test', 'foo'"); } diff --git a/third_party/Csocket b/third_party/Csocket index 3f536e9a..13473b0f 160000 --- a/third_party/Csocket +++ b/third_party/Csocket @@ -1 +1 @@ -Subproject commit 3f536e9a609bb058a5649cbe938b2e4cfde32d53 +Subproject commit 13473b0f97c9a23909634eb2364cd40c54a8d5ce From 5184d662d3b528b257658a7bb5a4dd9943c2c353 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Mon, 28 Apr 2025 21:56:45 +0100 Subject: [PATCH 32/35] macos test probably should be fixed now --- test/integration/tests/scripting.cpp | 27 ++++++++++++++++++++++----- third_party/Csocket | 2 +- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/test/integration/tests/scripting.cpp b/test/integration/tests/scripting.cpp index b58c6ba4..211c631d 100644 --- a/test/integration/tests/scripting.cpp +++ b/test/integration/tests/scripting.cpp @@ -180,18 +180,26 @@ TEST_F(ZNCTest, ModpythonUnixSocket) { class socktest(znc.Module): def OnLoad(self, args, ret): listen = self.CreateSocket(lis) - return listen.Listen(addrtype='unix', path=self.GetSavePath() + "/sock") + return listen.Listen(addrtype='unix', path=self.TestSockPath()) def OnModCommand(self, cmd): sock = self.CreateSocket() - sock.ConnectUnix(self.GetSavePath() + "/sock") + sock.ConnectUnix(self.TestSockPath()) sock.WriteBytes(b'blah') + + def TestSockPath(self): + path = self.GetSavePath() + "/sock" + # https://unix.stackexchange.com/questions/367008/why-is-socket-path-length-limited-to-a-hundred-chars + if len(path) < 100: + return path + return "./testsock.modpython" )"); auto ircd = ConnectIRCd(); auto client = LoginClient(); client.Write("znc loadmod modpython"); client.Write("znc loadmod socktest"); + client.ReadUntil("Loaded module socktest"); client.Write("PRIVMSG *socktest :foo"); client.ReadUntil("received 4 bytes"); } @@ -227,14 +235,23 @@ TEST_F(ZNCTest, ModperlUnixSocket) { sub OnLoad { my $self = shift; my $listen = $self->CreateSocket('socktest::lis'); - $listen->Listen(addrtype=>'unix', path=>$self->GetSavePath . "/sock"); + $listen->Listen(addrtype=>'unix', path=>$self->TestSockPath); } sub OnModCommand { my ($self, $cmd) = @_; my $sock = $self->CreateSocket('socktest::conn'); - $sock->ConnectUnix($self->GetSavePath . "/sock"); + $sock->ConnectUnix($self->TestSockPath); $sock->Write('blah'); } + sub TestSockPath { + my $self = shift; + my $path = $self->GetSavePath . "/sock"; + # https://unix.stackexchange.com/questions/367008/why-is-socket-path-length-limited-to-a-hundred-chars + if (length($path) < 100) { + return $path; + } + return "./testsock.modperl"; + } 1; )"); @@ -243,7 +260,7 @@ TEST_F(ZNCTest, ModperlUnixSocket) { auto client = LoginClient(); client.Write("znc loadmod modperl"); client.Write("znc loadmod socktest"); - sleep(1); + client.ReadUntil("Loaded module socktest"); client.Write("PRIVMSG *socktest :foo"); client.ReadUntil("received 4 bytes"); } diff --git a/third_party/Csocket b/third_party/Csocket index 13473b0f..c9fb2e11 160000 --- a/third_party/Csocket +++ b/third_party/Csocket @@ -1 +1 @@ -Subproject commit 13473b0f97c9a23909634eb2364cd40c54a8d5ce +Subproject commit c9fb2e110bff29450a4c2a710c69b5a66701466e From 5974d0ff3b90cb9379bbcba2adfe3b0e9ea7d4b0 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Thu, 1 May 2025 22:24:10 +0100 Subject: [PATCH 33/35] Workaround for parallel writing of LLVM profiler data Skip failing tests on cygwin: we'll just have to mark the unix socket feature as "experimental", but shouldn't block merging it due to cygwin being weird --- .github/build.sh | 2 +- test/CMakeLists.txt | 3 ++- test/integration/tests/scripting.cpp | 6 +++++ test/integration/wrapper.py | 39 ++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100755 test/integration/wrapper.py diff --git a/.github/build.sh b/.github/build.sh index 33a56ed3..b3ecfb45 100644 --- a/.github/build.sh +++ b/.github/build.sh @@ -47,7 +47,7 @@ case "${CC:-gcc}" in export PATH=$PATH:/Library/Developer/CommandLineTools/usr/bin fi llvm-profdata merge unittest.profraw -o unittest.profdata - llvm-profdata merge inttest.profraw -o inttest.profdata + llvm-profdata merge inttest.profraw* -o inttest.profdata llvm-cov show -show-line-counts-or-regions -instr-profile=unittest.profdata test/unittest_bin > unittest-cmake-coverage.txt llvm-cov show -show-line-counts-or-regions -instr-profile=inttest.profdata /usr/local/bin/znc > inttest-znc-coverage.txt find /usr/local/lib/znc -name '*.so' -or -name '*.bundle' | while read f; do llvm-cov show -show-line-counts-or-regions -instr-profile=inttest.profdata $f > inttest-$(basename $f)-coverage.txt; done diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 780a7fb6..827cdb86 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -102,6 +102,7 @@ add_custom_target(inttest COMMAND # znc-buildmod should use the correct compiler. # https://bugs.gentoo.org/699258 is an example of how it can go wrong. ${CMAKE_COMMAND} -E env MAKEFLAGS= CXX=${CMAKE_CXX_COMPILER} + "INTTEST_BIN=${CMAKE_CURRENT_BINARY_DIR}/integration/inttest" "${PROJECT_SOURCE_DIR}/third_party/gtest-parallel/gtest-parallel" - "${CMAKE_CURRENT_BINARY_DIR}/integration/inttest") + "${CMAKE_CURRENT_SOURCE_DIR}/integration/wrapper.py") add_dependencies(inttest inttest_bin) diff --git a/test/integration/tests/scripting.cpp b/test/integration/tests/scripting.cpp index 211c631d..86c3f950 100644 --- a/test/integration/tests/scripting.cpp +++ b/test/integration/tests/scripting.cpp @@ -159,6 +159,9 @@ TEST_F(ZNCTest, ModperlSocket) { TEST_F(ZNCTest, ModpythonUnixSocket) { #ifndef WANT_PYTHON GTEST_SKIP() << "Modpython is disabled"; +#endif +#ifdef __CYGWIN__ + GTEST_SKIP() << "Bug to fix: https://github.com/znc/znc/issues/1947"; #endif auto znc = Run(); znc->CanLeak(); @@ -207,6 +210,9 @@ TEST_F(ZNCTest, ModpythonUnixSocket) { TEST_F(ZNCTest, ModperlUnixSocket) { #ifndef WANT_PERL GTEST_SKIP() << "Modperl is disabled"; +#endif +#ifdef __CYGWIN__ + GTEST_SKIP() << "Bug to fix: https://github.com/znc/znc/issues/1947"; #endif auto znc = Run(); znc->CanLeak(); diff --git a/test/integration/wrapper.py b/test/integration/wrapper.py new file mode 100755 index 00000000..efa707a9 --- /dev/null +++ b/test/integration/wrapper.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2004-2025 ZNC, see the NOTICE file for details. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# The purpose of this file is to fix LLVM profiler data in the parallel +# execution of the test - all of them write to the same file, so it races and +# produces invalid data + +import os +import sys +import re + +test = '' +for arg in sys.argv: + m = re.match(r'--gtest_filter=(.*)', arg) + if m: + test = m[1] + break + +if test != '' and 'LLVM_PROFILE_FILE' in os.environ: + os.environ['LLVM_PROFILE_FILE'] += '.' + test + +binary = os.environ['INTTEST_BIN'] +sys.argv[0] = binary + +os.execv(binary, sys.argv) From 864252bfc611f91ebd6ff9943ead78c90a18bfc4 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Thu, 1 May 2025 22:31:12 +0100 Subject: [PATCH 34/35] update csocket --- third_party/Csocket | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/Csocket b/third_party/Csocket index c9fb2e11..e21e85d1 160000 --- a/third_party/Csocket +++ b/third_party/Csocket @@ -1 +1 @@ -Subproject commit c9fb2e110bff29450a4c2a710c69b5a66701466e +Subproject commit e21e85d1b890182c7606f07e7d9b2e146f316f24 From 00579d151e5347b1d926b6f1287c6a27273ad13f Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Thu, 1 May 2025 22:44:39 +0100 Subject: [PATCH 35/35] Pass %p to LLVM_PROFILE_FILE instead of wrapper --- .github/build.sh | 2 +- test/CMakeLists.txt | 3 +-- test/integration/wrapper.py | 39 ------------------------------------- 3 files changed, 2 insertions(+), 42 deletions(-) delete mode 100755 test/integration/wrapper.py diff --git a/.github/build.sh b/.github/build.sh index b3ecfb45..5c4cabae 100644 --- a/.github/build.sh +++ b/.github/build.sh @@ -32,7 +32,7 @@ sudo make install /usr/local/bin/znc --version # TODO: use DEVEL_COVER_OPTIONS for https://metacpan.org/pod/Devel::Cover -env LLVM_PROFILE_FILE="$PWD/inttest.profraw" ZNC_MODPERL_COVERAGE_OPTS="-db,$PWD/cover_db" PYTHONWARNINGS=error make VERBOSE=1 inttest +env LLVM_PROFILE_FILE="$PWD/inttest.profraw.%p" ZNC_MODPERL_COVERAGE_OPTS="-db,$PWD/cover_db" PYTHONWARNINGS=error make VERBOSE=1 inttest ls -lRa ~/perl5/bin/cover --no-gcov --report=clover diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 827cdb86..780a7fb6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -102,7 +102,6 @@ add_custom_target(inttest COMMAND # znc-buildmod should use the correct compiler. # https://bugs.gentoo.org/699258 is an example of how it can go wrong. ${CMAKE_COMMAND} -E env MAKEFLAGS= CXX=${CMAKE_CXX_COMPILER} - "INTTEST_BIN=${CMAKE_CURRENT_BINARY_DIR}/integration/inttest" "${PROJECT_SOURCE_DIR}/third_party/gtest-parallel/gtest-parallel" - "${CMAKE_CURRENT_SOURCE_DIR}/integration/wrapper.py") + "${CMAKE_CURRENT_BINARY_DIR}/integration/inttest") add_dependencies(inttest inttest_bin) diff --git a/test/integration/wrapper.py b/test/integration/wrapper.py deleted file mode 100755 index efa707a9..00000000 --- a/test/integration/wrapper.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2004-2025 ZNC, see the NOTICE file for details. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# The purpose of this file is to fix LLVM profiler data in the parallel -# execution of the test - all of them write to the same file, so it races and -# produces invalid data - -import os -import sys -import re - -test = '' -for arg in sys.argv: - m = re.match(r'--gtest_filter=(.*)', arg) - if m: - test = m[1] - break - -if test != '' and 'LLVM_PROFILE_FILE' in os.environ: - os.environ['LLVM_PROFILE_FILE'] += '.' + test - -binary = os.environ['INTTEST_BIN'] -sys.argv[0] = binary - -os.execv(binary, sys.argv)