90fa9170dd
Change-Id: I4e3b3683b33b600cc4c1e4f09ba02c59e6f5cbb9 Fix: TypeError: unsupported operand type(s) for +: 'dict_items' and 'dict_items'
249 lines
8.7 KiB
Python
Executable File
249 lines
8.7 KiB
Python
Executable File
#! /usr/bin/env python
|
|
|
|
# Copyright 2011, 2013-2014 OpenStack Foundation
|
|
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import configparser
|
|
import argparse
|
|
import irc.client
|
|
import logging
|
|
import ssl
|
|
import sys
|
|
import time
|
|
import yaml
|
|
|
|
logging.basicConfig(
|
|
format='%(asctime)s [%(levelname)s] %(name)s - %(message)s',
|
|
level=logging.DEBUG)
|
|
|
|
|
|
class SetAccess(irc.client.SimpleIRCClient):
|
|
log = logging.getLogger("setaccess")
|
|
|
|
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
|
|
self.server = server
|
|
self.port = int(port)
|
|
self.noop = noop
|
|
self.channels = [x['name'] for x in self.config['channels']]
|
|
self.current_channel = None
|
|
self.current_list = []
|
|
self.changes = []
|
|
self.identified = False
|
|
if self.port == 6697:
|
|
factory = irc.connection.Factory(wrapper=ssl.wrap_socket)
|
|
self.connect(self.server, self.port, self.nick,
|
|
connect_factory=factory)
|
|
else:
|
|
self.connect(self.server, self.port, self.nick)
|
|
|
|
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'):
|
|
self.identified = True
|
|
# Prejoin and set ourselves as op in these channels,
|
|
# to facilitate +f forwarding.
|
|
for channel in self.config.get('op_channels', []):
|
|
c.join("#%s" % channel)
|
|
c.privmsg("chanserv", "op #%s" % channel)
|
|
self.advance()
|
|
return
|
|
if auth != '+' or nick != 'ChanServ':
|
|
self.log.debug("Ignoring message from unauthenticated "
|
|
"user %s" % nick)
|
|
return
|
|
self.failed = False
|
|
self.advance(msg)
|
|
|
|
def _get_access_list(self, channel_name):
|
|
ret = {}
|
|
alumni = []
|
|
mode = ''
|
|
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()) +
|
|
list(channel.items())):
|
|
if access == 'mask':
|
|
mask = self.config['access'].get(nicks)
|
|
continue
|
|
if access == 'alumni':
|
|
alumni += nicks
|
|
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 '+-':
|
|
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
|
|
|
|
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))
|
|
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:
|
|
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
|
|
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)
|
|
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.
|
|
if not mode and global_mode:
|
|
mode = global_mode
|
|
self.log.debug("Setting mode to : %s" % mode)
|
|
if mode:
|
|
changes.append('set #%s mlock %s' % (self.current_channel, mode))
|
|
|
|
return changes
|
|
|
|
def advance(self, msg=None):
|
|
if self.changes:
|
|
if self.noop:
|
|
for change in self.changes:
|
|
self.log.info('NOOP: ' + change)
|
|
self.changes = []
|
|
else:
|
|
change = self.changes.pop()
|
|
self.log.info(change)
|
|
self.connection.privmsg('chanserv', change)
|
|
time.sleep(1)
|
|
return
|
|
if not self.current_channel:
|
|
if not self.channels:
|
|
self.connection.quit()
|
|
return
|
|
self.current_channel = self.channels.pop()
|
|
self.current_list = []
|
|
self.connection.privmsg('chanserv', 'access list #%s' %
|
|
self.current_channel)
|
|
time.sleep(1)
|
|
return
|
|
if 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():
|
|
parser = argparse.ArgumentParser(description='IRC channel access check')
|
|
parser.add_argument('-c', dest='config', nargs=1,
|
|
help='specify the config file')
|
|
parser.add_argument('-l', dest='channels',
|
|
default='/etc/irc/channels.yaml',
|
|
help='path to the channel config')
|
|
parser.add_argument('--noop', dest='noop',
|
|
action='store_true',
|
|
help="Don't make any changes")
|
|
args = parser.parse_args()
|
|
|
|
config = configparser.ConfigParser()
|
|
config.read(args.config)
|
|
|
|
channels = yaml.load(open(args.channels))
|
|
|
|
a = SetAccess(channels, args.noop,
|
|
config.get('ircbot', 'nick'),
|
|
config.get('ircbot', 'pass'),
|
|
config.get('ircbot', 'server'),
|
|
config.get('ircbot', 'port'))
|
|
a.start()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|