diff --git a/Makefile.in b/Makefile.in index ae6235af..74d8896e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -47,7 +47,7 @@ LIB_SRCS := $(addprefix src/,$(LIB_SRCS)) BIN_SRCS := src/main.cpp LIB_OBJS := $(patsubst %cpp,%o,$(LIB_SRCS)) BIN_OBJS := $(patsubst %cpp,%o,$(BIN_SRCS)) -TESTS := StringTest ConfigTest UtilsTest ThreadTest NickTest +TESTS := StringTest ConfigTest UtilsTest ThreadTest NickTest ClientTest TESTS := $(addprefix test/,$(addsuffix .o,$(TESTS))) CLEAN := znc src/*.o test/*.o core core.* .version_extra .depend modules/.depend unittest DISTCLEAN := Makefile config.log config.status znc-buildmod \ diff --git a/include/znc/Client.h b/include/znc/Client.h index 938df057..855357e5 100644 --- a/include/znc/Client.h +++ b/include/znc/Client.h @@ -112,6 +112,7 @@ public: CString GetNick(bool bAllowIRCNick = true) const; CString GetNickMask() const; + CString GetIdentifier() const { return m_sIdentifier; } bool HasNamesx() const { return m_bNamesx; } bool HasUHNames() const { return m_bUHNames; } bool IsAway() const { return m_bAway; } @@ -119,6 +120,8 @@ public: bool HasBatch() const { return m_bBatch; } bool HasSelfMessage() const { return m_bSelfMessage; } + static bool IsValidIdentifier(const CString& sIdentifier); + void UserCommand(CString& sLine); void UserPortCommand(CString& sLine); void StatusCTCP(const CString& sCommand); @@ -157,6 +160,9 @@ public: private: void HandleCap(const CString& sLine); void RespondCap(const CString& sResponse); + void ParsePass(const CString& sAuthLine); + void ParseUser(const CString& sAuthLine); + void ParseIdentifier(const CString& sAuthLine); protected: bool m_bGotPass; @@ -175,8 +181,11 @@ protected: CString m_sPass; CString m_sUser; CString m_sNetwork; + CString m_sIdentifier; std::shared_ptr m_spAuth; SCString m_ssAcceptedCaps; + + friend class ClientTest; }; #endif // !_CLIENT_H diff --git a/src/Client.cpp b/src/Client.cpp index 05cc2e21..b79e3576 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -118,20 +118,7 @@ void CClient::ReadLine(const CString& sData) { m_bGotPass = true; CString sAuthLine = sLine.Token(1, true).TrimPrefix_n(); - - // [user[/network]:]password - if (sAuthLine.find(":") == CString::npos) { - m_sPass = sAuthLine; - sAuthLine = ""; - } else { - m_sPass = sAuthLine.Token(1, true, ":"); - sAuthLine = sAuthLine.Token(0, false, ":"); - } - - if (!sAuthLine.empty()) { - m_sUser = sAuthLine.Token(0, false, "/"); - m_sNetwork = sAuthLine.Token(1, true, "/"); - } + ParsePass(sAuthLine); AuthUser(); return; // Don't forward this msg. ZNC has already registered us. @@ -144,12 +131,10 @@ void CClient::ReadLine(const CString& sData) { AuthUser(); return; // Don't forward this msg. ZNC will handle nick changes until auth is complete } else if (sCommand.Equals("USER")) { - // user[/network] CString sAuthLine = sLine.Token(1); if (m_sUser.empty() && !sAuthLine.empty()) { - m_sUser = sAuthLine.Token(0, false, "/"); - m_sNetwork = sAuthLine.Token(1, true, "/"); + ParseUser(sAuthLine); } m_bGotUser = true; @@ -771,9 +756,12 @@ void CClient::PutIRC(const CString& sLine) { CString CClient::GetFullName() const { if (!m_pUser) return GetRemoteIP(); - if (!m_pNetwork) - return m_pUser->GetUserName(); - return m_pUser->GetUserName() + "/" + m_pNetwork->GetName(); + CString sFullName = m_pUser->GetUserName(); + if (!m_sIdentifier.empty()) + sFullName += "@" + m_sIdentifier; + if (m_pNetwork) + sFullName += "/" + m_pNetwork->GetName(); + return sFullName; } void CClient::PutClient(const CString& sLine) { @@ -849,6 +837,25 @@ CString CClient::GetNickMask() const { return GetNick() + "!" + (m_pNetwork ? m_pNetwork->GetBindHost() : m_pUser->GetIdent()) + "@" + sHost; } +bool CClient::IsValidIdentifier(const CString& sIdentifier) { + // ^[-\w]+$ + + if (sIdentifier.empty()) { + return false; + } + + const char *p = sIdentifier.c_str(); + while (*p) { + if (*p != '_' && *p != '-' && !isalnum(*p)) { + return false; + } + + p++; + } + + return true; +} + void CClient::RespondCap(const CString& sResponse) { PutClient(":irc.znc.in CAP " + GetNick() + " " + sResponse); @@ -971,3 +978,47 @@ void CClient::HandleCap(const CString& sLine) PutClient(":irc.znc.in 410 " + GetNick() + " " + sSubCmd + " :Invalid CAP subcommand"); } } + +void CClient::ParsePass(const CString& sAuthLine) { + // [user[@identifier][/network]:]password + + const size_t uColon = sAuthLine.find(":"); + if (uColon != CString::npos) { + m_sPass = sAuthLine.substr(uColon + 1); + + ParseUser(sAuthLine.substr(0, uColon)); + } else { + m_sPass = sAuthLine; + } +} + +void CClient::ParseUser(const CString& sAuthLine) { + // user[@identifier][/network] + + const size_t uSlash = sAuthLine.rfind("/"); + if (uSlash != CString::npos) { + m_sNetwork = sAuthLine.substr(uSlash + 1); + + ParseIdentifier(sAuthLine.substr(0, uSlash)); + } else { + ParseIdentifier(sAuthLine); + } +} + +void CClient::ParseIdentifier(const CString& sAuthLine) { + // user[@identifier] + + const size_t uAt = sAuthLine.rfind("@"); + if (uAt != CString::npos) { + const CString sId = sAuthLine.substr(uAt + 1); + + if (IsValidIdentifier(sId)) { + m_sIdentifier = sId; + m_sUser = sAuthLine.substr(0, uAt); + } else { + m_sUser = sAuthLine; + } + } else { + m_sUser = sAuthLine; + } +} diff --git a/test/ClientTest.cpp b/test/ClientTest.cpp new file mode 100644 index 00000000..36edc967 --- /dev/null +++ b/test/ClientTest.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2004-2014 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. + */ + +#include +#include +#include + +class ClientTest : public ::testing::Test { +protected: + void SetUp() { CZNC::CreateInstance(); } + void TearDown() { CZNC::DestroyInstance(); } + void testPass(const CString& sInput, const CString& sUser, const CString& sIdentifier, const CString& sNetwork, const CString& sPass) const { + CClient client; + client.ParsePass(sInput); + EXPECT_EQ(sUser, client.m_sUser); + EXPECT_EQ(sIdentifier, client.m_sIdentifier); + EXPECT_EQ(sNetwork, client.m_sNetwork); + EXPECT_EQ(sPass, client.m_sPass); + } + + void testUser(const CString& sInput, const CString& sUser, const CString& sIdentifier, const CString& sNetwork) const { + CClient client; + client.ParseUser(sInput); + EXPECT_EQ(sUser, client.m_sUser); + EXPECT_EQ(sIdentifier, client.m_sIdentifier); + EXPECT_EQ(sNetwork, client.m_sNetwork); + } +}; + +TEST_F(ClientTest, Pass) { + testPass("p@ss#w0rd", "", "", "", "p@ss#w0rd"); + testPass("user:p@ss#w0rd", "user", "", "", "p@ss#w0rd"); + testPass("user/net-work:p@ss#w0rd", "user", "", "net-work", "p@ss#w0rd"); + testPass("user@identifier:p@ss#w0rd", "user", "identifier", "", "p@ss#w0rd"); + testPass("user@identifier/net-work:p@ss#w0rd", "user", "identifier", "net-work", "p@ss#w0rd"); + + testPass("user@znc.in:p@ss#w0rd", "user@znc.in", "", "", "p@ss#w0rd"); + testPass("user@znc.in/net-work:p@ss#w0rd", "user@znc.in", "", "net-work", "p@ss#w0rd"); + testPass("user@znc.in@identifier:p@ss#w0rd", "user@znc.in", "identifier", "", "p@ss#w0rd"); + testPass("user@znc.in@identifier/net-work:p@ss#w0rd", "user@znc.in", "identifier", "net-work", "p@ss#w0rd"); +} + +TEST_F(ClientTest, User) { + testUser("user/net-work", "user", "", "net-work"); + testUser("user@identifier", "user", "identifier", ""); + testUser("user@identifier/net-work", "user", "identifier", "net-work"); + + testUser("user@znc.in/net-work", "user@znc.in", "", "net-work"); + testUser("user@znc.in@identifier", "user@znc.in", "identifier", ""); + testUser("user@znc.in@identifier/net-work", "user@znc.in", "identifier", "net-work"); +}