On 11.5.2016 12:08, Martin Basti wrote:
> 
> 
> On 03.05.2016 14:59, Petr Spacek wrote:
>> Hello,
>>
>> DNS upgrade: change forwarding policy to "only" if private IPs are used.
>>
>> https://fedorahosted.org/freeipa/ticket/5710
>>
>> This is the upgrade part. I will add one more patch to print a warning in
>> dnsforwardzone* commands to avoid surprises. Please do not close the ticket
>> yet.
>>
>>
>>
> 
> 1)
> Upgrade failed with 'BindInstance' object has no attribute
> 'named_conf_get_directive'
> IPA server upgrade failed: Inspect /var/log/ipaupgrade.log and run command
> ipa-server-upgrade manually.
> ('IPA upgrade failed.', 1)
> The ipa-server-upgrade command failed. See /var/log/ipaupgrade.log for more
> information
> 
> 2016-05-11T08:26:20Z ERROR Upgrade failed with 'BindInstance' object has no
> attribute 'named_conf_get_directive'
> 2016-05-11T08:26:20Z DEBUG Traceback (most recent call last):
>   File
> "/usr/lib/python2.7/site-packages/ipaserver/install/upgradeinstance.py", line
> 213, in __upgrade
>     self.modified = (ld.update(self.files) or self.modified)
>   File "/usr/lib/python2.7/site-packages/ipaserver/install/ldapupdate.py",
> line 917, in update
>     self._run_updates(all_updates)
>   File "/usr/lib/python2.7/site-packages/ipaserver/install/ldapupdate.py",
> line 889, in _run_updates
>     self._run_update_plugin(update['plugin'])
>   File "/usr/lib/python2.7/site-packages/ipaserver/install/ldapupdate.py",
> line 862, in _run_update_plugin
>     restart_ds, updates = self.api.Updater[plugin_name]()
>   File "/usr/lib/python2.7/site-packages/ipalib/frontend.py", line 1418, in
> __call__
>     return self.execute(**options)
>   File "/usr/lib/python2.7/site-packages/ipaserver/install/plugins/dns.py",
> line 547, in execute
>     self.update_global_named_conf_forwarder(bind)
>   File "/usr/lib/python2.7/site-packages/ipaserver/install/plugins/dns.py",
> line 508, in update_global_named_conf_forwarder
>     if bind.named_conf_get_directive(
> AttributeError: 'BindInstance' object has no attribute 
> 'named_conf_get_directive'
> 
> 2016-05-11T08:26:20Z DEBUG Traceback (most recent call last):
>   File "/usr/lib/python2.7/site-packages/ipaserver/install/service.py", line
> 447, in start_creation
>     run_step(full_msg, method)
>   File "/usr/lib/python2.7/site-packages/ipaserver/install/service.py", line
> 437, in run_step
>     method()
>   File
> "/usr/lib/python2.7/site-packages/ipaserver/install/upgradeinstance.py", line
> 221, in __upgrade
>     raise RuntimeError(e)
> RuntimeError: 'BindInstance' object has no attribute 
> 'named_conf_get_directive'
> 
> PATCH * Add ipaDNSVersion option to dnsconfig* commands and use new attribute 
> *
> 2)
> +        Int('ipadnsversion?',
> +            label=_('IPA DNS version'),
> +        ),
> 
> Shouldn't be this part of System: Read DNS Configuration permission?
> 
> 3)
> -    def postprocess_result(self, result):
> +    def postprocess_result(self, result, show_version):
>          if not any(param in result['result'] for param in self.params):
>              result['summary'] = unicode(_('Global DNS configuration is 
> empty'))
> 
> show_version param was added but I don't see it used in this patch.
> 
> 4)
> +        Int('ipadnsversion?',
> +            label=_('IPA DNS version'),
> +        ),
> 
> Could we add comment here that this option is accessible only from installers
> and upgrade?
> 
> 5)
> +        for config_option in container_entry.get("ipaConfigString", []):
> +            matched = re.match("^DNSVersion\s+(?P<version>\d+)$",
> +                               config_option, flags=re.I)
> +            if matched:
> +                version = int(matched.group("version"))
> 
> Shouldn't we print error if version cannot be parsed?
> 
> PATCH  * DNS upgrade: separate backup logic to make it reusable *
> 
> LGTM
> 
> PATCH * Add function ipapython.dnsutil.related_to_auto_empty_zone() *
> 
> 7)
> I'm curious why do you need to check superdomains?
> 
> PATCH * DNS upgrade: change forwarding policy to = only for conflicting
> forward zones*
> 
> 8)
> +            self.log.debug('Zone %s was sucessfully modified to use '
> +                           'forward policy "only"', zone['idnsname'][0])
> <---missing empty line---->
> +    def execute(self, **options):
> 
> PATCH * DNS upgrade: change global forwarding policy in LDAP to "only" if
> private IPs are used *
> 9)
> - dnsutil.related_to_auto_empty_zone(zone.get('idnsname')[0])
> +                dnsutil.related_to_auto_empty_zone(
> +                    dnsutil.DNSName(zone.get('idnsname')[0]))
> 
> Should be in previous commit
> 
> 10)
> -            return
> +            return False, []
> This should be fixed in the previous commit
> 
> PATCH * DNS upgrade: change global forwarding policy in named.conf to "only"
> if private IPs are used *
> 11)
> IMO this is an upgrade of configuration and this should be in
> ipaserver/install/server/upgrade.py, upgrade plugins are used only for
> updating of LDAP values
> 
> Unless you really want to use this as precedence, but then it requires broader
> discussion.
> 
> 12)
> 
> bind.named_conf_get_directive
> should be
> bindinstance.named_conf_get_directive
> 
> see 1)

This new patchset completely obsoletes the old one. I had to reshuffle few
things to to make the split between server config & LDAP upgrade possible.

Hopefully I addressed all your comment.

-- 
Petr^2 Spacek
From 245fc3ec1838a6dc77c5e8ea8673ddd42ba50c64 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Tue, 17 May 2016 17:20:25 +0200
Subject: [PATCH] Use root_logger for verify_host_resolvable()

After discussion with Martin Basti we decided to standardize on root_logger
with hope that one day we will use root_logger.getLogger('module')
to make logging prettier and tunable per module.

https://fedorahosted.org/freeipa/ticket/5710
---
 client/ipa-client-install          | 2 +-
 install/tools/ipa-replica-manage   | 2 +-
 ipalib/plugins/dns.py              | 4 ++--
 ipalib/plugins/host.py             | 2 +-
 ipalib/plugins/service.py          | 8 ++++----
 ipalib/util.py                     | 8 +++++---
 ipaserver/install/bindinstance.py  | 2 +-
 ipatests/test_integration/tasks.py | 2 +-
 8 files changed, 16 insertions(+), 14 deletions(-)

diff --git a/client/ipa-client-install b/client/ipa-client-install
index cff3fbfcdee8690c9466ea339a362edfb151a11a..538b6adee8a22839c5f16d46a242228ca3ca43ee 100755
--- a/client/ipa-client-install
+++ b/client/ipa-client-install
@@ -1753,7 +1753,7 @@ def get_server_connection_interface(server):
 def client_dns(server, hostname, options):
 
     try:
-        verify_host_resolvable(hostname, root_logger)
+        verify_host_resolvable(hostname)
         dns_ok = True
     except errors.DNSNotARecordError:
         root_logger.warning("Hostname (%s) does not have A/AAAA record.",
diff --git a/install/tools/ipa-replica-manage b/install/tools/ipa-replica-manage
index 14e768965601cef08f13792bb5cd086534199538..8e692d295aafdcd6ae8a32b6d125f998434d8adb 100755
--- a/install/tools/ipa-replica-manage
+++ b/install/tools/ipa-replica-manage
@@ -830,7 +830,7 @@ def enforce_host_existence(host, message=None):
         return
 
     try:
-        verify_host_resolvable(host, root_logger)
+        verify_host_resolvable(host)
     except errors.DNSNotARecordError as ex:
         if message is None:
             message = "Unknown host %s: %s" % (host, ex)
diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py
index 9add5e7a1e944ba67a8dc554d0f9f208b8fa2ed1..d7ea553c81bc4199d68ca26053a92ba5da997b68 100644
--- a/ipalib/plugins/dns.py
+++ b/ipalib/plugins/dns.py
@@ -1573,7 +1573,7 @@ def check_ns_rec_resolvable(zone, name, log):
         # this is a DNS name relative to the zone
         name = name.derelativize(zone.make_absolute())
     try:
-        verify_host_resolvable(name, log)
+        verify_host_resolvable(name)
     except errors.DNSNotARecordError:
         raise errors.NotFound(
             reason=_('Nameserver \'%(host)s\' does not have a corresponding '
@@ -4232,7 +4232,7 @@ class dns_resolve(Command):
         query=args[0]
 
         try:
-            verify_host_resolvable(query, self.log)
+            verify_host_resolvable(query)
         except errors.DNSNotARecordError:
             raise errors.NotFound(
                 reason=_('Host \'%(host)s\' not found') % {'host': query}
diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py
index 611f9a9c7c51d5f81ba9ca91c6a900028aa249f4..494f8d04d4b9fe39e33c0016d8023d37b291d30f 100644
--- a/ipalib/plugins/host.py
+++ b/ipalib/plugins/host.py
@@ -635,7 +635,7 @@ class host_add(LDAPCreate):
                     check_forward=True,
                     check_reverse=check_reverse)
         if not options.get('force', False) and not 'ip_address' in options:
-            util.verify_host_resolvable(keys[-1], self.log)
+            util.verify_host_resolvable(keys[-1])
         if 'locality' in entry_attrs:
             entry_attrs['l'] = entry_attrs['locality']
         entry_attrs['cn'] = keys[-1]
diff --git a/ipalib/plugins/service.py b/ipalib/plugins/service.py
index 644b07f463a008eff12d3ff36de9404c03cf8a69..fb52b338a7c5222fe41de9a35e0582f068a0fe69 100644
--- a/ipalib/plugins/service.py
+++ b/ipalib/plugins/service.py
@@ -564,10 +564,10 @@ class service_add(LDAPCreate):
         entry_attrs['usercertificate'] = certs_der
 
         if not options.get('force', False):
-             # We know the host exists if we've gotten this far but we
-             # really want to discourage creating services for hosts that
-             # don't exist in DNS.
-             util.verify_host_resolvable(hostname, self.log)
+            # We know the host exists if we've gotten this far but we
+            # really want to discourage creating services for hosts that
+            # don't exist in DNS.
+            util.verify_host_resolvable(hostname)
         if not 'managedby' in entry_attrs:
             entry_attrs['managedby'] = hostresult['dn']
 
diff --git a/ipalib/util.py b/ipalib/util.py
index 709bfbb8ef90924768823ed4316aacb9a0b9d79b..7d3a502e4b8bbc264310801ca4bd660699833823 100644
--- a/ipalib/util.py
+++ b/ipalib/util.py
@@ -43,6 +43,7 @@ from ipapython.ssh import SSHPublicKey
 from ipapython.dn import DN, RDN
 from ipapython.dnsutil import DNSName
 from ipapython.graph import Graph
+from ipapython.ipa_log_manager import root_logger
 
 if six.PY3:
     unicode = str
@@ -64,7 +65,8 @@ def json_serialize(obj):
         return ''
     return json_serialize(obj.__json__())
 
-def verify_host_resolvable(fqdn, log):
+
+def verify_host_resolvable(fqdn):
     """
     See if the hostname has a DNS A/AAAA record.
     """
@@ -75,12 +77,12 @@ def verify_host_resolvable(fqdn, log):
     for rdtype in ('A', 'AAAA'):
         try:
             answers = resolver.query(fqdn, rdtype)
-            log.debug(
+            root_logger.debug(
                 'IPA: found %d %s records for %s: %s' % (len(answers),
                 rdtype, fqdn, ' '.join(str(answer) for answer in answers))
             )
         except DNSException:
-            log.debug(
+            root_logger.debug(
                 'IPA: DNS %s record lookup failed for %s' %
                 (rdtype, fqdn)
             )
diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py
index 0a988562fcb3171f5c4db096fe217acb6416b362..2a642422ae4a1e689ab5e223e80b155a125b9578 100644
--- a/ipaserver/install/bindinstance.py
+++ b/ipaserver/install/bindinstance.py
@@ -878,7 +878,7 @@ class BindInstance(service.Service):
         if not dns_zone_exists(zone, self.api):
             # check if master hostname is resolvable
             try:
-                verify_host_resolvable(fqdn, root_logger)
+                verify_host_resolvable(fqdn)
             except errors.DNSNotARecordError:
                 root_logger.warning("Master FQDN (%s) is not resolvable.",
                                     fqdn)
diff --git a/ipatests/test_integration/tasks.py b/ipatests/test_integration/tasks.py
index 70f4fa7fd96f9d993332996f087577d1c88f828e..aebd907ebfdd0c513c8c96eece220f009a0e8953 100644
--- a/ipatests/test_integration/tasks.py
+++ b/ipatests/test_integration/tasks.py
@@ -1064,7 +1064,7 @@ def add_a_records_for_hosts_in_master_domain(master):
         # We don't need to take care of the zone creation since it is master
         # domain
         try:
-            verify_host_resolvable(host.hostname, log)
+            verify_host_resolvable(host.hostname)
             log.debug("The host (%s) is resolvable." % host.domain.name)
         except errors.DNSNotARecordError:
             log.debug("Hostname (%s) does not have A/AAAA record. Adding new one.",
-- 
2.5.5

From 405f3c35b897e46f1bb957f4da201027e6eced90 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Tue, 17 May 2016 17:26:20 +0200
Subject: [PATCH] Move IP address resolution from
 ipaserver.install.installutils to ipapython.dnsutil

This is to make it reusable from other modules and to avoid future code
duplication.

https://fedorahosted.org/freeipa/ticket/5710
---
 ipapython/dnsutil.py              | 59 +++++++++++++++++++++++++++++++++++++++
 ipaserver/install/bindinstance.py | 16 ++++++++---
 ipaserver/install/installutils.py | 26 ++++-------------
 3 files changed, 76 insertions(+), 25 deletions(-)

diff --git a/ipapython/dnsutil.py b/ipapython/dnsutil.py
index 6287e3eefb683fb124766789eaad6db4c2ce4cf5..7fa7646d96e4462d7c08e57928c1bca41f57a411 100644
--- a/ipapython/dnsutil.py
+++ b/ipapython/dnsutil.py
@@ -24,6 +24,9 @@ import copy
 
 import six
 
+from ipapython.ipautil import CheckedIPAddress
+from ipapython.ipa_log_manager import root_logger
+
 if six.PY3:
     unicode = str
 
@@ -231,6 +234,62 @@ def inside_auto_empty_zone(name):
     return False
 
 
+def resolve_rrsets(fqdn, rdtypes):
+    """
+    Get Resource Record sets for given FQDN.
+    CNAME chain is followed during resolution
+    but CNAMEs are not returned in the resulting rrset.
+
+    :returns:
+        set of dns.rrset.RRset objects, can be empty
+        if the FQDN does not exist or if none of rrtypes exist
+    """
+    # empty set of rdtypes would always return empty set of rrsets
+    assert rdtypes, "rdtypes must not be empty"
+
+    if not isinstance(fqdn, DNSName):
+        fqdn = DNSName(fqdn)
+
+    fqdn = fqdn.make_absolute()
+    rrsets = set()
+    for rdtype in rdtypes:
+        try:
+            answer = dns.resolver.query(fqdn, rdtype)
+            root_logger.debug('found %d %s records for %s: %s',
+                              len(answer), rdtype, fqdn, ' '.join(
+                                  str(rr) for rr in answer))
+            rrsets.add(answer.rrset)
+        except dns.resolver.NXDOMAIN as ex:
+            root_logger.debug(ex)
+            break  # no such FQDN, do not iterate
+        except dns.resolver.NoAnswer as ex:
+            root_logger.debug(ex)  # record type does not exist for given FQDN
+        except dns.exception.DNSException as ex:
+            root_logger.error('DNS query for %s %s failed: %s',
+                              fqdn, rdtype, ex)
+            raise
+
+    return rrsets
+
+
+def resolve_ip_addresses(fqdn):
+    """Get IP addresses from DNS A/AAAA records for given host.
+    :returns:
+        list of IP addresses as CheckedIPAddress objects
+    """
+    rrsets = resolve_rrsets(fqdn, ['A', 'AAAA'])
+    ip_addresses = set()
+    for rrset in rrsets:
+        ip_addresses.update({CheckedIPAddress(ip,  # accept whatever is in DNS
+                                              parse_netmask=False,
+                                              allow_network=True,
+                                              allow_loopback=True,
+                                              allow_broadcast=True,
+                                              allow_multicast=True)
+                             for ip in rrset})
+    return ip_addresses
+
+
 def check_zone_overlap(zone, raise_on_error=True):
     root_logger.info("Checking DNS domain %s, please wait ..." % zone)
     if not isinstance(zone, DNSName):
diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py
index 2a642422ae4a1e689ab5e223e80b155a125b9578..ec8526a8e596c352999c450abe6c22ae03e66b4f 100644
--- a/ipaserver/install/bindinstance.py
+++ b/ipaserver/install/bindinstance.py
@@ -908,7 +908,9 @@ class BindInstance(service.Service):
             if fqdn == self.fqdn:
                 continue
 
-            addrs = installutils.resolve_host(fqdn)
+            addrs = dnsutil.resolve_ip_addresses(fqdn)
+            # hack, will go away with locations
+            addrs = [str(addr) for addr in addrs]
 
             root_logger.debug("Adding DNS records for master %s" % fqdn)
             self.__add_master_records(fqdn, addrs)
@@ -964,7 +966,9 @@ class BindInstance(service.Service):
                 if dns_zone_exists(zone, self.api):
                     addrs = get_fwd_rr(zone, host, api=self.api)
                 else:
-                    addrs = installutils.resolve_host(fqdn)
+                    addrs = dnsutil.resolve_ip_addresses(fqdn)
+                    # hack, will go away with locations
+                    addrs = [str(addr) for addr in addrs]
 
                 self.__add_ipa_ca_records(fqdn, addrs, True)
 
@@ -1084,7 +1088,9 @@ class BindInstance(service.Service):
         if dns_zone_exists(zone, self.api):
             addrs = get_fwd_rr(zone, host, api=self.api)
         else:
-            addrs = installutils.resolve_host(fqdn)
+            addrs = dnsutil.resolve_ip_addresses(fqdn)
+            # hack, will go away with locations
+            addrs = [str(addr) for addr in addrs]
 
         self.domain = domain_name
 
@@ -1172,7 +1178,9 @@ class BindInstance(service.Service):
         if dns_zone_exists(zone, self.api):
             addrs = get_fwd_rr(zone, host, api=self.api)
         else:
-            addrs = installutils.resolve_host(fqdn)
+            addrs = dnsutil.resolve_ip_addresses(fqdn)
+            # hack, will go away with locations
+            addrs = [str(addr) for addr in addrs]
 
         for addr in addrs:
             del_fwd_rr(domain_name, IPA_CA_RECORD, addr, api=self.api)
diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
index 179909543e8791ff2a85b6bf4ce57dee8d00508b..2a71ef7ac767c8259c6d2bc63399fdec55b3f8dc 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -55,6 +55,7 @@ from ipaserver.install import certs, service, sysupgrade
 from ipaplatform import services
 from ipaplatform.paths import paths
 from ipaplatform.tasks import tasks
+from ipapython import dnsutil
 
 if six.PY3:
     unicode = str
@@ -444,24 +445,6 @@ def create_keytab(path, principal):
 
     kadmin("ktadd -k " + path + " " + principal)
 
-def resolve_host(host_name):
-    try:
-        addrinfos = socket.getaddrinfo(host_name, None,
-                                       socket.AF_UNSPEC, socket.SOCK_STREAM)
-
-        ip_list = []
-
-        for ai in addrinfos:
-            ip = ai[4][0]
-            if ip == "127.0.0.1" or ip == "::1":
-                raise HostnameLocalhost("The hostname resolves to the localhost address")
-
-            ip_list.append(ip)
-
-        return ip_list
-    except socket.error:
-        return []
-
 def get_host_name(no_host_dns):
     """
     Get the current FQDN from the socket and verify that it is valid.
@@ -477,9 +460,10 @@ def get_host_name(no_host_dns):
 
 def get_server_ip_address(host_name, unattended, setup_dns, ip_addresses):
     # Check we have a public IP that is associated with the hostname
-    try:
-        hostaddr = resolve_host(host_name)
-    except HostnameLocalhost:
+    hostaddr = dnsutil.resolve_ip_addresses(host_name)
+    if hostaddr.intersection(
+            {ipautil.CheckedIPAddress(ip, allow_loopback=True)
+             for ip in ['127.0.0.1', '::1']}):
         print("The hostname resolves to the localhost address (127.0.0.1/::1)", file=sys.stderr)
         print("Please change your /etc/hosts file so that the hostname", file=sys.stderr)
         print("resolves to the ip address of your network interface.", file=sys.stderr)
-- 
2.5.5

From 307d1e6c641a7d0242eaafa90a2535de3c137d45 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Tue, 17 May 2016 17:28:36 +0200
Subject: [PATCH] Turn verify_host_resolvable() into a wrapper around
 ipapython.dnsutil

The code was duplicate and less generic anyway.

https://fedorahosted.org/freeipa/ticket/5710
---
 ipalib/util.py | 28 +++-------------------------
 1 file changed, 3 insertions(+), 25 deletions(-)

diff --git a/ipalib/util.py b/ipalib/util.py
index 7d3a502e4b8bbc264310801ca4bd660699833823..eef3cd90457736ff1a417e776d5ed954084bab16 100644
--- a/ipalib/util.py
+++ b/ipalib/util.py
@@ -42,8 +42,8 @@ from ipalib.text import _
 from ipapython.ssh import SSHPublicKey
 from ipapython.dn import DN, RDN
 from ipapython.dnsutil import DNSName
+from ipapython.dnsutil import resolve_ip_addresses
 from ipapython.graph import Graph
-from ipapython.ipa_log_manager import root_logger
 
 if six.PY3:
     unicode = str
@@ -67,30 +67,8 @@ def json_serialize(obj):
 
 
 def verify_host_resolvable(fqdn):
-    """
-    See if the hostname has a DNS A/AAAA record.
-    """
-    if not isinstance(fqdn, DNSName):
-        fqdn = DNSName(fqdn)
-
-    fqdn = fqdn.make_absolute()
-    for rdtype in ('A', 'AAAA'):
-        try:
-            answers = resolver.query(fqdn, rdtype)
-            root_logger.debug(
-                'IPA: found %d %s records for %s: %s' % (len(answers),
-                rdtype, fqdn, ' '.join(str(answer) for answer in answers))
-            )
-        except DNSException:
-            root_logger.debug(
-                'IPA: DNS %s record lookup failed for %s' %
-                (rdtype, fqdn)
-            )
-            continue
-        else:
-            return
-    # dns lookup failed in both tries
-    raise errors.DNSNotARecordError()
+    if not resolve_ip_addresses(fqdn):
+        raise errors.DNSNotARecordError()
 
 
 def has_soa_or_ns_record(domain):
-- 
2.5.5

From 795c2d52d4d3f57cc720bc35df2f1022f8c6d15a Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Mon, 25 Apr 2016 14:07:16 +0200
Subject: [PATCH] Add ipaDNSVersion option to dnsconfig* commands and use new
 attribute

Ad-hoc LDAP calls in DNS upgrade code were hard to maintain and
ipaConfigString was bad idea from the very beginning as it was hard to
manipulate the number in it.

To avoid problems in future we are introducing new ipaDNSVersion
attribute which is used on cn=dns instead of ipaConfigString.
Original value of ipaConfigString is kept in the tree for now
so older upgraders see it and do not execute the upgrade procedure again.

The attribute can be changed only by installer/upgrade so it is not
exposed in dnsconfig_mod API.

Command dnsconfig_show displays it only if --all option was used.

https://fedorahosted.org/freeipa/ticket/5710
---
 ACI.txt                                        |  2 +-
 install/share/60ipadns.ldif                    |  2 +
 install/share/dns.ldif                         |  4 +-
 install/updates/40-dns.update                  |  1 -
 install/updates/90-post_upgrade_plugins.update |  1 +
 ipalib/plugins/dns.py                          | 14 ++++-
 ipaserver/install/plugins/dns.py               | 77 ++++++++++++++++++++------
 7 files changed, 78 insertions(+), 23 deletions(-)

diff --git a/ACI.txt b/ACI.txt
index c3d9baee9d8956250298359845121033a7e1d026..a6fcf71f718f457593cfef62bbba0d3584536805 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -51,7 +51,7 @@ aci: (targetattr = "cospriority")(targetfilter = "(objectclass=costemplate)")(ve
 dn: cn=costemplates,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "cn || cospriority || createtimestamp || entryusn || krbpwdpolicyreference || modifytimestamp || objectclass")(targetfilter = "(objectclass=costemplate)")(version 3.0;acl "permission:System: Read Group Password Policy costemplate";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Group Password Policy costemplate,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: dc=ipa,dc=example
-aci: (targetattr = "createtimestamp || entryusn || idnsallowsyncptr || idnsforwarders || idnsforwardpolicy || idnspersistentsearch || idnszonerefresh || modifytimestamp || objectclass")(target = "ldap:///cn=dns,dc=ipa,dc=example";)(targetfilter = "(objectclass=idnsConfigObject)")(version 3.0;acl "permission:System: Read DNS Configuration";allow (read) groupdn = "ldap:///cn=System: Read DNS Configuration,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+aci: (targetattr = "createtimestamp || entryusn || idnsallowsyncptr || idnsforwarders || idnsforwardpolicy || idnspersistentsearch || idnszonerefresh || ipadnsversion || modifytimestamp || objectclass")(target = "ldap:///cn=dns,dc=ipa,dc=example";)(targetfilter = "(objectclass=idnsConfigObject)")(version 3.0;acl "permission:System: Read DNS Configuration";allow (read) groupdn = "ldap:///cn=System: Read DNS Configuration,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: dc=ipa,dc=example
 aci: (targetattr = "idnsallowsyncptr || idnsforwarders || idnsforwardpolicy || idnspersistentsearch || idnszonerefresh")(target = "ldap:///cn=dns,dc=ipa,dc=example";)(targetfilter = "(objectclass=idnsConfigObject)")(version 3.0;acl "permission:System: Write DNS Configuration";allow (write) groupdn = "ldap:///cn=System: Write DNS Configuration,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: dc=ipa,dc=example
diff --git a/install/share/60ipadns.ldif b/install/share/60ipadns.ldif
index 99abf07b19073d8fbf5f8a07478e77db08a48ef9..ecbea5a453bccd5165112ed744f8aa7b559e1bfe 100644
--- a/install/share/60ipadns.ldif
+++ b/install/share/60ipadns.ldif
@@ -72,11 +72,13 @@ attributeTypes: ( 2.16.840.1.113730.3.8.5.27 NAME 'idnsSecAlgorithm' DESC 'DNSKE
 attributeTypes: ( 2.16.840.1.113730.3.8.5.28 NAME 'idnsSecKeyRef' DESC 'PKCS#11 URI of the key' EQUALITY caseExactMatch SINGLE-VALUE SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.1' )
 attributeTypes: ( 2.16.840.1.113730.3.8.5.32 NAME 'ipaLocation' DESC 'Reference to IPA location' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'IPA v4.4' )
 attributeTypes: ( 2.16.840.1.113730.3.8.5.33 NAME 'ipaLocationWeight' DESC 'Weight for the server in IPA location' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.4' )
+attributeTypes: ( 2.16.840.1.113730.3.8.11.74 NAME 'ipaDNSVersion' DESC 'IPA DNS data version' EQUALITY integerMatch ORDERING integerOrderingMatch SINGLE-VALUE SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 X-ORIGIN 'IPA v4.3' )
 objectClasses: ( 2.16.840.1.113730.3.8.6.0 NAME 'idnsRecord' DESC 'dns Record, usually a host' SUP top STRUCTURAL MUST idnsName MAY ( cn $ idnsAllowDynUpdate $ dNSTTL $ dNSClass $ aRecord $ aAAARecord $ a6Record $ nSRecord $ cNAMERecord $ pTRRecord $ sRVRecord $ tXTRecord $ mXRecord $ mDRecord $ hInfoRecord $ mInfoRecord $ aFSDBRecord $ SigRecord $ KeyRecord $ LocRecord $ nXTRecord $ nAPTRRecord $ kXRecord $ certRecord $ dNameRecord $ dSRecord $ sSHFPRecord $ rRSIGRecord $ nSECRecord $ DLVRecord $ TLSARecord $ UnknownRecord $ RPRecord $ APLRecord $ IPSECKEYRecord $ DHCIDRecord $ HIPRecord $ SPFRecord ) )
 objectClasses: ( 2.16.840.1.113730.3.8.6.1 NAME 'idnsZone' DESC 'Zone class' SUP idnsRecord STRUCTURAL MUST ( idnsZoneActive $ idnsSOAmName $ idnsSOArName $ idnsSOAserial $ idnsSOArefresh $ idnsSOAretry $ idnsSOAexpire $ idnsSOAminimum ) MAY ( idnsUpdatePolicy $ idnsAllowQuery $ idnsAllowTransfer $ idnsAllowSyncPTR $ idnsForwardPolicy $ idnsForwarders $ idnsSecInlineSigning $ nSEC3PARAMRecord ) )
 objectClasses: ( 2.16.840.1.113730.3.8.6.2 NAME 'idnsConfigObject' DESC 'DNS global config options' STRUCTURAL MAY ( idnsForwardPolicy $ idnsForwarders $ idnsAllowSyncPTR $ idnsZoneRefresh $ idnsPersistentSearch ) )
 objectClasses: ( 2.16.840.1.113730.3.8.12.18 NAME 'ipaDNSZone' SUP top AUXILIARY MUST idnsName MAY managedBy X-ORIGIN 'IPA v3' )
 objectClasses: ( 2.16.840.1.113730.3.8.6.3 NAME 'idnsForwardZone' DESC 'Forward Zone class' SUP top STRUCTURAL MUST ( idnsName $ idnsZoneActive ) MAY ( idnsForwarders $ idnsForwardPolicy ) )
 objectClasses: ( 2.16.840.1.113730.3.8.6.4 NAME 'idnsSecKey' DESC 'DNSSEC key metadata' STRUCTURAL MUST ( idnsSecKeyRef $ idnsSecKeyCreated $ idnsSecAlgorithm ) MAY ( idnsSecKeyPublish $ idnsSecKeyActivate $ idnsSecKeyInactive $ idnsSecKeyDelete $ idnsSecKeyZone $ idnsSecKeyRevoke $ idnsSecKeySep $ cn ) X-ORIGIN 'IPA v4.1' )
 objectClasses: ( 2.16.840.1.113730.3.8.6.7 NAME 'ipaLocationObject' DESC 'Object for storing IPA server location' STRUCTURAL MUST ( idnsName ) MAY ( description ) X-ORIGIN 'IPA v4.4' )
 objectClasses: ( 2.16.840.1.113730.3.8.6.8 NAME 'ipaLocationMember' DESC 'Member object of IPA location' AUXILIARY MAY ( ipaLocation $ ipaLocationWeight ) X-ORIGIN 'IPA v4.4' )
+objectClasses: ( 2.16.840.1.113730.3.8.12.36 NAME 'ipaDNSContainer' DESC 'IPA DNS container' AUXILIARY MUST ( ipaDNSVersion ) X-ORIGIN 'IPA v4.3' )
diff --git a/install/share/dns.ldif b/install/share/dns.ldif
index 54a452bcd8715240905166e6ab18f722983be00f..1a6ce3ae10754fa41fe09fa68d8396f14dbb85d6 100644
--- a/install/share/dns.ldif
+++ b/install/share/dns.ldif
@@ -2,10 +2,10 @@ dn: cn=dns,$SUFFIX
 changetype: add
 objectClass: idnsConfigObject
 objectClass: nsContainer
-objectClass: ipaConfigObject
+objectClass: ipaDNSContainer
 objectClass: top
 cn: dns
-ipaConfigString: DNSVersion 1
+ipaDNSVersion: 1
 aci: (targetattr = "*")(version 3.0; acl "Allow read access"; allow (read,search,compare) groupdn = "ldap:///cn=Read DNS Entries,cn=permissions,cn=pbac,$SUFFIX" or userattr = "parent[0,1].managedby#GROUPDN";)
 aci: (target = "ldap:///idnsname=*,cn=dns,$SUFFIX";)(version 3.0;acl "Add DNS entries in a zone";allow (add) userattr = "parent[1].managedby#GROUPDN";)
 aci: (target = "ldap:///idnsname=*,cn=dns,$SUFFIX";)(version 3.0;acl "Remove DNS entries from a zone";allow (delete) userattr = "parent[1].managedby#GROUPDN";)
diff --git a/install/updates/40-dns.update b/install/updates/40-dns.update
index 9f64a2f707db5cb0e3503259a0e64d9831ae92f2..4c0824b8350052e47e6f73fc4985cc03e08ee36e 100644
--- a/install/updates/40-dns.update
+++ b/install/updates/40-dns.update
@@ -2,7 +2,6 @@
 # update DNS container
 dn: cn=dns, $SUFFIX
 addifexist: objectClass: idnsConfigObject
-addifexist: objectClass: ipaConfigObject
 addifexist: aci:(target = "ldap:///idnsname=*,cn=dns,$SUFFIX";)(version 3.0;acl "Add DNS entries in a zone";allow (add) userattr = "parent[1].managedby#GROUPDN";)
 addifexist: aci:(target = "ldap:///idnsname=*,cn=dns,$SUFFIX";)(version 3.0;acl "Remove DNS entries from a zone";allow (delete) userattr = "parent[1].managedby#GROUPDN";)
 addifexist: aci:(targetattr = "a6record || aaaarecord || afsdbrecord || aplrecord || arecord || certrecord || cn || cnamerecord || dhcidrecord || dlvrecord || dnamerecord || dnsclass || dnsttl || dsrecord || hinforecord || hiprecord || idnsallowdynupdate || idnsallowquery || idnsallowsyncptr || idnsallowtransfer || idnsforwarders || idnsforwardpolicy || idnsname || idnssecinlinesigning || idnssoaexpire || idnssoaminimum || idnssoamname || idnssoarefresh || idnssoaretry || idnssoarname || idnssoaserial || idnsupdatepolicy || idnszoneactive || ipseckeyrecord || keyrecord || kxrecord || locrecord || mdrecord || minforecord || mxrecord || naptrrecord || nsecrecord || nsec3paramrecord || nsrecord || nxtrecord || ptrrecord || rprecord || rrsigrecord || sigrecord || spfrecord || srvrecord || sshfprecord || tlsarecord || txtrecord || unknownrecord ")(target = "ldap:///idnsname=*,cn=dns,$SUFFIX";)(version 3.0;acl "Update DNS entries in a zone";allow (write) userattr = "parent[0,1].managedby#GROUPDN";)
diff --git a/install/updates/90-post_upgrade_plugins.update b/install/updates/90-post_upgrade_plugins.update
index 9c9ee160fffedbc8e8d59705169e6cf08ddc9779..d8498edd4565fbad91ca5dea48e5bc2296e3fc25 100644
--- a/install/updates/90-post_upgrade_plugins.update
+++ b/install/updates/90-post_upgrade_plugins.update
@@ -3,6 +3,7 @@
 
 # middle
 plugin: update_ca_topology
+plugin: update_ipaconfigstring_dnsversion_to_ipadnsversion
 plugin: update_dnszones
 plugin: update_dns_limits
 plugin: update_sigden_extdom_broken_config
diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py
index d7ea553c81bc4199d68ca26053a92ba5da997b68..60ea2eb5b1790626fc1bd5a0fe9fc3a3aa5f9aea 100644
--- a/ipalib/plugins/dns.py
+++ b/ipalib/plugins/dns.py
@@ -4312,6 +4312,9 @@ class dnsconfig(LDAPObject):
             cli_name='zone_refresh',
             label=_('Zone refresh interval'),
         ),
+        Int('ipadnsversion?',  # available only in installer/upgrade
+            label=_('IPA DNS version'),
+        ),
     )
     managed_permissions = {
         'System: Write DNS Configuration': {
@@ -4338,7 +4341,7 @@ class dnsconfig(LDAPObject):
             'ipapermdefaultattr': {
                 'objectclass',
                 'idnsallowsyncptr', 'idnsforwarders', 'idnsforwardpolicy',
-                'idnspersistentsearch', 'idnszonerefresh'
+                'idnspersistentsearch', 'idnszonerefresh', 'ipadnsversion'
             },
             'default_privileges': {'DNS Administrators', 'DNS Servers'},
         },
@@ -4359,11 +4362,17 @@ class dnsconfig(LDAPObject):
             result['summary'] = unicode(_('Global DNS configuration is empty'))
 
 
-
 @register()
 class dnsconfig_mod(LDAPUpdate):
     __doc__ = _('Modify global DNS configuration.')
 
+    def get_options(self):
+        """hide ipadnsversion outside of installer/upgrade"""
+        for option in super(dnsconfig_mod, self).get_options():
+            if option.name == 'ipadnsversion':
+                option = option.clone(include=('installer', 'updates'))
+            yield option
+
     def interactive_prompt_callback(self, kw):
 
         # show informative message on client side
@@ -4421,6 +4430,7 @@ class dnsconfig_show(LDAPRetrieve):
         return result
 
 
+
 @register()
 class dnsforwardzone(DNSZoneBase):
     """
diff --git a/ipaserver/install/plugins/dns.py b/ipaserver/install/plugins/dns.py
index c723953274b96c4f3201affef77cbf3d8033106b..156b4b513986ca439ce634a8835d248769fe3251 100644
--- a/ipaserver/install/plugins/dns.py
+++ b/ipaserver/install/plugins/dns.py
@@ -30,6 +30,61 @@ from ipalib.plugins.dns import dns_container_exists
 from ipapython.ipa_log_manager import root_logger
 
 
+class DNSUpdater(Updater):
+    def version_update_needed(self, target_version):
+        """Test if IPA DNS version is smaller than target version."""
+        assert isinstance(target_version, int)
+
+        try:
+            return int(self.api.Command['dnsconfig_show'](
+                all=True)['result']['ipadnsversion'][0]) < target_version
+        except errors.NotFound:
+            # IPA DNS is not configured
+            return False
+
+class update_ipaconfigstring_dnsversion_to_ipadnsversion(Updater):
+    """
+    IPA <= 4.3.1 used ipaConfigString "DNSVersion 1" on DNS container.
+    This was hard to deal with in API so from IPA 4.3.2 we are using
+    new ipaDNSVersion attribute with integer syntax.
+    Old ipaConfigString is left there for now so if someone accidentally
+    executes upgrade on an old replica again it will not re-upgrade the data.
+    """
+    def execute(self, **options):
+        ldap = self.api.Backend.ldap2
+        dns_container_dn = DN(self.api.env.container_dns, self.api.env.basedn)
+        try:
+            container_entry = ldap.get_entry(dns_container_dn)
+        except errors.NotFound:
+            # DNS container not found, nothing to upgrade
+            return False, []
+
+        if 'ipadnscontainer' in [
+            o.lower() for o in container_entry['objectclass']
+        ]:
+            # version data are already migrated
+            return False, []
+
+        self.log.debug('Migrating DNS ipaConfigString to ipaDNSVersion')
+        container_entry['objectclass'].append('ipadnscontainer')
+        version = 0
+        for config_option in container_entry.get("ipaConfigString", []):
+            matched = re.match("^DNSVersion\s+(?P<version>\d+)$",
+                               config_option, flags=re.I)
+            if matched:
+                version = int(matched.group("version"))
+            else:
+                self.log.error(
+                    'Failed to parse DNS version from ipaConfigString, '
+                    'defaulting to version %s', version)
+        container_entry['ipadnsversion'] = version
+        ldap.update_entry(container_entry)
+        self.log.debug('ipaDNSVersion = %s', version)
+        return False, []
+
+api.register(update_ipaconfigstring_dnsversion_to_ipadnsversion)
+
+
 class update_dnszones(Updater):
     """
     Update all zones to meet requirements in the new FreeIPA versions
@@ -140,7 +195,7 @@ class update_dns_limits(Updater):
 api.register(update_dns_limits)
 
 
-class update_master_to_dnsforwardzones(Updater):
+class update_master_to_dnsforwardzones(DNSUpdater):
     """
     Update all zones to meet requirements in the new FreeIPA versions
 
@@ -157,26 +212,14 @@ class update_master_to_dnsforwardzones(Updater):
     def execute(self, **options):
         ldap = self.api.Backend.ldap2
         # check LDAP if forwardzones already uses new semantics
-        dns_container_dn = DN(self.api.env.container_dns, self.api.env.basedn)
-        try:
-            container_entry = ldap.get_entry(dns_container_dn)
-        except errors.NotFound:
-            # DNS container not found, nothing to upgrade
+        if not self.version_update_needed(target_version=1):
+            # forwardzones already uses new semantics,
+            # no upgrade is required
             return False, []
 
-        for config_option in container_entry.get("ipaConfigString", []):
-            matched = re.match("^DNSVersion\s+(?P<version>\d+)$",
-                               config_option, flags=re.I)
-            if matched and int(matched.group("version")) >= 1:
-                # forwardzones already uses new semantics,
-                # no upgrade is required
-                return False, []
-
         self.log.debug('Updating forward zones')
         # update the DNSVersion, following upgrade can be executed only once
-        container_entry.setdefault(
-            'ipaConfigString', []).append(u"DNSVersion 1")
-        ldap.update_entry(container_entry)
+        self.api.Command['dnsconfig_mod'](ipadnsversion=1)
 
         # Updater in IPA version from 4.0 to 4.1.2 doesn't work well, this
         # should detect if update in past has been executed, and set proper
-- 
2.5.5

From d9245c6feba09b2b13f47cf62bccaaee7cb87dec Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Wed, 27 Apr 2016 12:49:23 +0200
Subject: [PATCH] DNS upgrade: separate backup logic to make it reusable

https://fedorahosted.org/freeipa/ticket/5710
---
 ipaserver/install/plugins/dns.py | 146 ++++++++++++++++++++-------------------
 1 file changed, 74 insertions(+), 72 deletions(-)

diff --git a/ipaserver/install/plugins/dns.py b/ipaserver/install/plugins/dns.py
index 156b4b513986ca439ce634a8835d248769fe3251..9f833303e43a247d1b6c17a019830d2deefc38f7 100644
--- a/ipaserver/install/plugins/dns.py
+++ b/ipaserver/install/plugins/dns.py
@@ -31,6 +31,18 @@ from ipapython.ipa_log_manager import root_logger
 
 
 class DNSUpdater(Updater):
+    backup_dir = u'/var/lib/ipa/backup/'
+    # override backup_filename in subclass, it will be mangled by strftime
+    backup_filename = None
+
+    def __init__(self, api):
+        super(DNSUpdater, self).__init__(api)
+        backup_path = u'%s%s' % (self.backup_dir, self.backup_filename)
+        self.backup_path = time.strftime(backup_path)
+        self._ldif_writer = None
+        self._saved_privileges = set()  # store privileges only once
+        self.saved_zone_to_privilege = {}
+
     def version_update_needed(self, target_version):
         """Test if IPA DNS version is smaller than target version."""
         assert isinstance(target_version, int)
@@ -42,6 +54,57 @@ class DNSUpdater(Updater):
             # IPA DNS is not configured
             return False
 
+    @property
+    def ldif_writer(self):
+        if not self._ldif_writer:
+            self.log.info('Original zones will be saved in LDIF format in '
+                          '%s file' % self.backup_path)
+            self._ldif_writer = LDIFWriter(open(self.backup_path, 'w'))
+        return self._ldif_writer
+
+    def backup_zone(self, zone):
+        """Backup zone object, its records, permissions, and privileges.
+
+        Mapping from zone to privilege (containing zone's permissions)
+        will be stored in saved_zone_to_privilege dict for further usage.
+        """
+        dn = str(zone['dn'])
+        del zone['dn']  # dn shouldn't be as attribute in ldif
+        self.ldif_writer.unparse(dn, zone)
+
+        ldap = self.api.Backend.ldap2
+        if 'managedBy' in zone:
+            permission = ldap.get_entry(DN(zone['managedBy'][0]))
+            self.ldif_writer.unparse(str(permission.dn), dict(permission.raw))
+            for privilege_dn in permission.get('member', []):
+                # privileges can be shared by multiples zones
+                if privilege_dn not in self._saved_privileges:
+                    self._saved_privileges.add(privilege_dn)
+                    privilege = ldap.get_entry(privilege_dn)
+                    self.ldif_writer.unparse(str(privilege.dn),
+                                             dict(privilege.raw))
+
+            # remember privileges referened by permission
+            if 'member' in permission:
+                self.saved_zone_to_privilege[
+                    zone['idnsname'][0]
+                ] = permission['member']
+
+        if 'idnszone' in zone['objectClass']:
+            # raw values are required to store into ldif
+            records = self.api.Command['dnsrecord_find'](zone['idnsname'][0],
+                                                         all=True,
+                                                         raw=True,
+                                                         sizelimit=0)['result']
+            for record in records:
+                if record['idnsname'][0] == u'@':
+                    # zone record was saved before
+                    continue
+                dn = str(record['dn'])
+                del record['dn']
+                self.ldif_writer.unparse(dn, record)
+
+
 class update_ipaconfigstring_dnsversion_to_ipadnsversion(Updater):
     """
     IPA <= 4.3.1 used ipaConfigString "DNSVersion 1" on DNS container.
@@ -205,9 +268,7 @@ class update_master_to_dnsforwardzones(DNSUpdater):
 
     This should be applied only once, and only if original version was lower than 4.0
     """
-    backup_dir = u'/var/lib/ipa/backup/'
-    backup_filename = u'dns-forward-zones-backup-%Y-%m-%d-%H-%M-%S.ldif'
-    backup_path = u'%s%s' % (backup_dir, backup_filename)
+    backup_filename = u'dns-master-to-forward-zones-%Y-%m-%d-%H-%M-%S.ldif'
 
     def execute(self, **options):
         ldap = self.api.Backend.ldap2
@@ -259,77 +320,18 @@ class update_master_to_dnsforwardzones(DNSUpdater):
             zones_to_transform.append(zone)
 
         if zones_to_transform:
-            # add time to filename
-            self.backup_path = time.strftime(self.backup_path)
-
-            # DNs of privileges which contain dns managed permissions
-            privileges_to_ldif = set()  # store priviledges only once
-            zone_to_privileges = {}  # zone: [privileges cn]
-
             self.log.info('Zones with specified forwarders with policy different'
                           ' than none will be transformed to forward zones.')
-            self.log.info('Original zones will be saved in LDIF format in '
-                          '%s file' % self.backup_path)
-            try:
-
-                with open(self.backup_path, 'w') as f:
-                    writer = LDIFWriter(f)
-                    for zone in zones_to_transform:
-                        # save backup to ldif
-                        try:
-
-                            dn = str(zone['dn'])
-                            del zone['dn']  # dn shouldn't be as attribute in ldif
-                            writer.unparse(dn, zone)
-
-                            if 'managedBy' in zone:
-                                entry = ldap.get_entry(DN(zone['managedBy'][0]))
-                                for privilege_member_dn in entry.get('member', []):
-                                    privileges_to_ldif.add(privilege_member_dn)
-                                writer.unparse(str(entry.dn), dict(entry.raw))
-
-                                # privileges where permission is used
-                                if entry.get('member'):
-                                    zone_to_privileges[zone['idnsname'][0]] = entry['member']
-
-                            # raw values are required to store into ldif
-                            records = self.api.Command['dnsrecord_find'](
-                                        zone['idnsname'][0],
-                                        all=True,
-                                        raw=True,
-                                        sizelimit=0)['result']
-                            for record in records:
-                                if record['idnsname'][0] == u'@':
-                                    # zone record was saved before
-                                    continue
-                                dn = str(record['dn'])
-                                del record['dn']
-                                writer.unparse(dn, record)
-
-                        except Exception as e:
-                            self.log.error('Unable to backup zone %s' %
-                                           zone['idnsname'][0])
-                            self.log.error(traceback.format_exc())
-                            return False, []
-
-                    for privilege_dn in privileges_to_ldif:
-                        try:
-                            entry = ldap.get_entry(privilege_dn)
-                            writer.unparse(str(entry.dn), dict(entry.raw))
-                        except Exception as e:
-                            self.log.error('Unable to backup privilege %s' %
-                                           privilege_dn)
-                            self.log.error(traceback.format_exc())
-                            return False, []
-
-                    f.close()
-            except Exception:
-                self.log.error('Unable to create backup file')
-                self.log.error(traceback.format_exc())
-                return False, []
-
             # update
             for zone in zones_to_transform:
+                try:
+                    self.backup_zone(zone)
+                except Exception:
+                    self.log.error('Unable to create backup for zone, '
+                                   'terminating zone upgrade')
+                    self.log.error(traceback.format_exc())
+                    return False, []
+
                 # delete master zone
                 try:
                     self.api.Command['dnszone_del'](zone['idnsname'])
@@ -373,9 +375,9 @@ class update_master_to_dnsforwardzones(DNSUpdater):
                         continue
 
                     else:
-                        if zone['idnsname'][0] in zone_to_privileges:
+                        if zone['idnsname'][0] in self.saved_zone_to_privilege:
                             privileges = [
-                                dn[0].value for dn in zone_to_privileges[zone['idnsname'][0]]
+                                dn[0].value for dn in self.saved_zone_to_privilege[zone['idnsname'][0]]
                             ]
                             try:
                                 self.api.Command['permission_add_member'](perm_name,
-- 
2.5.5

From fb7ad539da81638793075fd1c5500f363d8fcc62 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Wed, 27 Apr 2016 13:26:48 +0200
Subject: [PATCH] Add function ipapython.dnsutil.related_to_auto_empty_zone()

It allows to test if given DNS name is sub/super domain
of an automatic empty zone.

https://fedorahosted.org/freeipa/ticket/5710
---
 ipapython/dnsutil.py | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/ipapython/dnsutil.py b/ipapython/dnsutil.py
index 7fa7646d96e4462d7c08e57928c1bca41f57a411..98fcc1bfcb1d745a2f9e1b3165bec04f1d997680 100644
--- a/ipapython/dnsutil.py
+++ b/ipapython/dnsutil.py
@@ -234,6 +234,36 @@ def inside_auto_empty_zone(name):
     return False
 
 
+def related_to_auto_empty_zone(name):
+    """True if specified absolute name is a sub/superdomain of an automatic
+    empty zone.
+
+    DNS domain is a subdomain of itself so this function
+    returns True for zone apexes, too.
+
+    >>> related_to_auto_empty_zone(DNSName('.'))
+    True
+    >>> related_to_auto_empty_zone(DNSName('in-addr.arpa.'))
+    True
+    >>> related_to_auto_empty_zone(DNSName('10.in-addr.arpa.'))
+    True
+    >>> related_to_auto_empty_zone(DNSName('1.10.in-addr.arpa.'))
+    True
+    >>> related_to_auto_empty_zone(DNSName('unrelated.example.'))
+    False
+    >>> related_to_auto_empty_zone(DNSName('1.10.in-addr.arpa'))
+    Traceback (most recent call last):
+      ...
+    AssertionError: ...
+    """
+    assert_absolute_dnsname(name)
+    relations = {dns.name.NAMERELN_SUBDOMAIN,
+                 dns.name.NAMERELN_EQUAL,
+                 dns.name.NAMERELN_SUPERDOMAIN}
+    return any(name.fullcompare(aez)[0] in relations
+               for aez in EMPTY_ZONES)
+
+
 def resolve_rrsets(fqdn, rdtypes):
     """
     Get Resource Record sets for given FQDN.
-- 
2.5.5

From 053dcbfd7e6fffcfeeda706fd9b7314616a0a56b Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Wed, 27 Apr 2016 14:44:17 +0200
Subject: [PATCH] DNS upgrade: change forwarding policy to = only for
 conflicting forward zones

This change is necessary to override automatic empty zone configuration
in latest BIND and bind-dyndb-ldap 9.0+.

This procedure is still not complete because we need to handle global
forwarders too (in LDAP and in named.conf on each server).

https://fedorahosted.org/freeipa/ticket/5710
---
 install/share/dns.ldif                         |  4 +-
 install/updates/90-post_upgrade_plugins.update |  3 +
 ipaserver/install/plugins/dns.py               | 79 ++++++++++++++++++++++++++
 3 files changed, 85 insertions(+), 1 deletion(-)

diff --git a/install/share/dns.ldif b/install/share/dns.ldif
index 1a6ce3ae10754fa41fe09fa68d8396f14dbb85d6..6cee478674af191350cf24e0aef74c5e418f392e 100644
--- a/install/share/dns.ldif
+++ b/install/share/dns.ldif
@@ -2,10 +2,12 @@ dn: cn=dns,$SUFFIX
 changetype: add
 objectClass: idnsConfigObject
 objectClass: nsContainer
+objectClass: ipaConfigObject
 objectClass: ipaDNSContainer
 objectClass: top
 cn: dns
-ipaDNSVersion: 1
+ipaConfigString: DNSVersion 1
+ipaDNSVersion: 2
 aci: (targetattr = "*")(version 3.0; acl "Allow read access"; allow (read,search,compare) groupdn = "ldap:///cn=Read DNS Entries,cn=permissions,cn=pbac,$SUFFIX" or userattr = "parent[0,1].managedby#GROUPDN";)
 aci: (target = "ldap:///idnsname=*,cn=dns,$SUFFIX";)(version 3.0;acl "Add DNS entries in a zone";allow (add) userattr = "parent[1].managedby#GROUPDN";)
 aci: (target = "ldap:///idnsname=*,cn=dns,$SUFFIX";)(version 3.0;acl "Remove DNS entries from a zone";allow (delete) userattr = "parent[1].managedby#GROUPDN";)
diff --git a/install/updates/90-post_upgrade_plugins.update b/install/updates/90-post_upgrade_plugins.update
index d8498edd4565fbad91ca5dea48e5bc2296e3fc25..ccb72efa4fa7af7fd0391b489d9c746332426fb9 100644
--- a/install/updates/90-post_upgrade_plugins.update
+++ b/install/updates/90-post_upgrade_plugins.update
@@ -17,7 +17,10 @@ plugin: update_service_principalalias
 plugin: update_upload_cacrt
 
 # last
+# DNS version 1
 plugin: update_master_to_dnsforwardzones
+# DNS version 2
+plugin: update_dnsforward_emptyzones
 plugin: update_managed_post
 plugin: update_managed_permissions
 plugin: update_read_replication_agreements_permission
diff --git a/ipaserver/install/plugins/dns.py b/ipaserver/install/plugins/dns.py
index 9f833303e43a247d1b6c17a019830d2deefc38f7..2f85f8d9daf423494789f675d333a49eb8269483 100644
--- a/ipaserver/install/plugins/dns.py
+++ b/ipaserver/install/plugins/dns.py
@@ -26,6 +26,7 @@ from ldif import LDIFWriter
 from ipalib import api, errors, util
 from ipalib import Updater
 from ipapython.dn import DN
+from ipapython import dnsutil
 from ipalib.plugins.dns import dns_container_exists
 from ipapython.ipa_log_manager import root_logger
 
@@ -398,3 +399,81 @@ class update_master_to_dnsforwardzones(DNSUpdater):
         return False, []
 
 api.register(update_master_to_dnsforwardzones)
+
+
+class update_dnsforward_emptyzones(DNSUpdater):
+    """
+    Migrate forward policies which conflict with automatic empty zones
+    (RFC 6303) to use forward policy = only.
+
+    BIND ignores conflicting forwarding configuration
+    when forwarding policy != only.
+    bind-dyndb-ldap 9.0+ will do the same so we have to adjust FreeIPA zones
+    accordingly.
+    """
+    backup_filename = u'dns-forwarding-empty-zones-%Y-%m-%d-%H-%M-%S.ldif'
+
+    def update_zones(self):
+        try:
+            fwzones = self.api.Command.dnsforwardzone_find(all=True,
+                                                           raw=True)['result']
+        except errors.NotFound:
+            # No forwardzones found, we are done
+            return
+
+        logged_once = False
+        for zone in fwzones:
+            if not (
+                dnsutil.related_to_auto_empty_zone(
+                    dnsutil.DNSName(zone.get('idnsname')[0]))
+                and zone.get('idnsforwardpolicy', [u'first'])[0] != u'only'
+                and zone.get('idnsforwarders', []) != []
+            ):
+                # this zone does not conflict with automatic empty zone
+                continue
+
+            if not logged_once:
+                self.log.info('Forward policy for zones conflicting with '
+                              'automatic empty zones will be changed to '
+                              '"only"')
+                logged_once = True
+
+            # backup
+            try:
+                self.backup_zone(zone)
+            except Exception:
+                self.log.error('Unable to create backup for zone %s, '
+                               'terminating zone upgrade', zone['idnsname'][0])
+                self.log.error(traceback.format_exc())
+                continue
+
+            # change forward policy
+            try:
+                self.api.Command['dnsforwardzone_mod'](
+                    zone['idnsname'][0],
+                    idnsforwardpolicy=u'only'
+                )
+            except Exception as e:
+                self.log.error('Forward policy update for zone %s failed '
+                               '(%s)' % (zone['idnsname'][0], e))
+                self.log.error(traceback.format_exc())
+                continue
+
+            self.log.debug('Zone %s was sucessfully modified to use '
+                           'forward policy "only"', zone['idnsname'][0])
+
+    def execute(self, **options):
+        # check LDAP if DNS subtree already uses new semantics
+        if not self.version_update_needed(target_version=2):
+            # forwardzones already use new semantics, no upgrade is required
+            return False, []
+
+        self.log.debug('Updating forwarding policies to avoid conflicts '
+                       'with automatic empty zones')
+        # update the DNSVersion, following upgrade can be executed only once
+        self.api.Command['dnsconfig_mod'](ipadnsversion=2)
+
+        self.update_zones()
+        return False, []
+
+api.register(update_dnsforward_emptyzones)
-- 
2.5.5

From 03dfc3cbf9af6b5240182ece2834847f0c4e8db0 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Wed, 27 Apr 2016 15:24:01 +0200
Subject: [PATCH] DNS upgrade: change global forwarding policy in LDAP to
 "only" if private IPs are used

This change is necessary to override automatic empty zone configuration
in latest BIND and bind-dyndb-ldap 9.0+.

This procedure is still not complete because we need to handle global
forwarders in named.conf too (independently on each server).

https://fedorahosted.org/freeipa/ticket/5710
---
 ipapython/dnsutil.py             | 18 ++++++++++++++++++
 ipaserver/install/plugins/dns.py | 16 ++++++++++++++++
 2 files changed, 34 insertions(+)

diff --git a/ipapython/dnsutil.py b/ipapython/dnsutil.py
index 98fcc1bfcb1d745a2f9e1b3165bec04f1d997680..f68d3c4105b13e5a8b1338da194612d51aae5331 100644
--- a/ipapython/dnsutil.py
+++ b/ipapython/dnsutil.py
@@ -264,6 +264,24 @@ def related_to_auto_empty_zone(name):
                for aez in EMPTY_ZONES)
 
 
+def has_empty_zone_addresses(hostname):
+    """Detect if given host is using IP address belonging to
+    an automatic empty zone.
+
+    Information from --ip-address option used in installed is lost by
+    the time when upgrade is run. Use IP addresses from DNS as best
+    approximation.
+
+    This is brain-dead and duplicates logic from DNS installer
+    but I did not find other way around.
+    """
+    ip_addresses = resolve_ip_addresses(hostname)
+    return any(
+        inside_auto_empty_zone(DNSName(ip.reverse_dns))
+        for ip in ip_addresses
+    )
+
+
 def resolve_rrsets(fqdn, rdtypes):
     """
     Get Resource Record sets for given FQDN.
diff --git a/ipaserver/install/plugins/dns.py b/ipaserver/install/plugins/dns.py
index 2f85f8d9daf423494789f675d333a49eb8269483..071a4e8a569c1f3abce900f3a182a42b95d4861d 100644
--- a/ipaserver/install/plugins/dns.py
+++ b/ipaserver/install/plugins/dns.py
@@ -462,6 +462,19 @@ class update_dnsforward_emptyzones(DNSUpdater):
             self.log.debug('Zone %s was sucessfully modified to use '
                            'forward policy "only"', zone['idnsname'][0])
 
+    def update_global_ldap_forwarder(self):
+        config = self.api.Command['dnsconfig_show'](all=True,
+                                                    raw=True)['result']
+        if (
+            config.get('idnsforwardpolicy', [u'first'])[0] == u'first'
+            and config.get('idnsforwarders', [])
+        ):
+            self.log.info('Global forward policy in LDAP for all servers will '
+                          'be changed to "only" to avoid conflicts with '
+                          'automatic empty zones')
+            self.backup_zone(config)
+            self.api.Command['dnsconfig_mod'](idnsforwardpolicy=u'only')
+
     def execute(self, **options):
         # check LDAP if DNS subtree already uses new semantics
         if not self.version_update_needed(target_version=2):
@@ -474,6 +487,9 @@ class update_dnsforward_emptyzones(DNSUpdater):
         self.api.Command['dnsconfig_mod'](ipadnsversion=2)
 
         self.update_zones()
+        if dnsutil.has_empty_zone_addresses(self.api.env.host):
+            self.update_global_ldap_forwarder()
+
         return False, []
 
 api.register(update_dnsforward_emptyzones)
-- 
2.5.5

From eca9aae8c4d6d965f3f9ff2c5a5f95e6fe2654f1 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Thu, 28 Apr 2016 22:19:03 +0200
Subject: [PATCH] DNS upgrade: change global forwarding policy in named.conf to
 "only" if private IPs are used

This change is necessary to override automatic empty zone configuration
in latest BIND and bind-dyndb-ldap 9.0+.

This upgrade has to be done on each IPA DNS server independently.

https://fedorahosted.org/freeipa/ticket/5710
---
 ipaserver/install/bindinstance.py   |  7 ++++++
 ipaserver/install/plugins/dns.py    |  7 +++---
 ipaserver/install/server/upgrade.py | 46 +++++++++++++++++++++++++++++++++++++
 3 files changed, 57 insertions(+), 3 deletions(-)

diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py
index ec8526a8e596c352999c450abe6c22ae03e66b4f..afcb6b0c10e54b1aae975fd1e81f37144e6b97ed 100644
--- a/ipaserver/install/bindinstance.py
+++ b/ipaserver/install/bindinstance.py
@@ -32,6 +32,7 @@ import six
 
 from ipaserver.install import installutils
 from ipaserver.install import service
+from ipaserver.install import sysupgrade
 from ipaserver.install.cainstance import IPA_CA_RECORD
 from ipapython import sysrestore, ipautil, ipaldap
 from ipapython import dnsutil
@@ -1038,6 +1039,12 @@ class BindInstance(service.Service):
                                      section=NAMED_SECTION_OPTIONS,
                                      str_val=False)
 
+        # prevent repeated upgrade on new installs
+        sysupgrade.set_upgrade_state(
+            'named.conf',
+            'forward_policy_conflict_with_empty_zones_handled', True
+        )
+
     def __setup_resolv_conf(self):
         if not self.fstore.has_file(RESOLV_CONF):
             self.fstore.backup_file(RESOLV_CONF)
diff --git a/ipaserver/install/plugins/dns.py b/ipaserver/install/plugins/dns.py
index 071a4e8a569c1f3abce900f3a182a42b95d4861d..17f3de9ddcc41cc70216d04536d1119b688efbfe 100644
--- a/ipaserver/install/plugins/dns.py
+++ b/ipaserver/install/plugins/dns.py
@@ -267,7 +267,8 @@ class update_master_to_dnsforwardzones(DNSUpdater):
     than none, will be tranformed to forward zones.
     Original masters zone will be backed up to ldif file.
 
-    This should be applied only once, and only if original version was lower than 4.0
+    This should be applied only once,
+    and only if original version was lower than 4.0
     """
     backup_filename = u'dns-master-to-forward-zones-%Y-%m-%d-%H-%M-%S.ldif'
 
@@ -481,8 +482,8 @@ class update_dnsforward_emptyzones(DNSUpdater):
             # forwardzones already use new semantics, no upgrade is required
             return False, []
 
-        self.log.debug('Updating forwarding policies to avoid conflicts '
-                       'with automatic empty zones')
+        self.log.debug('Updating forwarding policies in LDAP '
+                       'to avoid conflicts with automatic empty zones')
         # update the DNSVersion, following upgrade can be executed only once
         self.api.Command['dnsconfig_mod'](ipadnsversion=2)
 
diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
index 375d18522e9cfcf6d85c7a65b7be259bdd364134..92c153f9f2d1ed512fd7538cdb97a2533d528e1a 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -24,6 +24,7 @@ from ipapython import ipautil, sysrestore, version, certdb
 from ipapython import ipaldap
 from ipapython.ipa_log_manager import root_logger
 from ipapython import certmonger
+from ipapython import dnsutil
 from ipapython.dn import DN
 from ipaplatform.constants import constants
 from ipaplatform.paths import paths
@@ -776,6 +777,50 @@ def named_root_key_include():
     return True
 
 
+def named_update_global_forwarder_policy():
+    bind = bindinstance.BindInstance()
+    if not bindinstance.named_conf_exists() or not bind.is_configured():
+        # DNS service may not be configured
+        root_logger.info('DNS is not configured')
+        return False
+
+    root_logger.info('[Checking global forwarding policy in named.conf '
+                     'to avoid conflicts with automatic empty zones]')
+    if sysupgrade.get_upgrade_state(
+        'named.conf', 'forward_policy_conflict_with_empty_zones_handled'
+    ):
+        # upgrade was done already
+        return False
+
+    sysupgrade.set_upgrade_state(
+        'named.conf',
+        'forward_policy_conflict_with_empty_zones_handled',
+        True
+    )
+    if not dnsutil.has_empty_zone_addresses(api.env.host):
+        # guess: local server does not have IP addresses from private ranges
+        # so hopefully automatic empty zones are not a problem
+        return False
+
+    if bindinstance.named_conf_get_directive(
+            'forward',
+            section=bindinstance.NAMED_SECTION_OPTIONS,
+            str_val=False
+    ) == 'only':
+        return False
+
+    root_logger.info('Global forward policy in named.conf will '
+                     'be changed to "only" to avoid conflicts with '
+                     'automatic empty zones')
+    bindinstance.named_conf_set_directive(
+        'forward',
+        'only',
+        section=bindinstance.NAMED_SECTION_OPTIONS,
+        str_val=False
+    )
+    return True
+
+
 def certificate_renewal_update(ca, ds, http):
     """
     Update certmonger certificate renewal configuration.
@@ -1601,6 +1646,7 @@ def upgrade_configuration():
                           named_bindkey_file_option(),
                           named_managed_keys_dir_option(),
                           named_root_key_include(),
+                          named_update_global_forwarder_policy(),
                           mask_named_regular(),
                           fix_dyndb_ldap_workdir_permissions(),
                          )
-- 
2.5.5

From 9becab41308e19e15c20328b205a9fe176be095f Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Wed, 4 May 2016 10:30:18 +0200
Subject: [PATCH] DNS: Warn if forwarding policy conflicts with automatic empty
 zones

Forwarding policy "first" or "none" may conflicts with some automatic empty
zones. Queries for zones specified by RFC 6303 will ignore
forwarding and recursion and always result in NXDOMAIN answers.

This is not detected and warned about. Global forwarding is equivalent
to forward zone ".".

Example:
Forward zone 1.10.in-addr.arpa with policy "first"
will not forward anything because BIND will automatically prefer
automatic empty zone "10.in-addr.arpa." which is authoritative.

https://fedorahosted.org/freeipa/ticket/5710
---
 ipalib/messages.py    | 17 +++++++++++++++++
 ipalib/plugins/dns.py | 26 ++++++++++++++++++++++++++
 2 files changed, 43 insertions(+)

diff --git a/ipalib/messages.py b/ipalib/messages.py
index c37cceb3fce677c2ad55cfb81fb47c0bafefc8ea..dc5f9a3ab94c6fa3e6bbe16022fa5c9e1cb0112f 100644
--- a/ipalib/messages.py
+++ b/ipalib/messages.py
@@ -371,6 +371,23 @@ class FailedToRemoveHostDNSRecords(PublicMessage):
                "(%(reason)s)")
 
 
+class DNSForwardPolicyConflictWithEmptyZone(PublicMessage):
+    """
+    **13021** Forward zone 1.10.in-addr.arpa with policy "first"
+    will not forward anything because BIND automatically prefers
+    empty zone "10.in-addr.arpa.".
+    """
+
+    errno = 13021
+    type = "warning"
+    format = _(
+        "Forwarding policy conflicts with some automatic empty zones. "
+        "Queries for zones specified by RFC 6303 will ignore "
+        "forwarding and recursion and always result in NXDOMAIN answers. "
+        "To override this behavior use forward policy 'only'."
+    )
+
+
 def iter_messages(variables, base):
     """Return a tuple with all subclasses
     """
diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py
index 60ea2eb5b1790626fc1bd5a0fe9fc3a3aa5f9aea..d06feba942c262213d90a4a76f6347b5f5629560 100644
--- a/ipalib/plugins/dns.py
+++ b/ipalib/plugins/dns.py
@@ -67,6 +67,7 @@ from ipapython.dn import DN
 from ipapython.ipautil import CheckedIPAddress
 from ipapython.dnsutil import check_zone_overlap
 from ipapython.dnsutil import DNSName
+from ipapython.dnsutil import related_to_auto_empty_zone
 
 if six.PY3:
     unicode = str
@@ -1997,6 +1998,20 @@ def _add_warning_fw_zone_is_not_effective(api, result, fwzone, version):
         )
 
 
+def _add_warning_fw_policy_conflict_aez(result, fwzone, **options):
+    """Warn if forwarding policy conflicts with an automatic empty zone."""
+    fwd_policy = result['result'].get(u'idnsforwardpolicy',
+                                      dnsforwardzone.default_forward_policy)
+    if (
+        fwd_policy != [u'only']
+        and related_to_auto_empty_zone(DNSName(fwzone))
+    ):
+        messages.add_message(
+            options['version'], result,
+            messages.DNSForwardPolicyConflictWithEmptyZone()
+        )
+
+
 class DNSZoneBase(LDAPObject):
     """
     Base class for DNS Zone
@@ -4390,7 +4405,13 @@ class dnsconfig_mod(LDAPUpdate):
         result = super(dnsconfig_mod, self).execute(*keys, **options)
         self.obj.postprocess_result(result)
 
+        # this check makes sense only when resulting forwarders are non-empty
+        if result['result'].get('idnsforwarders'):
+            fwzone = DNSName('.')
+            _add_warning_fw_policy_conflict_aez(result, fwzone, **options)
+
         if forwarders:
+            # forwarders were changed
             for forwarder in forwarders:
                 try:
                     validate_dnssec_global_forwarder(forwarder, log=self.log)
@@ -4532,6 +4553,7 @@ class dnsforwardzone(DNSZoneBase):
                 )
             )
 
+
 @register()
 class dnsforwardzone_add(DNSZoneBase_add):
     __doc__ = _('Create new DNS forward zone.')
@@ -4562,8 +4584,10 @@ class dnsforwardzone_add(DNSZoneBase_add):
         return dn
 
     def execute(self, *keys, **options):
+        fwzone = keys[-1]
         result = super(dnsforwardzone_add, self).execute(*keys, **options)
         self.obj._warning_fw_zone_is_not_effective(result, *keys, **options)
+        _add_warning_fw_policy_conflict_aez(result, fwzone, **options)
         if options.get('idnsforwarders'):
             self.obj._warning_if_forwarders_do_not_work(
                 result, True, *keys, **options)
@@ -4619,7 +4643,9 @@ class dnsforwardzone_mod(DNSZoneBase_mod):
         return dn
 
     def execute(self, *keys, **options):
+        fwzone = keys[-1]
         result = super(dnsforwardzone_mod, self).execute(*keys, **options)
+        _add_warning_fw_policy_conflict_aez(result, fwzone, **options)
         if options.get('idnsforwarders'):
             self.obj._warning_if_forwarders_do_not_work(result, False, *keys,
                                                         **options)
-- 
2.5.5

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to