mirror of
https://github.com/znc/znc.git
synced 2026-05-09 23:04:47 +02:00
Import the route_replies module from znc-extra
This module fixes some issues if multiple clients are connected to the same ZNC user. ZNC itself would send replies to e.g. /who to all connected clients and this modules makes the reply go to the client which sent the request. Thanks to SilverLeo, tomaw and Vadtec for finding missing numerics. git-svn-id: https://znc.svn.sourceforge.net/svnroot/znc/trunk@1594 726aef4b-f618-498e-8847-2d620e286838
This commit is contained in:
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* Copyright (C) 2004-2009 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.h"
|
||||
#include "User.h"
|
||||
|
||||
struct reply {
|
||||
const char *szReply;
|
||||
bool bLastResponse;
|
||||
};
|
||||
|
||||
// TODO this list is far from complete, no errors are handled
|
||||
static const struct {
|
||||
const char *szRequest;
|
||||
struct reply vReplies[10];
|
||||
} vRouteReplies[] = {
|
||||
{"WHO", {
|
||||
{"352", false},
|
||||
{"403", true}, // No such chan
|
||||
{"315", true},
|
||||
{NULL, true}
|
||||
}},
|
||||
{"LIST", {
|
||||
{"321", false},
|
||||
{"322", false},
|
||||
{"323", true},
|
||||
{NULL, true}
|
||||
}},
|
||||
{"NAMES", {
|
||||
{"353", false},
|
||||
{"366", true},
|
||||
{NULL, true},
|
||||
}},
|
||||
{"LUSERS", {
|
||||
{"251", false},
|
||||
{"252", false},
|
||||
{"253", false},
|
||||
{"254", false},
|
||||
{"255", false},
|
||||
{"265", false},
|
||||
{"266", false},
|
||||
{"250", true},
|
||||
{NULL, true}
|
||||
}},
|
||||
{"WHOIS", {
|
||||
{"311", false},
|
||||
{"319", false},
|
||||
{"312", false},
|
||||
// "<ip> :actually using host"
|
||||
{"338", false},
|
||||
{"318", true},
|
||||
// No such nick/channel
|
||||
{"401", true},
|
||||
// No such server
|
||||
{"402", true},
|
||||
// Not enough params
|
||||
{"461", true},
|
||||
{NULL, true}
|
||||
}},
|
||||
{"PING", {
|
||||
{"PONG", true},
|
||||
{NULL, true}
|
||||
}},
|
||||
{"USERHOST", {
|
||||
{"302", true},
|
||||
{NULL, true}
|
||||
}},
|
||||
{"TIME", {
|
||||
{"391", true},
|
||||
{NULL, true}
|
||||
}},
|
||||
{"WHOWAS", {
|
||||
{"312", false},
|
||||
{"314", false},
|
||||
{"369", true},
|
||||
{NULL, true}
|
||||
}},
|
||||
{"ISON", {
|
||||
{"303", true},
|
||||
// Not enough parameters
|
||||
{"461", true},
|
||||
{NULL, true}
|
||||
}},
|
||||
{"LINKS", {
|
||||
{"364", false},
|
||||
{"365", true},
|
||||
{NULL, true}
|
||||
}},
|
||||
{"MAP", {
|
||||
{"006", false},
|
||||
// inspircd
|
||||
{"270", false},
|
||||
// SilverLeo wants this two added
|
||||
{"015", false},
|
||||
{"017", true},
|
||||
{"007", true},
|
||||
{NULL, true}
|
||||
}},
|
||||
{"TRACE", {
|
||||
{"200", false},
|
||||
{"205", false},
|
||||
{"262", true},
|
||||
{NULL, true}
|
||||
}},
|
||||
{"USERS", {
|
||||
{"265", false},
|
||||
{"266", true},
|
||||
{NULL, true},
|
||||
}},
|
||||
// END (last item!)
|
||||
{NULL, {{NULL, true}}}
|
||||
};
|
||||
|
||||
class CRouteTimeout : public CTimer {
|
||||
public:
|
||||
CRouteTimeout(CModule* pModule, unsigned int uInterval, unsigned int uCycles,
|
||||
const CString& sLabel, const CString& sDescription)
|
||||
: CTimer(pModule, uInterval, uCycles, sLabel, sDescription) {}
|
||||
virtual ~CRouteTimeout() {}
|
||||
|
||||
protected:
|
||||
virtual void RunJob();
|
||||
};
|
||||
|
||||
struct queued_req {
|
||||
CString sLine;
|
||||
const struct reply *reply;
|
||||
};
|
||||
|
||||
typedef std::map<CClient *, std::vector<struct queued_req> > requestQueue;
|
||||
|
||||
class CRouteRepliesMod : public CModule
|
||||
{
|
||||
public:
|
||||
MODCONSTRUCTOR(CRouteRepliesMod)
|
||||
{
|
||||
m_pDoing = NULL;
|
||||
m_pReplies = NULL;
|
||||
}
|
||||
|
||||
virtual ~CRouteRepliesMod() {
|
||||
requestQueue::iterator it;
|
||||
|
||||
while (!m_vsPending.empty()) {
|
||||
it = m_vsPending.begin();
|
||||
|
||||
while (!it->second.empty()) {
|
||||
PutIRC(it->second[0].sLine);
|
||||
it->second.erase(it->second.begin());
|
||||
}
|
||||
|
||||
m_vsPending.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void OnIRCConnected()
|
||||
{
|
||||
m_pDoing = NULL;
|
||||
m_pReplies = NULL;
|
||||
m_vsPending.clear();
|
||||
|
||||
// No way we get a reply, so stop the timer (If it's running)
|
||||
RemTimer("RouteTimeout");
|
||||
}
|
||||
|
||||
virtual void OnIRCDisconnected()
|
||||
{
|
||||
OnIRCConnected(); // Let's keep it in one place
|
||||
}
|
||||
|
||||
virtual void OnClientDisconnect()
|
||||
{
|
||||
requestQueue::iterator it;
|
||||
|
||||
if (m_pClient == m_pDoing) {
|
||||
// The replies which aren't received yet will be
|
||||
// broadcasted to everyone, but at least nothing breaks
|
||||
m_pDoing = NULL;
|
||||
m_pReplies = NULL;
|
||||
}
|
||||
|
||||
it = m_vsPending.find(m_pClient);
|
||||
|
||||
if (it == m_vsPending.end())
|
||||
return;
|
||||
|
||||
m_vsPending.erase(it);
|
||||
}
|
||||
|
||||
virtual EModRet OnRaw(CString& sLine)
|
||||
{
|
||||
CString sCmd = sLine.Token(1).AsUpper();
|
||||
size_t i = 0;
|
||||
|
||||
if (!m_pReplies)
|
||||
return CONTINUE;
|
||||
|
||||
while (m_pReplies[i].szReply != NULL) {
|
||||
if (m_pReplies[i].szReply == sCmd) {
|
||||
if (RouteReply(sLine, m_pReplies[i].bLastResponse))
|
||||
return HALTCORE;
|
||||
return CONTINUE;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
// TODO HALTCORE is wrong, it should not be passed to
|
||||
// the clients, but the core itself should still handle it!
|
||||
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
virtual EModRet OnUserRaw(CString& sLine)
|
||||
{
|
||||
CString sCmd = sLine.Token(0).AsUpper();
|
||||
size_t i = 0;
|
||||
|
||||
if (!m_pUser->GetIRCSock())
|
||||
return CONTINUE;
|
||||
|
||||
while (vRouteReplies[i].szRequest != NULL) {
|
||||
if (vRouteReplies[i].szRequest == sCmd) {
|
||||
|
||||
struct queued_req req = {
|
||||
sLine, vRouteReplies[i].vReplies
|
||||
};
|
||||
m_vsPending[m_pClient].push_back(req);
|
||||
SendRequest();
|
||||
|
||||
return HALTCORE;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
void Timeout()
|
||||
{
|
||||
// The timer will be deleted after this by the event loop
|
||||
|
||||
PutModule("This module hit a timeout. This is a BUG!");
|
||||
|
||||
PutModule("Last request: " + m_sLastRequest);
|
||||
|
||||
PutModule("Expected replies: ");
|
||||
|
||||
for (size_t i = 0; m_pReplies[i].szReply != NULL; i++) {
|
||||
if (m_pReplies[i].bLastResponse)
|
||||
PutModule(m_pReplies[i].szReply +
|
||||
CString(" (last)"));
|
||||
else
|
||||
PutModule(m_pReplies[i].szReply);
|
||||
}
|
||||
|
||||
m_pDoing = NULL;
|
||||
m_pReplies = NULL;
|
||||
SendRequest();
|
||||
}
|
||||
|
||||
private:
|
||||
bool RouteReply(const CString& sLine, bool bFinished = false)
|
||||
{
|
||||
if (!m_pDoing)
|
||||
return false;
|
||||
|
||||
m_pDoing->PutClient(sLine);
|
||||
|
||||
if (bFinished) {
|
||||
// Stop the timeout
|
||||
RemTimer("RouteTimeout");
|
||||
|
||||
m_pDoing = NULL;
|
||||
m_pReplies = NULL;
|
||||
SendRequest();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SendRequest()
|
||||
{
|
||||
requestQueue::iterator it;
|
||||
|
||||
if (m_pDoing || m_pReplies)
|
||||
return;
|
||||
|
||||
if (m_vsPending.empty())
|
||||
return;
|
||||
|
||||
it = m_vsPending.begin();
|
||||
|
||||
if (it->second.empty()) {
|
||||
m_vsPending.erase(it);
|
||||
SendRequest();
|
||||
return;
|
||||
}
|
||||
|
||||
// When we are called from the timer, we need to remove it.
|
||||
// We can't delete it (segfault on return), thus we
|
||||
// just stop it. The main loop will delete it.
|
||||
CTimer *pTimer = FindTimer("RouteTimeout");
|
||||
if (pTimer) {
|
||||
pTimer->Stop();
|
||||
UnlinkTimer(pTimer);
|
||||
}
|
||||
AddTimer(new CRouteTimeout(this, 20, 1, "RouteTimeout",
|
||||
"Recover from missing / wrong server replies"));
|
||||
|
||||
m_pDoing = it->first;
|
||||
m_pReplies = it->second[0].reply;
|
||||
m_sLastRequest = it->second[0].sLine;
|
||||
PutIRC(it->second[0].sLine);
|
||||
it->second.erase(it->second.begin());
|
||||
}
|
||||
|
||||
CClient *m_pDoing;
|
||||
const struct reply *m_pReplies;
|
||||
requestQueue m_vsPending;
|
||||
// This field is only used for display purpose.
|
||||
CString m_sLastRequest;
|
||||
};
|
||||
|
||||
void CRouteTimeout::RunJob()
|
||||
{
|
||||
CRouteRepliesMod *pMod = (CRouteRepliesMod *) m_pModule;
|
||||
pMod->Timeout();
|
||||
}
|
||||
|
||||
MODULEDEFS(CRouteRepliesMod, "Send replies (e.g. to /who) to the right client only")
|
||||
Reference in New Issue
Block a user