#!/usr/bin/python2.6
#
# Univention Migrate SBS
# Migrates a SBS server to the local Samba 4 system
#
# Copyright 2012-2013 Univention GmbH
#
# http://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# .
from optparse import OptionParser, OptionValueError
import samba.getopt
import sys, os
import subprocess
import shutil
from univention import config_registry
import ldb
import samba
from samba.samdb import SamDB
from samba.auth import system_session
from samba.param import LoadParm
import socket, time, struct
import ldap
import re
from samba.ndr import ndr_pack, ndr_unpack
from samba.dcerpc import security
import univention.admin.uldap
import univention.admin.uexceptions
samba_dir = '/var/lib/samba'
samba_private_dir = os.path.join(samba_dir, 'private')
sysvol_path = os.path.join(samba_dir, 'sysvol')
well_known_sids = {
"Null Authority": "S-1-0",
"Nobody": "S-1-0-0",
"World Authority": "S-1-1",
"Everyone": "S-1-1-0",
"Local Authority": "S-1-2",
"Local": "S-1-2-0",
"Console Logon": "S-1-2-1",
"Creator Authority": "S-1-3",
"Creator Owner": "S-1-3-0",
"Creator Group": "S-1-3-1",
"Creator Owner Server": "S-1-3-2",
"Creator Group Server": "S-1-3-3",
"Owner Rights": "S-1-3-4",
"All Services": "S-1-5-80-0",
"Non-unique Authority": "S-1-4",
"NT Authority": "S-1-5",
"Dialup": "S-1-5-1",
"Network": "S-1-5-2",
"Batch": "S-1-5-3",
"Interactive": "S-1-5-4",
"Service": "S-1-5-6",
"Anonymous": "S-1-5-7",
"Proxy": "S-1-5-8",
"Enterprise Domain Controllers": "S-1-5-9",
"Principal Self": "S-1-5-10",
"Authenticated Users": "S-1-5-11",
"Restricted Code": "S-1-5-12",
"Terminal Server Users": "S-1-5-13",
"Remote Interactive Logon": "S-1-5-14",
"This Organization": "S-1-5-15",
"IUSR": "S-1-5-17",
"Local System": "S-1-5-18",
"NT Authority": "S-1-5-20",
"Administrators": "S-1-5-32-544",
"Users": "S-1-5-32-545",
"Guests": "S-1-5-32-546",
"Power Users": "S-1-5-32-547",
"Account Operators": "S-1-5-32-548",
"Server Operators": "S-1-5-32-549",
"System Operators": "S-1-5-32-549", ## duplicate names
"Print Operators": "S-1-5-32-550",
"Backup Operators": "S-1-5-32-551",
"Replicators": "S-1-5-32-552",
}
well_known_domain_rids = {
"Administrator": 500,
"Guest": 501,
"KRBTGT": 502,
"Domain Admins": 512,
"Domain Users": 513,
"Domain Guests": 514,
"Domain Computers": 515,
"Domain Controllers": 516,
"Cert Publishers": 517,
"Schema Admins": 518,
"Enterprise Admins": 519,
"Group Policy Creator Owners": 520,
"RAS and IAS Servers": 553,
## Windows Server 2008
"Enterprise Read-only Domain Controllers": 498,
"Read-Only Domain Controllers": 521,
"Allowed RODC Password Replication Group": 571,
"Denied RODC Password Replication Group": 572,
## Windows Server "8"
"Cloneable Domain Controllers": 522,
}
def _connect_ucs(ucr, binddn=None, bindpwd=None):
''' Connect to OpenLDAP '''
if binddn and bindpwd:
bindpw = bindpwd
else:
bindpw_file = ucr.get('connector/ldap/bindpw', '/etc/ldap.secret')
binddn = ucr.get('connector/ldap/binddn', 'cn=admin,'+ucr['ldap/base'])
bindpw=open(bindpw_file).read()
if bindpw[-1] == '\n':
bindpw=bindpw[0:-1]
host = ucr.get('connector/ldap/server', ucr.get('ldap/master'))
try:
port = int(ucr.get('connector/ldap/port', ucr.get('ldap/master/port')))
except:
port = 7389
lo = univention.admin.uldap.access(host=host, port=port, base=ucr['ldap/base'], binddn=binddn, bindpw=bindpw, start_tls=0, follow_referral=True)
return lo
def operatingSystem_attribute(ucr, samdb):
msg = samdb.search(base=samdb.domain_dn(), scope=samba.ldb.SCOPE_SUBTREE,
expression="(sAMAccountName=%s$)" % ucr["hostname"],
attrs=["operatingSystem", "operatingSystemVersion"])
if msg:
obj = msg[0]
if not "operatingSystem" in obj:
delta = ldb.Message()
delta.dn = obj.dn
delta["operatingSystem"] = ldb.MessageElement("Univention Corporate Server", ldb.FLAG_MOD_REPLACE, "operatingSystem")
samdb.modify(delta)
if not "operatingSystemVersion" in obj:
delta = ldb.Message()
delta.dn = obj.dn
delta["operatingSystemVersion"] = ldb.MessageElement("3.0", ldb.FLAG_MOD_REPLACE, "operatingSystemVersion")
samdb.modify(delta)
def let_samba4_manage_etc_krb5_keytab(ucr, secretsdb):
msg = secretsdb.search(base="cn=Primary Domains", scope=samba.ldb.SCOPE_SUBTREE,
expression="(flatName=%s)" % ucr["windows/domain"],
attrs=["krb5Keytab"])
if msg:
obj = msg[0]
if not "krb5Keytab" in obj or not "/etc/krb5.keytab" in obj["krb5Keytab"]:
delta = ldb.Message()
delta.dn = obj.dn
delta["krb5Keytab"] = ldb.MessageElement("/etc/krb5.keytab", ldb.FLAG_MOD_ADD, "krb5Keytab")
secretsdb.modify(delta)
def add_servicePrincipals(ucr, secretsdb, spn_list):
msg = secretsdb.search(base="cn=Primary Domains", scope=samba.ldb.SCOPE_SUBTREE,
expression="(flatName=%s)" % ucr["windows/domain"],
attrs=["servicePrincipalName"])
if msg:
obj = msg[0]
delta = ldb.Message()
delta.dn = obj.dn
for spn in spn_list:
if not "servicePrincipalName" in obj or not spn in obj["servicePrincipalName"]:
delta[spn] = ldb.MessageElement(spn, ldb.FLAG_MOD_ADD, "servicePrincipalName")
secretsdb.modify(delta)
def sync_time(server):
## source: http://code.activestate.com/recipes/117211-simple-very-sntp-client/
TIME1970 = 2208988800L # Thanks to F.Lundh
client = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
data = '\x1b' + 47 * '\0'
client.settimeout(15.0)
try:
client.sendto( data, ( server, 123 ))
data, address = client.recvfrom( 1024 )
if data:
print 'NTP Response received from server %s' % server
t = struct.unpack( '!12I', data )[10]
t -= TIME1970
if time.gmtime(t) >= time.gmtime():
print "Setting local time: ",
p = subprocess.Popen(["/bin/date", "-s", time.ctime(t)], stdout=subprocess.PIPE)
(stdout, stderr) = p.communicate()
print stdout
else:
print "Error: time %s on server %s is earlier than" % (time.strftime("%a, %d %b %Y %H:%M:%S %Z", time.localtime(t)), server)
print " time %s on this server!" % time.strftime("%a, %d %b %Y %H:%M:%S %Z", time.localtime())
print " Refusing to reset time on this server to avoid SSL certificate problems."
print " Please check time on server %s" % server
sys.exit(1)
except socket.error:
print "Warning: Could not retrive time from %s via NTP" % server
def check_for_phase_II(ucr, lp, ad_server_ip):
## Check if we are in Phase II and the AD server is already switched off:
if "hosts/static/%s" % ad_server_ip in ucr:
ad_server_fqdn, ad_server_name = ucr["hosts/static/%s" % ad_server_ip].split()
## Check if the AD server is already in the local SAM db
samdb = SamDB(os.path.join(samba_private_dir, "sam.ldb"), session_info=system_session(lp), lp=lp)
msgs = samdb.search(base=ucr["samba4/ldap/base"], scope=samba.ldb.SCOPE_SUBTREE,
expression="(sAMAccountName=%s$)" % ad_server_name,
attrs=["objectSid"])
if msgs:
return (1, ad_server_fqdn, ad_server_name)
else:
return (2, ad_server_fqdn, ad_server_name)
return (0, None, None)
def run_phaseI(opts, args):
ad_server_name = None
if len(args) > 0:
ad_server_ip = args[0]
if len(args) == 2:
ad_server_name = args[1]
elif len(args) != 1:
parser.print_usage()
sys.exit(1)
devnull = open('/dev/null', 'w')
ucr = config_registry.ConfigRegistry()
ucr.load()
local_fqdn = '.'.join((ucr["hostname"], ucr["domainname"]))
# lp = LoadParm()
# lp.load('/etc/samba/smb.conf')
lp = sambaopts.get_loadparm()
### First plausibility checks
## 1.a Check that local domainname matches kerberos realm
if ucr["domainname"].lower() != ucr["kerberos/realm"].lower():
print "Mismatching DNS domain and kerberos realm. Please reinstall the server with the same Domain as your AD"
sys.exit(1)
## 1.b ping the given AD server IP
print "Pinging AD IP %s: " % ad_server_ip,
p1 = subprocess.Popen(["fping", ad_server_ip], stdout=devnull, stderr=devnull)
rc= p1.poll()
while rc is None:
sys.stdout.write(".")
sys.stdout.flush()
time.sleep(1)
rc= p1.poll()
print
if rc != 0:
## Check if we are in Phase II and the AD server is already switched off:
(rc, tmp_fqdn, tmp_name) = check_for_phase_II(ucr, lp, ad_server_ip)
if rc == 0:
print "Error: Server IP %s not reachable" % ad_server_ip
elif rc == 1:
print "Note: The AD Server IP %s not reachable" % ad_server_ip
print "Error: But found the AD DC %s account already in the Samba 4 SAM backend" % tmp_name
print " Looks like it was switched of to finalize the migration?"
print " If this is true, then restart this script with option --fsmo-takeover"
elif rc == 2:
print "Error: Server IP %s not reachable" % ad_server_ip
print "Error: It seems that this script was run once already for the first migration step,"
print " but the server %s cannot be found in the local Samba SAM database." % tmp_name
print " Don't know how to continue, giving up at this point."
sys.exit(1)
else:
print "Ok, Server IP %s is online." % ad_server_ip
## 1.c Check, if a AD DNS domain is given and if it matches the local one
ad_server_fqdn = None
if ad_server_name:
char_idx = ad_server_name.find(".")
if char_idx == -1:
ad_server_fqdn = "%s.%s" % (ad_server_name, ucr["domainname"])
else:
ad_server_fqdn = ad_server_name
else:
try:
p1 = subprocess.Popen(["dig", "@%s" % ad_server_ip, "SRV", "_kerberos._tcp.dc._msdcs.%s" % ucr["domainname"], "+short"], stdout=subprocess.PIPE)
(stdout, stderr) = p1.communicate()
except:
print "Error: Failed to determine DC for IP %s" % ad_server_ip
print "Please retry by passing the AD server name as additional command line argument"
sys.exit(1)
lines = stdout.rstrip('\n').split('\n')
if len(lines) != 1:
print "Warning: Multiple DCs registered for DNS SRV record _kerberos._tcp.dc._msdcs.%s" % ucr["domainname"]
local_fqdn_found_in_AD_SRV = False
for line in lines:
tmp_fqdn = line.split()[3].rstrip('.')
if tmp_fqdn == local_fqdn:
print "Warning: This UCS server is already registered as DC at the DNS server %s" % ad_server_ip
local_fqdn_found_in_AD_SRV = True
continue
else:
p1 = subprocess.Popen(["dig", "@%s" % ad_server_ip, tmp_fqdn, "+short"], stdout=subprocess.PIPE)
(stdout, stderr) = p1.communicate()
if stdout.strip('\n') == ad_server_ip:
if not ad_server_fqdn:
ad_server_fqdn = tmp_fqdn
else:
print "Error: More than one of the registered DC FQDNs matches the given AD server IP:"
print " %s" % ad_server_fqdn
print " %s" % tmp_fqdn
print "Error: Failed to determine DC for IP %s" % ad_server_ip
print "Please retry by passing the AD server name as additional command line argument"
sys.exit(1)
if not ad_server_fqdn:
print "Error: Failed to determine DC for IP %s" % ad_server_ip
print "Please retry by passing the AD server name as additional command line argument"
sys.exit(1)
else:
print "Sucessfull determined AD DC FQDN %s for given IP %s" % (ad_server_fqdn, ad_server_ip)
if local_fqdn_found_in_AD_SRV and ad_server_fqdn:
## Check if we are in Phase II and the AD server is already switched off:
(rc, tmp_fqdn, tmp_name) = check_for_phase_II(ucr, lp, ad_server_ip)
if rc == 0:
pass
elif rc == 1:
print "Error: Account for the AD DC %s is already the Samba 4 SAM backend." % tmp_name
print " It seems that this script was run once already for the first migration step."
print " If this is true, then go over to the AD DC to migrate the SYSVOL,"
print " and switch it off before restarting this script with option --fsmo-takeover"
sys.exit(1)
elif rc == 2:
print "Error: It seems that this script was run once already for the first migration step,"
print " but the server %s cannot be found in the local Samba SAM database." % tmp_name
print " Don't know how to continue, giving up at this point."
sys.exit(1)
else:
## OK, we have a unique match
ad_server_fqdn = lines[0].split()[3]
ad_server_fqdn = ad_server_fqdn.rstrip('.')
print "Sucessfull determined AD DC FQDN %s for given IP %s" % (ad_server_fqdn, ad_server_ip)
char_idx = ad_server_fqdn.find(".")
if char_idx == -1:
print "Error: AD server did not return FQDN for IP %s" % ad_server_ip
print "Please retry by passing the AD server name as additional command line argument"
sys.exit(1)
elif not ad_server_fqdn[char_idx+1:] == ucr["domainname"]:
print "Error: local DNS domain %s does not match AD server DNS domain." % ucr["domainname"]
sys.exit(1)
else:
ad_server_name = ad_server_fqdn.split('.', 1)[0]
## 2. Check, if there is a DNS Server running at ad_server_ip which is
## able to resolve ad_server_fqdn
p1 = subprocess.Popen(["dig", "@%s" % ad_server_ip, ad_server_fqdn, "+short"], stdout=subprocess.PIPE)
(stdout, stderr) = p1.communicate()
if not stdout.strip('\n') == ad_server_ip:
print "Error: Cannot resolve DNS name %s using DNS server %s" % (ad_server_fqdn, ad_server_ip)
print " Please check DNS name, IP or configuration."
sys.exit(1)
## 3. Check, if the given AD Credentials work
creds = credopts.get_credentials(lp)
if creds.get_username() == "root":
creds.set_username("Administrator")
ad_join_user = creds.get_username()
ad_join_password = creds.get_password()
try:
remote_samdb = SamDB("ldap://%s" % ad_server_ip, credentials=creds, session_info=system_session(lp), lp=lp)
except:
sys.exit(1)
## 4. Determine Site of given server, important for locale-dependend names like "Standardname-des-ersten-Standorts"
sitename = None
msgs = remote_samdb.search(base=ucr["samba4/ldap/base"], scope=samba.ldb.SCOPE_SUBTREE,
expression="(sAMAccountName=%s$)" % ad_server_name,
attrs=["serverReferenceBL"])
if msgs:
obj = msgs[0]
serverReferenceBL = obj["serverReferenceBL"][0]
serverReferenceBL_RDNs = ldap.explode_dn(serverReferenceBL)
serverReferenceBL_RDNs.reverse()
config_partition_index = None
site_container_index = None
for i in xrange(len(serverReferenceBL_RDNs)):
if site_container_index:
sitename = serverReferenceBL_RDNs[i].split('=', 1)[1]
break
elif config_partition_index and serverReferenceBL_RDNs[i] == "CN=Sites":
site_container_index = i
elif not site_container_index and serverReferenceBL_RDNs[i] == "CN=Configuration":
config_partition_index = i
i = i+1
print "Located server %s site %s in AD SAM" % (ad_server_fqdn, sitename)
## OK, we are quite shure that we have the basics right, note the AD server IP and FQDN in UCR for phase II
config_registry.handler_set(["hosts/static/%s=%s %s" % (ad_server_ip, ad_server_fqdn, ad_server_name) ])
### Phase I.a: Join to AD
## Essential: Sync the time
sync_time(ad_server_ip)
## Stop the S4 Connector for phase I
p = subprocess.Popen(["/etc/init.d/univention-s4-connector", "stop"], stdout=devnull, stderr=devnull)
p.wait()
## Stop Samba
p = subprocess.Popen(["/etc/init.d/samba4", "stop"])
p.wait()
## Move current Samba directory out of the way
if os.path.exists(samba_dir):
backup_samba_dir = "%s.bak" % samba_private_dir
if not os.path.exists(backup_samba_dir):
os.rename(samba_private_dir, backup_samba_dir)
os.makedirs(samba_private_dir)
else:
shutil.rmtree(samba_private_dir)
os.mkdir(samba_private_dir)
## Adjust some UCR settings
if "nameserver1/local" in ucr:
nameserver1_orig = ucr["nameserver1/local"]
else:
nameserver1_orig = ucr["nameserver1"]
config_registry.handler_set(["nameserver1/local=%s" % nameserver1_orig, "nameserver1=%s" % ad_server_ip])
config_registry.handler_set(["directory/manager/web/modules/users/user/properties/username/syntax=string"])
config_registry.handler_set(["dns/backend=ldap"])
ucr.load()
## Stop the NSCD
p = subprocess.Popen(["/etc/init.d/nscd", "stop"])
p.wait()
## Restart bind9 to use the OpenLDAP backend, just to be sure
p = subprocess.Popen(["/etc/init.d/bind9", "restart"])
p.wait()
## Get machine credentials
try:
machine_secret = open('/etc/machine.secret','r').read().strip()
except IOError, e:
univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, 'Could not read machine credentials: %s', str(e))
sys.exit(1)
## Join into the domain
if sitename:
p = subprocess.Popen(["/usr/sbin/samba-tool", "domain", "join", ucr["domainname"], "DC", "-U%s%%%s" % (ad_join_user, ad_join_password), "--realm=%s" % ucr["kerberos/realm"], "--machinepass=%s" % machine_secret, "--server=%s" % ad_server_fqdn, "--site=%s" % sitename])
if p.wait() != 0:
sys.exit(1)
else:
print "Error: Cannot determine site for server %s" % ad_server_fqdn
sys.exit(1)
## Fix some attributes in local SamDB
ad_domainsid = None
samdb = SamDB(os.path.join(samba_private_dir, "sam.ldb"), session_info=system_session(lp), lp=lp)
msgs = samdb.search(base=ucr["samba4/ldap/base"], scope=samba.ldb.SCOPE_BASE,
expression="(objectClass=domain)",
attrs=["objectSid"])
if msgs:
obj = msgs[0]
ad_domainsid = str(ndr_unpack(security.dom_sid, obj["objectSid"][0]))
if not ad_domainsid:
print "Error: Could not determine domain SID"
sys.exit(1)
old_domainsid = None
lo = _connect_ucs(ucr)
ldap_result = lo.search(filter="(&(objectClass=sambaDomain)(sambaDomainName=%s))" % ucr["windows/domain"], attr=["sambaSID"])
if len(ldap_result) == 1:
ucs_object_dn = ldap_result[0][0]
if os.path.exists("/var/tmp/old_sambasid"):
f = open("/var/tmp/old_sambasid", 'r')
old_domainsid = f.read()
f.close()
else:
old_domainsid = ldap_result[0][1]["sambaSID"][0]
f = open("/var/tmp/old_sambasid", 'w')
f.write("%s" % old_domainsid)
f.close()
elif len(ldap_result) > 0:
print 'ERROR: Found more than one sambaDomain object with sambaDomainName=%s' % ucr["windows/domain"]
else:
print 'ERROR: Did not find a sambaDomain object with sambaDomainName=%s' % ucr["windows/domain"]
print "Replacing OLD sambaSID: %s" % old_domainsid
if old_domainsid != ad_domainsid:
ml = [("sambaSID", old_domainsid, ad_domainsid)]
lo.modify(ucs_object_dn, ml)
operatingSystem_attribute(ucr, samdb)
## Fix some attributes in SecretsDB
secretsdb = samba.Ldb(os.path.join(samba_private_dir, "secrets.ldb"), session_info=system_session(lp), lp=lp)
let_samba4_manage_etc_krb5_keytab(ucr, secretsdb)
fqdn = "%s.%s" % (ucr['hostname'], ucr['domainname'])
spn_list = ("host/%s" % fqdn, "ldap/%s" % fqdn)
add_servicePrincipals(ucr, secretsdb, spn_list)
## Set Samba domain password settings. Note: rotation of passwords will only work with UCS 3.1, so max password age must be disabled for now.
p = subprocess.Popen(["samba-tool", "domain", "passwordsettings", "set", "--history-length=3", "--min-pwd-age=0", "--max-pwd-age=0"])
p.wait()
## Start Samba
p = subprocess.Popen(["/etc/init.d/samba4", "start"])
p.wait()
### Phase I.b: Pre-Map SIDs (locale adjustment etc.)
## construct locale mapping
ucs_ad_name_map = { }
for (name, rid) in well_known_domain_rids.items():
msgs = samdb.search(base=ucr["samba4/ldap/base"], scope=samba.ldb.SCOPE_SUBTREE,
expression="(objectSid=%s-%s)" % (ad_domainsid, rid),
attrs=["sAMAccountName"])
if msgs:
obj = msgs[0]
ad_object_name = obj["sAMAccountName"][0]
## Special Mapping for UCS
if name == "Domain Computers":
name ="Windows Hosts"
if ad_object_name != name:
ucs_ad_name_map[name] = ad_object_name
config_registry.handler_set(["connector/s4/mapping/group/table/%s=%s" % (name, ad_object_name)])
## construct dict of old UCS sambaSIDs
old_sambaSID_dict = {}
samba_sid_map = {}
## Users and Computers
ldap_result = lo.search(filter="(&(objectClass=sambaSamAccount)(sambaSID=*))", attr=["uid", "sambaSID"])
for record in ldap_result:
(ucs_object_dn, ucs_object_dict) = record
old_sid = ucs_object_dict["sambaSID"][0]
ucs_name = ucs_object_dict["uid"][0]
if old_sid.startswith(old_domainsid):
old_sambaSID_dict[old_sid] = ucs_name
## lookup new sid
new_sid = None
if ucs_name in ucs_ad_name_map:
lookup_name = ucs_ad_name_map[ucs_name]
else:
lookup_name = ucs_name
msgs = samdb.search(base=ucr["samba4/ldap/base"], scope=samba.ldb.SCOPE_SUBTREE,
expression="(sAMAccountName=%s)" % lookup_name,
attrs=["objectSid"])
if msgs:
obj = msgs[0]
new_sid = str(ndr_unpack(security.dom_sid, obj["objectSid"][0]))
samba_sid_map[old_sid] = new_sid
if new_sid:
if opts.verbose:
print "Rewriting user %s SID %s to %s" % (old_sambaSID_dict[old_sid], old_sid, new_sid)
ml = [("sambaSID", old_sid, new_sid)]
lo.modify(ucs_object_dn, ml)
## Groups
ldap_result = lo.search(filter="(&(objectClass=sambaGroupMapping)(sambaSID=*))", attr=["cn", "sambaSID"])
for record in ldap_result:
(ucs_object_dn, ucs_object_dict) = record
old_sid = ucs_object_dict["sambaSID"][0]
ucs_name = ucs_object_dict["cn"][0]
if old_sid.startswith(old_domainsid):
old_sambaSID_dict[old_sid] = ucs_name
## lookup new sid
new_sid = None
if ucs_name in ucs_ad_name_map:
lookup_name = ucs_ad_name_map[ucs_name]
else:
lookup_name = ucs_name
msgs = samdb.search(base=ucr["samba4/ldap/base"], scope=samba.ldb.SCOPE_SUBTREE,
expression="(sAMAccountName=%s)" % lookup_name,
attrs=["objectSid"])
if msgs:
obj = msgs[0]
new_sid = str(ndr_unpack(security.dom_sid, obj["objectSid"][0]))
samba_sid_map[old_sid] = new_sid
if new_sid:
if opts.verbose:
print "Rewriting group '%s' SID %s to %s" % (old_sambaSID_dict[old_sid], old_sid, new_sid)
ml = [("sambaSID", old_sid, new_sid)]
lo.modify(ucs_object_dn, ml)
ldap_result = lo.search(filter="(sambaPrimaryGroupSID=*)", attr=["sambaPrimaryGroupSID"])
for record in ldap_result:
(ucs_object_dn, ucs_object_dict) = record
old_sid = ucs_object_dict["sambaPrimaryGroupSID"][0]
if old_sid.startswith(old_domainsid):
if old_sid in samba_sid_map:
ml = [("sambaPrimaryGroupSID", old_sid, samba_sid_map[old_sid])]
lo.modify(ucs_object_dn, ml)
else:
if old_sid in old_sambaSID_dict:
print "Error: Could not find new sambaPrimaryGroupSID for %s" % old_sambaSID_dict[old_sid]
else:
print "Error: Unknown sambaPrimaryGroupSID %s" % old_sid
### Pre-Create mail domains for all mail and proxyAddresses:
samdb = SamDB(os.path.join(samba_private_dir, "sam.ldb"), session_info=system_session(lp), lp=lp)
msgs = samdb.search(base=ucr["samba4/ldap/base"], scope=samba.ldb.SCOPE_SUBTREE,
expression="(|(mail=*)(proxyAddresses=*))",
attrs=["mail", "proxyAddresses"])
maildomains = []
for msg in msgs:
for attr in ("mail", "proxyAddresses"):
if attr in msg:
for address in msg[attr]:
char_idx = address.find("@")
if char_idx != -1:
domainpart = address[char_idx+1:].lower()
if not domainpart.endswith(".local"):
if not domainpart in maildomains:
maildomains.append(domainpart)
for maildomain in maildomains:
p = subprocess.Popen(["/usr/sbin/univention-directory-manager", "mail/domain", "create", "--ignore_exists", "--position", "cn=domain,cn=mail,%s" % ucr["ldap/base"], "--set" , "name=%s" % maildomain])
p.wait()
### Copy UCS AdministratorPassword to S4 Administrator object
### (Account is disabled in SBS 2008 by default and the password might be unknown)
## workaround for samba ndr parsing traceback
p = subprocess.Popen(["samba-tool", "user", "setpassword", "Administrator", "--newpassword=DummyPW123"]) ## will be overwritten in the next step
p.wait()
p = subprocess.Popen(["/usr/sbin/univention-password_sync_ucs_to_s4", "Administrator"])
if p.wait() != 0: ## retry logic from 97univention-s4-connector.inst join script
p = subprocess.Popen(["/etc/init.d/samba4", "restart"])
p.wait()
time.sleep(3)
p = subprocess.Popen(["/usr/sbin/univention-password_sync_ucs_to_s4", "Administrator"])
p.wait()
## TODO: fix proxyAddresses mapping
config_registry.handler_set(["connector/s4/mapping/mailAlternativeAddress=False"])
### Phase I.c: Run S4 Connector
old_sleep = ucr.get("connector/s4/poll/sleep", "5")
old_retry = ucr.get("connector/s4/retryrejected", "10")
config_registry.handler_set(["connector/s4/poll/sleep=1", "connector/s4/retryrejected=2"])
p = subprocess.Popen(["/usr/share/univention-s4-connector/msgpo.py", "--write2ucs"])
p.wait()
time.sleep(3) ## wait for listener
## Reset S4 Connector and handler state
p = subprocess.Popen(["/etc/init.d/univention-directory-listener", "stop"])
p.wait()
if os.path.exists("/var/lib/univention-directory-listener/handlers/s4-connector"):
os.unlink("/var/lib/univention-directory-listener/handlers/s4-connector")
# if os.path.exists("/var/lib/univention-directory-listener/handlers/samba4-idmap"):
# os.unlink("/var/lib/univention-directory-listener/handlers/samba4-idmap")
if os.path.exists("/etc/univention/connector/s4internal.sqlite"):
os.unlink("/etc/univention/connector/s4internal.sqlite")
for foldername in ("/var/lib/univention-connector/s4", "/var/lib/univention-connector/s4/tmp"):
for entry in os.listdir(foldername):
filename = os.path.join(foldername, entry)
try:
if os.path.isfile(filename):
os.unlink(filename)
except Exception, e:
print "Error removing file: %s" % str(e)
p = subprocess.Popen(["/etc/init.d/univention-directory-listener", "start"])
p.wait()
time.sleep(10) ## wait for listener
print "Starting S4 Connector"
p = subprocess.Popen(["/etc/init.d/univention-s4-connector", "start"])
p.wait()
## Reset normal relication intervals
config_registry.handler_set(["connector/s4/poll/sleep=%s" % old_sleep, "connector/s4/retryrejected=%s" % old_retry])
print "Waiting 30 sec for S4 Connector sync"
time.sleep(30)
## rebuild idmap
p = subprocess.Popen(["/usr/lib/univention-directory-listener/system/samba4-idmap.py", "--direct-resync"], stdout=devnull, stderr=devnull)
p.wait()
print "TODO: Determine replication status"
## Wait for Sync to finish
# pattern = re.compile(r"(^.+\n-{38}\ntry to sync \d+ changes from UCS\ndone: .+\nChanges from UCS: (\d+) \((\d+) saved rejected\)\n-{38}\n-{38}\ntry to sync \d+ changes from S4\ndone: .+\nChanges from S4: (\d+) \((\d+) saved rejected\)\n-{38}\n)", re.MULTILINE)
# changes_from_ucs = changes_from_s4 = 1
# while changes_from_ucs > 0 or changes_from_s4 > 0:
# time.sleep(5)
# with open("/var/log/univention/connector-s4-status.log", "r") as f:
# status_log = f.read()
# m = pattern.match(status_log)
# changes_from_ucs = m.group(2)
# rejects_from_ucs = m.group(3)
# changes_from_s4 = m.group(4)
# rejects_from_s4 = m.group(5)
### Phase II: AD-Side Sync
print "TODO: Ask user to robocopy sysvol"
print "TODO: Ask user to robocopy server side homedirs (?)"
# print "TODO: Profile kopieren: z.B. nach /home/$user/windows-profiles/default.V2"
print "TODO: Ask user to switch off AD server"
sys.exit(1)
def run_phaseIII(opts, args):
ad_server_name = None
if len(args) > 0:
ad_server_ip = args[0]
if len(args) == 2:
ad_server_name = args[1]
elif len(args) != 1:
parser.print_usage()
sys.exit(1)
devnull = open('/dev/null', 'w')
ucr = config_registry.ConfigRegistry()
ucr.load()
local_fqdn = '.'.join((ucr["hostname"], ucr["domainname"]))
# lp = LoadParm()
# lp.load('/etc/samba/smb.conf')
lp = sambaopts.get_loadparm()
### First plausibility checks
## 1.a check if the given IP was mapped to a host name via UCR in Phase I
(rc, phaseI_fqdn, phaseI_name) = check_for_phase_II(ucr, lp, ad_server_ip)
if rc == 0:
print "Error: given IP %s was not mapped to a hostname in phase I"
print " Please complete phase I of the migration before initiating the FSMO takeover"
sys.exit(1)
elif rc == 1:
print "OK, Found the AD DC %s account in the local Samba 4 SAM backend" % phaseI_name
elif rc == 2:
print "Error: It seems that this script was run once already for the first migration step,"
print " but the server %s cannot be found in the local Samba SAM database." % phaseI_name
print " Don't know how to continue, giving up at this point."
print " Maybe the steps needed for migration have been finished already?"
sys.exit(1)
## 1.b Check, if a AD DNS domain is given and if it matches the one given before
ad_server_fqdn = None
if ad_server_name:
char_idx = ad_server_name.find(".")
if char_idx == -1:
ad_server_fqdn = "%s.%s" % (ad_server_name, ucr["domainname"])
else:
ad_server_fqdn = ad_server_name
ad_server_name = ad_server_fqdn.split('.', 1)[0]
if ad_server_name != phaseI_name:
print "Error: Given AD server name %s does not match the one recorded for IP %s in phase I: %s" % (ad_server_name, ad_server_ip, phaseI_name)
print " Consider not explicetely passing an AD server name."
sys.exit(1)
else:
ad_server_fqdn = phaseI_fqdn
ad_server_name = phaseI_name
## 1.c Check that local domainname matches kerberos realm
if ucr["domainname"].lower() != ucr["kerberos/realm"].lower():
print "Mismatching DNS domain and kerberos realm. Please reinstall the server with the same Domain as your AD"
sys.exit(1)
## 1.d ping the given AD server IP
print "Pinging AD IP %s: " % ad_server_ip,
p1 = subprocess.Popen(["fping", ad_server_ip], stdout=devnull, stderr=devnull)
rc= p1.poll()
while rc is None:
sys.stdout.write(".")
sys.stdout.flush()
time.sleep(1)
rc= p1.poll()
print
if rc == 0:
print "Error: The server IP %s is still reachable" % ad_server_ip
print " Return to the AD DC to migrate the SYSVOL, and then"
print " switch it off before restarting this script with option --fsmo-takeover"
sys.exit(1)
else:
print "Ok, Server IP %s unreachable." % ad_server_ip
### Phase III: Promote to FSMO master and DNS server
## 1. Determine Site of local server, important for locale-dependend names like "Standardname-des-ersten-Standorts"
sitename = None
samdb = SamDB(os.path.join(samba_private_dir, "sam.ldb"), session_info=system_session(lp), lp=lp)
msgs = samdb.search(base=ucr["samba4/ldap/base"], scope=samba.ldb.SCOPE_SUBTREE,
expression="(sAMAccountName=%s$)" % ucr["hostname"],
attrs=["serverReferenceBL"])
if msgs:
obj = msgs[0]
serverReferenceBL = obj["serverReferenceBL"][0]
serverReferenceBL_RDNs = ldap.explode_dn(serverReferenceBL)
serverReferenceBL_RDNs.reverse()
config_partition_index = None
site_container_index = None
for i in xrange(len(serverReferenceBL_RDNs)):
if site_container_index:
sitename = serverReferenceBL_RDNs[i].split('=', 1)[1]
break
elif config_partition_index and serverReferenceBL_RDNs[i] == "CN=Sites":
site_container_index = i
elif not site_container_index and serverReferenceBL_RDNs[i] == "CN=Configuration":
config_partition_index = i
i = i+1
print "Located server %s site %s in Samba4 SAM" % (ucr["hostname"], sitename)
## Re-Set Defaul NTACLs on sysvol
p = subprocess.Popen(["/usr/share/univention-samba4/scripts/set_sysvol_ntacl.py", sysvol_path], stdout=devnull, stderr=devnull)
p.wait()
## Re-set default fACLs so sysvol-sync can read files and directories
p = subprocess.Popen(["setfacl", "-R", "-P", "-m", "g:Authenticated Users:r-x,d:g:Authenticated Users:r-x", sysvol_path])
if p.wait() != 0:
print "Error: Could not set fACL for %s" % sysvol_path
print "Warning: Continuing anyway. Please fix later:"
print " setfacl -R -P -m 'g:Authenticated Users:r-x,d:g:Authenticated Users:r-x' %s" % sysvol_path
## Add DNS records to UDM:
p = subprocess.Popen(["/usr/share/univention-samba4/scripts/setup-dns-in-ucsldap.sh", "--dc", "--pdc", "--gc", "--site=%s" % sitename])
p.wait()
## Replace DNS host record for AD Server name by DNS Alias
p = subprocess.Popen(["univention-directory-manager", "dns/host_record", "delete", "", "--superordinate", "zoneName=%s,cn=dns,%s" % (ucr["domainname"], ucr["ldap/base"]), "--dn", "relativeDomainName=%s,zoneName=%s,cn=dns,%s" % (ad_server_name, ucr["domainname"], ucr["ldap/base"]) ], stdout=devnull, stderr=devnull)
p.wait()
p = subprocess.Popen(["univention-directory-manager", "dns/alias", "create", "--superordinate", "zoneName=%s,cn=dns,%s" % (ucr["domainname"], ucr["ldap/base"]), "--set", "name=%s" % ad_server_name, "--set", "cname=%s" % local_fqdn])
p.wait()
## Create NETBIOS Alias
f = open('/etc/samba/local.conf', 'a')
f.write('[global]\nnetbios aliases = "%s"\n' % ad_server_name)
f.close()
p = subprocess.Popen(["/usr/sbin/univention-config-registry", "commit", "/etc/samba/smb.conf"])
p.wait()
## Resolve against local Bind9
## use OpenLDAP backend until the S4 Connector has run
if "nameserver1/local" in ucr:
nameserver1_orig = ucr["nameserver1/local"]
config_registry.handler_set(["nameserver1=%s" % nameserver1_orig])
## unset temporary variable
config_registry.handler_unset(["nameserver1/local"])
else:
print "Warning: Weird, unable to determine previous nameserver1..."
print " Using localhost as fallback, probably that's the right thing to do."
config_registry.handler_set(["nameserver1=127.0.0.1"])
## Use Samba4 as DNS backend
config_registry.handler_set(["dns/backend=samba4"])
p = subprocess.Popen(["/etc/init.d/samba4", "restart"])
p.wait()
time.sleep(3)
## Restart Bind (get's restarted by samba4 if running)
#p = subprocess.Popen(["/etc/init.d/bind9", "restart"])
#p.wait()
## Start NSCD again
p = subprocess.Popen(["/etc/init.d/nscd", "start"])
p.wait()
## Claim FSMO roles
print "Claiming FSMO roles"
for fsmo_role in ('rid', 'pdc', 'infrastructure', 'schema', 'naming'):
p = subprocess.Popen(["samba-tool", "fsmo", "seize", "--role=%s" % fsmo_role, "--force"])
p.wait()
## Restart Samba
p = subprocess.Popen(["/etc/init.d/samba4", "restart"])
p.wait()
## re-create /etc/krb5.keytab
## https://forge.univention.org/bugzilla/show_bug.cgi?id=27426
p = subprocess.Popen(["/usr/share/univention-samba4/scripts/create-keytab.sh"])
p.wait()
## Cleanup necessary for NETBIOS
print "Cleaning up:"
print "Removing AD DC account from local Samba4 SAM database"
p = subprocess.Popen(["/usr/bin/ldbdel", "-r", "-H", os.path.join(samba_private_dir, "sam.ldb"), "CN=%s,CN=Domain System Volume (SYSVOL share),CN=File Replication Service,CN=System,%s" % (ad_server_name, ucr["samba4/ldap/base"])], stdout=devnull, stderr=devnull)
p.wait()
p = subprocess.Popen(["/usr/bin/ldbdel", "-r", "-H", os.path.join(samba_private_dir, "sam.ldb"), "CN=%s,CN=Servers,CN=%s,CN=Sites,CN=Configuration,%s" % (ad_server_name, sitename, ucr["samba4/ldap/base"])])
p.wait()
p = subprocess.Popen(["/usr/bin/ldbdel", "-r", "-H", os.path.join(samba_private_dir, "sam.ldb"), "CN=%s,OU=Domain Controllers,%s" % (ad_server_name, ucr["samba4/ldap/base"])])
p.wait()
## Finally, for consistency remove AD DC object from UDM
print "Removing AD DC account from local Univention Directory Manager"
p = subprocess.Popen(["univention-directory-manager", "computers/windows_domaincontroller", "delete", "--dn", "cn=%s,cn=dc,cn=computers,%s" % (ad_server_name, ucr["ldap/base"]) ])
p.wait()
## finally run KCC
p = subprocess.Popen(["/usr/sbin/samba-tool", "drs", "kcc"])
p.wait()
if __name__ == '__main__':
parser = OptionParser("%prog [options] []")
parser.add_option("-v", "--verbose", action="store_true")
parser.add_option("--fsmo-takeover", action="store_true")
sambaopts = samba.getopt.SambaOptions(parser)
parser.add_option_group(sambaopts)
parser.add_option_group(samba.getopt.VersionOptions(parser))
# use command line creds if available
credopts = samba.getopt.CredentialsOptions(parser)
parser.add_option_group(credopts)
opts, args = parser.parse_args()
if not opts.fsmo_takeover:
run_phaseI(opts, args)
else:
run_phaseIII(opts, args)