mirror of
https://github.com/znc/znc.git
synced 2026-03-28 17:42:41 +01:00
Allow clients to specify an ID via PASS or USER
- PASS [user[@identifier][/network]:]password - USER user[@identifier][/network] ... NOTE: There's a slight ambiguosity with the '@' character, which happens to be a valid character in usernames, but also acts as a marker for the identifier. Therefore, '@' is considered as part of the username if it's followed by non-word characters (as in an email address), otherwise as a marker for an identifier. This is only an enabler for #343. The rest can be done with modules: - managing client ID specific playback buffers - filtering channels based on the client ID The reason this should be part of ZNC core is that only global modules have access to OnUnknownUserRaw(), which is needed to capture USER/PASS. First of all, the aforementioned modules shouldn't be global. Furthermore, it would be possible to have only one module that parsed and removed the client ID so that ZNC core woulnd't choke.
This commit is contained 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 \
|
||||
|
||||
@@ -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<CAuthBase> m_spAuth;
|
||||
SCString m_ssAcceptedCaps;
|
||||
|
||||
friend class ClientTest;
|
||||
};
|
||||
|
||||
#endif // !_CLIENT_H
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
64
test/ClientTest.cpp
Normal file
64
test/ClientTest.cpp
Normal file
@@ -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 <gtest/gtest.h>
|
||||
#include <znc/Client.h>
|
||||
#include <znc/znc.h>
|
||||
|
||||
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");
|
||||
}
|
||||
Reference in New Issue
Block a user