Files
znc/src/Chan.cpp
Uli Schlachter 8ffab186b4 CModule::OnMode(): Fix a stupid NULL pointer dereference
When joining a channel, OnMode() (via SetModes()) was called with pOpNick ==
NULL. This bad pointer was turned into a reference and given to modules.

This bug exists since 2008 when the OnMode() module call was added. It wasn't
noticed before because apparently no module used this CNick argument before.

Signed-off-by: Uli Schlachter <psychon@znc.in>
2012-12-06 17:28:05 +01:00

586 lines
15 KiB
C++

/*
* Copyright (C) 2004-2012 See the AUTHORS file for details.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*/
#include <znc/Chan.h>
#include <znc/FileUtils.h>
#include <znc/IRCSock.h>
#include <znc/User.h>
#include <znc/IRCNetwork.h>
#include <znc/znc.h>
#include <znc/Config.h>
using std::set;
using std::vector;
using std::map;
CChan::CChan(const CString& sName, CIRCNetwork* pNetwork, bool bInConfig, CConfig *pConfig) {
m_sName = sName.Token(0);
m_sKey = sName.Token(1);
m_pNetwork = pNetwork;
if (!m_pNetwork->IsChan(m_sName)) {
m_sName = "#" + m_sName;
}
m_bInConfig = bInConfig;
m_Nick.SetNetwork(m_pNetwork);
m_bDetached = false;
m_bDisabled = false;
SetBufferCount(m_pNetwork->GetUser()->GetBufferCount(), true);
SetAutoClearChanBuffer(m_pNetwork->GetUser()->AutoClearChanBuffer());
Reset();
if (pConfig) {
CString sValue;
if (pConfig->FindStringEntry("buffer", sValue))
SetBufferCount(sValue.ToUInt(), true);
if (pConfig->FindStringEntry("autoclearchanbuffer", sValue))
SetAutoClearChanBuffer(sValue.ToBool());
if (pConfig->FindStringEntry("keepbuffer", sValue))
SetAutoClearChanBuffer(!sValue.ToBool()); // XXX Compatibility crap, added in 0.207
if (pConfig->FindStringEntry("detached", sValue))
SetDetached(sValue.ToBool());
if (pConfig->FindStringEntry("autocycle", sValue))
if (sValue.Equals("true"))
CUtils::PrintError("WARNING: AutoCycle has been removed, instead try -> LoadModule = autocycle " + sName);
if (pConfig->FindStringEntry("key", sValue))
SetKey(sValue);
if (pConfig->FindStringEntry("modes", sValue))
SetDefaultModes(sValue);
}
}
CChan::~CChan() {
ClearNicks();
}
void CChan::Reset() {
m_bIsOn = false;
m_bModeKnown = false;
m_musModes.clear();
m_sTopic = "";
m_sTopicOwner = "";
m_ulTopicDate = 0;
m_ulCreationDate = 0;
m_Nick.Reset();
ClearNicks();
ResetJoinTries();
}
CConfig CChan::ToConfig() {
CConfig config;
CUser *pUser = m_pNetwork->GetUser();
if (pUser->GetBufferCount() != GetBufferCount())
config.AddKeyValuePair("Buffer", CString(GetBufferCount()));
if (pUser->AutoClearChanBuffer() != AutoClearChanBuffer())
config.AddKeyValuePair("AutoClearChanBuffer", CString(AutoClearChanBuffer()));
if (IsDetached())
config.AddKeyValuePair("Detached", "true");
if (!GetKey().empty())
config.AddKeyValuePair("Key", GetKey());
if (!GetDefaultModes().empty())
config.AddKeyValuePair("Modes", GetDefaultModes());
return config;
}
void CChan::Clone(CChan& chan) {
// We assume that m_sName and m_pNetwork are equal
SetBufferCount(chan.GetBufferCount(), true);
SetAutoClearChanBuffer(chan.AutoClearChanBuffer());
SetKey(chan.GetKey());
SetDefaultModes(chan.GetDefaultModes());
if (IsDetached() != chan.IsDetached()) {
// Only send something if it makes sense
// (= Only detach if client is on the channel
// and only attach if we are on the channel)
if (IsOn()) {
if (IsDetached()) {
JoinUser(false, "");
} else {
DetachUser();
}
}
SetDetached(chan.IsDetached());
}
}
void CChan::Cycle() const {
m_pNetwork->PutIRC("PART " + GetName() + "\r\nJOIN " + GetName() + " " + GetKey());
}
void CChan::JoinUser(bool bForce, const CString& sKey, CClient* pClient) {
if (!bForce && (!IsOn() || !IsDetached())) {
m_pNetwork->PutIRC("JOIN " + GetName() + " " + ((sKey.empty()) ? GetKey() : sKey));
SetDetached(false);
return;
}
m_pNetwork->PutUser(":" + m_pNetwork->GetIRCNick().GetNickMask() + " JOIN :" + GetName(), pClient);
if (!GetTopic().empty()) {
m_pNetwork->PutUser(":" + m_pNetwork->GetIRCServer() + " 332 " + m_pNetwork->GetIRCNick().GetNick() + " " + GetName() + " :" + GetTopic(), pClient);
m_pNetwork->PutUser(":" + m_pNetwork->GetIRCServer() + " 333 " + m_pNetwork->GetIRCNick().GetNick() + " " + GetName() + " " + GetTopicOwner() + " " + CString(GetTopicDate()), pClient);
}
CString sPre = ":" + m_pNetwork->GetIRCServer() + " 353 " + m_pNetwork->GetIRCNick().GetNick() + " " + GetModeForNames() + " " + GetName() + " :";
CString sLine = sPre;
CString sPerm, sNick;
vector<CClient*>& vpClients = m_pNetwork->GetClients();
for (vector<CClient*>::iterator it = vpClients.begin(); it != vpClients.end(); ++it) {
CClient* pThisClient;
if (!pClient)
pThisClient = *it;
else
pThisClient = pClient;
for (map<CString,CNick>::iterator a = m_msNicks.begin(); a != m_msNicks.end(); ++a) {
if (pThisClient->HasNamesx()) {
sPerm = a->second.GetPermStr();
} else {
char c = a->second.GetPermChar();
sPerm = "";
if (c != '\0') {
sPerm += c;
}
}
if (pThisClient->HasUHNames() && !a->second.GetIdent().empty() && !a->second.GetHost().empty()) {
sNick = a->first + "!" + a->second.GetIdent() + "@" + a->second.GetHost();
} else {
sNick = a->first;
}
sLine += sPerm + sNick;
if (sLine.size() >= 490 || a == (--m_msNicks.end())) {
m_pNetwork->PutUser(sLine, pThisClient);
sLine = sPre;
} else {
sLine += " ";
}
}
if (pClient) // We only want to do this for one client
break;
}
m_pNetwork->PutUser(":" + m_pNetwork->GetIRCServer() + " 366 " + m_pNetwork->GetIRCNick().GetNick() + " " + GetName() + " :End of /NAMES list.", pClient);
m_bDetached = false;
// Send Buffer
SendBuffer(pClient);
}
void CChan::DetachUser() {
if (!m_bDetached) {
m_pNetwork->PutUser(":" + m_pNetwork->GetIRCNick().GetNickMask() + " PART " + GetName());
m_bDetached = true;
}
}
void CChan::AttachUser() {
if (m_bDetached) {
m_pNetwork->PutUser(":" + m_pNetwork->GetIRCNick().GetNickMask() + " JOIN " + GetName());
m_bDetached = false;
}
}
CString CChan::GetModeString() const {
CString sModes, sArgs;
for (map<unsigned char, CString>::const_iterator it = m_musModes.begin(); it != m_musModes.end(); ++it) {
sModes += it->first;
if (it->second.size()) {
sArgs += " " + it->second;
}
}
return sModes.empty() ? sModes : CString("+" + sModes + sArgs);
}
CString CChan::GetModeForNames() const {
CString sMode;
for (map<unsigned char, CString>::const_iterator it = m_musModes.begin(); it != m_musModes.end(); ++it) {
if (it->first == 's') {
sMode = "@";
} else if ((it->first == 'p') && sMode.empty()){
sMode = "*";
}
}
return (sMode.empty() ? "=" : sMode);
}
void CChan::SetModes(const CString& sModes) {
m_musModes.clear();
ModeChange(sModes);
}
void CChan::SetAutoClearChanBuffer(bool b) {
m_bAutoClearChanBuffer = b;
if (m_bAutoClearChanBuffer && !IsDetached() && m_pNetwork->IsUserOnline()) {
ClearBuffer();
}
}
void CChan::OnWho(const CString& sNick, const CString& sIdent, const CString& sHost) {
CNick* pNick = FindNick(sNick);
if (pNick) {
pNick->SetIdent(sIdent);
pNick->SetHost(sHost);
}
}
void CChan::ModeChange(const CString& sModes, const CNick* pOpNick) {
CString sModeArg = sModes.Token(0);
CString sArgs = sModes.Token(1, true);
bool bAdd = true;
/* Try to find a CNick* from this channel so that pOpNick->HasPerm()
* works as expected. */
if (pOpNick) {
CNick* OpNick = FindNick(pOpNick->GetNick());
/* If nothing was found, use the original pOpNick, else use the
* CNick* from FindNick() */
if (OpNick)
pOpNick = OpNick;
}
if (pOpNick) {
NETWORKMODULECALL(OnRawMode(*pOpNick, *this, sModeArg, sArgs), m_pNetwork->GetUser(), m_pNetwork, NULL, NOTHING);
}
for (unsigned int a = 0; a < sModeArg.size(); a++) {
const unsigned char& uMode = sModeArg[a];
if (uMode == '+') {
bAdd = true;
} else if (uMode == '-') {
bAdd = false;
} else if (m_pNetwork->GetIRCSock()->IsPermMode(uMode)) {
CString sArg = GetModeArg(sArgs);
CNick* pNick = FindNick(sArg);
if (pNick) {
unsigned char uPerm = m_pNetwork->GetIRCSock()->GetPermFromMode(uMode);
if (uPerm) {
bool bNoChange = (pNick->HasPerm(uPerm) == bAdd);
if (bAdd) {
pNick->AddPerm(uPerm);
if (pNick->GetNick().Equals(m_pNetwork->GetCurNick())) {
AddPerm(uPerm);
}
} else {
pNick->RemPerm(uPerm);
if (pNick->GetNick().Equals(m_pNetwork->GetCurNick())) {
RemPerm(uPerm);
}
}
if (uMode && pOpNick) {
NETWORKMODULECALL(OnChanPermission(*pOpNick, *pNick, *this, uMode, bAdd, bNoChange), m_pNetwork->GetUser(), m_pNetwork, NULL, NOTHING);
if (uMode == CChan::M_Op) {
if (bAdd) {
NETWORKMODULECALL(OnOp(*pOpNick, *pNick, *this, bNoChange), m_pNetwork->GetUser(), m_pNetwork, NULL, NOTHING);
} else {
NETWORKMODULECALL(OnDeop(*pOpNick, *pNick, *this, bNoChange), m_pNetwork->GetUser(), m_pNetwork, NULL, NOTHING);
}
} else if (uMode == CChan::M_Voice) {
if (bAdd) {
NETWORKMODULECALL(OnVoice(*pOpNick, *pNick, *this, bNoChange), m_pNetwork->GetUser(), m_pNetwork, NULL, NOTHING);
} else {
NETWORKMODULECALL(OnDevoice(*pOpNick, *pNick, *this, bNoChange), m_pNetwork->GetUser(), m_pNetwork, NULL, NOTHING);
}
}
}
}
}
} else {
bool bList = false;
CString sArg;
switch (m_pNetwork->GetIRCSock()->GetModeType(uMode)) {
case CIRCSock::ListArg:
bList = true;
sArg = GetModeArg(sArgs);
break;
case CIRCSock::HasArg:
sArg = GetModeArg(sArgs);
break;
case CIRCSock::NoArg:
break;
case CIRCSock::ArgWhenSet:
if (bAdd) {
sArg = GetModeArg(sArgs);
}
break;
}
if (pOpNick) {
bool bNoChange;
if (bList) {
bNoChange = false;
} else if (bAdd) {
bNoChange = HasMode(uMode) && GetModeArg(uMode) == sArg;
} else {
bNoChange = !HasMode(uMode);
}
NETWORKMODULECALL(OnMode(*pOpNick, *this, uMode, sArg, bAdd, bNoChange), m_pNetwork->GetUser(), m_pNetwork, NULL, NOTHING);
}
if (!bList) {
(bAdd) ? AddMode(uMode, sArg) : RemMode(uMode);
}
}
}
}
CString CChan::GetOptions() const {
CString sRet;
if (IsDetached()) {
sRet += (sRet.empty()) ? "Detached" : ", Detached";
}
if (AutoClearChanBuffer()) {
sRet += (sRet.empty()) ? "AutoClearChanBuffer" : ", AutoClearChanBuffer";
}
return sRet;
}
CString CChan::GetModeArg(unsigned char uMode) const {
if (uMode) {
map<unsigned char, CString>::const_iterator it = m_musModes.find(uMode);
if (it != m_musModes.end()) {
return it->second;
}
}
return "";
}
bool CChan::HasMode(unsigned char uMode) const {
return (uMode && m_musModes.find(uMode) != m_musModes.end());
}
bool CChan::AddMode(unsigned char uMode, const CString& sArg) {
m_musModes[uMode] = sArg;
return true;
}
bool CChan::RemMode(unsigned char uMode) {
if (!HasMode(uMode)) {
return false;
}
m_musModes.erase(uMode);
return true;
}
CString CChan::GetModeArg(CString& sArgs) const {
CString sRet = sArgs.substr(0, sArgs.find(' '));
sArgs = (sRet.size() < sArgs.size()) ? sArgs.substr(sRet.size() +1) : "";
return sRet;
}
void CChan::ClearNicks() {
m_msNicks.clear();
}
int CChan::AddNicks(const CString& sNicks) {
int iRet = 0;
VCString vsNicks;
VCString::iterator it;
sNicks.Split(" ", vsNicks, false);
for (it = vsNicks.begin(); it != vsNicks.end(); ++it) {
if (AddNick(*it)) {
iRet++;
}
}
return iRet;
}
bool CChan::AddNick(const CString& sNick) {
const char* p = sNick.c_str();
CString sPrefix, sTmp, sIdent, sHost;
while (m_pNetwork->GetIRCSock()->IsPermChar(*p)) {
sPrefix += *p;
if (!*++p) {
return false;
}
}
sTmp = p;
// The UHNames extension gets us nick!ident@host instead of just plain nick
sIdent = sTmp.Token(1, true, "!");
sHost = sIdent.Token(1, true, "@");
sIdent = sIdent.Token(0, false, "@");
// Get the nick
sTmp = sTmp.Token(0, false, "!");
CNick tmpNick(sTmp);
CNick* pNick = FindNick(sTmp);
if (!pNick) {
pNick = &tmpNick;
pNick->SetNetwork(m_pNetwork);
}
if (!sIdent.empty())
pNick->SetIdent(sIdent);
if (!sHost.empty())
pNick->SetHost(sHost);
for (CString::size_type i = 0; i < sPrefix.length(); i++) {
pNick->AddPerm(sPrefix[i]);
}
if (pNick->GetNick().Equals(m_pNetwork->GetCurNick())) {
for (CString::size_type i = 0; i < sPrefix.length(); i++) {
AddPerm(sPrefix[i]);
}
}
m_msNicks[pNick->GetNick()] = *pNick;
return true;
}
map<char, unsigned int> CChan::GetPermCounts() const {
map<char, unsigned int> mRet;
map<CString,CNick>::const_iterator it;
for (it = m_msNicks.begin(); it != m_msNicks.end(); ++it) {
CString sPerms = it->second.GetPermStr();
for (unsigned int p = 0; p < sPerms.size(); p++) {
mRet[sPerms[p]]++;
}
}
return mRet;
}
bool CChan::RemNick(const CString& sNick) {
map<CString,CNick>::iterator it;
set<unsigned char>::iterator it2;
it = m_msNicks.find(sNick);
if (it == m_msNicks.end()) {
return false;
}
m_msNicks.erase(it);
return true;
}
bool CChan::ChangeNick(const CString& sOldNick, const CString& sNewNick) {
map<CString,CNick>::iterator it = m_msNicks.find(sOldNick);
if (it == m_msNicks.end()) {
return false;
}
// Rename this nick
it->second.SetNick(sNewNick);
// Insert a new element into the map then erase the old one, do this to change the key to the new nick
m_msNicks[sNewNick] = it->second;
m_msNicks.erase(it);
return true;
}
const CNick* CChan::FindNick(const CString& sNick) const {
map<CString,CNick>::const_iterator it = m_msNicks.find(sNick);
return (it != m_msNicks.end()) ? &it->second : NULL;
}
CNick* CChan::FindNick(const CString& sNick) {
map<CString,CNick>::iterator it = m_msNicks.find(sNick);
return (it != m_msNicks.end()) ? &it->second : NULL;
}
void CChan::SendBuffer(CClient* pClient) {
if (m_pNetwork && m_pNetwork->IsUserAttached()) {
// in the event that pClient is NULL, need to send this to all clients for the user
// I'm presuming here that pClient is listed inside vClients thus vClients at this
// point can't be empty.
//
// This loop has to be cycled twice to maintain the existing behavior which is
// 1. OnChanBufferStarting
// 2. OnChanBufferPlayLine
// 3. ClearBuffer() if not keeping the buffer
// 4. OnChanBufferEnding
//
// With the exception of ClearBuffer(), this needs to happen per client, and
// if pClient is not NULL, the loops break after the first iteration.
//
// Rework this if you like ...
if (!m_Buffer.IsEmpty()) {
const vector<CClient*> & vClients = m_pNetwork->GetClients();
for (size_t uClient = 0; uClient < vClients.size(); ++uClient) {
CClient * pUseClient = (pClient ? pClient : vClients[uClient]);
bool bSkipStatusMsg = pUseClient->HasServerTime();
NETWORKMODULECALL(OnChanBufferStarting(*this, *pUseClient), m_pNetwork->GetUser(), m_pNetwork, NULL, &bSkipStatusMsg);
if (!bSkipStatusMsg) {
m_pNetwork->PutUser(":***!znc@znc.in PRIVMSG " + GetName() + " :Buffer Playback...", pUseClient);
}
size_t uSize = m_Buffer.Size();
for (size_t uIdx = 0; uIdx < uSize; uIdx++) {
CString sLine = m_Buffer.GetLine(uIdx, *pUseClient);
bool bNotShowThisLine = false;
NETWORKMODULECALL(OnChanBufferPlayLine(*this, *pUseClient, sLine), m_pNetwork->GetUser(), m_pNetwork, NULL, &bNotShowThisLine);
if (bNotShowThisLine) continue;
m_pNetwork->PutUser(sLine, pUseClient);
}
bSkipStatusMsg = pUseClient->HasServerTime();
NETWORKMODULECALL(OnChanBufferEnding(*this, *pUseClient), m_pNetwork->GetUser(), m_pNetwork, NULL, &bSkipStatusMsg);
if (!bSkipStatusMsg) {
m_pNetwork->PutUser(":***!znc@znc.in PRIVMSG " + GetName() + " :Playback Complete.", pUseClient);
}
if (pClient)
break;
}
if (AutoClearChanBuffer()) {
ClearBuffer();
}
}
}
}
void CChan::Enable() {
ResetJoinTries();
m_bDisabled = false;
}