Add IRCv3→SASL support for nickserv auth, added optional module whitelist, added server option for recording channel keys, increased flexibility for event raising in linehandler, probably other things too
This commit is contained in:
parent
81edacfba6
commit
e232ad5dae
5 changed files with 140 additions and 42 deletions
|
@ -8,32 +8,43 @@ RE_CHANTYPES = re.compile(r"\bCHANTYPES=(\W+)(?:\b|$)")
|
||||||
|
|
||||||
handlers = {}
|
handlers = {}
|
||||||
descriptions = {}
|
descriptions = {}
|
||||||
|
default_events = {}
|
||||||
current_description = None
|
current_description = None
|
||||||
|
current_default_event = False
|
||||||
handle_lock = threading.Lock()
|
handle_lock = threading.Lock()
|
||||||
line, line_split, bot, server = None, None, None, None
|
line, line_split, bot, server = None, None, None, None
|
||||||
|
|
||||||
def handler(f=None, description=None):
|
def handler(f=None, description=None, default_event=False):
|
||||||
global current_description
|
global current_description, current_default_event
|
||||||
if description:
|
if not f:
|
||||||
current_description = description
|
current_description = description
|
||||||
|
current_default_event = default_event
|
||||||
return handler
|
return handler
|
||||||
name = f.__name__.split("handle_")[1].upper()
|
name = f.__name__.split("handle_")[1].upper()
|
||||||
handlers[name] = f
|
handlers[name] = f
|
||||||
if current_description:
|
|
||||||
descriptions[name] = current_description
|
descriptions[name] = current_description
|
||||||
current_description = None
|
default_events[name] = current_default_event
|
||||||
|
current_description, current_default_event = None, False
|
||||||
def handle(_line, _line_split, _bot, _server):
|
def handle(_line, _line_split, _bot, _server):
|
||||||
global line, line_split, bot, server
|
global line, line_split, bot, server
|
||||||
handler_function = None
|
handler_function = None
|
||||||
|
|
||||||
if len(_line_split) > 1:
|
if len(_line_split) > 1:
|
||||||
|
name = _line_split[1]
|
||||||
if _line_split[0][0] == ":":
|
if _line_split[0][0] == ":":
|
||||||
if _line_split[1] in handlers:
|
if name in handlers:
|
||||||
handler_function = handlers[_line_split[1]]
|
handler_function = handlers[name]
|
||||||
elif _line_split[1].isdigit():
|
if default_events.get(name, False) or not name in handlers:
|
||||||
_bot.events.on("received").on("numeric").on(
|
if name.isdigit():
|
||||||
_line_split[1]).call(line=_line,
|
_bot.events.on("received").on("numeric").on(
|
||||||
line_split=_line_split, server=_server,
|
name).call(line=_line,
|
||||||
number=_line_split[1])
|
line_split=_line_split, server=_server,
|
||||||
|
number=name)
|
||||||
|
else:
|
||||||
|
_bot.events.on("received").on(name).call(
|
||||||
|
line=_line, line_split=_line_split, server=_server,
|
||||||
|
command=name)
|
||||||
elif _line_split[0] in handlers:
|
elif _line_split[0] in handlers:
|
||||||
handler_function = handlers[_line_split[0]]
|
handler_function = handlers[_line_split[0]]
|
||||||
if handler_function:
|
if handler_function:
|
||||||
|
@ -48,12 +59,10 @@ def handle_PING():
|
||||||
server.send_pong(Utils.remove_colon(line_split[1]))
|
server.send_pong(Utils.remove_colon(line_split[1]))
|
||||||
bot.events.on("received").on("ping").call(line=line,
|
bot.events.on("received").on("ping").call(line=line,
|
||||||
line_split=line_split, server=server, nonce=nonce)
|
line_split=line_split, server=server, nonce=nonce)
|
||||||
@handler(description="the first line sent to a registered client")
|
@handler(description="the first line sent to a registered client", default_event=True)
|
||||||
def handle_001():
|
def handle_001():
|
||||||
server.set_own_nickname(line_split[2])
|
server.set_own_nickname(line_split[2])
|
||||||
server.send_whois(server.nickname)
|
server.send_whois(server.nickname)
|
||||||
bot.events.on("received").on("numeric").on("001").call(
|
|
||||||
line=line, line_split=line_split, server=server)
|
|
||||||
@handler(description="the extra supported things line")
|
@handler(description="the extra supported things line")
|
||||||
def handle_005():
|
def handle_005():
|
||||||
isupport_line = Utils.arbitrary(line_split, 3)
|
isupport_line = Utils.arbitrary(line_split, 3)
|
||||||
|
@ -74,8 +83,8 @@ def handle_005():
|
||||||
server.channel_types = list(match.group(1))
|
server.channel_types = list(match.group(1))
|
||||||
bot.events.on("received").on("numeric").on("005").call(
|
bot.events.on("received").on("numeric").on("005").call(
|
||||||
line=line, line_split=line_split, server=server,
|
line=line, line_split=line_split, server=server,
|
||||||
isupport=isupport_line)
|
isupport=isupport_line, number="005")
|
||||||
@handler(description="whois respose (nickname, username, realname, hostname)")
|
@handler(description="whois respose (nickname, username, realname, hostname)", default_event=True)
|
||||||
def handle_311():
|
def handle_311():
|
||||||
nickname = line_split[3]
|
nickname = line_split[3]
|
||||||
if server.is_own_nickname(nickname):
|
if server.is_own_nickname(nickname):
|
||||||
|
@ -86,12 +95,12 @@ def handle_311():
|
||||||
hostname = line_split[5]
|
hostname = line_split[5]
|
||||||
target.username = username
|
target.username = username
|
||||||
target.hostname = hostname
|
target.hostname = hostname
|
||||||
@handler(description="on-join channel topic line")
|
@handler(description="on-join channel topic line", default_event=True)
|
||||||
def handle_332():
|
def handle_332():
|
||||||
channel = server.get_channel(line_split[3])
|
channel = server.get_channel(line_split[3])
|
||||||
topic = Utils.arbitrary(line_split, 4)
|
topic = Utils.arbitrary(line_split, 4)
|
||||||
channel.set_topic(topic)
|
channel.set_topic(topic)
|
||||||
@handler(description="on-join channel topic set by/at")
|
@handler(description="on-join channel topic set by/at", default_event=True)
|
||||||
def handle_333():
|
def handle_333():
|
||||||
channel = server.get_channel(line_split[3])
|
channel = server.get_channel(line_split[3])
|
||||||
topic_setter_hostmask = line_split[4]
|
topic_setter_hostmask = line_split[4]
|
||||||
|
@ -101,7 +110,7 @@ def handle_333():
|
||||||
) else None
|
) else None
|
||||||
channel.set_topic_setter(nickname, username, hostname)
|
channel.set_topic_setter(nickname, username, hostname)
|
||||||
channel.set_topic_time(topic_time)
|
channel.set_topic_time(topic_time)
|
||||||
@handler(description="on-join user list with status symbols")
|
@handler(description="on-join user list with status symbols", default_event=True)
|
||||||
def handle_353():
|
def handle_353():
|
||||||
channel = server.get_channel(line_split[4])
|
channel = server.get_channel(line_split[4])
|
||||||
nicknames = line_split[5:]
|
nicknames = line_split[5:]
|
||||||
|
@ -157,7 +166,7 @@ def handle_PART():
|
||||||
bot.events.on("self").on("part").call(line=line,
|
bot.events.on("self").on("part").call(line=line,
|
||||||
line_split=line_split, server=server, channel=channel,
|
line_split=line_split, server=server, channel=channel,
|
||||||
reason=reason)
|
reason=reason)
|
||||||
@handler(description="unknown command sent by us, oops!")
|
@handler(description="unknown command sent by us, oops!", default_event=True)
|
||||||
def handle_421():
|
def handle_421():
|
||||||
print("warning: unknown command '%s'." % line_split[3])
|
print("warning: unknown command '%s'." % line_split[3])
|
||||||
@handler(description="a user has disconnected!")
|
@handler(description="a user has disconnected!")
|
||||||
|
@ -172,6 +181,31 @@ def handle_QUIT():
|
||||||
user=user)
|
user=user)
|
||||||
else:
|
else:
|
||||||
server.disconnect()
|
server.disconnect()
|
||||||
|
|
||||||
|
@handler(description="The server is telling us about its capabilities!")
|
||||||
|
def handle_CAP():
|
||||||
|
_line = line
|
||||||
|
_line_split = line_split
|
||||||
|
if line.startswith(":"):
|
||||||
|
_line = " ".join(line_split[1:])
|
||||||
|
_line_split = _line.split()
|
||||||
|
|
||||||
|
capability_list = Utils.arbitrary(_line_split, 3).split()
|
||||||
|
bot.events.on("received").on("cap").call(line=line,
|
||||||
|
line_split=line_split, server=server,
|
||||||
|
nickname=_line_split[1], subcommand=_line_split[2],
|
||||||
|
capabilities=capability_list)
|
||||||
|
|
||||||
|
@handler(description="The server is asking for authentication")
|
||||||
|
def handle_AUTHENTICATE():
|
||||||
|
_line_split = line_split
|
||||||
|
if line.startswith(":"):
|
||||||
|
_line_split = line_split[1:]
|
||||||
|
bot.events.on("received").on("authenticate").call(line=line,
|
||||||
|
line_split=line_split, server=server,
|
||||||
|
message=Utils.arbitrary(_line_split, 1)
|
||||||
|
)
|
||||||
|
|
||||||
@handler(description="someone has changed their nickname")
|
@handler(description="someone has changed their nickname")
|
||||||
def handle_NICK():
|
def handle_NICK():
|
||||||
nickname, username, hostname = Utils.seperate_hostmask(line_split[0])
|
nickname, username, hostname = Utils.seperate_hostmask(line_split[0])
|
||||||
|
@ -269,12 +303,12 @@ def handle_PRIVMSG():
|
||||||
user=user, message=message, message_split=message_split,
|
user=user, message=message, message_split=message_split,
|
||||||
action=action)
|
action=action)
|
||||||
user.log.add_line(user.nickname, message, action)
|
user.log.add_line(user.nickname, message, action)
|
||||||
@handler(description="response to a WHO command for user information")
|
@handler(description="response to a WHO command for user information", default_event=True)
|
||||||
def handle_352():
|
def handle_352():
|
||||||
user = server.get_user(line_split[7])
|
user = server.get_user(line_split[7])
|
||||||
user.username = line_split[4]
|
user.username = line_split[4]
|
||||||
user.hostname = line_split[5]
|
user.hostname = line_split[5]
|
||||||
@handler(description="response to an empty mode command")
|
@handler(description="response to an empty mode command", default_event=True)
|
||||||
def handle_324():
|
def handle_324():
|
||||||
channel = server.get_channel(line_split[3])
|
channel = server.get_channel(line_split[3])
|
||||||
modes = line_split[4]
|
modes = line_split[4]
|
||||||
|
@ -282,14 +316,14 @@ def handle_324():
|
||||||
for mode in modes[1:]:
|
for mode in modes[1:]:
|
||||||
if mode in server.channel_modes:
|
if mode in server.channel_modes:
|
||||||
channel.add_mode(mode)
|
channel.add_mode(mode)
|
||||||
@handler(description="channel creation unix timestamp")
|
@handler(description="channel creation unix timestamp", default_event=True)
|
||||||
def handle_329():
|
def handle_329():
|
||||||
channel = server.get_channel(line_split[3])
|
channel = server.get_channel(line_split[3])
|
||||||
channel.creation_timestamp = int(line_split[4])
|
channel.creation_timestamp = int(line_split[4])
|
||||||
@handler(description="nickname already in use")
|
@handler(description="nickname already in use", default_event=True)
|
||||||
def handle_433():
|
def handle_433():
|
||||||
pass
|
pass
|
||||||
@handler(description="we need a registered nickname for this channel")
|
@handler(description="we need a registered nickname for this channel", default_event=True)
|
||||||
def handle_477():
|
def handle_477():
|
||||||
bot.add_timer("rejoin", 5, channel_name=line_split[3],
|
bot.add_timer("rejoin", 5, channel_name=line_split[3],
|
||||||
key=server.attempted_join[line_split[3].lower()],
|
key=server.attempted_join[line_split[3].lower()],
|
||||||
|
|
14
IRCServer.py
14
IRCServer.py
|
@ -52,8 +52,14 @@ class Server(object):
|
||||||
return self.cached_fileno if fileno == -1 else fileno
|
return self.cached_fileno if fileno == -1 else fileno
|
||||||
def connect(self):
|
def connect(self):
|
||||||
self.socket.connect((self.target_hostname, self.port))
|
self.socket.connect((self.target_hostname, self.port))
|
||||||
|
|
||||||
if self.password:
|
if self.password:
|
||||||
self.send_pass(self.password)
|
self.send_pass(self.password)
|
||||||
|
# In principle, this belongs in the NS module. In reality, it's more practical to put this
|
||||||
|
# One-off case here for SASL
|
||||||
|
if "Nickserv" in self.bot.modules.modules and self.get_setting("nickserv-password"):
|
||||||
|
self.send_capability_request("sasl")
|
||||||
|
|
||||||
self.send_user(self.original_username, self.original_realname)
|
self.send_user(self.original_username, self.original_realname)
|
||||||
self.send_nick(self.original_nickname)
|
self.send_nick(self.original_nickname)
|
||||||
self.connected = True
|
self.connected = True
|
||||||
|
@ -172,6 +178,14 @@ class Server(object):
|
||||||
self.send("USER %s - - :%s" % (username, realname))
|
self.send("USER %s - - :%s" % (username, realname))
|
||||||
def send_nick(self, nickname):
|
def send_nick(self, nickname):
|
||||||
self.send("NICK %s" % nickname)
|
self.send("NICK %s" % nickname)
|
||||||
|
|
||||||
|
def send_capability_request(self, capname):
|
||||||
|
self.send("CAP REQ :%s" % capname)
|
||||||
|
def send_capability_end(self):
|
||||||
|
self.send("CAP END")
|
||||||
|
def send_authenticate(self, text):
|
||||||
|
self.send("AUTHENTICATE %s" % text)
|
||||||
|
|
||||||
def send_pass(self, password):
|
def send_pass(self, password):
|
||||||
self.send("PASS %s" % password)
|
self.send("PASS %s" % password)
|
||||||
def send_ping(self, nonce="hello"):
|
def send_ping(self, nonce="hello"):
|
||||||
|
|
|
@ -14,6 +14,10 @@ class ModuleManager(object):
|
||||||
|
|
||||||
def _load_module(self, filename):
|
def _load_module(self, filename):
|
||||||
name = self.module_name(filename)
|
name = self.module_name(filename)
|
||||||
|
|
||||||
|
whitelist = self.bot.config.get("module_whitelist", [])
|
||||||
|
if whitelist and name not in whitelist: return
|
||||||
|
|
||||||
with open(filename) as module_file:
|
with open(filename) as module_file:
|
||||||
while True:
|
while True:
|
||||||
line = module_file.readline().strip()
|
line = module_file.readline().strip()
|
||||||
|
@ -61,6 +65,7 @@ class ModuleManager(object):
|
||||||
if name in self.waiting_requirement:
|
if name in self.waiting_requirement:
|
||||||
for filename in self.waiting_requirement:
|
for filename in self.waiting_requirement:
|
||||||
self.load_module(filename)
|
self.load_module(filename)
|
||||||
|
sys.stderr.write("module '%s' loaded.\n" % filename)
|
||||||
else:
|
else:
|
||||||
sys.stderr.write("module '%s' not loaded.\n" % filename)
|
sys.stderr.write("module '%s' not loaded.\n" % filename)
|
||||||
def load_modules(self):
|
def load_modules(self):
|
||||||
|
|
|
@ -1,18 +1,29 @@
|
||||||
|
|
||||||
|
|
||||||
class Module(object):
|
class Module(object):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
bot.events.on("self").on("join").hook(self.on_join)
|
bot.events.on("self").on("part").hook(self.on_self_part)
|
||||||
bot.events.on("received").on("numeric").on("366").hook(
|
bot.events.on("self").on("join").hook(self.on_join)
|
||||||
self.on_connect)
|
bot.events.on("received").on("numeric").on("366").hook(
|
||||||
|
self.on_identify_trigger)
|
||||||
|
bot.events.on("received").on("numeric").on("001").hook(
|
||||||
|
self.on_identify_trigger)
|
||||||
|
|
||||||
def on_join(self, event):
|
def on_self_part(self, event):
|
||||||
channels = set(event["server"].get_setting("autojoin", []))
|
pass
|
||||||
channels.add(event["channel"].name)
|
|
||||||
event["server"].set_setting("autojoin", list(channels))
|
|
||||||
|
|
||||||
def on_connect(self, event):
|
def on_join(self, event):
|
||||||
if event["line_split"][3].lower() == "#bitbot":
|
channels = set(event["server"].get_setting("autojoin", []))
|
||||||
channels = event["server"].get_setting("autojoin", [])
|
channels.add(event["channel"].name)
|
||||||
for channel in channels:
|
event["server"].set_setting("autojoin", list(channels))
|
||||||
event["server"].send_join(channel)
|
|
||||||
|
def on_identify_trigger(self, event):
|
||||||
|
if event["number"]=="001" and not event["server"].sasl_success: return
|
||||||
|
if event["line_split"][3].lower() == "#bitbot" or event["number"]=="001":
|
||||||
|
channels = event["server"].get_setting("autojoin", [])
|
||||||
|
chan_keys = event["server"].get_setting("channel_keys", {})
|
||||||
|
for channel in channels:
|
||||||
|
if channel in chan_keys:
|
||||||
|
event["server"].send_join(channel, key=chan_keys[channel])
|
||||||
|
else:
|
||||||
|
event["server"].send_join(channel)
|
||||||
|
|
|
@ -1,18 +1,28 @@
|
||||||
|
import base64
|
||||||
|
|
||||||
class Module(object):
|
class Module(object):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
|
bot.events.on("new").on("server").hook(self.on_new_server)
|
||||||
bot.events.on("received").on("numeric").on("001"
|
bot.events.on("received").on("numeric").on("001"
|
||||||
).hook(self.on_connect)
|
).hook(self.on_connect)
|
||||||
bot.events.on("received").on("command").on("setnickserv"
|
bot.events.on("received").on("command").on("setnickserv"
|
||||||
).hook(self.set_nickserv, min_args=1, permission="setnickserv",
|
).hook(self.set_nickserv, min_args=1, permission="setnickserv",
|
||||||
help="Set bot's nickserv password", usage="<password>",
|
help="Set bot's nickserv password", usage="<password>",
|
||||||
private_only=True)
|
private_only=True)
|
||||||
|
bot.events.on("received").on("cap").hook(self.on_cap)
|
||||||
|
bot.events.on("received").on("authenticate").hook(self.on_authenticate)
|
||||||
|
for code in ["902", "903", "904", "905", "906", "907", "908"]:
|
||||||
|
bot.events.on("received").on("numeric").on(code).hook(self.on_90x)
|
||||||
|
|
||||||
|
def on_new_server(self, event):
|
||||||
|
event["server"].attempted_auth = False
|
||||||
|
event["server"].sasl_success = False
|
||||||
|
|
||||||
def on_connect(self, event):
|
def on_connect(self, event):
|
||||||
nickserv_password = event["server"].get_setting(
|
nickserv_password = event["server"].get_setting(
|
||||||
"nickserv-password")
|
"nickserv-password")
|
||||||
if nickserv_password:
|
if nickserv_password and not event["server"].sasl_success:
|
||||||
|
event["server"].attempted_auth = True
|
||||||
event["server"].send_message("nickserv",
|
event["server"].send_message("nickserv",
|
||||||
"identify %s" % nickserv_password)
|
"identify %s" % nickserv_password)
|
||||||
|
|
||||||
|
@ -20,3 +30,27 @@ class Module(object):
|
||||||
nickserv_password = event["args"]
|
nickserv_password = event["args"]
|
||||||
event["server"].set_setting("nickserv-password", nickserv_password)
|
event["server"].set_setting("nickserv-password", nickserv_password)
|
||||||
event["stdout"].write("Nickserv password saved")
|
event["stdout"].write("Nickserv password saved")
|
||||||
|
|
||||||
|
def on_cap(self, event):
|
||||||
|
if event["subcommand"] == "NAK":
|
||||||
|
event["server"].send_capability_end()
|
||||||
|
elif event["subcommand"] == "ACK":
|
||||||
|
event["server"].send_authenticate("PLAIN")
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_authenticate(self, event):
|
||||||
|
if event["message"] != "+":
|
||||||
|
event["server"].send_authenticate("*")
|
||||||
|
else:
|
||||||
|
nick = event["server"].original_nickname
|
||||||
|
password = event["server"].get_setting("nickserv-password")
|
||||||
|
event["server"].attempted_auth = True
|
||||||
|
event["server"].send_authenticate(
|
||||||
|
base64.b64encode(("%s\0%s\0%s" % (nick, nick, password)).encode("utf8")).decode("utf8")
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_90x(self, event):
|
||||||
|
if event["number"]=="903":
|
||||||
|
event["server"].sasl_success = True
|
||||||
|
event["server"].send_capability_end()
|
||||||
|
|
Loading…
Reference in a new issue