Replace makeExtBanmask with makeExtBanmasks

Now that we can return both account extbans and regular masks,
it makes sense to ban both.

Otherwise, adding 'account' to supybot.protocols.irc.banmask
means we banned only the account instead of the hostmask,
which arguably makes the ban weaker (/NS LOGOUT to evade)
This commit is contained in:
Valentin Lorentz 2022-11-23 20:33:48 +01:00
parent fc49d17faa
commit f73fe5095e
5 changed files with 85 additions and 42 deletions

View File

@ -163,9 +163,9 @@ class AutoMode(callbacks.Plugin):
# We're not in the channel anymore. # We're not in the channel anymore.
pass pass
schedule.addEvent(unban, time.time()+period) schedule.addEvent(unban, time.time()+period)
banmask = conf.supybot.protocols.irc.banmask.makeExtBanmask( banmasks = conf.supybot.protocols.irc.banmask.makeExtBanmasks(
msg.prefix, channel=channel, network=irc.network) msg.prefix, channel=channel, network=irc.network)
irc.queueMsg(ircmsgs.ban(channel, banmask)) irc.queueMsg(ircmsgs.bans(channel, banmasks))
irc.queueMsg(ircmsgs.kick(channel, msg.nick)) irc.queueMsg(ircmsgs.kick(channel, msg.nick))
try: try:

View File

@ -368,7 +368,7 @@ class Channel(callbacks.Plugin):
try: try:
bannedHostmask = irc.state.nickToHostmask(target) bannedHostmask = irc.state.nickToHostmask(target)
banmaskstyle = conf.supybot.protocols.irc.banmask banmaskstyle = conf.supybot.protocols.irc.banmask
banmask = banmaskstyle.makeExtBanmask( banmasks = banmaskstyle.makeExtBanmasks(
bannedHostmask, [o[0] for o in optlist], bannedHostmask, [o[0] for o in optlist],
channel=channel, network=irc.network) channel=channel, network=irc.network)
except KeyError: except KeyError:
@ -376,12 +376,14 @@ class Channel(callbacks.Plugin):
target.startswith('$'): target.startswith('$'):
# Select the last part, or the whole target: # Select the last part, or the whole target:
bannedNick = target.split(':')[-1] bannedNick = target.split(':')[-1]
banmask = bannedHostmask = target bannedHostmask = target
banmasks = [bannedHostmask]
else: else:
irc.error(format(_('I haven\'t seen %s.'), bannedNick), Raise=True) irc.error(format(_('I haven\'t seen %s.'), bannedNick), Raise=True)
else: else:
bannedNick = ircutils.nickFromHostmask(target) bannedNick = ircutils.nickFromHostmask(target)
banmask = bannedHostmask = target bannedHostmask = target
banmasks = [bannedHostmask]
if not irc.isNick(bannedNick): if not irc.isNick(bannedNick):
self.log.warning('%q tried to kban a non nick: %q', self.log.warning('%q tried to kban a non nick: %q',
msg.prefix, bannedNick) msg.prefix, bannedNick)
@ -398,6 +400,8 @@ class Channel(callbacks.Plugin):
reason = msg.nick reason = msg.nick
capability = ircdb.makeChannelCapability(channel, 'op') capability = ircdb.makeChannelCapability(channel, 'op')
# Check (again) that they're not trying to make us kickban ourself. # Check (again) that they're not trying to make us kickban ourself.
for banmask in banmasks:
# TODO: check account ban too
if ircutils.hostmaskPatternEqual(banmask, irc.prefix): if ircutils.hostmaskPatternEqual(banmask, irc.prefix):
if ircutils.hostmaskPatternEqual(bannedHostmask, irc.prefix): if ircutils.hostmaskPatternEqual(bannedHostmask, irc.prefix):
self.log.warning('%q tried to make me kban myself.',msg.prefix) self.log.warning('%q tried to make me kban myself.',msg.prefix)
@ -406,21 +410,28 @@ class Channel(callbacks.Plugin):
else: else:
self.log.warning('Using exact hostmask since banmask would ' self.log.warning('Using exact hostmask since banmask would '
'ban myself.') 'ban myself.')
banmask = bannedHostmask banmasks = [bannedHostmask]
# Now, let's actually get to it. Check to make sure they have # Now, let's actually get to it. Check to make sure they have
# #channel,op and the bannee doesn't have #channel,op; or that the # #channel,op and the bannee doesn't have #channel,op; or that the
# bannee and the banner are both the same person. # bannee and the banner are both the same person.
def doBan(): def doBan():
if irc.state.channels[channel].isOp(bannedNick): if irc.state.channels[channel].isOp(bannedNick):
irc.queueMsg(ircmsgs.deop(channel, bannedNick)) irc.queueMsg(ircmsgs.deop(channel, bannedNick))
irc.queueMsg(ircmsgs.ban(channel, banmask)) irc.queueMsg(ircmsgs.bans(channel, banmasks))
if kick: if kick:
irc.queueMsg(ircmsgs.kick(channel, bannedNick, reason)) irc.queueMsg(ircmsgs.kick(channel, bannedNick, reason))
if expiry > 0: if expiry > 0:
def f(): def f():
if channel in irc.state.channels and \ if channel not in irc.state.channels:
banmask in irc.state.channels[channel].bans: return
irc.queueMsg(ircmsgs.unban(channel, banmask)) remaining_banmasks = [
banmask
for banmask in banmasks
if banmask in irc.state.channels[channel].bans
]
if remaining_banmasks:
irc.queueMsg(ircmsgs.unbans(
channel, remaining_banmasks))
schedule.addEvent(f, expiry) schedule.addEvent(f, expiry)
if bannedNick == msg.nick: if bannedNick == msg.nick:
doBan() doBan()
@ -591,7 +602,7 @@ class Channel(callbacks.Plugin):
hostmask = wrap(hostmask, ['op', ('haveHalfop+', _('ban someone')), 'text']) hostmask = wrap(hostmask, ['op', ('haveHalfop+', _('ban someone')), 'text'])
@internationalizeDocstring @internationalizeDocstring
def add(self, irc, msg, args, channel, banmask, expires): def add(self, irc, msg, args, channel, banmasks, expires):
"""[<channel>] <nick|hostmask> [<expires>] """[<channel>] <nick|hostmask> [<expires>]
If you have the #channel,op capability, this will effect a If you have the #channel,op capability, this will effect a
@ -605,11 +616,14 @@ class Channel(callbacks.Plugin):
channel itself. channel itself.
""" """
c = ircdb.channels.getChannel(channel) c = ircdb.channels.getChannel(channel)
if isinstance(banmasks, str):
banmasks = [banmasks]
for banmask in banmasks:
c.addBan(banmask, expires) c.addBan(banmask, expires)
ircdb.channels.setChannel(channel, c) ircdb.channels.setChannel(channel, c)
irc.replySuccess() irc.replySuccess()
add = wrap(add, ['op', add = wrap(add, ['op',
first('hostmask', 'extbanmask'), first('hostmask', 'extbanmasks'),
additional('expiry', 0)]) additional('expiry', 0)])
@internationalizeDocstring @internationalizeDocstring

View File

@ -29,6 +29,8 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
import itertools
from supybot.test import * from supybot.test import *
import supybot.conf as conf import supybot.conf as conf
@ -161,9 +163,13 @@ class ChannelTestCase(ChannelPluginTestCase):
self.assertTrue(m.command == 'MODE' and self.assertTrue(m.command == 'MODE' and
m.args == (self.channel, '+v', 'bar')) m.args == (self.channel, '+v', 'bar'))
def assertKban(self, query, hostmask, **kwargs): def assertKban(self, query, *hostmasks, **kwargs):
m = self.getMsg(query, **kwargs) m = self.getMsg(query, **kwargs)
self.assertEqual(m, ircmsgs.ban(self.channel, hostmask)) self.assertIn(m, [
ircmsgs.bans(self.channel, permutation)
for permutation in itertools.permutations(hostmasks)
])
m = self.getMsg(' ') m = self.getMsg(' ')
self.assertEqual(m.command, 'KICK') self.assertEqual(m.command, 'KICK')
def assertBan(self, query, hostmask, **kwargs): def assertBan(self, query, hostmask, **kwargs):
@ -278,25 +284,29 @@ class ChannelTestCase(ChannelPluginTestCase):
for style in (['exact'], ['account', 'exact']): for style in (['exact'], ['account', 'exact']):
with conf.supybot.protocols.irc.banmask.context(style): with conf.supybot.protocols.irc.banmask.context(style):
self.assertKban('kban --account --exact foobar', self.assertKban('kban --account --exact foobar',
'~a:account1') '~a:account1', 'foobar!user@host.domain.tld')
join() join()
self.assertKban('kban --account foobar', self.assertKban('kban --account foobar',
'~a:account1') '~a:account1')
join() join()
self.assertKban('kban --account --host foobar', self.assertKban('kban --account --host foobar',
'~a:account1') '~a:account1', '*!*@host.domain.tld')
join() join()
with conf.supybot.protocols.irc.banmask.context(['account', 'exact']): with conf.supybot.protocols.irc.banmask.context(['account', 'exact']):
self.assertKban('kban foobar', self.assertKban('kban foobar',
'~a:account1') '~a:account1', 'foobar!user@host.domain.tld')
join()
with conf.supybot.protocols.irc.banmask.context(['account', 'host']):
self.assertKban('kban foobar',
'~a:account1', '*!*@host.domain.tld')
join() join()
def testBan(self): def testBan(self):
with conf.supybot.protocols.irc.banmask.context(['exact']): with conf.supybot.protocols.irc.banmask.context(['exact']):
self.assertNotError('ban add foo!bar@baz') self.assertNotError('ban add foo!bar@baz')
self.assertNotError('ban remove foo!bar@baz') self.assertNotError('ban remove foo!bar@baz')
orig = conf.supybot.protocols.irc.strictRfc()
with conf.supybot.protocols.irc.strictRfc.context(True): with conf.supybot.protocols.irc.strictRfc.context(True):
# something wonky is going on here. irc.error (src/Channel.py|449) # something wonky is going on here. irc.error (src/Channel.py|449)
# is being called but the assert is failing # is being called but the assert is failing
@ -322,7 +332,6 @@ class ChannelTestCase(ChannelPluginTestCase):
'"foobar!*@baz" (never expires)') '"foobar!*@baz" (never expires)')
def testIgnore(self): def testIgnore(self):
orig = conf.supybot.protocols.irc.banmask()
def ignore(given, expect=None): def ignore(given, expect=None):
if expect is None: if expect is None:
expect = given expect = given
@ -330,6 +339,7 @@ class ChannelTestCase(ChannelPluginTestCase):
self.assertResponse('channel ignore list', "'%s'" % expect) self.assertResponse('channel ignore list', "'%s'" % expect)
self.assertNotError('channel ignore remove %s' % expect) self.assertNotError('channel ignore remove %s' % expect)
self.assertRegexp('channel ignore list', 'not currently') self.assertRegexp('channel ignore list', 'not currently')
with conf.supybot.protocols.irc.banmask.context(['host']):
ignore('foo!bar@baz', '*!*@baz') ignore('foo!bar@baz', '*!*@baz')
ignore('foo!*@*') ignore('foo!*@*')
with conf.supybot.protocols.irc.banmask.context(['exact']): with conf.supybot.protocols.irc.banmask.context(['exact']):

View File

@ -437,11 +437,11 @@ def getBanmask(irc, msg, args, state):
state.args[-1] = banmaskstyle.makeBanmask(state.args[-1], state.args[-1] = banmaskstyle.makeBanmask(state.args[-1],
channel=state.channel, network=irc.network) channel=state.channel, network=irc.network)
def getExtBanmask(irc, msg, args, state): def getExtBanmasks(irc, msg, args, state):
getHostmask(irc, msg, args, state) getHostmask(irc, msg, args, state)
getChannel(irc, msg, args, state) getChannel(irc, msg, args, state)
banmaskstyle = conf.supybot.protocols.irc.extbanmask banmaskstyle = conf.supybot.protocols.irc.extbanmask
state.args[-1] = banmaskstyle.makeExtBanmask(state.args[-1], state.args[-1] = banmaskstyle.makeExtBanmasks(state.args[-1],
channel=state.channel, network=irc.network) channel=state.channel, network=irc.network)
def getUser(irc, msg, args, state): def getUser(irc, msg, args, state):
@ -813,7 +813,7 @@ wrappers = ircutils.IrcDict({
'commandName': getCommandName, 'commandName': getCommandName,
'email': getEmail, 'email': getEmail,
'expiry': getExpiry, 'expiry': getExpiry,
'extbanmask': getExtBanmask, 'extbanmasks': getExtBanmasks,
'filename': getSomething, # XXX Check for validity. 'filename': getSomething, # XXX Check for validity.
'float': getFloat, 'float': getFloat,
'glob': getGlob, 'glob': getGlob,

View File

@ -1215,7 +1215,7 @@ class Banmask(registry.SpaceSeparatedSetOfStrings):
isn't specified via options, the value of isn't specified via options, the value of
conf.supybot.protocols.irc.banmask is used. conf.supybot.protocols.irc.banmask is used.
Unlike :meth:`makeExtBanmask`, this is guaranteed to return an Unlike :meth:`makeExtBanmasks`, this is guaranteed to return an
RFC1459-like mask, suitable for ircdb's ignore lists. RFC1459-like mask, suitable for ircdb's ignore lists.
options - A list specifying which parts of the hostmask should options - A list specifying which parts of the hostmask should
@ -1228,10 +1228,15 @@ class Banmask(registry.SpaceSeparatedSetOfStrings):
options = supybot.protocols.irc.banmask.getSpecific( options = supybot.protocols.irc.banmask.getSpecific(
network, channel)() network, channel)()
options = [option for option in options if option != 'account'] options = [option for option in options if option != 'account']
return self.makeExtBanmask(hostmask, options, channel, network=network) print(hostmask, options)
masks = self.makeExtBanmasks(
hostmask, options, channel, network=network)
assert len(masks) == 1, 'Unexpected number of banmasks: %r' % masks
print(masks)
return masks[0]
def makeExtBanmask(self, hostmask, options=None, channel=None, *, network): def makeExtBanmasks(self, hostmask, options=None, channel=None, *, network):
"""Create a banmask from the given hostmask. If a style of banmask """Create banmasks from the given hostmask. If a style of banmask
isn't specified via options, the value of isn't specified via options, the value of
conf.supybot.protocols.irc.banmask is used. conf.supybot.protocols.irc.banmask is used.
@ -1256,15 +1261,22 @@ class Banmask(registry.SpaceSeparatedSetOfStrings):
if not options: if not options:
options = supybot.protocols.irc.banmask.getSpecific( options = supybot.protocols.irc.banmask.getSpecific(
network, channel)() network, channel)()
add_star_mask = False
masks = []
for option in options: for option in options:
if option == 'nick': if option == 'nick':
bnick = nick bnick = nick
add_star_mask = True
elif option == 'user': elif option == 'user':
buser = user buser = user
add_star_mask = True
elif option == 'host': elif option == 'host':
bhost = host bhost = host
add_star_mask = True
elif option == 'exact': elif option == 'exact':
return hostmask masks.append(hostmask)
elif option == 'account': elif option == 'account':
import supybot.world as world import supybot.world as world
irc = world.getIrc(network) irc = world.getIrc(network)
@ -1272,11 +1284,18 @@ class Banmask(registry.SpaceSeparatedSetOfStrings):
continue continue
extban = ircutils.accountExtban(nick, irc) extban = ircutils.accountExtban(nick, irc)
if extban is not None: if extban is not None:
return extban masks.append(extban)
if add_star_mask and (bnick, buser, bhost) != ('*', '*', '*'):
masks.append(ircutils.joinHostmask(bnick, buser, bhost))
if (bnick, buser, bhost) == ('*', '*', '*') and \ if (bnick, buser, bhost) == ('*', '*', '*') and \
ircutils.isUserHostmask(hostmask): ircutils.isUserHostmask(hostmask) and \
return hostmask masks == []:
return ircutils.joinHostmask(bnick, buser, bhost) masks.append(hostmask)
return masks
registerChannelValue(supybot.protocols.irc, 'banmask', registerChannelValue(supybot.protocols.irc, 'banmask',
Banmask(['host'], _("""Determines what will be used as the Banmask(['host'], _("""Determines what will be used as the