diff --git a/.github/build.sh b/.github/build.sh index 33a56ed3..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 @@ -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/.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..83b6f8a1 100644 --- a/NOTICE +++ b/NOTICE @@ -17,6 +17,8 @@ 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 integration test includes modified code from Qt, licensed under LGPL. ZNC is developed by these people: 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/Listener.h b/include/znc/Listener.h index f0a2ae6d..383547a2 100644 --- a/include/znc/Listener.h +++ b/include/znc/Listener.h @@ -22,33 +22,26 @@ // Forward Declarations class CRealListener; +class CConfig; // !Forward Declarations 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; } @@ -58,20 +51,71 @@ 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(); + virtual CConfig ToConfig() const; private: protected: + void SetupSSL() const; + 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 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/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/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/include/znc/znc.h b/include/znc/znc.h index 8ffda566..e6da2a2e 100644 --- a/include/znc/znc.h +++ b/include/znc/znc.h @@ -202,11 +202,22 @@ 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, + 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 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) { @@ -267,6 +278,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/make-tarball.sh b/make-tarball.sh index a96a1970..17ea90d8 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/modules/controlpanel.cpp b/modules/controlpanel.cpp index a8c3f121..e76d0a68 100644 --- a/modules/controlpanel.cpp +++ b/modules/controlpanel.cpp @@ -21,6 +21,7 @@ #include #include #include +#include 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/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/modperl/startup.pl b/modules/modperl/startup.pl index e355127a..3c4cf2f2 100644 --- a/modules/modperl/startup.pl +++ b/modules/modperl/startup.pl @@ -779,7 +779,7 @@ sub Connect { $self->GetModule->GetManager->Connect( $host, $port, - "perl-socket", + $self->ConstructSockName("Perl-C"), $arg{timeout}//60, $arg{ssl}//0, $arg{bindhost}//'', @@ -787,11 +787,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) { } @@ -800,7 +815,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, @@ -811,7 +826,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 3665caa0..721b7a41 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/modules/webadmin.cpp b/modules/webadmin.cpp index 283f285a..feacc452 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 CListener( - 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"); @@ -1048,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())); @@ -1223,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()); } } @@ -1480,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()); } } @@ -1920,33 +1860,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) { @@ -1965,8 +1882,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); } @@ -1982,28 +1930,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()) { @@ -2043,45 +1997,61 @@ class CWebAdminMod : public CModule { for (const CListener* pListener : vpListeners) { CTemplate& l = Tmpl.AddRow("ListenLoop"); - l["Port"] = CString(pListener->GetPort()); - l["BindHost"] = pListener->GetBindHost(); + if (const CTCPListener* pTCPListener = + dynamic_cast(pListener)) { + l["Type"] = "TCP"; + 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()); +#ifdef HAVE_IPV6 + 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"; +#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); - 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()); + CString sURIPrefix = pListener->GetURIPrefix(); + if (!sURIPrefix.EndsWith("/")) { + sURIPrefix += "/"; + } + l["URIPrefix"] = sURIPrefix; #ifdef HAVE_LIBSSL if (pListener->IsSSL()) { l["IsSSL"] = "true"; } #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; - } -#else - l["IsIPV4"] = "true"; -#endif } vector vDirs; diff --git a/src/Client.cpp b/src/Client.cpp index 48a8bac5..31cb7ac5 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -1341,7 +1341,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"); diff --git a/src/ClientCommand.cpp b/src/ClientCommand.cpp index 41352cd4..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") : ""); @@ -1620,6 +1633,10 @@ void CClient::UserCommand(CString& sLine) { } } +namespace { +struct PortCommandUsage {}; +} + void CClient::UserPortCommand(CString& sLine) { const CString sCommand = sLine.Token(0); @@ -1636,23 +1653,31 @@ void CClient::UserPortCommand(CString& sLine) { for (const CListener* pListener : vpListeners) { Table.AddRow(); - Table.SetCell(t_s("Port", "listports"), - CString(pListener->GetPort())); - Table.SetCell( - t_s("BindHost", "listports"), - (pListener->GetBindHost().empty() ? CString("*") - : pListener->GetBindHost())); + 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())); + + EAddrType eAddr = pTCPListener->GetAddrType(); + Table.SetCell( + t_s("Protocol", "listports"), + eAddr == ADDR_ALL + ? t_s("IPv4 and IPv6", "listports") + : (eAddr == ADDR_IPV4ONLY ? t_s("IPv4", "listports") + : t_s("IPv6", "listports"))); + } else if (const CUnixListener* pUnixListener = + dynamic_cast(pListener)) { + Table.SetCell(t_s("Port", "listports"), + pUnixListener->GetPath()); + } Table.SetCell(t_s("SSL", "listports"), pListener->IsSSL() ? t_s("yes", "listports|ssl") : t_s("no", "listports|ssl")); - EAddrType eAddr = pListener->GetAddrType(); - Table.SetCell(t_s("Protocol", "listports"), - eAddr == ADDR_ALL ? t_s("IPv4 and IPv6", "listports") - : (eAddr == ADDR_IPV4ONLY - ? t_s("IPv4", "listports") - : t_s("IPv6", "listports"))); - CListener::EAcceptType eAccept = pListener->GetAcceptType(); Table.SetCell(t_s("IRC", "listports"), eAccept == CListener::ACCEPT_ALL || @@ -1672,68 +1697,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 CListener(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); @@ -1741,6 +1790,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/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/Listener.cpp b/src/Listener.cpp index 178c8b44..bc763fbf 100644 --- a/src/Listener.cpp +++ b/src/Listener.cpp @@ -15,39 +15,100 @@ */ #include +#include #include CListener::~CListener() { if (m_pListener) CZNC::Get().GetManager().DelSockByAddr(m_pListener); } -bool CListener::Listen() { +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; +} + +void CListener::SetupSSL() 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() { +} + +bool CTCPListener::Listen() { if (!m_uPort || m_pListener) { errno = EINVAL; return false; } m_pListener = new CRealListener(*this); - - 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()); - } -#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); } +CConfig CTCPListener::ToConfig() const { + CConfig listenerConfig = CListener::ToConfig(); + + listenerConfig.AddKeyValuePair("Host", GetBindHost()); + listenerConfig.AddKeyValuePair("Port", CString(GetPort())); + + listenerConfig.AddKeyValuePair( + "IPv4", CString(GetAddrType() != ADDR_IPV6ONLY)); + listenerConfig.AddKeyValuePair( + "IPv6", CString(GetAddrType() != ADDR_IPV4ONLY)); + + return listenerConfig; +} + +CUnixListener::~CUnixListener() { +} + +bool CUnixListener::Listen() { + if (m_pListener) { + errno = EINVAL; + return false; + } + + m_pListener = new CRealListener(*this); + SetupSSL(); + + return CZNC::Get().GetManager().ListenUnix("UNIX_LISTENER", m_sPath, + m_pListener); +} + +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/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/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/src/znc.cpp b/src/znc.cpp index f4a67d8d..9dc9daea 100644 --- a/src/znc.cpp +++ b/src/znc.cpp @@ -486,29 +486,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)); @@ -646,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 CListener( - (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(); @@ -700,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); @@ -809,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)) { @@ -1586,9 +1581,21 @@ 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; +} + +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; @@ -1639,8 +1646,37 @@ bool CZNC::AddListener(const CString& sLine, CString& sError) { sError); } -bool CZNC::AddListener(unsigned short uPort, const CString& sBindHost, - const CString& sURIPrefixRaw, bool bSSL, EAddrType eAddr, +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& sURIPrefix, bool bSSL, EAddrType eAddr, CListener::EAcceptType eAccept, CString& sError) { CString sHostComment; @@ -1672,54 +1708,32 @@ bool CZNC::AddListener(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); + return FinishAddingListener(pListener, sError); +} + +bool CZNC::AddUnixListener(const CString& sPath, const CString& sURIPrefix, + bool bSSL, CListener::EAcceptType eAccept, + CString& sError) { + CUtils::PrintAction("Binding to path [" + sPath + "]" + (bSSL ? " with SSL" : "")); + + if (!CheckSslAndPemFile(bSSL, sError)) return false; CListener* pListener = - new CListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept); + 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); @@ -1736,6 +1750,7 @@ bool CZNC::AddListener(unsigned short uPort, const CString& sBindHost, bool CZNC::AddListener(CConfig* pConfig, CString& sError) { CString sBindHost; CString sURIPrefix; + CString sPath; bool bSSL; bool b4; #ifdef HAVE_IPV6 @@ -1746,32 +1761,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; @@ -1785,8 +1790,38 @@ bool CZNC::AddListener(CConfig* pConfig, CString& sError) { return false; } - return AddListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept, - sError); + // 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); + 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 AddTCPListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, + eAccept, sError); + } + return AddUnixListener(sPath, sURIPrefix, bSSL, eAccept, sError); } bool CZNC::AddListener(CListener* pListener) { 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/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/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..1663f121 100644 --- a/test/integration/framework/base.h +++ b/test/integration/framework/base.h @@ -20,11 +20,11 @@ #include #include +#include #include +#include #include -#include - namespace znc_inttest { template @@ -34,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. @@ -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: @@ -114,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(); @@ -156,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(); @@ -202,6 +233,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 +243,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/cygwin.cpp b/test/integration/framework/cygwin.cpp new file mode 100644 index 00000000..deee497b --- /dev/null +++ b/test/integration/framework/cygwin.cpp @@ -0,0 +1,107 @@ +// 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 +#undef QT_THREADSAFE_CLOEXEC +#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())).data(); + 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) { + qDebug() << "CygwinWorkaroundLocalConnect: qt_safe_socket errored"; + 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) { + qDebug() << "CygwinWorkaroundLocalConnect: qt_safe_connect errored"; + return; + } + + ::fcntl(d->connectingSocket, F_SETFL, + ::fcntl(d->connectingSocket, F_GETFL) | O_NONBLOCK); + 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; + 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 77628c3e..e52732fd 100644 --- a/test/integration/framework/znctest.cpp +++ b/test/integration/framework/znctest.cpp @@ -15,7 +15,9 @@ */ #include +#include #include "znctest.h" +#include "cygwin.h" #ifndef ZNC_BIN_DIR #define ZNC_BIN_DIR "" @@ -24,13 +26,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 +45,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 +65,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 +76,13 @@ 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.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/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 202fa21b..4d57a1f5 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..652fccb4 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 "); 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 "); client2.ReadUntil( - "NOTICE nick :*** user@identifier attached from 127.0.0.1"); + "NOTICE nick :*** user@identifier attached from "); client2.Write("QUIT"); - client.ReadUntil("NOTICE nick :*** user detached from 127.0.0.1"); + client.ReadUntil("NOTICE nick :*** user detached from "); client3.Close(); client.ReadUntil( - "NOTICE nick :*** user@identifier detached from 127.0.0.1"); + "NOTICE nick :*** user@identifier detached from "); } TEST_F(ZNCTest, ClientNotifyModule) { @@ -64,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 (127.0.0.1) 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 (127.0.0.1) 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 (127.0.0.1) 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 (127.0.0.1 / 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 (127.0.0.1 / 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 (127.0.0.1 / 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 (127.0.0.1 / 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 (127.0.0.1 / 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) { @@ -241,13 +247,18 @@ 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(); 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 +363,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 +389,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 +406,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 +420,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 2bccaa53..86c3f950 100644 --- a/test/integration/tests/scripting.cpp +++ b/test/integration/tests/scripting.cpp @@ -156,6 +156,121 @@ TEST_F(ZNCTest, ModperlSocket) { client.ReadUntil("received 4 bytes"); } +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(); + + 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.TestSockPath()) + + def OnModCommand(self, cmd): + sock = self.CreateSocket() + 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"); +} + +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(); + + 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->TestSockPath); + } + sub OnModCommand { + my ($self, $cmd) = @_; + my $sock = $self->CreateSocket('socktest::conn'); + $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; + )"); + + auto ircd = ConnectIRCd(); + auto client = LoginClient(); + client.Write("znc loadmod modperl"); + client.Write("znc loadmod socktest"); + client.ReadUntil("Loaded module socktest"); + client.Write("PRIVMSG *socktest :foo"); + client.ReadUntil("received 4 bytes"); +} + TEST_F(ZNCTest, ModpythonVCString) { #ifndef WANT_PYTHON GTEST_SKIP() << "Modpython is disabled"; @@ -176,6 +291,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'"); } @@ -399,9 +515,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@127.0.0.1 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) { @@ -456,9 +572,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@127.0.0.1 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 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