aboutsummaryrefslogtreecommitdiffstats
path: root/ddns
diff options
context:
space:
mode:
authorMarius Halden <marius.h@lden.org>2014-03-17 04:16:49 +0100
committerMarius Halden <marius.h@lden.org>2014-03-17 04:16:49 +0100
commit7c575aaa8e98a6aa7eda8d69e2b14d014ee91b09 (patch)
tree7bd082fd216e1577440cf1ea599467993c2fef36 /ddns
downloadDDNS.py-7c575aaa8e98a6aa7eda8d69e2b14d014ee91b09.tar.gz
DDNS.py-7c575aaa8e98a6aa7eda8d69e2b14d014ee91b09.tar.bz2
DDNS.py-7c575aaa8e98a6aa7eda8d69e2b14d014ee91b09.tar.xz
Initial commit
Diffstat (limited to 'ddns')
-rw-r--r--ddns/__init__.py16
-rw-r--r--ddns/auth.py27
-rw-r--r--ddns/backend/__init__.py0
-rw-r--r--ddns/backend/dnsupdate.py74
-rw-r--r--ddns/cfg_parser.py17
-rw-r--r--ddns/frontend/__init__.py0
-rw-r--r--ddns/frontend/dyn_com.py47
-rw-r--r--ddns/hash.py23
-rwxr-xr-xddns/main.py61
9 files changed, 265 insertions, 0 deletions
diff --git a/ddns/__init__.py b/ddns/__init__.py
new file mode 100644
index 0000000..b0de21e
--- /dev/null
+++ b/ddns/__init__.py
@@ -0,0 +1,16 @@
+from flask import Flask, request, Response
+import ddns.cfg_parser
+
+cfg_parser.cfg_file = '/home/marius/ddns/ddns.cfg'
+cfg_parser.read_config()
+
+app = Flask(__name__)
+
+def index():
+ return "index"
+
+app.add_url_rule('/', 'index', index)
+
+#from ddns.frontend.dyn_com import dyn_com
+import ddns.frontend.dyn_com
+app.add_url_rule('/nic/update', 'ddns.frontend.dyn_com.dyn_com', ddns.frontend.dyn_com.dyn_com)
diff --git a/ddns/auth.py b/ddns/auth.py
new file mode 100644
index 0000000..6624aad
--- /dev/null
+++ b/ddns/auth.py
@@ -0,0 +1,27 @@
+## These functions are modified versions of these: http://flask.pocoo.org/snippets/8/
+from flask import request, Response
+from functools import wraps
+import ddns.cfg_parser
+import hash
+
+auth_cfg = ddns.cfg_parser.cfg['users']
+
+def check_auth(username, password):
+ for user in auth_cfg:
+ if username == user['username'] and \
+ hash.hash(user['hash'], password) == user['password']:
+ return True
+ return False
+
+def authenticate(message='badauth'):
+ return Response(message, 401,
+ {'WWW-Authenticate': 'Basic realm="login required"'})
+
+def require_auth(f):
+ @wraps(f)
+ def decorated(*args, **kwargs):
+ auth = request.authorization
+ if not auth or not check_auth(auth.username, auth.password):
+ return authenticate()
+ return f(*args, **kwargs)
+ return decorated
diff --git a/ddns/backend/__init__.py b/ddns/backend/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ddns/backend/__init__.py
diff --git a/ddns/backend/dnsupdate.py b/ddns/backend/dnsupdate.py
new file mode 100644
index 0000000..cd1dee2
--- /dev/null
+++ b/ddns/backend/dnsupdate.py
@@ -0,0 +1,74 @@
+import dns.query
+import dns.tsig
+import dns.tsigkeyring
+import dns.update
+import dns.resolver
+import ddns.cfg_parser
+
+zone_cfg = None
+keyring = None
+
+def resolve(domain, rtype='A'):
+ return dns.resolver.query(domain, rtype)
+
+def check_ip(domain, ip, rtype='A'):
+ ans = resolve(domain.encode('ascii'), rtype)
+
+ if not ans:
+ return False
+
+ for rdata in ans:
+ if rdata == ip.strNormal(0):
+ return True
+ return False
+
+def get_zone(name):
+ for zone in zone_cfg:
+ if zone['name'] == name:
+ return zone
+ return None
+
+def gen_keyring(dnskeys):
+ global keyring
+
+ keys = {}
+ for key in dnskeys:
+ keys[key['name']] = key['key']
+
+ keyring = dns.tsigkeyring.from_text(keys)
+
+def get_hash_method(hash_name):
+ if hash_name == 'HMAC-MD5':
+ return dns.tsig.HMAC_MD5
+ if hash_name == 'HMAC-SHA1':
+ return dns.tsig.HMAC_SHA1
+ if hash_name == 'HMAC-SHA224':
+ return dns.tsig.HMAC_SHA224
+ if hash_name == 'HMAC-SHA256':
+ return dns.tsig.HMAC_SHA256
+ if hash_name == 'HMAC-SHA384':
+ return dns.tsig.HMAC_384
+ if hash_name == 'HMAC-SHA512':
+ return dns.tsig.HMAC_512
+ return dns.tsig.default_algorithm
+
+def update_dns(zone, hostname, ip, ttl=300):
+ zone = get_zone(zone)
+ dns_srv = zone['ns']
+
+ update = dns.update.Update(zone['name'], keyring=keyring, \
+ keyname=zone['key']['name'], \
+ keyalgorithm=get_hash_method(zone['key']['algorithm']))
+
+ if ip.version() == 6:
+ rtype = 'AAAA'
+ else:
+ rtype = 'A'
+
+# if not check_ip(hostname+'.'+zone['name'], ip, rtype):
+ update.replace(hostname.encode('ascii'), ttl, rtype, ip.strNormal(0))
+ res = dns.query.tcp(update, dns_srv)
+
+zone_cfg = ddns.cfg_parser.cfg['zones']
+keyring = gen_keyring(ddns.cfg_parser.cfg['dnskeys'])
+
diff --git a/ddns/cfg_parser.py b/ddns/cfg_parser.py
new file mode 100644
index 0000000..e8134cd
--- /dev/null
+++ b/ddns/cfg_parser.py
@@ -0,0 +1,17 @@
+import yaml
+import io
+import os
+
+cfg_file = None
+cfg = None
+
+def read_config():
+ global cfg
+
+ if not cfg_file or not os.path.exists(cfg_file):
+ return None
+
+ with io.open(cfg_file, 'r') as fp:
+ _cfg = yaml.load(fp)
+
+ cfg = _cfg
diff --git a/ddns/frontend/__init__.py b/ddns/frontend/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ddns/frontend/__init__.py
diff --git a/ddns/frontend/dyn_com.py b/ddns/frontend/dyn_com.py
new file mode 100644
index 0000000..bbd61e5
--- /dev/null
+++ b/ddns/frontend/dyn_com.py
@@ -0,0 +1,47 @@
+from flask import request, Response
+import ddns.backend.dnsupdate
+from IPy import IP
+import ddns.auth
+import ddns.cfg_parser
+
+@ddns.auth.require_auth
+def dyn_com():
+ if request.method != 'GET':
+ return "badagent"
+
+ if not request.args.has_key('hostname'):
+ return "nohost"
+
+ if len(request.args.getlist('hostname')) > 1:
+ return "numhost"
+
+ if not request.args.has_key('myip'):
+ return "nohost"
+
+ hostname = request.args.get('hostname')
+ if not '.' in hostname:
+ return "notfqdn"
+
+ zone_name = hostname[hostname.find('.')+1:]
+ if zone_name[-1] != '.':
+ zone_name += '.'
+
+ hostname = hostname[0:hostname.find('.')]
+
+ try:
+ ip = IP(request.args.get('myip'))
+ except ValueError:
+ return "nohost"
+
+ for zone in ddns.cfg_parser.cfg['zones']:
+ if zone_name == zone['name']:
+ for domain in zone['domains']:
+ if domain['domain'] == hostname:
+ for user in domain['users']:
+ if request.authorization.username == user['username']:
+ ddns.backend.dnsupdate.update_dns(zone_name, hostname, ip)
+ # We should probably check something here...
+ return "good"
+ return auth.authenticate("!yours")
+ return "nohost"
+ return "nohost"
diff --git a/ddns/hash.py b/ddns/hash.py
new file mode 100644
index 0000000..7bb3b3b
--- /dev/null
+++ b/ddns/hash.py
@@ -0,0 +1,23 @@
+import hashlib
+
+algs = [None, 'sha1', 'sha256', 'sha512']
+
+def hash(algo, passwd):
+ if algo == None: # None
+ return passwd
+ if algo == 'sha1': # sha1
+ return sha1(passwd)
+ if algo == 'sha256': # sha256
+ return sha256(passwd)
+ if algo == 'sha512': # sha512
+ return sha512(passwd)
+ return passwd
+
+def sha1(passwd):
+ return hashlib.sha1(passwd).hexdigest()
+
+def sha256(passwd):
+ return hashlib.sha256(passwd).hexdigest()
+
+def sha512(passwd):
+ return hashlib.sha512(passwd).hexdigest()
diff --git a/ddns/main.py b/ddns/main.py
new file mode 100755
index 0000000..5ee6eaa
--- /dev/null
+++ b/ddns/main.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+
+from flask import Flask, request, Response
+import ddns
+import cfg_parser
+import auth
+from IPy import IP
+
+cfg_file="/srv/http/lden.org/ddns/ddns/ddns.cfg"
+cfg = cfg_parser.read_config(cfg_file)
+
+auth.auth_cfg = cfg['users']
+ddns.zone_cfg = cfg['zones']
+ddns.gen_keyring(cfg['dnskeys'])
+
+app = Flask(__name__)
+
+@app.route("/nic/update")
+@auth.require_auth
+def dyndns():
+ if request.method != 'GET':
+ return "badagent"
+
+ if not request.args.has_key('hostname'):
+ return "nohost"
+
+ if len(request.args.getlist('hostname')) > 1:
+ return "numhost"
+
+ if not request.args.has_key('myip'):
+ return "nohost"
+
+ hostname = request.args.get('hostname')
+ if not '.' in hostname:
+ return "notfqdn"
+
+ zone_name = hostname[hostname.find('.')+1:]
+ if zone_name[-1] != '.':
+ zone_name += '.'
+
+ hostname = hostname[0:hostname.find('.')]
+
+ try:
+ ip = IP(request.args.get('myip'))
+ except ValueError:
+ return "nohost"
+
+ for zone in cfg['zones']:
+ if zone_name == zone['name']:
+ for domain in zone['domains']:
+ if domain['domain'] == hostname:
+ for user in domain['users']:
+ if request.authorization.username == user['username']:
+ ddns.update_dns(zone_name, hostname, ip)
+ return "good"
+ return auth.authenticate("!yours")
+ return "nohost"
+ return "nohost"
+
+if __name__ == "__main__":
+ app.run(host="0.0.0.0", debug=True)