Commit 693dd5fe authored by Konrad Mohrfeldt's avatar Konrad Mohrfeldt
Browse files

rewrite dynschiss

* replace bobo with bottle as there’s no support for bobo in py3
* allow environment configuration of zone and base domain
* add support for determining the ip address automatically
* only apply dns changes if the ip address changed
* use stdin for nsupdate instead of tempfiles
parent d8874625
from base64 import b64decode
import logging
import os
import socket
import subprocess
import bottle
from bottle import abort, request
ZONE = os.environ['DYNSCHISS_ZONE']
try:
BASE_DOMAIN = '.' + os.environ['DYNSCHISS_BASE']
except KeyError:
BASE_DOMAIN = ''
app = application = bottle.Bottle()
@app.route('/update', 'POST')
def update_dns():
auth_str = bottle.request.headers['Authorization'].split()[1]
user = b64decode(auth_str).decode().split(sep=':')[0]
ip = request.forms.get('ip', request.environ['REMOTE_ADDR'])
updater = DynDnsUpdater(ZONE, user + BASE_DOMAIN)
try:
domain = request.forms['domain']
except KeyError:
return abort(400, 'domain missing')
try:
updater.update(domain, ip)
except ValueError as exc:
return abort(400, str(exc))
return bottle.HTTPResponse(status=201)
class DynDnsUpdater:
NSUPDATE_TEMPLATE = """server {dns_host} {dns_port}
zone {zone}.
update delete {domain} A
update add {domain} {ttl} A {ip}
send"""
def __init__(self, zone, base_domain, dns_host='127.0.0.1', dns_port=53, ttl=60):
self.zone = zone
self.base_domain = base_domain
self.dns_host = dns_host
self.dns_port = dns_port
self.ttl = ttl
def update(self, domain, ip):
self._validate(domain)
fqdn = f'{domain}{self.zone}.'
current_ip = socket.gethostbyname(fqdn)
if current_ip != ip:
logging.info('Changing IP address for %s. Current address is '
'%s, new address is %s.', fqdn, current_ip, ip)
update_commands = self.NSUPDATE_TEMPLATE.format(
dns_host=self.dns_host, dns_port=self.dns_port,
zone=self.zone, domain=fqdn, ttl=self.ttl, ip=ip)
self._execute_nsupdate(update_commands)
return self
def _validate(self, domain):
namespace = f'.{self.base_domain}.' if self.base_domain else '.' + self.zone
if not domain.endswith(namespace):
raise ValueError(f'Domain is not in predefined namespace. '
f'Your domain must end with {namespace}')
def _execute_nsupdate(self, content):
subprocess.run(['nsupdate'], input=content, encoding='utf-8')
if __name__ == '__main__':
bottle.run(app)
import bobo
from tempfile import NamedTemporaryFile
from subprocess import call
from base64 import b64decode
ZONE = "hack-hro.de"
BASEDOMAIN = "people"
@bobo.subroute('/update', scan=True)
class Dynschiss:
def __init__(self, request):
self.request = request
authstring = self.request.headers['Authorization'].split()[1]
self.user = b64decode(authstring).decode('utf-8').split(sep=':')[0]
if "ip" and "domain" in self.request.params:
self.ip = self.request.params['ip']
self.domain = self.request.params['domain']
self.status = "sucess"
else:
self.status = "fail"
@bobo.post('/')
def base(self):
updater = DynDnsUpdater(ZONE, self.user + "." + BASEDOMAIN)
updater.update(self.domain, self.ip)
return self.status
class DynDnsUpdater:
template = """server %s %d
zone %s.
update delete %s A
update add %s %d A %s
send"""
def __init__(self, zone, baseDomain, dnsIP="127.0.0.1", dnsPort=53, ttl=60):
self.zone = zone
self.baseDomain = baseDomain
self.dnsIP = dnsIP
self.dnsPort = dnsPort
self.ttl = ttl
def update(self, domain, ip):
self._validate(domain)
template = self._renderUpdateTemplate(domain, ip)
self._writeUpdate(template)
return self
def _renderUpdateTemplate(self, domain, ip):
return self.template % (self.dnsIP, self.dnsPort, self.zone,
domain, domain, self.ttl, ip)
def _validate(self, domain):
namespace = "%s.%s" % ("." + self.baseDomain if
self.baseDomain else "", self.zone)
if(not domain.endswith(namespace)):
raise ValueError("domain is not in predefined namespace. your\
domain must end with %s" % (namespace))
def _writeUpdate(self, content):
tmp = NamedTemporaryFile()
tmp.file.write(bytes(content, 'UTF-8'))
tmp.file.close()
call(["nsupdate", tmp.name])
if __name__ == '__main__':
app = Dynschiss()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment