123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- # -*- coding: utf-8 -*-
- from __future__ import print_function
- import willie
- import git
- import netaddr
- import json
- import urllib2
- import re
- import os
- import subprocess
- import dns.resolver,dns.reversename
- import socket
- import SocketServer
- import threading
- msgserver = None
- peers_repo = None
- stats = None
- alfred_method = None
- alfred_data = None
- ffpb_resolver = dns.resolver.Resolver ()
- ffpb_resolver.nameservers = ['10.132.254.53']
- class MsgHandler(SocketServer.BaseRequestHandler):
- def handle(self):
- data = self.request.recv(2048).strip()
- sender = self._resolve_name (self.client_address[0])
- bot = self.server.bot
- if bot is None:
- print("ERROR: No bot in handle() :-(")
- return
- target = bot.config.core.owner
- if bot.config.has_section('ffpb'):
- is_public = data.lstrip().lower().startswith("public:")
- if is_public and not (bot.config.ffpb.msg_target_public is None):
- data = data[7:].lstrip()
- target = bot.config.ffpb.msg_target_public
- elif not (bot.config.ffpb.msg_target is None):
- target = bot.config.ffpb.msg_target
- bot.msg(target, "[{0}] {1}".format(sender, str(data)))
- def _resolve_name (self, ip):
- if ip.startswith ("127."):
- return "localhost"
- try:
- addr = dns.reversename.from_address (ip)
- return re.sub ("(.infra)?.ffpb.", "", str (ffpb_resolver.query (addr, "PTR")[0]))
- except dns.resolver.NXDOMAIN:
- return ip
- class ThreadingTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
- pass
- def setup(bot):
- global msgserver, peers_repo, alfred_method
- if not bot.config.has_section('ffpb'):
- return
- if not bot.config.ffpb.peers_directory is None:
- peers_repo = git.Repo(bot.config.ffpb.peers_directory)
- assert peers_repo.bare == False
- if int(bot.config.ffpb.msg_enable) == 1:
- host = "localhost"
- port = 2342
- if not bot.config.ffpb.msg_host is None: host = bot.config.ffpb.msg_host
- if not bot.config.ffpb.msg_port is None: port = int(bot.config.ffpb.msg_port)
- msgserver = ThreadingTCPServer((host,port), MsgHandler)
- msgserver.bot = bot
- ip, port = msgserver.server_address
- print("Messaging server listening on {}:{}".format(ip,port))
- msgserver_thread = threading.Thread(target=msgserver.serve_forever)
- msgserver_thread.daemon = True
- msgserver_thread.start()
- alfred_method = bot.config.ffpb.alfred_method
- ffpb_updatealfred(bot)
- def shutdown(bot):
- global msgserver
- if not msgserver is None:
- msgserver.shutdown()
- print("Closed messaging server.")
- msgserver = None
- def ffpb_findnode(name):
- if name is None or len(name) == 0:
- return None
- name = str(name).strip()
- # try to match MAC
- m = re.search("^([0-9a-fA-F][0-9a-fA-F]:){5}[0-9a-fA-F][0-9a-fA-F]$", name)
- if (not m is None):
- mac = m.group(0).lower()
- if mac in alfred_data:
- return alfred_data[mac]
- # try to find alias MAC
- for nodeid in alfred_data:
- node = alfred_data[nodeid]
- if "network" in node:
- if "mac" in node["network"] and node["network"]["mac"].lower() == mac:
- return node
- if "mesh_interfaces" in node["network"]:
- for mim in node["network"]["mesh_interfaces"]:
- if mim.lower() == mac:
- return node
- # look through the ALFRED peers
- possible_matches = []
- for nodeid in alfred_data:
- node = alfred_data[nodeid]
- if "hostname" in node and node["hostname"].lower() == name.lower():
- return node
- # still not found -> try peers_repo
- if not peers_repo is None:
- peer_name = None
- peer_mac = None
- peer_file = None
- for b in peers_repo.heads.master.commit.tree.blobs:
- if b.name.lower() == name.lower():
- peer_name = b.name
- peer_file = b.abspath
- break
- if (not peer_file is None) and os.path.exists(peer_file):
- peerfile = open(peer_file, "r")
- for line in peerfile:
- if line.startswith("# MAC:"):
- peer_mac = line[6:].strip()
- peerfile.close()
- if not (peer_mac is None):
- return { "hostname": peer_name, "network": { "addresses": [ mac2ipv6(peer_mac, "fdca:ffee:ff12:132:") ], "mac": peer_mac } }
- return None
- def mac2ipv6(mac, prefix=None):
- result = str(netaddr.EUI(mac).ipv6_link_local())
- if (not prefix is None) and (result.startswith("fe80::")):
- result = prefix + result[6:]
- return result
- @willie.module.interval(30)
- def ffpb_updatealfred(bot):
- """Aktualisiere ALFRED-Daten"""
- global alfred_data
- if alfred_method is None or alfred_method == "None":
- return
- if alfred_method == "exec":
- rawdata = subprocess.check_output(['alfred-json', '-z', '-r', '158'])
- alfred_data = json.load(rawdata)
- #print("Fetched new ALFRED data:", len(alfred_data), "entries")
- return
- if alfred_method.startswith("http"):
- rawdata = urllib2.urlopen(alfred_method)
- alfred_data = json.load(rawdata)
- #print("Downloaded new ALFRED data:", len(alfred_data), "entries")
- return
- print("Unknown ALFRED data method '", alfred_method, "', cannot load new data.", sep="")
- alfred_data = None
- @willie.module.commands('info')
- def ffpb_peerinfo(bot, trigger):
- target_name = trigger.group(2)
- if (target_name is None or len(target_name) == 0):
- bot.say(str(trigger.nick + ": Grün."))
- return
- if alfred_data is None:
- bot.say("Informationen sind ausverkauft, kommen erst morgen wieder rein.")
- return
- node = ffpb_findnode(target_name)
- if node is None:
- bot.say("Kein Plan wer oder was mit '" + target_name + "' gemeint ist :(")
- return
- info_mac = node["network"]["mac"]
- info_name = node["hostname"]
- info_hw = ""
- if "hardware" in node:
- if "model" in node["hardware"]:
- model = node["hardware"]["model"]
- info_hw = " model='" + model + "'"
- info_fw = ""
- info_update = ""
- if "software" in node:
- if "firmware" in node["software"]:
- fwinfo = str(node["software"]["firmware"]["release"]) if "release" in node["software"]["firmware"] else "unknown"
- info_fw = " firmware=" + fwinfo
- if "autoupdater" in node["software"]:
- autoupdater = node["software"]["autoupdater"]["branch"] if node["software"]["autoupdater"]["enabled"] else "off"
- info_update = " (autoupdater="+autoupdater+")"
- info_uptime = ""
- if "statistics" in node and "uptime" in node["statistics"]:
- u = int(float(node["statistics"]["uptime"]))
-
- d, r1 = divmod(int(float(node["statistics"]["uptime"])), 86400)
- h, r2 = divmod(r1, 3600)
- m, s = divmod(r2, 60)
- if d > 0:
- info_uptime = ' up {0}d {1}h'.format(d,h)
- elif h > 0:
- info_uptime = ' up {0}h {1}m'.format(h,m)
- else:
- info_uptime = ' up {0}m'.format(m)
- bot.say('[{1}]{2}{3}{4}{5}'.format(info_mac, info_name, info_hw, info_fw, info_update, info_uptime))
- @willie.module.commands('link')
- def ffpb_peerlink(bot, trigger):
- target_name = trigger.group(2)
- if (target_name is None or len(target_name) == 0):
- bot.say(str(trigger.nick + ": Grün."))
- return
- if alfred_data is None:
- bot.say("Informationen sind ausverkauft, kommen erst morgen wieder rein.")
- return
- node = ffpb_findnode(target_name)
- if node is None:
- bot.say("Kein Plan wer oder was mit '" + target_name + "' gemeint ist :(")
- return
- info_mac = node["network"]["mac"]
- info_name = node["hostname"]
- info_v6 = mac2ipv6(info_mac, 'fdca:ffee:ff12:132:')
- bot.say('[{1}] mac {0} -> http://[{2}]/'.format(info_mac, info_name, info_v6))
- @willie.module.interval(60)
- def ffpb_updatepeers(bot):
- """Aktualisiere die Knotenliste und melde das Diff"""
- if peers_repo is None:
- print('WARNING: peers_repo is None')
- return
- old_head = peers_repo.head.commit
- peers_repo.remotes.origin.pull()
- new_head = peers_repo.head.commit
- if new_head != old_head:
- print('git pull: from ' + str(old_head) + ' to ' + str(new_head))
- added = []
- changed = []
- renamed = []
- deleted = []
- for f in old_head.diff(new_head):
- if f.new_file:
- added.append(f.b_blob.name)
- elif f.deleted_file:
- deleted.append(f.a_blob.name)
- elif f.renamed:
- renamed.append([f.rename_from, f.rename_to])
- else:
- changed.append(f.a_blob.name)
- response = "Knoten-Update (VPN +{0} %{1} -{2}): ".format(len(added), len(renamed)+len(changed), len(deleted))
- for f in added:
- response += " +'{}'".format(f)
- for f in changed:
- response += " %'{}'".format(f)
- for f in renamed:
- response += " '{}'->'{}'".format(f[0],f[1])
- for f in deleted:
- response += " -'{}'".format(f)
- bot.msg(bot.config.ffpb.msg_target, response)
- @willie.module.interval(15)
- def ffpb_get_stats(bot):
- global stats
- response = urllib2.urlopen('http://map.paderborn.freifunk.net/nodes.json')
- data = json.load(response)
- nodes_active = 0
- nodes_total = 0
- clients_count = 0
- for node in data['nodes']:
- if node['flags']['gateway'] or node['flags']['client']:
- continue
- nodes_total += 1
- if node['flags']['online']:
- nodes_active += 1
- for link in data['links']:
- if link['type'] == 'client':
- clients_count += 1
- if stats is None:
- stats = { }
- stats["nodes_active"] = nodes_active
- stats["nodes_total"] = nodes_total
- stats["clients"] = clients_count
- @willie.module.commands('status')
- def ffpb_status(bot, trigger):
- """Status des FFPB-Netzes: Anzahl (aktiver) Knoten + Clients"""
- if stats is None:
- bot.say('Uff, kein Plan wo der Zettel ist. Fragst du später nochmal?')
- return
- bot.say('Es sind {0} Knoten und ca. {1} Clients online.'.format(stats["nodes_active"], stats["clients"]))
- @willie.module.commands('ping')
- def ffpb_ping(bot, trigger):
- """Ping FFPB-Knoten"""
- target_name = trigger.group(2)
- if target_name is None or len(target_name) == 0:
- bot.say('Alter, wen soll ich denn pingen? Einmal mit Profis arbeiten -.-')
- return
- node = ffpb_findnode(target_name)
- if node is None:
- bot.say('Kein Plan wer mit \'' + target_name + '\' gemeint ist :/')
- return
- target = node["network"]["addresses"][0]
- target_alias = node["hostname"]
- print("ping '", target , '"', sep='')
- result = os.system('ping6 -c 2 -W 1 ' + target + ' 2>/dev/null')
- if result == 0:
- bot.say('Knoten "' + target_alias + '" antwortet \o/')
- elif result == 1 or result == 256:
- bot.say('Keine Antwort von "' + target_alias + '" :-(')
- else:
- bot.say('Uh oh, irgendwas ist kaputt. Chef, ping result = ' + str(result) + ' - darf ich das essen?')
- @willie.module.commands('exec-on-peer')
- def ffpb_remoteexec(bot, trigger):
- """Remote Execution fuer FFPB_Knoten"""
- bot_params = trigger.group(2).split(' ',1)
- if len(bot_params) != 2:
- bot.say('Wenn du nicht sagst wo mach ich remote execution bei dir!')
- bot.say('Tipp: !exec-on-peer <peer> <cmd>')
- return
- target_name = bot_params[0]
- target_cmd = bot_params[1]
- if not trigger.admin:
- bot.say('I can haz sudo?')
- return
- if trigger.is_privmsg:
- bot.say('Bitte per Channel.')
- return
- if not trigger.nick in bot.ops[trigger.sender]:
- bot.say('Geh weg.')
- return
- node = ffpb_findnode(target_name)
- if node is None:
- bot.say('Kein Plan wer mit \'' + target_name + '\' gemeint ist :/')
- return
- target = node["network"]["addresses"][0]
- target_alias = node["hostname"]
- cmd = 'ssh -6 -l root ' + target + ' -- "' + target_cmd + '"'
- print("REMOTE EXEC = " + cmd)
- try:
- result = subprocess.check_output(['ssh', '-6n', '-l', 'root', '-o', 'BatchMode=yes', '-o','StrictHostKeyChecking=no', target, target_cmd], stderr=subprocess.STDOUT, shell=False)
- lines = str(result).splitlines()
- if len(lines) == 0:
- bot.say('exec-on-peer(' + target_alias + '): No output')
- return
- msg = 'exec-on-peer(' + target_alias + '): ' + str(len(lines)) + ' Zeilen'
- if len(lines) > 8:
- msg += ' (zeige max. 8)'
- bot.say(msg + ':')
- for line in lines[0:8]:
- bot.say(line)
- except subprocess.CalledProcessError as e:
- bot.say('Fehler '+str(e.returncode)+' bei exec-on-peer('+target_alias+'): ' + e.output)
|