diff options
author | Marius Halden <marius.h@lden.org> | 2014-03-17 04:16:49 +0100 |
---|---|---|
committer | Marius Halden <marius.h@lden.org> | 2014-03-17 04:16:49 +0100 |
commit | 7c575aaa8e98a6aa7eda8d69e2b14d014ee91b09 (patch) | |
tree | 7bd082fd216e1577440cf1ea599467993c2fef36 /ddns | |
download | DDNS.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__.py | 16 | ||||
-rw-r--r-- | ddns/auth.py | 27 | ||||
-rw-r--r-- | ddns/backend/__init__.py | 0 | ||||
-rw-r--r-- | ddns/backend/dnsupdate.py | 74 | ||||
-rw-r--r-- | ddns/cfg_parser.py | 17 | ||||
-rw-r--r-- | ddns/frontend/__init__.py | 0 | ||||
-rw-r--r-- | ddns/frontend/dyn_com.py | 47 | ||||
-rw-r--r-- | ddns/hash.py | 23 | ||||
-rwxr-xr-x | ddns/main.py | 61 |
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) |