|
@@ -28,9 +28,10 @@ nodeaccess = None
|
|
|
|
|
|
alfred_method = None
|
|
|
|
|
|
-ffpb_resolver = dns.resolver.Resolver ()
|
|
|
+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."""
|
|
|
|
|
@@ -70,10 +71,12 @@ class MsgHandler(SocketServer.BaseRequestHandler):
|
|
|
except dns.resolver.NXDOMAIN:
|
|
|
return ipaddr
|
|
|
|
|
|
+
|
|
|
class ThreadingTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
|
|
|
"""Defines a threaded TCP socket server."""
|
|
|
bot = None
|
|
|
|
|
|
+
|
|
|
def setup(bot):
|
|
|
"""Called by willie upon loading this plugin."""
|
|
|
|
|
@@ -97,7 +100,7 @@ def setup(bot):
|
|
|
# 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
|
|
|
+ assert peers_repo.bare is False
|
|
|
|
|
|
# if configured, start the messaging server
|
|
|
if int(bot.config.ffpb.msg_enable) == 1:
|
|
@@ -128,6 +131,7 @@ def setup(bot):
|
|
|
# signal end of setup routine
|
|
|
bot.memory['ffpb_in_setup'] = False
|
|
|
|
|
|
+
|
|
|
def shutdown(bot):
|
|
|
global msgserver, nodeaccess
|
|
|
|
|
@@ -149,6 +153,7 @@ def shutdown(bot):
|
|
|
print("Closed messaging server.")
|
|
|
msgserver = None
|
|
|
|
|
|
+
|
|
|
@willie.module.commands("help")
|
|
|
@willie.module.commands("hilfe")
|
|
|
@willie.module.commands("man")
|
|
@@ -180,9 +185,12 @@ def ffpb_help(bot, trigger):
|
|
|
|
|
|
bot.say("Allgemeine Hilfe gibt's mit !help - ohne Parameter.")
|
|
|
|
|
|
+
|
|
|
def playitsafe(bot, trigger,
|
|
|
- botadmin=False, admin_channel=False, via_channel=False, via_privmsg=False, need_op=False, node=None,
|
|
|
- reply_directly=True, debug_user=None, debug_ignorebotadmin=False):
|
|
|
+ botadmin=False, admin_channel=False,
|
|
|
+ via_channel=False, via_privmsg=False, need_op=False,
|
|
|
+ node=None, reply_directly=True,
|
|
|
+ debug_user=None, debug_ignorebotadmin=False):
|
|
|
"""
|
|
|
helper: checks that the triggering user has the necessary rights
|
|
|
|
|
@@ -191,8 +199,8 @@ def playitsafe(bot, trigger,
|
|
|
"""
|
|
|
|
|
|
if via_channel and via_privmsg:
|
|
|
- raise Exception('Der Entwickler ist ein dummer, dummer Junge ' +
|
|
|
- '(playitsafe hat via_channel + via_privmsg gleichzeitig gesetzt).')
|
|
|
+ raise Exception('Der Entwickler ist ein dummer, dummer Junge. ' +
|
|
|
+ '(playitsafe: via_channel && via_privmsg).')
|
|
|
|
|
|
user = trigger.nick if debug_user is None else debug_user
|
|
|
user = user.lower()
|
|
@@ -212,11 +220,13 @@ def playitsafe(bot, trigger,
|
|
|
# via_privmsg: the request must be a private conversation
|
|
|
if via_privmsg and not trigger.is_privmsg:
|
|
|
if reply_directly:
|
|
|
- bot.say('Solche Informationen gibt es nur per PM, da bin ich ja schon ein klein wenig sensibel ...')
|
|
|
+ bot.say('Solche Informationen gibt es nur per PM, ' +
|
|
|
+ 'da bin ich ja schon ein klein wenig sensibel ...')
|
|
|
return False
|
|
|
|
|
|
# need_op: if the message is in a channel, check that the user has OP there
|
|
|
- if need_op and (not trigger.is_privmsg) and (not user in bot.ops[trigger.sender]):
|
|
|
+ if need_op and (not trigger.is_privmsg) and \
|
|
|
+ (not user in bot.ops[trigger.sender]):
|
|
|
if reply_directly:
|
|
|
bot.say('Keine Zimtschnecke, keine Kekse.')
|
|
|
return False
|
|
@@ -244,6 +254,7 @@ def playitsafe(bot, trigger,
|
|
|
|
|
|
return True
|
|
|
|
|
|
+
|
|
|
@willie.module.commands('nodeacl')
|
|
|
def ffpb_nodeacl(bot, trigger):
|
|
|
"""Configure ACL for nodes."""
|
|
@@ -296,19 +307,19 @@ def ffpb_nodeacl(bot, trigger):
|
|
|
nodeaccess[uid] = []
|
|
|
if not value in nodeaccess[uid]:
|
|
|
nodeaccess[uid].append(value)
|
|
|
- bot.say('201 nodeACL \'{0}\' +\'{1}\''.format(uid, value))
|
|
|
+ bot.say("201 nodeACL '{0}' +'{1}'".format(uid, value))
|
|
|
else:
|
|
|
- bot.say('304 nodeACL \'{0}\' contains \'{1}\''.format(uid, value))
|
|
|
+ bot.say("304 nodeACL '{0}' contains '{1}'".format(uid, value))
|
|
|
|
|
|
elif cmd == 'del':
|
|
|
if len(uid) == 0:
|
|
|
- bot.say('404 nodeACL \'{0}\''.format(uid))
|
|
|
+ bot.say("404 nodeACL '{0}'".format(uid))
|
|
|
return
|
|
|
if value in nodeaccess[uid]:
|
|
|
nodeaccess[uid].remove(value)
|
|
|
- bot.say('200 nodeACL \'{0}\' -\'{1}\''.format(uid, value))
|
|
|
+ bot.say("200 nodeACL '{0}' -'{1}'".format(uid, value))
|
|
|
else:
|
|
|
- bot.say('404 nodeACL \'{0}\' does not contain \'{1}\''.format(uid, value))
|
|
|
+ bot.say("404 nodeACL '{0}' has no '{1}'".format(uid, value))
|
|
|
|
|
|
elif cmd == 'check':
|
|
|
if len(uid) == 0:
|
|
@@ -320,17 +331,22 @@ def ffpb_nodeacl(bot, trigger):
|
|
|
bot.say('Nope, kein Plan was für ein Knoten das ist.')
|
|
|
return
|
|
|
|
|
|
- result = playitsafe(bot, trigger, debug_user=uid[0], debug_ignorebotadmin=True, node=node, reply_directly=False)
|
|
|
- if result == True:
|
|
|
+ result = playitsafe(bot, trigger,
|
|
|
+ debug_user=uid[0], debug_ignorebotadmin=True,
|
|
|
+ node=node, reply_directly=False)
|
|
|
+ if result is True:
|
|
|
bot.say('Jupp.')
|
|
|
- elif result == False:
|
|
|
+ elif result is False:
|
|
|
bot.say('Nope.')
|
|
|
else:
|
|
|
bot.say('Huh? result=' + str(result))
|
|
|
|
|
|
return
|
|
|
|
|
|
- bot.say('Unbekanntes Kommando. Probier "list [user]", "add user value" oder "del user value". Value kann node_id oder hostname-Maske sein.')
|
|
|
+ bot.say('Unbekanntes Kommando. Probier ' +
|
|
|
+ '"list [user]", "add user value" oder "del user value". ' +
|
|
|
+ 'Value kann node_id oder hostname-Maske sein.')
|
|
|
+
|
|
|
|
|
|
def ffpb_ensurenodeid(nodedata):
|
|
|
"""Makes sure that the given dict has a 'node_id' field."""
|
|
@@ -339,7 +355,9 @@ def ffpb_ensurenodeid(nodedata):
|
|
|
return nodedata
|
|
|
|
|
|
# derive node's id
|
|
|
- nodeid = nodedata['network']['mac'].replace(':', '') if 'network' in nodedata and 'mac' in nodedata['network'] else None
|
|
|
+ nodeid = None
|
|
|
+ if 'network' in nodedata and 'mac' in nodedata['network']:
|
|
|
+ nodeid = nodedata['network']['mac'].replace(':', '')
|
|
|
|
|
|
# assemble extended data
|
|
|
result = {'node_id': nodeid}
|
|
@@ -348,6 +366,7 @@ def ffpb_ensurenodeid(nodedata):
|
|
|
|
|
|
return result
|
|
|
|
|
|
+
|
|
|
def ffpb_findnode(name, alfred_data=None, allow_fuzzymatching=True):
|
|
|
"""helper: try to identify the node the user meant by the given name"""
|
|
|
|
|
@@ -359,7 +378,7 @@ def ffpb_findnode(name, alfred_data=None, allow_fuzzymatching=True):
|
|
|
|
|
|
# disable fuzzy matching if name is enclosed in quotes
|
|
|
if name.startswith('\'') and name.endswith('\'') or \
|
|
|
- name.startswith('"') and name.endswith('"'):
|
|
|
+ name.startswith('"') and name.endswith('"'):
|
|
|
name = name[1:-1]
|
|
|
allow_fuzzymatching = False
|
|
|
|
|
@@ -377,7 +396,7 @@ def ffpb_findnode(name, alfred_data=None, allow_fuzzymatching=True):
|
|
|
for nodeid in alfred_data:
|
|
|
node = alfred_data[nodeid]
|
|
|
if "network" in node:
|
|
|
- if "mac" in node["network"] and node["network"]["mac"].lower() == mac:
|
|
|
+ if node["network"].get("mac", "").lower() == mac:
|
|
|
return ffpb_ensurenodeid(node)
|
|
|
if "mesh_interfaces" in node["network"]:
|
|
|
for mim in node["network"]["mesh_interfaces"]:
|
|
@@ -431,7 +450,7 @@ def ffpb_findnode(name, alfred_data=None, allow_fuzzymatching=True):
|
|
|
'node_id': peer_mac.replace(':', ''),
|
|
|
'hostname': peer_name,
|
|
|
'network': {
|
|
|
- 'addresses': [mac2ipv6(peer_mac, 'fdca:ffee:ff12:132:'),],
|
|
|
+ 'addresses': [mac2ipv6(peer_mac, 'fdca:ffee:ff12:132:'), ],
|
|
|
'mac': peer_mac,
|
|
|
},
|
|
|
'hardware': {
|
|
@@ -454,8 +473,9 @@ def ffpb_findnode(name, alfred_data=None, allow_fuzzymatching=True):
|
|
|
# none of the above was able to identify the requested node
|
|
|
return None
|
|
|
|
|
|
+
|
|
|
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"""
|
|
|
+ """helper: call ffpb_findnode() and give common answers via bot on error"""
|
|
|
|
|
|
if name is None or len(name) == 0:
|
|
|
if not bot is None:
|
|
@@ -466,7 +486,7 @@ def ffpb_findnode_from_botparam(bot, name, ensure_recent_alfreddata=True):
|
|
|
if ensure_recent_alfreddata and alfred_data is None:
|
|
|
if not bot is None:
|
|
|
bot.say('Informationen sind ausverkauft bzw. veraltet, ' +
|
|
|
- 'daher sage ich mal lieber nichts zu \'' + name + '\'.')
|
|
|
+ 'daher sage ich mal lieber nichts zu \'' + name + '\'.')
|
|
|
return None
|
|
|
|
|
|
node = ffpb_findnode(name, alfred_data)
|
|
@@ -476,6 +496,7 @@ 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."""
|
|
@@ -485,6 +506,7 @@ def mac2ipv6(mac, prefix=None):
|
|
|
result = prefix + result[6:]
|
|
|
return result
|
|
|
|
|
|
+
|
|
|
@willie.module.interval(30)
|
|
|
def ffpb_updatealfred(bot):
|
|
|
"""Aktualisiere ALFRED-Daten"""
|
|
@@ -522,7 +544,7 @@ def ffpb_updatealfred(bot):
|
|
|
bot.memory['alfred_data'] = alfred_data
|
|
|
bot.memory['alfred_update'] = updated
|
|
|
|
|
|
- seen_nodes = bot.memory['seen_nodes'] if 'seen_nodes' in bot.memory else None
|
|
|
+ seen_nodes = bot.memory.get('seen_nodes', None)
|
|
|
if not seen_nodes is None:
|
|
|
new = []
|
|
|
for nodeid in alfred_data:
|
|
@@ -569,14 +591,15 @@ def ffpb_updatealfred(bot):
|
|
|
action_target = bot.config.ffpb.msg_target_public
|
|
|
bot.msg(action_target, '\x01ACTION %s\x01' % action_msg)
|
|
|
|
|
|
+
|
|
|
def get_alfred_data(bot, ensure_not_outdated=True):
|
|
|
"""
|
|
|
Retrieves the stored alfred_data and optionally checks
|
|
|
that it has been updated no more than 5 minutes ago.
|
|
|
"""
|
|
|
|
|
|
- alfred_data = bot.memory['alfred_data'] if 'alfred_data' in bot.memory else None
|
|
|
- alfred_update = bot.memory['alfred_update'] if 'alfred_update' in bot.memory else 0
|
|
|
+ alfred_data = bot.memory.get('alfred_data', None)
|
|
|
+ alfred_update = bot.memory.get('alfred_update', 0)
|
|
|
|
|
|
if alfred_data is None:
|
|
|
return None
|
|
@@ -589,15 +612,17 @@ def get_alfred_data(bot, ensure_not_outdated=True):
|
|
|
|
|
|
return alfred_data
|
|
|
|
|
|
+
|
|
|
def ffpb_get_batcave_nodefield(nodeid, field):
|
|
|
"""Query the given field for the given nodeid from the BATCAVE."""
|
|
|
|
|
|
raw_data = None
|
|
|
try:
|
|
|
- # query BATCAVE for node's field
|
|
|
+ # query BATCAVE for node's field
|
|
|
raw_data = urllib2.urlopen('http://[fdca:ffee:ff12:a255::253]:8888/node/{0}/{1}'.format(nodeid, field))
|
|
|
except urllib2.URLError as err:
|
|
|
- print('Failed to contact BATCAVE for \'{0}\'->\'{1}\': {2}'.format(nodeid, field, err))
|
|
|
+ print('Failed to contact BATCAVE for \'{0}\'->\'{1}\': {2}'.format(
|
|
|
+ nodeid, field, err))
|
|
|
return None
|
|
|
|
|
|
try:
|
|
@@ -607,6 +632,7 @@ def ffpb_get_batcave_nodefield(nodeid, field):
|
|
|
print('Could not parse BATCAVE\'s response as JSON for \'{0}\'->\'{1}\':'.format(nodeid, field, err))
|
|
|
return None
|
|
|
|
|
|
+
|
|
|
@willie.module.commands('debug-alfred')
|
|
|
def ffpb_debug_alfred(bot, trigger):
|
|
|
"""Show statistics of available ALFRED data."""
|
|
@@ -615,7 +641,9 @@ def ffpb_debug_alfred(bot, trigger):
|
|
|
if alfred_data is None:
|
|
|
bot.say("Keine ALFRED-Daten vorhanden.")
|
|
|
else:
|
|
|
- bot.say("ALFRED Daten: count={0} lastupdate={1}".format(len(alfred_data), bot.memory['alfred_update']))
|
|
|
+ bot.say("ALFRED Daten: count={0} lastupdate={1}".format(
|
|
|
+ len(alfred_data), bot.memory['alfred_update']))
|
|
|
+
|
|
|
|
|
|
@willie.module.interval(60)
|
|
|
def ffpb_updatepeers(bot):
|
|
@@ -649,7 +677,8 @@ def ffpb_updatepeers(bot):
|
|
|
else:
|
|
|
changed.append(f.a_blob.name)
|
|
|
|
|
|
- response = "Knoten-Update (VPN +{0} %{1} -{2}): ".format(len(added), len(renamed)+len(changed), len(deleted))
|
|
|
+ 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:
|
|
@@ -661,6 +690,7 @@ 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."""
|
|
@@ -697,6 +727,7 @@ def ffpb_fetch_stats(bot, url, memoryid):
|
|
|
|
|
|
return (nodes_active, nodes_total, clients_count)
|
|
|
|
|
|
+
|
|
|
def pretty_date(timestamp=False):
|
|
|
"""
|
|
|
Get a datetime object or a int() Epoch timestamp and return a
|
|
@@ -739,6 +770,7 @@ def pretty_date(timestamp=False):
|
|
|
return "vor " + str(day_diff) + " Tagen"
|
|
|
return "am " + compare.strftime('%d.%m.%Y um %H:%M Uhr')
|
|
|
|
|
|
+
|
|
|
@willie.module.commands('ping')
|
|
|
def ffpb_ping(bot, trigger=None, target_name=None, reply_directly=True):
|
|
|
"""Ping the given node"""
|
|
@@ -747,12 +779,13 @@ def ffpb_ping(bot, trigger=None, target_name=None, reply_directly=True):
|
|
|
if target_name is None:
|
|
|
target_name = trigger.group(2)
|
|
|
node = ffpb_findnode_from_botparam(bot, target_name,
|
|
|
- ensure_recent_alfreddata=False)
|
|
|
+ ensure_recent_alfreddata=False)
|
|
|
if node is None:
|
|
|
return None
|
|
|
|
|
|
# get the first non-linklocal address from the node
|
|
|
- target = [x for x in node["network"]["addresses"] if not x.lower().startswith("fe80:")][0]
|
|
|
+ target = [x for x in node["network"]["addresses"]
|
|
|
+ if not x.lower().startswith("fe80:")][0]
|
|
|
target_alias = node["hostname"]
|
|
|
|
|
|
# execute the actual ping and reply the result
|
|
@@ -777,6 +810,7 @@ def ffpb_ping(bot, trigger=None, target_name=None, reply_directly=True):
|
|
|
bot.say('Uh oh, irgendwas ist kaputt. Chef, ping result = ' + str(result) + ' - darf ich das essen?')
|
|
|
return None
|
|
|
|
|
|
+
|
|
|
@willie.module.commands('mesh')
|
|
|
def ffpb_nodemesh(bot, trigger):
|
|
|
"""Display mesh partners of the given node."""
|
|
@@ -784,22 +818,24 @@ def ffpb_nodemesh(bot, trigger):
|
|
|
# identify node or bail out
|
|
|
target_name = trigger.group(2)
|
|
|
node = ffpb_findnode_from_botparam(bot, target_name,
|
|
|
- ensure_recent_alfreddata=False)
|
|
|
+ ensure_recent_alfreddata=False)
|
|
|
if node is None:
|
|
|
return None
|
|
|
|
|
|
# derive node's id
|
|
|
nodeid = node['node_id'] if 'node_id' in node else None
|
|
|
if nodeid is None:
|
|
|
- msg = 'Mist, ich habe gerade den Zettel verlegt auf dem die Node-ID von \'{0}\' steht, bitte frag später noch einmal.'
|
|
|
- bot.say(msg.format(node['hostname'] if 'hostname' in node else target_name))
|
|
|
+ msg = 'Mist, ich habe gerade den Zettel verlegt auf dem die Node-ID' + \
|
|
|
+ ' von \'{0}\' steht, bitte frag später noch einmal.'
|
|
|
+ bot.say(msg.format(node.get('hostname', target_name)))
|
|
|
return
|
|
|
|
|
|
# query BATCAVE for node's neighbours (result is a list of MAC addresses)
|
|
|
cave_result = ffpb_get_batcave_nodefield(nodeid, 'neighbours')
|
|
|
if cave_result is None:
|
|
|
- msg = 'Hm, scheinbar liegen zu \'{0}\' keine Daten vor. Klingt komisch, ist aber so.'
|
|
|
- bot.say(msg.format(node['hostname'] if 'hostname' in node else target_name))
|
|
|
+ msg = 'Hm, scheinbar liegen zu \'{0}\' keine Daten vor. ' + \
|
|
|
+ 'Klingt komisch, ist aber so.'
|
|
|
+ bot.say(msg.format(node.get('hostname', target_name)))
|
|
|
return
|
|
|
|
|
|
# query BATCAVE for neighbour's names
|
|
@@ -811,10 +847,10 @@ def ffpb_nodemesh(bot, trigger):
|
|
|
gateways = set()
|
|
|
for line in req:
|
|
|
ident, name = line.strip().split('=')
|
|
|
- if ident == name and ident.startswith('c0:ff:ee:ba:be:'):
|
|
|
- gateways.add('Gateway ' + ident[len('c0:ff:ee:ba:be:'):])
|
|
|
- else:
|
|
|
- neighbours.add(name)
|
|
|
+ if ident == name and ident.startswith('c0:ff:ee:ba:be:'):
|
|
|
+ gateways.add('Gateway ' + ident[len('c0:ff:ee:ba:be:'):])
|
|
|
+ else:
|
|
|
+ neighbours.add(name)
|
|
|
neighbours = [x for x in neighbours]
|
|
|
gateways = sorted([x for x in gateways])
|
|
|
|
|
@@ -825,7 +861,9 @@ def ffpb_nodemesh(bot, trigger):
|
|
|
elif len(neighbours) == 1:
|
|
|
reply += u' mesht mit \'{0}\''.format(neighbours[0])
|
|
|
else:
|
|
|
- reply += ' mesht mit \'{0}\' und \'{1}\''.format('\', \''.join(neighbours[:-1]), neighbours[-1])
|
|
|
+ all_except_last = '\', \''.join(neighbours[:-1])
|
|
|
+ last = neighbours[-1]
|
|
|
+ reply += ' mesht mit \'{0}\' und \'{1}\''.format(all_except_last, last)
|
|
|
|
|
|
if len(gateways) > 0:
|
|
|
if len(neighbours) == 0:
|
|
@@ -836,10 +874,13 @@ def ffpb_nodemesh(bot, trigger):
|
|
|
if len(gateways) == 1:
|
|
|
reply += gateways[0]
|
|
|
else:
|
|
|
- reply += '{0} und {1}'.format('\', \''.join(gateways[:-1]), gateways[-1])
|
|
|
+ all_except_last = '\', \''.join(gateways[:-1])
|
|
|
+ last = gateways[-1]
|
|
|
+ reply += '{0} und {1}'.format(all_except_last, last)
|
|
|
|
|
|
bot.say(reply)
|
|
|
|
|
|
+
|
|
|
@willie.module.commands('exec-on-peer')
|
|
|
def ffpb_remoteexec(bot, trigger):
|
|
|
"""Remote execution on the given node"""
|
|
@@ -855,7 +896,7 @@ def ffpb_remoteexec(bot, trigger):
|
|
|
|
|
|
# identify requested node or bail out
|
|
|
node = ffpb_findnode_from_botparam(bot, target_name,
|
|
|
- ensure_recent_alfreddata=False)
|
|
|
+ ensure_recent_alfreddata=False)
|
|
|
if node is None:
|
|
|
return
|
|
|
|