some cleanup, bugfixes, commands for the irc bot, shared-secret for the API
authorTero Marttila <terom@paivola.fi>
Thu, 20 Mar 2008 19:46:04 +0200
changeset 6 614161f85d9b
parent 5 8e7493df9f52
child 7 6a49fc285842
some cleanup, bugfixes, commands for the irc bot, shared-secret for the API

committer: Tero Marttila <terom@paivola.fi>
api.py
irc.py
logwatcher.py
nexus.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!")
--- 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 <command|module> - 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 [<module>] - 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()
+        )   
--- 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<username>\S+) : TTY=(?P<tty>\S+) ; PWD=(?P<pwd>.+?) ; USER=(?P<target_user>\S+) ; COMMAND=(?P<command>.*)"
+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<username>\S+) : TTY=(?P<tty>\S+) ; PWD=(?P<pwd>.+?) ; USER=(?P<target_user>\S+) ; COMMAND=(?P<command>.*)"
+    OUTPUT  = "%(username)s:%(tty)s - %(pwd)s - `%(command)s` as %(target_user)s"
+
+class SSHFilter (AutoFilter) :
+    EVENT   = "ssh"
+    REGEXP  = "(?P<success>Accepted|Failed) password for (?P<username>\S+) from (?P<ip>\S+) port (?P<port>\S+) (?P<proto>\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()
 
--- 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)