From 7cda4841b7591d9b32ffbda00c32d7b726aa93f4 Mon Sep 17 00:00:00 2001 From: Jan Cholasta Date: Mon, 29 Aug 2016 12:34:25 +0200 Subject: [PATCH 1/2] dns: prompt for missing record parts in CLI Fix the code which determines if a record part is required and thus should be prompted not to wrongfully consider all record parts to be optional. https://fedorahosted.org/freeipa/ticket/6203 --- ipaclient/plugins/dns.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ipaclient/plugins/dns.py b/ipaclient/plugins/dns.py index e17c282..18903cd 100644 --- a/ipaclient/plugins/dns.py +++ b/ipaclient/plugins/dns.py @@ -45,11 +45,21 @@ _rev_top_record_types = ('PTR', ) _zone_top_record_types = ('NS', 'MX', 'LOC', ) +_optional_part_params = { + 'loc_part_lat_min', + 'loc_part_lat_sec', + 'loc_part_lon_min', + 'loc_part_lon_sec', + 'loc_part_size', + 'loc_part_h_precision', + 'loc_part_v_precision', +} + def __get_part_param(cmd, part, output_kw, default=None): name = part.name label = unicode(part.label) - optional = not part.required + optional = name in _optional_part_params output_kw[name] = cmd.prompt_param(part, optional=optional, @@ -91,7 +101,7 @@ def prompt_missing_parts(rrtype, cmd, kw, prompt_optional=False): if name in kw: continue - optional = not part.required + optional = name in _optional_part_params if optional and not prompt_optional: continue From 672d35470ed7e87d707726f52626a8bcc0b4256e Mon Sep 17 00:00:00 2001 From: Jan Cholasta Date: Tue, 23 Aug 2016 12:53:39 +0200 Subject: [PATCH 2/2] dns: fix crash in interactive mode against old servers Add a client-side fallback of the dnsrecord_split_parts command for old servers to avoid CommandError in dnsrecord_add and dnsrecord_mod CLI interactive mode. https://fedorahosted.org/freeipa/ticket/6203 --- ipaclient/plugins/dns.py | 6 ++++- ipalib/dns.py | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ ipaserver/plugins/dns.py | 62 +++++---------------------------------------- 3 files changed, 76 insertions(+), 57 deletions(-) diff --git a/ipaclient/plugins/dns.py b/ipaclient/plugins/dns.py index 18903cd..23601c2 100644 --- a/ipaclient/plugins/dns.py +++ b/ipaclient/plugins/dns.py @@ -25,7 +25,8 @@ from ipaclient.frontend import MethodOverride from ipalib import errors -from ipalib.dns import (get_part_rrtype, +from ipalib.dns import (dnsrecord_split_parts, + get_part_rrtype, get_record_rrtype, has_cli_options, iterate_rrparams_by_parts, @@ -129,6 +130,9 @@ class dnszone_mod(DNSZoneMethodOverride): pass +register(no_fail=True)(dnsrecord_split_parts) + + @register(override=True, no_fail=True) class dnsrecord_add(MethodOverride): no_option_msg = 'No options to add a specific record provided.\n' \ diff --git a/ipalib/dns.py b/ipalib/dns.py index 95c7989..d2e5afc 100644 --- a/ipalib/dns.py +++ b/ipalib/dns.py @@ -23,6 +23,8 @@ import re from ipalib import errors +from ipalib.frontend import Command +from ipalib.parameters import Str # dnsrecord param name formats record_name_format = '%srecord' @@ -118,3 +120,66 @@ def iterate_rrparams_by_parts(cmd, kw, skip_extra=False): if rrparam.name not in processed: processed.append(rrparam.name) yield rrparam + + +def split_rrparam(name, value): + def split_exactly(count): + values = value.split() + if len(values) != count: + return None + return tuple(values) + + rrtype = get_record_rrtype(name) + if rrtype in ('A', 'AAAA', 'CNAME', 'DNAME', 'NS', 'PTR'): + return split_exactly(1) + elif rrtype in ('AFSDB', 'KX', 'MX'): + return split_exactly(2) + elif rrtype in ('CERT', 'DLV', 'DS', 'SRV', 'TLSA'): + return split_exactly(4) + elif rrtype in ('NAPTR'): + return split_exactly(6) + elif rrtype in ('A6', 'TXT'): + return (value,) + elif rrtype == 'LOC': + regex = re.compile( + r'(?P\d{1,2}\s+)' + r'(?:(?P\d{1,2}\s+)' + r'(?P\d{1,2}(?:\.\d{1,3})?\s+)?)?' + r'(?P[NS])\s+' + r'(?P\d{1,3}\s+)' + r'(?:(?P\d{1,2}\s+)' + r'(?P\d{1,2}(?:\.\d{1,3})?\s+)?)?' + r'(?P[WE])\s+' + r'(?P-?\d{1,8}(?:\.\d{1,2})?)m?' + r'(?:\s+(?P\d{1,8}(?:\.\d{1,2})?)m?' + r'(?:\s+(?P\d{1,8}(?:\.\d{1,2})?)m?' + r'(?:\s+(?P\d{1,8}(?:\.\d{1,2})?)m?\s*)?)?)?$') + + m = regex.match(value) + + if m is None: + return None + + return tuple(x.strip() if x is not None else x for x in m.groups()) + elif rrtype == 'SSHFP': + # fingerprint part can contain space in LDAP, return it as one part + values = value.split(None, 2) + if len(values) != 3: + return None + + return tuple(values) + else: + return tuple() + + +class dnsrecord_split_parts(Command): + NO_CLI = True + + takes_args = ( + Str('name'), + Str('value'), + ) + + def execute(self, name, value, *args, **options): + result = split_rrparam(name, value) + return dict(result=result) diff --git a/ipaserver/plugins/dns.py b/ipaserver/plugins/dns.py index 6f1bd71..22d429f 100644 --- a/ipaserver/plugins/dns.py +++ b/ipaserver/plugins/dns.py @@ -32,7 +32,8 @@ import dns.resolver import six -from ipalib.dns import (extra_name_format, +from ipalib.dns import (dnsrecord_split_parts, + extra_name_format, get_extra_rrtype, get_part_rrtype, get_record_rrtype, @@ -40,7 +41,8 @@ has_cli_options, iterate_rrparams_by_parts, part_name_format, - record_name_format) + record_name_format, + split_rrparam) from ipalib.frontend import Method, Object from ipalib.request import context from ipalib import api, errors, output @@ -692,10 +694,7 @@ def __init__(self, name=None, *rules, **kw): super(DNSRecord, self).__init__(name, *rules, **kw) def _get_part_values(self, value): - values = value.split() - if len(values) != len(self.parts): - return None - return tuple(values) + return split_rrparam(self.name, value) def _part_values_to_string(self, values, idna=True): self._validate_parts(values) @@ -920,9 +919,6 @@ class UnsupportedDNSRecord(DNSRecord): """ supported = False - def _get_part_values(self, value): - return tuple() - class ARecord(ForwardRecord): rrtype = 'A' @@ -943,9 +939,6 @@ class A6Record(DNSRecord): ), ) - def _get_part_values(self, value): - # A6 RR type is obsolete and only a raw interface is provided - return (value,) class AAAARecord(ForwardRecord): rrtype = 'AAAA' @@ -1171,28 +1164,6 @@ class LOCRecord(DNSRecord): siz, hp, vp: [0 .. 90000000.00] (size/precision in meters) See RFC 1876 for details""") - def _get_part_values(self, value): - regex = re.compile( - r'(?P\d{1,2}\s+)' - r'(?:(?P\d{1,2}\s+)' - r'(?P\d{1,2}(?:\.\d{1,3})?\s+)?)?' - r'(?P[NS])\s+' - r'(?P\d{1,3}\s+)' - r'(?:(?P\d{1,2}\s+)' - r'(?P\d{1,2}(?:\.\d{1,3})?\s+)?)?' - r'(?P[WE])\s+' - r'(?P-?\d{1,8}(?:\.\d{1,2})?)m?' - r'(?:\s+(?P\d{1,8}(?:\.\d{1,2})?)m?' - r'(?:\s+(?P\d{1,8}(?:\.\d{1,2})?)m?' - r'(?:\s+(?P\d{1,8}(?:\.\d{1,2})?)m?\s*)?)?)?$') - - m = regex.match(value) - - if m is None: - return None - - return tuple(x.strip() if x is not None else x for x in m.groups()) - def _validate_parts(self, parts): super(LOCRecord, self)._validate_parts(parts) @@ -1389,13 +1360,6 @@ class SSHFPRecord(DNSRecord): ), ) - def _get_part_values(self, value): - # fingerprint part can contain space in LDAP, return it as one part - values = value.split(None, 2) - if len(values) != len(self.parts): - return None - return tuple(values) - class TLSARecord(DNSRecord): rrtype = 'TLSA' @@ -1431,9 +1395,6 @@ class TXTRecord(DNSRecord): ), ) - def _get_part_values(self, value): - # ignore any space in TXT record - return (value,) _dns_records = ( ARecord(), @@ -3471,18 +3432,7 @@ def warning_suspicious_relative_name(self, result, *keys, **options): ) -@register() -class dnsrecord_split_parts(Command): - NO_CLI = True - - takes_args = ( - Str('name'), - Str('value'), - ) - - def execute(self, name, value, *args, **options): - result = self.api.Object.dnsrecord.params[name]._get_part_values(value) - return dict(result=result) +register()(dnsrecord_split_parts) @register()