init repo

This commit is contained in:
root 2019-03-30 16:23:24 +00:00
commit 1472938a30
4 changed files with 615 additions and 0 deletions

33
README.md Normal file
View File

@ -0,0 +1,33 @@
t]
Description=Address Python Server
After=syslog.target
[Service]
Type=simple
User=addressserver
Group=addressserver
WorkingDirectory=/home/addressserver/address_scanner
ExecStart=/usr/bin/python3 /home/addressserver/address_scanner/server.py
SyslogIdentifier=address_server
StandardOutput=syslog
StandardError=syslog
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
```
```
apt-get install python3-pip
pip3 install netifaces paho-mqtt
*/2 * * * * /usr/bin/python3 /root/scanner.py -i eth0 -4 192 -6 2a02 --host 192.168.25.221 --username mc8051 --password XXXXXXX -q
```
```
https://www.thomaschristlieb.de/ein-python-script-mit-systemd-als-daemon-systemd-tut-garnicht-weh/
https://stackoverflow.com/a/47370156
```

93
scanner.py Normal file
View File

@ -0,0 +1,93 @@
import netifaces
import optparse
import sys
import socket
import time
import paho.mqtt.client as mqtt
is_connected = False
def on_connect(client, userdata, flags, rc):
global is_connected
if(rc == 0):
is_connected = True
hostname = socket.gethostname()
parser = optparse.OptionParser()
parser.add_option('-i', '--interface', action="store", dest="interface", help="Network Interface", default="")
parser.add_option('-q', '--quite', action="store_true", dest="quite", help="No output")
parser.add_option('-4', '--match4', action="store", dest="match4", help="IPv4 MUST start with this argument", default="")
parser.add_option('-6', '--match6', action="store", dest="match6", help="IPv6 MUST start with this argument", default="")
parser.add_option('--host', action="store", dest="mqqt_host", help="MQTT Host", default="")
parser.add_option('--port', action="store", dest="mqqt_port", help="MQTT Port", default="1883")
parser.add_option('--username', action="store", dest="mqqt_user", help="MQTT Username (optional)", default="")
parser.add_option('--password', action="store", dest="mqqt_pass", help="MQTT Password (optional)", default="")
options, args = parser.parse_args()
client_enabled = not(not options.mqqt_host)
if client_enabled:
client = mqtt.Client()
client.connect(options.mqqt_host, int(options.mqqt_port), 60)
if options.mqqt_user and options.mqqt_pass:
client.username_pw_set(username=options.mqqt_user, password=options.mqqt_pass)
client.on_connect = on_connect
client.loop_start()
else:
print("Warning: no MQTT server defined")
if client_enabled:
timeout = 10
while (not is_connected) and (timeout > 0):
timeout -= 1
time.sleep(0.5)
if timeout <= 0:
print("MQTT timeout")
sys.exit(-1)
try:
addrs = netifaces.ifaddresses(options.interface)
except ValueError as ex:
print("Invalid interface " + options.interface)
sys.exit(-1)
if not options.quite:
print("Checking network interface " + options.interface + " on " + hostname)
if not options.match4 and not options.quite:
print("Warning: no IPv4-Match set")
if not options.match6 and not options.quite:
print("Warning: no IPv6-Match set")
ipv4 = ""
for addr in addrs[netifaces.AF_INET]:
if addr["addr"].startswith(options.match4):
ipv4 = addr["addr"]
break
ipv6 = ""
for addr in addrs[netifaces.AF_INET6]:
if addr["addr"].startswith(options.match6):
ipv6 = addr["addr"]
break
if not options.quite:
print("")
print("Found IPv4 " + ipv4 + " and IPv6 " + ipv6)
if client_enabled:
client.publish("network/" + hostname + "/hostname", hostname)
client.publish("network/" + hostname + "/ipv4", ipv4)
client.publish("network/" + hostname + "/ipv6", ipv6)
client.loop_stop()

420
server.py Normal file
View File

@ -0,0 +1,420 @@
import schedule
import time
import paho.mqtt.client as mqtt
from urllib.parse import urlparse
import requests
import json
import socket
import CloudFlare
import logging
import smtplib
import ssl
import io
logger = logging.getLogger('dns_updater')
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', datefmt='%m/%d/%Y:%H:%M:%S')
ch = logging.StreamHandler()
ch.setFormatter(formatter)
logger.addHandler(ch)
log_capture_string = io.StringIO()
ch2 = logging.StreamHandler(log_capture_string)
ch2.setFormatter(formatter)
logger.addHandler(ch2)
OPENWRT_PATH = "/cgi-bin/luci/"
INTERVAL = 60
userdata = dict()
mqtt_data = dict()
mail_data = dict()
openwrt = dict()
hosts = dict()
dns = dict()
firewall = dict()
def readFile():
with open("settings.json") as json_file:
global userdata
global hosts
global dns
global firewall
global mqtt_data
global mail_data
global openwrt
global OPENWRT_PATH
global INTERVAL
data = json.load(json_file)
userdata = {"apikey": data["apikey"], "username": data["user"]}
INTERVAL = data["interval"]
mqtt_data = data["mqtt"]
mail_data = data["mail"]
openwrt = data["openwrt"]
OPENWRT_PATH = "http://" + openwrt["host"] + OPENWRT_PATH
if "hosts" in data:
z = hosts.copy()
z.update(data["hosts"])
hosts = z
if "dns" in data:
z = dns.copy()
z.update(data["dns"])
dns = z
if "firewall" in data:
z = firewall.copy()
z.update(data["firewall"])
firewall = z
readFile()
def saveFile():
logger.info("Saving settings.json file...")
with open('settings.json', 'wt') as out:
global userdata
global hosts
global dns
global INTERVAL
obj = {
"apikey": userdata["apikey"],
"user": userdata["username"],
"interval": INTERVAL,
"dns": dns,
"hosts": hosts,
"firewall": firewall,
"mqtt": mqtt_data,
"openwrt": openwrt,
"mail": mail_data,
}
res = json.dump(obj, out, sort_keys=True, indent=4, separators=(',', ': '))
logger.info("File saved")
schedule.every(2).minutes.do(saveFile)
def getPublicIP():
r = requests.get("https://ipinfo.io/")
data = json.loads(r.text)
return data.get("ip")
def do_dns_update(cf, zone_name, zone_id, dns_name, ip_address, ip_address_type):
"""Cloudflare API code - example"""
logger.info("Update %s to %s" % (dns_name+"."+zone_name, ip_address))
try:
prefix = "" if dns_name == "@" else dns_name+"."
params = {'name':prefix+zone_name, 'match':'all', 'type':ip_address_type}
dns_records = cf.zones.dns_records.get(zone_id, params=params)
except CloudFlare.exceptions.CloudFlareAPIError as e:
logger.error('/zones/dns_records %s - %d %s - api call failed' % (dns_name, e, e))
updated = False
changed = False
# update the record - unless it's already correct
for dns_record in dns_records:
old_ip_address = dns_record['content']
old_ip_address_type = dns_record['type']
if ip_address_type not in ['A', 'AAAA']:
# we only deal with A / AAAA records
continue
if ip_address_type != old_ip_address_type:
# only update the correct address type (A or AAAA)
# we don't see this becuase of the search params above
logger.debug('IGNORED: %s %s ; wrong address family' % (dns_name, old_ip_address))
continue
if ip_address == old_ip_address:
logger.debug('UNCHANGED: %s %s' % (dns_name, ip_address))
updated = True
continue
# Yes, we need to update this record - we know it's the same address type
dns_record_id = dns_record['id']
dns_record = {
'name':dns_name,
'type':ip_address_type,
'content':ip_address
}
try:
dns_record = cf.zones.dns_records.put(zone_id, dns_record_id, data=dns_record)
changed = True
except CloudFlare.exceptions.CloudFlareAPIError as e:
logger.error('/zones.dns_records.put %s - %d %s - api call failed' % (dns_name, e, e))
logger.debug('UPDATED: %s %s -> %s' % (dns_name, old_ip_address, ip_address))
updated = True
if updated:
return changed
# no exsiting dns record to update - so create dns record
dns_record = {
'name':dns_name,
'type':ip_address_type,
'content':ip_address
}
try:
dns_record = cf.zones.dns_records.post(zone_id, data=dns_record)
changed = True
except CloudFlare.exceptions.CloudFlareAPIError as e:
logger.error('/zones.dns_records.post %s - %d %s - api call failed' % (dns_name, e, e))
logger.debug('CREATED: %s %s' % (dns_name, ip_address))
return changed
if openwrt["enabled"] == True:
jar = requests.cookies.RequestsCookieJar()
fw_login = requests.get(OPENWRT_PATH + "/rpc/auth", cookies=jar, json={
"id": 1,
"method": "login",
"params": [
openwrt["username"],
openwrt["password"]
]
})
if "result" not in fw_login.json() or fw_login.json()["result"] is None:
exit("Incorrect OpenWrt Login")
o = urlparse(OPENWRT_PATH)
jar.set('sysauth', fw_login.json()["result"], domain=o.netloc, path=o.path)
def setFirewall(section, key, new_ipv6):
global jar
logger.info("Updating firewall " + section + "." + key + "=" + new_ipv6)
if not new_ipv6:
logger.debug("Empty IPv6... Skipping...")
return
r = requests.get(OPENWRT_PATH + "/rpc/uci", cookies=jar, json={
"id": 1,
"method": "get_all",
"params": [
"firewall",
section
]
})
data = r.json()
if not "result" in data or data["result"] is None:
logger.warning("Unknown firewall section %s... Skipping..." % (section))
return
result = data["result"]
if not "family" in result or result["family"] is None:
logger.debug("No family set in firewall section %s... Skipping..." % (section))
return
if not key in result or result[key] is None:
logger.debug("No %s set in firewall section %s... Skipping..." % (key, section))
return
if result["family"] != "ipv6":
logger.debuginfo("Section %s is no ipv6... Skipping..." % (section))
return
if result[key] == new_ipv6:
logger.debug("Section %s has same ipv6... Skipping..." % (section))
return
r = requests.get(OPENWRT_PATH + "/rpc/uci", cookies=jar, json={
"id": 1,
"method": "set",
"params": [
"firewall",
section,
key,
new_ipv6
]
})
logger.debug("updated = %s" % r.json()["result"])
return True
def commitFirewall():
global jar
r = requests.get(OPENWRT_PATH + "/rpc/uci", cookies=jar, json={
"id": 1,
"method": "commit",
"params": [
"firewall",
]
})
def updateFirewall():
global hosts
global dns
global firewall
global commitFirewall
global setFirewall
logger.info("Updating firewall")
changed = False
for section in firewall:
for key in firewall[section]:
value = firewall[section][key]
ipv6 = hosts.get(value, dict()).get("ipv6", "")
changed = setFirewall(section, key, ipv6)
if changed == True:
logger.info("Firewall set... Commiting...")
commitFirewall()
logger.info("Firewall commited")
else:
logger.info("Nothing changed. Skipping firewall commit...")
return changed
def updateDNS():
global hosts
global userdata
global dns
global getPublicIP
global do_dns_update
logger.info("Updating DNS")
cf = CloudFlare.CloudFlare(email=userdata["username"], token=userdata["apikey"])
PUBLIC = getPublicIP()
if not PUBLIC or PUBLIC is None:
logger.error("EMPTY PUBLIC IP?!")
return False
changed = False
for domain in dns:
params = {'name':domain}
zones = cf.zones.get(params=params)
zone = zones[0]
zone_name = zone['name']
zone_id = zone['id']
for subdomain in dns[domain]:
ipv4_host = dns[domain][subdomain].get("ipv4", "")
ipv6_host = dns[domain][subdomain].get("ipv6", "")
if ipv4_host.lower() == "public":
ipv4 = PUBLIC
else:
ipv4 = hosts.get(ipv4_host, dict()).get("ipv4", "")
ipv6 = hosts.get(ipv6_host, dict()).get("ipv6", "")
dns_records = []
if ipv4:
if do_dns_update(cf, zone_name, zone_id, subdomain, ipv4, "A"):
changed = True
if ipv6:
if do_dns_update(cf, zone_name, zone_id, subdomain, ipv6, "AAAA"):
changed = True
return changed
def sendMail(mail_data, title, content):
logger.info("Sending mail to %s" % mail_data["receipent"])
# Create a secure SSL context
context = ssl.create_default_context()
# Try to log in to server and send email
try:
server = smtplib.SMTP(mail_data["host"], mail_data["port"])
server.ehlo() # Can be omitted
server.starttls(context=context) # Secure the connection
server.ehlo() # Can be omitted
server.login(mail_data["username"], mail_data["password"])
message = "From: " + mail_data["username"] + "\n"
message += "To: " + mail_data["receipent"] + "\n"
message += "Subject: "+title+"\n"
message += "\n"
message += content
server.sendmail(mail_data["username"], mail_data["receipent"], message)
logger.debug("Mail send")
except Exception as e:
logger.error(e)
finally:
server.quit()
def updateAll():
global updateDNS
global updateFirewall
global openwrt
global log_capture_string
global mail_data
log_capture_string.truncate(0)
log_capture_string.seek(0)
changed = False
if openwrt["enabled"] == True:
if updateFirewall():
changed = True
else:
logger.info("Firewall update is disabled")
if updateDNS():
changed = True
if mail_data and "enabled" in mail_data and mail_data["enabled"] == True:
if changed == True:
sendMail(mail_data, "Info: DNS/Firewall Address Server", log_capture_string.getvalue())
else:
logger.debug("No email. nothing changed")
else:
logger.info("Email is disabled")
schedule.every(INTERVAL).seconds.do(updateAll)
def on_connect(client, userdata, flags, rc):
logger.debug("Connected with result code "+str(rc))
if rc != 0:
logger.error("ERROR: Please check MQTT Login")
client.subscribe("network/+/hostname")
client.subscribe("network/+/ipv4")
client.subscribe("network/+/ipv6")
def on_message(client, userdata, msg):
global hosts
pl = str((msg.payload).decode("utf-8")).lower()
logger.debug(msg.topic + ": " + pl)
if msg.topic.startswith("network/") and (msg.topic.endswith("/ipv4") or msg.topic.endswith("/ipv6")):
host = msg.topic.split("/")[1]
if not host in hosts:
hosts[host] = dict()
hosts[host]["ipv4" if msg.topic.endswith("/ipv4") else "ipv6"] = pl
client = mqtt.Client()
client.username_pw_set(username=mqtt_data["username"], password=mqtt_data["password"])
client.connect(mqtt_data["host"], mqtt_data["port"], 60)
client.on_connect = on_connect
client.on_message = on_message
client.loop_start()
try:
while True:
schedule.run_pending()
time.sleep(1)
except KeyboardInterrupt as ex:
saveFile()
# https://htmlpreview.github.io/?https://raw.githubusercontent.com/openwrt/luci/master/documentation/api/modules/luci.model.uci.html#Cursor.get
# https://wiki.teltonika.lt/view/UCI_command_usage

69
settings.json.sample Normal file
View File

@ -0,0 +1,69 @@
{
"apikey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"dns": {
"domain2.com": {
"@": {
"ipv4": "public",
"ipv6": "proxy"
},
"playing": {
"ipv6": "playground"
}
},
"domain2.com": {
"test": {
"ipv4": "public",
"ipv6": "proxy"
}
}
},
"firewall": {
"cf2bd": {
"dest_ip": "proxy"
},
"cfgbd": {
"dest_ip": "proxy"
},
"cfgd": {
"dest_ip": "plex"
}
},
"hosts": {
"playground": {
"ipv4": "192.168.1.12",
"ipv6": "2a02:908::8b0b"
},
"plex": {
"ipv4": "192.168.1.11",
"ipv6": "2a02:908::d237"
},
"proxy": {
"ipv4": "192.168.1.10",
"ipv6": "2a02:908::9867"
}
},
"interval": 600,
"mail": {
"enabled": true,
"host": "mail.example.com",
"password": "",
"port": 587,
"receipent": "user@example.com",
"username": "dns-updater@example.com"
},
"mqtt": {
"_comment": "Please secure your MQTT server",
"host": "192.168.1.2",
"password": "",
"port": 1883,
"username": "mc8051"
},
"openwrt": {
"_comment": "Please install luci JSON rpc package",
"enabled": true,
"host": "192.168.1.1",
"password": "",
"username": "root"
},
"user": "user@example.com"
}