diff --git a/powerdnsadmin/models/record.py b/powerdnsadmin/models/record.py index d852b8f..ecea629 100644 --- a/powerdnsadmin/models/record.py +++ b/powerdnsadmin/models/record.py @@ -1,5 +1,5 @@ +import re import traceback -import itertools import dns.reversename import dns.inet import dns.name @@ -113,7 +113,14 @@ class Record(object): def add(self, domain_name, rrset): """ - Add a record to a domain (a reverse domain name) + Add a record to a domain (Used by auto_ptr and DynDNS) + + Args: + domain_name(str): The zone name + rrset(dict): The record in PDNS rrset format + + Returns: + (dict): A dict contains status code and message """ # Validate record first rrsets = self.get_rrsets(domain_name) @@ -131,46 +138,6 @@ class Record(object): headers = {} headers['X-API-Key'] = self.PDNS_API_KEY - # if self.NEW_SCHEMA: - # data = { - # "rrsets": [{ - # "name": - # self.name.rstrip('.') + '.', - # "type": - # self.type, - # "changetype": - # "REPLACE", - # "ttl": - # self.ttl, - # "records": [{ - # "content": self.data, - # "disabled": self.status, - # }], - # "comments": - # [self.comment_data] if self.comment_data else [] - # }] - # } - # else: - # data = { - # "rrsets": [{ - # "name": - # self.name, - # "type": - # self.type, - # "changetype": - # "REPLACE", - # "records": [{ - # "content": self.data, - # "disabled": self.status, - # "name": self.name, - # "ttl": self.ttl, - # "type": self.type - # }], - # "comments": - # [self.comment_data] if self.comment_data else [] - # }] - # } - try: jdata = utils.fetch_json(urljoin( self.PDNS_STATS_URL, self.API_EXTENDED_URL + @@ -184,8 +151,10 @@ class Record(object): return {'status': 'ok', 'msg': 'Record was added successfully'} except Exception as e: current_app.logger.error( - "Cannot add record {0}/{1}/{2} to domain {3}. DETAIL: {4}". - format(self.name, self.type, self.data, domain_name, e)) + "Cannot add record to domain {}. Error: {}".format( + domain_name, e)) + current_app.logger.debug("Submitted record rrset: \n{}".format( + utils.pretty_json(rrset))) return { 'status': 'error', 'msg': @@ -226,11 +195,26 @@ class Record(object): rrsets = [] for record in submitted_records: # Format the record name - record_name = "{}.{}.".format( - record["record_name"], - domain_name) if record["record_name"] not in [ - '@', '' - ] else domain_name + '.' + # + # If it is ipv6 reverse zone and PRETTY_IPV6_PTR is enabled, + # We convert ipv6 address back to reverse record format + # before submitting to PDNS API. + if self.PRETTY_IPV6_PTR and re.search(r'ip6\.arpa', domain_name): + if record['record_type'] == 'PTR' and ':' in record[ + 'record_name']: + record_name = dns.reversename.from_address( + record['record_name']).to_text() + + # Else, it is forward zone, then record name should be + # in format "..". If it is root + # domain name (name == '@' or ''), the name should + # be in format "." + else: + record_name = "{}.{}.".format( + record["record_name"], + domain_name) if record["record_name"] not in [ + '@', '' + ] else domain_name + '.' # Format the record content, it musts end # with a dot character if in following types @@ -249,7 +233,7 @@ class Record(object): record_comments = [{ "content": record["record_comment"], "account": "" - }] if record["record_comment"] else [] + }] if record.get("record_comment") else [] # Add the formatted record to rrsets list rrsets.append({ @@ -267,7 +251,7 @@ class Record(object): # Sort the list before using groupby rrsets = sorted(rrsets, key=lambda r: (r['name'], r['type'])) groups = groupby(rrsets, key=lambda r: (r['name'], r['type'])) - for k, v in groups: + for _k, v in groups: group = list(v) transformed_rrsets.append(self.merge_rrsets(group)) @@ -336,38 +320,6 @@ class Record(object): # Get the list of rrsets to be added and deleted new_rrsets, del_rrsets = self.compare(domain_name, submitted_records) - # records = [] - # for r in deleted_records: - # r_name = r['name'].rstrip( - # '.') + '.' if self.NEW_SCHEMA else r['name'] - # r_type = r['type'] - # if self.PRETTY_IPV6_PTR: # only if activated - # if self.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, - # "type": r_type, - # "changetype": "DELETE", - # "records": [] - # } - # records.append(record) - - # postdata_for_delete = {"rrsets": records} - - # records = [] - # for r in new_records: - # if self.NEW_SCHEMA: - # r_name = r['name'].rstrip('.') + '.' - # r_type = r['type'] - # if self.PRETTY_IPV6_PTR: # only if activated - # if r_type == 'PTR': # only ptr - # if ':' in r['name']: # dirty ipv6 check - # r_name = r['name'] - # Submit the changes to PDNS API try: headers = {} diff --git a/powerdnsadmin/routes/admin.py b/powerdnsadmin/routes/admin.py index 7e31c28..f4ff840 100644 --- a/powerdnsadmin/routes/admin.py +++ b/powerdnsadmin/routes/admin.py @@ -3,7 +3,7 @@ import json import traceback from ast import literal_eval from distutils.version import StrictVersion -from flask import Blueprint, render_template, make_response, url_for, current_app, request, redirect, jsonify, abort +from flask import Blueprint, render_template, make_response, url_for, current_app, request, redirect, jsonify, abort, flash from flask_login import login_required, current_user from ..decorators import operator_role_required, admin_role_required @@ -843,7 +843,7 @@ def create_template_from_zone(): for jr in jrecords: if jr['type'] in Setting().get_records_allow_to_edit(): name = '@' if jr['name'] == domain_name else re.sub( - '\.{}$'.format(domain_name), '', jr['name']) + r'\.{}$'.format(domain_name), '', jr['name']) for subrecord in jr['records']: record = DomainTemplateRecord( name=name, @@ -858,7 +858,7 @@ def create_template_from_zone(): for jr in jrecords: if jr['type'] in Setting().get_records_allow_to_edit(): name = '@' if jr['name'] == domain_name else re.sub( - '\.{}$'.format(domain_name), '', jr['name']) + r'\.{}$'.format(domain_name), '', jr['name']) record = DomainTemplateRecord( name=name, type=jr['type'], diff --git a/powerdnsadmin/routes/domain.py b/powerdnsadmin/routes/domain.py index 093f818..c3c5880 100644 --- a/powerdnsadmin/routes/domain.py +++ b/powerdnsadmin/routes/domain.py @@ -1,6 +1,8 @@ import re import json import traceback +import dns.name +import dns.reversename from distutils.version import StrictVersion from flask import Blueprint, render_template, make_response, url_for, current_app, request, redirect, abort, jsonify from flask_login import login_required, current_user @@ -63,15 +65,27 @@ def domain(domain_name): if StrictVersion(Setting().get('pdns_version')) >= StrictVersion('4.0.0'): for r in rrsets: if r['type'] in records_allow_to_edit: + r_name = r['name'].rstrip('.') + + # If it is reverse zone and pretty_ipv6_ptr setting + # is enabled, we reformat the name for ipv6 records. + if Setting().get('pretty_ipv6_ptr') and r[ + 'type'] == 'PTR' and 'ip6.arpa' in r_name: + r_name = dns.reversename.to_address( + dns.name.from_text(r_name)) + + # Create the list of records in format that + # PDA jinja2 template can understand. index = 0 for record in r['records']: record_entry = RecordEntry( - name=r['name'].rstrip('.'), + name=r_name, type=r['type'], status='Disabled' if record['disabled'] else 'Active', ttl=r['ttl'], data=record['content'], - comment=r['comments'][index]['content'] if r['comments'] else '', + comment=r['comments'][index]['content'] + if r['comments'] else '', is_allowed_edit=True) index += 1 records.append(record_entry) @@ -79,7 +93,7 @@ def domain(domain_name): # Unsupported version abort(500) - if not re.search('ip6\.arpa|in-addr\.arpa$', domain_name): + if not re.search(r'ip6\.arpa|in-addr\.arpa$', domain_name): editable_records = forward_records_allow_to_edit else: editable_records = reverse_records_allow_to_edit diff --git a/powerdnsadmin/routes/index.py b/powerdnsadmin/routes/index.py index e244933..81008ee 100644 --- a/powerdnsadmin/routes/index.py +++ b/powerdnsadmin/routes/index.py @@ -1,4 +1,5 @@ import os +import re import json import traceback import datetime @@ -423,7 +424,7 @@ def dyndns_update(): domain = None domain_segments = hostname.split('.') - for index in range(len(domain_segments)): + for _index in range(len(domain_segments)): full_domain = '.'.join(domain_segments) potential_domain = Domain.query.filter( Domain.name == full_domain).first() @@ -489,12 +490,23 @@ def dyndns_update(): DomainSetting.setting == 'create_via_dyndns').first() if (ondemand_creation is not None) and (strtobool( ondemand_creation.value) == True): - record = Record(name=hostname, - type=rtype, - data=str(ip), - status=False, - ttl=3600) - result = record.add(domain.name) + + # Build the rrset + rrset_data = [{ + "changetype": "REPLACE", + "name": hostname + '.', + "ttl": 3600, + "type": rtype, + "records": [{ + "content": str(ip), + "disabled": False + }], + "comments": [] + }] + + # Format the rrset + rrset = {"rrsets": rrset_data} + result = Record().add(domain.name, rrset) if result['status'] == 'ok': history = History( msg= @@ -679,7 +691,7 @@ def handle_account(account_name): clean_name = ''.join(c for c in account_name.lower() if c in "abcdefghijklmnopqrstuvwxyz0123456789") if len(clean_name) > Account.name.type.length: - logging.error( + current_app.logger.error( "Account name {0} too long. Truncated.".format(clean_name)) account = Account.query.filter_by(name=clean_name).first() if not account: