From 6bd6f2492b6345ae71da0bb0505a20af3bcc2b75 Mon Sep 17 00:00:00 2001 From: jesopo Date: Wed, 5 Sep 2018 11:58:10 +0100 Subject: [PATCH] Support IRCv3's account-notify/extended-join along with WHOX to replace internal register/identify --- IRCLineHandler.py | 20 +++++++++- IRCServer.py | 4 ++ IRCUser.py | 21 ++++++++--- modules/permissions.py | 85 ++++++++++++++++++++++++------------------ 4 files changed, 87 insertions(+), 43 deletions(-) diff --git a/IRCLineHandler.py b/IRCLineHandler.py index a73f6a02..b7508234 100644 --- a/IRCLineHandler.py +++ b/IRCLineHandler.py @@ -26,6 +26,7 @@ class LineHandler(object): events.on("raw.366").hook(self.handle_366, default_event=True) events.on("raw.421").hook(self.handle_421, default_event=True) events.on("raw.352").hook(self.handle_352, default_event=True) + events.on("raw.354").hook(self.handle_354, default_event=True) events.on("raw.324").hook(self.handle_324, default_event=True) events.on("raw.329").hook(self.handle_329, default_event=True) events.on("raw.433").hook(self.handle_433, default_event=True) @@ -110,8 +111,10 @@ class LineHandler(object): # server telling us what it supports def handle_005(self, event): isupport_line = " ".join(event["args"][1:]) + if "NAMESX" in isupport_line: event["server"].send("PROTOCTL NAMESX") + match = re.search(RE_PREFIXES, isupport_line) if match: event["server"].mode_prefixes.clear() @@ -201,7 +204,7 @@ class LineHandler(object): # on-join user list has finished def handle_366(self, event): - event["server"].send_who(event["args"][1]) + event["server"].send_whox(event["args"][1], "ahnrtu", "001") # on user joining channel def join(self, event): @@ -484,6 +487,21 @@ class LineHandler(object): user = event["server"].get_user(event["args"][5]) user.username = event["args"][2] user.hostname = event["args"][3] + # response to a WHOX command for user information, including account name + def handle_354(self, event): + if event["args"][1] == "001": + username = event["args"][2] + hostname = event["args"][3] + nickname = event["args"][4] + account = event["args"][5] + realname = event["arbitrary"] + + user = event["server"].get_user(nickname) + user.username = username + user.hostname = hostname + user.realname = realname + if not account == "0": + user.identified_account = account # response to an empty mode command def handle_324(self, event): diff --git a/IRCServer.py b/IRCServer.py index b36bc169..96fd831c 100644 --- a/IRCServer.py +++ b/IRCServer.py @@ -379,3 +379,7 @@ class Server(object): "" if server == None else " :%s" % server)) def send_who(self, filter=None): self.send("WHO%s" % ("" if filter == None else " %s" % filter)) + def send_whox(self, filter, flags, label=None): + self.send("WHO %s %s%s" % (filter, + "%"+flags if flags else "", + ","+label if label else "")) diff --git a/IRCUser.py b/IRCUser.py index 696b4a3b..8782e083 100644 --- a/IRCUser.py +++ b/IRCUser.py @@ -11,13 +11,22 @@ class User(object): self.server = server self.bot = bot self.channels = set([]) + self.identified_account = None + self.identified_account_override = None + + self.identified_account_id = None + self.identified_account_id_override = None self.away = False self.buffer = IRCBuffer.Buffer(bot) def __repr__(self): return "IRCUser.User(%s|%s)" % (self.server.name, self.name) + def _get_id(self): + return (self.identified_account_id_override or + self.identified_account_id or self.id) + def set_nickname(self, nickname): self.nickname = nickname self.nickname_lower = nickname.lower() @@ -27,21 +36,21 @@ class User(object): def part_channel(self, channel): self.channels.remove(channel) def set_setting(self, setting, value): - self.bot.database.user_settings.set(self.id, setting, value) + self.bot.database.user_settings.set(self._get_id(), setting, value) def get_setting(self, setting, default=None): - return self.bot.database.user_settings.get(self.id, setting, + return self.bot.database.user_settings.get(self._get_id(), setting, default) def find_settings(self, pattern, default=[]): - return self.bot.database.user_settings.find(self.id, pattern, + return self.bot.database.user_settings.find(self._get_id(), pattern, default) def find_settings_prefix(self, prefix, default=[]): - return self.bot.database.user_settings.find_prefix(self.id, + return self.bot.database.user_settings.find_prefix(self._get_id(), prefix, default) def del_setting(self, setting): - self.bot.database.user_settings.delete(self.id, setting) + self.bot.database.user_settings.delete(self._get_id(), setting) def get_channel_settings_per_setting(self, setting, default=[]): return self.bot.database.user_channel_settings.find_by_setting( - self.id, setting, default) + self._get_id(), setting, default) def send_message(self, message, prefix=None): self.server.send_message(self.nickname, message, prefix=prefix) diff --git a/modules/permissions.py b/modules/permissions.py index 0820c1e4..5d5f4bb3 100644 --- a/modules/permissions.py +++ b/modules/permissions.py @@ -11,11 +11,10 @@ class Module(object): events.on("preprocess.command").hook( self.preprocess_command) events.on("received.part").hook(self.on_part) - events.on("received.nick").hook(self.on_nick) events.on("received").on("command").on("identify" - ).hook(self.identify, private_only=True, min_args=1, - usage="", help="Identify yourself") + ).hook(self.identify, private_only=True, min_args=2, + usage=" ", help="Identify yourself") events.on("received").on("command").on("register" ).hook(self.register, private_only=True, min_args=1, usage="", help="Register your nickname") @@ -37,18 +36,14 @@ class Module(object): self._logout(event["user"]) def on_part(self, event): - if len(event["user"].channels) == 1 and event["user"].identified: + if len(event["user"].channels) == 1 and event["user" + ].identified_account_override: event["user"].send_notice("You no longer share any channels " "with me so you have been signed out") - def on_nick(self, event): - if event["user"].identified: - self._logout(event["user"]) - event["user"].send_notice("You've changed nickname so you " - "have been signed out") - - def _get_hash(self, user): - hash, salt = user.get_setting("authentication", (None, None)) + def _get_hash(self, server, account): + hash, salt = server.get_user(account).get_setting("authentication", + (None, None)) return hash, salt def _make_salt(self): @@ -59,35 +54,48 @@ class Module(object): hash = base64.b64encode(scrypt.hash(password, salt)).decode("utf8") return hash, salt - def _identified(self, user): - user.identified = True + def _identified(self, server, user, account): + user.identified_account_override = account + user.identified_account_id_override = server.get_user(account).id def _logout(self, user): - user.identified = False + user.identified_account_override = None + user.identified_account_id_override = None def identify(self, event): + identity_mechanism = event["server"].get_setting("identity-mechanism", + "internal") + if not identity_mechanism == "internal": + event["stderr"].write("The 'identify' command isn't available " + "on this network") + return + if not event["user"].channels: event["stderr"].write("You must share at least one channel " "with me before you can identify") return - if not event["user"].identified: - password = event["args_split"][0] - hash, salt = self._get_hash(event["user"]) + + if not event["user"].identified_account_override: + account = event["args_split"][0] + password = " ".join(event["args_split"][1:]) + hash, salt = self._get_hash(event["server"], account) if hash and salt: attempt, _ = self._make_hash(password, salt) if attempt == hash: - self._identified(event["user"]) + self._identified(event["server"], event["user"], account) event["stdout"].write("Correct password, you have " - "been identified.") + "been identified as '%s'." % account) else: - event["stderr"].write("Incorrect password") + event["stderr"].write("Incorrect password for '%s'" % + account) else: - event["stderr"].write("This nickname is not registered") + event["stderr"].write("Account '%s' is not registered" % + account) else: event["stderr"].write("You are already identified") def register(self, event): - hash, salt = self._get_hash(event["user"]) + hash, salt = self._get_hash(event["server"], event["user"].nickname) if not hash and not salt: password = event["args_split"][0] hash, salt = self._make_hash(password) @@ -98,7 +106,7 @@ class Module(object): event["stderr"].write("This nickname is already registered") def logout(self, event): - if event["user"].identified: + if event["user"].identified_account_override: self._logout(event["user"]) event["stdout"].write("You have been logged out") else: @@ -118,25 +126,30 @@ class Module(object): target.nickname) def preprocess_command(self, event): - authentication = event["user"].get_setting("authentication", None) permission = event["hook"].kwargs.get("permission", None) authenticated = event["hook"].kwargs.get("authenticated", False) - protect_registered = event["hook"].kwargs.get("protect_registered", - False) + + identity_mechanism = event["server"].get_setting("identity-mechanism", + "internal") + identified_account = None + if identity_mechanism == "internal": + identified_account = event["user"].identified_account_override + elif identity_mechanism == "ircv3-account": + identified_account = event["user"].identified_account + + identified_user = None + permissions = [] + if identified_account: + identified_user = event["server"].get_user(identified_account) + permissions = identified_user.get_setting("permissions", []) if permission: - identified = event["user"].identified - user_permissions = event["user"].get_setting("permissions", []) has_permission = permission and ( - permission in user_permissions or "*" in user_permissions) - if not identified or not has_permission: + permission in permissions or "*" in permissions) + if not identified_account or not has_permission: return "You do not have permission to do that" elif authenticated: - if not event["user"].identified: - return REQUIRES_IDENTIFY % (event["server"].nickname, - event["server"].nickname) - elif protect_registered: - if authentication and not event["user"].identified: + if not identified_account: return REQUIRES_IDENTIFY % (event["server"].nickname, event["server"].nickname)