This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "Cappie detects new hosts on a network.".
The branch, master has been updated via 17a6cf4e2af6f220ac0049803ce173a275f5dac3 (commit) via 534780507041d0f1058b11c7c74c0f89a2bc6e37 (commit) from 39fedabd85a5d9703e7b65204976ee872679ee27 (commit)
Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below.
- Log ----------------------------------------------------------------- commit 17a6cf4e2af6f220ac0049803ce173a275f5dac3 Author: Michael Tremer michael.tremer@ipfire.org Date: Sat Apr 17 20:56:28 2010 +0200
Add .gitignore.
commit 534780507041d0f1058b11c7c74c0f89a2bc6e37 Author: Michael Tremer michael.tremer@ipfire.org Date: Sat Apr 17 20:55:02 2010 +0200
Splitted into daemon and python module.
-----------------------------------------------------------------------
Summary of changes: .gitignore | 1 + cappie.py | 431 --------------------------------------------------- cappie/__init__.py | 226 +++++++++++++++++++++++++++ cappie/constants.py | 25 +++ cappie/errors.py | 38 +++++ cappie/events.py | 79 ++++++++++ cappie/protocol.py | 95 +++++++++++ cappie/queue.py | 75 +++++++++ cappied | 51 ++++++ 9 files changed, 590 insertions(+), 431 deletions(-) create mode 100644 .gitignore delete mode 100644 cappie.py create mode 100644 cappie/__init__.py create mode 100644 cappie/constants.py create mode 100644 cappie/errors.py create mode 100644 cappie/events.py create mode 100644 cappie/protocol.py create mode 100644 cappie/queue.py create mode 100755 cappied
Difference in files: diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..539da74 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.py[co] diff --git a/cappie.py b/cappie.py deleted file mode 100644 index 5065a9d..0000000 --- a/cappie.py +++ /dev/null @@ -1,431 +0,0 @@ -#!/usr/bin/python - -import logging -import logging.handlers -import os -import pcapy -import struct -import subprocess -import sys -import time - -from ConfigParser import ConfigParser -from threading import Thread - -TYPE_ARP = 0 - -OPERATION_REQUEST = 0 -OPERATION_RESPONSE = 1 - -def getAllInterfaces(): - filters = ("lo", "any") - ret = [] - for dev in pcapy.findalldevs(): - if not dev in filters: - ret.append(dev) - return ret - -def val2int(val): - return int("".join(["%02d" % ord(c) for c in val]), 16) - -def val2ip4(val): - return ".".join(["%d" % ord(i) for i in val]) - -def val2mac(val): - return ":".join(["%02x" % ord(i) for i in val]) - -def decode_packet(data): - for func in (decode_arp_packet,): - try: - p =func(data) - except PacketTypeError: - continue - - return p - - raise PacketTypeError, "Could not determine type of packet" - -def decode_arp_packet(data): - operationmap = { - 1 : OPERATION_REQUEST, - 2 : OPERATION_RESPONSE, - } - - #if not len(data) == 42: - # raise DecodeError, "Data has wrong length: %d" % len(data) - - ret = { - "type" : TYPE_ARP, - } - - #"hwtype" : data[:2], - protocol = val2int(struct.unpack("!2s", data[12:14])[0]) - hw_addr_size = val2int(struct.unpack("!1s", data[18:19])[0]) - hw_prot_size = val2int(struct.unpack("!1s", data[19:20])[0]) - operation = val2int(struct.unpack("!2s", data[20:22])[0]) - - # Sanity checks - if not protocol == 0x0806: - raise PacketTypeError, "Not an ARP packet" - - # TODO Must check hwtype here... - - try: - ret["operation"] = operationmap[operation] - except KeyError: - raise DecodeError, "Unknown operation type" - - address_length = hw_addr_size + hw_prot_size - unpack_str = "!%ss%ss" % (hw_addr_size, hw_prot_size) - - ret["source_address"], ret["source_ip_address"] = \ - struct.unpack(unpack_str, data[22:22 + address_length]) - - ret["destination_address"], ret["destination_ip_address"] = \ - struct.unpack(unpack_str, data[22 + address_length:22 + address_length * 2]) - - for i in ("source_address", "destination_address"): - ret[i] = val2mac(ret[i]) - - for i in ("source_ip_address", "destination_ip_address"): - ret[i] = val2ip4(ret[i]) - - return ret - -def decode_ndp_packet(data): - raise PacketTypeError - -class PacketTypeError(Exception): - pass - -class DecodeError(Exception): - pass - - -class InterfaceError(Exception): - pass - - -class Database(object): - def __init__(self, interface): - self.interface = interface - self.dev = self.interface.dev - self.log = self.interface.log - - self.__data = {} - - def open(self): - self.log.debug("Opened database for %s" % self.dev) - - def close(self): - self.log.debug("Closing database for %s" % self.dev) - print self.__data - - def get(self, mac): - if self.has(mac): - return self.__data[mac] - - def has(self, mac): - return self.__data.has_key(mac) - - def put(self, mac, key, val): - if not self.has(mac): - self.__data[mac] = {} - - # TODO Check key for sanity - - self.__data[mac][key] = val - - -class Interface(Thread): - heartbeat = 0.1 - - def __init__(self, dev, cappie, promisc=False, mtu=1500): - Thread.__init__(self) - - self.cappie = cappie - self.dev = dev - self.log = self.cappie.log - self.mtu = mtu - self.promisc = promisc - self.queue = self.cappie.queue - - self.db = Database(self) - - self.log.debug("Created new interface %s" % self.dev) - - self.__running = True - - def _callback(self, header, data): - self.log.debug("Received packet on %s" % self.dev) - try: - p = decode_packet(data) - except PacketTypeError, e: - self.log.error("Got unknown packet: %s" % e) - return - except DecodeError, e: - self.log.warning("Got decoding error: %s" % e) - return - - # Dump packet information - for key, val in p.items(): - self.log.debug(" %s: %s" % (key, val)) - - if not self.db.has(p["source_address"]): - self.db.put(p["source_address"], "SOURCE_IP_ADDRESS", p["source_ip_address"]) - - def run(self): - self.log.info("Starting interface %s" % self.dev) - - self.db.open() - - p = pcapy.open_live(self.dev, self.mtu, self.promisc, 0) - p.setfilter(self.filter) - #p.loop(0, self._callback) - - p.setnonblock(1) - while True: - if not self.__running: - self.db.close() - return - - if p.dispatch(1, self._callback): - continue - - time.sleep(self.heartbeat) - - def shutdown(self): - if not self.__running: - return - - self.log.debug("Sending shutdown signal to %s" % self.dev) - self.__running = False - - @property - def filter(self): - return "arp or rarp" - - -class QueueFullError(Exception): - pass - - -class Queue(Thread): - heartbeat = 1.0 - maxitems = 100 - - def __init__(self, log): - Thread.__init__(self) - - self.log = log - - self.__running = True - self.__queue = [] - - def __len__(self): - return self.length - - def add(self, event): - if self.length > self.maxitems: - raise QueueFullError, "Cannot queue new event." - - self.__queue.append(event) - - @property - def length(self): - return len(self.__queue) - - def run(self): - self.log.debug("Started event queue") - - while self.__running or self.__queue: - if not self.__queue: - #self.log.debug("Queue sleeping for %s seconds" % self.heartbeat) - time.sleep(self.heartbeat) - continue - - event = self.__queue.pop(0) - self.log.debug("Processing queue event: %s" % event) - try: - event.run() - except EventException, e: - self.log.error("Catched event exception: %s" % e) - - def shutdown(self): - self.__running = False - self.log.debug("Shutting down queue") - self.log.debug("%d events in queue left" % len(self.__queue)) - - # Wait until queue handled all events - self.join() - - -class EventException(Exception): - pass - -class Event(object): - def __init__(self, interface): - self.cappie = interface.cappie - self.interface = interface - self.log = interface.log - - def __str__(self): - return self.__class__.__name__ - - def run(self): - raise NotImplementedError - - -class EventTimeout(EventException): - pass - - -class EventShell(Event): - heartbeat = 0.1 - timeout = 10 - - def __init__(self, interface, script): - Event.__init__(self, interface) - - self.script = script - - def run(self): - args = " ".join([self.script, self.interface.dev]) - - start = time.time() - self.log.debug("Running: %s" % args) - - p = subprocess.Popen(args, - close_fds=True, - shell=True, - stdin=open("/dev/null", "r"), - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - - while p.poll() is None: - time.sleep(self.heartbeat) - if (time.time() - start) > self.timeout: - try: - os.killpg(p.pid, 9) - except OSError: - pass - raise EventTimeout, "Script took too long to return" - - for line in p.stdout.read().splitlines(): - if not line: continue - self.log.debug(" %s" % line) - - self.cappie.log.debug("Child process returned with exit code: %s" % p.returncode) - return p.returncode - - -class Cappie(object): - def __init__(self): - self.__interfaces = [] - - self.log = logging.getLogger("cappie") - self.log.setLevel(logging.INFO) - - # Log to console - handler = logging.StreamHandler() - handler.setFormatter(logging.Formatter("%(levelname)7s %(message)s")) - self.log.addHandler(handler) - - # Setup syslog - handler = logging.handlers.SysLogHandler("/dev/log") - handler.setFormatter(logging.Formatter("cappie: %(message)s")) - self.log.addHandler(handler) - - self.queue = Queue(self.log) - - self.log.info("Cappie successfully started") - - def __del__(self): - self.shutdown() - self.log.info("Exiting") - - def setDebug(self, debug): - if debug: - self.log.setLevel(logging.DEBUG) - else: - self.log.setLevel(logging.INFO) - - def addInterface(self, dev, **kwargs): - if not dev in getAllInterfaces(): - raise InterfaceError, "No such interface %s" % dev - - kwargs["cappie"] = self - - iface = Interface(dev, **kwargs) - self.__interfaces.append(iface) - - def run(self): - if not self.__interfaces: - raise RuntimeError, "No interfaces were configured" - - # Start queue - self.queue.start() - - # Start a thread for each interface - for iface in self.__interfaces: - iface.start() - - while True: - if not self.queue.is_alive(): - self.log.critical("Queue thread died unexpectedly.") - return - - for iface in self.__interfaces: - if not iface.is_alive(): - self.log.critical("Thread died unexpectedly. %s" % iface.dev) - return - time.sleep(60) - - def readConfig(self, configfile): - config = ConfigParser() - config.read([configfile]) - - global_opts = {} - if config.has_section("global"): - for option, value in config.items("global"): - global_opts[option] = value - - config.remove_section("global") - - for iface in config.sections(): - options = {} - for option, value in config.items(iface): - options[option] = value - self.addInterface(iface, **options) - - def shutdown(self): - for iface in self.__interfaces: - iface.shutdown() - - self.queue.shutdown() - - -if __name__ == "__main__": - from optparse import OptionParser - op = OptionParser() - op.add_option("-c", "--config", dest="config", - help="read configuration from file", metavar="FILE", - default="/etc/cappie/cappie.conf") - op.add_option("-d", action="store_true", dest="debug", default=False) - - (options, args) = op.parse_args() - - cappie = Cappie() - if options.config: - cappie.readConfig(options.config) - cappie.setDebug(options.debug) - - try: - cappie.run() - except KeyboardInterrupt: - cappie.shutdown() - except RuntimeError, e: - print >>sys.stderr, e - sys.exit(1) - - #sys.exit(0) diff --git a/cappie/__init__.py b/cappie/__init__.py new file mode 100644 index 0000000..2fea5e0 --- /dev/null +++ b/cappie/__init__.py @@ -0,0 +1,226 @@ +#!/usr/bin/python +############################################################################### +# # +# Cappie # +# Copyright (C) 2010 Michael Tremer # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +import logging +import logging.handlers +import pcapy +import time + +from ConfigParser import ConfigParser +from threading import Thread + +import protocol +import queue + +from errors import * + +def getAllInterfaces(): + filters = ("lo", "any") + ret = [] + for dev in pcapy.findalldevs(): + if not dev in filters: + ret.append(dev) + return ret + +class Cappie(object): + def __init__(self): + self.__interfaces = [] + + self.log = logging.getLogger("cappie") + self.log.setLevel(logging.INFO) + + # Log to console + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(levelname)7s %(message)s")) + self.log.addHandler(handler) + + # Setup syslog + handler = logging.handlers.SysLogHandler("/dev/log") + handler.setFormatter(logging.Formatter("cappie: %(message)s")) + self.log.addHandler(handler) + + self.queue = queue.Queue(self.log) + + self.log.info("Cappie successfully started") + + def __del__(self): + self.shutdown() + self.log.info("Exiting") + + def setDebug(self, debug): + if debug: + self.log.setLevel(logging.DEBUG) + else: + self.log.setLevel(logging.INFO) + + def addInterface(self, dev, **kwargs): + if not dev in getAllInterfaces(): + raise InterfaceError, "No such interface %s" % dev + + kwargs["cappie"] = self + + iface = Interface(dev, **kwargs) + self.__interfaces.append(iface) + + def run(self): + if not self.__interfaces: + raise RuntimeError, "No interfaces were configured" + + # Start queue + self.queue.start() + + # Start a thread for each interface + for iface in self.__interfaces: + iface.start() + + while True: + if not self.queue.is_alive(): + self.log.critical("Queue thread died unexpectedly.") + return + + for iface in self.__interfaces: + if not iface.is_alive(): + self.log.critical("Thread died unexpectedly. %s" % iface.dev) + return + time.sleep(60) + + def readConfig(self, configfile): + config = ConfigParser() + config.read([configfile]) + + global_opts = {} + if config.has_section("global"): + for option, value in config.items("global"): + global_opts[option] = value + + config.remove_section("global") + + for iface in config.sections(): + options = {} + for option, value in config.items(iface): + options[option] = value + self.addInterface(iface, **options) + + def shutdown(self): + for iface in self.__interfaces: + iface.shutdown() + + self.queue.shutdown() + + +class Interface(Thread): + heartbeat = 0.1 + + def __init__(self, dev, cappie, promisc=False, mtu=1500): + Thread.__init__(self) + + self.cappie = cappie + self.dev = dev + self.log = self.cappie.log + self.mtu = mtu + self.promisc = promisc + self.queue = self.cappie.queue + + self.db = Database(self) + + self.log.debug("Created new interface %s" % self.dev) + + self.__running = True + + def _callback(self, header, data): + self.log.debug("Received packet on %s" % self.dev) + try: + p = protocol.decode_packet(data) + except PacketTypeError, e: + self.log.error("Got unknown packet: %s" % e) + return + except DecodeError, e: + self.log.warning("Got decoding error: %s" % e) + return + + # Dump packet information + for key, val in p.items(): + self.log.debug(" %s: %s" % (key, val)) + + if not self.db.has(p["source_address"]): + self.db.put(p["source_address"], "SOURCE_IP_ADDRESS", p["source_ip_address"]) + + def run(self): + self.log.info("Starting interface %s" % self.dev) + + self.db.open() + + p = pcapy.open_live(self.dev, self.mtu, self.promisc, 0) + p.setfilter(self.filter) + #p.loop(0, self._callback) + + p.setnonblock(1) + while True: + if not self.__running: + self.db.close() + return + + if p.dispatch(1, self._callback): + continue + + time.sleep(self.heartbeat) + + def shutdown(self): + if not self.__running: + return + + self.log.debug("Sending shutdown signal to %s" % self.dev) + self.__running = False + + @property + def filter(self): + return "arp or rarp" + + +class Database(object): + def __init__(self, interface): + self.interface = interface + self.dev = self.interface.dev + self.log = self.interface.log + + self.__data = {} + + def open(self): + self.log.debug("Opened database for %s" % self.dev) + + def close(self): + self.log.debug("Closing database for %s" % self.dev) + print self.__data + + def get(self, mac): + if self.has(mac): + return self.__data[mac] + + def has(self, mac): + return self.__data.has_key(mac) + + def put(self, mac, key, val): + if not self.has(mac): + self.__data[mac] = {} + + # TODO Check key for sanity + + self.__data[mac][key] = val diff --git a/cappie/constants.py b/cappie/constants.py new file mode 100644 index 0000000..50fb016 --- /dev/null +++ b/cappie/constants.py @@ -0,0 +1,25 @@ +#!/usr/bin/python +############################################################################### +# # +# Cappie # +# Copyright (C) 2010 Michael Tremer # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +TYPE_ARP = 0 + +OPERATION_REQUEST = 0 +OPERATION_RESPONSE = 1 diff --git a/cappie/errors.py b/cappie/errors.py new file mode 100644 index 0000000..ec9e481 --- /dev/null +++ b/cappie/errors.py @@ -0,0 +1,38 @@ +#!/usr/bin/python +############################################################################### +# # +# Cappie # +# Copyright (C) 2010 Michael Tremer # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +class DecodeError(Exception): + pass + +class EventException(Exception): + pass + +class EventTimeout(EventException): + pass + +class InterfaceError(Exception): + pass + +class PacketTypeError(Exception): + pass + +class QueueFullError(Exception): + pass diff --git a/cappie/events.py b/cappie/events.py new file mode 100644 index 0000000..d403b52 --- /dev/null +++ b/cappie/events.py @@ -0,0 +1,79 @@ +#!/usr/bin/python +############################################################################### +# # +# Cappie # +# Copyright (C) 2010 Michael Tremer # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +import os +import subprocess +import time + +from errors import * + +class Event(object): + def __init__(self, interface): + self.cappie = interface.cappie + self.interface = interface + self.log = interface.log + + def __str__(self): + return self.__class__.__name__ + + def run(self): + raise NotImplementedError + + +class EventShell(Event): + heartbeat = 0.1 + timeout = 10 + + def __init__(self, interface, script): + Event.__init__(self, interface) + + self.script = script + + def run(self): + args = " ".join([self.script, self.interface.dev]) + + start = time.time() + self.log.debug("Running: %s" % args) + + p = subprocess.Popen(args, + close_fds=True, + shell=True, + stdin=open("/dev/null", "r"), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + + while p.poll() is None: + time.sleep(self.heartbeat) + if (time.time() - start) > self.timeout: + try: + os.killpg(p.pid, 9) + except OSError: + pass + raise EventTimeout, "Script took too long to return" + + for line in p.stdout.read().splitlines(): + if not line: continue + self.log.debug(" %s" % line) + + self.cappie.log.debug("Child process returned with exit code: %s" % \ + p.returncode) + + return p.returncode diff --git a/cappie/protocol.py b/cappie/protocol.py new file mode 100644 index 0000000..ed5f4a3 --- /dev/null +++ b/cappie/protocol.py @@ -0,0 +1,95 @@ +#!/usr/bin/python +############################################################################### +# # +# Cappie # +# Copyright (C) 2010 Michael Tremer # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +import struct + +from constants import * +from errors import * + +def val2int(val): + return int("".join(["%02d" % ord(c) for c in val]), 16) + +def val2ip4(val): + return ".".join(["%d" % ord(i) for i in val]) + +def val2mac(val): + return ":".join(["%02x" % ord(i) for i in val]) + +def decode_packet(data): + for func in (decode_arp_packet,): + try: + p = func(data) + except PacketTypeError: + continue + + return p + + raise PacketTypeError, "Could not determine type of packet" + +def decode_arp_packet(data): + operationmap = { + 1 : OPERATION_REQUEST, + 2 : OPERATION_RESPONSE, + } + + #if not len(data) == 42: + # raise DecodeError, "Data has wrong length: %d" % len(data) + + ret = { + "type" : TYPE_ARP, + } + + #"hwtype" : data[:2], + protocol = val2int(struct.unpack("!2s", data[12:14])[0]) + hw_addr_size = val2int(struct.unpack("!1s", data[18:19])[0]) + hw_prot_size = val2int(struct.unpack("!1s", data[19:20])[0]) + operation = val2int(struct.unpack("!2s", data[20:22])[0]) + + # Sanity checks + if not protocol == 0x0806: + raise PacketTypeError, "Not an ARP packet" + + # TODO Must check hwtype here... + + try: + ret["operation"] = operationmap[operation] + except KeyError: + raise DecodeError, "Unknown operation type" + + address_length = hw_addr_size + hw_prot_size + unpack_str = "!%ss%ss" % (hw_addr_size, hw_prot_size) + + ret["source_address"], ret["source_ip_address"] = \ + struct.unpack(unpack_str, data[22:22 + address_length]) + + ret["destination_address"], ret["destination_ip_address"] = \ + struct.unpack(unpack_str, data[22 + address_length:22 + address_length * 2]) + + for i in ("source_address", "destination_address"): + ret[i] = val2mac(ret[i]) + + for i in ("source_ip_address", "destination_ip_address"): + ret[i] = val2ip4(ret[i]) + + return ret + +def decode_ndp_packet(data): + raise PacketTypeError diff --git a/cappie/queue.py b/cappie/queue.py new file mode 100644 index 0000000..e2d0dd6 --- /dev/null +++ b/cappie/queue.py @@ -0,0 +1,75 @@ +#!/usr/bin/python +############################################################################### +# # +# Cappie # +# Copyright (C) 2010 Michael Tremer # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +import time + +from threading import Thread + +from errors import * + +class Queue(Thread): + heartbeat = 1.0 + maxitems = 100 + + def __init__(self, log): + Thread.__init__(self) + + self.log = log + + self.__running = True + self.__queue = [] + + def __len__(self): + return self.length + + def add(self, event): + if self.length > self.maxitems: + raise QueueFullError, "Cannot queue new event." + + self.__queue.append(event) + + @property + def length(self): + return len(self.__queue) + + def run(self): + self.log.debug("Started event queue") + + while self.__running or self.__queue: + if not self.__queue: + #self.log.debug("Queue sleeping for %s seconds" % self.heartbeat) + time.sleep(self.heartbeat) + continue + + event = self.__queue.pop(0) + self.log.debug("Processing queue event: %s" % event) + try: + event.run() + except EventException, e: + self.log.error("Catched event exception: %s" % e) + + def shutdown(self): + self.__running = False + self.log.debug("Shutting down queue") + self.log.debug("%d events in queue left" % len(self.__queue)) + + # Wait until queue handled all events + self.join() diff --git a/cappied b/cappied new file mode 100755 index 0000000..0d27392 --- /dev/null +++ b/cappied @@ -0,0 +1,51 @@ +#!/usr/bin/python +############################################################################### +# # +# Cappie # +# Copyright (C) 2010 Michael Tremer # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +import sys + +from cappie import Cappie + +def main(): + from optparse import OptionParser + op = OptionParser() + op.add_option("-c", "--config", dest="config", + help="read configuration from file", metavar="FILE", + default="/etc/cappie/cappie.conf") + op.add_option("-d", action="store_true", dest="debug", default=False) + + (options, args) = op.parse_args() + + cappie = Cappie() + if options.config: + cappie.readConfig(options.config) + cappie.setDebug(options.debug) + + try: + cappie.run() + except KeyboardInterrupt: + cappie.shutdown() + except RuntimeError, e: + print >>sys.stderr, e + sys.exit(1) + + #sys.exit(0) + +main()
hooks/post-receive -- Cappie detects new hosts on a network.