#!/usr/bin/env python
# vim: expandtab
#
# Copyright (C) Matthieu Patou <mat@matws.net> 2011
#
# 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/>.


import optparse
import sys
# Allow to run from s4 source directory (without installing samba)
sys.path.insert(0, "bin/python")

import ldb
import samba
import samba.getopt as options
import os

from samba.credentials import DONT_USE_KERBEROS
from samba.auth import system_session
from samba import param
from samba.provision import find_provision_key_parameters, secretsdb_self_join
from samba.upgradehelpers import get_ldbs, get_paths


__docformat__ = "restructuredText"

parser = optparse.OptionParser("provision [options]")
sambaopts = options.SambaOptions(parser)
parser.add_option_group(sambaopts)
parser.add_option_group(options.VersionOptions(parser))
credopts = options.CredentialsOptions(parser)
parser.add_option_group(credopts)
parser.add_option("--oldname",
                  help="Old DC name")
parser.add_option("--newname",
                  help="New DC name")

opts = parser.parse_args()[0]

if len(sys.argv) == 1:
    opts.interactive = True
lp = sambaopts.get_loadparm()
smbconf = lp.configfile

creds = credopts.get_credentials(lp)
creds.set_kerberos_state(DONT_USE_KERBEROS)


if __name__ == '__main__':
    global defSDmodified
    defSDmodified = False
   # 1) First get files paths
    paths = get_paths(param, smbconf=smbconf)
    # Get ldbs with the system session, it is needed for searching
    # provision parameters
    session = system_session()

    ldbs = get_ldbs(paths, creds, session, lp)
    ldbs.sam.transaction_start()
    ldbs.secrets.transaction_start()

    if opts.oldname is None or opts.newname is None:
        raise Exception("Option oldname or newname is missing")
    res = ldbs.sam.search(expression="(&(name=%s)(serverReferenceBL=*))" % opts.oldname)
    if res is None or len(res) != 1:
        raise Exception("Wrong number of result returned, are you sure of the old name %s" %
                opts.oldname)

    # Ok got it then check that the new name is not used as well
    res2 = ldbs.sam.search(expression="(&(name=%s)(objectclass=computer))" % opts.newname)
    if len(res2) != 0:
        raise Exception("Seems that %s is a name that already exists, pick another one" %
                opts.newname)

    names = find_provision_key_parameters(ldbs.sam, ldbs.secrets, ldbs.idmap,
                                            paths, smbconf, lp)

    # First rename the entry
    # provision put the name in upper case so let's do it too !
    newdn = str(res[0].dn).replace("CN=%s" % opts.oldname, "CN=%s" % opts.newname.upper())
    dnobj = ldb.Dn(ldbs.sam, newdn)
    ldbs.sam.rename(res[0].dn, dnobj)

    # Then change password and samaccountname and dnshostname
    msg = ldb.Message(dnobj)
    machinepass = samba.generate_random_password(128, 255)
    mputf16 = machinepass.encode('utf-16-le')

    account = "%s$" % opts.newname.upper()
    msg["clearTextPassword"] = ldb.MessageElement(mputf16,
                                            ldb.FLAG_MOD_REPLACE,
                                            "clearTextPassword")

    msg["sAMAccountName"] = ldb.MessageElement(account,
                                            ldb.FLAG_MOD_REPLACE,
                                            "sAMAccountName")

    msg["dNSHostName"] = ldb.MessageElement("%s.%s" % (opts.newname,
                                            names.dnsdomain),
                                            ldb.FLAG_MOD_REPLACE,
                                            "dNSHostName")
    ldbs.sam.modify(msg)

    # Do a self join one more time to resync the secrets file
    res = ldbs.sam.search(expression=("distinguishedName=%s" % newdn),
            attrs=["msDs-keyVersionNumber", "serverReferenceBL"])
    assert(len(res) == 1)
    kvno = int(str(res[0]["msDs-keyVersionNumber"]))
    serverbldn = ldb.Dn(ldbs.sam, str(res[0]["serverReferenceBL"]))

    secrets_msg = ldbs.secrets.search(expression="sAMAccountName=%s$" %
                                            opts.oldname.upper(),
                                            attrs=["secureChannelType"])

    secChanType = int(secrets_msg[0]["secureChannelType"][0])

    secretsdb_self_join(ldbs.secrets, domain=names.domain,
                realm=names.realm,
                domainsid=names.domainsid,
                dnsdomain=names.dnsdomain,
                netbiosname=opts.newname.upper(),
                machinepass=machinepass,
                key_version_number=kvno,
                secure_channel_type=secChanType)

    # Update RID set reference as there is no back link for the moment.

    res = ldbs.sam.search(expression="(objectClass=rIDSet)", base=newdn, attrs=[])
    assert(len(res) == 1)
    newridset = str(res[0].dn)
    msg = ldb.Message(dnobj)

    msg["rIDSetReferences"] = ldb.MessageElement(newridset,
                                            ldb.FLAG_MOD_REPLACE,
                                            "rIDSetReferences")
    ldbs.sam.modify(msg)

    # Update the server's sites configuration
    if False:
        # Desactivated for the moment we have a couple of issues with site 
        # renaming first one is that it's currently forbidden
        # second one is that a lot of links are not backlinked
        # and so won't be updated when the DN change (ie. fmsowner ...)
        serverbl = str(serverbldn)
        dnparts = serverbl.split(",")
        dnparts[0] = "CN=%s" % opts.newname.upper()
        newserverref = ",".join(dnparts)

        newserverrefdn = ldb.Dn(ldbs.sam, newserverref)

        ldbs.sam.rename(serverbldn, newserverrefdn)

        msg = ldb.Message(newserverrefdn)
        msg["dNSHostName"] = ldb.MessageElement("%s.%s" % (opts.newname,
                                                names.dnsdomain),
                                                ldb.FLAG_MOD_REPLACE,
                                                "dNSHostName")
        ldbs.sam.modify(msg)

    try:
        ldbs.sam.transaction_prepare_commit()
        ldbs.secrets.transaction_prepare_commit()
    except Exception:
        ldbs.sam.rollback()
        ldbs.secrets.rollback()
        sys.exit(1)

    try:
        ldbs.sam.transaction_commit()
        ldbs.secrets.transaction_commit()
    except Exception:
        ldbs.sam.rollback()
        ldbs.secrets.rollback()

    # All good so far
    #print lp.get("private dir")
    cf = open(lp.configfile)
    ncfname = "%s.new" % lp.configfile
    newconf = open(ncfname, 'w')
    for l in cf.readlines():
        if l.find("netbios name") > 0:
             newconf.write("\tnetbios name = %s\n" % opts.newname.upper())
        else:
            newconf.write(l)
    newconf.close()
    cf.close()
    os.rename(ncfname, lp.configfile)