|
@@ -29,13 +29,15 @@ ffpb_resolver = dns.resolver.Resolver ()
|
|
|
ffpb_resolver.nameservers = ['10.132.254.53']
|
|
|
|
|
|
class MsgHandler(SocketServer.BaseRequestHandler):
|
|
|
+ """Reads line from TCP stream and forwards it to configured IRC channels."""
|
|
|
+
|
|
|
def handle(self):
|
|
|
- data = self.request.recv(2048).strip()
|
|
|
+ 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() :-(")
|
|
|
+ print("ERROR: No bot in handle( ) :-(")
|
|
|
return
|
|
|
|
|
|
target = bot.config.core.owner
|
|
@@ -51,6 +53,9 @@ class MsgHandler(SocketServer.BaseRequestHandler):
|
|
|
bot.msg(target, "[{0}] {1}".format(sender, str(data)))
|
|
|
|
|
|
def _resolve_name (self, ip):
|
|
|
+ """Resolves the host name of the given IP address
|
|
|
+ and strips away the suffix (.infra)?.ffpb"""
|
|
|
+
|
|
|
if ip.startswith ("127."):
|
|
|
return "localhost"
|
|
|
|
|
@@ -78,13 +83,16 @@ def setup(bot):
|
|
|
highscores['clients_ts'] = time.time()
|
|
|
bot.memory['highscores'] = highscores
|
|
|
|
|
|
+ # no need to configure anything else if the ffpb config section is missing
|
|
|
if not bot.config.has_section('ffpb'):
|
|
|
return
|
|
|
|
|
|
+ # open the git repository containing the peers files
|
|
|
if not bot.config.ffpb.peers_directory is None:
|
|
|
peers_repo = git.Repo(bot.config.ffpb.peers_directory)
|
|
|
assert peers_repo.bare == False
|
|
|
|
|
|
+ # if configured start the messaging server
|
|
|
if int(bot.config.ffpb.msg_enable) == 1:
|
|
|
host = "localhost"
|
|
|
port = 2342
|
|
@@ -100,19 +108,22 @@ def setup(bot):
|
|
|
msgserver_thread.daemon = True
|
|
|
msgserver_thread.start()
|
|
|
|
|
|
+ # initially fetch ALFRED data
|
|
|
alfred_method = bot.config.ffpb.alfred_method
|
|
|
ffpb_updatealfred(bot)
|
|
|
|
|
|
def shutdown(bot):
|
|
|
global msgserver
|
|
|
|
|
|
+ # store highscores
|
|
|
if 'highscores' in bot.memory and not bot.memory['highscores'] is None:
|
|
|
bot.memory['highscores'].sync()
|
|
|
bot.memory['highscores'].close()
|
|
|
del(bot.memory['highscores'])
|
|
|
|
|
|
+ # shut down messaging server
|
|
|
if not msgserver is None:
|
|
|
- msgserver.shutdown()
|
|
|
+ msgserver.shutdown()
|
|
|
print("Closed messaging server.")
|
|
|
msgserver = None
|
|
|
|
|
@@ -120,6 +131,8 @@ def shutdown(bot):
|
|
|
@willie.module.commands("hilfe")
|
|
|
@willie.module.commands("man")
|
|
|
def ffpb_help(bot, trigger):
|
|
|
+ """Meldet häufig benutzte Funktionen."""
|
|
|
+
|
|
|
functions = {
|
|
|
"!ping <knoten>": "Prüfe ob der Knoten erreichbar ist.",
|
|
|
"!status": "Aktuellen Status des Netzwerks (insb. Anzahl Knoten und Clients) ausgegeben.",
|
|
@@ -140,9 +153,12 @@ def ffpb_help(bot, trigger):
|
|
|
bot.say("Hilfe zu '" + fun + "': " + functions[fun])
|
|
|
return
|
|
|
|
|
|
- bot.say("Allgemeine Hilfe gibt's mit !help - ohne Parameter.")
|
|
|
+ bot.say("Allgemeine Hilfe gib t's mit !help - ohne Parameter.")
|
|
|
|
|
|
def ffpb_findnode(bot, name):
|
|
|
+ """helper: try to identify the node the user meant by the given name"""
|
|
|
+
|
|
|
+ # no name, no node
|
|
|
if name is None or len(name) == 0:
|
|
|
return None
|
|
|
|
|
@@ -152,7 +168,7 @@ def ffpb_findnode(bot, name):
|
|
|
alfred_data = bot.memory['alfred_data'] if 'alfred_data' in bot.memory else None
|
|
|
|
|
|
if not alfred_data is None:
|
|
|
- # try to match MAC
|
|
|
+ # try to match MAC in ALFRED data
|
|
|
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()
|
|
@@ -186,7 +202,7 @@ def ffpb_findnode(bot, name):
|
|
|
else:
|
|
|
names[h] = nodeid
|
|
|
|
|
|
- # still not found -> try peers_repo
|
|
|
+ # try peers_repo
|
|
|
if not peers_repo is None:
|
|
|
peer_name = None
|
|
|
peer_mac = None
|
|
@@ -220,16 +236,22 @@ def ffpb_findnode(bot, name):
|
|
|
# if we got exactly one candidate that might be it
|
|
|
return alfred_data[names[possibilities[0]]]
|
|
|
|
|
|
+ # none of the above was able to identify the requested node
|
|
|
return None
|
|
|
|
|
|
def ffpb_get_alfreddata(bot, ensure_recent=True):
|
|
|
+ """helper: return current ALFRED data (or None,
|
|
|
+ if the data is outdated and ensure_recent is set)"""
|
|
|
+
|
|
|
if not 'alfred_data' in bot.memory or bot.memory['alfred_data'] is None:
|
|
|
return None
|
|
|
|
|
|
if ensure_recent:
|
|
|
+ # get timestamp of last ALFRED update (set by ffpb_updatealfred())
|
|
|
alfred_update = bot.memory['alfred_update'] if 'alfred_update' in bot.memory else None
|
|
|
if alfred_update is None: return None
|
|
|
|
|
|
+ # data must not be older than 5 minutes
|
|
|
timeout = datetime.datetime.now() - datetime.timedelta(minutes=5)
|
|
|
is_outdated = timeout > alfred_update
|
|
|
#print("ALFRED outdated? {0} (timeout={1} vs. lastupdate={2})".format(is_outdated, timeout, alfred_update))
|
|
@@ -239,14 +261,15 @@ def ffpb_get_alfreddata(bot, ensure_recent=True):
|
|
|
return bot.memory['alfred_data']
|
|
|
|
|
|
def ffpb_findnode_from_botparam(bot, name, ensure_recent_alfreddata = True):
|
|
|
+ """helper: call ffpb_findnode() and give common answers via bot if nothing has been found"""
|
|
|
if (name is None or len(name) == 0):
|
|
|
bot.reply("Grün.")
|
|
|
return None
|
|
|
|
|
|
alfred_data = ffpb_get_alfreddata(bot, ensure_recent_alfreddata)
|
|
|
- if alfred_data is None:
|
|
|
+ if alfred_data is None and ensure_recent_alfreddata:
|
|
|
bot.say("Ich habe gerade keine (aktuellen) Informationen, daher sage ich mal lieber nichts zu '" + name + "'.")
|
|
|
- return None
|
|
|
+ return None
|
|
|
|
|
|
node = ffpb_findnode(bot, name)
|
|
|
if node is None:
|
|
@@ -255,6 +278,8 @@ def ffpb_findnode_from_botparam(bot, name, ensure_recent_alfreddata = True):
|
|
|
return node
|
|
|
|
|
|
def mac2ipv6(mac, prefix=None):
|
|
|
+ """Calculate IPv6 address from given MAC,
|
|
|
+ optionally replacing the fe80:: prefix with a given one."""
|
|
|
result = str(netaddr.EUI(mac).ipv6_link_local())
|
|
|
if (not prefix is None) and (result.startswith("fe80::")):
|
|
|
result = prefix + result[6:]
|
|
@@ -297,6 +322,8 @@ def ffpb_updatealfred(bot):
|
|
|
|
|
|
@willie.module.commands('debug-alfred')
|
|
|
def ffpb_debug_alfred(bot, trigger):
|
|
|
+ """Zeige Stand der Alfred-Daten an."""
|
|
|
+
|
|
|
alfred_data = ffpb_get_alfreddata(bot)
|
|
|
|
|
|
if alfred_data is None:
|
|
@@ -306,18 +333,24 @@ def ffpb_debug_alfred(bot, trigger):
|
|
|
|
|
|
@willie.module.commands('alfred-data')
|
|
|
def ffpb_peerdata(bot, trigger):
|
|
|
+ """Zeige Daten zum angegebenen Node an."""
|
|
|
+
|
|
|
+ # user must be a bot admin
|
|
|
if (not trigger.admin):
|
|
|
bot.say('I wont leak (possibly) sensitive data to you.')
|
|
|
return
|
|
|
|
|
|
+ # query must be a PM or as OP in the channel
|
|
|
if (not trigger.is_privmsg) and (not trigger.nick in bot.ops[trigger.sender]):
|
|
|
bot.say('Kein Keks? Keine Daten.')
|
|
|
return
|
|
|
|
|
|
+ # identify node or bail out
|
|
|
target_name = trigger.group(2)
|
|
|
node = ffpb_findnode_from_botparam(bot, target_name)
|
|
|
if node is None: return
|
|
|
|
|
|
+ # reply each key in the node's data
|
|
|
for key in node:
|
|
|
if key in [ 'hostname' ]: continue
|
|
|
bot.say("{0}.{1} = {2}".format(node['hostname'], key, str(node[key])))
|
|
@@ -325,6 +358,7 @@ def ffpb_peerdata(bot, trigger):
|
|
|
@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
|
|
@@ -366,6 +400,9 @@ def ffpb_updatepeers(bot):
|
|
|
bot.msg(bot.config.ffpb.msg_target, response)
|
|
|
|
|
|
def ffpb_fetch_stats(bot, url, memoryid):
|
|
|
+ """Fetch a ffmap-style nodes.json from the given URL and
|
|
|
+ store it in the bot's memory."""
|
|
|
+
|
|
|
response = urllib2.urlopen(url)
|
|
|
|
|
|
data = json.load(response)
|
|
@@ -400,6 +437,8 @@ def ffpb_fetch_stats(bot, url, memoryid):
|
|
|
|
|
|
@willie.module.interval(15)
|
|
|
def ffpb_get_stats(bot):
|
|
|
+ """Hole aktuelle Statistik-Daten, falls sich der Highscore ändert melde dies."""
|
|
|
+
|
|
|
highscores = bot.memory['highscores'] if 'highscores' in bot.memory else None
|
|
|
if highscores is None:
|
|
|
print('HIGHSCORE not in bot memory')
|
|
@@ -472,7 +511,8 @@ def pretty_date(time=False):
|
|
|
|
|
|
@willie.module.commands('ping')
|
|
|
def ffpb_ping(bot, trigger=None, target_name=None):
|
|
|
- """Ping FFPB-Knoten"""
|
|
|
+ """Ping an Knoten"""
|
|
|
+
|
|
|
if target_name is None: target_name = trigger.group(2)
|
|
|
node = ffpb_findnode_from_botparam(bot, target_name, ensure_recent_alfreddata=False)
|
|
|
if node is None: return None
|
|
@@ -497,7 +537,8 @@ def ffpb_ping(bot, trigger=None, target_name=None):
|
|
|
|
|
|
@willie.module.commands('exec-on-peer')
|
|
|
def ffpb_remoteexec(bot, trigger):
|
|
|
- """Remote Execution fuer FFPB_Knoten"""
|
|
|
+ """Remote Execution für Knoten (mit SSH-Key des Bots)"""
|
|
|
+
|
|
|
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!')
|
|
@@ -507,28 +548,37 @@ def ffpb_remoteexec(bot, trigger):
|
|
|
target_name = bot_params[0]
|
|
|
target_cmd = bot_params[1]
|
|
|
|
|
|
+ # remote execution may only be trigger by bot admins
|
|
|
if not trigger.admin:
|
|
|
bot.say('I can haz sudo?')
|
|
|
return
|
|
|
|
|
|
+ # make sure remote execution is done in public
|
|
|
if trigger.is_privmsg:
|
|
|
bot.say('Bitte per Channel.')
|
|
|
return
|
|
|
|
|
|
+ # double-safety: user must be op in the channel, too (hoping for NickServ authentication)
|
|
|
if not trigger.nick in bot.ops[trigger.sender]:
|
|
|
bot.say('Geh weg.')
|
|
|
return
|
|
|
|
|
|
+ # identify requested node or bail out
|
|
|
node = ffpb_findnode_from_botparam(bot, target_name, ensure_recent_alfreddata=False)
|
|
|
if node is None: return
|
|
|
|
|
|
+ # use the node's first non-linklocal address
|
|
|
target = [x for x in node["network"]["addresses"] if not x.lower().startswith("fe80:")][0]
|
|
|
target_alias = node["hostname"]
|
|
|
|
|
|
+ # assemble SSH command
|
|
|
cmd = 'ssh -6 -l root ' + target + ' -- "' + target_cmd + '"'
|
|
|
print("REMOTE EXEC = " + cmd)
|
|
|
try:
|
|
|
+ # call SSH
|
|
|
result = subprocess.check_output(['ssh', '-6n', '-l', 'root', '-o', 'BatchMode=yes', '-o','StrictHostKeyChecking=no', target, target_cmd], stderr=subprocess.STDOUT, shell=False)
|
|
|
+
|
|
|
+ # fetch results and send at most 8 of them as response
|
|
|
lines = str(result).splitlines()
|
|
|
|
|
|
if len(lines) == 0:
|
|
@@ -546,3 +596,4 @@ def ffpb_remoteexec(bot, trigger):
|
|
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
bot.say('Fehler '+str(e.returncode)+' bei exec-on-peer('+target_alias+'): ' + e.output)
|
|
|
+
|