mirror of
https://github.com/znc/znc.git
synced 2026-07-04 00:41:38 +02:00
Auto op module
git-svn-id: https://znc.svn.sourceforge.net/svnroot/znc/trunk@598 726aef4b-f618-498e-8847-2d620e286838
This commit is contained in:
@@ -0,0 +1,500 @@
|
||||
#include "main.h"
|
||||
#include "User.h"
|
||||
#include "Nick.h"
|
||||
#include "Modules.h"
|
||||
#include "Chan.h"
|
||||
|
||||
class CAutoOpMod;
|
||||
|
||||
class CAutoOpTimer : public CTimer {
|
||||
public:
|
||||
|
||||
CAutoOpTimer(CAutoOpMod* pModule)
|
||||
: CTimer((CModule*) pModule, 20, 0, "AutoOpChecker", "Check channels for auto op candidates") {
|
||||
m_pParent = pModule;
|
||||
}
|
||||
|
||||
virtual ~CAutoOpTimer() {}
|
||||
|
||||
private:
|
||||
protected:
|
||||
virtual void RunJob();
|
||||
|
||||
CAutoOpMod* m_pParent;
|
||||
};
|
||||
|
||||
class CAutoOpUser {
|
||||
public:
|
||||
CAutoOpUser() {}
|
||||
|
||||
CAutoOpUser(const CString& sLine) {
|
||||
FromString(sLine);
|
||||
}
|
||||
|
||||
CAutoOpUser(const CString& sUsername, const CString& sUserKey, const CString& sHostmask, const CString& sChannels) :
|
||||
m_sUsername(sUsername),
|
||||
m_sUserKey(sUserKey),
|
||||
m_sHostmask(sHostmask) {
|
||||
AddChans(sChannels);
|
||||
}
|
||||
|
||||
virtual ~CAutoOpUser() {}
|
||||
|
||||
const CString& GetUsername() const { return m_sUsername; }
|
||||
const CString& GetUserKey() const { return m_sUserKey; }
|
||||
const CString& GetHostmask() const { return m_sHostmask; }
|
||||
|
||||
bool ChannelMatches(const CString& sChan) const {
|
||||
for (set<CString>::iterator it = m_ssChans.begin(); it != m_ssChans.end(); it++) {
|
||||
if (sChan.AsLower().WildCmp(*it)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HostMatches(const CString& sHostmask) {
|
||||
return sHostmask.WildCmp(m_sHostmask);
|
||||
}
|
||||
|
||||
CString GetChannels() const {
|
||||
CString sRet;
|
||||
|
||||
for (set<CString>::iterator it = m_ssChans.begin(); it != m_ssChans.end(); it++) {
|
||||
if (!sRet.empty()) {
|
||||
sRet += " ";
|
||||
}
|
||||
|
||||
sRet += *it;
|
||||
}
|
||||
|
||||
return sRet;
|
||||
}
|
||||
|
||||
void DelChans(const CString& sChans) {
|
||||
VCString vsChans;
|
||||
sChans.Split(" ", vsChans);
|
||||
|
||||
for (unsigned int a = 0; a < vsChans.size(); a++) {
|
||||
m_ssChans.erase(vsChans[a].AsLower());
|
||||
}
|
||||
}
|
||||
|
||||
void AddChans(const CString& sChans) {
|
||||
VCString vsChans;
|
||||
sChans.Split(" ", vsChans);
|
||||
|
||||
for (unsigned int a = 0; a < vsChans.size(); a++) {
|
||||
m_ssChans.insert(vsChans[a].AsLower());
|
||||
}
|
||||
}
|
||||
|
||||
CString ToString() const {
|
||||
CString sChans;
|
||||
|
||||
for (set<CString>::iterator it = m_ssChans.begin(); it != m_ssChans.end(); it++) {
|
||||
if (!sChans.empty()) {
|
||||
sChans += " ";
|
||||
}
|
||||
|
||||
sChans += *it;
|
||||
}
|
||||
|
||||
return m_sUsername + "\t" + m_sHostmask + "\t" + m_sUserKey + "\t" + sChans;
|
||||
}
|
||||
|
||||
bool FromString(const CString& sLine) {
|
||||
m_sUsername = sLine.Token(0, false, "\t");
|
||||
m_sHostmask = sLine.Token(1, false, "\t");
|
||||
m_sUserKey = sLine.Token(2, false, "\t");
|
||||
sLine.Token(3, false, "\t").Split(m_ssChans);
|
||||
|
||||
return !m_sUserKey.empty();
|
||||
}
|
||||
private:
|
||||
protected:
|
||||
CString m_sUsername;
|
||||
CString m_sUserKey;
|
||||
CString m_sHostmask;
|
||||
set<CString> m_ssChans;
|
||||
};
|
||||
|
||||
class CAutoOpMod : public CModule {
|
||||
public:
|
||||
MODCONSTRUCTOR(CAutoOpMod) {}
|
||||
|
||||
virtual bool OnLoad(const CString& sArgs) {
|
||||
if (sArgs.empty() || sArgs.find(" ") != CString::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_sKey = sArgs;
|
||||
AddTimer(new CAutoOpTimer(this));
|
||||
|
||||
// Load the users
|
||||
for (MCString::iterator it = BeginNV(); it != EndNV(); it++) {
|
||||
const CString& sLine = it->second;
|
||||
CAutoOpUser* pUser = new CAutoOpUser;
|
||||
|
||||
if (!pUser->FromString(sLine) || FindUser(pUser->GetUsername().AsLower())) {
|
||||
delete pUser;
|
||||
} else {
|
||||
m_msUsers[pUser->GetUsername().AsLower()] = pUser;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual ~CAutoOpMod() {
|
||||
}
|
||||
|
||||
virtual void OnChanPermission(const CNick& OpNick, const CNick& Nick, CChan& Channel, unsigned char uMode, bool bAdded, bool bNoChange) {
|
||||
}
|
||||
|
||||
virtual void OnOp(const CNick& OpNick, const CNick& Nick, CChan& Channel, bool bNoChange) {
|
||||
}
|
||||
|
||||
virtual void OnDeop(const CNick& OpNick, const CNick& Nick, CChan& Channel, bool bNoChange) {
|
||||
}
|
||||
|
||||
virtual void OnVoice(const CNick& OpNick, const CNick& Nick, CChan& Channel, bool bNoChange) {
|
||||
}
|
||||
|
||||
virtual void OnDevoice(const CNick& OpNick, const CNick& Nick, CChan& Channel, bool bNoChange) {
|
||||
}
|
||||
|
||||
virtual void OnRawMode(const CNick& OpNick, CChan& Channel, const CString& sModes, const CString& sArgs) {
|
||||
}
|
||||
|
||||
virtual void OnJoin(const CNick& Nick, CChan& Channel) {
|
||||
if (Channel.HasPerm(CChan::Op)) { // If we have ops in this chan
|
||||
for (map<CString, CAutoOpUser*>::iterator it = m_msUsers.begin(); it != m_msUsers.end(); it++) {
|
||||
if (it->second->HostMatches(Nick.GetHostMask()) && it->second->ChannelMatches(Channel.GetName())) { // and the nick who joined is a valid user
|
||||
m_msQueue[Nick.GetNick().AsLower()] = ""; // then insert this nick into the queue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void OnQuit(const CNick& Nick, const CString& sMessage, const vector<CChan*>& vChans) {
|
||||
MCString::iterator it = m_msQueue.find(Nick.GetNick().AsLower());
|
||||
|
||||
if (it != m_msQueue.end()) {
|
||||
m_msQueue.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void OnNick(const CNick& OldNick, const CString& sNewNick, const vector<CChan*>& vChans) {
|
||||
// Update the queue with nick changes
|
||||
MCString::iterator it = m_msQueue.find(OldNick.GetNick().AsLower());
|
||||
|
||||
if (it != m_msQueue.end()) {
|
||||
m_msQueue[sNewNick.AsLower()] = it->second;
|
||||
m_msQueue.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
virtual EModRet OnPrivNotice(CNick& Nick, CString& sMessage) {
|
||||
if (sMessage.Token(0).CaseCmp("!ZNCAO") != 0) {
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
CString sCommand = sMessage.Token(1);
|
||||
|
||||
if (sCommand.CaseCmp("CHALLENGE") == 0) {
|
||||
ChallengeRespond(Nick, sMessage.Token(2));
|
||||
} else if (sCommand.CaseCmp("RESPONSE") == 0) {
|
||||
VerifyResponse(Nick, sMessage.Token(2));
|
||||
}
|
||||
|
||||
return HALTCORE;
|
||||
}
|
||||
|
||||
virtual void OnModCommand(const CString& sLine) {
|
||||
CString sCommand = sLine.Token(0).AsUpper();
|
||||
|
||||
if (sCommand.CaseCmp("HELP") == 0) {
|
||||
PutModule("Commands are: ListUsers, AddChans, DelChans, AddUser, DelUser");
|
||||
} else if (sCommand.CaseCmp("TIMERS") == 0) {
|
||||
ListTimers();
|
||||
} else if (sCommand.CaseCmp("ADDUSER") == 0 || sCommand.CaseCmp("DELUSER") == 0) {
|
||||
CString sUser = sLine.Token(1);
|
||||
CString sHost = sLine.Token(2);
|
||||
CString sKey = sLine.Token(3);
|
||||
|
||||
if (sCommand.CaseCmp("ADDUSER") == 0) {
|
||||
if (sHost.empty()) {
|
||||
PutModule("Usage: " + sCommand + " <user> <hostmask> <key> [channels]");
|
||||
} else {
|
||||
CAutoOpUser* pUser = AddUser(sUser, sKey, sHost, sLine.Token(4, true));
|
||||
|
||||
if (pUser) {
|
||||
SetNV(sUser, pUser->ToString());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DelUser(sUser);
|
||||
DelNV(sUser);
|
||||
}
|
||||
} else if (sCommand.CaseCmp("LISTUSERS") == 0) {
|
||||
if (m_msUsers.empty()) {
|
||||
PutModule("There are no users defined");
|
||||
return;
|
||||
}
|
||||
|
||||
CTable Table;
|
||||
|
||||
Table.AddColumn("User");
|
||||
Table.AddColumn("Hostmask");
|
||||
Table.AddColumn("Channels");
|
||||
|
||||
for (map<CString, CAutoOpUser*>::iterator it = m_msUsers.begin(); it != m_msUsers.end(); it++) {
|
||||
Table.AddRow();
|
||||
Table.SetCell("User", it->second->GetUsername());
|
||||
Table.SetCell("Hostmask", it->second->GetHostmask());
|
||||
Table.SetCell("Channels", it->second->GetChannels());
|
||||
}
|
||||
|
||||
CString sLine;
|
||||
unsigned int uTableIdx = 0;
|
||||
|
||||
while (Table.GetLine(uTableIdx++, sLine)) {
|
||||
PutModule(sLine);
|
||||
}
|
||||
} else if (sCommand.CaseCmp("ADDCHANS") == 0 || sCommand.CaseCmp("DELCHANS") == 0) {
|
||||
CString sUser = sLine.Token(1);
|
||||
CString sChans = sLine.Token(2, true);
|
||||
|
||||
if (sChans.empty()) {
|
||||
PutModule("Usage: " + sCommand + " <user> <channel> [channel] ...");
|
||||
return;
|
||||
}
|
||||
|
||||
CAutoOpUser* pUser = FindUser(sUser);
|
||||
|
||||
if (!pUser) {
|
||||
PutModule("No such user");
|
||||
return;
|
||||
}
|
||||
|
||||
if (sCommand.CaseCmp("ADDCHANS") == 0) {
|
||||
pUser->AddChans(sChans);
|
||||
PutModule("Channel(s) added to user [" + pUser->GetUsername() + "]");
|
||||
} else {
|
||||
pUser->DelChans(sChans);
|
||||
PutModule("Channel(s) Removed from user [" + pUser->GetUsername() + "]");
|
||||
}
|
||||
|
||||
SetNV(pUser->GetUsername(), pUser->ToString());
|
||||
} else {
|
||||
PutModule("Unknown command, try HELP");
|
||||
}
|
||||
}
|
||||
|
||||
CAutoOpUser* FindUser(const CString& sUser) {
|
||||
map<CString, CAutoOpUser*>::iterator it = m_msUsers.find(sUser.AsLower());
|
||||
|
||||
return (it != m_msUsers.end()) ? it->second : NULL;
|
||||
}
|
||||
|
||||
CAutoOpUser* FindUserByHost(const CString& sHostmask, const CString& sChannel = "") {
|
||||
for (map<CString, CAutoOpUser*>::iterator it = m_msUsers.begin(); it != m_msUsers.end(); it++) {
|
||||
CAutoOpUser* pUser = it->second;
|
||||
|
||||
if (pUser->HostMatches(sHostmask) && (sChannel.empty() || pUser->ChannelMatches(sChannel))) {
|
||||
return pUser;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DelUser(const CString& sUser) {
|
||||
map<CString, CAutoOpUser*>::iterator it = m_msUsers.find(sUser.AsLower());
|
||||
|
||||
if (it == m_msUsers.end()) {
|
||||
PutModule("That user does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
delete it->second;
|
||||
m_msUsers.erase(it);
|
||||
PutModule("User [" + sUser + "] removed");
|
||||
}
|
||||
|
||||
CAutoOpUser* AddUser(const CString& sUser, const CString& sKey, const CString& sHost, const CString& sChans) {
|
||||
if (m_msUsers.find(sUser) != m_msUsers.end()) {
|
||||
PutModule("That user already exists");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CAutoOpUser* pUser = new CAutoOpUser(sUser, sKey, sHost, sChans);
|
||||
m_msUsers[sUser.AsLower()] = pUser;
|
||||
PutModule("User [" + sUser + "] added with hostmask [" + sHost + "]");
|
||||
return pUser;
|
||||
}
|
||||
|
||||
/* This isn't being used yet
|
||||
bool RequestOps(const CString& sChannel) {
|
||||
CChan* pChan = m_pUser->FindChan(sChannel);
|
||||
return (pChan) ? RequestOps(*pChan) : false;
|
||||
}
|
||||
|
||||
bool RequestOps(const CChan& Channel) {
|
||||
if (Channel.HasPerm(CChan::Op)) {
|
||||
return false; // If we already have ops then don't bother
|
||||
}
|
||||
|
||||
const map<CString,CNick*>& msNicks = Channel.GetNicks();
|
||||
VCString vsNicks;
|
||||
|
||||
for (map<CString,CNick*>::const_iterator it = msNicks.begin(); it != msNicks.end(); it++) {
|
||||
const CNick& Nick = *it->second;
|
||||
|
||||
if (Nick.HasPerm(CChan::Op)) { // Ok, this guy is an op, now lets make sure he matches one of our defined users
|
||||
for (map<CString, CAutoOpUser*>::iterator it = m_msUsers.begin(); it != m_msUsers.end(); it++) {
|
||||
cerr << "??? [" << Nick.GetHostMask() << "] [" << Channel.GetName() << "]" << endl;
|
||||
if (it->second->HostMatches(Nick.GetHostMask()) && it->second->ChannelMatches(Channel.GetName())) {
|
||||
vsNicks.push_back(Nick.GetNick()); // Add the nick into a vector so we chan choose one at random later
|
||||
break; // Ok, we found a match, on to the next op in the channel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!vsNicks.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Need to make this random in the future, just ask the first one for now
|
||||
PutIRC("NOTICE " + vsNicks[0] + " :!ZNCAO REQUEST " + Channel.GetName());
|
||||
|
||||
return true;
|
||||
}*/
|
||||
|
||||
bool ChallengeRespond(const CNick& Nick, const CString& sChallenge) {
|
||||
// Validate before responding - don't blindly trust everyone
|
||||
bool bValid = false;
|
||||
bool bMatchedHost = false;
|
||||
|
||||
for (map<CString, CAutoOpUser*>::iterator it = m_msUsers.begin(); it != m_msUsers.end(); it++) {
|
||||
CAutoOpUser* pUser = it->second;
|
||||
|
||||
// First verify that the guy who challenged us matches a user's host
|
||||
if (pUser->HostMatches(Nick.GetHostMask())) {
|
||||
const vector<CChan*>& Chans = m_pUser->GetChans();
|
||||
bMatchedHost = true;
|
||||
|
||||
// Also verify that they are opped in at least one of the user's chans
|
||||
for (size_t a = 0; a < Chans.size(); a++) {
|
||||
const CChan& Chan = *Chans[a];
|
||||
|
||||
CNick* pNick = Chan.FindNick(Nick.GetNick());
|
||||
|
||||
if (pNick) {
|
||||
if (pNick->HasPerm(CChan::Op) && pUser->ChannelMatches(Chan.GetName())) {
|
||||
bValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bValid) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!bValid) {
|
||||
if (bMatchedHost) {
|
||||
PutModule("[" + Nick.GetNick() + "] sent us a challenge but they are not opped in any defined channels.");
|
||||
} else {
|
||||
PutModule("[" + Nick.GetNick() + "] sent us a challenge but they do not match a defined user.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
CString sResponse = m_sKey + "::" + sChallenge;
|
||||
PutIRC("NOTICE " + Nick.GetNick() + " :!ZNCAO RESPONSE " + sResponse.MD5());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VerifyResponse(const CNick& Nick, const CString& sResponse) {
|
||||
MCString::iterator itQueue = m_msQueue.find(Nick.GetNick().AsLower());
|
||||
|
||||
if (itQueue == m_msQueue.end()) {
|
||||
PutModule("[" + Nick.GetNick() + "] sent an unchallenged response. This could be due to lag.");
|
||||
return false;
|
||||
}
|
||||
|
||||
CString sChallenge = itQueue->second;
|
||||
m_msQueue.erase(itQueue);
|
||||
|
||||
for (map<CString, CAutoOpUser*>::iterator it = m_msUsers.begin(); it != m_msUsers.end(); it++) {
|
||||
if (it->second->HostMatches(Nick.GetHostMask())) {
|
||||
if (sResponse == CString(it->second->GetUserKey() + "::" + sChallenge).MD5()) {
|
||||
OpUser(Nick, *it->second);
|
||||
return true;
|
||||
} else {
|
||||
PutModule("WARNING! [" + Nick.GetNick() + "] sent a bad response. Please verify that you have their correct password.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PutModule("WARNING! [" + Nick.GetNick() + "] sent a response but did not match any defined users.");
|
||||
return false;
|
||||
}
|
||||
|
||||
void ProcessQueue() {
|
||||
bool bRemoved = true;
|
||||
|
||||
// First remove any stale challenges
|
||||
|
||||
while (bRemoved) {
|
||||
bRemoved = false;
|
||||
|
||||
for (MCString::iterator it = m_msQueue.begin(); it != m_msQueue.end(); it++) {
|
||||
if (!it->second.empty()) {
|
||||
m_msQueue.erase(it);
|
||||
bRemoved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now issue challenges for the new users in the queue
|
||||
for (MCString::iterator it = m_msQueue.begin(); it != m_msQueue.end(); it++) {
|
||||
it->second = CString::RandomString(32);
|
||||
PutIRC("NOTICE " + it->first + " :!ZNCAO CHALLENGE " + it->second);
|
||||
}
|
||||
}
|
||||
|
||||
void OpUser(const CNick& Nick, const CAutoOpUser& User) {
|
||||
const vector<CChan*>& Chans = m_pUser->GetChans();
|
||||
|
||||
for (size_t a = 0; a < Chans.size(); a++) {
|
||||
const CChan& Chan = *Chans[a];
|
||||
|
||||
if (Chan.HasPerm(CChan::Op) && User.ChannelMatches(Chan.GetName())) {
|
||||
CNick* pNick = Chan.FindNick(Nick.GetNick());
|
||||
|
||||
if (pNick && !pNick->HasPerm(CChan::Op)) {
|
||||
PutIRC("MODE " + Chan.GetName() + " +o " + Nick.GetNick());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
map<CString, CAutoOpUser*> m_msUsers;
|
||||
CString m_sKey;
|
||||
MCString m_msQueue;
|
||||
};
|
||||
|
||||
void CAutoOpTimer::RunJob() {
|
||||
m_pParent->ProcessQueue();
|
||||
}
|
||||
|
||||
MODULEDEFS(CAutoOpMod, "Auto op the good guys")
|
||||
Reference in New Issue
Block a user