Merge "Retool accessbot for OFTC"
This commit is contained in:
commit
2920ba83d5
@ -34,7 +34,6 @@ class SetAccess(irc.client.SimpleIRCClient):
|
||||
|
||||
def __init__(self, config, noop, nick, password, server, port):
|
||||
irc.client.SimpleIRCClient.__init__(self)
|
||||
self.identify_msg_cap = False
|
||||
self.config = config
|
||||
self.nick = nick
|
||||
self.password = password
|
||||
@ -44,6 +43,7 @@ class SetAccess(irc.client.SimpleIRCClient):
|
||||
self.channels = [x['name'] for x in self.config['channels']]
|
||||
self.current_channel = None
|
||||
self.current_list = []
|
||||
self.current_mode = ''
|
||||
self.changes = []
|
||||
self.identified = False
|
||||
if self.port == 6697:
|
||||
@ -56,30 +56,19 @@ class SetAccess(irc.client.SimpleIRCClient):
|
||||
def on_disconnect(self, connection, event):
|
||||
sys.exit(0)
|
||||
|
||||
def on_welcome(self, c, e):
|
||||
self.identify_msg_cap = False
|
||||
self.log.debug("Requesting identify-msg capability")
|
||||
c.cap('REQ', 'identify-msg')
|
||||
c.cap('END')
|
||||
|
||||
def on_cap(self, c, e):
|
||||
self.log.debug("Received cap response %s" % repr(e.arguments))
|
||||
if e.arguments[0] == 'ACK' and 'identify-msg' in e.arguments[1]:
|
||||
self.log.debug("identify-msg cap acked")
|
||||
self.identify_msg_cap = True
|
||||
self.log.debug("Identifying to nickserv")
|
||||
c.privmsg("nickserv", "identify %s " % self.password)
|
||||
|
||||
def on_privnotice(self, c, e):
|
||||
if not self.identify_msg_cap:
|
||||
self.log.debug("Ignoring message because identify-msg "
|
||||
"cap not enabled")
|
||||
return
|
||||
nick = e.source.split('!')[0]
|
||||
auth = e.arguments[0][0]
|
||||
msg = e.arguments[0][1:]
|
||||
if auth == '+' and nick == 'NickServ' and not self.identified:
|
||||
if msg.startswith('You are now identified'):
|
||||
msg = e.arguments[0]
|
||||
if nick == 'NickServ' and not self.identified:
|
||||
if msg.startswith('authenticate yourself to services'):
|
||||
self.log.debug("Identifying to nickserv")
|
||||
# TODO (fungi): We should protect against sending our
|
||||
# password to a false NickServ, perhaps with
|
||||
# https://www.oftc.net/NickServ/CertFP/ or eventually
|
||||
# SASL once the ircd implements that
|
||||
c.privmsg("nickserv", "identify %s " % self.password)
|
||||
return
|
||||
elif msg.startswith('You are successfully identified'):
|
||||
self.identified = True
|
||||
# Prejoin and set ourselves as op in these channels,
|
||||
# to facilitate +f forwarding.
|
||||
@ -88,8 +77,10 @@ class SetAccess(irc.client.SimpleIRCClient):
|
||||
c.privmsg("chanserv", "op #%s" % channel)
|
||||
self.advance()
|
||||
return
|
||||
if auth != '+' or nick != 'ChanServ':
|
||||
self.log.debug("Ignoring message from unauthenticated "
|
||||
else:
|
||||
return
|
||||
if nick not in ('ChanServ', 'NickServ'):
|
||||
self.log.debug("Ignoring message from non-ChanServ "
|
||||
"user %s" % nick)
|
||||
return
|
||||
self.failed = False
|
||||
@ -99,95 +90,78 @@ class SetAccess(irc.client.SimpleIRCClient):
|
||||
ret = {}
|
||||
alumni = []
|
||||
mode = ''
|
||||
level = ''
|
||||
channel = None
|
||||
for c in self.config['channels']:
|
||||
if c['name'] == channel_name:
|
||||
channel = c
|
||||
if channel is None:
|
||||
raise Exception("Unknown channel %s" % (channel_name,))
|
||||
mask = ''
|
||||
for access, nicks in (list(self.config['global'].items()) +
|
||||
for key, value in (list(self.config['global'].items()) +
|
||||
list(channel.items())):
|
||||
if access == 'mask':
|
||||
mask = self.config['access'].get(nicks)
|
||||
if key == 'alumni':
|
||||
alumni += value
|
||||
continue
|
||||
if access == 'alumni':
|
||||
alumni += nicks
|
||||
if key == 'mode':
|
||||
mode = value
|
||||
continue
|
||||
if access == 'mode':
|
||||
mode = nicks
|
||||
continue
|
||||
flags = self.config['access'].get(access)
|
||||
if flags is None:
|
||||
continue
|
||||
for nick in nicks:
|
||||
ret[nick] = flags
|
||||
return mask, ret, alumni, mode
|
||||
|
||||
def _get_access_change(self, current, target, mask):
|
||||
remove = ''
|
||||
add = ''
|
||||
change = ''
|
||||
for x in current:
|
||||
if x in '+-':
|
||||
# If we get this far, we assume the key is an access
|
||||
# level matching an entry in the access list
|
||||
level = self.config['access'].get(key)
|
||||
if level is None:
|
||||
# Skip if this doesn't match a defined access level
|
||||
continue
|
||||
if target:
|
||||
if x not in target:
|
||||
remove += x
|
||||
else:
|
||||
if x not in mask:
|
||||
remove += x
|
||||
for x in target:
|
||||
if x in '+-':
|
||||
continue
|
||||
if x not in current:
|
||||
add += x
|
||||
if remove:
|
||||
change += '-' + remove
|
||||
if add:
|
||||
change += '+' + add
|
||||
return change
|
||||
for nick in value:
|
||||
ret[nick] = level
|
||||
return ret, alumni, mode
|
||||
|
||||
def _get_access_change(self, current, target):
|
||||
if current != target:
|
||||
return target
|
||||
|
||||
def _get_access_changes(self):
|
||||
mask, target, alumni, mode = self._get_access_list(self.current_channel)
|
||||
self.log.debug("Mask for %s: %s" % (self.current_channel, mask))
|
||||
self.log.debug("Target for %s: %s" % (self.current_channel, target))
|
||||
target, alumni, mode = self._get_access_list(
|
||||
self.current_channel)
|
||||
self.log.debug("Target #%s ACL: %s" % (self.current_channel, target))
|
||||
all_nicks = set()
|
||||
global_alumni = self.config.get('alumni', {})
|
||||
global_mode = self.config.get('mode', '')
|
||||
current = {}
|
||||
changes = []
|
||||
for nick, flags, msg in self.current_list:
|
||||
for nick, level, msg in self.current_list:
|
||||
if nick in global_alumni or nick in alumni :
|
||||
self.log.debug("%s is an alumni; removing access", nick)
|
||||
changes.append('access #%s del %s' % (self.current_channel, nick))
|
||||
continue
|
||||
all_nicks.add(nick)
|
||||
current[nick] = flags
|
||||
current[nick] = level
|
||||
for nick in target.keys():
|
||||
all_nicks.add(nick)
|
||||
for nick in all_nicks:
|
||||
change = self._get_access_change(current.get(nick, ''),
|
||||
target.get(nick, ''), mask)
|
||||
target.get(nick, ''))
|
||||
if change:
|
||||
changes.append('access #%s add %s %s' % (self.current_channel,
|
||||
nick, change))
|
||||
|
||||
# Set the mode. Note we always just hard-set the mode for
|
||||
# simplicity (per the man page mlock always clears and sets
|
||||
# anyway). Channel mode overrides global mode.
|
||||
#
|
||||
# Note for +f you need to be op in the target channel; see
|
||||
# op_channel option.
|
||||
# Set the mode if what we want differs from what's already there.
|
||||
# Channel mode overrides global mode.
|
||||
if not mode and global_mode:
|
||||
mode = global_mode
|
||||
self.log.debug("Setting mode to : %s" % mode)
|
||||
if mode:
|
||||
if not mode:
|
||||
mode = '+'
|
||||
if sorted(mode) != sorted(self.current_mode):
|
||||
self.log.debug("Current mode for #%s is %s, replacing with %s" % (
|
||||
self.current_channel, self.current_mode, mode))
|
||||
changes.append('set #%s mlock %s' % (self.current_channel, mode))
|
||||
|
||||
return changes
|
||||
|
||||
def advance(self, msg=None):
|
||||
# Some service responses include a number of embedded 0x02 bytes
|
||||
if msg:
|
||||
msg = msg.replace('\x02', '')
|
||||
if self.changes:
|
||||
if self.noop:
|
||||
for change in self.changes:
|
||||
@ -204,19 +178,41 @@ class SetAccess(irc.client.SimpleIRCClient):
|
||||
self.connection.quit()
|
||||
return
|
||||
self.current_channel = self.channels.pop()
|
||||
# Clear the mode string before we request it, so if we get
|
||||
# no response we won't have the modes from an earlier channel
|
||||
self.current_mode = ''
|
||||
# Sending a set mlock with no value prompts the service to
|
||||
# respond with the current mlock value so we can compare
|
||||
# against it later
|
||||
self.connection.privmsg('chanserv', 'set #%s mlock' %
|
||||
self.current_channel)
|
||||
# Clear the access list before we request it, so if we get
|
||||
# no response we won't have the list from an earlier channel
|
||||
self.current_list = []
|
||||
self.connection.privmsg('chanserv', 'access list #%s' %
|
||||
self.connection.privmsg('chanserv', 'access #%s list' %
|
||||
self.current_channel)
|
||||
time.sleep(1)
|
||||
return
|
||||
if msg.startswith('End of'):
|
||||
# We tokenize every server message, and perform some rough
|
||||
# heuristics in order to determine what kind of response we're
|
||||
# dealing with and whether it's something we know how to parse
|
||||
parts = msg.split()
|
||||
# If the third word look like an access level, assume this is
|
||||
# an access list entry and that the second word is a
|
||||
# corresponding nick
|
||||
if parts[2] in ('MASTER', 'CHANOP', 'MEMBER'):
|
||||
self.current_list.append((parts[1], parts[2], msg))
|
||||
# If the message starts with "MLOCK is SET to" then assume the
|
||||
# fifth word is the channel's mode string
|
||||
elif msg.startswith('MLOCK is SET to'):
|
||||
self.current_mode = parts[4]
|
||||
# If the message starts with "End of" then assume this marks
|
||||
# the end of an access list
|
||||
elif msg.startswith('End of'):
|
||||
self.changes = self._get_access_changes()
|
||||
self.current_channel = None
|
||||
self.advance()
|
||||
return
|
||||
parts = msg.split()
|
||||
if parts[2].startswith('+'):
|
||||
self.current_list.append((parts[1], parts[2], msg))
|
||||
|
||||
|
||||
def main():
|
||||
|
Loading…
Reference in New Issue
Block a user