Skip to content

Commit

Permalink
Merge pull request #1546 from progval/account-ban
Browse files Browse the repository at this point in the history
Add support for account-based channel bans
  • Loading branch information
progval authored Jul 24, 2024
2 parents f7b8470 + be3dae3 commit 10a341c
Show file tree
Hide file tree
Showing 10 changed files with 512 additions and 38 deletions.
5 changes: 3 additions & 2 deletions plugins/AutoMode/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,9 @@ def unban():
# We're not in the channel anymore.
pass
schedule.addEvent(unban, time.time()+period)
banmask =conf.supybot.protocols.irc.banmask.makeBanmask(msg.prefix)
irc.queueMsg(ircmsgs.ban(channel, banmask))
banmasks = conf.supybot.protocols.irc.banmask.makeExtBanmasks(
msg.prefix, channel=channel, network=irc.network)
irc.queueMsg(ircmsgs.bans(channel, banmasks))
irc.queueMsg(ircmsgs.kick(channel, msg.nick))

try:
Expand Down
68 changes: 50 additions & 18 deletions plugins/Channel/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,13 +321,16 @@ def kban(self, irc, msg, args,
--exact bans only the exact hostmask; --nick bans just the nick;
--user bans just the user, and --host bans just the host
You can combine the --nick, --user, and --host options as you choose.
If --account is provided and the user is logged in and the network
supports account bans, this will ban the user's account instead.
<channel> is only necessary if the message isn't sent in the channel itself.
"""
self._ban(irc, msg, args,
channel, optlist, bannedNick, expiry, reason, True)
kban = wrap(kban,
['op',
getopts({'exact':'', 'nick':'', 'user':'', 'host':''}),
getopts({'exact':'', 'nick':'', 'user':'', 'host':'',
'account': ''}),
('haveHalfop+', _('kick or ban someone')),
'nickInChannel',
optional('expiry', 0),
Expand All @@ -343,13 +346,16 @@ def iban(self, irc, msg, args,
don't specify a number of seconds) it will ban the person indefinitely.
--exact can be used to specify an exact hostmask.
You can combine the --nick, --user, and --host options as you choose.
If --account is provided and the user is logged in and the network
supports account bans, this will ban the user's account instead.
<channel> is only necessary if the message isn't sent in the channel itself.
"""
self._ban(irc, msg, args,
channel, optlist, bannedNick, expiry, None, False)
iban = wrap(iban,
['op',
getopts({'exact':'', 'nick':'', 'user':'', 'host':''}),
getopts({'exact':'', 'nick':'', 'user':'', 'host':'',
'account': ''}),
('haveHalfop+', _('ban someone')),
first('nick', 'hostmask'),
optional('expiry', 0)])
Expand All @@ -362,18 +368,22 @@ def _ban(self, irc, msg, args,
try:
bannedHostmask = irc.state.nickToHostmask(target)
banmaskstyle = conf.supybot.protocols.irc.banmask
banmask = banmaskstyle.makeBanmask(bannedHostmask, [o[0] for o in optlist])
banmasks = banmaskstyle.makeExtBanmasks(
bannedHostmask, [o[0] for o in optlist],
channel=channel, network=irc.network)
except KeyError:
if not conf.supybot.protocols.irc.strictRfc() and \
target.startswith('$'):
# Select the last part, or the whole target:
bannedNick = target.split(':')[-1]
banmask = bannedHostmask = target
bannedHostmask = target
banmasks = [bannedHostmask]
else:
irc.error(format(_('I haven\'t seen %s.'), bannedNick), Raise=True)
else:
bannedNick = ircutils.nickFromHostmask(target)
banmask = bannedHostmask = target
bannedHostmask = target
banmasks = [bannedHostmask]
if not irc.isNick(bannedNick):
self.log.warning('%q tried to kban a non nick: %q',
msg.prefix, bannedNick)
Expand All @@ -389,30 +399,47 @@ def _ban(self, irc, msg, args,
if not reason:
reason = msg.nick
capability = ircdb.makeChannelCapability(channel, 'op')

# Check (again) that they're not trying to make us kickban ourself.
if ircutils.hostmaskPatternEqual(banmask, irc.prefix):
if ircutils.hostmaskPatternEqual(bannedHostmask, irc.prefix):
self_account_extban = ircutils.accountExtban(irc, irc.nick)
for banmask in banmasks:
if ircutils.hostmaskPatternEqual(banmask, irc.prefix):
if ircutils.hostmaskPatternEqual(bannedHostmask, irc.prefix):
self.log.warning('%q tried to make me kban myself.',msg.prefix)
irc.error(_('I cowardly refuse to ban myself.'))
return
else:
self.log.warning('Using exact hostmask since banmask would '
'ban myself.')
banmasks = [bannedHostmask]
elif self_account_extban is not None \
and banmask.lower() == self_account_extban.lower():
self.log.warning('%q tried to make me kban myself.',msg.prefix)
irc.error(_('I cowardly refuse to ban myself.'))
return
else:
self.log.warning('Using exact hostmask since banmask would '
'ban myself.')
banmask = bannedHostmask


# 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
# bannee and the banner are both the same person.
def doBan():
if irc.state.channels[channel].isOp(bannedNick):
irc.queueMsg(ircmsgs.deop(channel, bannedNick))
irc.queueMsg(ircmsgs.ban(channel, banmask))
irc.queueMsg(ircmsgs.bans(channel, banmasks))
if kick:
irc.queueMsg(ircmsgs.kick(channel, bannedNick, reason))
if expiry > 0:
def f():
if channel in irc.state.channels and \
banmask in irc.state.channels[channel].bans:
irc.queueMsg(ircmsgs.unban(channel, banmask))
if channel not in irc.state.channels:
return
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)
if bannedNick == msg.nick:
doBan()
Expand Down Expand Up @@ -583,7 +610,7 @@ def hostmask(self, irc, msg, args, channel, banmask):
hostmask = wrap(hostmask, ['op', ('haveHalfop+', _('ban someone')), 'text'])

@internationalizeDocstring
def add(self, irc, msg, args, channel, banmask, expires):
def add(self, irc, msg, args, channel, banmasks, expires):
"""[<channel>] <nick|hostmask> [<expires>]
If you have the #channel,op capability, this will effect a
Expand All @@ -597,10 +624,15 @@ def add(self, irc, msg, args, channel, banmask, expires):
channel itself.
"""
c = ircdb.channels.getChannel(channel)
c.addBan(banmask, expires)
if isinstance(banmasks, str):
banmasks = [banmasks]
for banmask in banmasks:
c.addBan(banmask, expires)
ircdb.channels.setChannel(channel, c)
irc.replySuccess()
add = wrap(add, ['op', first('hostmask', 'banmask'), additional('expiry', 0)])
add = wrap(add, ['op',
first('hostmask', 'extbanmasks'),
additional('expiry', 0)])

@internationalizeDocstring
def remove(self, irc, msg, args, channel, banmask):
Expand Down
137 changes: 131 additions & 6 deletions plugins/Channel/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,13 @@ def testVoice(self):
self.assertTrue(m.command == 'MODE' and
m.args == (self.channel, '+v', 'bar'))

def assertKban(self, query, hostmask, **kwargs):
def assertKban(self, query, *hostmasks, **kwargs):
m = self.getMsg(query, **kwargs)
self.assertEqual(m, ircmsgs.ban(self.channel, hostmask))
self.assertEqual(m.command, "MODE", m)
self.assertEqual(m.args[0], self.channel, m)
self.assertEqual(m.args[1], "+" + "b" * len(hostmasks), m)
self.assertCountEqual(m.args[2:], hostmasks, m)

m = self.getMsg(' ')
self.assertEqual(m.command, 'KICK')
def assertBan(self, query, hostmask, **kwargs):
Expand All @@ -185,6 +189,30 @@ def testIban(self):
self.assertBan('iban $a:nyuszika7h', '$a:nyuszika7h')
self.assertNotError('unban $a:nyuszika7h')

def testWontIbanItself(self):
self.irc.state.supported['ACCOUNTEXTBAN'] = 'a,account'
self.irc.state.supported['EXTBAN'] = '~,abc'

self.irc.feedMsg(ircmsgs.join(self.channel,
prefix='foobar!user@host.domain.tld'))
self.irc.feedMsg(ircmsgs.op(self.channel, self.irc.nick))

# not authenticated, falls back to hostname and notices the match
self.assertError('iban --account ' + self.nick)

self.irc.feedMsg(ircmsgs.IrcMsg(prefix=self.prefix, command='ACCOUNT',
args=['botaccount']))

# notices the matching account
self.assertError('iban --account ' + self.nick)

self.irc.feedMsg(ircmsgs.IrcMsg(prefix='othernick!otheruser@otherhost',
command='ACCOUNT',
args=['botaccount']))

# ditto
self.assertError('iban --account othernick')

def testKban(self):
self.irc.prefix = 'something!else@somehwere.else'
self.irc.nick = 'something'
Expand Down Expand Up @@ -219,11 +247,108 @@ def join():

self.assertRegexp('kban adlkfajsdlfkjsd', 'adlkfajsdlfkjsd is not in')

def testAccountKbanNoAccount(self):
self.irc.prefix = 'something!else@somehwere.else'
self.irc.nick = 'something'
self.irc.state.supported['ACCOUNTEXTBAN'] = 'a,account'
self.irc.state.supported['EXTBAN'] = '~,abc'
def join():
self.irc.feedMsg(ircmsgs.join(
self.channel, prefix='foobar!user@host.domain.tld'))
join()
self.irc.feedMsg(ircmsgs.op(self.channel, self.irc.nick))
self.assertKban('kban --account --exact foobar',
'foobar!user@host.domain.tld')
join()
self.assertKban('kban --account foobar',
'*!*@host.domain.tld')
join()
with conf.supybot.protocols.irc.banmask.context(['user', 'host']):
# falls back from --account to config
self.assertKban('kban --account foobar',
'*!user@host.domain.tld')
join()
with conf.supybot.protocols.irc.banmask.context(['account']):
# falls back from --account to config, then to only the host
self.assertKban('kban --account foobar',
'*!*@host.domain.tld')
join()
self.assertKban('kban --account --host foobar',
'*!*@host.domain.tld')

def testAccountKbanLoggedOut(self):
self.irc.prefix = 'something!else@somehwere.else'
self.irc.nick = 'something'
self.irc.state.supported['ACCOUNTEXTBAN'] = 'a,account'
self.irc.state.supported['EXTBAN'] = '~,abc'
self.irc.feedMsg(ircmsgs.IrcMsg(
prefix='foobar!user@host.domain.tld',
command='ACCOUNT', args=['*']))
def join():
self.irc.feedMsg(ircmsgs.join(
self.channel, prefix='foobar!user@host.domain.tld'))
join()
self.irc.feedMsg(ircmsgs.op(self.channel, self.irc.nick))
self.assertKban('kban --account --exact foobar',
'foobar!user@host.domain.tld')
join()
self.assertKban('kban --account foobar',
'*!*@host.domain.tld')
join()
with conf.supybot.protocols.irc.banmask.context(['user', 'host']):
# falls back from --account to config
self.assertKban('kban --account foobar',
'*!user@host.domain.tld')
join()
with conf.supybot.protocols.irc.banmask.context(['account']):
# falls back from --account to config, then to only the host
self.assertKban('kban --account foobar',
'*!*@host.domain.tld')
join()
self.assertKban('kban --account --host foobar',
'*!*@host.domain.tld')

def testAccountKbanLoggedIn(self):
self.irc.prefix = 'something!else@somehwere.else'
self.irc.nick = 'something'
self.irc.state.supported['ACCOUNTEXTBAN'] = 'a,account'
self.irc.state.supported['EXTBAN'] = '~,abc'
self.irc.feedMsg(ircmsgs.IrcMsg(
prefix='foobar!user@host.domain.tld',
command='ACCOUNT', args=['account1']))
def join():
self.irc.feedMsg(ircmsgs.join(
self.channel, prefix='foobar!user@host.domain.tld'))
join()
self.irc.feedMsg(ircmsgs.op(self.channel, self.irc.nick))


for style in (['exact'], ['account', 'exact']):
with conf.supybot.protocols.irc.banmask.context(style):
self.assertKban('kban --account --exact foobar',
'~a:account1', 'foobar!user@host.domain.tld')
join()
self.assertKban('kban --account foobar',
'~a:account1')
join()
self.assertKban('kban --account --host foobar',
'~a:account1', '*!*@host.domain.tld')
join()

with conf.supybot.protocols.irc.banmask.context(['account', 'exact']):
self.assertKban('kban foobar',
'~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()

def testBan(self):
with conf.supybot.protocols.irc.banmask.context(['exact']):
self.assertNotError('ban add foo!bar@baz')
self.assertNotError('ban remove foo!bar@baz')
orig = conf.supybot.protocols.irc.strictRfc()
with conf.supybot.protocols.irc.strictRfc.context(True):
# something wonky is going on here. irc.error (src/Channel.py|449)
# is being called but the assert is failing
Expand All @@ -249,16 +374,16 @@ def testBanList(self):
'"foobar!*@baz" (never expires)')

def testIgnore(self):
orig = conf.supybot.protocols.irc.banmask()
def ignore(given, expect=None):
if expect is None:
expect = given
self.assertNotError('channel ignore add %s' % given)
self.assertResponse('channel ignore list', "'%s'" % expect)
self.assertNotError('channel ignore remove %s' % expect)
self.assertRegexp('channel ignore list', 'not currently')
ignore('foo!bar@baz', '*!*@baz')
ignore('foo!*@*')
with conf.supybot.protocols.irc.banmask.context(['host']):
ignore('foo!bar@baz', '*!*@baz')
ignore('foo!*@*')
with conf.supybot.protocols.irc.banmask.context(['exact']):
ignore('foo!bar@baz')
ignore('foo!*@*')
Expand Down
10 changes: 9 additions & 1 deletion src/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,14 @@ def getBanmask(irc, msg, args, state):
getChannel(irc, msg, args, state)
banmaskstyle = conf.supybot.protocols.irc.banmask
state.args[-1] = banmaskstyle.makeBanmask(state.args[-1],
channel=state.channel)
channel=state.channel, network=irc.network)

def getExtBanmasks(irc, msg, args, state):
getHostmask(irc, msg, args, state)
getChannel(irc, msg, args, state)
banmaskstyle = conf.supybot.protocols.irc.extbanmask
state.args[-1] = banmaskstyle.makeExtBanmasks(state.args[-1],
channel=state.channel, network=irc.network)

def getUser(irc, msg, args, state):
try:
Expand Down Expand Up @@ -806,6 +813,7 @@ def getText(irc, msg, args, state):
'commandName': getCommandName,
'email': getEmail,
'expiry': getExpiry,
'extbanmasks': getExtBanmasks,
'filename': getSomething, # XXX Check for validity.
'float': getFloat,
'glob': getGlob,
Expand Down
Loading

0 comments on commit 10a341c

Please sign in to comment.