From 671a319e9333f15921a8a25a7024cc76460614e4 Mon Sep 17 00:00:00 2001 From: Joachim Tingvold Date: Fri, 19 Aug 2016 23:04:20 +0000 Subject: [PATCH 1/4] Pretty IPv6 PTR. Use the actual IPv6 address when editing PTR. Rather than dealing with ip6.arpa-dotted-strings from hell, you can now edit IPv6 PTR-records using the IPv6 address. --- app/models.py | 72 ++++++++++++++++++++++++++++++++++++++++------ app/views.py | 18 ++---------- config_template.py | 3 ++ 3 files changed, 69 insertions(+), 24 deletions(-) diff --git a/app/models.py b/app/models.py index 50144a7..ad9464f 100644 --- a/app/models.py +++ b/app/models.py @@ -7,6 +7,9 @@ import urlparse import itertools import traceback import onetimepass +import dns.inet +import dns.name +import dns.reversename from datetime import datetime from distutils.version import StrictVersion @@ -32,6 +35,7 @@ PDNS_STATS_URL = app.config['PDNS_STATS_URL'] PDNS_API_KEY = app.config['PDNS_API_KEY'] PDNS_VERSION = app.config['PDNS_VERSION'] API_EXTENDED_URL = utils.pdns_api_extended_uri(PDNS_VERSION) +PRETTY_IPV6_PTR = app.config['PRETTY_IPV6_PTR'] # Flag for pdns v4.x.x # TODO: Find another way to do this @@ -741,7 +745,14 @@ class Record(object): if NEW_SCHEMA: rrsets = jdata['rrsets'] for rrset in rrsets: - rrset['name'] = rrset['name'].rstrip('.') + r_name = rrset['name'].rstrip('.') + if PRETTY_IPV6_PTR: # only if activated + if rrset['type'] == 'PTR': # only ptr + if 'ip6.arpa' in r_name: # only if v6-ptr + v6_addr = dns.reversename.to_address(dns.name.from_text(r_name)) + r_name = v6_addr + + rrset['name'] = r_name rrset['content'] = rrset['records'][0]['content'] rrset['disabled'] = rrset['records'][0]['disabled'] return {'records': rrsets} @@ -837,13 +848,40 @@ class Record(object): """ Apply record changes to domain """ - deleted_records, new_records = self.compare(domain, post_records) + records = [] + for r in post_records: + r_name = domain if r['record_name'] in ['@', ''] else r['record_name'] + '.' + domain + r_type = r['record_type'] + if PRETTY_IPV6_PTR: # only if activated + if NEW_SCHEMA: # only if new schema + if r_type == 'PTR': # only ptr + if ':' in r['record_name']: # dirty ipv6 check + r_name = r['record_name'] + + record = { + "name": r_name, + "type": r_type, + "content": r['record_data'], + "disabled": True if r['record_status'] == 'Disabled' else False, + "ttl": int(r['record_ttl']) if r['record_ttl'] else 3600, + } + records.append(record) + + deleted_records, new_records = self.compare(domain, records) records = [] for r in deleted_records: + r_name = r['name'] + '.' if NEW_SCHEMA else r['name'] + r_type = r['type'] + if PRETTY_IPV6_PTR: # only if activated + if NEW_SCHEMA: # only if new schema + if r_type == 'PTR': # only ptr + if ':' in r['name']: # dirty ipv6 check + r_name = dns.reversename.from_address(r['name']).to_text() + record = { - "name": r['name'] + '.' if NEW_SCHEMA else r['name'], - "type": r['type'], + "name": r_name, + "type": r_type, "changetype": "DELETE", "records": [ ] @@ -855,9 +893,16 @@ class Record(object): records = [] for r in new_records: if NEW_SCHEMA: + r_name = r['name'] + '.' + r_type = r['type'] + if PRETTY_IPV6_PTR: # only if activated + if r_type == 'PTR': # only ptr + if ':' in r['name']: # dirty ipv6 check + r_name = r['name'] + record = { - "name": r['name'] + '.', - "type": r['type'], + "name": r_name, + "type": r_type, "changetype": "REPLACE", "ttl": r['ttl'], "records": [ @@ -891,10 +936,19 @@ class Record(object): records = sorted(records, key = lambda item: (item["name"], item["type"], item["changetype"])) for key, group in itertools.groupby(records, lambda item: (item["name"], item["type"], item["changetype"])): if NEW_SCHEMA: + r_name = key[0] + r_type = key[1] + r_changetype = key[2] + + if PRETTY_IPV6_PTR: # only if activated + if r_type == 'PTR': # only ptr + if ':' in r_name: # dirty ipv6 check + r_name = dns.reversename.from_address(r_name).to_text() + new_record = { - "name": key[0], - "type": key[1], - "changetype": key[2], + "name": r_name, + "type": r_type, + "changetype": r_changetype, "ttl": None, "records": [] } diff --git a/app/views.py b/app/views.py index a33e6e1..01fa4ec 100644 --- a/app/views.py +++ b/app/views.py @@ -311,6 +311,7 @@ def domain(domain_name): if jr['type'] in app.config['RECORDS_ALLOW_EDIT']: record = Record(name=jr['name'], type=jr['type'], status='Disabled' if jr['disabled'] else 'Active', ttl=jr['ttl'], data=jr['content']) records.append(record) + return render_template('domain.html', domain=domain, records=records, editable_records=app.config['RECORDS_ALLOW_EDIT']) else: return redirect(url_for('error', code=404)) @@ -408,24 +409,11 @@ def record_apply(domain_name): try: pdata = request.data jdata = json.loads(pdata) - records = [] - - for j in jdata: - record = { - "name": domain_name if j['record_name'] in ['@', ''] else j['record_name'] + '.' + domain_name, - "type": j['record_type'], - "content": j['record_data'], - "disabled": True if j['record_status'] == 'Disabled' else False, - "name": domain_name if j['record_name'] in ['@', ''] else j['record_name'] + '.' + domain_name, - "ttl": int(j['record_ttl']) if j['record_ttl'] else 3600, - "type": j['record_type'], - } - records.append(record) r = Record() - result = r.apply(domain_name, records) + result = r.apply(domain_name, jdata) if result['status'] == 'ok': - history = History(msg='Apply record changes to domain %s' % domain_name, detail=str(records), created_by=current_user.username) + history = History(msg='Apply record changes to domain %s' % domain_name, detail=str(jdata), created_by=current_user.username) history.add() return make_response(jsonify( result ), 200) else: diff --git a/config_template.py b/config_template.py index 65b9960..2a55431 100644 --- a/config_template.py +++ b/config_template.py @@ -76,3 +76,6 @@ PDNS_VERSION = '3.4.7' # RECORDS ALLOWED TO EDIT RECORDS_ALLOW_EDIT = ['A', 'AAAA', 'CNAME', 'SPF', 'PTR', 'MX', 'TXT'] + +# EXPERIMENTAL FEATURES +PRETTY_IPV6_PTR = False From 8908c7d69b6b7e0198ba6b8b325014c7dc52a044 Mon Sep 17 00:00:00 2001 From: Joachim Tingvold Date: Fri, 19 Aug 2016 23:07:36 +0000 Subject: [PATCH 2/4] Cosmetics. --- app/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models.py b/app/models.py index ad9464f..2ad6112 100644 --- a/app/models.py +++ b/app/models.py @@ -749,8 +749,7 @@ class Record(object): if PRETTY_IPV6_PTR: # only if activated if rrset['type'] == 'PTR': # only ptr if 'ip6.arpa' in r_name: # only if v6-ptr - v6_addr = dns.reversename.to_address(dns.name.from_text(r_name)) - r_name = v6_addr + r_name = dns.reversename.to_address(dns.name.from_text(r_name)) rrset['name'] = r_name rrset['content'] = rrset['records'][0]['content'] From 9ec1ac3e46e8bea266e4770ee1c5a93bcce856ae Mon Sep 17 00:00:00 2001 From: Joachim Tingvold Date: Fri, 19 Aug 2016 23:25:53 +0000 Subject: [PATCH 3/4] Update requirements. --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 787535d..0d00756 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ sqlalchemy-migrate==0.10.0 onetimepass==1.0.1 PyQRCode==1.2 Flask-OAuthlib==0.9.3 +dnspython>=1.12.0 \ No newline at end of file From 756b2d04b6966047b0b3192e04e215b0c4ff3d16 Mon Sep 17 00:00:00 2001 From: Joachim Tingvold Date: Fri, 19 Aug 2016 23:28:59 +0000 Subject: [PATCH 4/4] Add 'pretty IPv6 PTR' as a feature. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7bd0e52..c09aa7b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ PowerDNS Web-GUI - Built by Flask - User activity logging - Dashboard and pdns service statistics - DynDNS 2 protocol support +- Edit IPv6 PTRs using IPv6 addresses directly (no more editing of literal addresses!) ## Setup