Implement chghost capability

Interaction with extended-join doesn't yet work correctly, because ZNC
doesn't keep track of everyone's real names
This commit is contained in:
Alexey Sokolov
2024-10-15 11:29:36 +01:00
parent 25b19bb889
commit d49399bbca
7 changed files with 161 additions and 3 deletions
+2
View File
@@ -119,6 +119,7 @@ class CClient : public CIRCSocket {
bool HasExtendedJoin() const { return m_bExtendedJoin; }
bool HasNamesx() const { return m_bNamesx; }
bool HasUHNames() const { return m_bUHNames; }
bool HasChgHost() const { return m_bChgHost; }
bool IsAway() const { return m_bAway; }
bool HasServerTime() const { return m_bServerTime; }
bool HasBatch() const { return m_bBatch; }
@@ -289,6 +290,7 @@ class CClient : public CIRCSocket {
bool m_bExtendedJoin;
bool m_bNamesx;
bool m_bUHNames;
bool m_bChgHost;
bool m_bAway;
bool m_bServerTime;
bool m_bBatch;
+2
View File
@@ -131,6 +131,7 @@ class CIRCSock : public CIRCSocket {
unsigned int GetMaxNickLen() const { return m_uMaxNickLen; }
EChanModeArgs GetModeType(char cMode) const;
char GetPermFromMode(char cMode) const;
char GetModeFromPerm(char cPerm) const;
const std::map<char, EChanModeArgs>& GetChanModes() const {
return m_mceChanModes;
}
@@ -177,6 +178,7 @@ class CIRCSock : public CIRCSocket {
bool OnActionMessage(CActionMessage& Message);
bool OnAwayMessage(CMessage& Message);
bool OnCapabilityMessage(CMessage& Message);
bool OnChgHostMessage(CChgHostMessage& Message);
bool OnCTCPMessage(CCTCPMessage& Message);
bool OnErrorMessage(CMessage& Message);
bool OnInviteMessage(CMessage& Message);
+11
View File
@@ -67,6 +67,7 @@ class CMessage {
Action,
Away,
Capability,
ChgHost,
CTCP,
Error,
Invite,
@@ -121,6 +122,7 @@ class CMessage {
*/
VCString GetParamsSplit(unsigned int uIdx, unsigned int uLen = -1) const;
void SetParams(const VCString& vsParams);
void SetParams(VCString&& vsParams);
/// @deprecated use GetParamsColon() instead.
CString GetParams(unsigned int uIdx, unsigned int uLen = -1) const
@@ -333,4 +335,13 @@ class CTopicMessage : public CTargetMessage {
};
REGISTER_ZNC_MESSAGE(CTopicMessage);
class CChgHostMessage : public CMessage {
public:
CString GetNewIdent() const { return GetParam(0); }
void SetNewIdent(const CString& sIdent) { SetParam(0, sIdent); }
CString GetNewHost() const { return GetParam(1); }
void SetNewHost(const CString& sHost) { SetParam(1, sHost); }
};
REGISTER_ZNC_MESSAGE(CChgHostMessage);
#endif // !ZNC_MESSAGE_H
+4 -1
View File
@@ -75,7 +75,8 @@ using std::vector;
} \
}
CClient::CClient() : CIRCSocket(),
CClient::CClient()
: CIRCSocket(),
m_bGotPass(false),
m_bGotNick(false),
m_bGotUser(false),
@@ -87,6 +88,7 @@ CClient::CClient() : CIRCSocket(),
m_bExtendedJoin(false),
m_bNamesx(false),
m_bUHNames(false),
m_bChgHost(false),
m_bAway(false),
m_bServerTime(false),
m_bBatch(false),
@@ -762,6 +764,7 @@ CClient::CoreCaps() {
}},
{"cap-notify",
[](CClient* pClient, bool bVal) { pClient->m_bCapNotify = bVal; }},
{"chghost", [](CClient* pClient, bool bVal) { pClient->m_bChgHost = bVal; }},
};
// For compatibility with older clients
+80 -2
View File
@@ -185,6 +185,9 @@ void CIRCSock::ReadLine(const CString& sData) {
case CMessage::Type::Capability:
bReturn = OnCapabilityMessage(Message);
break;
case CMessage::Type::ChgHost:
bReturn = OnChgHostMessage(Message);
break;
case CMessage::Type::CTCP:
bReturn = OnCTCPMessage(Message);
break;
@@ -380,8 +383,8 @@ bool CIRCSock::OnCapabilityMessage(CMessage& Message) {
{"userhost-in-names", [this](bool bVal) { m_bUHNames = bVal; }},
{"cap-notify", [](bool bVal) {}},
{"server-time", [this](bool bVal) { m_bServerTime = bVal; }},
{"znc.in/server-time-iso",
[this](bool bVal) { m_bServerTime = bVal; }},
{"znc.in/server-time-iso", [this](bool bVal) { m_bServerTime = bVal; }},
{"chghost", [](bool) {}},
};
auto RemoveCap = [&](const CString& sCap) {
@@ -514,6 +517,69 @@ bool CIRCSock::OnCTCPMessage(CCTCPMessage& Message) {
return (pChan && pChan->IsDetached());
}
bool CIRCSock::OnChgHostMessage(CChgHostMessage& Message) {
// The emulation of QUIT+JOIN would be cleaner inside CClient::PutClient()
// but computation of new modes is difficult enough so that I don't want to
// repeat it for every client
//
// TODO: make CNick store modes (v, o) instead of perm chars (+, @), that
// would simplify this
bool bNeedEmulate = false;
for (CClient* pClient : m_pNetwork->GetClients()) {
if (pClient->HasChgHost()) {
pClient->PutClient(Message);
} else {
bNeedEmulate = true;
pClient->PutClient(CMessage(Message.GetNick(), "QUIT",
{"Changing hostname"},
Message.GetTags()));
}
}
if (!bNeedEmulate) return true;
CNick NewNick = Message.GetNick();
NewNick.SetIdent(Message.GetNewIdent());
NewNick.SetHost(Message.GetNewHost());
for (CChan* pChan : m_pNetwork->GetChans()) {
if (CNick* pNick = pChan->FindNick(Message.GetNick().GetNick())) {
pNick->SetIdent(Message.GetNewIdent());
pNick->SetHost(Message.GetNewHost());
}
CTargetMessage ModeMsg;
ModeMsg.SetNick(CNick(":irc.znc.in"));
ModeMsg.SetTags(Message.GetTags());
ModeMsg.SetCommand("MODE");
VCString vsModeParams = {pChan->GetName(), "+"};
if (CNick* pNick = pChan->FindNick(NewNick.GetNick())) {
for (char cPerm : pNick->GetPermStr()) {
char cMode = GetModeFromPerm(cPerm);
if (cMode) {
vsModeParams[1].append(1, cMode);
vsModeParams.push_back(NewNick.GetNick());
}
}
}
ModeMsg.SetParams(std::move(vsModeParams));
for (CClient* pClient : m_pNetwork->GetClients()) {
if (!pClient->HasChgHost()) {
// TODO: send account name and real name too, for
// extended-join
pClient->PutClient(CMessage(NewNick, "JOIN", {pChan->GetName()},
Message.GetTags()));
if (ModeMsg.GetParams().size() > 2) {
pClient->PutClient(ModeMsg);
}
}
}
}
return true;
}
bool CIRCSock::OnErrorMessage(CMessage& Message) {
// ERROR :Closing Link: nick[24.24.24.24] (Excess Flood)
CString sError = Message.GetParam(0);
@@ -1517,6 +1583,18 @@ char CIRCSock::GetPermFromMode(char cMode) const {
return 0;
}
char CIRCSock::GetModeFromPerm(char cPerm) const {
if (m_sPermModes.size() == m_sPerms.size()) {
for (unsigned int a = 0; a < m_sPermModes.size(); a++) {
if (m_sPerms[a] == cPerm) {
return m_sPermModes[a];
}
}
}
return 0;
}
CIRCSock::EChanModeArgs CIRCSock::GetModeType(char cMode) const {
map<char, EChanModeArgs>::const_iterator it =
m_mceChanModes.find(cMode);
+11
View File
@@ -81,6 +81,16 @@ void CMessage::SetParams(const VCString& vsParams) {
}
}
void CMessage::SetParams(VCString&& vsParams) {
m_vsParams = std::move(vsParams);
m_bColon = false;
if (m_eType == Type::Text || m_eType == Type::Notice ||
m_eType == Type::Action || m_eType == Type::CTCP) {
InitType();
}
}
CString CMessage::GetParam(unsigned int uIdx) const {
if (uIdx >= m_vsParams.size()) {
return "";
@@ -270,6 +280,7 @@ void CMessage::InitType() {
{"ACCOUNT", Type::Account},
{"AWAY", Type::Away},
{"CAP", Type::Capability},
{"CHGHOST", Type::ChgHost},
{"ERROR", Type::Error},
{"INVITE", Type::Invite},
{"JOIN", Type::Join},
+51
View File
@@ -745,5 +745,56 @@ TEST_F(ZNCTest, CapReqWithoutLs) {
ASSERT_THAT(client.ReadRemainder().toStdString(), Not(HasSubstr("Welcome")));
}
TEST_F(ZNCTest, ChgHostEmulation) {
auto znc = Run();
auto ircd = ConnectIRCd();
ircd.Write("CAP user LS :chghost");
ircd.ReadUntil("CAP REQ :chghost");
ircd.Write("CAP user ACK :chghost");
auto client1 = LoginClient();
auto client2 = LoginClient();
client2.Write("CAP REQ :chghost");
client2.ReadUntil("ACK");
ircd.Write(":user!oldident@oldhost JOIN #chan");
ircd.Write(":user!oldident@oldhost CHGHOST newident newhost");
client1.ReadUntil(":user!oldident@oldhost QUIT :Changing hostname");
client1.ReadUntil(":user!newident@newhost JOIN #chan");
ASSERT_THAT(client1.ReadRemainder().toStdString(), Not(HasSubstr("MODE")));
client2.ReadUntil(":user!oldident@oldhost CHGHOST newident newhost");
client2.Close();
ircd.Write(":server MODE #chan +v user");
client1.ReadUntil("MODE");
ircd.Write(":user!newident@newhost CHGHOST ident-2 host-2");
client1.ReadUntil(":irc.znc.in MODE #chan +v user");
ircd.Write(":server MODE #chan +o user");
client1.ReadUntil("MODE");
ircd.Write(":user!ident-2@host-2 CHGHOST ident-3 host-3");
client1.ReadUntil(":irc.znc.in MODE #chan +ov user user");
}
TEST_F(ZNCTest, ChgHostOnce) {
auto znc = Run();
auto ircd = ConnectIRCd();
ircd.Write("CAP user LS :chghost");
ircd.ReadUntil("CAP REQ :chghost");
ircd.Write("CAP user ACK :chghost");
auto client = LoginClient();
client.Write("CAP REQ :chghost");
client.ReadUntil("ACK");
ircd.Write(":user!oldident@oldhost JOIN #chan");
ircd.Write(":user!oldident@oldhost JOIN #chan2");
ircd.Write(":user!oldident@oldhost CHGHOST newident newhost");
client.ReadUntil("CHGHOST");
ASSERT_THAT(client.ReadRemainder().toStdString(),
Not(HasSubstr("CHGHOST")));
}
} // namespace
} // namespace znc_inttest