#!/usr/bin/python3
#
# Authors: Sumit Bose <sbose@redhat.com>
# Based on ipa-server-install by Karl MacMillan <kmacmillan@mentalrootkit.com>
# and ipa-dns-install by Martin Nagy
#
# Copyright (C) 2011  Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

from __future__ import print_function

import logging
import os
import sys

import six

from optparse import SUPPRESS_HELP  # pylint: disable=deprecated-module

from ipalib.install import sysrestore
from ipaserver.install import adtrust, service
from ipaserver.install.installutils import (
    read_password,
    check_server_configuration,
    run_script)
from ipapython.admintool import ScriptError
from ipapython import version
from ipapython import ipautil
from ipalib import api, errors, krb_utils
from ipapython.config import IPAOptionParser
from ipaplatform.paths import paths
from ipapython.ipa_log_manager import standard_logging_setup

if six.PY3:
    unicode = str

logger = logging.getLogger(os.path.basename(__file__))

log_file_name = paths.IPASERVER_INSTALL_LOG


def parse_options():
    parser = IPAOptionParser(version=version.VERSION)
    parser.add_option("-d", "--debug", dest="debug", action="store_true",
                      default=False, help="print debugging information")
    parser.add_option("--netbios-name", dest="netbios_name",
                      help="NetBIOS name of the IPA domain")

    # no-msdcs has not effect, option is here just for backward compatibility
    parser.add_option("--no-msdcs", dest="no_msdcs", action="store_true",
                      default=False, help=SUPPRESS_HELP)

    parser.add_option("--rid-base", dest="rid_base", type=int, default=1000,
                      help="Start value for mapping UIDs and GIDs to RIDs")
    parser.add_option("--secondary-rid-base", dest="secondary_rid_base",
                      type=int, default=100000000,
                      help="Start value of the secondary range for mapping "
                           "UIDs and GIDs to RIDs")
    parser.add_option("-U", "--unattended", dest="unattended",
                      action="store_true",
                      default=False,
                      help="unattended installation never prompts the user")
    parser.add_option("-a", "--admin-password",
                      sensitive=True, dest="admin_password",
                      help="admin user kerberos password")
    parser.add_option("-A", "--admin-name",
                      sensitive=True, dest="admin_name", default='admin',
                      help="admin user principal")
    parser.add_option("--add-sids", dest="add_sids", action="store_true",
                      default=False, help="Add SIDs for existing users and"
                                          " groups as the final step")
    parser.add_option("--add-agents", dest="add_agents", action="store_true",
                      default=False,
                      help="Add IPA masters to a list of hosts allowed to "
                      "serve information about users from trusted forests")
    parser.add_option("--enable-compat",
                      dest="enable_compat", default=False, action="store_true",
                      help="Enable support for trusted domains for old "
                           "clients")

    options, _args = parser.parse_args()
    safe_options = parser.get_safe_opts(options)

    return safe_options, options


def read_admin_password(admin_name):
    print("Configuring cross-realm trusts for IPA server requires password "
          "for user '%s'." % (admin_name))
    print("This user is a regular system account used for IPA server "
          "administration.")
    print("")
    admin_password = read_password(admin_name, confirm=False, validate=None)
    return admin_password


def ensure_admin_kinit(admin_name, admin_password):
    try:
        ipautil.run([paths.KINIT, admin_name], stdin=admin_password+'\n')
    except ipautil.CalledProcessError:
        print("There was error to automatically re-kinit your admin user "
              "ticket.")
        return False
    return True


def main():
    safe_options, options = parse_options()

    if os.getegid() != 0:
        raise ScriptError("Must be root to setup AD trusts on server")

    standard_logging_setup(log_file_name, debug=options.debug, filemode='a')
    print("\nThe log file for this installation can be found in %s"
          % log_file_name)

    logger.debug('%s was invoked with options: %s', sys.argv[0], safe_options)
    logger.debug(
        "missing options might be asked for interactively later\n")
    logger.debug('IPA version %s', version.VENDOR_VERSION)

    check_server_configuration()

    fstore = sysrestore.FileStore(paths.SYSRESTORE)

    print("================================================================"
          "==============")
    print("This program will setup components needed to establish trust to "
          "AD domains for")
    print("the FreeIPA Server.")
    print("")
    print("This includes:")
    print("  * Configure Samba")
    print("  * Add trust related objects to FreeIPA LDAP server")
    # TODO:
    # print "  * Add a SID to all users and Posix groups"
    print("")
    print("To accept the default shown in brackets, press the Enter key.")
    print("")

    # Check if samba packages are installed
    # the same check is in the adtrust module but we must fail first if the
    # package is missing
    adtrust.check_for_installed_deps()

    # Initialize the ipalib api
    api.bootstrap(
        in_server=True,
        debug=options.debug,
        context='install',
        confdir=paths.ETC_IPA
    )
    api.finalize()

    admin_password = options.admin_password
    if not (options.unattended or admin_password):
        admin_password = read_admin_password(options.admin_name)

    admin_kinited = None
    if admin_password:
        admin_kinited = ensure_admin_kinit(options.admin_name, admin_password)
        if not admin_kinited:
            print("Proceeding with credentials that existed before")

    try:
        principal = krb_utils.get_principal()
    except errors.CCacheError as e:
        raise ScriptError(
            "Must have Kerberos credentials to setup AD trusts on server: "
            "{err}".format(err=e))

    try:
        api.Backend.ldap2.connect()
    except errors.ACIError:
        raise ScriptError(
            "Outdated Kerberos credentials. "
            "Use kdestroy and kinit to update your ticket")
    except errors.DatabaseError:
        raise ScriptError(
            "Cannot connect to the LDAP database. Please check if IPA "
            "is running")

    try:
        user = api.Command.user_show(
            principal.partition('@')[0].partition('/')[0])['result']
        group = api.Command.group_show(u'admins')['result']
        if not (user['uid'][0] in group['member_user'] and
                group['cn'][0] in user['memberof_group']):
            raise errors.RequirementError(name='admins group membership')
    except errors.RequirementError as e:
        raise ScriptError(
            "Must have administrative privileges to setup AD trusts on server"
        )
    except Exception as e:
        raise ScriptError(
            "Unrecognized error during check of admin rights: %s" % e)

    adtrust.install_check(True, options, api)
    adtrust.install(True, options, fstore, api)

    # Enable configured services and update DNS SRV records
    service.sync_services_state(api.env.host)

    dns_help = adtrust.generate_dns_service_records_help(api)
    if dns_help:
        for line in dns_help:
            service.print_msg(line, sys.stdout)
    else:
        api.Command.dns_update_system_records()

    print("""
=============================================================================
Setup complete

You must make sure these network ports are open:
\tTCP Ports:
\t  * 135: epmap
\t  * 138: netbios-dgm
\t  * 139: netbios-ssn
\t  * 445: microsoft-ds
\t  * 1024..1300: epmap listener range
\t  * 3268: msft-gc
\tUDP Ports:
\t  * 138: netbios-dgm
\t  * 139: netbios-ssn
\t  * 389: (C)LDAP
\t  * 445: microsoft-ds

See the ipa-adtrust-install(1) man page for more details

=============================================================================
""")
    if admin_password:
        admin_kinited = ensure_admin_kinit(options.admin_name, admin_password)

    if not admin_kinited:
        print("""
WARNING: you MUST re-kinit admin user before using 'ipa trust-*' commands
family in order to re-generate Kerberos tickets to include AD-specific
information""")

    api.Backend.ldap2.disconnect()

    return 0

if __name__ == '__main__':
    run_script(
        main,
        log_file_name=log_file_name,
        operation_name='ipa-adtrust-install')
