diff --git a/roles/ipaclient/defaults/main.yml b/roles/ipaclient/defaults/main.yml
index 199485a0ee3da995f1f5e155ac84b199575b8396..890f35fde53889f7534488182e8391e4bfc1c52f 100644
--- a/roles/ipaclient/defaults/main.yml
+++ b/roles/ipaclient/defaults/main.yml
@@ -12,8 +12,12 @@ ipaclient_no_dns_lookup: no
 ipaclient_ssh_trust_dns: no
 ipaclient_no_ssh: no
 ipaclient_no_sshd: no
+#ipaclient_no_dns_sshfp: no
+#ipaclient_force: no
+ipaclient_force_ntpd: no
 ipaclient_no_nisdomain: no
 ipaclient_configure_firefox: no
+ipahost_all_ip_addresses: no
 
 ### packages ###
 ipaclient_install_packages: yes
diff --git a/roles/ipaclient/library/ipaclient_test.py b/roles/ipaclient/library/ipaclient_test.py
index 6a0d1e64c50878b590294503847885a04dad615a..a3dead2a57b521d9f82711c557bb4f726b1d592d 100644
--- a/roles/ipaclient/library/ipaclient_test.py
+++ b/roles/ipaclient/library/ipaclient_test.py
@@ -49,15 +49,6 @@ options:
   hostname:
     description: The hostname of the machine to join (FQDN).
     required: false
-  ca_cert_file:
-    description: A CA certificate to use.
-    required: false
-  on_master:
-    description: IPA client installation on IPA server
-    required: false
-    default: false
-    type: bool
-    default: no
   ntp_servers:
     description: List of NTP servers to use
     required: false
@@ -72,14 +63,56 @@ options:
     default: false
     type: bool
     default: no
+  force_ntpd:
+    description: Stop and disable any time&date synchronization services besides ntpd. Deprecated since 4.7.
+    requried: false
+    type: bool
+    default: no
+  nisdomain:
+    description: NIS domain name
+    required: false
   no_nisdomain:
     description: Do not configure NIS domain name
     required: false
     type: bool
     default: no
-  nisdomain:
-    description: NIS domain name
+  kinit_attempts:
+    description: Repeat the request for host Kerberos ticket X times.
+    required: false
+    type: int
+    default: 5
+  ca_cert_files:
+    description: CA certificates to use.
+    required: false
+  configure_firefox:
+    description: Configure Firefox to use IPA domain credentials
+    required: false
+    type: bool
+    default: no
+  firefox_dir:
+    description: Specify directory where Firefox is installed (for example: '/usr/lib/firefox')
+    required: false
+  ip_addresses:
+    description: All routable IP addresses configured on any interface will be added to DNS.
     required: false
+    type: bool
+    default: no
+  all_ip_addresses:
+    description: All routable IP addresses configured on any interface will be added to DNS.
+    required: false
+    type: bool
+    default: no
+  on_master:
+    description: IPA client installation on IPA server
+    required: false
+    default: false
+    type: bool
+    default: no
+  enable_dns_updates:
+    description: Configures the machine to attempt dns updates when the ip address changes.
+    required: false
+    type: bool
+    default: no
 author:
     - Thomas Woerner
 '''
@@ -227,301 +260,591 @@ def get_ipa_conf():
 def main():
     module = AnsibleModule(
         argument_spec = dict(
-            servers=dict(required=False, type='list', default=[]),
-            domain=dict(required=False),
-            realm=dict(required=False),
-            hostname=dict(required=False),
-            ca_cert_file=dict(required=False),
-            on_master=dict(required=False, type='bool', default=False),
-            ntp_servers=dict(required=False, type='list', default=[]),
-            ntp_pool=dict(required=False),
+            ### basic ###
+            domain=dict(required=False, default=None),
+            servers=dict(required=False, type='list', default=None),
+            realm=dict(required=False, default=None),
+            hostname=dict(required=False, default=None),
+            ntp_servers=dict(required=False, type='list', default=None),
+            ntp_pool=dict(required=False, default=None),
             no_ntp=dict(required=False, type='bool', default=False),
-            #no_nisdomain=dict(required=False, type='bool', default='no'),
-            #nisdomain=dict(required=False),
+            force_ntpd=dict(required=False, type='bool', default=False),
+            nisdomain=dict(required=False, default=None),
+            no_nisdomain=dict(required=False, type='bool', default='no'),
+            kinit_attempts=dict(required=False, type='int'),
+            ca_cert_files=dict(required=False, type='list', default=None),
+            configure_firefox=dict(required=False, type='bool', default=False),
+            firefox_dir=dict(required=False),
+            ip_addresses=dict(required=False, type='list', default=None),
+            all_ip_addresses=dict(required=False, type='bool', default=False),
+            on_master=dict(required=False, type='bool', default=False),
+            ### sssd ###
+            enable_dns_updates=dict(required=False, type='bool', default=False),
         ),
         supports_check_mode = True,
     )
 
-    module._ansible_debug = True
-    options.domain = module.params.get('domain')
+    #module._ansible_debug = True
+    options.domain_name = module.params.get('domain')
     options.servers = module.params.get('servers')
-    options.realm = module.params.get('realm')
-    options.hostname = module.params.get('hostname')
-    options.ca_cert_file = module.params.get('ca_cert_file')
-    options.on_master = module.params.get('on_master')
+    options.realm_name = module.params.get('realm')
+    options.host_name = module.params.get('hostname')
     options.ntp_servers = module.params.get('ntp_servers')
     options.ntp_pool = module.params.get('ntp_pool')
     options.no_ntp = module.params.get('no_ntp')
-    options.conf_ntp = not options.no_ntp
-    #options.no_nisdomain = module.params.get('no_nisdomain')
-    #options.nisdomain = module.params.get('nisdomain')
-    #options.ip_addresses
-    #options.all_ip_addresses
-    #options.enable_dns_updates
-
-    hostname = None
-    hostname_source = None
-    dnsok = False
-    cli_domain = None
-    cli_server = None
-    cli_realm = None
-    cli_kdc = None
-    client_domain = None
-    cli_basedn = None
-
-    fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
-    statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE)
+    options.force_ntpd = module.params.get('force_ntpd')
+    options.nisdomain = module.params.get('nisdomain')
+    options.no_nisdomain = module.params.get('no_nisdomain')
+    options.kinit_attempts = module.params.get('kinit_attempts')
+    options.ca_cert_files = module.params.get('ca_cert_files')
+    options.configure_firefox = module.params.get('configure_firefox')
+    options.firefox_dir = module.params.get('firefox_dir')
+    options.ip_addresses = module.params.get('ip_addresses')
+    options.all_ip_addresses = module.params.get('all_ip_addresses')
+    options.on_master = module.params.get('on_master')
+    options.enable_dns_updates = module.params.get('enable_dns_updates')
+
+    # Get domain from first server if domain is not set, but if there are
+    # servers
+    if options.domain_name is None and len(options.servers) > 0:
+        options.domain_name = options.servers[0][options.servers[0].find(".")+1:]
+
+    try:
+        self = options
+
+        ### HostNameInstallInterface ###
+
+        if options.ip_addresses is not None:
+            for value in options.ip_addresses:
+                try:
+                    CheckedIPAddress(value)
+                except Exception as e:
+                    raise ValueError("invalid IP address {0}: {1}".format(
+                        value, e))
+
+        ### ServiceInstallInterface ###
+
+        validate_domain_name(options.domain_name)
+
+        if options.realm_name:
+            validate_domain_name(options.realm_name, entity="realm")
+
+        ### ClientInstallInterface ###
+
+        if options.kinit_attempts < 1:
+            raise ValueError("expects an integer greater than 0.")
+
+        ### ClientInstallInterface.__init__ ###
+
+        if self.servers and not self.domain_name:
+            raise RuntimeError(
+                "--server cannot be used without providing --domain")
 
-    if options.ntp_servers and options.no_ntp:
-        module.fail_json(
-            msg="--ntp-server cannot be used together with --no-ntp")
+        if self.force_ntpd:
+            logger.warning("Option --force-ntpd has been deprecated")
 
-    if options.ntp_pool and options.no_ntp:
-        module.fail_json(
-            msg="--ntp-pool cannot be used together with --no-ntp")
+        if self.ntp_servers and self.no_ntp:
+            raise RuntimeError(
+                "--ntp-server cannot be used together with --no-ntp")
 
-    #if options.no_nisdomain and options.nisdomain:
-    #    module.fail_json(
-    #        "--no-nisdomain cannot be used together with --nisdomain")
+        if self.ntp_pool and self.no_ntp:
+            raise RuntimeError(
+                "--ntp-pool cannot be used together with --no-ntp")
 
-    #if options.ip_addresses:
-    #    if options.enable_dns_updates:
-    #        module.fail_json(
-    #            "--ip-addresses cannot be used together with"
-    #            " --enable-dns-updates")
+        if self.no_nisdomain and self.nisdomain:
+            raise RuntimeError(
+                "--no-nisdomain cannot be used together with --nisdomain")
 
-    #    if options.all_ip_addresses:
-    #        module.fail_json(
-    #            "--ip-address cannot be used together with"
-    #            "--all-ip-addresses")
+        if self.ip_addresses:
+            if self.enable_dns_updates:
+                raise RuntimeError(
+                    "--ip-address cannot be used together with"
+                " --enable-dns-updates")
 
-    if options.hostname:
-        hostname = options.hostname
-        hostname_source = 'Provided as option'
+            if self.all_ip_addresses:
+                raise RuntimeError(
+                    "--ip-address cannot be used together with"
+                    "--all-ip-addresses")
+
+        ### SSSDInstallInterface ###
+
+        self.no_sssd = False
+
+        ### ClientInstall ###
+
+        if options.ca_cert_files is not None:
+            for value in options.ca_cert_files:
+                if not isinstance(value, list):
+                    raise ValueError("Expected list, got {!r}".format(value))
+                # this is what init() does
+                value = value[-1]
+                if not os.path.exists(value):
+                    raise ValueError("'%s' does not exist" % value)
+                if not os.path.isfile(value):
+                    raise ValueError("'%s' is not a file" % value)
+                if not os.path.isabs(value):
+                    raise ValueError("'%s' is not an absolute file path" % value)
+
+                try:
+                    x509.load_certificate_from_file(value)
+                except Exception:
+                    raise ValueError("'%s' is not a valid certificate file" % value)
+
+        #self.prompt_password = self.interactive
+
+        self.no_ac = False
+
+        ### ClientInstall.__init__ ###
+
+        if self.firefox_dir and not self.configure_firefox:
+            raise RuntimeError(
+                "--firefox-dir cannot be used without --configure-firefox "
+                "option")
+
+    except (RuntimeError, ValueError) as e:
+        module.fail_json(msg=str(e))
+
+    ### ipaclient.install.client.init ###
+
+    # root_logger
+    options.debug = False
+    options.unattended = not installer.interactive
+    if options.domain_name:
+        options.domain = normalize_hostname(installer.domain_name)
     else:
-        hostname = socket.getfqdn()
-        hostname_source = "Machine's FQDN"
-    if hostname != hostname.lower():
-        module.fail_json(
-            msg="Invalid hostname '%s', must be lower-case." % hostname)
-
-    if (hostname == 'localhost') or (hostname == 'localhost.localdomain'):
-        module.fail_json(
-            msg="Invalid hostname, '%s' must not be used." % hostname)
-
-    # Get domain from first server if domain is not set, but there are servers
-    if options.domain is None and len(options.servers) > 0:
-        options.domain = options.servers[0][options.servers[0].find(".")+1:]
-
-    # Create the discovery instance
-    ds = ipadiscovery.IPADiscovery()
-
-    ret = ds.search(
-        domain=options.domain,
-        servers=options.servers,
-        realm=options.realm,
-        hostname=hostname,
-        ca_cert_path=get_cert_path(options.ca_cert_file))
-
-    if options.servers and ret != 0:
-        # There is no point to continue with installation as server list was
-        # passed as a fixed list of server and thus we cannot discover any
-        # better result
-        module.fail_json(msg="Failed to verify that %s is an IPA Server." % \
-                         ', '.join(options.servers))
-
-    if ret == ipadiscovery.BAD_HOST_CONFIG:
-        module.fail_json(msg="Can't get the fully qualified name of this host")
-    if ret == ipadiscovery.NOT_FQDN:
-        module.fail_json(msg="%s is not a fully-qualified hostname" % hostname)
-    if ret in (ipadiscovery.NO_LDAP_SERVER, ipadiscovery.NOT_IPA_SERVER) \
-            or not ds.domain:
-        if ret == ipadiscovery.NO_LDAP_SERVER:
-            if ds.server:
-                module.log("%s is not an LDAP server" % ds.server)
-            else:
-                module.log("No LDAP server found")
-        elif ret == ipadiscovery.NOT_IPA_SERVER:
-            if ds.server:
-                module.log("%s is not an IPA server" % ds.server)
-            else:
-                module.log("No IPA server found")
-        else:
-            module.log("Domain not found")
-        if options.domain:
-            cli_domain = options.domain
-            cli_domain_source = 'Provided as option'
-        else:
-            module.fail_json(
-                msg="Unable to discover domain, not provided")
+        options.domain = None
+    options.server = options.servers
+    options.realm = options.realm_name
+    #installer.primary = installer.fixed_primary
+    #if installer.principal:
+    #    installer.password = installer.admin_password
+    #else:
+    #    installer.password = installer.host_password
+    installer.hostname = installer.host_name
+    options.conf_ntp = not options.no_ntp
+    #installer.trust_sshfp = installer.ssh_trust_dns
+    #installer.conf_ssh = not installer.no_ssh
+    #installer.conf_sshd = not installer.no_sshd
+    #installer.conf_sudo = not installer.no_sudo
+    #installer.create_sshfp = not installer.no_dns_sshfp
+    if installer.ca_cert_files:
+        installer.ca_cert_file = installer.ca_cert_files[-1]
+    else:
+        installer.ca_cert_file = None
+    #installer.location = installer.automount_location
+    installer.dns_updates = installer.enable_dns_updates
+    #installer.krb5_offline_passwords = not installer.no_krb5_offline_passwords
+    installer.sssd = not installer.no_sssd
 
-        ret = ds.search(
-            domain=cli_domain,
-            servers=options.servers,
-            hostname=hostname,
-            ca_cert_path=get_cert_path(options.ca_cert_file))
-
-    if not cli_domain:
-        if ds.domain:
-            cli_domain = ds.domain
-            cli_domain_source = ds.domain_source
-            module.debug("will use discovered domain: %s" % cli_domain)
-
-    client_domain = hostname[hostname.find(".")+1:]
-
-    if ret in (ipadiscovery.NO_LDAP_SERVER, ipadiscovery.NOT_IPA_SERVER) \
-            or not ds.server:
-        module.debug("IPA Server not found")
-        if options.servers:
-            cli_server = options.servers
-            cli_server_source = 'Provided as option'
+    try:
+
+        ### client ###
+
+        # global variables
+        hostname = None
+        hostname_source = None
+        nosssd_files = None
+        dnsok = False
+        cli_domain = None
+        cli_server = None
+        subject_base = None
+        cli_realm = None
+        cli_kdc = None
+        client_domain = None
+        cli_basedn = None
+        # end of global variables
+
+        ### client.install_check ###
+
+        logger.info("This program will set up FreeIPA client.")
+        logger.info("Version {}".format(version.VERSION))
+        logger.info("")
+
+        cli_domain_source = 'Unknown source'
+        cli_server_source = 'Unknown source'
+
+        fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
+
+        if not os.getegid() == 0:
+            raise ScriptError(
+                "You must be root to run ipa-client-install.",
+                rval=CLIENT_INSTALL_ERROR)
+
+        tasks.check_selinux_status()
+
+        #if is_ipa_client_installed(fstore, on_master=options.on_master):
+        #    logger.error("IPA client is already configured on this system.")
+        #    logger.info(
+        #        "If you want to reinstall the IPA client, uninstall it first "
+        #        "using 'ipa-client-install --uninstall'.")
+        #    raise ScriptError(rval=CLIENT_ALREADY_CONFIGURED)
+
+        check_ldap_conf()
+
+        if options.conf_ntp:
+            try:
+                timeconf.check_timedate_services()
+            except timeconf.NTPConflictingService as e:
+                logger.info("WARNING: conflicting time&date synchronization service '{}'"
+                            " will be disabled".format(e.conflicting_service))
+                logger.info("in favor of chronyd")
+                logger.info("")
+            except timeconf.NTPConfigurationError:
+                pass
+
+        # password, principal and keytab are checked in tasks/install.yml
+        #if options.unattended and (
+        #    options.password is None and
+        #    options.principal is None and
+        #    options.keytab is None and
+        #    options.prompt_password is False and
+        #    not options.on_master
+        #):
+        #    raise ScriptError(
+        #        "One of password / principal / keytab is required.",
+        #        rval=CLIENT_INSTALL_ERROR)
+
+        if options.hostname:
+            hostname = options.hostname
+            hostname_source = 'Provided as option'
         else:
-            module.fail_json(msg="Unable to find IPA Server to join")
+            hostname = socket.getfqdn()
+            hostname_source = "Machine's FQDN"
+        if hostname != hostname.lower():
+            raise ScriptError(
+                "Invalid hostname '{}', must be lower-case.".format(hostname),
+                rval=CLIENT_INSTALL_ERROR
+            )
+
+        if hostname in ('localhost', 'localhost.localdomain'):
+            raise ScriptError(
+                "Invalid hostname, '{}' must not be used.".format(hostname),
+                rval=CLIENT_INSTALL_ERROR)
+
+        # --no-sssd is not supported any more for rhel-based distros
+        if not tasks.is_nosssd_supported() and not options.sssd:
+            raise ScriptError(
+                "Option '--no-sssd' is incompatible with the 'authselect' tool "
+                "provided by this distribution for configuring system "
+                "authentication resources",
+                rval=CLIENT_INSTALL_ERROR)
+
+        # --noac is not supported any more for rhel-based distros
+        if not tasks.is_nosssd_supported() and options.no_ac:
+            raise ScriptError(
+                "Option '--noac' is incompatible with the 'authselect' tool "
+                "provided by this distribution for configuring system "
+                "authentication resources",
+                rval=CLIENT_INSTALL_ERROR)
+
+        # when installing with '--no-sssd' option, check whether nss-ldap is
+        # installed
+        if not options.sssd:
+            if not os.path.exists(paths.PAM_KRB5_SO):
+                raise ScriptError(
+                    "The pam_krb5 package must be installed",
+                    rval=CLIENT_INSTALL_ERROR)
+
+            (nssldap_installed, nosssd_files) = nssldap_exists()
+            if not nssldap_installed:
+                raise ScriptError(
+                    "One of these packages must be installed: nss_ldap or "
+                    "nss-pam-ldapd",
+                    rval=CLIENT_INSTALL_ERROR)
+
+            # principal and keytab are checked in tasks/install.yml
+            #if options.keytab and options.principal:
+            #    raise ScriptError(
+            #        "Options 'principal' and 'keytab' cannot be used together.",
+            #        rval=CLIENT_INSTALL_ERROR)
+
+            # keytab and force_join are checked in tasks/install.yml
+            #if options.keytab and options.force_join:
+            #    logger.warning("Option 'force-join' has no additional effect "
+            #                   "when used with together with option 'keytab'.")
+
+        # Added with freeipa-4.7.1 >>>
+        # Remove invalid keytab file
+        try:
+            gssapi.Credentials(
+                store={'keytab': paths.KRB5_KEYTAB},
+                usage='accept',
+            )
+        except gssapi.exceptions.GSSError:
+            logger.debug("Deleting invalid keytab: '%s'.", paths.KRB5_KEYTAB)
+            remove_file(paths.KRB5_KEYTAB)
+        # Added with freeipa-4.7.1 <<<
+
+        # Check if old certificate exist and show warning
+        if (
+            not options.ca_cert_file and
+            get_cert_path(options.ca_cert_file) == paths.IPA_CA_CRT
+        ):
+            logger.warning("Using existing certificate '%s'.", paths.IPA_CA_CRT)
+
+        if not check_ip_addresses(options):
+            module.warn("Failed to check ip addresses, check installation log")
+            raise ScriptError(rval=CLIENT_INSTALL_ERROR)
+
+        # Create the discovery instance
+        ds = ipadiscovery.IPADiscovery()
 
         ret = ds.search(
-            domain=cli_domain,
-            servers=cli_server,
+            domain=options.domain,
+            servers=options.server,
+            realm=options.realm_name,
             hostname=hostname,
-            ca_cert_path=get_cert_path(options.ca_cert_file))
-
-    else:
-        # Only set dnsok to True if we were not passed in one or more servers
-        # and if DNS discovery actually worked.
-        if not options.servers:
-            (server, domain) = ds.check_domain(
-                ds.domain, set(), "Validating DNS Discovery")
-            if server and domain:
-                module.debug("DNS validated, enabling discovery")
-                dnsok = True
+            ca_cert_path=get_cert_path(options.ca_cert_file)
+        )
+
+        if options.server and ret != 0:
+            # There is no point to continue with installation as server list was
+            # passed as a fixed list of server and thus we cannot discover any
+            # better result
+            logger.error(
+                "Failed to verify that %s is an IPA Server.",
+                ', '.join(options.server))
+            logger.error(
+                "This may mean that the remote server is not up "
+                "or is not reachable due to network or firewall settings.")
+            print_port_conf_info()
+            module.warn("Failed to verify that %s is an IPA Server." %
+                        ', '.join(options.server))
+            raise ScriptError(rval=CLIENT_INSTALL_ERROR)
+
+        if ret == ipadiscovery.BAD_HOST_CONFIG:
+            logger.error("Can't get the fully qualified name of this host")
+            logger.info("Check that the client is properly configured")
+            module.warn("Can't get the fully qualified name of this host")
+            raise ScriptError(rval=CLIENT_INSTALL_ERROR)
+        if ret == ipadiscovery.NOT_FQDN:
+            raise ScriptError(
+                "{} is not a fully-qualified hostname".format(hostname),
+                rval=CLIENT_INSTALL_ERROR)
+        if ret in (ipadiscovery.NO_LDAP_SERVER, ipadiscovery.NOT_IPA_SERVER) \
+                or not ds.domain:
+            if ret == ipadiscovery.NO_LDAP_SERVER:
+                if ds.server:
+                    logger.debug("%s is not an LDAP server", ds.server)
+                else:
+                    logger.debug("No LDAP server found")
+            elif ret == ipadiscovery.NOT_IPA_SERVER:
+                if ds.server:
+                    logger.debug("%s is not an IPA server", ds.server)
+                else:
+                    logger.debug("No IPA server found")
             else:
-                module.debug("DNS discovery failed, disabling discovery")
+                logger.debug("Domain not found")
+            if options.domain:
+                cli_domain = options.domain
+                cli_domain_source = 'Provided as option'
+            elif options.unattended:
+                raise ScriptError(
+                    "Unable to discover domain, not provided on command line",
+                    rval=CLIENT_INSTALL_ERROR)
+            else:
+                raise ScriptError("No interactive installation")
+            #    logger.info(
+            #        "DNS discovery failed to determine your DNS domain")
+            #    cli_domain = user_input(
+            #        "Provide the domain name of your IPA server (ex: example.com)",
+            #        allow_empty=False)
+            #    cli_domain_source = 'Provided interactively'
+            #    logger.debug(
+            #        "will use interactively provided domain: %s", cli_domain)
+            ret = ds.search(
+                domain=cli_domain,
+                servers=options.server,
+                hostname=hostname,
+                ca_cert_path=get_cert_path(options.ca_cert_file))
+
+        if not cli_domain:
+            if ds.domain:
+                cli_domain = ds.domain
+                cli_domain_source = ds.domain_source
+                logger.debug("will use discovered domain: %s", cli_domain)
+
+        client_domain = hostname[hostname.find(".")+1:]
+
+        if ret in (ipadiscovery.NO_LDAP_SERVER, ipadiscovery.NOT_IPA_SERVER) \
+                or not ds.server:
+            logger.debug("IPA Server not found")
+            if options.server:
+                cli_server = options.server
+                cli_server_source = 'Provided as option'
+            elif options.unattended:
+                raise ScriptError(
+                    "Unable to find IPA Server to join",
+                    rval=CLIENT_INSTALL_ERROR)
+            else:
+                raise ScriptError("No interactive installation")
+            #    logger.debug("DNS discovery failed to find the IPA Server")
+            #    cli_server = [
+            #        user_input(
+            #            "Provide your IPA server name (ex: ipa.example.com)",
+            #            allow_empty=False)
+            #    ]
+            #    cli_server_source = 'Provided interactively'
+            #    logger.debug(
+            #        "will use interactively provided server: %s", cli_server[0])
+            ret = ds.search(
+                domain=cli_domain,
+                servers=cli_server,
+                hostname=hostname,
+                ca_cert_path=get_cert_path(options.ca_cert_file))
+
         else:
-            module.debug(
-                "Using servers from command line, disabling DNS discovery")
-
-    if not cli_server:
-        if options.servers:
-            cli_server = ds.servers
-            cli_server_source = 'Provided as option'
-            module.debug(
-                "will use provided server: %s" % ', '.join(options.servers))
-        elif ds.server:
-            cli_server = ds.servers
-            cli_server_source = ds.server_source
-            module.debug("will use discovered server: %s" % cli_server[0])
-
-    if ret == ipadiscovery.NOT_IPA_SERVER:
-        module.fail_json(msg="%s is not an IPA v2 Server." % cli_server[0])
-
-    if ret == ipadiscovery.NO_ACCESS_TO_LDAP:
-        module.warn("Anonymous access to the LDAP server is disabled.")
-        ret = 0
-
-    if ret == ipadiscovery.NO_TLS_LDAP:
-        module.warn(
-            "The LDAP server requires TLS is but we do not have the CA.")
-        ret = 0
-
-    if ret != 0:
-        module.fail_json(
-            msg="Failed to verify that %s is an IPA Server." % cli_server[0])
-
-    cli_kdc = ds.kdc
-    if dnsok and not cli_kdc:
-        module.fail_json(
-            msg="DNS domain '%s' is not configured for automatic "
-            "KDC address lookup." % ds.realm.lower())
-
-    if dnsok:
-        module.log("Discovery was successful!")
-
-    cli_realm = ds.realm
-    cli_realm_source = ds.realm_source
-    module.debug("will use discovered realm: %s" % cli_realm)
-
-    if options.realm and options.realm != cli_realm:
-        module.fail_json(
-            msg=
-            "The provided realm name [%s] does not match discovered one [%s]" %
-            (options.realm, cli_realm))
-
-    cli_basedn = str(ds.basedn)
-    cli_basedn_source = ds.basedn_source
-    module.debug("will use discovered basedn: %s" % cli_basedn)
-
-    module.log("Client hostname: %s" % hostname)
-    module.debug("Hostname source: %s" % hostname_source)
-    module.log("Realm: %s" % cli_realm)
-    module.debug("Realm source: %s" % cli_realm_source)
-    module.log("DNS Domain: %s" % cli_domain)
-    module.debug("DNS Domain source: %s" % cli_domain_source)
-    module.log("IPA Server: %s" % ', '.join(cli_server))
-    module.debug("IPA Server source: %s" % cli_server_source)
-    module.log("BaseDN: %s" % cli_basedn)
-    module.debug("BaseDN source: %s" % cli_basedn_source)
-
-    # ipa-join would fail with IP address instead of a FQDN
-    for srv in cli_server:
-        try:
-            socket.inet_pton(socket.AF_INET, srv)
-            is_ipaddr = True
-        except socket.error:
+            # Only set dnsok to True if we were not passed in one or more servers
+            # and if DNS discovery actually worked.
+            if not options.server:
+                (server, domain) = ds.check_domain(
+                    ds.domain, set(), "Validating DNS Discovery")
+                if server and domain:
+                    logger.debug("DNS validated, enabling discovery")
+                    dnsok = True
+                else:
+                    logger.debug("DNS discovery failed, disabling discovery")
+            else:
+                logger.debug(
+                    "Using servers from command line, disabling DNS discovery")
+
+        if not cli_server:
+            if options.server:
+                cli_server = ds.servers
+                cli_server_source = 'Provided as option'
+                logger.debug(
+                    "will use provided server: %s", ', '.join(options.server))
+            elif ds.server:
+                cli_server = ds.servers
+                cli_server_source = ds.server_source
+                logger.debug("will use discovered server: %s", cli_server[0])
+
+        if ret == ipadiscovery.NOT_IPA_SERVER:
+            logger.error("%s is not an IPA v2 Server.", cli_server[0])
+            print_port_conf_info()
+            logger.debug("(%s: %s)", cli_server[0], cli_server_source)
+            raise ScriptError(rval=CLIENT_INSTALL_ERROR)
+
+        if ret == ipadiscovery.NO_ACCESS_TO_LDAP:
+            logger.warning("Anonymous access to the LDAP server is disabled.")
+            logger.info("Proceeding without strict verification.")
+            logger.info(
+                "Note: This is not an error if anonymous access "
+                "has been explicitly restricted.")
+            ret = 0
+
+        if ret == ipadiscovery.NO_TLS_LDAP:
+            logger.warning(
+                "The LDAP server requires TLS is but we do not have the CA.")
+            logger.info("Proceeding without strict verification.")
+            ret = 0
+
+        if ret != 0:
+            logger.error(
+                "Failed to verify that %s is an IPA Server.",
+                cli_server[0])
+            logger.error(
+                "This may mean that the remote server is not up "
+                "or is not reachable due to network or firewall settings.")
+            print_port_conf_info()
+            logger.debug("(%s: %s)", cli_server[0], cli_server_source)
+            raise ScriptError(rval=CLIENT_INSTALL_ERROR)
+
+        cli_kdc = ds.kdc
+        if dnsok and not cli_kdc:
+            logger.error(
+                "DNS domain '%s' is not configured for automatic "
+                "KDC address lookup.", ds.realm.lower())
+            logger.debug("(%s: %s)", ds.realm, ds.realm_source)
+            logger.error("KDC address will be set to fixed value.")
+
+        if dnsok:
+            logger.info("Discovery was successful!")
+        elif not options.unattended:
+            raise ScriptError("No interactive installation")
+        #    if not options.server:
+        #        logger.warning(
+        #            "The failure to use DNS to find your IPA "
+        #            "server indicates that your resolv.conf file is not properly "
+        #            "configured.")
+        #    logger.info(
+        #        "Autodiscovery of servers for failover cannot work "
+        #        "with this configuration.")
+        #    logger.info(
+        #        "If you proceed with the installation, services "
+        #        "will be configured to always access the discovered server for "
+        #        "all operations and will not fail over to other servers in case "
+        #        "of failure.")
+        #    if not user_input(
+        #            "Proceed with fixed values and no DNS discovery?", False):
+        #        raise ScriptError(rval=CLIENT_INSTALL_ERROR)
+
+        cli_realm = ds.realm
+        cli_realm_source = ds.realm_source
+        logger.debug("will use discovered realm: %s", cli_realm)
+
+        if options.realm_name and options.realm_name != cli_realm:
+            logger.error(
+                "The provided realm name [%s] does not match discovered one [%s]",
+                options.realm_name, cli_realm)
+            logger.debug("(%s: %s)", cli_realm, cli_realm_source)
+            raise ScriptError(rval=CLIENT_INSTALL_ERROR)
+
+        cli_basedn = ds.basedn
+        cli_basedn_source = ds.basedn_source
+        logger.debug("will use discovered basedn: %s", cli_basedn)
+        subject_base = DN(('O', cli_realm))
+
+        logger.info("Client hostname: %s", hostname)
+        logger.debug("Hostname source: %s", hostname_source)
+        logger.info("Realm: %s", cli_realm)
+        logger.debug("Realm source: %s", cli_realm_source)
+        logger.info("DNS Domain: %s", cli_domain)
+        logger.debug("DNS Domain source: %s", cli_domain_source)
+        logger.info("IPA Server: %s", ', '.join(cli_server))
+        logger.debug("IPA Server source: %s", cli_server_source)
+        logger.info("BaseDN: %s", cli_basedn)
+        logger.debug("BaseDN source: %s", cli_basedn_source)
+
+        # ipa-join would fail with IP address instead of a FQDN
+        for srv in cli_server:
             try:
-                socket.inet_pton(socket.AF_INET6, srv)
+                socket.inet_pton(socket.AF_INET, srv)
                 is_ipaddr = True
             except socket.error:
-                is_ipaddr = False
+                try:
+                    socket.inet_pton(socket.AF_INET6, srv)
+                    is_ipaddr = True
+                except socket.error:
+                    is_ipaddr = False
+
+            if is_ipaddr:
+                logger.info()
+                logger.warning(
+                    "It seems that you are using an IP address "
+                    "instead of FQDN as an argument to --server. The "
+                    "installation may fail.")
+                break
 
-        if is_ipaddr:
-            module.warn(
-                "It seems that you are using an IP address "
-                "instead of FQDN as an argument to --server. The "
-                "installation may fail.")
-            break
+        #logger.info()
+        #if not options.unattended and not user_input(
+        #        "Continue to configure the system with these values?", False):
+        #    raise ScriptError(rval=CLIENT_INSTALL_ERROR)
 
-    ntp_servers = [ ]
-    if sync_time is not None:
-        if options.conf_ntp:
-            # Attempt to configure and sync time with NTP server (chrony).
-            sync_time(options, fstore, statestore)
-        elif options.on_master:
-            # If we're on master skipping the time sync here because it was done
-            # in ipa-server-install
-            logger.info("Skipping attempt to configure and synchronize time with"
-                        " chrony server as it has been already done on master.")
-        else:
-            logger.info("Skipping chrony configuration")
-
-    elif not options.on_master and options.conf_ntp:
-        # Attempt to sync time with IPA server.
-        # If we're skipping NTP configuration, we also skip the time sync here.
-        # We assume that NTP servers are discoverable through SRV records
-        # in the DNS.
-        # If that fails, we try to sync directly with IPA server,
-        # assuming it runs NTP
-        if len(options.ntp_servers) < 1:
-            # Detect NTP servers
-            ds = ipadiscovery.IPADiscovery()
-            ntp_servers = ds.ipadns_search_srv(cli_domain, '_ntp._udp',
-                                               None, break_on_first=False)
-        else:
-            ntp_servers = options.ntp_servers
-
-        # Attempt to sync time:
-        # At first with given or dicovered time servers. If no ntp
-        # servers have been given or discovered, then with the ipa
-        # server.
-        module.log('Synchronizing time ...')
-        synced_ntp = False
-        # use user specified NTP servers if there are any
-        for s in ntp_servers:
-            synced_ntp = timeconf.synconce_ntp(s, False)
-            if synced_ntp:
-                break
-        if not synced_ntp and not ntp_servers:
-            synced_ntp = timeconf.synconce_ntp(cli_server[0], False)
-        if not synced_ntp:
-            module.warn("Unable to sync time with NTP server")
+    except ScriptError as e:
+        module.warn("2nd part: %s" % e)
+        module.fail_json(msg=str(e))
+
+    #########################################################################
+
+    ### client._install ###
+
+    statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE)
+
+    # May not happen in here at this time
+    #if not options.on_master:
+    #    # Try removing old principals from the keytab
+    #    purge_host_keytab(cli_realm)
 
     # Check if ipa client is already configured
     if is_client_configured():
@@ -535,16 +858,16 @@ def main():
                                         "with a conflicting realm")
 
     # Done
-    module.exit_json(changed=True,
+    module.exit_json(changed=False,
                      servers=cli_server,
                      domain=cli_domain,
                      realm=cli_realm,
                      kdc=cli_kdc,
-                     basedn=cli_basedn,
+                     basedn=str(cli_basedn),
                      hostname=hostname,
                      client_domain=client_domain,
                      dnsok=dnsok,
-                     ntp_servers=ntp_servers,
+                     sssd=options.sssd,
                      ipa_python_version=IPA_PYTHON_VERSION)
 
 if __name__ == '__main__':
diff --git a/roles/ipaclient/module_utils/ansible_ipa_client.py b/roles/ipaclient/module_utils/ansible_ipa_client.py
index 44371792e328f724682f7e614596d8da8caebf92..efc7f690eeff454b8c87466f62809ad5737f7058 100644
--- a/roles/ipaclient/module_utils/ansible_ipa_client.py
+++ b/roles/ipaclient/module_utils/ansible_ipa_client.py
@@ -31,19 +31,51 @@ if NUM_VERSION < 30201:
 else:
     IPA_PYTHON_VERSION = NUM_VERSION
 
-class options_obj(object):
-    pass
-options = options_obj()
+class installer_obj(object):
+    def __init__(self):
+        pass
+
+    def set_logger(self, logger):
+        self.logger = logger
+
+    #def __getattribute__(self, attr):
+    #    value = super(installer_obj, self).__getattribute__(attr)
+    #    if not attr.startswith("--") and not attr.endswith("--"):
+    #        logger.debug(
+    #            "  <-- Accessing installer.%s (%s)" % (attr, repr(value)))
+    #    return value
+
+    def __getattr__(self, attr):
+        #logger.info("  --> ADDING missing installer.%s" % attr)
+        self.logger.warn("  --> ADDING missing installer.%s" % attr)
+        setattr(self, attr, None)
+        return getattr(self, attr)
+
+    #def __setattr__(self, attr, value):
+    #    logger.debug("  --> Setting installer.%s to %s" % (attr, repr(value)))
+    #    return super(installer_obj, self).__setattr__(attr, value)
+
+    def knobs(self):
+        for name in self.__dict__:
+            yield self, name
+
+# Initialize installer settings
+installer = installer_obj()
+# Create options
+options = installer
+options.interactive = False
 
 if NUM_VERSION >= 40400:
     # IPA version >= 4.4
 
     import sys
     import inspect
+    import gssapi
     import logging
 
     import six
 
+    from ipapython import version
     try:
         from ipaclient.install import ipadiscovery
     except ImportError:
@@ -63,6 +95,9 @@ if NUM_VERSION >= 40400:
         from ipalib import certstore
     from ipalib.rpc import delete_persistent_client_session_data
     from ipapython import certdb, ipautil
+    from ipapython.admintool import ScriptError
+    from ipapython.ipautil import CheckedIPAddress
+    from ipalib.util import validate_domain_name, normalize_hostname
     from ipaplatform import services
     from ipaplatform.paths import paths
     from ipaplatform.tasks import tasks
@@ -84,7 +119,11 @@ if NUM_VERSION >= 40400:
             configure_certmonger, update_ssh_keys, configure_openldap_conf, \
             hardcode_ldap_server, get_certs_from_ldap, save_state, \
             create_ipa_nssdb, configure_ssh_config, configure_sshd_config, \
-            configure_automount, configure_firefox, configure_nisdomain
+            configure_automount, configure_firefox, configure_nisdomain, \
+            CLIENT_INSTALL_ERROR, is_ipa_client_installed, \
+            CLIENT_ALREADY_CONFIGURED, nssldap_exists, remove_file, \
+            check_ip_addresses, print_port_conf_info, configure_ipa_conf, \
+            purge_host_keytab, configure_sssd_conf
     except ImportError:
         # Create temporary copy of ipa-client-install script (as
         # ipa_client_install.py) to be able to import the script easily
@@ -125,6 +164,7 @@ if NUM_VERSION >= 40400:
             configure_krb5_conf = ipa_client_install.configure_krb5_conf
         if NUM_VERSION < 40100:
             get_ca_cert = ipa_client_install.get_ca_cert
+            get_ca_certs = None
         else:
             get_ca_certs = ipa_client_install.get_ca_certs
         SECURE_PATH = ("/bin:/sbin:/usr/kerberos/bin:/usr/kerberos/sbin:/usr/bin:/usr/sbin")
@@ -179,9 +219,16 @@ if NUM_VERSION >= 40400:
     except ImportError:
         check_ldap_conf = None
 
+    try:
+        from ipaclient.install.client import sssd_enable_ifp
+    except ImportError:
+        sssd_enable_ifp = None
+
     logger = logging.getLogger("ipa-client-install")
+    root_logger = logger
 
 else:
     # IPA version < 4.4
 
     raise Exception("freeipa version '%s' is too old" % VERSION)
+
diff --git a/roles/ipaclient/tasks/install.yml b/roles/ipaclient/tasks/install.yml
index b770195a70dddbc12cd7ba44c0bc9eb2458eb203..ba16a9a72b952568999077341d17b81ba09a1c29 100644
--- a/roles/ipaclient/tasks/install.yml
+++ b/roles/ipaclient/tasks/install.yml
@@ -21,17 +21,36 @@
     ipaclient_servers: "{{ groups['ipaserver'] | list }}"
   when: ipaclient_no_dns_lookup | bool and groups.ipaserver is defined and ipaclient_servers is not defined
 
-- name: Install - IPA discovery
+- fail: msg="ipaadmin_principal and ipaadmin_keytab cannot be used together"
+  when: ipaadmin_keytab is defined and ipaadmin_principal is defined
+
+- name: Install - Set default principal if no keytab is given
+  set_fact:
+    ipaadmin_principal: admin
+  when: ipaadmin_principal is undefined and ipaclient_keytab is undefined
+
+- name: Install - IPA client test
   ipaclient_test:
+    ### basic ###
     domain: "{{ ipaserver_domain | default(ipaclient_domain) | default(omit) }}"
     servers: "{{ ipaclient_servers | default(omit) }}"
     realm: "{{ ipaserver_realm | default(ipaclient_realm) | default(omit) }}"
     hostname: "{{ ipaclient_hostname | default(ansible_fqdn) }}"
-    ca_cert_file: "{{ ipaclient_ca_cert_file | default(omit) }}"
-    on_master: "{{ ipaclient_on_master }}"
-    ntp_servers: "{{ ipaclient_ntp_servers | default([]) }}"
+    ntp_servers: "{{ ipaclient_ntp_servers | default(omit) }}"
     ntp_pool: "{{ ipaclient_ntp_pool | default(omit) }}"
     no_ntp: "{{ ipaclient_no_ntp }}"
+    force_ntpd: "{{ ipaclient_force_ntpd }}"
+    nisdomain: "{{ ipaclient_nisdomain | default(omit) }}"
+    no_nisdomain: "{{ ipaclient_no_nisdomain }}"
+    kinit_attempts: "{{ ipaclient_kinit_attempts }}"
+    ca_cert_files: "{{ ipaclient_ca_cert_file | default(omit) }}"
+    configure_firefox: "{{ ipaclient_configure_firefox }}"
+    firefox_dir: "{{ ipaclient_firefox_dir | default(omit) }}"
+    ip_addresses: "{{ ipaclient_ip_addresses | default(omit) }}"
+    all_ip_addresses: "{{ ipahost_all_ip_addresses }}"
+    on_master: "{{ ipaclient_on_master }}"
+    ### sssd ###
+    enable_dns_updates: "{{ ipassd_enable_dns_updates }}"
   register: result_ipaclient_test
 
 - name: Install - Set default principal if no keytab is given