X-Git-Url: http://git.samba.org/?a=blobdiff_plain;f=source4%2Fscripting%2Fpython%2Fsamba%2Fupgrade.py;h=13d33c14422b5ee739ed7a1887a20e69c6823c92;hb=b3413711e12c1357cb63cdbdaa250786f9119032;hp=01b62ff984050abf54b0b671b47d989803e24d97;hpb=e4165448d778db1721b1c7670b25ae3e40917d86;p=samba.git diff --git a/source4/scripting/python/samba/upgrade.py b/source4/scripting/python/samba/upgrade.py index 01b62ff9840..13d33c14422 100644 --- a/source4/scripting/python/samba/upgrade.py +++ b/source4/scripting/python/samba/upgrade.py @@ -1,172 +1,333 @@ -#!/usr/bin/python +# backend code for upgrading from Samba3 +# Copyright Jelmer Vernooij 2005-2007 +# Copyright Andrew Bartlett 2011 # -# backend code for upgrading from Samba3 -# Copyright Jelmer Vernooij 2005-2007 -# Released under the GNU GPL v3 or later +# 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 . # """Support code for upgrading from Samba 3 to Samba 4.""" -from provision import findnss, provision, FILL_DRS -import grp +__docformat__ = "restructuredText" + import ldb import time import pwd -import uuid -import registry -from samba import Ldb -from samba.samdb import SamDB - -def import_sam_policy(samldb, samba3_policy, domaindn): - """Import a Samba 3 policy database.""" - samldb.modify_ldif(""" -dn: %s -changetype: modify -replace: minPwdLength -minPwdLength: %d -pwdHistoryLength: %d -minPwdAge: %d -maxPwdAge: %d -lockoutDuration: %d -samba3ResetCountMinutes: %d -samba3UserMustLogonToChangePassword: %d -samba3BadLockoutMinutes: %d -samba3DisconnectTime: %d - -""" % (dn, policy.min_password_length, - policy.password_history, policy.minimum_password_age, - policy.maximum_password_age, policy.lockout_duration, - policy.reset_count_minutes, policy.user_must_logon_to_change_password, - policy.bad_lockout_minutes, policy.disconnect_time)) - - -def import_sam_account(samldb,acc,domaindn,domainsid): - """Import a Samba 3 SAM account. - - :param samldb: Samba 4 SAM Database handle - :param acc: Samba 3 account - :param domaindn: Domain DN - :param domainsid: Domain SID.""" - if acc.nt_username is None or acc.nt_username == "": - acc.nt_username = acc.username - - if acc.fullname is None: - try: - acc.fullname = pwd.getpwnam(acc.username)[4].split(",")[0] - except KeyError: - pass - if acc.fullname is None: - acc.fullname = acc.username - - assert acc.fullname is not None - assert acc.nt_username is not None - - samldb.add({ - "dn": "cn=%s,%s" % (acc.fullname, domaindn), - "objectClass": ["top", "user"], - "lastLogon": str(acc.logon_time), - "lastLogoff": str(acc.logoff_time), - "unixName": acc.username, - "sAMAccountName": acc.nt_username, - "cn": acc.nt_username, - "description": acc.acct_desc, - "primaryGroupID": str(acc.group_rid), - "badPwdcount": str(acc.bad_password_count), - "logonCount": str(acc.logon_count), - "samba3Domain": acc.domain, - "samba3DirDrive": acc.dir_drive, - "samba3MungedDial": acc.munged_dial, - "samba3Homedir": acc.homedir, - "samba3LogonScript": acc.logon_script, - "samba3ProfilePath": acc.profile_path, - "samba3Workstations": acc.workstations, - "samba3KickOffTime": str(acc.kickoff_time), - "samba3BadPwdTime": str(acc.bad_password_time), - "samba3PassLastSetTime": str(acc.pass_last_set_time), - "samba3PassCanChangeTime": str(acc.pass_can_change_time), - "samba3PassMustChangeTime": str(acc.pass_must_change_time), - "objectSid": "%s-%d" % (domainsid, acc.user_rid), - "lmPwdHash:": acc.lm_password, - "ntPwdHash:": acc.nt_password, - }) - - -def import_sam_group(samldb, sid, gid, sid_name_use, nt_name, comment, domaindn): - """Upgrade a SAM group. - - :param samldb: SAM database. - :param gid: Group GID - :param sid_name_use: SID name use - :param nt_name: NT Group Name - :param comment: NT Group Comment - :param domaindn: Domain DN +from samba import Ldb, registry +from samba.param import LoadParm +from samba.provision import provision, FILL_FULL, ProvisioningError, setsysvolacl +from samba.samba3 import passdb +from samba.samba3 import param as s3param +from samba.dcerpc import lsa, samr, security +from samba.dcerpc.security import dom_sid +from samba.credentials import Credentials +from samba import dsdb +from samba.ndr import ndr_pack +from samba import unix2nttime + + +def import_sam_policy(samdb, policy, logger): + """Import a Samba 3 policy. + + :param samdb: Samba4 SAM database + :param policy: Samba3 account policy + :param logger: Logger object """ - if sid_name_use == 5: # Well-known group - return None + # Following entries are used - + # min password length, password history, minimum password age, + # maximum password age, lockout duration + # + # Following entries are not used - + # reset count minutes, user must logon to change password, + # bad lockout minutes, disconnect time + + m = ldb.Message() + m.dn = samdb.get_default_basedn() + + if 'min password length' in policy: + m['a01'] = ldb.MessageElement(str(policy['min password length']), + ldb.FLAG_MOD_REPLACE, 'minPwdLength') + + if 'password history' in policy: + m['a02'] = ldb.MessageElement(str(policy['password history']), + ldb.FLAG_MOD_REPLACE, 'pwdHistoryLength') + + if 'minimum password age' in policy: + min_pw_age_unix = policy['minimum password age'] + min_pw_age_nt = int(-min_pw_age_unix * (1e7)) + m['a03'] = ldb.MessageElement(str(min_pw_age_nt), ldb.FLAG_MOD_REPLACE, + 'minPwdAge') + + if 'maximum password age' in policy: + max_pw_age_unix = policy['maximum password age'] + if max_pw_age_unix == -1 or max_pw_age_unix == 0: + max_pw_age_nt = -0x8000000000000000 + else: + max_pw_age_nt = int(-max_pw_age_unix * (1e7)) - if nt_name in ("Domain Guests", "Domain Users", "Domain Admins"): - return None - - if gid == -1: - gr = grp.getgrnam(nt_name) - else: - gr = grp.getgrgid(gid) + m['a04'] = ldb.MessageElement(str(max_pw_age_nt), ldb.FLAG_MOD_REPLACE, + 'maxPwdAge') + + if 'lockout duration' in policy: + lockout_duration_mins = policy['lockout duration'] + lockout_duration_nt = unix2nttime(lockout_duration_mins * 60) + + m['a05'] = ldb.MessageElement(str(lockout_duration_nt), + ldb.FLAG_MOD_REPLACE, 'lockoutDuration') + + try: + samdb.modify(m) + except ldb.LdbError, e: + logger.warn("Could not set account policy, (%s)", str(e)) + + +def add_posix_attrs(logger, samdb, sid, name, nisdomain, xid_type, home=None, + shell=None, pgid=None): + """Add posix attributes for the user/group - if gr is None: - unixname = "UNKNOWN" + :param samdb: Samba4 sam.ldb database + :param sid: user/group sid + :param sid: user/group name + :param nisdomain: name of the (fake) NIS domain + :param xid_type: type of id (ID_TYPE_UID/ID_TYPE_GID) + :param home: user homedir (Unix homepath) + :param shell: user shell + :param pgid: users primary group id + """ + + try: + m = ldb.Message() + m.dn = ldb.Dn(samdb, "" % str(sid)) + if xid_type == "ID_TYPE_UID": + m['unixHomeDirectory'] = ldb.MessageElement( + str(home), ldb.FLAG_MOD_REPLACE, 'unixHomeDirectory') + m['loginShell'] = ldb.MessageElement( + str(shell), ldb.FLAG_MOD_REPLACE, 'loginShell') + m['gidNumber'] = ldb.MessageElement( + str(pgid), ldb.FLAG_MOD_REPLACE, 'gidNumber') + + m['msSFU30NisDomain'] = ldb.MessageElement( + str(nisdomain), ldb.FLAG_MOD_REPLACE, 'msSFU30NisDomain') + + samdb.modify(m) + except ldb.LdbError, e: + logger.warn( + 'Could not add posix attrs for AD entry for sid=%s, (%s)', + str(sid), str(e)) + +def add_ad_posix_idmap_entry(samdb, sid, xid, xid_type, logger): + """Create idmap entry + + :param samdb: Samba4 sam.ldb database + :param sid: user/group sid + :param xid: user/group id + :param xid_type: type of id (ID_TYPE_UID/ID_TYPE_GID) + :param logger: Logger object + """ + + try: + m = ldb.Message() + m.dn = ldb.Dn(samdb, "" % str(sid)) + if xid_type == "ID_TYPE_UID": + m['uidNumber'] = ldb.MessageElement( + str(xid), ldb.FLAG_MOD_REPLACE, 'uidNumber') + m['objectClass'] = ldb.MessageElement( + "posixAccount", ldb.FLAG_MOD_ADD, 'objectClass') + elif xid_type == "ID_TYPE_GID": + m['gidNumber'] = ldb.MessageElement( + str(xid), ldb.FLAG_MOD_REPLACE, 'gidNumber') + m['objectClass'] = ldb.MessageElement( + "posixGroup", ldb.FLAG_MOD_ADD, 'objectClass') + + samdb.modify(m) + except ldb.LdbError, e: + logger.warn( + 'Could not modify AD idmap entry for sid=%s, id=%s, type=%s (%s)', + str(sid), str(xid), xid_type, str(e)) + + +def add_idmap_entry(idmapdb, sid, xid, xid_type, logger): + """Create idmap entry + + :param idmapdb: Samba4 IDMAP database + :param sid: user/group sid + :param xid: user/group id + :param xid_type: type of id (ID_TYPE_UID/ID_TYPE_GID) + :param logger: Logger object + """ + + # First try to see if we already have this entry + found = False + msg = idmapdb.search(expression='objectSid=%s' % str(sid)) + if msg.count == 1: + found = True + + if found: + try: + m = ldb.Message() + m.dn = msg[0]['dn'] + m['xidNumber'] = ldb.MessageElement( + str(xid), ldb.FLAG_MOD_REPLACE, 'xidNumber') + m['type'] = ldb.MessageElement( + xid_type, ldb.FLAG_MOD_REPLACE, 'type') + idmapdb.modify(m) + except ldb.LdbError, e: + logger.warn( + 'Could not modify idmap entry for sid=%s, id=%s, type=%s (%s)', + str(sid), str(xid), xid_type, str(e)) else: - unixname = gr.gr_name - - assert unixname is not None - - samldb.add({ - "dn": "cn=%s,%s" % (nt_name, domaindn), - "objectClass": ["top", "group"], - "description": comment, - "cn": nt_name, - "objectSid": sid, - "unixName": unixname, - "samba3SidNameUse": str(sid_name_use) - }) - - -def import_idmap(samdb,samba3_idmap,domaindn): + try: + idmapdb.add({"dn": "CN=%s" % str(sid), + "cn": str(sid), + "objectClass": "sidMap", + "objectSid": ndr_pack(sid), + "type": xid_type, + "xidNumber": str(xid)}) + except ldb.LdbError, e: + logger.warn( + 'Could not add idmap entry for sid=%s, id=%s, type=%s (%s)', + str(sid), str(xid), xid_type, str(e)) + + +def import_idmap(idmapdb, samba3, logger): """Import idmap data. - :param samdb: SamDB handle. - :param samba3_idmap: Samba 3 IDMAP database to import from - :param domaindn: Domain DN. + :param idmapdb: Samba4 IDMAP database + :param samba3_idmap: Samba3 IDMAP database to import from + :param logger: Logger object """ - samdb.add({ - "dn": domaindn, - "userHwm": str(samba3_idmap.get_user_hwm()), - "groupHwm": str(samba3_idmap.get_group_hwm())}) - for uid in samba3_idmap.uids(): - samdb.add({"dn": "SID=%s,%s" % (samba3_idmap.get_user_sid(uid), domaindn), - "SID": samba3_idmap.get_user_sid(uid), - "type": "user", - "unixID": str(uid)}) + try: + samba3_idmap = samba3.get_idmap_db() + except IOError, e: + logger.warn('Cannot open idmap database, Ignoring: %s', str(e)) + return + + currentxid = max(samba3_idmap.get_user_hwm(), samba3_idmap.get_group_hwm()) + lowerbound = currentxid + # FIXME: upperbound + + m = ldb.Message() + m.dn = ldb.Dn(idmapdb, 'CN=CONFIG') + m['lowerbound'] = ldb.MessageElement( + str(lowerbound), ldb.FLAG_MOD_REPLACE, 'lowerBound') + m['xidNumber'] = ldb.MessageElement( + str(currentxid), ldb.FLAG_MOD_REPLACE, 'xidNumber') + idmapdb.modify(m) + + for id_type, xid in samba3_idmap.ids(): + if id_type == 'UID': + xid_type = 'ID_TYPE_UID' + elif id_type == 'GID': + xid_type = 'ID_TYPE_GID' + else: + logger.warn('Wrong type of entry in idmap (%s), Ignoring', id_type) + continue + + sid = samba3_idmap.get_sid(xid, id_type) + add_idmap_entry(idmapdb, dom_sid(sid), xid, xid_type, logger) - for gid in samba3_idmap.uids(): - samdb.add({"dn": "SID=%s,%s" % (samba3_idmap.get_group_sid(gid), domaindn), - "SID": samba3_idmap.get_group_sid(gid), - "type": "group", - "unixID": str(gid)}) + +def add_group_from_mapping_entry(samdb, groupmap, logger): + """Add or modify group from group mapping entry + + param samdb: Samba4 SAM database + param groupmap: Groupmap entry + param logger: Logger object + """ + + # First try to see if we already have this entry + try: + msg = samdb.search( + base='' % str(groupmap.sid), scope=ldb.SCOPE_BASE) + found = True + except ldb.LdbError, (ecode, emsg): + if ecode == ldb.ERR_NO_SUCH_OBJECT: + found = False + else: + raise ldb.LdbError(ecode, emsg) + + if found: + logger.warn('Group already exists sid=%s, groupname=%s existing_groupname=%s, Ignoring.', + str(groupmap.sid), groupmap.nt_name, msg[0]['sAMAccountName'][0]) + else: + if groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP: + # In a lot of Samba3 databases, aliases are marked as well known groups + (group_dom_sid, rid) = groupmap.sid.split() + if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)): + return + + m = ldb.Message() + m.dn = ldb.Dn(samdb, "CN=%s,CN=Users,%s" % (groupmap.nt_name, samdb.get_default_basedn())) + m['cn'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'cn') + m['objectClass'] = ldb.MessageElement('group', ldb.FLAG_MOD_ADD, 'objectClass') + m['objectSid'] = ldb.MessageElement(ndr_pack(groupmap.sid), ldb.FLAG_MOD_ADD, + 'objectSid') + m['sAMAccountName'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, + 'sAMAccountName') + + if groupmap.comment: + m['description'] = ldb.MessageElement(groupmap.comment, ldb.FLAG_MOD_ADD, + 'description') + + # Fix up incorrect 'well known' groups that are actually builtin (per test above) to be aliases + if groupmap.sid_name_use == lsa.SID_NAME_ALIAS or groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP: + m['groupType'] = ldb.MessageElement(str(dsdb.GTYPE_SECURITY_DOMAIN_LOCAL_GROUP), + ldb.FLAG_MOD_ADD, 'groupType') + + try: + samdb.add(m, controls=["relax:0"]) + except ldb.LdbError, e: + logger.warn('Could not add group name=%s (%s)', groupmap.nt_name, str(e)) + + +def add_users_to_group(samdb, group, members, logger): + """Add user/member to group/alias + + param samdb: Samba4 SAM database + param group: Groupmap object + param members: List of member SIDs + param logger: Logger object + """ + for member_sid in members: + m = ldb.Message() + m.dn = ldb.Dn(samdb, "" % str(group.sid)) + m['a01'] = ldb.MessageElement("" % str(member_sid), ldb.FLAG_MOD_ADD, 'member') + + try: + samdb.modify(m) + except ldb.LdbError, (ecode, emsg): + if ecode == ldb.ERR_ENTRY_ALREADY_EXISTS: + logger.debug("skipped re-adding member '%s' to group '%s': %s", member_sid, group.sid, emsg) + elif ecode == ldb.ERR_NO_SUCH_OBJECT: + raise ProvisioningError("Could not add member '%s' to group '%s' as either group or user record doesn't exist: %s" % (member_sid, group.sid, emsg)) + else: + raise ProvisioningError("Could not add member '%s' to group '%s': %s" % (member_sid, group.sid, emsg)) def import_wins(samba4_winsdb, samba3_winsdb): """Import settings from a Samba3 WINS database. - + :param samba4_winsdb: WINS database to import to :param samba3_winsdb: WINS database to import from """ + version_id = 0 for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items(): - version_id+=1 + version_id += 1 type = int(name.split("#", 1)[1], 16) @@ -188,7 +349,7 @@ def import_wins(samba4_winsdb, samba3_winsdb): else: rState = 0x1 # released - nType = ((nb_flags & 0x60)>>5) + nType = ((nb_flags & 0x60) >> 5) samba4_winsdb.add({"dn": "name=%s,type=0x%s" % tuple(name.split("#")), "type": name.split("#")[1], @@ -207,77 +368,6 @@ def import_wins(samba4_winsdb, samba3_winsdb): "objectClass": "winsMaxVersion", "maxVersion": str(version_id)}) -def upgrade_provision(samba3, setup_dir, message, credentials, session_info, lp, paths): - oldconf = samba3.get_conf() - - if oldconf.get("domain logons") == "True": - serverrole = "domain controller" - else: - if oldconf.get("security") == "user": - serverrole = "standalone" - else: - serverrole = "member server" - - lp.set("server role", serverrole) - domainname = oldconf.get("workgroup") - if domainname: - domainname = str(domainname) - lp.set("workgroup", domainname) - realm = oldconf.get("realm") - netbiosname = oldconf.get("netbios name") - - secrets_db = samba3.get_secrets_db() - - if domainname is None: - domainname = secrets_db.domains()[0] - message("No domain specified in smb.conf file, assuming '%s'" % domainname) - - if realm is None: - realm = domainname.lower() - message("No realm specified in smb.conf file, assuming '%s'\n" % realm) - lp.set("realm", realm) - - domainguid = secrets_db.get_domain_guid(domainname) - domainsid = secrets_db.get_sid(domainname) - if domainsid is None: - message("Can't find domain secrets for '%s'; using random SID\n" % domainname) - - if netbiosname is not None: - machinepass = secrets_db.get_machine_password(netbiosname) - else: - machinepass = None - - domaindn = provision(lp=lp, setup_dir=setup_dir, message=message, - samdb_fill=FILL_DRS, paths=paths, session_info=session_info, - credentials=credentials, realm=realm, - domain=domainname, domainsid=domainsid, domainguid=domainguid, - machinepass=machinepass, serverrole=serverrole) - - samdb = SamDB(paths.samdb, credentials=credentials, lp=lp, session_info=session_info) - - import_wins(Ldb(paths.winsdb), samba3.get_wins_db()) - - # FIXME: import_registry(registry.Registry(), samba3.get_registry()) - - # FIXME: import_idmap(samdb,samba3.get_idmap_db(),domaindn) - - groupdb = samba3.get_groupmapping_db() - for sid in groupdb.groupsids(): - (gid, sid_name_use, nt_name, comment) = groupdb.get_group(sid) - # FIXME: import_sam_group(samdb, sid, gid, sid_name_use, nt_name, comment, domaindn) - - # FIXME: Aliases - - passdb = samba3.get_sam_db() - for name in passdb: - user = passdb[name] - #FIXME: import_sam_account(samdb, user, domaindn, domainsid) - - if hasattr(passdb, 'ldap_url'): - message("Enabling Samba3 LDAP mappings for SAM database") - - enable_samba3sam(samdb, passdb.ldap_url) - def enable_samba3sam(samdb, ldapurl): """Enable Samba 3 LDAP URL database. @@ -296,7 +386,7 @@ replace: @LIST smbconf_keep = [ - "dos charset", + "dos charset", "unix charset", "display charset", "comment", @@ -340,8 +430,6 @@ smbconf_keep = [ "write raw", "disable netbios", "nt status support", - "announce version", - "announce as", "max mux", "max xmit", "name resolve order", @@ -389,15 +477,16 @@ smbconf_keep = [ "host msdfs", "winbind separator"] -def upgrade_smbconf(oldconf,mark): + +def upgrade_smbconf(oldconf, mark): """Remove configuration variables not present in Samba4 :param oldconf: Old configuration structure - :param mark: Whether removed configuration variables should be + :param mark: Whether removed configuration variables should be kept in the new configuration as "samba3:" """ data = oldconf.data() - newconf = param_init() + newconf = LoadParm() for s in data: for p in data[s]: @@ -410,7 +499,7 @@ def upgrade_smbconf(oldconf,mark): if keep: newconf.set(s, p, oldconf.get(s, p)) elif mark: - newconf.set(s, "samba3:"+p, oldconf.get(s,p)) + newconf.set(s, "samba3:" + p, oldconf.get(s, p)) return newconf @@ -418,6 +507,7 @@ SAMBA3_PREDEF_NAMES = { 'HKLM': registry.HKEY_LOCAL_MACHINE, } + def import_registry(samba4_registry, samba3_regdb): """Import a Samba 3 registry database into the Samba 4 registry. @@ -437,4 +527,397 @@ def import_registry(samba4_registry, samba3_regdb): for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items(): key_handle.set_value(value_name, value_type, value_data) +def get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, user, attr): + """Get posix attributes from a samba3 ldap backend + :param ldbs: a list of ldb connection objects + :param base_dn: the base_dn of the connection + :param user: the user to get the attribute for + :param attr: the attribute to be retrieved + """ + try: + msg = ldb_object.search(base_dn, scope=ldb.SCOPE_SUBTREE, + expression=("(&(objectClass=posixAccount)(uid=%s))" + % (user)), attrs=[attr]) + except ldb.LdbError, e: + raise ProvisioningError("Failed to retrieve attribute %s for user %s, the error is: %s", attr, user, e) + else: + if msg.count <= 1: + # This will raise KeyError (which is what we want) if there isn't a entry for this user + return msg[0][attr][0] + else: + logger.warning("LDAP entry for user %s contains more than one %s", user, attr) + raise KeyError + + +def upgrade_from_samba3(samba3, logger, targetdir, session_info=None, + useeadb=False, dns_backend=None, use_ntvfs=False): + """Upgrade from samba3 database to samba4 AD database + + :param samba3: samba3 object + :param logger: Logger object + :param targetdir: samba4 database directory + :param session_info: Session information + """ + serverrole = samba3.lp.server_role() + domainname = samba3.lp.get("workgroup") + realm = samba3.lp.get("realm") + netbiosname = samba3.lp.get("netbios name") + + if samba3.lp.get("ldapsam:trusted") is None: + samba3.lp.set("ldapsam:trusted", "yes") + + # secrets db + try: + secrets_db = samba3.get_secrets_db() + except IOError, e: + raise ProvisioningError("Could not open '%s', the Samba3 secrets database: %s. Perhaps you specified the incorrect smb.conf, --testparm or --dbdir option?" % (samba3.privatedir_path("secrets.tdb"), str(e))) + + if not domainname: + domainname = secrets_db.domains()[0] + logger.warning("No workgroup specified in smb.conf file, assuming '%s'", + domainname) + + if not realm: + if serverrole == "ROLE_DOMAIN_BDC" or serverrole == "ROLE_DOMAIN_PDC": + raise ProvisioningError("No realm specified in smb.conf file and being a DC. That upgrade path doesn't work! Please add a 'realm' directive to your old smb.conf to let us know which one you want to use (it is the DNS name of the AD domain you wish to create.") + else: + realm = domainname.upper() + logger.warning("No realm specified in smb.conf file, assuming '%s'", + realm) + + # Find machine account and password + next_rid = 1000 + + try: + machinepass = secrets_db.get_machine_password(netbiosname) + except KeyError: + machinepass = None + + if samba3.lp.get("passdb backend").split(":")[0].strip() == "ldapsam": + base_dn = samba3.lp.get("ldap suffix") + ldapuser = samba3.lp.get("ldap admin dn") + ldappass = (secrets_db.get_ldap_bind_pw(ldapuser)).strip('\x00') + ldap = True + else: + ldapuser = None + ldappass = None + ldap = False + + # We must close the direct pytdb database before the C code loads it + secrets_db.close() + + # Connect to old password backend + passdb.set_secrets_dir(samba3.lp.get("private dir")) + s3db = samba3.get_sam_db() + + # Get domain sid + try: + domainsid = passdb.get_global_sam_sid() + except passdb.error: + raise Exception("Can't find domain sid for '%s', Exiting." % domainname) + + # Get machine account, sid, rid + try: + machineacct = s3db.getsampwnam('%s$' % netbiosname) + except passdb.error: + machinerid = None + machinesid = None + else: + machinesid, machinerid = machineacct.user_sid.split() + + # Export account policy + logger.info("Exporting account policy") + policy = s3db.get_account_policy() + + # Export groups from old passdb backend + logger.info("Exporting groups") + grouplist = s3db.enum_group_mapping() + groupmembers = {} + for group in grouplist: + sid, rid = group.sid.split() + if sid == domainsid: + if rid >= next_rid: + next_rid = rid + 1 + + # Get members for each group/alias + if group.sid_name_use == lsa.SID_NAME_ALIAS: + try: + members = s3db.enum_aliasmem(group.sid) + groupmembers[str(group.sid)] = members + except passdb.error, e: + logger.warn("Ignoring group '%s' %s listed but then not found: %s", + group.nt_name, group.sid, e) + continue + elif group.sid_name_use == lsa.SID_NAME_DOM_GRP: + try: + members = s3db.enum_group_members(group.sid) + groupmembers[str(group.sid)] = members + except passdb.error, e: + logger.warn("Ignoring group '%s' %s listed but then not found: %s", + group.nt_name, group.sid, e) + continue + elif group.sid_name_use == lsa.SID_NAME_WKN_GRP: + (group_dom_sid, rid) = group.sid.split() + if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)): + logger.warn("Ignoring 'well known' group '%s' (should already be in AD, and have no members)", + group.nt_name) + continue + # A number of buggy databases mix up well known groups and aliases. + try: + members = s3db.enum_aliasmem(group.sid) + groupmembers[str(group.sid)] = members + except passdb.error, e: + logger.warn("Ignoring group '%s' %s listed but then not found: %s", + group.nt_name, group.sid, e) + continue + else: + logger.warn("Ignoring group '%s' %s with sid_name_use=%d", + group.nt_name, group.sid, group.sid_name_use) + continue + + # Export users from old passdb backend + logger.info("Exporting users") + userlist = s3db.search_users(0) + userdata = {} + uids = {} + admin_user = None + for entry in userlist: + if machinerid and machinerid == entry['rid']: + continue + username = entry['account_name'] + if entry['rid'] < 1000: + logger.info(" Skipping wellknown rid=%d (for username=%s)", entry['rid'], username) + continue + if entry['rid'] >= next_rid: + next_rid = entry['rid'] + 1 + + user = s3db.getsampwnam(username) + acct_type = (user.acct_ctrl & (samr.ACB_NORMAL|samr.ACB_WSTRUST|samr.ACB_SVRTRUST|samr.ACB_DOMTRUST)) + if (acct_type == samr.ACB_NORMAL or acct_type == samr.ACB_WSTRUST): + pass + + elif acct_type == samr.ACB_SVRTRUST: + logger.warn(" Demoting BDC account trust for %s, this DC must be elevated to an AD DC using 'samba-tool domain promote'" % username[:-1]) + user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_SVRTRUST) | samr.ACB_WSTRUST + + elif acct_type == samr.ACB_DOMTRUST: + logger.warn(" Skipping inter-domain trust from domain %s, this trust must be re-created as an AD trust" % username[:-1]) + + elif acct_type == (samr.ACB_NORMAL|samr.ACB_WSTRUST) and username[-1] == '$': + logger.warn(" Fixing account %s which had both ACB_NORMAL (U) and ACB_WSTRUST (W) set. Account will be marked as ACB_WSTRUST (W), i.e. as a domain member" % username) + user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_NORMAL) + + elif acct_type == (samr.ACB_NORMAL|samr.ACB_SVRTRUST) and username[-1] == '$': + logger.warn(" Fixing account %s which had both ACB_NORMAL (U) and ACB_SVRTRUST (S) set. Account will be marked as ACB_WSTRUST (S), i.e. as a domain member" % username) + user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_NORMAL) + + else: + raise ProvisioningError("""Failed to upgrade due to invalid account %s, account control flags 0x%08X must have exactly one of +ACB_NORMAL (N, 0x%08X), ACB_WSTRUST (W 0x%08X), ACB_SVRTRUST (S 0x%08X) or ACB_DOMTRUST (D 0x%08X). + +Please fix this account before attempting to upgrade again +""" + % (user.acct_flags, username, + samr.ACB_NORMAL, samr.ACB_WSTRUST, samr.ACB_SVRTRUST, samr.ACB_DOMTRUST)) + + userdata[username] = user + try: + uids[username] = s3db.sid_to_id(user.user_sid)[0] + except passdb.error: + try: + uids[username] = pwd.getpwnam(username).pw_uid + except KeyError: + pass + + if not admin_user and username.lower() == 'root': + admin_user = username + if username.lower() == 'administrator': + admin_user = username + + try: + group_memberships = s3db.enum_group_memberships(user); + for group in group_memberships: + if str(group) in groupmembers: + if user.user_sid not in groupmembers[str(group)]: + groupmembers[str(group)].append(user.user_sid) + else: + groupmembers[str(group)] = [user.user_sid]; + except passdb.error, e: + logger.warn("Ignoring group memberships of '%s' %s: %s", + username, user.user_sid, e) + + logger.info("Next rid = %d", next_rid) + + # Check for same username/groupname + group_names = set([g.nt_name for g in grouplist]) + user_names = set([u['account_name'] for u in userlist]) + common_names = group_names.intersection(user_names) + if common_names: + logger.error("Following names are both user names and group names:") + for name in common_names: + logger.error(" %s" % name) + raise ProvisioningError("Please remove common user/group names before upgrade.") + + # Check for same user sid/group sid + group_sids = set([str(g.sid) for g in grouplist]) + if len(grouplist) != len(group_sids): + raise ProvisioningError("Please remove duplicate group sid entries before upgrade.") + user_sids = set(["%s-%u" % (domainsid, u['rid']) for u in userlist]) + if len(userlist) != len(user_sids): + raise ProvisioningError("Please remove duplicate user sid entries before upgrade.") + common_sids = group_sids.intersection(user_sids) + if common_sids: + logger.error("Following sids are both user and group sids:") + for sid in common_sids: + logger.error(" %s" % str(sid)) + raise ProvisioningError("Please remove duplicate sid entries before upgrade.") + + # Get posix attributes from ldap or the os + homes = {} + shells = {} + pgids = {} + if ldap: + creds = Credentials() + creds.guess(samba3.lp) + creds.set_bind_dn(ldapuser) + creds.set_password(ldappass) + urls = samba3.lp.get("passdb backend").split(":",1)[1].strip('"') + for url in urls.split(): + try: + ldb_object = Ldb(url, credentials=creds) + except ldb.LdbError, e: + logger.warning("Could not open ldb connection to %s, the error message is: %s", url, e) + else: + break + logger.info("Exporting posix attributes") + userlist = s3db.search_users(0) + for entry in userlist: + username = entry['account_name'] + if username in uids.keys(): + try: + if ldap: + homes[username] = get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, username, "homeDirectory") + else: + homes[username] = pwd.getpwnam(username).pw_dir + except KeyError: + pass + + try: + if ldap: + shells[username] = get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, username, "loginShell") + else: + shells[username] = pwd.getpwnam(username).pw_shell + except KeyError: + pass + + try: + if ldap: + pgids[username] = get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, username, "gidNumber") + else: + pgids[username] = pwd.getpwnam(username).pw_gid + except KeyError: + pass + + logger.info("Reading WINS database") + samba3_winsdb = None + try: + samba3_winsdb = samba3.get_wins_db() + except IOError, e: + logger.warn('Cannot open wins database, Ignoring: %s', str(e)) + + if not (serverrole == "ROLE_DOMAIN_BDC" or serverrole == "ROLE_DOMAIN_PDC"): + dns_backend = "NONE" + + # Do full provision + result = provision(logger, session_info, None, + targetdir=targetdir, realm=realm, domain=domainname, + domainsid=str(domainsid), next_rid=next_rid, + dc_rid=machinerid, + dom_for_fun_level=dsdb.DS_DOMAIN_FUNCTION_2003, + hostname=netbiosname.lower(), machinepass=machinepass, + serverrole=serverrole, samdb_fill=FILL_FULL, + useeadb=useeadb, dns_backend=dns_backend, use_rfc2307=True, + use_ntvfs=use_ntvfs, skip_sysvolacl=True) + result.report_logger(logger) + + # Import WINS database + logger.info("Importing WINS database") + + if samba3_winsdb: + import_wins(Ldb(result.paths.winsdb), samba3_winsdb) + + # Set Account policy + logger.info("Importing Account policy") + import_sam_policy(result.samdb, policy, logger) + + # Migrate IDMAP database + logger.info("Importing idmap database") + import_idmap(result.idmap, samba3, logger) + + # Set the s3 context for samba4 configuration + new_lp_ctx = s3param.get_context() + new_lp_ctx.load(result.lp.configfile) + new_lp_ctx.set("private dir", result.lp.get("private dir")) + new_lp_ctx.set("state directory", result.lp.get("state directory")) + new_lp_ctx.set("lock directory", result.lp.get("lock directory")) + + # Connect to samba4 backend + s4_passdb = passdb.PDB(new_lp_ctx.get("passdb backend")) + + # Export groups to samba4 backend + logger.info("Importing groups") + for g in grouplist: + # Ignore uninitialized groups (gid = -1) + if g.gid != -1: + add_group_from_mapping_entry(result.samdb, g, logger) + add_ad_posix_idmap_entry(result.samdb, g.sid, g.gid, "ID_TYPE_GID", logger) + add_posix_attrs(samdb=result.samdb, sid=g.sid, name=g.nt_name, nisdomain=domainname.lower(), xid_type="ID_TYPE_GID", logger=logger) + + # Export users to samba4 backend + logger.info("Importing users") + for username in userdata: + if username.lower() == 'administrator': + if userdata[username].user_sid != dom_sid(str(domainsid) + "-500"): + logger.error("User 'Administrator' in your existing directory has SID %s, expected it to be %s" % (userdata[username].user_sid, dom_sid(str(domainsid) + "-500"))) + raise ProvisioningError("User 'Administrator' in your existing directory does not have SID ending in -500") + if username.lower() == 'root': + if userdata[username].user_sid == dom_sid(str(domainsid) + "-500"): + logger.warn('User root has been replaced by Administrator') + else: + logger.warn('User root has been kept in the directory, it should be removed in favour of the Administrator user') + + s4_passdb.add_sam_account(userdata[username]) + if username in uids: + add_ad_posix_idmap_entry(result.samdb, userdata[username].user_sid, uids[username], "ID_TYPE_UID", logger) + if (username in homes) and (homes[username] is not None) and \ + (username in shells) and (shells[username] is not None) and \ + (username in pgids) and (pgids[username] is not None): + add_posix_attrs(samdb=result.samdb, sid=userdata[username].user_sid, name=username, nisdomain=domainname.lower(), xid_type="ID_TYPE_UID", home=homes[username], shell=shells[username], pgid=pgids[username], logger=logger) + + logger.info("Adding users to groups") + for g in grouplist: + if str(g.sid) in groupmembers: + add_users_to_group(result.samdb, g, groupmembers[str(g.sid)], logger) + + # Set password for administrator + if admin_user: + logger.info("Setting password for administrator") + admin_userdata = s4_passdb.getsampwnam("administrator") + admin_userdata.nt_passwd = userdata[admin_user].nt_passwd + if userdata[admin_user].lanman_passwd: + admin_userdata.lanman_passwd = userdata[admin_user].lanman_passwd + admin_userdata.pass_last_set_time = userdata[admin_user].pass_last_set_time + if userdata[admin_user].pw_history: + admin_userdata.pw_history = userdata[admin_user].pw_history + s4_passdb.update_sam_account(admin_userdata) + logger.info("Administrator password has been set to password of user '%s'", admin_user) + + if result.server_role == "active directory domain controller": + setsysvolacl(result.samdb, result.paths.netlogon, result.paths.sysvol, + result.paths.root_uid, result.paths.root_gid, + security.dom_sid(result.domainsid), result.names.dnsdomain, + result.names.domaindn, result.lp, use_ntvfs) + + # FIXME: import_registry(registry.Registry(), samba3.get_registry()) + # FIXME: shares