Fixes to auth detection and message parsing

* don't check authentication if the network doesn't need to
  register
* don't pass through muser for ZNC type messages
* avoid duplicate message for queries containing highlights
* make a copy of the cast for metadata analysis to avoid poisoning it
* set up callback for when the instance is authenticated, so we can
  request a LIST immediately if so desired
* separate out seeding functions to populate CHANLIMIT to ease future
  work involving other options, such as PREFIX
This commit is contained in:
Mark Veidemanis 2020-06-07 17:26:53 +01:00
parent 2a9869d0f9
commit 3acf182171
5 changed files with 116 additions and 85 deletions

View File

@ -125,9 +125,9 @@ class IRCRelay(IRCClient):
class IRCBot(IRCClient): class IRCBot(IRCClient):
def __init__(self, net, num): def __init__(self, net, num):
self.isconnected = False self.isconnected = False
self.authenticated = False
self.channels = [] self.channels = []
self.net = net self.net = net
self.authenticated = not regproc.needToRegister(self.net)
self.num = num self.num = num
self.buffer = "" self.buffer = ""
self.name = net + str(num) self.name = net + str(num)
@ -230,6 +230,7 @@ class IRCBot(IRCClient):
del cast["ident"] del cast["ident"]
del cast["host"] del cast["host"]
del cast["channel"] del cast["channel"]
del cast["muser"]
if "Disconnected from IRC" in cast["msg"]: if "Disconnected from IRC" in cast["msg"]:
log("ZNC disconnected on %s - %i" % (self.net, self.num)) log("ZNC disconnected on %s - %i" % (self.net, self.num))
self.isconnected = False self.isconnected = False
@ -244,16 +245,16 @@ class IRCBot(IRCClient):
cast["num"] = self.num cast["num"] = self.num
if "channel" in cast.keys(): if "channel" in cast.keys():
if cast["type"] == "mode": if cast["type"] == "mode":
if self.nickname.lower() == cast["channel"].lower(): if cast["channel"].lower() == self.nickname.lower():
castDup = deepcopy(cast) #castDup = deepcopy(cast)
castDup["mtype"] = cast["type"] cast["mtype"] = cast["type"]
castDup["type"] = "self" cast["type"] = "self"
self.event(**castDup) #self.event(**castDup)
if cast["modearg"]: if cast["modearg"]: # check if modearg is non-NoneType
if self.nickname.lower() == cast["modearg"].lower(): if self.nickname.lower() == cast["modearg"].lower():
castDup = deepcopy(cast) castDup = deepcopy(cast)
castDup["mtype"] = cast["type"] castDup["mtype"] = cast["type"]
castDup["type"] = "self" castDup["type"] = "highlight"
self.event(**castDup) self.event(**castDup)
else: else:
if cast["channel"].lower() == self.nickname.lower(): if cast["channel"].lower() == self.nickname.lower():
@ -263,34 +264,39 @@ class IRCBot(IRCClient):
# Don't call self.event for this one because queries are not events on a # Don't call self.event for this one because queries are not events on a
# channel, but we still want to see them # channel, but we still want to see them
# we have been kicked # TODO: better way to do this
if "user" in cast.keys(): # as we changed the types above, check again
if cast["user"].lower() == self.nickname.lower(): if not cast["type"] in {"query", "self", "highlight", "znc", "who"}:
castDup = deepcopy(cast) # we have been kicked
castDup["mtype"] = cast["type"] if "user" in cast.keys():
castDup["type"] = "self" if cast["user"].lower() == self.nickname.lower():
self.event(**castDup)
# we sent a message/left/joined/kick someone/quit
if "nick" in cast.keys():
if cast["nick"].lower() == self.nickname.lower():
castDup = deepcopy(cast)
castDup["mtype"] = cast["type"]
castDup["type"] = "self"
# we have been mentioned in a msg/notice/action/part/quit/topic message
if "msg" in cast.keys(): # Don't highlight queries
if not cast["msg"] == None:
if self.nickname.lower() in cast["msg"].lower():
castDup = deepcopy(cast) castDup = deepcopy(cast)
castDup["mtype"] = cast["type"] castDup["mtype"] = cast["type"]
castDup["type"] = "highlight" castDup["type"] = "self"
self.event(**castDup) self.event(**castDup)
# we sent a message/left/joined/kick someone/quit
if "nick" in cast.keys():
if cast["nick"].lower() == self.nickname.lower():
castDup = deepcopy(cast)
castDup["mtype"] = cast["type"]
castDup["type"] = "self"
# we have been mentioned in a msg/notice/action/part/quit/topic message
if "msg" in cast.keys(): # Don't highlight queries
if not cast["msg"] == None:
if self.nickname.lower() in cast["msg"].lower():
castDup = deepcopy(cast)
castDup["mtype"] = cast["type"]
castDup["type"] = "highlight"
self.event(**castDup)
if not "net" in cast.keys(): if not "net" in cast.keys():
cast["net"] = self.net cast["net"] = self.net
if not "num" in cast.keys(): if not "num" in cast.keys():
cast["num"] = self.num cast["num"] = self.num
if not self.authenticated:
regproc.registerTest(cast)
counters.event(self.net, cast["type"]) counters.event(self.net, cast["type"])
monitor.event(self.net, cast) monitor.event(self.net, cast)
@ -429,8 +435,8 @@ class IRCBot(IRCClient):
return d return d
def list(self, noargs=False, nocheck=False): def list(self, noargs=False, nocheck=False):
if not main.network[self.net].relays[self.num]["registered"]: if not self.authenticated:
debug("Will not send LIST, unregistered: %s - %i" % (self.net, self.num)) debug("Will not send LIST, unauthenticated: %s - %i" % (self.net, self.num))
return return
if self.listAttempted: if self.listAttempted:
debug("List request dropped, already asked for LIST - %s - %i" % (self.net, self.num)) debug("List request dropped, already asked for LIST - %s - %i" % (self.net, self.num))
@ -496,45 +502,15 @@ class IRCBot(IRCClient):
return return
chankeep.initialList(self.net, self.num, listinfo, self.chanlimit) chankeep.initialList(self.net, self.num, listinfo, self.chanlimit)
def isupport(self, options): def recheckList(self):
interested = ("CHANLIMIT", "MAXCHANNELS") print("list being rechecked")
if not any((x for x in options if any(y in x for y in interested))):
return # check if any of interested is in any of options, some networks
chanlimit = None # call isupport() more than once, so discard swiftly anything
if not self.isconnected: # we don't care about
log("endpoint connected: %s - %i" % (self.net, self.num))
self.isconnected = True
if not main.network[self.net].relays[self.num]["registered"]:
if main.config["AutoReg"]:
self._regAttempt = reactor.callLater(5, regproc.registerAccount, self.net, self.num)
#regproc.registerAccount(self.net, self.num)
for i in options:
if i.startswith("CHANLIMIT"):
if ":" in i:
split = i.split(":")
if len(split) >= 2:
chanlimit = split[1]
break
elif i.startswith("MAXCHANNELS"):
if "=" in i:
split = i.split("=")
if len(split) == 2:
chanlimit = split[1]
break
try:
self.chanlimit = int(chanlimit)
except TypeError:
warn("Invalid chanlimit: %s" % i)
if self.chanlimit == 0:
self.chanlimit = 200 # don't take the piss if it's not limited
allRelays = chankeep.allRelaysActive(self.net) allRelays = chankeep.allRelaysActive(self.net)
if allRelays: if allRelays:
for i in main.network.keys(): print("allrelays now passed")
for x in main.network[i].relays.keys(): name = self.net+"1"
name = i+str(x) if main.IRCPool[name].wantList == True:
if main.IRCPool[name].wantList == True: main.IRCPool[name].list(nocheck=True)
main.IRCPool[name].list(nocheck=True) debug("Asking for a list for %s after final relay %i connected" % (self.net, self.num))
debug("Asking for a list for %s after final relay %i connected" % (self.net, self.num))
if self.num == 1: # Only one instance should do a list if self.num == 1: # Only one instance should do a list
if self.chanlimit: if self.chanlimit:
if allRelays: if allRelays:
@ -545,6 +521,51 @@ class IRCBot(IRCClient):
debug("Aborting LIST due to bad chanlimit") debug("Aborting LIST due to bad chanlimit")
self.checkChannels() self.checkChannels()
def seed_chanlimit(self, chanlimit):
if not main.network[self.net].relays[self.num]["registered"]: #TODO: add check for register request sent, only send it once
if main.config["AutoReg"]:
self._regAttempt = reactor.callLater(5, regproc.registerAccount, self.net, self.num)
#regproc.registerAccount(self.net, self.num)
try:
self.chanlimit = int(chanlimit)
except TypeError:
warn("Invalid chanlimit: %s" % i)
if self.chanlimit == 0:
self.chanlimit = 200 # don't take the piss if it's not limited
if not regproc.needToRegister(self.net): # if we need to register, only recheck on auth confirmation
self.recheckList()
def seed_prefix(self, prefix):
print("PREFIX", prefix)
def isupport(self, options):
interested = {"CHANLIMIT", "MAXCHANNELS", "PREFIX"}
newOptions = {x for x in options if any(y in x for y in interested)}
if len(newOptions) == 0:
return
if not self.isconnected:
log("endpoint connected: %s - %i" % (self.net, self.num))
self.isconnected = True
for i in newOptions:
if i.startswith("PREFIX"):
if "=" in i:
split = i.split("=")
if len(split) == 2:
prefix = split[1]
self.seed_prefix(prefix)
elif i.startswith("CHANLIMIT"):
if ":" in i:
split = i.split(":")
if len(split) >= 2:
chanlimit = split[1]
self.seed_chanlimit(chanlimit)
elif i.startswith("MAXCHANNELS"):
if "=" in i:
split = i.split("=")
if len(split) == 2:
chanlimit = split[1]
self.seed_chanlimit(chanlimit)
# We need to override these functions as Twisted discards # We need to override these functions as Twisted discards
# the hostname and other useful information in the functions # the hostname and other useful information in the functions
# that these call by default # that these call by default

View File

@ -12,7 +12,7 @@ def allRelaysActive(net):
for i in main.network[net].relays.keys(): for i in main.network[net].relays.keys():
name = net+str(i) name = net+str(i)
if name in main.IRCPool.keys(): if name in main.IRCPool.keys():
if main.IRCPool[name].authenticated and main.network[net].relays[i]["registered"]: if main.IRCPool[name].authenticated:
existNum += 1 existNum += 1
if existNum == relayNum: if existNum == relayNum:
return True return True

View File

@ -11,13 +11,9 @@ order = ["type", "net", "num", "channel", "msg", "nick",
"ident", "host", "mtype", "user", "mode", "modearg", "ident", "host", "mtype", "user", "mode", "modearg",
"realname", "server", "status", "time"] "realname", "server", "status", "time"]
def event(numName, c): # yes I'm using a short variable because otherwise it goes off the screen def parsemeta(numName, c):
if not "channel" in c.keys(): if not "channel" in c.keys():
c["channel"] = None c["channel"] = None
if dedup(numName, c):
return
regproc.registerTest(c)
# metadata scraping # metadata scraping
# need to check if this was received from a relay # need to check if this was received from a relay
# in which case, do not do this # in which case, do not do this
@ -39,7 +35,13 @@ def event(numName, c): # yes I'm using a short variable because otherwise it goe
if c["mtype"] == "nick": if c["mtype"] == "nick":
userinfo.renameUser(c["net"], c["nick"], c["muser"], c["user"], c["user"]+"!"+c["ident"]+"@"+c["host"]) userinfo.renameUser(c["net"], c["nick"], c["muser"], c["user"], c["user"]+"!"+c["ident"]+"@"+c["host"])
def event(numName, c): # yes I'm using a short variable because otherwise it goes off the screen
if dedup(numName, c):
return
# make a copy of the object with dict() to prevent sending notifications with channel of None
parsemeta(numName, dict(c))
if "muser" in c.keys(): if "muser" in c.keys():
del c["muser"] del c["muser"]
sendRelayNotification({k: c[k] for k in order if k in c}) # Sort dict keys according to order sendRelayNotification({k: c[k] for k in order if k in c}) # Sort dict keys according to order

View File

@ -3,6 +3,7 @@ import json
from modules import alias from modules import alias
from modules.chankeep import nukeNetwork from modules.chankeep import nukeNetwork
from modules.regproc import needToRegister
from twisted.internet import reactor from twisted.internet import reactor
from core.bot import IRCBot, IRCBotFactory from core.bot import IRCBot, IRCBotFactory
import main import main
@ -28,13 +29,10 @@ class Network:
elif num == self.last: elif num == self.last:
self.last += 1 self.last += 1
registered = False registered = False
if self.net in main.irc.keys(): if not needToRegister(self.net):
if "register" in main.irc[self.net].keys(): registered = True
if not main.irc[self.net]["register"]: # Don't need to register if it's been disabled in definitions,
registered = True # so we'll pretend we already did
# Don't need to register if it's been disabled in definitions,
# so we'll pretend we already did
self.relays[num] = { self.relays[num] = {
"enabled": main.config["ConnectOnCreate"], "enabled": main.config["ConnectOnCreate"],
"net": self.net, "net": self.net,

View File

@ -4,6 +4,14 @@ from utils.logging.log import *
from utils.logging.debug import * from utils.logging.debug import *
from copy import deepcopy from copy import deepcopy
def needToRegister(net):
inst = selectInst(net)
if "register" in inst.keys():
if inst["register"]:
return True
else:
return False
def selectInst(net): def selectInst(net):
if net in main.irc.keys(): if net in main.irc.keys():
inst = deepcopy(main.irc[net]) inst = deepcopy(main.irc[net])
@ -48,6 +56,7 @@ def confirmRegistration(net, num):
if name in main.IRCPool.keys(): if name in main.IRCPool.keys():
debug("Relay authenticated: %s - %i" %(net, num)) debug("Relay authenticated: %s - %i" %(net, num))
main.IRCPool[name].authenticated = True main.IRCPool[name].authenticated = True
main.IRCPool[name].recheckList()
if obj.relays[num]["registered"]: if obj.relays[num]["registered"]:
return return
if name in main.IRCPool.keys(): if name in main.IRCPool.keys():
@ -78,7 +87,8 @@ def registerTest(c):
confirmRegistration(c["net"], c["num"]) confirmRegistration(c["net"], c["num"])
return return
elif inst["checktype"] == "mode": elif inst["checktype"] == "mode":
if c["type"] == "mode": if c["type"] == "self":
if inst["checkmode"] in c["modes"] and c["status"] == True: if c["mtype"] == "mode":
confirmRegistration(c["net"], c["num"]) if inst["checkmode"] in c["mode"] and c["status"] == True:
return confirmRegistration(c["net"], c["num"])
return