On 09/25/2014 04:17 PM, David Kupka wrote:
On 09/24/2014 08:54 PM, Martin Basti wrote:
On 24/09/14 15:44, David Kupka wrote:
On 09/23/2014 08:25 PM, Martin Basti wrote:
On 23/09/14 13:23, David Kupka wrote:
On 09/18/2014 06:34 PM, Martin Basti wrote:
...
1)
+        if options.unattended:
+            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:
+                    rz = get_reverse_zone_default(str(ip))
+                    ret_reverse_zones.append(rz)
+        elif options.reverse_zones or create_reverse():
+            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:
+                    rz = get_reverse_zone_default(str(ip))
+                    rz = read_reverse_zone(rz, str(ip))
+                    ret_reverse_zones.append(rz)
+        else:
+            options.no_reverse = True
+            ret_reverse_zones = []

You can make it shorter without duplications:

# this ifs can be in one line
if not options.unatended:
     if not options.reverse_zones
         if not create_reverse():
             options.no_reverse=True
             return []

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:
             rz = get_reverse_zone_default(str(ip))
             if not options.unattended:
                 rz = read_reverse_zone(rz, str(ip))
             ret_reverse_zones.append(rz)


Thanks, I modified it bit different way to alse address recommendation
3).


2)
Typo?     There is no IP address matching reverze_zone %s."
---------------------------------------------^^


Thanks, fixed.


3)
Would be nice to ask user to create new zones only if new zones are
required. (If all required zones exist in LDAP, you ask user anyway)


I added one more variable and ask only once.

4)
Ask framework gurus, if installutils module is better place for
function
above



Petr^3 said that it's ok to have it in bindinstance.py.



NACK, most important point is 7

1)
I'm not sure if this is bug, but interactively is allowed to add only
one ip address

Unable to resolve IP address for host name
Please provide the IP address to be used for this host name:
2001:db8::2
The kerberos protocol requires a Realm name to be defined.


For the sake of infinite usability and UX I rewrote it to ask for
multiple addresses the same way as for DNS forwarders. Also I really
simplified IP address checking code when I was in it. I tested it but
please look at it carefully.
Also I found that ipa-dns-install and ipa-adtrust-install also accept
--ip-address param. So I modified ipa-dns-install in the same way as
ipa-server-install and ipa-replica-install. After discussion with
tbabej I decided to dont touch ipa-adtrust-install now as it do not
use specified value at all. I will remove the processing code and mark
the param as deprecated in separate patch.

2)
I'm getting error message

Invalid reverse zone 10.in-addr.arpa. for IP address
2001:db8::dead:beef
Invalid reverse zone 10.in-addr.arpa. for IP address
fed0:babe:baab:0:21a:4aff:fe10:4e18

  - or -

Do you want to configure the reverse zone? [yes]:
Please specify the reverse zone name
[0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.]:
Invalid reverse zone 0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa. for IP
address fed0:babe:baab:0:21a:4aff:fe10:4e18
Please specify the reverse zone name
[0.0.0.0.b.a.a.b.e.b.a.b.0.d.e.f.ip6.arpa.]:
Using reverse zone(s) 0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.,
0.0.0.0.b.a.a.b.e.b.a.b.0.d.e.f.ip6.arpa.

This shouldn't be there

Moved the message to function used when installation is attended by
user.


Could be better to ask user to specific zone for ip address a.b.c.d.

Probably, but lets leave some work for future.


4) just nitpick
The IPA Master Server will be configured with:
...
IP address(es): 2001:db8::dead:beef,
fed0:babe:baab:0:21a:4aff:fe10:4e18
...
Reverse zone:  0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.,
0.0.0.0.b.a.a.b.e.b.a.b.0.d.e.f.ip6.arpa.

You have label "IP address(es)", so you should use label "Reverse
zone(s)"


Fixed.

5)
ipa-server-install --ip-address=10.16.78.105
--reverse-zone=10.in-addr.arpa. --reverse-zone=16.10.in-addr.arpa.
--setup-dns

Creates both reverse zones, but 10.in-addr.arpa. is empty. I'm not sure
if this is wrong, but we prevents user to add zone without address in
it, so we should prevents, to add empty zone.


It would be nice but not in this patch.

6)
ipa-replica-prepare --ip-address 10.16.78.105 --ip-address
2001:db8::dead:beef --reverse-zone 1.0.0.2.ip6.arpa. vm-105.example.com
Directory Manager (existing master) password:

Invalid reverse zone 1.0.0.2.ip6.arpa. for IP address 10.16.78.105
Invalid reverse zone 1.0.0.2.ip6.arpa.

IMO This should work, right?

+                sys.exit("There is no IP address matching reverse zone
%s." % rz)
I expected at least this error to be shown.

Fixed, thanks.


7)
ipa-replica-prepare --ip-address 10.16.78.105 --ip-address
2001:db8::dead:beef vm-105.example.com
Directory Manager (existing master) password:

.......
Adding DNS records for vm-105.example.com
Values instance has no attribute 'ip_address'

Command returns the attribute error.
It fails with one --ip-address too.


Sorry, fixed.


*) Not related with your patch
Problem with installation:
I'm getting message:
IPA server is already configured on this system.
Even if I validation wasn't successful and installation was aborted.

IPA install detects previous installations by checking state file and
restore files. Function get_server_ip_address() stores some data and
hosts file and modify the host file, before user agreed installation.
This error was there before your patch.
https://fedorahosted.org/freeipa/ticket/4561




NACK

0)
# ipa-dns-install --ip-address 2001:db8::feed

2014-09-24T06:02:13Z DEBUG stderr=
2014-09-24T06:02:13Z DEBUG   File
"/usr/lib/python2.7/site-packages/ipaserver/install/installutils.py",
line 645, in run_script
     return_value = main_function()

   File "/sbin/ipa-dns-install", line 135, in main
     ip_addresses = get_server_ip_address(api.env.host, fstore,
options.unattended, options)

   File
"/usr/lib/python2.7/site-packages/ipaserver/install/installutils.py",
line 473, in get_server_ip_address
     if options.setup_dns:

2014-09-24T06:02:13Z DEBUG The ipa-dns-install command failed,
exception: AttributeError: Values instance has no attribute 'setup_dns'

Obviously there is no option --setup-dns in ipa-dns-install.
Fixed, modified get_server_ip_address function.


1)
# ipa-replica-prepare vm-073.example.com --ip-address
2620:52:0::fe10:4e18 --ip-address 10.16.78.73
Directory Manager (existing master) password:

Preparing replica for vm-073.example.com from vm-105.example.com
Creating SSL certificate for the Directory Server
Creating SSL certificate for the dogtag Directory Server
Saving dogtag Directory Server port
Creating SSL certificate for the Web Server
Exporting RA certificate
Copying additional files
Finalizing configuration
Packaging replica information into
/var/lib/ipa/replica-info-vm-073.example.com.gpg
Adding DNS records for vm-073.example.com
Values instance has no attribute 'unattended'

It should be unatended automatically, or we need add the --unattended
option to ipa-replica-prepare

ipa-replica-install is missing --unattended option. I fixed it in my
code for now but we should add it there.


2) This is nto user friendly, could be IP address check before
installation?
[root@vm-073 ~]# ipa-replica-install replica.file.gpg --ip-address
2620:52::fe10:4e18 --reverse-zone 10.in-addr.arpa.  --setup-dns
--no-forwarders
Directory Manager (existing master) password:

Run connection check to master
...
<long long list of succesfully configured services />
...
Restarting the KDC
There is no IP address matching reverse zone 10.in-addr.arpa..

LOG:
     return_value = main_function()

   File "/sbin/ipa-replica-install", line 721, in main
     install_bind(config, options)

   File "/sbin/ipa-replica-install", line 265, in install_bind
     reverse_zones =
bindinstance.check_reverse_zones(config.ip_addresses,
options.reverse_zones, options, True)

   File
"/usr/lib/python2.7/site-packages/ipaserver/install/bindinstance.py",
line 426, in check_reverse_zones
     sys.exit("There is no IP address matching reverse zone %s." % rz)

2014-09-24T06:39:23Z DEBUG The ipa-replica-install command failed,
exception: SystemExit: There is no IP address matching reverse zone
10.in-addr.arpa..

Fixed. Asking everything before actual installation.


3)
I'm not sure if sys.exit() is good, replica-install shoudl wrote
something about partially configured system

This is quite common in installation scripts. I moved this parts before
actual installation.


4) I'm not sure if this is the best place to ask about reverse zones
....
Done configuring the web interface (httpd).
Configuring ipa-otpd
   [1/2]: starting ipa-otpd
   [2/2]: configuring ipa-otpd to start on boot
Done configuring ipa-otpd.
Applying LDAP updates
Restarting the directory server
Restarting the KDC
Do you want to configure the reverse zone? [yes]:

Moved.


5) And error
# ipa-replica-install replica.file.gpg --ip-address 2620:52:xxxx
--setup-dns
....
Do you want to configure the reverse zone? [yes]:
Please specify the reverse zone name
[c.4.0.1.0.0.0.0.2.5.0.0.0.2.6.2.ip6.arpa.]:
Using reverse zone(s) c.4.0.1.0.0.0.0.2.5.0.0.0.2.6.2.ip6.arpa.

Your system may be partly configured.
Run /usr/sbin/ipa-server-install --uninstall to clean up.

Unexpected error - see /var/log/ipareplica-install.log for details:
AttributeError: 'str' object has no attribute 'version'

LOG:
2014-09-24T06:50:44Z DEBUG retrieving schema for SchemaCache
url=ldapi://%2fvar%2frun%2fslapd-IDM-LAB-BOS-REDHAT-COM.socket
conn=<ldap.ldapobject.SimpleLDAPObject instance at 0x46ba950>
2014-09-24T06:50:45Z DEBUG   File
"/usr/lib/python2.7/site-packages/ipaserver/install/installutils.py",
line 645, in run_script
     return_value = main_function()

   File "/sbin/ipa-replica-install", line 721, in main
     install_bind(config, options)

   File "/sbin/ipa-replica-install", line 272, in install_bind
     ca_configured=options.setup_ca)

   File
"/usr/lib/python2.7/site-packages/ipaserver/install/bindinstance.py",
line 550, in setup
     self.__setup_sub_dict()

   File
"/usr/lib/python2.7/site-packages/ipaserver/install/bindinstance.py",
line 651, in __setup_sub_dict
     if addr.version in (4, 6):

2014-09-24T06:50:45Z DEBUG The ipa-replica-install command failed,
exception: AttributeError: 'str' object has no attribute 'version'

Fixed. We are using IP addresses as a strings and as a CheckedIPAddress.
I swapped them here.


*) I don't like this asking to specify zone without IP
Do you want to configure the reverse zone? [yes]:
Please specify the reverse zone name
[0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.]:
Please specify the reverse zone name [78.16.10.in-addr.arpa.]:


Would be nice. Prefer to do it as a part of more powerfull reverse zone
validation logic.


**) I'm not sure how often this case can happen:
master and replica without DNS, you run --ipa-dns-install on master then
on replica, then replica DNS installation will not try to find existent
reverse zones in ldap, due configuration in ipa-dns-install.
Maybe you should detect if DNS container exists and then set flag
"search_reverse_zones=True"

ipa-dns-install:
+    reverse_zones = bindinstance.check_reverse_zones(ip_addresses,
options.reverse_zones, options)

Same as above.





_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel


Rebased to current master and ipa-4-1. Removed unintentional modification of ipa-adtrust-install.

--
David Kupka
From 9b1ccad7108c6ca9b3deca151a9f20437416b02f Mon Sep 17 00:00:00 2001
From: David Kupka <dku...@redhat.com>
Date: Wed, 27 Aug 2014 13:50:21 +0200
Subject: [PATCH] Detect and configure all usable IP addresses.

Find, verify and configure all IP addresses that can be used to reach the server
FreeIPA is being installed on. Ignore some IP address only if user specifies
subset of detected addresses using --ip-address option.
This change simplyfies FreeIPA installation on multihomed and dual-stacked servers.

https://fedorahosted.org/freeipa/ticket/3575
---
 install/tools/ipa-dns-install            |  71 ++++-------------
 install/tools/ipa-replica-install        |  59 +++++++-------
 install/tools/ipa-server-install         |  54 ++++++-------
 ipaserver/install/bindinstance.py        | 110 ++++++++++++++++++-------
 ipaserver/install/installutils.py        | 133 ++++++++++++++++---------------
 ipaserver/install/ipa_replica_prepare.py |  82 ++++++++++---------
 6 files changed, 257 insertions(+), 252 deletions(-)

diff --git a/install/tools/ipa-dns-install b/install/tools/ipa-dns-install
index c9ea63ce3ce719324737c94fc35996a294cbb10f..399cdd4d3a18bada336b3cbf1473a46f7a00ea90 100755
--- a/install/tools/ipa-dns-install
+++ b/install/tools/ipa-dns-install
@@ -42,13 +42,16 @@ def parse_options():
                       sensitive=True, help="admin password")
     parser.add_option("-d", "--debug", dest="debug", action="store_true",
                       default=False, help="print debugging information")
-    parser.add_option("--ip-address", dest="ip_address",
+    parser.add_option("--ip-address", dest="ip_addresses",
+                      default=[], action="append",
                       type="ip", ip_local=True, help="Master Server IP Address")
     parser.add_option("--forwarder", dest="forwarders", action="append",
                       type="ip", help="Add a DNS forwarder")
     parser.add_option("--no-forwarders", dest="no_forwarders", action="store_true",
                       default=False, help="Do not add any DNS forwarders, use root servers instead")
-    parser.add_option("--reverse-zone", dest="reverse_zone", help="The reverse DNS zone to use")
+    parser.add_option("--reverse-zone", dest="reverse_zones",
+                      default=[], action="append",
+                      help="The reverse DNS zone to use")
     parser.add_option("--no-reverse", dest="no_reverse", action="store_true",
                       default=False, help="Do not create new reverse DNS zone")
     parser.add_option("--zonemgr", action="callback", callback=bindinstance.zonemgr_callback,
@@ -62,7 +65,7 @@ def parse_options():
 
     if options.forwarders and options.no_forwarders:
         parser.error("You cannot specify a --forwarder option together with --no-forwarders")
-    elif options.reverse_zone and options.no_reverse:
+    elif options.reverse_zones and options.no_reverse:
         parser.error("You cannot specify a --reverse-zone option together with --no-reverse")
 
     if options.unattended:
@@ -130,46 +133,8 @@ def main():
     except errors.ACIError:
         sys.exit("Password is not valid!")
 
-    # Check we have a public IP that is associated with the hostname
-    if options.ip_address:
-        ip = options.ip_address
-    else:
-        hostaddr = resolve_host(api.env.host)
-        try:
-            if len(hostaddr) > 1:
-                print >> sys.stderr, "The server hostname resolves to more than one address:"
-                for addr in hostaddr:
-                    print >> sys.stderr, "  %s" % addr
-
-                if options.ip_address:
-                    if str(options.ip_address) not in hostaddr:
-                        print >> sys.stderr, "Address passed in --ip-address did not match any resolved"
-                        print >> sys.stderr, "address!"
-                        sys.exit(1)
-                    print "Selected IP address:", str(options.ip_address)
-                    ip = options.ip_address
-                else:
-                    if options.unattended:
-                        print >> sys.stderr, "Please use --ip-address option to specify the address"
-                        sys.exit(1)
-                    else:
-                        ip = read_ip_address(api.env.host, fstore)
-            else:
-                ip = hostaddr and ipautil.CheckedIPAddress(hostaddr[0], match_local=True)
-        except Exception, e:
-            print "Error: Invalid IP Address %s: %s" % (ip, e)
-            ip = None
-
-    if not ip:
-        if options.unattended:
-            sys.exit("Unable to resolve IP address for host name")
-        else:
-            ip = read_ip_address(api.env.host, fstore)
-    ip_address = str(ip)
-    root_logger.debug("will use ip_address: %s\n", ip_address)
-
-    if options.reverse_zone and not bindinstance.verify_reverse_zone(options.reverse_zone, ip):
-        sys.exit(1)
+    ip_addresses = get_server_ip_address(api.env.host, fstore,
+        options.unattended, True, options.ip_addresses)
 
     if options.no_forwarders:
         dns_forwarders = ()
@@ -186,19 +151,11 @@ def main():
         ccache = krbV.default_context().default_ccache()
         api.Backend.ldap2.connect(ccache)
 
-    if options.reverse_zone:
-        reverse_zone = bindinstance.normalize_zone(options.reverse_zone)
-    else:
-        reverse_zone = bindinstance.find_reverse_zone(ip)
-        if reverse_zone is None and not options.no_reverse:
-            if options.unattended:
-                reverse_zone = util.get_reverse_zone_default(ip)
-            elif bindinstance.create_reverse():
-                reverse_zone = util.get_reverse_zone_default(ip)
-                reverse_zone = bindinstance.read_reverse_zone(reverse_zone, ip)
+    reverse_zones = bindinstance.check_reverse_zones(ip_addresses,
+        options.reverse_zones, options, options.unattended)
 
-    if reverse_zone is not None:
-        print "Using reverse zone %s" % reverse_zone
+    if reverse_zones is not None:
+        print "Using reverse zone %s" % ', '.join(reverse_zones)
 
     conf_ntp = ntpinstance.NTPInstance(fstore).is_enabled()
 
@@ -208,8 +165,8 @@ def main():
         print "Please wait until the prompt is returned."
         print ""
 
-    bind.setup(api.env.host, ip_address, api.env.realm, api.env.domain,
-               dns_forwarders, conf_ntp, reverse_zone, zonemgr=options.zonemgr)
+    bind.setup(api.env.host, ip_addresses, api.env.realm, api.env.domain,
+               dns_forwarders, conf_ntp, reverse_zones, zonemgr=options.zonemgr)
     bind.create_instance()
 
     # Restart http instance to make sure that python-dns has the right resolver
diff --git a/install/tools/ipa-replica-install b/install/tools/ipa-replica-install
index 2986685d0975024428480d191cb0188d4a499f02..167f68dd166b2445f66d794478d9b1f9dd6fbe5f 100755
--- a/install/tools/ipa-replica-install
+++ b/install/tools/ipa-replica-install
@@ -65,10 +65,8 @@ def parse_options():
     basic_group = OptionGroup(parser, "basic options")
     basic_group.add_option("--setup-ca", dest="setup_ca", action="store_true",
                       default=False, help="configure a dogtag CA")
-    basic_group.add_option("--setup-kra", dest="setup_kra", action="store_true",
-                      default=False, help="configure a dogtag KRA")
-    basic_group.add_option("--ip-address", dest="ip_address",
-                      type="ip", ip_local=True,
+    basic_group.add_option("--ip-address", dest="ip_addresses",
+                      type="ip", ip_local=True, action="append", default=[],
                       help="Replica server IP Address")
     basic_group.add_option("-p", "--password", dest="password", sensitive=True,
                       help="Directory Manager (existing master) password")
@@ -112,7 +110,8 @@ def parse_options():
                       type="ip", help="Add a DNS forwarder")
     dns_group.add_option("--no-forwarders", dest="no_forwarders", action="store_true",
                       default=False, help="Do not add any DNS forwarders, use root servers instead")
-    dns_group.add_option("--reverse-zone", dest="reverse_zone", help="The reverse DNS zone to use")
+    dns_group.add_option("--reverse-zone", dest="reverse_zones", default=[],
+                         action="append", help="The reverse DNS zone to use")
     dns_group.add_option("--no-reverse", dest="no_reverse", action="store_true",
                       default=False, help="Do not create new reverse DNS zone")
     dns_group.add_option("--no-host-dns", dest="no_host_dns", action="store_true",
@@ -133,7 +132,7 @@ def parse_options():
             parser.error("You cannot specify a --forwarder option without the --setup-dns option")
         if options.no_forwarders:
             parser.error("You cannot specify a --no-forwarders option without the --setup-dns option")
-        if options.reverse_zone:
+        if options.reverse_zones:
             parser.error("You cannot specify a --reverse-zone option without the --setup-dns option")
         if options.no_reverse:
             parser.error("You cannot specify a --no-reverse option without the --setup-dns option")
@@ -141,7 +140,7 @@ def parse_options():
         parser.error("You cannot specify a --forwarder option together with --no-forwarders")
     elif not options.forwarders and not options.no_forwarders:
         parser.error("You must specify at least one --forwarder option or --no-forwarders option")
-    elif options.reverse_zone and options.no_reverse:
+    elif options.reverse_zones and options.no_reverse:
         parser.error("You cannot specify a --reverse-zone option together with --no-reverse")
 
     return safe_options, options, args[0]
@@ -264,23 +263,9 @@ def install_bind(config, options):
         forwarders = ()
     bind = bindinstance.BindInstance(dm_password=config.dirman_password)
 
-    if options.reverse_zone:
-        if not bindinstance.verify_reverse_zone(options.reverse_zone, config.ip):
-            sys.exit(1)
-        reverse_zone = bindinstance.normalize_zone(options.reverse_zone)
-    else:
-        reverse_zone = bindinstance.find_reverse_zone(config.ip)
-        if reverse_zone is None and not options.no_reverse:
-            reverse_zone = util.get_reverse_zone_default(config.ip)
-            if not options.unattended and bindinstance.create_reverse():
-                reverse_zone = bindinstance.read_reverse_zone(reverse_zone, config.ip)
-
-    if reverse_zone is not None:
-        print "Using reverse zone %s" % reverse_zone
-
-    bind.setup(config.host_name, config.ip_address, config.realm_name,
-               config.domain_name, forwarders, options.conf_ntp, reverse_zone,
-               ca_configured=options.setup_ca)
+    bind.setup(config.host_name, config.ips, config.realm_name,
+               config.domain_name, forwarders, options.conf_ntp,
+               config.reverse_zones, ca_configured=options.setup_ca)
     bind.create_instance()
 
     print ""
@@ -326,12 +311,16 @@ def install_dns_records(config, options):
             config.master_host_name, config.dirman_password):
         try:
             bind = bindinstance.BindInstance(dm_password=config.dirman_password)
-            reverse_zone = bindinstance.find_reverse_zone(config.ip)
+            for ip, ip_address in zip(config.ips, config.ip_addresses):
+                reverse_zone = bindinstance.find_reverse_zone(ip)
 
-            bind.add_master_dns_records(config.host_name, config.ip_address,
-                                        config.realm_name, config.domain_name,
-                                        reverse_zone, options.conf_ntp,
-                                        options.setup_ca)
+                bind.add_master_dns_records(config.host_name,
+                                            ip_address,
+                                            config.realm_name,
+                                            config.domain_name,
+                                            reverse_zone,
+                                            options.conf_ntp,
+                                            options.setup_ca)
         except errors.NotFound, e:
             root_logger.debug('Replica DNS records could not be added '
                               'on master: %s', str(e))
@@ -534,8 +523,16 @@ def main():
 
 
     # check replica host IP resolution
-    config.ip = installutils.get_server_ip_address(config.host_name, fstore, True, options)
-    config.ip_address = str(config.ip)
+    config.ips = installutils.get_server_ip_address(config.host_name, fstore,
+        options.unattended, options.setup_dns, options.ip_addresses)
+    config.ip_addresses = [str(ip) for ip in config.ips]
+
+    config.reverse_zones = bindinstance.check_reverse_zones(
+        config.ip_addresses, options.reverse_zones, options, True)
+
+    if config.reverse_zones is not None:
+        print "Using reverse zone(s) %s" % ', '.join(config.reverse_zones)
+
 
     # Create the management framework config file
     # Note: We must do this before bootstraping and finalizing ipalib.api
diff --git a/install/tools/ipa-server-install b/install/tools/ipa-server-install
index 86422e3322068dd80f37a4b5dc6dde77dfb57387..533023f2e2f72d0b05616303740656fa8b38331f 100755
--- a/install/tools/ipa-server-install
+++ b/install/tools/ipa-server-install
@@ -175,8 +175,8 @@ def parse_options():
                            help="create home directories for users "
                                 "on their first login")
     basic_group.add_option("--hostname", dest="host_name", help="fully qualified name of server")
-    basic_group.add_option("--ip-address", dest="ip_address",
-                      type="ip", ip_local=True,
+    basic_group.add_option("--ip-address", dest="ip_addresses",
+                      type="ip", ip_local=True, action="append", default=[],
                       help="Master Server IP Address")
     basic_group.add_option("-N", "--no-ntp", dest="conf_ntp", action="store_false",
                       help="do not configure ntp", default=True)
@@ -236,7 +236,8 @@ def parse_options():
                       type="ip", help="Add a DNS forwarder")
     dns_group.add_option("--no-forwarders", dest="no_forwarders", action="store_true",
                       default=False, help="Do not add any DNS forwarders, use root servers instead")
-    dns_group.add_option("--reverse-zone", dest="reverse_zone", help="The reverse DNS zone to use")
+    dns_group.add_option("--reverse-zone", dest="reverse_zones", help="The reverse DNS zone to use",
+                      action="append", default=[])
     dns_group.add_option("--no-reverse", dest="no_reverse", action="store_true",
                       default=False, help="Do not create reverse DNS zone")
     dns_group.add_option("--zonemgr", action="callback", callback=bindinstance.zonemgr_callback,
@@ -280,13 +281,13 @@ def parse_options():
             parser.error("You cannot specify a --forwarder option without the --setup-dns option")
         if options.no_forwarders:
             parser.error("You cannot specify a --no-forwarders option without the --setup-dns option")
-        if options.reverse_zone:
+        if options.reverse_zones:
             parser.error("You cannot specify a --reverse-zone option without the --setup-dns option")
         if options.no_reverse:
             parser.error("You cannot specify a --no-reverse option without the --setup-dns option")
     elif options.forwarders and options.no_forwarders:
         parser.error("You cannot specify a --forwarder option together with --no-forwarders")
-    elif options.reverse_zone and options.no_reverse:
+    elif options.reverse_zones and options.no_reverse:
         parser.error("You cannot specify a --reverse-zone option together with --no-reverse")
 
     if options.uninstall:
@@ -829,11 +830,11 @@ def main():
     realm_name = ""
     host_name = ""
     domain_name = ""
-    ip_address = ""
+    ip_addresses = []
     master_password = ""
     dm_password = ""
     admin_password = ""
-    reverse_zone = None
+    reverse_zones = []
 
     if not options.setup_dns and not options.unattended:
         if ipautil.user_input("Do you want to configure integrated DNS (BIND)?", False):
@@ -892,11 +893,8 @@ def main():
 
     domain_name = domain_name.lower()
 
-    ip = get_server_ip_address(host_name, fstore, options.unattended, options)
-    ip_address = str(ip)
-
-    if options.reverse_zone and not bindinstance.verify_reverse_zone(options.reverse_zone, ip):
-        sys.exit(1)
+    ip_addresses = get_server_ip_address(host_name, fstore,
+        options.unattended, options.setup_dns, options.ip_addresses)
 
     if not options.realm_name:
         realm_name = read_realm_name(domain_name, options.unattended)
@@ -973,35 +971,29 @@ def main():
         else:
             dns_forwarders = read_dns_forwarders()
 
-        if options.reverse_zone:
-            reverse_zone = bindinstance.normalize_zone(options.reverse_zone)
-        elif not options.no_reverse:
-            if options.unattended:
-                reverse_zone = util.get_reverse_zone_default(ip)
-            elif bindinstance.create_reverse():
-                reverse_zone = util.get_reverse_zone_default(ip)
-                reverse_zone = bindinstance.read_reverse_zone(reverse_zone, ip)
+        reverse_zones = bindinstance.check_reverse_zones(ip_addresses,
+            options.reverse_zones, options, options.unattended)
 
-        if reverse_zone is not None:
-            print "Using reverse zone %s" % reverse_zone
+        if reverse_zones:
+            print "Using reverse zone(s) %s" % ", ".join(str(rz) for rz in reverse_zones)
     else:
         dns_forwarders = ()
     root_logger.debug("will use dns_forwarders: %s\n" % str(dns_forwarders))
 
     print
     print "The IPA Master Server will be configured with:"
-    print "Hostname:      %s" % host_name
-    print "IP address:    %s" % ip_address
-    print "Domain name:   %s" % domain_name
-    print "Realm name:    %s" % realm_name
+    print "Hostname:       %s" % host_name
+    print "IP address(es): %s" % ", ".join(str(ip) for ip in ip_addresses)
+    print "Domain name:    %s" % domain_name
+    print "Realm name:     %s" % realm_name
     print
 
     if options.setup_dns:
         print "BIND DNS server will be configured to serve IPA domain with:"
         print "Forwarders:    %s" % ("No forwarders" if not dns_forwarders \
                 else ", ".join([str(ip) for ip in dns_forwarders]))
-        print "Reverse zone:  %s" % ("No reverse zone" if options.no_reverse \
-                or reverse_zone is None else reverse_zone)
+        print "Reverse zone(s):  %s" % ("No reverse zone" if options.no_reverse \
+                or reverse_zones is None else ", ".join(str(rz) for rz in reverse_zones))
         print
 
     # If domain name and realm does not match, IPA server will not be able
@@ -1112,7 +1104,7 @@ def main():
             options.host_name = host_name
             options.unattended = True
             options.forwarders = dns_forwarders
-            options.reverse_zone = reverse_zone
+            options.reverse_zones = reverse_zones
             write_cache(vars(options))
             ca.configure_instance(host_name, domain_name, dm_password,
                                   dm_password, csr_file=paths.ROOT_IPA_CSR,
@@ -1206,8 +1198,8 @@ def main():
 
     # Create a BIND instance
     bind = bindinstance.BindInstance(fstore, dm_password)
-    bind.setup(host_name, ip_address, realm_name, domain_name, dns_forwarders,
-               options.conf_ntp, reverse_zone, zonemgr=options.zonemgr,
+    bind.setup(host_name, ip_addresses, realm_name, domain_name, dns_forwarders,
+               options.conf_ntp, reverse_zones, zonemgr=options.zonemgr,
                ca_configured=setup_ca)
     if options.setup_dns:
         api.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=dm_password)
diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py
index 09760d66700390a5609f7a0504d9b2cfec91d268..d55551987a1582a61116ac79051b5dea3486b38b 100644
--- a/ipaserver/install/bindinstance.py
+++ b/ipaserver/install/bindinstance.py
@@ -22,6 +22,7 @@ import os
 import pwd
 import netaddr
 import re
+import sys
 
 import ldap
 
@@ -265,7 +266,6 @@ def verify_reverse_zone(zone, ip_address):
     try:
         get_reverse_record_name(zone, ip_address)
     except ValueError:
-        print "Invalid reverse zone %s" % zone
         return False
 
     return True
@@ -291,6 +291,8 @@ def read_reverse_zone(default, ip_address):
             return None
         if verify_reverse_zone(zone, ip_address):
             break
+        else:
+            print "Invalid reverse zone %s for IP address %s" % (zone, ip_address)
 
     return normalize_zone(zone)
 
@@ -393,6 +395,48 @@ 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
+        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 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)
+
+    return ret_reverse_zones
+
+
 class DnsBackup(object):
     def __init__(self, service):
         self.service = service
@@ -452,11 +496,11 @@ class BindInstance(service.Service):
         self.named_user = None
         self.domain = None
         self.host = None
-        self.ip_address = None
+        self.ip_addresses = []
         self.realm = None
         self.forwarders = None
         self.sub_dict = None
-        self.reverse_zone = None
+        self.reverse_zones = []
         self.dm_password = dm_password
 
         if fstore:
@@ -466,19 +510,19 @@ class BindInstance(service.Service):
 
     suffix = ipautil.dn_attribute_property('_suffix')
 
-    def setup(self, fqdn, ip_address, realm_name, domain_name, forwarders, ntp,
-              reverse_zone, named_user="named", zonemgr=None,
+    def setup(self, fqdn, ip_addresses, realm_name, domain_name, forwarders, ntp,
+              reverse_zones, named_user="named", zonemgr=None,
               ca_configured=None):
         self.named_user = named_user
         self.fqdn = fqdn
-        self.ip_address = ip_address
+        self.ip_addresses = ip_addresses
         self.realm = realm_name
         self.domain = domain_name
         self.forwarders = forwarders
         self.host = fqdn.split(".")[0]
         self.suffix = ipautil.realm_to_suffix(self.realm)
         self.ntp = ntp
-        self.reverse_zone = reverse_zone
+        self.reverse_zones = reverse_zones
         self.ca_configured = ca_configured
 
         if not zonemgr:
@@ -524,8 +568,9 @@ class BindInstance(service.Service):
         # get a connection to the DS
         self.ldap_connect()
 
-        if installutils.record_in_hosts(self.ip_address, self.fqdn) is None:
-            installutils.add_record_to_hosts(self.ip_address, self.fqdn)
+        for ip_address in self.ip_addresses:
+            if installutils.record_in_hosts(str(ip_address), self.fqdn) is None:
+                installutils.add_record_to_hosts(str(ip_address), self.fqdn)
 
         # Make sure generate-rndc-key.sh runs before named restart
         self.step("generating rndc key file", self.__generate_rndc_key)
@@ -535,8 +580,7 @@ class BindInstance(service.Service):
 
         if not dns_zone_exists(self.domain):
             self.step("setting up our zone", self.__setup_zone)
-
-        if self.reverse_zone is not None:
+        if self.reverse_zones:
             self.step("setting up reverse zone", self.__setup_reverse_zone)
 
         self.step("setting up our own record", self.__add_self)
@@ -589,18 +633,17 @@ class BindInstance(service.Service):
         else:
             optional_ntp = ""
 
-        addr = netaddr.IPAddress(self.ip_address)
-        if addr.version in (4, 6):
-            ipa_ca = "%s\t\t\tIN %s\t\t\t%s\n" % (
-                IPA_CA_RECORD,
-                "A" if addr.version == 4 else "AAAA",
-                self.ip_address)
-        else:
-            ipa_ca = ""
+        ipa_ca = ""
+        for addr in self.ip_addresses:
+            if addr.version in (4, 6):
+                ipa_ca += "%s\t\t\tIN %s\t\t\t%s\n" % (
+                    IPA_CA_RECORD,
+                    "A" if addr.version == 4 else "AAAA",
+                    str(addr))
 
         self.sub_dict = dict(
             FQDN=self.fqdn,
-            IP=self.ip_address,
+            IP=[str(ip) for ip in self.ip_addresses],
             DOMAIN=self.domain,
             HOST=self.host,
             REALM=self.realm,
@@ -633,7 +676,8 @@ class BindInstance(service.Service):
 
     def __setup_reverse_zone(self):
         # Always use force=True as named is not set up yet
-        add_zone(self.reverse_zone, self.zonemgr, ns_hostname=api.env.host,
+        for reverse_zone in self.reverse_zones:
+            add_zone(reverse_zone, self.zonemgr, ns_hostname=api.env.host,
                 dns_backup=self.dns_backup, force=True)
 
     def __add_master_records(self, fqdn, addrs):
@@ -680,7 +724,7 @@ class BindInstance(service.Service):
                 add_ptr_rr(reverse_zone, addr, fqdn)
 
     def __add_self(self):
-        self.__add_master_records(self.fqdn, [self.ip_address])
+        self.__add_master_records(self.fqdn, self.ip_addresses)
 
     def __add_others(self):
         entries = self.admin_conn.get_entries(
@@ -725,7 +769,7 @@ class BindInstance(service.Service):
             pass
 
     def __add_ipa_ca_record(self):
-        self.__add_ipa_ca_records(self.fqdn, [self.ip_address],
+        self.__add_ipa_ca_records(self.fqdn, self.ip_addresses,
                                   self.ca_configured)
 
         if self.first_instance:
@@ -813,7 +857,17 @@ class BindInstance(service.Service):
 
     def __setup_resolv_conf(self):
         self.fstore.backup_file(RESOLV_CONF)
-        resolv_txt = "search "+self.domain+"\nnameserver "+self.ip_address+"\n"
+        resolv_txt = "search "+self.domain+"\n"
+
+        for ip_address in self.ip_addresses:
+            if ip_address.version == 4:
+                resolv_txt += "nameserver 127.0.0.1\n"
+                break
+
+        for ip_address in self.ip_addresses:
+            if ip_address.version == 6:
+                resolv_txt += "nameserver ::1\n"
+                break
         try:
             resolv_fd = open(RESOLV_CONF, 'w')
             resolv_fd.seek(0)
@@ -827,16 +881,16 @@ class BindInstance(service.Service):
         installutils.check_entropy()
         ipautil.run(['/usr/libexec/generate-rndc-key.sh'])
 
-    def add_master_dns_records(self, fqdn, ip_address, realm_name, domain_name,
-                               reverse_zone, ntp=False, ca_configured=None):
+    def add_master_dns_records(self, fqdn, ip_addresses, realm_name, domain_name,
+                               reverse_zones, ntp=False, ca_configured=None):
         self.fqdn = fqdn
-        self.ip_address = ip_address
+        self.ip_addresses = ip_addresses
         self.realm = realm_name
         self.domain = domain_name
         self.host = fqdn.split(".")[0]
         self.suffix = ipautil.realm_to_suffix(self.realm)
         self.ntp = ntp
-        self.reverse_zone = reverse_zone
+        self.reverse_zones = reverse_zones
         self.ca_configured = ca_configured
         self.first_instance = False
         self.zonemgr = 'hostmaster.%s' % self.domain
diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
index 34ae306249bded29ee5c058941d0e81927b6a738..05790a441353ebdbda28a5c1277a1baec59344b6 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -222,6 +222,7 @@ def add_record_to_hosts(ip, host_name, conf_file=paths.HOSTS):
     hosts_fd.write(ip+'\t'+host_name+' '+host_name.split('.')[0]+'\n')
     hosts_fd.close()
 
+# TODO: Remove when removing usage from ipa-adtrust-install
 def read_ip_address(host_name, fstore):
     while True:
         ip = ipautil.user_input("Please provide the IP address to be used for this host name", allow_empty = False)
@@ -235,6 +236,22 @@ def read_ip_address(host_name, fstore):
 
     return ip_parsed
 
+def read_ip_addresses(host_name, fstore):
+    ips = []
+    print "Enter the IP address to use, or press Enter to finish."
+    while True:
+        ip = ipautil.user_input("Please provide the IP address to be used for this host name", allow_empty = True)
+        if not ip:
+            break
+        try:
+            ip_parsed = ipautil.CheckedIPAddress(ip, match_local=True)
+        except Exception, e:
+            print "Error: Invalid IP Address %s: %s" % (ip, e)
+            continue
+        ips.append(ip)
+
+    return ips
+
 def read_dns_forwarders():
     addrs = []
     if ipautil.user_input("Do you want to configure DNS forwarders?", True):
@@ -426,7 +443,7 @@ def get_host_name(no_host_dns):
     verify_fqdn(hostname, no_host_dns)
     return hostname
 
-def get_server_ip_address(host_name, fstore, unattended, options):
+def get_server_ip_address(host_name, fstore, unattended, setup_dns, ip_addresses):
     # Check we have a public IP that is associated with the hostname
     try:
         hostaddr = resolve_host(host_name)
@@ -441,74 +458,58 @@ def get_server_ip_address(host_name, fstore, unattended, options):
 
     ip_add_to_hosts = False
 
-    if len(hostaddr) > 1:
-        print >> sys.stderr, "The server hostname resolves to more than one address:"
-        for addr in hostaddr:
-            print >> sys.stderr, "  %s" % addr
+    ips = []
+    if len(hostaddr):
+        for ha in hostaddr:
+            try:
+                ips.append(ipautil.CheckedIPAddress(ha, match_local=True))
+            except ValueError, e:
+                root_logger.warning("Invalid IP address %s for %s: %s", ha, host_name, unicode(e))
 
-        if options.ip_address:
-            if str(options.ip_address) not in hostaddr:
-                print >> sys.stderr, "Address passed in --ip-address did not match any resolved"
-                print >> sys.stderr, "address!"
-                sys.exit(1)
-            print "Selected IP address:", str(options.ip_address)
-            ip = options.ip_address
+    if not ips and not ip_addresses:
+        if not unattended:
+            ip_addresses = read_ip_addresses(host_name, fstore)
+
+    if ip_addresses:
+        if setup_dns:
+            ips = ip_addresses
         else:
-            if unattended:
-                print >> sys.stderr, "Please use --ip-address option to specify the address"
-                sys.exit(1)
+            # all specified addresses was resolved for this host
+            if set(ip_addresses) <= set(ips):
+                ips = ip_addresses
             else:
-                ip = read_ip_address(host_name, fstore)
-    elif len(hostaddr) == 1:
-        try:
-            ip = ipautil.CheckedIPAddress(hostaddr[0], match_local=True)
-        except ValueError, e:
-            sys.exit("Invalid IP Address %s for %s: %s" % (hostaddr[0], host_name, unicode(e)))
-    else:
-        # hostname is not resolvable
-        ip = options.ip_address
-        ip_add_to_hosts = True
-
-    if ip is None:
-        print "Unable to resolve IP address for host name"
-        if unattended:
-            sys.exit(1)
-
-    if options.ip_address:
-        if options.ip_address != ip and not options.setup_dns:
-            print >>sys.stderr, "Error: the hostname resolves to an IP address that is different"
-            print >>sys.stderr, "from the one provided on the command line.  Please fix your DNS"
-            print >>sys.stderr, "or /etc/hosts file and restart the installation."
-            sys.exit(1)
-
-        ip = options.ip_address
-
-    if ip is None:
-        ip = read_ip_address(host_name, fstore)
-        root_logger.debug("read ip_address: %s\n" % str(ip))
-
-    ip_address = str(ip)
-
-    # check /etc/hosts sanity, add a record when needed
-    hosts_record = record_in_hosts(ip_address)
-
-    if hosts_record is None:
-        if ip_add_to_hosts or options.setup_dns:
-            print "Adding ["+ip_address+" "+host_name+"] to your /etc/hosts file"
-            fstore.backup_file(paths.HOSTS)
-            add_record_to_hosts(ip_address, host_name)
-    else:
-        primary_host = hosts_record[1][0]
-        if primary_host != host_name:
-            print >>sys.stderr, "Error: there is already a record in /etc/hosts for IP address %s:" \
-                    % ip_address
-            print >>sys.stderr, hosts_record[0], " ".join(hosts_record[1])
-            print >>sys.stderr, "Chosen hostname %s does not match configured canonical hostname %s" \
-                    % (host_name, primary_host)
-            print >>sys.stderr, "Please fix your /etc/hosts file and restart the installation."
-            sys.exit(1)
-
-    return ip
+                print >>sys.stderr, "Error: the hostname resolves to IP address(es) that are different"
+                print >>sys.stderr, "from those provided on the command line.  Please fix your DNS"
+                print >>sys.stderr, "or /etc/hosts file and restart the installation."
+                print >>sys.stderr, "Provided but not resolved address(es): %s" % \
+                                    ", ".join(str(ip) for ip in (set(ip_addresses) - set(ips)))
+                sys.exit(1)
+
+    if not ips:
+        print >> sys.stderr, "No usable IP address provided nor resolved."
+        sys.exit(1)
+
+    for ip_address in ips:
+        # check /etc/hosts sanity, add a record when needed
+        hosts_record = record_in_hosts(str(ip_address))
+
+        if hosts_record is None:
+            if ip_add_to_hosts:
+                print "Adding ["+str(ip_address)+" "+host_name+"] to your /etc/hosts file"
+                fstore.backup_file(paths.HOSTS)
+                add_record_to_hosts(str(ip_address), host_name)
+        else:
+            primary_host = hosts_record[1][0]
+            if primary_host != host_name:
+                print >>sys.stderr, "Error: there is already a record in /etc/hosts for IP address %s:" \
+                        % ip_address
+                print >>sys.stderr, hosts_record[0], " ".join(hosts_record[1])
+                print >>sys.stderr, "Chosen hostname %s does not match configured canonical hostname %s" \
+                        % (host_name, primary_host)
+                print >>sys.stderr, "Please fix your /etc/hosts file and restart the installation."
+                sys.exit(1)
+
+    return ips
 
 def expand_replica_info(filename, password):
     """
diff --git a/ipaserver/install/ipa_replica_prepare.py b/ipaserver/install/ipa_replica_prepare.py
index 7768fd311c9088994b62e73b3cc3e72cfbaca430..e27eb6dd48bf55d2df9a12680e6b5ab76207bfca 100644
--- a/ipaserver/install/ipa_replica_prepare.py
+++ b/ipaserver/install/ipa_replica_prepare.py
@@ -54,9 +54,11 @@ class ReplicaPrepare(admintool.AdminTool):
 
         parser.add_option("-p", "--password", dest="password",
             help="Directory Manager password (for the existing master)")
-        parser.add_option("--ip-address", dest="ip_address", type="ip",
+        parser.add_option("--ip-address", dest="ip_addresses", type="ip",
+            action="append", default=[],
             help="add A and PTR records of the future replica")
-        parser.add_option("--reverse-zone", dest="reverse_zone",
+        parser.add_option("--reverse-zone", dest="reverse_zones",
+            action="append", default=[],
             help="the reverse DNS zone to use")
         parser.add_option("--no-reverse", dest="no_reverse",
             action="store_true", default=False,
@@ -95,14 +97,14 @@ class ReplicaPrepare(admintool.AdminTool):
         super(ReplicaPrepare, self).validate_options(needs_root=True)
         installutils.check_server_configuration()
 
-        if not options.ip_address:
-            if options.reverse_zone:
+        if not options.ip_addresses:
+            if options.reverse_zones:
                 self.option_parser.error("You cannot specify a --reverse-zone "
                     "option without the --ip-address option")
             if options.no_reverse:
                 self.option_parser.error("You cannot specify a --no-reverse "
                     "option without the --ip-address option")
-        elif options.reverse_zone and options.no_reverse:
+        elif options.reverse_zones and options.no_reverse:
             self.option_parser.error("You cannot specify a --reverse-zone "
                 "option together with --no-reverse")
 
@@ -192,7 +194,7 @@ class ReplicaPrepare(admintool.AdminTool):
         except installutils.BadHostError, e:
             msg = str(e)
             if isinstance(e, installutils.HostLookupError):
-                if options.ip_address is None:
+                if not options.ip_addresses:
                     if dns_container_exists(
                             api.env.host, api.env.basedn,
                             dm_password=self.dirman_password,
@@ -206,7 +208,7 @@ class ReplicaPrepare(admintool.AdminTool):
             else:
                 raise
 
-        if options.ip_address:
+        if options.ip_addresses:
             if not dns_container_exists(api.env.host, api.env.basedn,
                                         dm_password=self.dirman_password,
                                         ldapi=True, realm=api.env.realm):
@@ -215,9 +217,19 @@ class ReplicaPrepare(admintool.AdminTool):
                     "because DNS is not managed by IPA. Please create DNS "
                     "record manually and then omit --ip-address option.")
                 raise admintool.ScriptError("Cannot add DNS record")
-            if options.reverse_zone and not bindinstance.verify_reverse_zone(
-                    options.reverse_zone, options.ip_address):
-                raise admintool.ScriptError("Invalid reverse zone")
+
+            disconnect = False
+            if not api.Backend.ldap2.isconnected():
+                api.Backend.ldap2.connect(
+                    bind_dn=DN(('cn', 'Directory Manager')),
+                    bind_pw=self.dirman_password)
+                disconnect = True
+
+            options.reverse_zones = bindinstance.check_reverse_zones(
+                options.ip_addresses, options.reverse_zones, options, False,
+                True)
+            if disconnect:
+                api.Backend.ldap2.disconnect()
 
         if options.http_pkcs12:
             if options.http_pin is None:
@@ -293,7 +305,7 @@ class ReplicaPrepare(admintool.AdminTool):
         finally:
             shutil.rmtree(self.top_dir)
 
-        if options.ip_address:
+        if options.ip_addresses:
             self.add_dns_records()
 
         if options.wait_for_dns:
@@ -420,46 +432,38 @@ class ReplicaPrepare(admintool.AdminTool):
         options = self.options
 
         self.log.info("Adding DNS records for %s", self.replica_fqdn)
-        api.Backend.ldap2.connect(
-            bind_dn=DN(('cn', 'Directory Manager')),
-            bind_pw=self.dirman_password)
-
         name, domain = self.replica_fqdn.split(".", 1)
 
-        ip = options.ip_address
-        ip_address = str(ip)
-
-        if options.reverse_zone:
-            reverse_zone = bindinstance.normalize_zone(options.reverse_zone)
-        else:
-            reverse_zone = bindinstance.find_reverse_zone(ip)
-            if reverse_zone is None and not options.no_reverse:
-                reverse_zone = bindinstance.get_reverse_zone_default(ip)
-
+        if not api.Backend.ldap2.isconnected():
+            api.Backend.ldap2.connect(
+                bind_dn=DN(('cn', 'Directory Manager')),
+                bind_pw=self.dirman_password)
         try:
             add_zone(domain)
         except errors.PublicError, e:
             raise admintool.ScriptError(
                 "Could not create forward DNS zone for the replica: %s" % e)
 
-        try:
-            add_fwd_rr(domain, name, ip_address)
-        except errors.PublicError, e:
-            raise admintool.ScriptError(
-                "Could not add forward DNS record for the replica: %s" % e)
+        for reverse_zone in options.reverse_zones:
+            self.log.info("Adding reverse zone %s", reverse_zone)
+            add_zone(reverse_zone)
 
-        if reverse_zone is not None:
-            self.log.info("Using reverse zone %s", reverse_zone)
+        for ip in options.ip_addresses:
+            ip_address = str(ip)
             try:
-                add_zone(reverse_zone)
+                add_fwd_rr(domain, name, ip_address)
             except errors.PublicError, e:
                 raise admintool.ScriptError(
-                    "Could not create reverse DNS zone for replica: %s" % e)
-            try:
-                add_ptr_rr(reverse_zone, ip_address, self.replica_fqdn)
-            except errors.PublicError, e:
-                raise admintool.ScriptError(
-                    "Could not add reverse DNS record for the replica: %s" % e)
+                    "Could not add forward DNS record for the replica: %s" % e)
+
+            if not options.no_reverse:
+                reverse_zone = bindinstance.find_reverse_zone(ip)
+                try:
+                    add_ptr_rr(reverse_zone, ip_address, self.replica_fqdn)
+                except errors.PublicError, e:
+                    raise admintool.ScriptError(
+                        "Could not add reverse DNS record for the replica: %s"
+                        % e)
 
     def check_dns(self, replica_fqdn):
         """Return true if the replica hostname is resolvable"""
-- 
1.9.3

From a6c46940e71d66e65242cd97fd0a32d7806a7416 Mon Sep 17 00:00:00 2001
From: David Kupka <dku...@redhat.com>
Date: Wed, 27 Aug 2014 13:50:21 +0200
Subject: [PATCH] Detect and configure all usable IP addresses.

Find, verify and configure all IP addresses that can be used to reach the server
FreeIPA is being installed on. Ignore some IP address only if user specifies
subset of detected addresses using --ip-address option.
This change simplyfies FreeIPA installation on multihomed and dual-stacked servers.

https://fedorahosted.org/freeipa/ticket/3575
---
 install/tools/ipa-dns-install            |  71 ++++-------------
 install/tools/ipa-replica-install        |  57 +++++++------
 install/tools/ipa-server-install         |  54 ++++++-------
 ipaserver/install/bindinstance.py        | 110 ++++++++++++++++++-------
 ipaserver/install/installutils.py        | 133 ++++++++++++++++---------------
 ipaserver/install/ipa_replica_prepare.py |  82 ++++++++++---------
 6 files changed, 257 insertions(+), 250 deletions(-)

diff --git a/install/tools/ipa-dns-install b/install/tools/ipa-dns-install
index 5e191974b6b91cd893b483ad56a9aa723770eafe..d040674b6a28cb395fa95fd02d70432481e46ff5 100755
--- a/install/tools/ipa-dns-install
+++ b/install/tools/ipa-dns-install
@@ -41,13 +41,16 @@ def parse_options():
                       sensitive=True, help="admin password")
     parser.add_option("-d", "--debug", dest="debug", action="store_true",
                       default=False, help="print debugging information")
-    parser.add_option("--ip-address", dest="ip_address",
+    parser.add_option("--ip-address", dest="ip_addresses",
+                      default=[], action="append",
                       type="ip", ip_local=True, help="Master Server IP Address")
     parser.add_option("--forwarder", dest="forwarders", action="append",
                       type="ip", help="Add a DNS forwarder")
     parser.add_option("--no-forwarders", dest="no_forwarders", action="store_true",
                       default=False, help="Do not add any DNS forwarders, use root servers instead")
-    parser.add_option("--reverse-zone", dest="reverse_zone", help="The reverse DNS zone to use")
+    parser.add_option("--reverse-zone", dest="reverse_zones",
+                      default=[], action="append",
+                      help="The reverse DNS zone to use")
     parser.add_option("--no-reverse", dest="no_reverse", action="store_true",
                       default=False, help="Do not create new reverse DNS zone")
     parser.add_option("--zonemgr", action="callback", callback=bindinstance.zonemgr_callback,
@@ -61,7 +64,7 @@ def parse_options():
 
     if options.forwarders and options.no_forwarders:
         parser.error("You cannot specify a --forwarder option together with --no-forwarders")
-    elif options.reverse_zone and options.no_reverse:
+    elif options.reverse_zones and options.no_reverse:
         parser.error("You cannot specify a --reverse-zone option together with --no-reverse")
 
     if options.unattended:
@@ -129,46 +132,8 @@ def main():
     except errors.ACIError:
         sys.exit("Password is not valid!")
 
-    # Check we have a public IP that is associated with the hostname
-    if options.ip_address:
-        ip = options.ip_address
-    else:
-        hostaddr = resolve_host(api.env.host)
-        try:
-            if len(hostaddr) > 1:
-                print >> sys.stderr, "The server hostname resolves to more than one address:"
-                for addr in hostaddr:
-                    print >> sys.stderr, "  %s" % addr
-
-                if options.ip_address:
-                    if str(options.ip_address) not in hostaddr:
-                        print >> sys.stderr, "Address passed in --ip-address did not match any resolved"
-                        print >> sys.stderr, "address!"
-                        sys.exit(1)
-                    print "Selected IP address:", str(options.ip_address)
-                    ip = options.ip_address
-                else:
-                    if options.unattended:
-                        print >> sys.stderr, "Please use --ip-address option to specify the address"
-                        sys.exit(1)
-                    else:
-                        ip = read_ip_address(api.env.host, fstore)
-            else:
-                ip = hostaddr and ipautil.CheckedIPAddress(hostaddr[0], match_local=True)
-        except Exception, e:
-            print "Error: Invalid IP Address %s: %s" % (ip, e)
-            ip = None
-
-    if not ip:
-        if options.unattended:
-            sys.exit("Unable to resolve IP address for host name")
-        else:
-            ip = read_ip_address(api.env.host, fstore)
-    ip_address = str(ip)
-    root_logger.debug("will use ip_address: %s\n", ip_address)
-
-    if options.reverse_zone and not bindinstance.verify_reverse_zone(options.reverse_zone, ip):
-        sys.exit(1)
+    ip_addresses = get_server_ip_address(api.env.host, fstore,
+        options.unattended, True, options.ip_addresses)
 
     if options.no_forwarders:
         dns_forwarders = ()
@@ -185,19 +150,11 @@ def main():
         ccache = krbV.default_context().default_ccache()
         api.Backend.ldap2.connect(ccache)
 
-    if options.reverse_zone:
-        reverse_zone = bindinstance.normalize_zone(options.reverse_zone)
-    else:
-        reverse_zone = bindinstance.find_reverse_zone(ip)
-        if reverse_zone is None and not options.no_reverse:
-            if options.unattended:
-                reverse_zone = util.get_reverse_zone_default(ip)
-            elif bindinstance.create_reverse():
-                reverse_zone = util.get_reverse_zone_default(ip)
-                reverse_zone = bindinstance.read_reverse_zone(reverse_zone, ip)
+    reverse_zones = bindinstance.check_reverse_zones(ip_addresses,
+        options.reverse_zones, options, options.unattended)
 
-    if reverse_zone is not None:
-        print "Using reverse zone %s" % reverse_zone
+    if reverse_zones is not None:
+        print "Using reverse zone %s" % ', '.join(reverse_zones)
 
     conf_ntp = ntpinstance.NTPInstance(fstore).is_enabled()
 
@@ -207,8 +164,8 @@ def main():
         print "Please wait until the prompt is returned."
         print ""
 
-    bind.setup(api.env.host, ip_address, api.env.realm, api.env.domain,
-               dns_forwarders, conf_ntp, reverse_zone, zonemgr=options.zonemgr)
+    bind.setup(api.env.host, ip_addresses, api.env.realm, api.env.domain,
+               dns_forwarders, conf_ntp, reverse_zones, zonemgr=options.zonemgr)
     bind.create_instance()
 
     # Restart http instance to make sure that python-dns has the right resolver
diff --git a/install/tools/ipa-replica-install b/install/tools/ipa-replica-install
index 23bd698a00f2f41129ffc7d277355a3206be827e..c11d8a3696ca0239d365d2d9ff54bfee506ee711 100755
--- a/install/tools/ipa-replica-install
+++ b/install/tools/ipa-replica-install
@@ -65,8 +65,8 @@ def parse_options():
     basic_group = OptionGroup(parser, "basic options")
     basic_group.add_option("--setup-ca", dest="setup_ca", action="store_true",
                       default=False, help="configure a dogtag CA")
-    basic_group.add_option("--ip-address", dest="ip_address",
-                      type="ip", ip_local=True,
+    basic_group.add_option("--ip-address", dest="ip_addresses",
+                      type="ip", ip_local=True, action="append", default=[],
                       help="Replica server IP Address")
     basic_group.add_option("-p", "--password", dest="password", sensitive=True,
                       help="Directory Manager (existing master) password")
@@ -110,7 +110,8 @@ def parse_options():
                       type="ip", help="Add a DNS forwarder")
     dns_group.add_option("--no-forwarders", dest="no_forwarders", action="store_true",
                       default=False, help="Do not add any DNS forwarders, use root servers instead")
-    dns_group.add_option("--reverse-zone", dest="reverse_zone", help="The reverse DNS zone to use")
+    dns_group.add_option("--reverse-zone", dest="reverse_zones", default=[],
+                         action="append", help="The reverse DNS zone to use")
     dns_group.add_option("--no-reverse", dest="no_reverse", action="store_true",
                       default=False, help="Do not create new reverse DNS zone")
     dns_group.add_option("--no-host-dns", dest="no_host_dns", action="store_true",
@@ -131,7 +132,7 @@ def parse_options():
             parser.error("You cannot specify a --forwarder option without the --setup-dns option")
         if options.no_forwarders:
             parser.error("You cannot specify a --no-forwarders option without the --setup-dns option")
-        if options.reverse_zone:
+        if options.reverse_zones:
             parser.error("You cannot specify a --reverse-zone option without the --setup-dns option")
         if options.no_reverse:
             parser.error("You cannot specify a --no-reverse option without the --setup-dns option")
@@ -139,7 +140,7 @@ def parse_options():
         parser.error("You cannot specify a --forwarder option together with --no-forwarders")
     elif not options.forwarders and not options.no_forwarders:
         parser.error("You must specify at least one --forwarder option or --no-forwarders option")
-    elif options.reverse_zone and options.no_reverse:
+    elif options.reverse_zones and options.no_reverse:
         parser.error("You cannot specify a --reverse-zone option together with --no-reverse")
 
     return safe_options, options, args[0]
@@ -261,23 +262,9 @@ def install_bind(config, options):
         forwarders = ()
     bind = bindinstance.BindInstance(dm_password=config.dirman_password)
 
-    if options.reverse_zone:
-        if not bindinstance.verify_reverse_zone(options.reverse_zone, config.ip):
-            sys.exit(1)
-        reverse_zone = bindinstance.normalize_zone(options.reverse_zone)
-    else:
-        reverse_zone = bindinstance.find_reverse_zone(config.ip)
-        if reverse_zone is None and not options.no_reverse:
-            reverse_zone = util.get_reverse_zone_default(config.ip)
-            if not options.unattended and bindinstance.create_reverse():
-                reverse_zone = bindinstance.read_reverse_zone(reverse_zone, config.ip)
-
-    if reverse_zone is not None:
-        print "Using reverse zone %s" % reverse_zone
-
-    bind.setup(config.host_name, config.ip_address, config.realm_name,
-               config.domain_name, forwarders, options.conf_ntp, reverse_zone,
-               ca_configured=options.setup_ca)
+    bind.setup(config.host_name, config.ips, config.realm_name,
+               config.domain_name, forwarders, options.conf_ntp,
+               config.reverse_zones, ca_configured=options.setup_ca)
     bind.create_instance()
 
     print ""
@@ -323,12 +310,16 @@ def install_dns_records(config, options):
             config.master_host_name, config.dirman_password):
         try:
             bind = bindinstance.BindInstance(dm_password=config.dirman_password)
-            reverse_zone = bindinstance.find_reverse_zone(config.ip)
+            for ip, ip_address in zip(config.ips, config.ip_addresses):
+                reverse_zone = bindinstance.find_reverse_zone(ip)
 
-            bind.add_master_dns_records(config.host_name, config.ip_address,
-                                        config.realm_name, config.domain_name,
-                                        reverse_zone, options.conf_ntp,
-                                        options.setup_ca)
+                bind.add_master_dns_records(config.host_name,
+                                            ip_address,
+                                            config.realm_name,
+                                            config.domain_name,
+                                            reverse_zone,
+                                            options.conf_ntp,
+                                            options.setup_ca)
         except errors.NotFound, e:
             root_logger.debug('Replica DNS records could not be added '
                               'on master: %s', str(e))
@@ -551,8 +542,16 @@ def main():
 
 
     # check replica host IP resolution
-    config.ip = installutils.get_server_ip_address(config.host_name, fstore, True, options)
-    config.ip_address = str(config.ip)
+    config.ips = installutils.get_server_ip_address(config.host_name, fstore,
+        options.unattended, options.setup_dns, options.ip_addresses)
+    config.ip_addresses = [str(ip) for ip in config.ips]
+
+    config.reverse_zones = bindinstance.check_reverse_zones(
+        config.ip_addresses, options.reverse_zones, options, True)
+
+    if config.reverse_zones is not None:
+        print "Using reverse zone(s) %s" % ', '.join(config.reverse_zones)
+
 
     # Create the management framework config file
     # Note: We must do this before bootstraping and finalizing ipalib.api
diff --git a/install/tools/ipa-server-install b/install/tools/ipa-server-install
index cb5931173ecd256e5f9b729d4dbcc21d89745db9..7d60d27bcfae9a89ad7c5d811d3f9d8a9fda60cb 100755
--- a/install/tools/ipa-server-install
+++ b/install/tools/ipa-server-install
@@ -174,8 +174,8 @@ def parse_options():
                            help="create home directories for users "
                                 "on their first login")
     basic_group.add_option("--hostname", dest="host_name", help="fully qualified name of server")
-    basic_group.add_option("--ip-address", dest="ip_address",
-                      type="ip", ip_local=True,
+    basic_group.add_option("--ip-address", dest="ip_addresses",
+                      type="ip", ip_local=True, action="append", default=[],
                       help="Master Server IP Address")
     basic_group.add_option("-N", "--no-ntp", dest="conf_ntp", action="store_false",
                       help="do not configure ntp", default=True)
@@ -235,7 +235,8 @@ def parse_options():
                       type="ip", help="Add a DNS forwarder")
     dns_group.add_option("--no-forwarders", dest="no_forwarders", action="store_true",
                       default=False, help="Do not add any DNS forwarders, use root servers instead")
-    dns_group.add_option("--reverse-zone", dest="reverse_zone", help="The reverse DNS zone to use")
+    dns_group.add_option("--reverse-zone", dest="reverse_zones", help="The reverse DNS zone to use",
+                      action="append", default=[])
     dns_group.add_option("--no-reverse", dest="no_reverse", action="store_true",
                       default=False, help="Do not create reverse DNS zone")
     dns_group.add_option("--zonemgr", action="callback", callback=bindinstance.zonemgr_callback,
@@ -279,13 +280,13 @@ def parse_options():
             parser.error("You cannot specify a --forwarder option without the --setup-dns option")
         if options.no_forwarders:
             parser.error("You cannot specify a --no-forwarders option without the --setup-dns option")
-        if options.reverse_zone:
+        if options.reverse_zones:
             parser.error("You cannot specify a --reverse-zone option without the --setup-dns option")
         if options.no_reverse:
             parser.error("You cannot specify a --no-reverse option without the --setup-dns option")
     elif options.forwarders and options.no_forwarders:
         parser.error("You cannot specify a --forwarder option together with --no-forwarders")
-    elif options.reverse_zone and options.no_reverse:
+    elif options.reverse_zones and options.no_reverse:
         parser.error("You cannot specify a --reverse-zone option together with --no-reverse")
 
     if options.uninstall:
@@ -812,11 +813,11 @@ def main():
     realm_name = ""
     host_name = ""
     domain_name = ""
-    ip_address = ""
+    ip_addresses = []
     master_password = ""
     dm_password = ""
     admin_password = ""
-    reverse_zone = None
+    reverse_zones = []
 
     if not options.setup_dns and not options.unattended:
         if ipautil.user_input("Do you want to configure integrated DNS (BIND)?", False):
@@ -875,11 +876,8 @@ def main():
 
     domain_name = domain_name.lower()
 
-    ip = get_server_ip_address(host_name, fstore, options.unattended, options)
-    ip_address = str(ip)
-
-    if options.reverse_zone and not bindinstance.verify_reverse_zone(options.reverse_zone, ip):
-        sys.exit(1)
+    ip_addresses = get_server_ip_address(host_name, fstore,
+        options.unattended, options.setup_dns, options.ip_addresses)
 
     if not options.realm_name:
         realm_name = read_realm_name(domain_name, options.unattended)
@@ -956,35 +954,29 @@ def main():
         else:
             dns_forwarders = read_dns_forwarders()
 
-        if options.reverse_zone:
-            reverse_zone = bindinstance.normalize_zone(options.reverse_zone)
-        elif not options.no_reverse:
-            if options.unattended:
-                reverse_zone = util.get_reverse_zone_default(ip)
-            elif bindinstance.create_reverse():
-                reverse_zone = util.get_reverse_zone_default(ip)
-                reverse_zone = bindinstance.read_reverse_zone(reverse_zone, ip)
+        reverse_zones = bindinstance.check_reverse_zones(ip_addresses,
+            options.reverse_zones, options, options.unattended)
 
-        if reverse_zone is not None:
-            print "Using reverse zone %s" % reverse_zone
+        if reverse_zones:
+            print "Using reverse zone(s) %s" % ", ".join(str(rz) for rz in reverse_zones)
     else:
         dns_forwarders = ()
     root_logger.debug("will use dns_forwarders: %s\n" % str(dns_forwarders))
 
     print
     print "The IPA Master Server will be configured with:"
-    print "Hostname:      %s" % host_name
-    print "IP address:    %s" % ip_address
-    print "Domain name:   %s" % domain_name
-    print "Realm name:    %s" % realm_name
+    print "Hostname:       %s" % host_name
+    print "IP address(es): %s" % ", ".join(str(ip) for ip in ip_addresses)
+    print "Domain name:    %s" % domain_name
+    print "Realm name:     %s" % realm_name
     print
 
     if options.setup_dns:
         print "BIND DNS server will be configured to serve IPA domain with:"
         print "Forwarders:    %s" % ("No forwarders" if not dns_forwarders \
                 else ", ".join([str(ip) for ip in dns_forwarders]))
-        print "Reverse zone:  %s" % ("No reverse zone" if options.no_reverse \
-                or reverse_zone is None else reverse_zone)
+        print "Reverse zone(s):  %s" % ("No reverse zone" if options.no_reverse \
+                or reverse_zones is None else ", ".join(str(rz) for rz in reverse_zones))
         print
 
     # If domain name and realm does not match, IPA server will not be able
@@ -1094,7 +1086,7 @@ def main():
             options.host_name = host_name
             options.unattended = True
             options.forwarders = dns_forwarders
-            options.reverse_zone = reverse_zone
+            options.reverse_zones = reverse_zones
             write_cache(vars(options))
             ca.configure_instance(host_name, domain_name, dm_password,
                                   dm_password, csr_file=paths.ROOT_IPA_CSR,
@@ -1188,8 +1180,8 @@ def main():
 
     # Create a BIND instance
     bind = bindinstance.BindInstance(fstore, dm_password)
-    bind.setup(host_name, ip_address, realm_name, domain_name, dns_forwarders,
-               options.conf_ntp, reverse_zone, zonemgr=options.zonemgr,
+    bind.setup(host_name, ip_addresses, realm_name, domain_name, dns_forwarders,
+               options.conf_ntp, reverse_zones, zonemgr=options.zonemgr,
                ca_configured=setup_ca)
     if options.setup_dns:
         api.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=dm_password)
diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py
index 09760d66700390a5609f7a0504d9b2cfec91d268..d55551987a1582a61116ac79051b5dea3486b38b 100644
--- a/ipaserver/install/bindinstance.py
+++ b/ipaserver/install/bindinstance.py
@@ -22,6 +22,7 @@ import os
 import pwd
 import netaddr
 import re
+import sys
 
 import ldap
 
@@ -265,7 +266,6 @@ def verify_reverse_zone(zone, ip_address):
     try:
         get_reverse_record_name(zone, ip_address)
     except ValueError:
-        print "Invalid reverse zone %s" % zone
         return False
 
     return True
@@ -291,6 +291,8 @@ def read_reverse_zone(default, ip_address):
             return None
         if verify_reverse_zone(zone, ip_address):
             break
+        else:
+            print "Invalid reverse zone %s for IP address %s" % (zone, ip_address)
 
     return normalize_zone(zone)
 
@@ -393,6 +395,48 @@ 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
+        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 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)
+
+    return ret_reverse_zones
+
+
 class DnsBackup(object):
     def __init__(self, service):
         self.service = service
@@ -452,11 +496,11 @@ class BindInstance(service.Service):
         self.named_user = None
         self.domain = None
         self.host = None
-        self.ip_address = None
+        self.ip_addresses = []
         self.realm = None
         self.forwarders = None
         self.sub_dict = None
-        self.reverse_zone = None
+        self.reverse_zones = []
         self.dm_password = dm_password
 
         if fstore:
@@ -466,19 +510,19 @@ class BindInstance(service.Service):
 
     suffix = ipautil.dn_attribute_property('_suffix')
 
-    def setup(self, fqdn, ip_address, realm_name, domain_name, forwarders, ntp,
-              reverse_zone, named_user="named", zonemgr=None,
+    def setup(self, fqdn, ip_addresses, realm_name, domain_name, forwarders, ntp,
+              reverse_zones, named_user="named", zonemgr=None,
               ca_configured=None):
         self.named_user = named_user
         self.fqdn = fqdn
-        self.ip_address = ip_address
+        self.ip_addresses = ip_addresses
         self.realm = realm_name
         self.domain = domain_name
         self.forwarders = forwarders
         self.host = fqdn.split(".")[0]
         self.suffix = ipautil.realm_to_suffix(self.realm)
         self.ntp = ntp
-        self.reverse_zone = reverse_zone
+        self.reverse_zones = reverse_zones
         self.ca_configured = ca_configured
 
         if not zonemgr:
@@ -524,8 +568,9 @@ class BindInstance(service.Service):
         # get a connection to the DS
         self.ldap_connect()
 
-        if installutils.record_in_hosts(self.ip_address, self.fqdn) is None:
-            installutils.add_record_to_hosts(self.ip_address, self.fqdn)
+        for ip_address in self.ip_addresses:
+            if installutils.record_in_hosts(str(ip_address), self.fqdn) is None:
+                installutils.add_record_to_hosts(str(ip_address), self.fqdn)
 
         # Make sure generate-rndc-key.sh runs before named restart
         self.step("generating rndc key file", self.__generate_rndc_key)
@@ -535,8 +580,7 @@ class BindInstance(service.Service):
 
         if not dns_zone_exists(self.domain):
             self.step("setting up our zone", self.__setup_zone)
-
-        if self.reverse_zone is not None:
+        if self.reverse_zones:
             self.step("setting up reverse zone", self.__setup_reverse_zone)
 
         self.step("setting up our own record", self.__add_self)
@@ -589,18 +633,17 @@ class BindInstance(service.Service):
         else:
             optional_ntp = ""
 
-        addr = netaddr.IPAddress(self.ip_address)
-        if addr.version in (4, 6):
-            ipa_ca = "%s\t\t\tIN %s\t\t\t%s\n" % (
-                IPA_CA_RECORD,
-                "A" if addr.version == 4 else "AAAA",
-                self.ip_address)
-        else:
-            ipa_ca = ""
+        ipa_ca = ""
+        for addr in self.ip_addresses:
+            if addr.version in (4, 6):
+                ipa_ca += "%s\t\t\tIN %s\t\t\t%s\n" % (
+                    IPA_CA_RECORD,
+                    "A" if addr.version == 4 else "AAAA",
+                    str(addr))
 
         self.sub_dict = dict(
             FQDN=self.fqdn,
-            IP=self.ip_address,
+            IP=[str(ip) for ip in self.ip_addresses],
             DOMAIN=self.domain,
             HOST=self.host,
             REALM=self.realm,
@@ -633,7 +676,8 @@ class BindInstance(service.Service):
 
     def __setup_reverse_zone(self):
         # Always use force=True as named is not set up yet
-        add_zone(self.reverse_zone, self.zonemgr, ns_hostname=api.env.host,
+        for reverse_zone in self.reverse_zones:
+            add_zone(reverse_zone, self.zonemgr, ns_hostname=api.env.host,
                 dns_backup=self.dns_backup, force=True)
 
     def __add_master_records(self, fqdn, addrs):
@@ -680,7 +724,7 @@ class BindInstance(service.Service):
                 add_ptr_rr(reverse_zone, addr, fqdn)
 
     def __add_self(self):
-        self.__add_master_records(self.fqdn, [self.ip_address])
+        self.__add_master_records(self.fqdn, self.ip_addresses)
 
     def __add_others(self):
         entries = self.admin_conn.get_entries(
@@ -725,7 +769,7 @@ class BindInstance(service.Service):
             pass
 
     def __add_ipa_ca_record(self):
-        self.__add_ipa_ca_records(self.fqdn, [self.ip_address],
+        self.__add_ipa_ca_records(self.fqdn, self.ip_addresses,
                                   self.ca_configured)
 
         if self.first_instance:
@@ -813,7 +857,17 @@ class BindInstance(service.Service):
 
     def __setup_resolv_conf(self):
         self.fstore.backup_file(RESOLV_CONF)
-        resolv_txt = "search "+self.domain+"\nnameserver "+self.ip_address+"\n"
+        resolv_txt = "search "+self.domain+"\n"
+
+        for ip_address in self.ip_addresses:
+            if ip_address.version == 4:
+                resolv_txt += "nameserver 127.0.0.1\n"
+                break
+
+        for ip_address in self.ip_addresses:
+            if ip_address.version == 6:
+                resolv_txt += "nameserver ::1\n"
+                break
         try:
             resolv_fd = open(RESOLV_CONF, 'w')
             resolv_fd.seek(0)
@@ -827,16 +881,16 @@ class BindInstance(service.Service):
         installutils.check_entropy()
         ipautil.run(['/usr/libexec/generate-rndc-key.sh'])
 
-    def add_master_dns_records(self, fqdn, ip_address, realm_name, domain_name,
-                               reverse_zone, ntp=False, ca_configured=None):
+    def add_master_dns_records(self, fqdn, ip_addresses, realm_name, domain_name,
+                               reverse_zones, ntp=False, ca_configured=None):
         self.fqdn = fqdn
-        self.ip_address = ip_address
+        self.ip_addresses = ip_addresses
         self.realm = realm_name
         self.domain = domain_name
         self.host = fqdn.split(".")[0]
         self.suffix = ipautil.realm_to_suffix(self.realm)
         self.ntp = ntp
-        self.reverse_zone = reverse_zone
+        self.reverse_zones = reverse_zones
         self.ca_configured = ca_configured
         self.first_instance = False
         self.zonemgr = 'hostmaster.%s' % self.domain
diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
index 70cb5ff95727aa44b98694f9872f7065f03ccf30..22c17ea2a34464094e721f69bcbfbc588fa49552 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -221,6 +221,7 @@ def add_record_to_hosts(ip, host_name, file=paths.HOSTS):
     hosts_fd.write(ip+'\t'+host_name+' '+host_name.split('.')[0]+'\n')
     hosts_fd.close()
 
+# TODO: Remove when removing usage from ipa-adtrust-install
 def read_ip_address(host_name, fstore):
     while True:
         ip = ipautil.user_input("Please provide the IP address to be used for this host name", allow_empty = False)
@@ -234,6 +235,22 @@ def read_ip_address(host_name, fstore):
 
     return ip_parsed
 
+def read_ip_addresses(host_name, fstore):
+    ips = []
+    print "Enter the IP address to use, or press Enter to finish."
+    while True:
+        ip = ipautil.user_input("Please provide the IP address to be used for this host name", allow_empty = True)
+        if not ip:
+            break
+        try:
+            ip_parsed = ipautil.CheckedIPAddress(ip, match_local=True)
+        except Exception, e:
+            print "Error: Invalid IP Address %s: %s" % (ip, e)
+            continue
+        ips.append(ip)
+
+    return ips
+
 def read_dns_forwarders():
     addrs = []
     if ipautil.user_input("Do you want to configure DNS forwarders?", True):
@@ -425,7 +442,7 @@ def get_host_name(no_host_dns):
     verify_fqdn(hostname, no_host_dns)
     return hostname
 
-def get_server_ip_address(host_name, fstore, unattended, options):
+def get_server_ip_address(host_name, fstore, unattended, setup_dns, ip_addresses):
     # Check we have a public IP that is associated with the hostname
     try:
         hostaddr = resolve_host(host_name)
@@ -440,74 +457,58 @@ def get_server_ip_address(host_name, fstore, unattended, options):
 
     ip_add_to_hosts = False
 
-    if len(hostaddr) > 1:
-        print >> sys.stderr, "The server hostname resolves to more than one address:"
-        for addr in hostaddr:
-            print >> sys.stderr, "  %s" % addr
+    ips = []
+    if len(hostaddr):
+        for ha in hostaddr:
+            try:
+                ips.append(ipautil.CheckedIPAddress(ha, match_local=True))
+            except ValueError, e:
+                root_logger.warning("Invalid IP address %s for %s: %s", ha, host_name, unicode(e))
 
-        if options.ip_address:
-            if str(options.ip_address) not in hostaddr:
-                print >> sys.stderr, "Address passed in --ip-address did not match any resolved"
-                print >> sys.stderr, "address!"
-                sys.exit(1)
-            print "Selected IP address:", str(options.ip_address)
-            ip = options.ip_address
+    if not ips and not ip_addresses:
+        if not unattended:
+            ip_addresses = read_ip_addresses(host_name, fstore)
+
+    if ip_addresses:
+        if setup_dns:
+            ips = ip_addresses
         else:
-            if unattended:
-                print >> sys.stderr, "Please use --ip-address option to specify the address"
-                sys.exit(1)
+            # all specified addresses was resolved for this host
+            if set(ip_addresses) <= set(ips):
+                ips = ip_addresses
             else:
-                ip = read_ip_address(host_name, fstore)
-    elif len(hostaddr) == 1:
-        try:
-            ip = ipautil.CheckedIPAddress(hostaddr[0], match_local=True)
-        except ValueError, e:
-            sys.exit("Invalid IP Address %s for %s: %s" % (hostaddr[0], host_name, unicode(e)))
-    else:
-        # hostname is not resolvable
-        ip = options.ip_address
-        ip_add_to_hosts = True
-
-    if ip is None:
-        print "Unable to resolve IP address for host name"
-        if unattended:
-            sys.exit(1)
-
-    if options.ip_address:
-        if options.ip_address != ip and not options.setup_dns:
-            print >>sys.stderr, "Error: the hostname resolves to an IP address that is different"
-            print >>sys.stderr, "from the one provided on the command line.  Please fix your DNS"
-            print >>sys.stderr, "or /etc/hosts file and restart the installation."
-            sys.exit(1)
-
-        ip = options.ip_address
-
-    if ip is None:
-        ip = read_ip_address(host_name, fstore)
-        root_logger.debug("read ip_address: %s\n" % str(ip))
-
-    ip_address = str(ip)
-
-    # check /etc/hosts sanity, add a record when needed
-    hosts_record = record_in_hosts(ip_address)
-
-    if hosts_record is None:
-        if ip_add_to_hosts or options.setup_dns:
-            print "Adding ["+ip_address+" "+host_name+"] to your /etc/hosts file"
-            fstore.backup_file(paths.HOSTS)
-            add_record_to_hosts(ip_address, host_name)
-    else:
-        primary_host = hosts_record[1][0]
-        if primary_host != host_name:
-            print >>sys.stderr, "Error: there is already a record in /etc/hosts for IP address %s:" \
-                    % ip_address
-            print >>sys.stderr, hosts_record[0], " ".join(hosts_record[1])
-            print >>sys.stderr, "Chosen hostname %s does not match configured canonical hostname %s" \
-                    % (host_name, primary_host)
-            print >>sys.stderr, "Please fix your /etc/hosts file and restart the installation."
-            sys.exit(1)
-
-    return ip
+                print >>sys.stderr, "Error: the hostname resolves to IP address(es) that are different"
+                print >>sys.stderr, "from those provided on the command line.  Please fix your DNS"
+                print >>sys.stderr, "or /etc/hosts file and restart the installation."
+                print >>sys.stderr, "Provided but not resolved address(es): %s" % \
+                                    ", ".join(str(ip) for ip in (set(ip_addresses) - set(ips)))
+                sys.exit(1)
+
+    if not ips:
+        print >> sys.stderr, "No usable IP address provided nor resolved."
+        sys.exit(1)
+
+    for ip_address in ips:
+        # check /etc/hosts sanity, add a record when needed
+        hosts_record = record_in_hosts(str(ip_address))
+
+        if hosts_record is None:
+            if ip_add_to_hosts:
+                print "Adding ["+str(ip_address)+" "+host_name+"] to your /etc/hosts file"
+                fstore.backup_file(paths.HOSTS)
+                add_record_to_hosts(str(ip_address), host_name)
+        else:
+            primary_host = hosts_record[1][0]
+            if primary_host != host_name:
+                print >>sys.stderr, "Error: there is already a record in /etc/hosts for IP address %s:" \
+                        % ip_address
+                print >>sys.stderr, hosts_record[0], " ".join(hosts_record[1])
+                print >>sys.stderr, "Chosen hostname %s does not match configured canonical hostname %s" \
+                        % (host_name, primary_host)
+                print >>sys.stderr, "Please fix your /etc/hosts file and restart the installation."
+                sys.exit(1)
+
+    return ips
 
 def expand_replica_info(filename, password):
     """
diff --git a/ipaserver/install/ipa_replica_prepare.py b/ipaserver/install/ipa_replica_prepare.py
index c8d2e64c2ffc9addbb8cffbd13317ac2f82be5f6..dcd45fa967cbee714432c756bbb4a91624318563 100644
--- a/ipaserver/install/ipa_replica_prepare.py
+++ b/ipaserver/install/ipa_replica_prepare.py
@@ -54,9 +54,11 @@ class ReplicaPrepare(admintool.AdminTool):
 
         parser.add_option("-p", "--password", dest="password",
             help="Directory Manager password (for the existing master)")
-        parser.add_option("--ip-address", dest="ip_address", type="ip",
+        parser.add_option("--ip-address", dest="ip_addresses", type="ip",
+            action="append", default=[],
             help="add A and PTR records of the future replica")
-        parser.add_option("--reverse-zone", dest="reverse_zone",
+        parser.add_option("--reverse-zone", dest="reverse_zones",
+            action="append", default=[],
             help="the reverse DNS zone to use")
         parser.add_option("--no-reverse", dest="no_reverse",
             action="store_true", default=False,
@@ -95,14 +97,14 @@ class ReplicaPrepare(admintool.AdminTool):
         super(ReplicaPrepare, self).validate_options(needs_root=True)
         installutils.check_server_configuration()
 
-        if not options.ip_address:
-            if options.reverse_zone:
+        if not options.ip_addresses:
+            if options.reverse_zones:
                 self.option_parser.error("You cannot specify a --reverse-zone "
                     "option without the --ip-address option")
             if options.no_reverse:
                 self.option_parser.error("You cannot specify a --no-reverse "
                     "option without the --ip-address option")
-        elif options.reverse_zone and options.no_reverse:
+        elif options.reverse_zones and options.no_reverse:
             self.option_parser.error("You cannot specify a --reverse-zone "
                 "option together with --no-reverse")
 
@@ -192,7 +194,7 @@ class ReplicaPrepare(admintool.AdminTool):
         except installutils.BadHostError, e:
             msg = str(e)
             if isinstance(e, installutils.HostLookupError):
-                if options.ip_address is None:
+                if not options.ip_addresses:
                     if dns_container_exists(
                             api.env.host, api.env.basedn,
                             dm_password=self.dirman_password,
@@ -206,7 +208,7 @@ class ReplicaPrepare(admintool.AdminTool):
             else:
                 raise
 
-        if options.ip_address:
+        if options.ip_addresses:
             if not dns_container_exists(api.env.host, api.env.basedn,
                                         dm_password=self.dirman_password,
                                         ldapi=True, realm=api.env.realm):
@@ -215,9 +217,19 @@ class ReplicaPrepare(admintool.AdminTool):
                     "because DNS is not managed by IPA. Please create DNS "
                     "record manually and then omit --ip-address option.")
                 raise admintool.ScriptError("Cannot add DNS record")
-            if options.reverse_zone and not bindinstance.verify_reverse_zone(
-                    options.reverse_zone, options.ip_address):
-                raise admintool.ScriptError("Invalid reverse zone")
+
+            disconnect = False
+            if not api.Backend.ldap2.isconnected():
+                api.Backend.ldap2.connect(
+                    bind_dn=DN(('cn', 'Directory Manager')),
+                    bind_pw=self.dirman_password)
+                disconnect = True
+
+            options.reverse_zones = bindinstance.check_reverse_zones(
+                options.ip_addresses, options.reverse_zones, options, False,
+                True)
+            if disconnect:
+                api.Backend.ldap2.disconnect()
 
         if options.http_pkcs12:
             if options.http_pin is None:
@@ -293,7 +305,7 @@ class ReplicaPrepare(admintool.AdminTool):
         finally:
             shutil.rmtree(self.top_dir)
 
-        if options.ip_address:
+        if options.ip_addresses:
             self.add_dns_records()
 
         if options.wait_for_dns:
@@ -419,46 +431,38 @@ class ReplicaPrepare(admintool.AdminTool):
         options = self.options
 
         self.log.info("Adding DNS records for %s", self.replica_fqdn)
-        api.Backend.ldap2.connect(
-            bind_dn=DN(('cn', 'Directory Manager')),
-            bind_pw=self.dirman_password)
-
         name, domain = self.replica_fqdn.split(".", 1)
 
-        ip = options.ip_address
-        ip_address = str(ip)
-
-        if options.reverse_zone:
-            reverse_zone = bindinstance.normalize_zone(options.reverse_zone)
-        else:
-            reverse_zone = bindinstance.find_reverse_zone(ip)
-            if reverse_zone is None and not options.no_reverse:
-                reverse_zone = bindinstance.get_reverse_zone_default(ip)
-
+        if not api.Backend.ldap2.isconnected():
+            api.Backend.ldap2.connect(
+                bind_dn=DN(('cn', 'Directory Manager')),
+                bind_pw=self.dirman_password)
         try:
             add_zone(domain)
         except errors.PublicError, e:
             raise admintool.ScriptError(
                 "Could not create forward DNS zone for the replica: %s" % e)
 
-        try:
-            add_fwd_rr(domain, name, ip_address)
-        except errors.PublicError, e:
-            raise admintool.ScriptError(
-                "Could not add forward DNS record for the replica: %s" % e)
+        for reverse_zone in options.reverse_zones:
+            self.log.info("Adding reverse zone %s", reverse_zone)
+            add_zone(reverse_zone)
 
-        if reverse_zone is not None:
-            self.log.info("Using reverse zone %s", reverse_zone)
+        for ip in options.ip_addresses:
+            ip_address = str(ip)
             try:
-                add_zone(reverse_zone)
+                add_fwd_rr(domain, name, ip_address)
             except errors.PublicError, e:
                 raise admintool.ScriptError(
-                    "Could not create reverse DNS zone for replica: %s" % e)
-            try:
-                add_ptr_rr(reverse_zone, ip_address, self.replica_fqdn)
-            except errors.PublicError, e:
-                raise admintool.ScriptError(
-                    "Could not add reverse DNS record for the replica: %s" % e)
+                    "Could not add forward DNS record for the replica: %s" % e)
+
+            if not options.no_reverse:
+                reverse_zone = bindinstance.find_reverse_zone(ip)
+                try:
+                    add_ptr_rr(reverse_zone, ip_address, self.replica_fqdn)
+                except errors.PublicError, e:
+                    raise admintool.ScriptError(
+                        "Could not add reverse DNS record for the replica: %s"
+                        % e)
 
     def check_dns(self, replica_fqdn):
         """Return true if the replica hostname is resolvable"""
-- 
1.9.3

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to