diff --git a/surveillance/arpwatch/common.py b/surveillance/arpwatch/common.py new file mode 100644 index 00000000..7b514fd7 --- /dev/null +++ b/surveillance/arpwatch/common.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +#-*- coding: utf-8 -*- +PIDFILE = '/var/run/arpwatch_sendmail.pid' +USER='arpwatch' +GROUP='adm' +SOCKET_FILE='/var/run/arpwatch_sendmail.sock' diff --git a/surveillance/arpwatch/initscript b/surveillance/arpwatch/initscript new file mode 100755 index 00000000..46498e1c --- /dev/null +++ b/surveillance/arpwatch/initscript @@ -0,0 +1,49 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: arpwatch_sendmail +# Required-Start: $remote_fs +# Required-Stop: $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: arpwatch_sendmail +# Description: Report mail d'arpwatch +### END INIT INFO + +NAME=arpwatch_sendmail +PIDF=/var/run/$NAME.pid +PATH=/usr/scripts/surveillance/arpwatch +ARGS=root@crans.org +BIN="/usr/bin/python $PATH/sendmail_server.py" +DESCR="Script d'envoi de mails arpwatch" + +. /lib/lsb/init-functions + +set -e + +case "$1" in + start) + echo -n "Démarrage de $NAME" + /sbin/start-stop-daemon --start --quiet --pidfile $PIDF --exec $BIN $ARGS + echo "." + ;; + stop) + echo -n "Arrêt de $NAME" + /sbin/start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile $PIDF + echo "." + ;; + + restart) + echo -n "Redémarrage (arrêt) de $NAME" + /sbin/start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile $PIDF + /bin/sleep 1 + echo -n "Redémarrage (démarrage) de $NAME" + /sbin/start-stop-daemon --start --quiet --pidfile $PIDF --exec $BIN $ARGS + echo "." + ;; + + *) + echo "Usage: /etc/init.d/$NAME {start|stop|restart}" + exit 1 +esac + +exit 0 diff --git a/surveillance/arpwatch/report.py b/surveillance/arpwatch/report.py new file mode 100755 index 00000000..06451e07 --- /dev/null +++ b/surveillance/arpwatch/report.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Ajout d'un whos et d'un tracage aux mails d'arpwatch +# Auteurs : Stéphane Glondu, Cyril Cohen, Daniel STAN, Valentin Samir +# Licence : GPLv2 + +from __future__ import print_function + +import sys, os, re +import socket + +import common + +sys.path.append('/usr/scripts') +from gestion.tools.locate_mac import trace_machine, format_mac, info_machine +from gestion.config import NETs +from gestion.iptools import AddrInNets +from utils.sendmail import sendmail + +# On récupère les destinataires dans les arguments (très ad hoc) +recipients = [ mail for mail in sys.argv[1:] if '@' in mail ] +if not recipients: + print("Pas de mail fourni: affichage sur stdout.", + file=sys.stderr) + +find_mac = re.compile(r'[0-9A-Fa-f]{1,2}(?::[0-9A-Fa-f]{1,2}){5}') +find_ip = re.compile(r'[0-9]{1,3}(?:\.[0-9]{1,3}){3}') +arpwatched_net = NETs['all'] + NETs['adm'] + NETs['accueil'] + NETs['isolement'] + NETs['personnel-ens'] + NETs['evenementiel'] + +def get_machine(unformated_mac): + """Renvoie les informations sur la machine à partir de sa mac""" + mac = format_mac(unformated_mac) + return u"\n" + info_machine(mac) + u"\n" + trace_machine(mac) + +def get_subject(headers_list): + for line in headers_list: + if line.lower().startswith('subject:'): + return line[9:].strip() + return None + +def report(texte, fallback=False): + """Envoi d'un rapport""" + textes = texte.splitlines(True) + try: + i = textes.index('\n') + except ValueError: + print("wrong formatted mail", file=sys.stderr) + return + + headers = textes[:i] + textes = textes[(i+1):] + + # Extrait le sujet + subject = get_subject(headers) + + # Cherche les IP dans le corps + try: + ip = set(find_ip.findall(texte)).pop() + except KeyError: + ip = None + # On complète le message + if u'flip flop' in subject and ip is not None and AddrInNets(ip, arpwatched_net): + try: + macs = find_mac.findall(texte) + for mac in macs: + textes.append(get_machine(mac)) + except: + # En cas d'exception, on envoie le traceback + import traceback + textes.append(u'\n') + textes.append(u''.join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))) + if fallback: + textes.append(u'\nAttention, arpwatch/sendmail_server.py inactif !\n') + textes.append(u'\n-- \narpwatch_sendmail.py\n') + + out = ''.join(textes) + if recipients: + sendmail(u"arpwatch@crans.org", recipients, subject, out, + more_headers = { + 'X-Mailer': __file__, + }) + else: + print(out) + +if __name__ == '__main__': + data = sys.stdin.read() + # On essaye d'envoyer le mail à la socket de sendmail_server + # et au pire, on envoie nous-même le mail + try: + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(common.SOCKET_FILE) + s.send(data) + s.close() + except socket.error: + report(data, fallback=True) diff --git a/surveillance/arpwatch/sendmail_server.py b/surveillance/arpwatch/sendmail_server.py new file mode 100755 index 00000000..db94e269 --- /dev/null +++ b/surveillance/arpwatch/sendmail_server.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Serveur sendmail d'arpwatch. +Permet de ne charger qu'une fois le module report (et donc lc_ldap +et cie) +Ceci écoute sur une socket unix les rapports arpwatch (via report.py) +""" + +from __future__ import print_function +import SocketServer +import sys, os, pwd, grp +import common + +class Handler(SocketServer.StreamRequestHandler): + """Gestion d'une requête""" + def handle(self): + self.server.reporter.report(self.request.recv(1024)) + +class MyServer(SocketServer.UnixStreamServer): + """Un server, avec une instance du module report""" + reporter = None + +def run(): + """Serveur permanent""" + import report + + try: + os.remove(common.SOCKET_FILE) + print("Removed old socket", file=sys.stderr) + except OSError: + pass + + # La socket ne doit pas être accessible pour tous: on garde adm + os.umask(007) + os.setgid(grp.getgrnam(common.GROUP).gr_gid) + + serv = SocketServer.UnixStreamServer(common.SOCKET_FILE, Handler) + serv.reporter = report + + serv.serve_forever() + +# fork en arrière plan + pidfile +if __name__ == "__main__": + # do the UNIX double-fork magic, see Stevens' "Advanced + # Programming in the UNIX Environment" for details (ISBN 0201563177) + try: + pid = os.fork() + if pid > 0: + # exit first parent + sys.exit(0) + except OSError, e: + print >>sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror) + sys.exit(1) + + # decouple from parent environment + os.chdir("/") #don't prevent unmounting.... + os.setsid() + os.umask(0) + + # do second fork + try: + pid = os.fork() + if pid > 0: + # exit from second parent, print eventual PID before + #print "Daemon PID %d" % pid + open(common.PIDFILE, 'w').write("%d" % pid) + sys.exit(0) + except OSError, e: + print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror) + sys.exit(1) + + # start the daemon main loop + run() + diff --git a/surveillance/arpwatch_sendmail b/surveillance/arpwatch_sendmail deleted file mode 100755 index 352ed405..00000000 --- a/surveillance/arpwatch_sendmail +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -#execution avec des droits suffisants -LANG=fr_FR.UTF-8 sudo -u 'arpwatch' /usr/scripts/surveillance/arpwatch_sendmail.py $* diff --git a/surveillance/arpwatch_sendmail.py b/surveillance/arpwatch_sendmail.py deleted file mode 100755 index cb7f7ca0..00000000 --- a/surveillance/arpwatch_sendmail.py +++ /dev/null @@ -1,64 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- - -# Ajout d'un whos et d'un tracage aux mails d'arpwatch -# Auteurs : Stéphane Glondu, Cyril Cohen -# Licence : GPLv2 - -import sys, os, re, smtplib -from commands import getstatusoutput - -sys.path.append('/usr/scripts/gestion') -sys.path.append('/usr/scripts/gestion/tools') -from locate_mac import trace_machine, format_mac, info_machine -from config import NETs -from iptools import AddrInNets - -find_mac = re.compile(r'[0-9A-Fa-f]{1,2}(?::[0-9A-Fa-f]{1,2}){5}') -find_ip = re.compile(r'[0-9]{1,3}(?:\.[0-9]{1,3}){3}') -arpwatched_net = NETs['all'] + NETs['adm'] + NETs['accueil'] + NETs['isolement'] + NETs['personnel-ens'] + NETs['evenementiel'] - - -def get_machine(unformated_mac): - mac = format_mac(unformated_mac) - return u"\n" + info_machine(mac) + u"\n" + trace_machine(mac) - -def get_subject(headers_list): - for line in headers_list: - if line.lower().startswith('subject:'): - return line - return None - -if __name__ == "__main__": - texte = sys.stdin.read() #.decode('ISO-8859-15') - textes = texte.splitlines(True) - i = textes.index(u'\n') - subject = get_subject(textes[:i]) - textes[i-1:i-1] = [ - u'MIME-Version: 1.0\n', - u'Content-Type: text/plain; charset=UTF-8\n', - u'Content-Transfer-Encoding: 8bit\n', - ] - - # On récupère les destinataires dans les arguments (très ad hoc) - recipients = sys.argv[2].split(',') - - try : ip = set(find_ip.findall(texte)).pop() - except KeyError: ip = None - # On complète le message - if 'flip flop' in subject and ip is not None and AddrInNets(ip, arpwatched_net): - try: - macs = find_mac.findall(texte) - for mac in macs: - textes.append(get_machine(mac)) - except: - # En cas d'exception, on envoie le traceback - import traceback - textes.append(u'\n') - textes.append(u''.join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))) - textes.append('\n-- \narpwatch_sendmail.py\n') - - smtp = smtplib.SMTP() - smtp.connect() - smtp.sendmail("arpwatch@crans.org", recipients, u''.join(textes).encode('UTF-8')) - smtp.quit()