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']
BASE_DOMAIN = '.' + os.environ['DYNSCHISS_BASE']
except KeyError:
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)
domain = request.forms['domain']
except KeyError:
return abort(400, 'domain missing')
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}
def __init__(self, zone, base_domain, dns_host='', dns_port=53, ttl=60): = zone
self.base_domain = base_domain
self.dns_host = dns_host
self.dns_port = dns_port
self.ttl = ttl
def update(self, domain, ip):
fqdn = f'{domain}{}.'
current_ip = socket.gethostbyname(fqdn)
if current_ip != ip:'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,, domain=fqdn, ttl=self.ttl, ip=ip)
return self
def _validate(self, domain):
namespace = f'.{self.base_domain}.' if self.base_domain else '.' +
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):['nsupdate'], input=content, encoding='utf-8')
if __name__ == '__main__':
import bobo
from tempfile import NamedTemporaryFile
from subprocess import call
from base64 import b64decode
ZONE = ""
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"
self.status = "fail"'/')
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
def __init__(self, zone, baseDomain, dnsIP="", dnsPort=53, ttl=60): = zone
self.baseDomain = baseDomain
self.dnsIP = dnsIP
self.dnsPort = dnsPort
self.ttl = ttl
def update(self, domain, ip):
template = self._renderUpdateTemplate(domain, ip)
return self
def _renderUpdateTemplate(self, domain, ip):
return self.template % (self.dnsIP, self.dnsPort,,
domain, domain, self.ttl, ip)
def _validate(self, domain):
namespace = "%s.%s" % ("." + self.baseDomain if
self.baseDomain else "",
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'))
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