Пример #1
0
    def help_for_command(self, irc, command):
        acl = ACLController()
        callbacks = self.event.get_command_callbacks(command)
        found_help = False

        if callbacks is None:
            irc.reply("No such command")
            return

        for plugin, callback, privileged in callbacks:
            # Don't show help for privileged commands if the user doesn't have access
            if privileged and not acl.check_access(irc.message.source, command):
                continue

            doc = callback.__doc__
            if doc:
                found_help = True

                if hasattr(plugin, "name"):
                    irc.notice(irc.message.source.nick, plugin.name + " - " + command)

                # Filter out empty lines
                lines = [line for line in doc.splitlines() if len(line) > 0]

                # Find minimum whitespace so we can normalize doc-comment
                min_whitespace = min([len(x) - len(x.lstrip()) for x in lines])

                for line in lines:
                    # Tweak whitespace and replace tabs with spaces
                    line = line[min_whitespace:].replace("\t", "    ")
                    irc.notice(irc.message.source.nick, line)

        if not found_help:
            irc.reply("No help available")
Пример #2
0
    def list_commands(self, irc):
        acl = ACLController()
        commands = self.event.get_all_commands()
        num_columns = 6

        cmdlist = []

        for command in commands:
            callbacks = self.event.get_command_callbacks(command)

            masters = self.config.get('masters')
            if masters:
                masterAccess = reduce(lambda x, y : x or y, [irc.message.source.is_matching(hostmask) for hostmask in masters])
            else:
                masterAccess = False

            for _, _, privileged in callbacks:
                if privileged and not (masterAccess or acl.check_access(irc.message.source, command)):
                    pass
                else:
                    if privileged:
                        cmdlist.append("*" + command)
                    else:
                        cmdlist.append(" " + command)

        table = TableFormatter(['']*num_columns, 'Available commands')

        while len(cmdlist) > 0:
            row = cmdlist[0:num_columns]
            row += [' '] * (num_columns - len(row))
            table.add_row(row)
            del cmdlist[0:num_columns]

        for row in table.get_table(column_headers = False):
            irc.notice(irc.message.source.nick, row)
Пример #3
0
class EventController(Singleton):
    ## @brief Constructor
    def construct(self):
        self.commandCallbacks = {}  # "command" : [(object_instance, callback, privileged), ...]
        self.eventCallbacks = {}    # "event"   : [(object_instance, callback), ...]
        self.syseventCallbacks = {} # "sysevent": [(object_instance, callback), ...]

        # {obj: timer}
        self.moduleTimers = {}

        self.config = ConfigController()
        self.acl = ACLController()

        self.command_prefix = "!"

        # Number of active dispatched event threads
        self._pending_events = 0
        self._pending_events_lock = threading.RLock()
        self._pending_events_event = threading.Event()

    ##
    # Increment the number of active event threads
    def _increment_event_threads(self):
        with self._pending_events_lock:
            self._pending_events += 1
            self._pending_events_event.clear()

    ##
    # Decrement the number of active event threads
    # and signal any waiters if the number of event
    # threads reaches zero
    def _decrement_event_threads(self):
        with self._pending_events_lock:
            self._pending_events -= 1
            if self._pending_events == 0:
                self._pending_events_event.set()

    ##
    # Block until all dispatched events have finished.
    # This is primarily used by the simulator.
    def wait_for_pending_events(self):
        self._pending_events_event.wait()

    ## @brief Release callbacks registered by a module
    # @param obj Module instance for which callbacks should be released
    # @warning This should NEVER be called directly by a module
    def release_related(self, obj):
        for table in [self.commandCallbacks,
                      self.eventCallbacks,
                      self.syseventCallbacks]:

            for key in table.keys():
                entries = [entry for entry in table[key] if entry[0] == obj]

                for entry in entries:
                    table[key].remove(entry)

                if len(table[key]) == 0:
                    del table[key]

        # Stop timers
        if self.moduleTimers.has_key(obj):
            for timer in self.moduleTimers[obj]:
                timer.cancel()

            del self.moduleTimers[obj]

    ## @brief Run a specified function after a specified time
    # @param callback Function to call
    # @param interval Time to wait before calling the callback function
    # @param args (Optional) Tuple containing arguments in the order they should be passed
    # @param kwargs (Optional) Dict containing named arguments to pass to the callback
    def register_timer(self, callback, interval, args = (), kwargs = {}):
        def do_timeout(data):
            callback = data["callback"]
            try:
                callback(*data["args"], **data["kwargs"])
            except Exception as e:
                Logger.warning("Timer caught exception: %s" % e)

            # Free timer
            if self.moduleTimers.has_key(callback.im_self):
                for timer in self.moduleTimers[callback.im_self]:
                    if timer == threading.currentThread():
                        self.moduleTimers[callback.im_self].remove(timer)

                if len(self.moduleTimers[callback.im_self]) == 0:
                    del self.moduleTimers[callback.im_self]

        data = {
            "callback": callback,
            "args"  : args,
            "kwargs": kwargs,
        }

        if not hasattr(callback, "im_self"):
            raise Exception("Only class methods can be registered as timer callbacks")

        # Create timer object with associated data
        timer = threading.Timer(interval, do_timeout, kwargs = {"data": data})

        # Insert timer in list of active timers
        if not self.moduleTimers.has_key(callback.im_self):
            self.moduleTimers[callback.im_self] = []
        self.moduleTimers[callback.im_self].append(timer)

        # Dispatch timer
        timer.start()

    ## @brief Register command callback
    # @param command Command excluding command prefix. Example: "mycommand"
    # @param callback Function to execute when this command is dispatched
    # @param privileged Whether or not the command is privileged or not and subject to ACL
    #
    # @note
    # <pre>
    # Callback functions should have the form
    # def callback(ModuleInterface, params)
    # Where params will be the parameters of the command. Example: "!mycommand param1 param2 param3" will yield "param1 param2 param3"
    # </pre>
    def register_command(self, command, callback, privileged = False):
        key = command.lower()

        if not self.commandCallbacks.has_key(key):
            self.commandCallbacks[key] = []

        self.commandCallbacks[key].append((callback.im_self, callback, privileged))

    ##
    # Retrieve all callbacks associated with a command
    # @param command Command to find associated callbacks for
    # @return List of tuples (classinstance, callback, privileged?)
    def get_command_callbacks(self, command):
        key = command.lower()

        if not self.commandCallbacks.has_key(key):
            return []

        return self.commandCallbacks[key]

    ##
    # Retrieve a list of all available commands
    def get_all_commands(self):
        return self.commandCallbacks.keys()

    ## @brief Dispatch a command
    # @param irc IRCController instance
    # @param msg IRCMessage instance
    # @param command The command. Example: "mycommand"
    # @param params Parameters to the command. Example: "param1 param2 param3"
    def dispatch_command(self, irc, msg, command, params):
        def dispatcher_command_thread(callback, interface, params):
            try:
                callback(interface, params)
            except:
                Logger.log_traceback(callback.im_self)
            finally:
                self._decrement_event_threads()

        params = Arguments(params)

        callbacks = self.get_command_callbacks(command)

        Logger.debug1("Found %d receiver(s) for command '%s'" % (len(callbacks), command))

        if callbacks:
            interface = IRCMessageController(irc, msg)

            for (obj, callback, privileged) in callbacks:
                try:
                    # This will abort instantly as soon as a command without access is found
                    if privileged:
                        if not self.acl.check_access(msg.source, command.lower()):
                            interface.reply("Access denied")
                            return

                    thread = threading.Thread(target = dispatcher_command_thread,
                                              kwargs = {"callback": callback, "interface": interface, "params": params})
                    self._increment_event_threads()
                    thread.start()
                except Exception as e:
                    Logger.log_traceback(callback.im_self)

    ## @brief Register event callback
    # Register callback for an IRC-event, this could be any command the server sends, including numerics.
    # @param callback Function to execute when this event is dispatched
    # @param event Event to register. Example: "PRIVMSG", "PART", "QUIT", "001"
    #
    # @note
    # <pre>
    # Callback functions should have the form
    # def callback(IRCMessageController)
    # </pre>
    def register_event(self, event, callback):
        key = str(event).upper()
        if not self.eventCallbacks.has_key(key):
            self.eventCallbacks[key] = []

        self.eventCallbacks[key].append((callback.im_self, callback))

    ##
    # Register a system event callback
    # @param event Event code
    # @param callback Callback function to receive the event
    def register_system_event(self, event, callback):
        key = str(event).upper()
        if not self.syseventCallbacks.has_key(key):
            self.syseventCallbacks[key] = []

        self.syseventCallbacks[key].append((callback.im_self, callback))

    ##
    # Dispatch an bot internal event
    # @param event Event code
    # @param params Parameters to send to the event handler (optional)
    def dispatch_system_event(self, event, params = []):
        key = str(event).upper()
        def dispatcher_event_thread(callback, params):
            try:
                callback(params)
            except:
                Logger.log_traceback(callback.im_self)

        if self.syseventCallbacks.has_key(key):
            for (_, callback) in self.syseventCallbacks[key]:
                thread = threading.Thread(target = dispatcher_event_thread, kwargs = {"callback": callback, "params": params})
                thread.start()

    ## @brief Dispatch an event
    # @param irc IRCController instance
    # @param msg IRCMessage instance. IRCMessage.command contains the event.
    def dispatch_event(self, irc, msg):
        def dispatcher_event_thread(callback, interface):
            try:
                callback(interface)
            except:
                Logger.log_traceback(callback.im_self)
            finally:
                self._decrement_event_threads()

        if not msg.command:
            return

        # Dispatch the event
        key = msg.command.upper()
        if self.eventCallbacks.has_key(key):
            interface = IRCMessageController(irc, msg)

            for (obj, callback) in self.eventCallbacks[key]:
                thread = threading.Thread(target = dispatcher_event_thread,
                                          kwargs = {"callback": callback, "interface": interface})
                self._increment_event_threads()
                thread.start()

        # Check if it may be a command
        if msg.command == "PRIVMSG":
            match = re.match("^%s([^ ]+)(?:$| (.*)$)" % self.command_prefix, msg.params)
            if match:
                command, params = match.groups()
                self.dispatch_command(irc, msg, command, params)
Пример #4
0
class TestACLController(unittest.TestCase):
    def setUp(self):
        self.tempdir = tempfile.mkdtemp()
        self.acl = ACLController(self.tempdir + "/acl.db")

    def tearDown(self):
        shutil.rmtree(self.tempdir)

    def testCreateDeleteUsers(self):
        self.acl.add_user('bill')
        self.acl.add_user('bob')
        self.acl.add_user('anna')
        self.acl.add_user('linda')

        self.assertRaises(Exception, self.acl.add_user, 'linda')
        self.assertRaises(Exception, self.acl.del_user, 'user_that_doesnt_exist')

    def testAddDelHostMask(self):
        self.acl.add_user('theuser')
        self.acl.user_add_hostmask('theuser', '*[email protected]')
        self.acl.user_add_hostmask('theuser', '*[email protected]')

        self.assertRaises(Exception, self.acl.user_add_hostmask, 'theuser', 'invalid hostmask')
        self.assertRaises(Exception, self.acl.user_add_hostmask, 'theuser', '*!*invalid hostmask')
        self.assertRaises(Exception, self.acl.user_add_hostmask, 'theuser', 'invalid!hostmask')
        self.assertRaises(Exception, self.acl.user_add_hostmask, 'theuser', '')
        self.assertRaises(Exception, self.acl.user_add_hostmask, 'user_that_doesnt_exist', 'anyhostmask')
        self.assertRaises(Exception, self.acl.user_del_hostmask, 'user_that_doesnt_exist', 'anyhostmask')
        self.assertRaises(Exception, self.acl.user_del_hostmask, 'theuser', 'hostmask_that_doesnt_exist')

        self.acl.user_del_hostmask('theuser', '*[email protected]')

    def testAddDelGroups(self):
        self.acl.add_group("mygroup")
        self.acl.del_group("mygroup")

        self.acl.add_group("duplicate")
        self.assertRaises(Exception, self.acl.add_group, "duplicate")

        self.assertRaises(Exception, self.acl.del_group, "nonexistant")

    def testMatchHostmasks(self):
        self.acl.add_user('theuser')
        self.acl.user_add_hostmask('theuser', '[email protected]')

        self.acl.add_user('superuser')
        self.acl.user_add_hostmask('superuser', '[email protected]')

        self.acl.add_user('baduser')
        self.acl.user_add_hostmask('baduser', '[email protected]')

        identityNormal = IRCUser('[email protected]')
        identitySuper  = IRCUser('[email protected]')
        identityBad    = IRCUser('[email protected]')

        self.assertRaises(Exception, self.acl.access_allow, 'secret', 'nosuchuser')
        self.acl.access_allow('secret', 'superuser')

        self.assertFalse(self.acl.check_access(identityNormal, 'secret'))
        self.assertTrue(self.acl.check_access(identitySuper, 'secret'))
        self.assertFalse(self.acl.check_access(identitySuper, 'SECRET'))
        self.assertFalse(self.acl.check_access(identitySuper, 'secret2'))

        # Test group access
        self.acl.add_group('superusers')
        self.acl.group_add_user('superuser', 'superusers')

        self.acl.add_group('normalusers')
        self.acl.group_add_user('theuser', 'normalusers')

        # "any"-group
        self.acl.access_allow('foreveryone', 'any')
        self.assertTrue(self.acl.check_access(identityNormal, 'foreveryone'))

        # Exclusion (Ticket #1)
        #self.assertTrue(self.acl.check_access(identityBad, 'foreveryone'))
        #self.acl.access_deny('foreveryone', 'baduser')
        #self.assertFalse(self.acl.check_access(identityBad, 'foreveryone'))

        # Group membership
        self.acl.access_allow('supersecret', 'superusers')
        self.assertTrue(self.acl.check_access(identitySuper, 'supersecret'))
        self.assertFalse(self.acl.check_access(identityNormal, 'supersecret'))
        self.assertFalse(self.acl.check_access(identityBad, 'supersecret'))

    def testMasterAccess(self):
        config = ConfigController()
        config.load_defaults()
        config.get('masters').append("*[email protected]")

        identityMaster = IRCUser('[email protected]')
        identityNormal = IRCUser('[email protected]')

        self.assertTrue(self.acl.check_access(identityMaster, 'something'))
        self.assertFalse(self.acl.check_access(identityNormal, 'something'))