# HG changeset patch # User Tero Marttila # Date 1206035164 -7200 # Node ID 614161f85d9b1f5a07cd0e91210eb31778756104 # Parent 8e7493df9f521fea855b8c5a5fb8a11da09b3b8d some cleanup, bugfixes, commands for the irc bot, shared-secret for the API committer: Tero Marttila diff -r 8e7493df9f52 -r 614161f85d9b api.py --- a/api.py Thu Mar 20 18:47:58 2008 +0200 +++ b/api.py Thu Mar 20 19:46:04 2008 +0200 @@ -1,12 +1,15 @@ from twisted.internet import protocol, reactor from twisted.python import log from datetime import datetime +import sys import buffer PORT = 34888 SERVER_HOST = "127.0.0.1" +from api_secret import secret + class ModuleInfo (object) : """ Some info about a module @@ -96,6 +99,10 @@ def on_module_init (self, i) : self._assert(not self.module, "module_init with non-None self.module") + peer_secret = i.readVarLen('B') + + self._assert(peer_secret == secret, "Mismatching API secrets!") + m = ModuleInfo() m.name = i.readVarLen('B') @@ -132,7 +139,7 @@ if self.module : return str(self.module) else : - return super(APIProtocol, self).logPrefix() + return super(ServerProtocol, self).logPrefix() class ClientProtocol (buffer.StreamProtocol, protocol.Protocol) : RECV_COMMANDS = SERVER_COMMANDS @@ -142,6 +149,7 @@ log.msg("Connected to API server, sending module init message") o = self.startCommand('module_init') + o.writeVarLen('B', secret) o.writeVarLen('B', self.factory.name) o.writeItem("H", self.factory.version) buffer.writeStringStream(o, 'B', self.factory.event_types) @@ -171,6 +179,11 @@ reactor.connectTCP(SERVER_HOST, PORT, self) self.connection = None + + def run (self) : + log.startLogging(sys.stderr) + + reactor.run() def connected (self, connection) : log.msg("Connected!") diff -r 8e7493df9f52 -r 614161f85d9b irc.py --- a/irc.py Thu Mar 20 18:47:58 2008 +0200 +++ b/irc.py Thu Mar 20 19:46:04 2008 +0200 @@ -1,6 +1,7 @@ from twisted.words.protocols import irc from twisted.internet import protocol from twisted.python import log +import traceback import buffer @@ -10,6 +11,10 @@ USERNAME = "fixme" CHANNEL = "#fixme-test" +class ReplyException (Exception) : + def __init__ (self, reply) : + self.reply = reply + class BotProtocol (irc.IRCClient, object) : """ Fixme IRC bot @@ -23,6 +28,8 @@ def connectionMade (self) : log.msg("Connected") super(BotProtocol, self).connectionMade() + + self.nexus = self.factory.nexus def connectionLost (self, reason) : log.msg("Connection lost: %s" % reason) @@ -57,5 +64,76 @@ def moduleDisconnected (self, module, reason) : self.send("{modules.%s} disconnected: %s" % (module.name, reason)) + + class _noDefault : pass + def _lookupCommand (self, command, default=_noDefault) : + if '.' in command : + raise ReplyException("No support for module commands yet :P") + else : + method = getattr(self, "cmd_%s" % command, None) + if method : + return method + elif default is self._noDefault : + raise ReplyException("No such command '%s'. See `help commands'" % command) + else : + return default + + def privmsg (self, user, channel, message) : + if message.lower().startswith(self.nickname.lower()) : + me, command = message.split(":", 1) + + args = command.strip().split() + command = args.pop(0) + + try : + method = self._lookupCommand(command) + + reply = method(*args) + + if reply : + self.send(reply) + + except ReplyException, e : + self.send(e.reply) + + except Exception, e : + self.send("Error: %s: %s" % (e.__class__.__name__, e)) + traceback.print_exc() + + def cmd_help (self, cmd="help") : + """help - Display help about the given command or module""" + + method = self._lookupCommand(cmd, None) + + if method : + return method.__doc__ + else : + try : + module, addr = self.nexus.getModuleInfo(cmd) + + return "%s is version %d from %s:%d. Events: %s. See `commands %s' for a list of commands" % (module.name, module.version, addr.host, addr.port, ', '.join(module.event_types), module.name) + + except KeyError : + raise ReplyException("No command/module called `%s'. See `help commands'" % cmd) + + def cmd_commands (self, module=None) : + """commands [] - Show primary commands, or commands in the given module (see `help modules')""" + + if module : + raise ReplyException("No support for module commands yet :P") + else : + return "Commands: %s" % ', '.join( + attr_name.split('_', 1)[1] + for attr_name in BotProtocol.__dict__.iterkeys() + if attr_name.startswith("cmd_") + ) + + def cmd_modules (self) : + """modules - Show a list of connected modules""" + + return "Modules: %s" % ', '.join( + module.name + for module in self.nexus.getModules() + ) diff -r 8e7493df9f52 -r 614161f85d9b logwatcher.py --- a/logwatcher.py Thu Mar 20 18:47:58 2008 +0200 +++ b/logwatcher.py Thu Mar 20 19:46:04 2008 +0200 @@ -52,14 +52,31 @@ def _filter (self, match) : return match.string -class SudoFilter (Filter) : - REGEXP = "sudo:\s*(?P\S+) : TTY=(?P\S+) ; PWD=(?P.+?) ; USER=(?P\S+) ; COMMAND=(?P.*)" +class AutoFilter (Filter) : + # your event type here, as a string + EVENT = None + # your regexp here, with named matchgroups + REGEXP = None + + # your output format, with named interpolation params + OUTPUT = None + def __init__ (self) : - super(SudoFilter, self).__init__(self.REGEXP, "sudo") + super(AutoFilter, self).__init__(self.REGEXP, self.EVENT) + + def _filter (self, match) : + return self.OUTPUT % match.groupdict() - def _filter (self, match) : - return "%(username)s:%(tty)s - %(pwd)s - `%(command)s` as %(target_user)s" % match.groupdict() +class SudoFilter (AutoFilter) : + EVENT = "sudo" + REGEXP = "sudo:\s*(?P\S+) : TTY=(?P\S+) ; PWD=(?P.+?) ; USER=(?P\S+) ; COMMAND=(?P.*)" + OUTPUT = "%(username)s:%(tty)s - %(pwd)s - `%(command)s` as %(target_user)s" + +class SSHFilter (AutoFilter) : + EVENT = "ssh" + REGEXP = "(?PAccepted|Failed) password for (?P\S+) from (?P\S+) port (?P\S+) (?P\S+)" + OUTPUT = "%(success)s login for %(username)s from %(ip)s:%(port)s proto %(proto)s" class ExampleModule (api.Module) : name = "logs" @@ -67,12 +84,14 @@ event_types = [ "error", - "sudo" + "sudo", + "ssh", ] log_files = ( ("auth.log", "/var/log/auth.log", ( SudoFilter(), + SSHFilter(), )), ) @@ -88,14 +107,11 @@ p = self.log_objs[name] = TailProcessProtocol(self, name, filters) - reactor.spawnProcess(p, "/usr/bin/tail", ["tail", "--follow=name", file]) + reactor.spawnProcess(p, "/usr/bin/tail", ["tail", "-n0", "--follow=name", file]) def error (self, msg) : self.sendEvent("error", msg) if __name__ == '__main__' : - log.startLogging(sys.stderr) - - module = ExampleModule() - reactor.run() + ExampleModule().run() diff -r 8e7493df9f52 -r 614161f85d9b nexus.py --- a/nexus.py Thu Mar 20 18:47:58 2008 +0200 +++ b/nexus.py Thu Mar 20 19:46:04 2008 +0200 @@ -46,6 +46,14 @@ def handleEvent (self, event) : self.irc.connection.sendEvent(event) + + def getModules (self) : + return (module for (module, transport) in self.modules.itervalues()) + + def getModuleInfo (self, name) : + module, connection = self.modules[name] + + return module, connection.transport.getPeer() if __name__ == '__main__' : log.startLogging(sys.stderr)