On 10.5.2011 20:06, Jan Cholasta wrote:
Parse netmasks in IP addresses passed to server install.

ticket 1212

Patch updated.

TODO: Write unit test for ipapython.ipautil.CheckedIPAddress
TODO: Clean unreachable code paths off of ipa-server-install (?)
TODO: Workarounds for netaddr bugs (?)

--
Jan Cholasta
>From 778ea5b4177bd6dc5e8866e6c84adbc104095b1a Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Fri, 20 May 2011 20:19:25 +0200
Subject: [PATCH] Parse netmasks in IP addresses passed to server install.

ticket 1212
---
 freeipa.spec.in                   |    1 +
 install/tools/ipa-dns-install     |    9 +++--
 install/tools/ipa-replica-install |    4 ++-
 install/tools/ipa-replica-prepare |   11 +++---
 install/tools/ipa-server-install  |   36 ++++++++++-----------
 ipapython/config.py               |   13 +++++++-
 ipapython/ipautil.py              |   63 +++++++++++++++++++++++++++++++++++++
 ipaserver/install/installutils.py |   39 ++++++++++------------
 8 files changed, 126 insertions(+), 50 deletions(-)

diff --git a/freeipa.spec.in b/freeipa.spec.in
index b936616..fba2f31 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -188,6 +188,7 @@ Requires: python-kerberos >= 1.1-3
 %endif
 Requires: authconfig
 Requires: gnupg
+Requires: iproute
 Requires: pyOpenSSL
 Requires: python-nss >= 0.11
 Requires: python-lxml
diff --git a/install/tools/ipa-dns-install b/install/tools/ipa-dns-install
index aac85bf..491585b 100755
--- a/install/tools/ipa-dns-install
+++ b/install/tools/ipa-dns-install
@@ -37,9 +37,10 @@ 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", help="Master Server IP Address")
+    parser.add_option("--ip-address", dest="ip_address",
+                      type="ipnet", help="Master Server IP Address")
     parser.add_option("--forwarder", dest="forwarders", action="append",
-                      help="Add a DNS forwarder")
+                      type="ipaddr", 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("--no-reverse", dest="no_reverse",
@@ -130,12 +131,14 @@ def main():
     if options.ip_address:
         ip_address = options.ip_address
     else:
-        ip_address = resolve_host(api.env.host)
+        hostaddr = resolve_host(api.env.host)
+        ip_address = hostaddr and ipautil.CheckedIPAddress(hostaddr)
     if not ip_address or not verify_ip_address(ip_address):
         if options.unattended:
             sys.exit("Unable to resolve IP address for host name")
         else:
             ip_address = read_ip_address(api.env.host, fstore)
+    ip_address = str(ip_address)
     logging.debug("will use ip_address: %s\n", ip_address)
 
     if options.no_forwarders:
diff --git a/install/tools/ipa-replica-install b/install/tools/ipa-replica-install
index 49df7fe..2b7c8ca 100755
--- a/install/tools/ipa-replica-install
+++ b/install/tools/ipa-replica-install
@@ -63,7 +63,7 @@ def parse_options():
     parser.add_option("--setup-dns", dest="setup_dns", action="store_true",
                       default=False, help="configure bind with our zone")
     parser.add_option("--forwarder", dest="forwarders", action="append",
-                      help="Add a DNS forwarder")
+                      type="ipaddr", 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("--no-reverse", dest="no_reverse", action="store_true",
@@ -285,6 +285,8 @@ def install_bind(config, options):
     ip_address = resolve_host(config.host_name)
     if not ip_address:
         sys.exit("Unable to resolve IP address for host name")
+    ip = installutils.parse_ip_address(ip_address)
+    ip_address = str(ip)
 
     create_reverse = True
     if options.unattended:
diff --git a/install/tools/ipa-replica-prepare b/install/tools/ipa-replica-prepare
index e912235..04a6478 100755
--- a/install/tools/ipa-replica-prepare
+++ b/install/tools/ipa-replica-prepare
@@ -53,7 +53,7 @@ def parse_options():
     parser.add_option("-p", "--password", dest="password", 
                       help="Directory Manager (existing master) password")
     parser.add_option("--ip-address", dest="ip_address",
-                      help="Add A and PTR records of the future replica")
+                      type="ipnet", help="Add A and PTR records of the future replica")
     parser.add_option("--ca", dest="ca_file", default="/root/cacert.p12",
                       help="Location of CA PKCS#12 file, default /root/cacert.p12")
     parser.add_option("--no-pkinit", dest="setup_pkinit", action="store_false",
@@ -425,10 +425,11 @@ def main():
         name = domain.pop(0)
         domain = ".".join(domain)
 
-        zone = add_zone(domain, nsaddr=options.ip_address)
-        add_rr(zone, name, "A", options.ip_address)
-        add_reverse_zone(options.ip_address)
-        add_ptr_rr(options.ip_address, replica_fqdn)
+        ip_address = str(options.ip_address)
+        zone = add_zone(domain, nsaddr=ip_address)
+        add_rr(zone, name, "A", ip_address)
+        add_reverse_zone(ip_address)
+        add_ptr_rr(ip_address, replica_fqdn)
 
 try:
     if not os.geteuid()==0:
diff --git a/install/tools/ipa-server-install b/install/tools/ipa-server-install
index 00b1334..4a93802 100755
--- a/install/tools/ipa-server-install
+++ b/install/tools/ipa-server-install
@@ -100,11 +100,12 @@ def parse_options():
     parser.add_option("", "--external_ca_file", dest="external_ca_file",
                       help="File containing PKCS#10 of the external CA chain")
     parser.add_option("--hostname", dest="host_name", help="fully qualified name of server")
-    parser.add_option("--ip-address", dest="ip_address", help="Master Server IP Address")
+    parser.add_option("--ip-address", dest="ip_address",
+                      type="ipnet", help="Master Server IP Address")
     parser.add_option("--setup-dns", dest="setup_dns", action="store_true",
                       default=False, help="configure bind with our zone")
     parser.add_option("--forwarder", dest="forwarders", action="append",
-                      help="Add a DNS forwarder")
+                      type="ipaddr", 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("--no-reverse", dest="no_reverse", action="store_true",
@@ -615,37 +616,34 @@ def main():
     domain_name = domain_name.lower()
 
     # Check we have a public IP that is associated with the hostname
-    ip = resolve_host(host_name)
-    if ip is None:
-        if options.ip_address:
-            ip = options.ip_address
+    hostaddr = resolve_host(host_name)
+    if hostaddr is not None:
+        ip = CheckedIPAddress(hostaddr)
+    else:
+        ip = options.ip_address
     if ip is None and options.unattended:
         sys.exit("Unable to resolve IP address for host name")
 
     if not verify_ip_address(ip):
-        ip = ""
+        ip = None
         if options.unattended:
             sys.exit(1)
 
-    if options.ip_address and options.ip_address != ip:
-        if options.setup_dns:
-            if not verify_ip_address(options.ip_address):
-                return 1
-            ip = options.ip_address
-        else:
+    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."
             return 1
 
-    if options.unattended:
-        if not ip:
-            sys.exit("Unable to resolve IP address")
+        ip = options.ip_address
+        if not verify_ip_address(ip):
+            return 1
 
-    if not ip:
+    if ip is None:
         ip = read_ip_address(host_name, fstore)
-        logging.debug("read ip_address: %s\n" % ip)
-    ip_address = ip
+        logging.debug("read ip_address: %s\n" % str(ip))
+    ip_address = str(ip)
 
     print "The IPA Master Server will be configured with"
     print "Hostname:    " + host_name
diff --git a/ipapython/config.py b/ipapython/config.py
index 7e5b195..c785085 100644
--- a/ipapython/config.py
+++ b/ipapython/config.py
@@ -18,7 +18,8 @@
 #
 
 import ConfigParser
-from optparse import Option, Values, OptionParser, IndentedHelpFormatter
+from optparse import Option, Values, OptionParser, IndentedHelpFormatter, OptionValueError
+from copy import copy
 
 import socket
 import ipapython.dnsclient
@@ -46,12 +47,22 @@ class IPAFormatter(IndentedHelpFormatter):
             ret += "%s %s\n" % (spacing, line)
         return ret
 
+def check_ip_option(option, opt, value):
+    from ipapython.ipautil import CheckedIPAddress
+    try:
+        return CheckedIPAddress(value, parse_netmask=(option.type == "ipnet"))
+    except Exception as e:
+        raise OptionValueError("option %s: invalid IP address %s: %s" % (opt, value, e))
+
 class IPAOption(Option):
     """
     optparse.Option subclass with support of options labeled as
     security-sensitive such as passwords.
     """
     ATTRS = Option.ATTRS + ["sensitive"]
+    TYPES = Option.TYPES + ("ipaddr", "ipnet")
+    TYPE_CHECKER = copy(Option.TYPE_CHECKER)
+    TYPE_CHECKER["ipaddr"] = TYPE_CHECKER["ipnet"] = check_ip_option
 
 class IPAOptionParser(OptionParser):
     """
diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index 4280cd9..2ad9240 100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -39,6 +39,7 @@ from types import *
 import re
 import xmlrpclib
 import datetime
+import netaddr
 from ipapython import config
 try:
     from subprocess import CalledProcessError
@@ -63,6 +64,68 @@ def get_domain_name():
 
     return domain_name
 
+class CheckedIPAddress(netaddr.IPAddress):
+    def __init__(self, addr, match_local=True, parse_netmask=True):
+        if isinstance(addr, CheckedIPAddress):
+            super(CheckedIPAddress, self).__init__(addr)
+            self.prefixlen = addr.prefixlen
+            self.interface = addr.interface
+            return
+
+        net = None
+        iface = None
+
+        if isinstance(addr, netaddr.IPNetwork):
+            net = addr
+            addr = net.ip
+        elif isinstance(addr, netaddr.IPAddress):
+            pass
+        else:
+            try:
+                addr = netaddr.IPAddress(addr)
+            except ValueError:
+                net = netaddr.IPNetwork(addr)
+                if not parse_netmask:
+                    raise ValueError("netmask and prefix length not allowed here")
+                addr = net.ip
+
+        if addr.version not in (4, 6):
+            raise ValueError("unsupported IP version")
+        if addr.is_loopback():
+            raise ValueError("cannot use loopback IP address")
+
+        if match_local:
+            if addr.version == 4:
+                family = 'inet'
+            elif addr.version == 6:
+                family = 'inet6'
+
+            ipresult = run(['/sbin/ip', '-family', family, '-oneline', 'address', 'show'])
+            lines = ipresult[0].split('\n')
+            for line in lines:
+                fields = line.split()
+                if len(fields) < 4:
+                    continue
+
+                ifnet = netaddr.IPNetwork(fields[3])
+                if ifnet == net or ifnet.ip == addr:
+                    net = ifnet
+                    iface = fields[1]
+                    break
+
+        if net is None:
+            if addr.version == 4:
+                net = netaddr.IPNetwork(netaddr.cidr_abbrev_to_verbose(str(addr)))
+            elif addr.version == 6:
+                net = netaddr.IPNetwork(str(addr) + '/64')
+
+        super(CheckedIPAddress, self).__init__(addr)
+        self.prefixlen = net.prefixlen
+        self.interface = iface
+
+    def is_local(self):
+        return self.interface is not None
+
 def realm_to_suffix(realm_name):
     s = realm_name.split(".")
     terms = ["dc=" + x.lower() for x in s]
diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
index 3868c4d..480c350 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -148,17 +148,18 @@ def verify_fqdn(host_name,no_host_dns=False):
     else:
         print "Warning: Hostname (%s) not found in DNS" % host_name
 
-def verify_ip_address(ip):
-    is_ok = True
+def parse_ip_address(addr, match_local=True, parse_netmask=True):
     try:
-        socket.inet_pton(socket.AF_INET, ip)
-    except:
-        try:
-            socket.inet_pton(socket.AF_INET6, ip)
-        except:
-            print "Unable to verify IP address"
-            is_ok = False
-    return is_ok
+        ip = ipautil.CheckedIPAddress(addr, match_local=match_local, parse_netmask=parse_netmask)
+        if match_local and not ip.is_local():
+            print "Warning: No network interface matches IP address %s" % addr
+        return ip
+    except Exception as e:
+        print "Error: Invalid IP Address %s: %s" % (addr, e)
+        return None
+
+def verify_ip_address(addr, match_local=True, parse_netmask=True):
+    return parse_ip_address(addr, match_local, parse_netmask) is not None
 
 def record_in_hosts(ip, host_name, file="/etc/hosts"):
     hosts = open(file, 'r').readlines()
@@ -191,19 +192,17 @@ def add_record_to_hosts(ip, host_name, file="/etc/hosts"):
 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)
+        ip_parsed = parse_ip_address(ip)
 
-        if ip == "127.0.0.1" or ip == "::1":
-            print "The IPA Server can't use localhost as a valid IP"
-            continue
-
-        if verify_ip_address(ip):
+        if ip_parsed is not None:
             break
 
+    ip = str(ip_parsed)
     print "Adding ["+ip+" "+host_name+"] to your /etc/hosts file"
     fstore.backup_file("/etc/hosts")
     add_record_to_hosts(ip, host_name)
 
-    return ip
+    return ip_parsed
 
 def read_dns_forwarders():
     addrs = []
@@ -215,15 +214,13 @@ def read_dns_forwarders():
                                     allow_empty=True)
             if not ip:
                 break
-            if ip == "127.0.0.1" or ip == "::1":
-                print "You cannot use localhost as a DNS forwarder"
-                continue
-            if not verify_ip_address(ip):
+            ip_parsed = parse_ip_address(ip, match_local=False, parse_netmask=False)
+            if ip_parsed is None:
                 print "DNS forwarder %s not added" % ip
                 continue
 
             print "DNS forwarder %s added" % ip
-            addrs.append(ip)
+            addrs.append(str(ip_parsed))
 
     if not addrs:
         print "No DNS forwarders configured"
-- 
1.7.4.4

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

Reply via email to