From 28166ed4eea4b9ae1a175f1aeb0b0e3df586c8b4 Mon Sep 17 00:00:00 2001 From: Ian Carpenter Date: Mon, 18 Feb 2019 20:14:04 -0500 Subject: [PATCH 01/14] Only write the pid file when --no-pid isn't passed (#633) Prevents --no-pid from breaking (cherry picked from commit 30387c9ed5e02205a637f34fa9fc0a7fdcb3f98c) --- launcher.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/launcher.py b/launcher.py index 0703f81..f7e589b 100644 --- a/launcher.py +++ b/launcher.py @@ -120,9 +120,10 @@ def _main(): elif os.name == 'posix': sys.stdout.write("\x1b]2;PyLink %s\x07" % __version__) - # Write the PID file only after forking. - with open(pidfile, 'w') as f: - f.write(str(os.getpid())) + if not args.no_pid: + # Write the PID file only after forking. + with open(pidfile, 'w') as f: + f.write(str(os.getpid())) # Load configured plugins to_load = conf.conf['plugins'] From 8f10af99422ea5eff9b1181d50ea1513540c46e7 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sun, 31 Mar 2019 01:33:46 -0700 Subject: [PATCH 02/14] PyLink 2.0.2 --- RELNOTES.md | 18 ++++++++++++++++++ VERSION | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/RELNOTES.md b/RELNOTES.md index 7981713..3651cca 100644 --- a/RELNOTES.md +++ b/RELNOTES.md @@ -1,3 +1,21 @@ +# PyLink 2.0.2 (2019-03-31) + +Changes since 2.0.1: + +#### Feature changes +- Antispam now supports filtering away Unicode lookalike characters when processing text +- Allow disabling dynamic channels via a new "join_empty_channels" option +- relay: add an explicit `forcetag` command, since IRC kills are now relayed between networks + +#### Bug fixes +- launcher: fix crash when --no-pid is set +- relay: fix DB corruption when editing modedelta modes +- automode: fix sending joins to the wrong network when editing remote channels + +#### Internal improvements +- relay: minor optimizations and cleanup +- Disable throttling on S2S links by default, since it usually isn't necessary there + # PyLink 2.0.1 (2018-10-06) Changes since 2.0.0: diff --git a/VERSION b/VERSION index 94245d2..e9307ca 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.2-dev +2.0.2 From 739c87ef50817c1672743f921dc6b71fd1d490f4 Mon Sep 17 00:00:00 2001 From: James Lu Date: Mon, 8 Apr 2019 22:33:55 -0700 Subject: [PATCH 03/14] clientbot: only split /names replies by spaces This fixes issues with colored hostnames because \x1f is treated as a whitespace char by str.split(). Closes #641. (cherry picked from commit 905679963331764db17f0bda2dfd7b299d8d2a20) --- protocols/clientbot.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/protocols/clientbot.py b/protocols/clientbot.py index 04dbbe9..bfc3354 100644 --- a/protocols/clientbot.py +++ b/protocols/clientbot.py @@ -681,14 +681,19 @@ class ClientbotWrapperProtocol(IRCCommonProtocol): prefix_to_mode = {v:k for k, v in self.prefixmodes.items()} prefixes = ''.join(self.prefixmodes.values()) - for name in args[-1].split(): + # N.B. only split on spaces because of color in hosts nonsense... + # str.split() by default treats \x1f as whitespace + for name in args[-1].split(' '): nick = name.lstrip(prefixes) # Handle userhost-in-names where available. + ident = host = None if 'userhost-in-names' in self.ircv3_caps: - nick, ident, host = utils.split_hostmask(nick) - else: - ident = host = None + try: + nick, ident, host = utils.split_hostmask(nick) + except ValueError: + log.exception('(%s) Failed to split hostmask %r from /names reply on %s', self.name, nick, channel) + # If error, leave nick unsplit # Get the PUID for the given nick. If one doesn't exist, spawn # a new virtual user. From 7e088dfacb7c71d0152bdb3a595057465382e706 Mon Sep 17 00:00:00 2001 From: James Lu Date: Mon, 29 Apr 2019 11:58:18 -0700 Subject: [PATCH 04/14] clientbot: log the entire args list when splitting /names reply fails (cherry picked from commit a8bb5f66e53c9d3d3f7872587b2c752ff8a1b16d) --- protocols/clientbot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/clientbot.py b/protocols/clientbot.py index bfc3354..37f345d 100644 --- a/protocols/clientbot.py +++ b/protocols/clientbot.py @@ -692,7 +692,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol): try: nick, ident, host = utils.split_hostmask(nick) except ValueError: - log.exception('(%s) Failed to split hostmask %r from /names reply on %s', self.name, nick, channel) + log.exception('(%s) Failed to split hostmask %r from /names reply on %s; args=%s', self.name, nick, channel, args) # If error, leave nick unsplit # Get the PUID for the given nick. If one doesn't exist, spawn From 81bf6480df34224a48894a586a531361a8697e01 Mon Sep 17 00:00:00 2001 From: James Lu Date: Mon, 29 Apr 2019 12:04:11 -0700 Subject: [PATCH 05/14] clientbot: avoid adding empty nicks to the state It looks like names replies may end with an extra space, which should not be considered as part of the nick list.. (cherry picked from commit f90b0c857745090ff330fe70421966e11d65a9d6) --- protocols/clientbot.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/protocols/clientbot.py b/protocols/clientbot.py index 37f345d..04153d8 100644 --- a/protocols/clientbot.py +++ b/protocols/clientbot.py @@ -683,7 +683,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol): # N.B. only split on spaces because of color in hosts nonsense... # str.split() by default treats \x1f as whitespace - for name in args[-1].split(' '): + for name in args[-1].strip().split(' '): nick = name.lstrip(prefixes) # Handle userhost-in-names where available. @@ -695,6 +695,9 @@ class ClientbotWrapperProtocol(IRCCommonProtocol): log.exception('(%s) Failed to split hostmask %r from /names reply on %s; args=%s', self.name, nick, channel, args) # If error, leave nick unsplit + if not nick: + continue + # Get the PUID for the given nick. If one doesn't exist, spawn # a new virtual user. idsource = self._get_UID(nick, ident=ident, host=host) From c7e4c05cbd2ced670d1fab378dfb0c9f2a933a82 Mon Sep 17 00:00:00 2001 From: James Lu Date: Tue, 9 Apr 2019 19:01:35 -0700 Subject: [PATCH 06/14] changehost: only send a host change if new host != original (cherry picked from commit 13be40e08bf4ca842b3908ec9b15ee9008ef95b9) --- plugins/changehost.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/plugins/changehost.py b/plugins/changehost.py index 1e29f3e..98a36a0 100644 --- a/plugins/changehost.py +++ b/plugins/changehost.py @@ -41,6 +41,13 @@ def _changehost(irc, target, args): return args = args.copy() + + # $host is explicitly forbidden by default because it can cause recursive + # loops when IP or real host masks are used to match a target. vHost + # updates do not affect these fields, so any further host application will + # cause the vHost to grow rapidly in size. + # That said, it is possible to get away with this expansion if you're + # careful enough, and that's why this hidden option exists. if not changehost_conf.get('force_host_expansion'): del args['host'] @@ -57,16 +64,6 @@ def _changehost(irc, target, args): # Substitute using the fields provided the hook data. This means # that the following variables are available for substitution: # $uid, $ts, $nick, $realhost, $ident, and $ip. - - # $host is explicitly forbidden by default because it can cause - # recursive loops when IP or real host masks are used to match a - # target. vHost updates do not affect these fields, so any further - # execution of 'applyhosts' will cause $host to expand again to - # the user's new host, causing the vHost to grow rapidly in size. - # That said, it is possible to get away with this expansion if - # you're careful with what you're doing, and that is why this - # hidden option exists. -GLolol - try: new_host = template.substitute(args) except KeyError as e: @@ -78,7 +75,9 @@ def _changehost(irc, target, args): if char not in allowed_chars: new_host = new_host.replace(char, '-') - irc.update_client(target, 'HOST', new_host) + # Only send a host change if something has changed + if new_host != irc.users[target].host: + irc.update_client(target, 'HOST', new_host) # Only operate on the first match. break From 6054476900e31be392b433140b2bf6b0b6402828 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 7 Jun 2019 14:10:42 -0700 Subject: [PATCH 07/14] More secure password hashing defaults (cherry picked from commit eba5d912999cd3d5346eb12949f592fa755ebdc5) Default hash method to pbkdf2-sha256 & allow customizing CryptContext options This introduces a new login::cryptcontext_settings config option. Closes #645. --- coremods/control.py | 3 ++- coremods/login.py | 34 +++++++++++++++++++++++----------- example-conf.yml | 10 ++++++++++ 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/coremods/control.py b/coremods/control.py index 0805f47..a918599 100644 --- a/coremods/control.py +++ b/coremods/control.py @@ -9,7 +9,7 @@ import atexit from pylinkirc import world, utils, conf # Do not import classes, it'll import loop from pylinkirc.log import log, _make_file_logger, _stop_file_loggers, _get_console_log_level -from . import permissions +from . import permissions, login def remove_network(ircobj): """Removes a network object from the pool.""" @@ -104,6 +104,7 @@ def rehash(): log.debug('rehash: updating console log level') world.console_handler.setLevel(_get_console_log_level()) + login._make_cryptcontext() # refresh password hashing settings for network, ircobj in world.networkobjects.copy().items(): # Server was removed from the config file, disconnect them. diff --git a/coremods/login.py b/coremods/login.py index 5cd03b3..7559776 100644 --- a/coremods/login.py +++ b/coremods/login.py @@ -5,18 +5,30 @@ login.py - Implement core login abstraction. from pylinkirc import conf, utils, world from pylinkirc.log import log -try: - from passlib.context import CryptContext -except ImportError: - CryptContext = None - log.warning("Hashed passwords are disabled because passlib is not installed. Please install " - "it (pip3 install passlib) and restart for this feature to work.") - +# PyLink's global password context pwd_context = None -if CryptContext: - pwd_context = CryptContext(["sha512_crypt", "sha256_crypt"], - sha256_crypt__default_rounds=180000, - sha512_crypt__default_rounds=90000) + +_DEFAULT_CRYPTCONTEXT_SETTINGS = { + 'schemes': ["pbkdf2_sha256", "sha512_crypt"] +} +def _make_cryptcontext(): + try: + from passlib.context import CryptContext + except ImportError: + log.warning("Hashed passwords are disabled because passlib is not installed. Please install " + "it (pip3 install passlib) and rehash for this feature to work.") + return + + context_settings = conf.conf.get('login', {}).get('cryptcontext_settings') or _DEFAULT_CRYPTCONTEXT_SETTINGS + global pwd_context + if pwd_context is None: + log.debug("Initialized new CryptContext with settings: %s", context_settings) + pwd_context = CryptContext(**context_settings) + else: + log.debug("Updated CryptContext with settings: %s", context_settings) + pwd_context.update(**context_settings) + +_make_cryptcontext() # This runs at startup and in rehash (control.py) def _get_account(accountname): """ diff --git a/example-conf.yml b/example-conf.yml index 5ac6eeb..3d0d430 100644 --- a/example-conf.yml +++ b/example-conf.yml @@ -117,6 +117,16 @@ login: # are supported here as well. #hosts: ["*!*@localhost", "*!*@trusted.isp"] + # For ADVANCED users: adjusts settings for PyLink's default passlib CryptContext. + # As of PyLink 2.1, the default is to use pbkdf2_sha256 for new hashes, while also allowing verifying + # sha512_crypt for compatibility with PyLink < 2.1. + + # This is configured as a dict of settings, which will be passed into the CryptContext constructor. + # See https://passlib.readthedocs.io/en/stable/lib/passlib.context.html for a list of valid options. + # Changes to this setting require a rehash to apply. + #cryptcontext_settings: + #schemes: ["pbkdf2_sha256", "sha512_crypt"] + permissions: # Permissions blocks in PyLink are define as a mapping of PyLink targets (i.e. hostmasks or # exttargets) to lists of permission nodes. You can find a list of permissions that PyLink and From 46d1738f6687afeb1763e2467fabb94b4f20b637 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sun, 16 Jun 2019 11:39:07 -0700 Subject: [PATCH 08/14] example-conf: mention PyLink 2.0.3 instead of 2.1 for CryptContext changes --- example-conf.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example-conf.yml b/example-conf.yml index 3d0d430..0877575 100644 --- a/example-conf.yml +++ b/example-conf.yml @@ -118,8 +118,8 @@ login: #hosts: ["*!*@localhost", "*!*@trusted.isp"] # For ADVANCED users: adjusts settings for PyLink's default passlib CryptContext. - # As of PyLink 2.1, the default is to use pbkdf2_sha256 for new hashes, while also allowing verifying - # sha512_crypt for compatibility with PyLink < 2.1. + # As of PyLink 2.0.3, the default is to use pbkdf2_sha256 for new hashes, while also allowing verifying + # sha512_crypt for compatibility with PyLink < 2.0.3. # This is configured as a dict of settings, which will be passed into the CryptContext constructor. # See https://passlib.readthedocs.io/en/stable/lib/passlib.context.html for a list of valid options. From fae63d77b2d7edd656f6ccdc632cfc0396e0b1c6 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sun, 18 Aug 2019 19:48:19 -0700 Subject: [PATCH 09/14] README: mention that ngIRCd's CloakHost and CloakUserToNick are not supported Cloak tools that enforce hosts on remote users are by nature unsupportable because they cause hostmask desyncs when forwarding Relay users. This in turn makes channel moderation impossible. [skip ci] (cherry picked from commit 1a692f55ad6e4a08f0b7eab08c11f6677b1d0ae9) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4e88f50..62975cb 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ Support for these IRCds exist, but are not tested as frequently and thoroughly. * [ngIRCd](https://ngircd.barton.de/) (24+) - module `ngircd` - For GLINEs to propagate, the `AllowRemoteOper` option must be enabled in ngIRCd. - `+` (modeless) channels are not supported, and should be disabled for PyLink to function correctly. + - For use with Relay, the `CloakHostModeX` setting will work fine but `CloakHost` and `CloakUserToNick` are *not* supported. * [snircd](https://development.quakenet.org/) (1.3.x+) - module `p10` - Outbound host changing (i.e. for the `changehost` plugin) is not supported on P10 variants other than Nefarious. From 2cdcd8e1933a03f4423d49df530308d4f9638b08 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sun, 23 Jun 2019 20:13:04 -0700 Subject: [PATCH 10/14] clientbot: fix error when MODES is defined in ISUPPORT but given no value (cherry picked from commit 61ca8dd781aa1bc0458d9c4cbb2e6223f0fa0499) This fixes connections to e.g. Oragono --- protocols/clientbot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/clientbot.py b/protocols/clientbot.py index 04153d8..5536871 100644 --- a/protocols/clientbot.py +++ b/protocols/clientbot.py @@ -244,7 +244,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol): log.debug('(%s) mode: filtered modes for %s: %s', self.name, channel, extmodes) if extmodes: bufsize = self.S2S_BUFSIZE - len(':%s MODE %s ' % (self.get_hostmask(self.pseudoclient.uid), channel)) - for msg in self.wrap_modes(extmodes, bufsize, max_modes_per_msg=int(self._caps.get('MODES', 0))): + for msg in self.wrap_modes(extmodes, bufsize, max_modes_per_msg=int(self._caps.get('MODES') or 0)): self.send('MODE %s %s' % (channel, msg)) # Don't update the state here: the IRCd sill respond with a MODE reply if successful. From e02ab9f2fff9d7f9442e7fd62d768b98253fd89d Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 26 Jun 2019 12:41:26 -0700 Subject: [PATCH 11/14] relay: consistency fixes for the hideoper setting - Don't enforce +H on /oper when the hideoper option is disabled - Skip relaying -H if the hideoper option is enabled - closes #629 (cherry picked from commit 9a74626d62b18a695f76449369645a42ceffb8cc) --- plugins/relay.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/relay.py b/plugins/relay.py index 6e08164..b215c79 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -1766,10 +1766,19 @@ def handle_mode(irc, numeric, command, args): # Set hideoper on remote opers, to prevent inflating # /lusers and various /stats hideoper_mode = remoteirc.umodes.get('hideoper') + try: + use_hideoper = conf.conf['relay']['hideoper'] + except KeyError: + use_hideoper = True + + # If Relay oper hiding is enabled, don't allow unsetting +H + if use_hideoper and ('-%s' % hideoper_mode, None) in modes: + modes.remove(('-%s' % hideoper_mode, None)) + modes = get_supported_umodes(irc, remoteirc, modes) if hideoper_mode: - if ('+o', None) in modes: + if ('+o', None) in modes and use_hideoper: modes.append(('+%s' % hideoper_mode, None)) elif ('-o', None) in modes: modes.append(('-%s' % hideoper_mode, None)) From e0a618f31747d01e31798715bed1759cbcc76c0d Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 26 Jun 2019 13:17:00 -0700 Subject: [PATCH 12/14] [SECURITY] permissions: only whitelist the defined login:user for legacy accounts It's possible for login:user and login:accounts to be used together, although this is discouraged. (cherry picked from commit 4eb0420378e7b627dbf1f3c90e0e33012f54d4b6) --- coremods/permissions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coremods/permissions.py b/coremods/permissions.py index 4026afd..f0c17ad 100644 --- a/coremods/permissions.py +++ b/coremods/permissions.py @@ -32,7 +32,8 @@ def check_permissions(irc, uid, perms, also_show=[]): """ # For old (< 1.1 login blocks): # If the user is logged in, they automatically have all permissions. - if irc.match_host('$pylinkacc', uid) and conf.conf['login'].get('user'): + olduser = conf.conf['login'].get('user') + if olduser and irc.match_host('$pylinkacc:%s' % olduser, uid): log.debug('permissions: overriding permissions check for old-style admin user %s', irc.get_hostmask(uid)) return True From d57b121600fc5f028caeafc4e578282813371579 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 1 Mar 2019 23:34:32 -0800 Subject: [PATCH 13/14] unreal: work around a potential race when sending kills on join (cherry picked from commit 1780271dd0077cb19fb0367d1df99827b10362b5) --- protocols/unreal.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/protocols/unreal.py b/protocols/unreal.py index 7577bb9..a39de15 100644 --- a/protocols/unreal.py +++ b/protocols/unreal.py @@ -607,6 +607,11 @@ class UnrealProtocol(TS6BaseProtocol): continue user = self._get_UID(user) # Normalize nicks to UIDs for Unreal 3.2 links + if user not in self.users: + # Work around a potential race when sending kills on join + log.debug("(%s) Ignoring user %s in SJOIN to %s, they don't exist anymore", self.name, user, channel) + continue + # Unreal uses slightly different prefixes in SJOIN. +q is * instead of ~, # and +a is ~ instead of &. modeprefix = (r.group(1) or '').replace("~", "&").replace("*", "~") From 2c53ce0682edae8e2517e2490e4a8b961af9009e Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 11 Oct 2019 10:18:43 -0700 Subject: [PATCH 14/14] PyLink 2.0.3 --- RELNOTES.md | 16 ++++++++++++++++ VERSION | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/RELNOTES.md b/RELNOTES.md index 3651cca..5bd0d7b 100644 --- a/RELNOTES.md +++ b/RELNOTES.md @@ -1,3 +1,19 @@ +# PyLink 2.0.3 (2019-10-11) + +Changes since 2.0.2: + +#### Feature changes +- Switch to more secure password hashing defaults, using pbkdf2-sha256 as the default hash method +- Introduce a `login::cryptcontext_settings` option to further tweak passlib settings if desired + +#### Bug fixes +- **SECURITY**: Only allow the defined `login:user` to take all permissions when legacy accounts are enabled +- clientbot: fix /names handling on networks with colours in hostnames +- clientbot: fix crash when MODES is defined in ISUPPORT but given no value (affects connections to Oragono) +- changehost: only send a host change if new host != original +- relay: fix inconsistent handling of the hideoper setting. [issue#629](https://github.com/jlu5/PyLink/issues/629) +- unreal: work around a potential race when sending kills on join + # PyLink 2.0.2 (2019-03-31) Changes since 2.0.1: diff --git a/VERSION b/VERSION index e9307ca..50ffc5a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.2 +2.0.3