From 981c38cacb3d6b9e6e9c45bbd5cda9eddc2aa55c Mon Sep 17 00:00:00 2001 From: Khanh Ngo Date: Mon, 20 Jun 2016 16:32:14 +0700 Subject: [PATCH] Add dyndns feature --- app/models.py | 68 ++++++++++++++++++++++++++++++++ app/templates/dyndns.html | 1 + app/views.py | 81 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+) create mode 100644 app/templates/dyndns.html diff --git a/app/models.py b/app/models.py index f0944fd..e5fb130 100644 --- a/app/models.py +++ b/app/models.py @@ -945,6 +945,74 @@ class Record(object): """ return self.type in app.config['RECORDS_ALLOW_EDIT'] + def exists(self, domain): + """ + Check if record is present within domain records, and if it's present set self to found record + """ + jdata = self.get_record_data(domain) + jrecords = jdata['records'] + + for jr in jrecords: + if jr['name'] == self.name: + self.name = jr['name'] + self.type = jr['type'] + self.status = jr['disabled'] + self.ttl = jr['ttl'] + self.data = jr['content'] + self.priority = 10 + return True + return False + + def update(self, domain, content): + """ + Update single record + """ + headers = {} + headers['X-API-Key'] = PDNS_API_KEY + + if NEW_SCHEMA: + data = {"rrsets": [ + { + "name": self.name + '.', + "type": self.type, + "ttl": self.ttl, + "changetype": "REPLACE", + "records": [ + { + "content": content, + "disabled": self.status, + } + ] + } + ] + } + else: + data = {"rrsets": [ + { + "name": self.name, + "type": self.type, + "changetype": "REPLACE", + "records": [ + { + "content": content, + "disabled": self.status, + "name": self.name, + "ttl": self.ttl, + "type": self.type, + "priority": 10 + } + ] + } + ] + } + try: + jdata = utils.fetch_json(urlparse.urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers, method='PATCH', data=data) + logging.debug("dyndns data: " % data) + return {'status': 'ok', 'msg': 'Record was updated successfully'} + except Exception, e: + logging.error("Cannot add record %s/%s/%s to domain %s. DETAIL: %s" % (self.name, self.type, self.data, domain, str(e))) + return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'} + class Server(object): """ diff --git a/app/templates/dyndns.html b/app/templates/dyndns.html new file mode 100644 index 0000000..e7dff2c --- /dev/null +++ b/app/templates/dyndns.html @@ -0,0 +1 @@ +{{ response }} \ No newline at end of file diff --git a/app/views.py b/app/views.py index 209db3a..7eddc40 100644 --- a/app/views.py +++ b/app/views.py @@ -3,6 +3,7 @@ import json import jinja2 import traceback import pyqrcode +import base64 from functools import wraps from flask.ext.login import login_user, logout_user, current_user, login_required @@ -54,6 +55,37 @@ def load_user(id): """ return User.query.get(int(id)) +def dyndns_login_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if current_user.is_authenticated is False: + return render_template('dyndns.html', response='badauth'), 200 + return f(*args, **kwargs) + return decorated_function + +@login_manager.request_loader +def login_via_authorization_header(request): + auth_header = request.headers.get('Authorization') + if auth_header: + auth_header = auth_header.replace('Basic ', '', 1) + try: + auth_header = base64.b64decode(auth_header) + username,password = auth_header.split(":") + except TypeError, e: + error = e.message['desc'] if 'desc' in e.message else e + return None + user = User(username=username, password=password, plain_text_password=password) + try: + auth = user.is_validate(method='LOCAL') + if auth == False: + return None + else: + login_user(user, remember = False) + return user + except Exception, e: + return None + return None + # END USER AUTHENTICATION HANDLER # START CUSTOMIZE DECORATOR @@ -607,6 +639,55 @@ def qrcode(): 'Expires': '0'} +@app.route('/nic/checkip.html', methods=['GET', 'POST']) +def dyndns_checkip(): + # route covers the default ddclient 'web' setting for the checkip service + return render_template('dyndns.html', response=request.headers.get('X-Real-IP')) + +@app.route('/nic/update', methods=['GET', 'POST']) +@dyndns_login_required +def dyndns_update(): + # dyndns protocol response codes in use are: + # good: update successful + # nochg: IP address already set to update address + # nohost: hostname does not exist for this user account + # 911: server error + # have to use 200 HTTP return codes because ddclient does not read the return string if the code is other than 200 + # reference: https://help.dyn.com/remote-access-api/perform-update/ + # reference: https://help.dyn.com/remote-access-api/return-codes/ + hostname = request.args.get('hostname') + myip = request.args.get('myip') + + try: + # get all domains owned by the current user + domains = User(id=current_user.id).get_domain() + except: + return render_template('dyndns.html', response='911'), 200 + for domain in domains: + # create new record object to use for searching and updating + r = Record() + r.name = hostname + # check if the user requested record exists within this domain + if r.exists(domain.name) and r.is_allowed: + if r.data == myip: + # record content did not change, return 'nochg' + history = History(msg="DynDNS update: attempted update of %s but record did not change" % hostname, created_by=current_user.username) + history.add() + return render_template('dyndns.html', response='nochg'), 200 + else: + oldip = r.data + result = r.update(domain.name, myip) + if result['status'] == 'ok': + history = History(msg='DynDNS update: updated record %s in zone %s, it changed from %s to %s' % (hostname,domain.name,oldip,myip), detail=str(result), created_by=current_user.username) + history.add() + return render_template('dyndns.html', response='good'), 200 + else: + return render_template('dyndns.html', response='911'), 200 + history = History(msg="DynDNS update: attempted update of %s but it does not exist for this user" % hostname, created_by=current_user.username) + history.add() + return render_template('dyndns.html', response='nohost'), 200 + + @app.route('/', methods=['GET', 'POST']) @login_required def index():