On 07/12/15 14:06, David Kupka wrote:
On 09/09/15 13:39, Petr Spacek wrote:
On 8.9.2015 16:30, David Kupka wrote:
On 28/08/15 13:36, Martin Basti wrote:


On 08/28/2015 10:03 AM, Petr Spacek wrote:
On 27.8.2015 14:22, David Kupka wrote:
@@ -2101,11 +2101,25 @@ class DNSZoneBase(LDAPObject):
   class DNSZoneBase_add(LDAPCreate):
+    takes_options = LDAPCreate.takes_options + (
+        Flag('force',
+             label=_('Force'),
+             doc=_('Force DNS zone creation.')
+        ),
+        Flag('skip_overlap_check',
+             doc=_('Force DNS zone creation even if it will overlap
with '
+                   'existing zone.')
+        ),
+    )
+
       has_output_params = LDAPCreate.has_output_params +
dnszone_output_params
       def pre_callback(self, ldap, dn, entry_attrs, attrs_list,
*keys, **options):
           assert isinstance(dn, DN)
+        if options['force']:
+            options['skip_overlap_check'] = True
+
           try:
               entry = ldap.get_entry(dn)
           except errors.NotFound:
@@ -2120,6 +2134,12 @@ class DNSZoneBase_add(LDAPCreate):
           entry_attrs['idnszoneactive'] = 'TRUE'
+        if not options['skip_overlap_check']:
+            try:
+                check_zone_overlap(keys[-1])
+            except RuntimeError as e:
+                raise errors.InvocationError(e.message)
+
           return dn
@@ -2673,9 +2693,9 @@ class dnszone_add(DNSZoneBase_add):
       __doc__ = _('Create new DNS zone (SOA record).')
       takes_options = DNSZoneBase_add.takes_options + (
-        Flag('force',
-             label=_('Force'),
-             doc=_('Force DNS zone creation even if nameserver is
not resolvable.'),
+        Flag('skip_nameserver_check',
+             doc=_('Force DNS zone creation even if nameserver is
not '
+                   'resolvable.')
           ),
           # Deprecated
@@ -2699,6 +2719,9 @@ class dnszone_add(DNSZoneBase_add):
       def pre_callback(self, ldap, dn, entry_attrs, attrs_list,
*keys, **options):
           assert isinstance(dn, DN)
+        if options['force']:
+            options['skip_nameserver_check'] = True
Why is it in DNSZoneBase_add.pre_callback?

Shouldn't the equation force = (skip_nameserver_check +
skip_nameserver_check)
be handled in parameter parsing & validation? (Again, I do not know
the IPA
framework :-))

+
           dn = super(dnszone_add, self).pre_callback(
               ldap, dn, entry_attrs, attrs_list, *keys, **options)
@@ -2713,7 +2736,7 @@ class dnszone_add(DNSZoneBase_add):
                       error=_("Nameserver for reverse zone cannot be
a relative DNS name"))
               # verify if user specified server is resolvable
-            if not options['force']:
+            if not options['skip_nameserver_check']:
                   check_ns_rec_resolvable(keys[0],
entry_attrs['idnssoamname'])
               # show warning about --name-server option
               context.show_warning_nameserver_option = True
diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index
d959bb369d946217acd080e78483cc9013dda4c7..18f477d4fb6620090b7073689c8df51b65a8307a


100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -924,6 +924,20 @@ def host_exists(host):
       else:
           return True
+def check_zone_overlap(zone):
+    if resolver.zone_for_name(zone) == zone:
+        try:
+            ns = [ans.to_text() for ans in resolver.query(zone,
'NS')]
+        except DNSException as e:
+            root_logger.debug("Failed to resolve nameserver(s) for
domain"
+                " {0}: {1}".format(zone, e))
+            ns = []
+
+        msg = u"DNS zone {0} already exists".format(zone)
Nitpick: I would say "already exists in DNS" to make it absolutely
clear. Just
'already exists' might be confusing because ipa dnszone-show will say
that the
zone does not exist ...

+        if ns:
+            msg += u" and is handled by server(s): {0}".format(',
'.join(ns))
+        raise RuntimeError(msg)
+
   def get_ipa_basedn(conn):
       """
       Get base DN of IPA suffix in given LDAP server.
0064
NACK

ipa-replica-install should have the --skip-overlap-check too, because
any replica can be the first DNS server.

Thanks for the catch, added.


0064+0058
Can be the options --allow-zone-overlap and --skip-overlap-check merged
into an one name, to have just one name for same thing?


Each option has bit different behavior:
The '--skip-overlap-check' option in API call prevent the check to be
performed and thus no error or warning is raised. This is the way
'--force'
option was originally working.

The '--allow-zone-overlap' options in installers do not skip the
check but
change the error to warning instead and let the installation continue.

If you think that this can confuse users we need to change the names
or even
the logic.

Updated patches attached.

Hello,

thank you very much for updating the patch.

Unfortunately it is not yet ready, but we are getting there.


* Serious problems:

a) ipa-server/replica/dns-install by default creates reverse zones
even if
these zones exist. The check which is done for main IPA domain should
be done
for all reverse zones, too. If a zone exists user should use
--no-reverse or
--allow-zone-overlap if necessary.

I believe that this is in scope of
https://fedorahosted.org/freeipa/ticket/3681 .


b) ipa-server/replica/dns-install by default always add DNS zone which
contains hostname of the IPA server, even if the IPA domain is
different and
even if the DNS zone already exists.

If the hostname of the server does not belong to IPA domain we should
not add
anything. Just check that the hostname is resolvable. Theoretically we
could
add an option which explicitly asks for creating zone which encloses the
hostnmame.

I believe that this is in scope of
https://fedorahosted.org/freeipa/ticket/5087 .


c) I did not realize that checks in ipa dns*zone-add will break zone
addition
for automatic empty zones.

There is list of special-case DNS zones which need special handling:
https://source.isc.org/cgi-bin/gitweb.cgi?p=bind9.git;a=blob;f=bin/named/server.c;h=1a25531df354f6f0593bfb86e5aa3e3c9b9c80e5;hb=HEAD#l272


Feel free to copy&paste list of the domains to IPA code - list comes from
RFCs, so it should be pretty stable. It would be good

If one of these zones is detected as "already existing" we need to
further
check values in SOA record and automatically override the check if:
(SOA mname = zone name) & (SOA rname = .)

In that case we should print informational message "automatic empty
zone will
be overridden by the zone you defined now".


d) ipa-server/replica/dns-install fails when attempt to resolve main IPA
domain name fails with DNS timeout. This might easily happen if the
domain
name is already delegated to IPA server which is being installed. In
that case
we should print a warning and allow to continue.

"""
Warning: DNS check for domain <IPA domain> failed with error <exception>.
Please make sure that the domain is properly delegated to this IPA
server.
"""


* Nitpicks - can be handled in separate patches:
1) Interactive ipa-server-install tells the user that the zone name he
entered
exists too late in the process.

I.e. user has to enter DM and admin password (twice!) and only after
that he
will receive an error, which may be frustrating :-)

Please move the check right after DNS zone name input so user does not
waste
his time and nerves :-) Even better, in an interactive mode it could make
sense to ask the user again if the previous input was not acceptable.


2) I found out that ipa-server-install prints message "Warning:
skipping DNS
resolution of host vm-206.abc.idm.lab.eng.brq.redhat.com" even if the
host
name does not belong to IPA domain. That is a mistake - the check
should be
skipped only in case when IPA server is part of the IPA domain.


3) When adding an forward as an override for automatic empty zones we
should
throw a warning if forward policy is not 'only'. It does not make
sense to use
policy 'forward' in these cases.
"""
Warning: It is strongly recommended to use forward policy 'only' for the
forward zones defined for private use.
"""


Hi!
I was finally able to get back to this patches. I've (hopefully)
addressed all the items from "Serious problems" section plus the first
"nitpick" as I found it really annoying.
Updated patches attached. Patch freeipa-dkupka-0070 is needed for this
to work.


Oh, stupid me, I just dropped is_host_resolvable() during patch rebase. Updated patch attached.

--
David Kupka
From 5355b86a1e21ebde4cc2c1e66367711ce9b82fcc Mon Sep 17 00:00:00 2001
From: David Kupka <dku...@redhat.com>
Date: Wed, 2 Dec 2015 13:17:13 +0000
Subject: [PATCH] dns: do not add (forward)zone if it is already resolvable.

Check if the zone user wants to add is already resolvable and refuse to
create it if yes. --skip-overlap-check and --force options suppress this check.

https://fedorahosted.org/freeipa/ticket/5087
---
 API.txt               |  7 ++--
 ipalib/plugins/dns.py | 33 ++++++++++++++---
 ipapython/ipautil.py  | 99 +++++++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 130 insertions(+), 9 deletions(-)

diff --git a/API.txt b/API.txt
index 60c98c31aa85d6c8879cd145f3d84188d4fea5b7..3a9fb65a386a2a6529b8cd241642446c135471f2 100644
--- a/API.txt
+++ b/API.txt
@@ -959,7 +959,7 @@ output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDA
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: dnsforwardzone_add
-args: 1,8,3
+args: 1,9,3
 arg: DNSNameParam('idnsname', attribute=True, cli_name='name', multivalue=False, only_absolute=True, primary_key=True, required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
@@ -968,6 +968,7 @@ option: StrEnum('idnsforwardpolicy', attribute=True, cli_name='forward_policy',
 option: Str('name_from_ip', attribute=False, cli_name='name_from_ip', multivalue=False, required=False)
 option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
 option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Flag('skip_overlap_check', autofill=True, default=False)
 option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
@@ -1366,7 +1367,7 @@ output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDA
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: dnszone_add
-args: 1,26,3
+args: 1,28,3
 arg: DNSNameParam('idnsname', attribute=True, cli_name='name', multivalue=False, only_absolute=True, primary_key=True, required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
@@ -1393,6 +1394,8 @@ option: Str('name_from_ip', attribute=False, cli_name='name_from_ip', multivalue
 option: Str('nsec3paramrecord', attribute=True, cli_name='nsec3param_rec', multivalue=False, pattern='^\\d+ \\d+ \\d+ (([0-9a-fA-F]{2})+|-)$', required=False)
 option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
 option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Flag('skip_nameserver_check', autofill=True, default=False)
+option: Flag('skip_overlap_check', autofill=True, default=False)
 option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py
index 67947360eb207de31ed114bb630705c409b2f9a9..c3d157d6951b1be84811012003bf29bb858949fa 100644
--- a/ipalib/plugins/dns.py
+++ b/ipalib/plugins/dns.py
@@ -53,8 +53,8 @@ from ipalib.util import (normalize_zonemgr,
                          validate_dnssec_zone_forwarder_step1,
                          validate_dnssec_zone_forwarder_step2,
                          verify_host_resolvable)
-
-from ipapython.ipautil import CheckedIPAddress
+from ipapython.ipautil import (CheckedIPAddress, is_host_resolvable,
+                               check_zone_overlap)
 from ipapython.dnsutil import DNSName
 
 if six.PY3:
@@ -2121,6 +2121,13 @@ class DNSZoneBase(LDAPObject):
 
 class DNSZoneBase_add(LDAPCreate):
 
+    takes_options = LDAPCreate.takes_options + (
+        Flag('skip_overlap_check',
+             doc=_('Force DNS zone creation even if it will overlap with '
+                   'an existing zone.')
+        ),
+    )
+
     has_output_params = LDAPCreate.has_output_params + dnszone_output_params
 
     def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
@@ -2140,6 +2147,12 @@ class DNSZoneBase_add(LDAPCreate):
 
         entry_attrs['idnszoneactive'] = 'TRUE'
 
+        if not options['skip_overlap_check']:
+            try:
+                check_zone_overlap(keys[-1])
+            except ValueError as e:
+                raise errors.InvocationError(e.message)
+
         return dn
 
 
@@ -2696,8 +2709,13 @@ class dnszone_add(DNSZoneBase_add):
 
     takes_options = DNSZoneBase_add.takes_options + (
         Flag('force',
-             label=_('Force'),
-             doc=_('Force DNS zone creation even if nameserver is not resolvable.'),
+            doc=_('Force DNS zone creation even if nameserver is not '
+                  'resolvable. (Deprecated)'),
+        ),
+
+        Flag('skip_nameserver_check',
+            doc=_('Force DNS zone creation even if nameserver is not '
+                  'resolvable.'),
         ),
 
         # Deprecated
@@ -2721,6 +2739,11 @@ class dnszone_add(DNSZoneBase_add):
     def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
         assert isinstance(dn, DN)
 
+        if options.get('force'):
+            if options.get('skip_nameserver_check'):
+                ("'force' is deprecated.")
+            options['skip_nameserver_check'] = True
+
         dn = super(dnszone_add, self).pre_callback(
             ldap, dn, entry_attrs, attrs_list, *keys, **options)
 
@@ -2736,7 +2759,7 @@ class dnszone_add(DNSZoneBase_add):
                     error=_("Nameserver for reverse zone cannot be a relative DNS name"))
 
             # verify if user specified server is resolvable
-            if not options['force']:
+            if not options['skip_nameserver_check']:
                 check_ns_rec_resolvable(keys[0], entry_attrs['idnssoamname'],
                                         self.log)
             # show warning about --name-server option
diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index 89047b2e8ea16d14a6634e551c49abe240c54009..326a1191d765130f85ffedff545b4b75fb213865 100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -39,7 +39,7 @@ import grp
 from contextlib import contextmanager
 
 from dns import resolver, rdatatype
-from dns.exception import DNSException
+from dns.exception import DNSException, Timeout
 import six
 from six.moves import input
 from six.moves import urllib
@@ -50,6 +50,7 @@ from ipapython import config
 from ipaplatform.paths import paths
 from ipapython.dn import DN
 from ipapython.dnsutil import DNSName
+from ipalib.util import normalize_zone
 
 SHARE_DIR = paths.USR_SHARE_IPA_DIR
 PLUGINS_SHARE_DIR = paths.IPA_PLUGINS
@@ -240,7 +241,6 @@ def template_file(infilename, vars):
     with open(infilename) as f:
         return template_str(f.read(), vars)
 
-
 def copy_template_file(infilename, outfilename, vars):
     """Copy a file, performing template substitutions"""
     txt = template_file(infilename, vars)
@@ -911,6 +911,18 @@ def bind_port_responder(port, socket_type=socket.SOCK_STREAM, socket_timeout=Non
     if s is None and last_socket_error is not None:
         raise last_socket_error # pylint: disable=E0702
 
+def is_host_resolvable(fqdn):
+    if not isinstance(fqdn, DNSName):
+        fqdn = DNSName(fqdn)
+    for rdtype in (rdatatype.A, rdatatype.AAAA):
+        try:
+            resolver.query(fqdn.make_absolute(), rdtype)
+        except DNSException:
+            continue
+        else:
+            return True
+
+    return False
 
 def host_exists(host):
     """
@@ -925,6 +937,89 @@ def host_exists(host):
     else:
         return True
 
+def check_zone_overlap(zone, raise_on_timeout=True):
+    if not isinstance(zone, DNSName):
+        zone = DNSName(normalize_zone(zone))
+
+    try:
+        containing_zone = resolver.zone_for_name(zone)
+    except Timeout as e:
+        msg = ("DNS check for domain %s failed: %s. Please make sure that the "
+               "domain is properly delegated to this IPA server." % (zone, e))
+        if raise_on_timeout:
+            raise ValueError(msg)
+        else:
+            root_logger.warning(msg)
+            return
+
+    if containing_zone == zone:
+        try:
+            ns = [ans.to_text() for ans in resolver.query(zone, 'NS')]
+        except DNSException as e:
+            root_logger.debug("Failed to resolve nameserver(s) for domain"
+                " {0}: {1}".format(zone, e))
+            ns = []
+
+        msg = u"DNS zone {0} already exists in DNS".format(zone)
+        if ns:
+            msg += u" and is handled by server(s): {0}".format(', '.join(ns))
+        raise ValueError(msg)
+
+def check_reverse_zone_overlap(zone, raise_on_timeout=True):
+    automatic_empty_zones = [
+        # RFC 1918
+        "10.IN-ADDR.ARPA", "16.172.IN-ADDR.ARPA", "17.172.IN-ADDR.ARPA",
+        "18.172.IN-ADDR.ARPA", "19.172.IN-ADDR.ARPA", "20.172.IN-ADDR.ARPA",
+        "21.172.IN-ADDR.ARPA", "22.172.IN-ADDR.ARPA", "23.172.IN-ADDR.ARPA",
+        "24.172.IN-ADDR.ARPA", "25.172.IN-ADDR.ARPA", "26.172.IN-ADDR.ARPA",
+        "27.172.IN-ADDR.ARPA", "28.172.IN-ADDR.ARPA", "29.172.IN-ADDR.ARPA",
+        "30.172.IN-ADDR.ARPA", "31.172.IN-ADDR.ARPA", "168.192.IN-ADDR.ARPA",
+        # RFC 6598
+        "64.100.IN-ADDR.ARPA", "65.100.IN-ADDR.ARPA", "66.100.IN-ADDR.ARPA",
+        "67.100.IN-ADDR.ARPA", "68.100.IN-ADDR.ARPA", "69.100.IN-ADDR.ARPA",
+        "70.100.IN-ADDR.ARPA", "71.100.IN-ADDR.ARPA", "72.100.IN-ADDR.ARPA",
+        "73.100.IN-ADDR.ARPA", "74.100.IN-ADDR.ARPA", "75.100.IN-ADDR.ARPA",
+        "76.100.IN-ADDR.ARPA", "77.100.IN-ADDR.ARPA", "78.100.IN-ADDR.ARPA",
+        "79.100.IN-ADDR.ARPA", "80.100.IN-ADDR.ARPA", "81.100.IN-ADDR.ARPA",
+        "82.100.IN-ADDR.ARPA", "83.100.IN-ADDR.ARPA", "84.100.IN-ADDR.ARPA",
+        "85.100.IN-ADDR.ARPA", "86.100.IN-ADDR.ARPA", "87.100.IN-ADDR.ARPA",
+        "88.100.IN-ADDR.ARPA", "89.100.IN-ADDR.ARPA", "90.100.IN-ADDR.ARPA",
+        "91.100.IN-ADDR.ARPA", "92.100.IN-ADDR.ARPA", "93.100.IN-ADDR.ARPA",
+        "94.100.IN-ADDR.ARPA", "95.100.IN-ADDR.ARPA", "96.100.IN-ADDR.ARPA",
+        "97.100.IN-ADDR.ARPA", "98.100.IN-ADDR.ARPA", "99.100.IN-ADDR.ARPA",
+        "100.100.IN-ADDR.ARPA", "101.100.IN-ADDR.ARPA",
+        "102.100.IN-ADDR.ARPA", "103.100.IN-ADDR.ARPA",
+        "104.100.IN-ADDR.ARPA", "105.100.IN-ADDR.ARPA",
+        "106.100.IN-ADDR.ARPA", "107.100.IN-ADDR.ARPA",
+        "108.100.IN-ADDR.ARPA", "109.100.IN-ADDR.ARPA",
+        "110.100.IN-ADDR.ARPA", "111.100.IN-ADDR.ARPA",
+        "112.100.IN-ADDR.ARPA", "113.100.IN-ADDR.ARPA",
+        "114.100.IN-ADDR.ARPA", "115.100.IN-ADDR.ARPA",
+        "116.100.IN-ADDR.ARPA", "117.100.IN-ADDR.ARPA",
+        "118.100.IN-ADDR.ARPA", "119.100.IN-ADDR.ARPA",
+        "120.100.IN-ADDR.ARPA", "121.100.IN-ADDR.ARPA",
+        "122.100.IN-ADDR.ARPA", "123.100.IN-ADDR.ARPA",
+        "124.100.IN-ADDR.ARPA", "125.100.IN-ADDR.ARPA",
+        "126.100.IN-ADDR.ARPA", "127.100.IN-ADDR.ARPA",
+        # RFC 5735 and RFC 5737
+        "0.IN-ADDR.ARPA", "127.IN-ADDR.ARPA", "254.169.IN-ADDR.ARPA",
+        "2.0.192.IN-ADDR.ARPA", "100.51.198.IN-ADDR.ARPA",
+        "113.0.203.IN-ADDR.ARPA", "255.255.255.255.IN-ADDR.ARPA",
+        # Local IPv6 Unicast Addresses
+        "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA",
+        "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA",
+        # LOCALLY ASSIGNED LOCAL ADDRESS SCOPE
+        "D.F.IP6.ARPA", "8.E.F.IP6.ARPA", "9.E.F.IP6.ARPA", "A.E.F.IP6.ARPA",
+        "B.E.F.IP6.ARPA",
+        # Example Prefix, RFC 3849.
+        "8.B.D.0.1.0.0.2.IP6.ARPA",
+        # RFC 7534
+        "EMPTY.AS112.ARPA",
+    ]
+    if zone in automatic_empty_zones:
+        return
+    check_zone_overlap(zone, raise_on_timeout=raise_on_timeout)
+
 def get_ipa_basedn(conn):
     """
     Get base DN of IPA suffix in given LDAP server.
-- 
2.5.0

From 17a36872e12b4ac5e9a7e21a375ed38e1ac7a441 Mon Sep 17 00:00:00 2001
From: David Kupka <dku...@redhat.com>
Date: Wed, 2 Dec 2015 14:20:50 +0000
Subject: [PATCH] dns: Check if domain already exists.

Raise an error when the domain already exists. This can be overriden using
--force or --allow-zone-overlap options.

https://fedorahosted.org/freeipa/ticket/3681
---
 install/tools/ipa-dns-install      |   3 +
 ipaserver/install/bindinstance.py  | 124 +++++++++++++++++++++++--------------
 ipaserver/install/dns.py           |  20 ++++++
 ipaserver/install/server/common.py |  18 ++++++
 4 files changed, 117 insertions(+), 48 deletions(-)

diff --git a/install/tools/ipa-dns-install b/install/tools/ipa-dns-install
index bdaffd30b2554c100dc29bd18e0474165d05c024..4fd75670f044a3ccf1e22463b5f2dd77515172d3 100755
--- a/install/tools/ipa-dns-install
+++ b/install/tools/ipa-dns-install
@@ -57,6 +57,9 @@ def parse_options():
                       help="The reverse DNS zone to use. This option can be used multiple times")
     parser.add_option("--no-reverse", dest="no_reverse", action="store_true",
                       default=False, help="Do not create new reverse DNS zone")
+    parser.add_option("--allow-zone-overlap", dest="allow_zone_overlap",
+                      action="store_true", default=False, help="Create DNS "
+                      "zone even if it already exists")
     parser.add_option("--no-dnssec-validation", dest="no_dnssec_validation", action="store_true",
                       default=False, help="Disable DNSSEC validation")
     parser.add_option("--dnssec-master", dest="dnssec_master", action="store_true",
diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py
index 6bfde83de600df43e3430299100565e554a80583..1576196f9bafba1f10b4942797422231ef3a92c6 100644
--- a/ipaserver/install/bindinstance.py
+++ b/ipaserver/install/bindinstance.py
@@ -278,15 +278,23 @@ def find_reverse_zone(ip_address, api=api):
     return None
 
 
-def read_reverse_zone(default, ip_address):
+def read_reverse_zone(default, ip_address, allow_zone_overlap=False):
     while True:
         zone = ipautil.user_input("Please specify the reverse zone name", default=default)
         if not zone:
             return None
-        if verify_reverse_zone(zone, ip_address):
-            break
-        else:
-            print("Invalid reverse zone %s for IP address %s" % (zone, ip_address))
+        if not verify_reverse_zone(zone, ip_address):
+            root_logger.error("Invalid reverse zone %s for IP address %s"
+                              % (zone, ip_address))
+            continue
+        if not allow_zone_overlap:
+            try:
+                ipautil.check_reverse_zone_overlap(zone, raise_on_timeout=False)
+            except ValueError as e:
+                root_logger.error("Reverse zone %s will not be used: %s"
+                                  % (zone, e))
+                continue
+        break
 
     return normalize_zone(zone)
 
@@ -407,43 +415,64 @@ def zonemgr_callback(option, opt_str, value, parser):
     parser.values.zonemgr = value
 
 def check_reverse_zones(ip_addresses, reverse_zones, options, unattended, search_reverse_zones=False):
-    reverse_asked = False
-
     ret_reverse_zones = []
-    # check that there is IP address in every reverse zone
-    if reverse_zones:
-        for rz in reverse_zones:
-            for ip in ip_addresses:
-                if verify_reverse_zone(rz, ip):
-                    ret_reverse_zones.append(normalize_zone(rz))
-                    break
-            else:
-                # no ip matching reverse zone found
-                sys.exit("There is no IP address matching reverse zone %s." % rz)
-    if not options.no_reverse:
-        # check that there is reverse zone for every IP
+
+    if not options.no_reverse and not reverse_zones:
+        if unattended:
+            options.no_reverse = True
+        else:
+            options.no_reverse = not create_reverse()
+
+    # shortcut
+    if options.no_reverse:
+        return ret_reverse_zones
+
+    # verify zones passed in options
+    for rz in reverse_zones:
+        # is there a IP for this zone
         for ip in ip_addresses:
-            if search_reverse_zones and find_reverse_zone(str(ip)):
-                # reverse zone is already in LDAP
-                continue
-            for rz in ret_reverse_zones:
-                if verify_reverse_zone(rz, ip):
-                    # reverse zone was entered by user
-                    break
+            if verify_reverse_zone(rz, ip):
+                break
+        else:
+            msg = ("Reverse zone %s will not be used: There is no IP "
+                  "address matching reverse zone %s." % (rz, rz))
+            if options.unattended:
+                sys.exit(msg)
             else:
-                # no reverse zone for ip found
-                if not reverse_asked:
-                    if not unattended and not reverse_zones:
-                        # user did not specify reverse_zone nor no_reverse
-                        options.no_reverse = not create_reverse()
-                        if options.no_reverse:
-                            # user decided not to create reverse zone
-                            return []
-                    reverse_asked = True
-                rz = get_reverse_zone_default(str(ip))
-                if not unattended:
-                    rz = read_reverse_zone(rz, str(ip))
-                ret_reverse_zones.append(rz)
+                root_logger.warning(msg)
+
+        # isn't the zone managed by someone else
+        if not options.allow_zone_overlap:
+            try:
+                ipautil.check_reverse_zone_overlap(rz)
+            except ValueError as e:
+                msg = "Reverse zone %s will not be used: %s" % (rz, e)
+                if options.unattended:
+                    sys.exit(msg)
+                else:
+                    root_logger.warning(msg)
+                continue
+
+        ret_reverse_zones.append(normalize_zone(rz))
+
+    # check that there is reverse zone for every IP
+    for ip in ip_addresses:
+        if search_reverse_zones and find_reverse_zone(str(ip)):
+            # reverse zone is already in LDAP
+            continue
+        for rz in ret_reverse_zones:
+            if verify_reverse_zone(rz, ip):
+                # reverse zone was entered by user
+                break
+        else:
+            # no reverse zone for ip found
+            if not unattended:
+                if ipautil.user_input("Do you want to create reverse zone for "
+                                      "IP %s" % ip, True):
+                    rz = get_reverse_zone_default(str(ip))
+                    rz = read_reverse_zone(rz, str(ip),
+                                           options.allow_zone_overlap)
+                    ret_reverse_zones.append(rz)
 
     return ret_reverse_zones
 
@@ -817,18 +846,17 @@ class BindInstance(service.Service):
                    api=self.api)
 
         if not dns_zone_exists(zone, self.api):
-            # add DNS domain for host first
-            root_logger.debug(
-                "Host domain (%s) is different from DNS domain (%s)!" % (
-                    zone, self.domain))
-            root_logger.debug("Add DNS zone for host first.")
-
-            add_zone(zone, self.zonemgr, dns_backup=self.dns_backup,
-                     ns_hostname=self.fqdn, force=True, api=self.api)
+            # check if master hostname is resolvable
+            if not ipautil.is_host_resolvable(fqdn):
+                root_logger.warning("Master FQDN (%s) is not resolvable.",
+                                    fqdn)
 
         # Add forward and reverse records to self
         for addr in addrs:
-            add_fwd_rr(zone, host, addr, api=self.api)
+            try:
+                add_fwd_rr(zone, host, addr, self.api)
+            except errors.NotFound as e:
+                pass
 
             reverse_zone = find_reverse_zone(addr, self.api)
             if reverse_zone:
diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py
index 258bf5dbe46e2167e07a62127c7fd8fd4be23ee6..276dae2778eb15e01f65f3ab05b4e4fc9bdcffe4 100644
--- a/ipaserver/install/dns.py
+++ b/ipaserver/install/dns.py
@@ -13,11 +13,13 @@ from subprocess import CalledProcessError
 
 from ipalib import api
 from ipalib import errors
+from ipalib import util
 from ipaplatform.paths import paths
 from ipaplatform.constants import constants
 from ipaplatform import services
 from ipapython import ipautil
 from ipapython import sysrestore
+from ipapython import dnsutil
 from ipapython.dn import DN
 from ipapython.ipa_log_manager import root_logger
 from ipapython.ipaldap import AUTOBIND_ENABLED
@@ -106,6 +108,24 @@ def install_check(standalone, replica, options, hostname):
         raise RuntimeError("Integrated DNS requires '%s' package" %
                            constants.IPA_DNS_PACKAGE_NAME)
 
+    domain = dnsutil.DNSName(util.normalize_zone(api.env.domain))
+    try:
+        ipautil.check_zone_overlap(domain, raise_on_timeout=False)
+    except ValueError as e:
+        if options.force or options.allow_zone_overlap:
+            root_logger.warning(e.message)
+        else:
+            raise e
+
+    for reverse_zone in options.reverse_zones:
+        try:
+            ipautil.check_reverse_zone_overlap(reverse_zone)
+        except ValueError as e:
+            if options.force or options.allow_zone_overlap:
+                root_logger.warning(e.message)
+            else:
+                raise e
+
     if standalone:
         print("==============================================================================")
         print("This program will setup DNS for the FreeIPA Server.")
diff --git a/ipaserver/install/server/common.py b/ipaserver/install/server/common.py
index 1c161120bcb22d384afcd9a5c462645cfcc753a8..2f76b70ed6c0afa9dc02b0175782efe255a0391c 100644
--- a/ipaserver/install/server/common.py
+++ b/ipaserver/install/server/common.py
@@ -10,6 +10,8 @@ from ipapython.install import common, core
 from ipapython.install.core import Knob
 from ipalib.util import validate_domain_name
 from ipaserver.install import bindinstance
+from ipapython.ipautil import check_reverse_zone_overlap, check_zone_overlap
+from ipapython.dnsutil import DNSName
 
 VALID_SUBJECT_ATTRS = ['st', 'o', 'ou', 'dnqualifier', 'c',
                        'serialnumber', 'l', 'title', 'sn', 'givenname',
@@ -171,6 +173,11 @@ class BaseServerDNS(common.Installable, core.Group, core.Composite):
         description="Do not add any DNS forwarders, use root servers instead",
     )
 
+    allow_zone_overlap = Knob(
+        bool, False,
+        description="Create DNS zone even if it already exists",
+    )
+
     reverse_zones = Knob(
         (list, str), [],
         description=("The reverse DNS zone to use. This option can be used "
@@ -179,6 +186,13 @@ class BaseServerDNS(common.Installable, core.Group, core.Composite):
         cli_metavar='REVERSE_ZONE',
     )
 
+    @reverse_zones.validator
+    def reverse_zones(self, values):
+        return
+        if not self.allow_zone_overlap:
+            for zone in values:
+                check_reverse_zone_overlap(zone)
+
     no_reverse = Knob(
         bool, False,
         description="Do not create new reverse DNS zone",
@@ -255,6 +269,9 @@ class BaseServer(common.Installable, common.Interactive, core.Composite):
     @domain_name.validator
     def domain_name(self, value):
         validate_domain_name(value)
+        return
+        if self.setup_dns and not self.dns.allow_zone_overlap: # pylint: disable=no-member
+            check_zone_overlap(value)
 
     dm_password = Knob(
         str, None,
@@ -452,6 +469,7 @@ class BaseServer(common.Installable, common.Interactive, core.Composite):
         self.no_forwarders = self.dns.no_forwarders
         self.reverse_zones = self.dns.reverse_zones
         self.no_reverse = self.dns.no_reverse
+        self.allow_zone_overlap = self.dns.allow_zone_overlap
         self.no_dnssec_validation = self.dns.no_dnssec_validation
         self.dnssec_master = self.dns.dnssec_master
         self.disable_dnssec_master = self.dns.disable_dnssec_master
-- 
2.5.0

-- 
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