1 # backend code for upgrading from Samba3
2 # Copyright Jelmer Vernooij 2005-2007
3 # Copyright Andrew Bartlett 2011
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 """Support code for upgrading from Samba 3 to Samba 4."""
21 __docformat__ = "restructuredText"
27 from samba import Ldb, registry
28 from samba.param import LoadParm
29 from samba.provision import provision, FILL_FULL, ProvisioningError
30 from samba.samba3 import passdb
31 from samba.samba3 import param as s3param
32 from samba.dcerpc import lsa, samr, security
33 from samba.dcerpc.security import dom_sid
34 from samba import dsdb
35 from samba.ndr import ndr_pack
36 from samba import unix2nttime
39 def import_sam_policy(samdb, policy, logger):
40 """Import a Samba 3 policy.
42 :param samdb: Samba4 SAM database
43 :param policy: Samba3 account policy
44 :param logger: Logger object
47 # Following entries are used -
48 # min password length, password history, minimum password age,
49 # maximum password age, lockout duration
51 # Following entries are not used -
52 # reset count minutes, user must logon to change password,
53 # bad lockout minutes, disconnect time
56 m.dn = samdb.get_default_basedn()
57 m['a01'] = ldb.MessageElement(str(policy['min password length']),
58 ldb.FLAG_MOD_REPLACE, 'minPwdLength')
59 m['a02'] = ldb.MessageElement(str(policy['password history']),
60 ldb.FLAG_MOD_REPLACE, 'pwdHistoryLength')
62 min_pw_age_unix = policy['minimum password age']
63 min_pw_age_nt = 0 - unix2nttime(min_pw_age_unix)
64 m['a03'] = ldb.MessageElement(str(min_pw_age_nt), ldb.FLAG_MOD_REPLACE,
67 max_pw_age_unix = policy['maximum password age']
68 if (max_pw_age_unix == 0xFFFFFFFF):
71 max_pw_age_nt = unix2nttime(max_pw_age_unix)
73 m['a04'] = ldb.MessageElement(str(max_pw_age_nt), ldb.FLAG_MOD_REPLACE,
76 lockout_duration_mins = policy['lockout duration']
77 lockout_duration_nt = unix2nttime(lockout_duration_mins * 60)
79 m['a05'] = ldb.MessageElement(str(lockout_duration_nt),
80 ldb.FLAG_MOD_REPLACE, 'lockoutDuration')
84 except ldb.LdbError, e:
85 logger.warn("Could not set account policy, (%s)", str(e))
88 def add_idmap_entry(idmapdb, sid, xid, xid_type, logger):
91 :param idmapdb: Samba4 IDMAP database
92 :param sid: user/group sid
93 :param xid: user/group id
94 :param xid_type: type of id (UID/GID)
95 :param logger: Logger object
98 # First try to see if we already have this entry
100 msg = idmapdb.search(expression='objectSid=%s' % str(sid))
108 m['xidNumber'] = ldb.MessageElement(
109 str(xid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
110 m['type'] = ldb.MessageElement(
111 xid_type, ldb.FLAG_MOD_REPLACE, 'type')
113 except ldb.LdbError, e:
115 'Could not modify idmap entry for sid=%s, id=%s, type=%s (%s)',
116 str(sid), str(xid), xid_type, str(e))
119 idmapdb.add({"dn": "CN=%s" % str(sid),
121 "objectClass": "sidMap",
122 "objectSid": ndr_pack(sid),
124 "xidNumber": str(xid)})
125 except ldb.LdbError, e:
127 'Could not add idmap entry for sid=%s, id=%s, type=%s (%s)',
128 str(sid), str(xid), xid_type, str(e))
131 def import_idmap(idmapdb, samba3, logger):
132 """Import idmap data.
134 :param idmapdb: Samba4 IDMAP database
135 :param samba3_idmap: Samba3 IDMAP database to import from
136 :param logger: Logger object
140 samba3_idmap = samba3.get_idmap_db()
142 logger.warn('Cannot open idmap database, Ignoring: %s', str(e))
145 currentxid = max(samba3_idmap.get_user_hwm(), samba3_idmap.get_group_hwm())
146 lowerbound = currentxid
150 m.dn = ldb.Dn(idmapdb, 'CN=CONFIG')
151 m['lowerbound'] = ldb.MessageElement(
152 str(lowerbound), ldb.FLAG_MOD_REPLACE, 'lowerBound')
153 m['xidNumber'] = ldb.MessageElement(
154 str(currentxid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
157 for id_type, xid in samba3_idmap.ids():
159 xid_type = 'ID_TYPE_UID'
160 elif id_type == 'GID':
161 xid_type = 'ID_TYPE_GID'
163 logger.warn('Wrong type of entry in idmap (%s), Ignoring', id_type)
166 sid = samba3_idmap.get_sid(xid, id_type)
167 add_idmap_entry(idmapdb, dom_sid(sid), xid, xid_type, logger)
170 def add_group_from_mapping_entry(samdb, groupmap, logger):
171 """Add or modify group from group mapping entry
173 param samdb: Samba4 SAM database
174 param groupmap: Groupmap entry
175 param logger: Logger object
178 # First try to see if we already have this entry
181 base='<SID=%s>' % str(groupmap.sid), scope=ldb.SCOPE_BASE)
183 except ldb.LdbError, (ecode, emsg):
184 if ecode == ldb.ERR_NO_SUCH_OBJECT:
187 raise ldb.LdbError(ecode, emsg)
190 logger.warn('Group already exists sid=%s, groupname=%s existing_groupname=%s, Ignoring.',
191 str(groupmap.sid), groupmap.nt_name, msg[0]['sAMAccountName'][0])
193 if groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
194 # In a lot of Samba3 databases, aliases are marked as well known groups
195 (group_dom_sid, rid) = groupmap.sid.split()
196 if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)):
200 m.dn = ldb.Dn(samdb, "CN=%s,CN=Users,%s" % (groupmap.nt_name, samdb.get_default_basedn()))
201 m['cn'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'cn')
202 m['objectClass'] = ldb.MessageElement('group', ldb.FLAG_MOD_ADD, 'objectClass')
203 m['objectSid'] = ldb.MessageElement(ndr_pack(groupmap.sid), ldb.FLAG_MOD_ADD,
205 m['sAMAccountName'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD,
209 m['description'] = ldb.MessageElement(groupmap.comment, ldb.FLAG_MOD_ADD,
212 # Fix up incorrect 'well known' groups that are actually builtin (per test above) to be aliases
213 if groupmap.sid_name_use == lsa.SID_NAME_ALIAS or groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
214 m['groupType'] = ldb.MessageElement(str(dsdb.GTYPE_SECURITY_DOMAIN_LOCAL_GROUP),
215 ldb.FLAG_MOD_ADD, 'groupType')
218 samdb.add(m, controls=["relax:0"])
219 except ldb.LdbError, e:
220 logger.warn('Could not add group name=%s (%s)', groupmap.nt_name, str(e))
223 def add_users_to_group(samdb, group, members, logger):
224 """Add user/member to group/alias
226 param samdb: Samba4 SAM database
227 param group: Groupmap object
228 param members: List of member SIDs
229 param logger: Logger object
231 for member_sid in members:
233 m.dn = ldb.Dn(samdb, "<SID=%s>" % str(group.sid))
234 m['a01'] = ldb.MessageElement("<SID=%s>" % str(member_sid), ldb.FLAG_MOD_ADD, 'member')
238 except ldb.LdbError, (ecode, emsg):
239 if ecode == ldb.ERR_ENTRY_ALREADY_EXISTS:
240 logger.info("skipped re-adding member '%s' to group '%s': %s", member_sid, group.sid, emsg)
241 elif ecode == ldb.ERR_NO_SUCH_OBJECT:
242 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))
244 raise ProvisioningError("Could not add member '%s' to group '%s': %s" % (member_sid, group.sid, emsg))
247 def import_wins(samba4_winsdb, samba3_winsdb):
248 """Import settings from a Samba3 WINS database.
250 :param samba4_winsdb: WINS database to import to
251 :param samba3_winsdb: WINS database to import from
256 for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items():
259 type = int(name.split("#", 1)[1], 16)
274 if ttl > time.time():
275 rState = 0x0 # active
277 rState = 0x1 # released
279 nType = ((nb_flags & 0x60) >> 5)
281 samba4_winsdb.add({"dn": "name=%s,type=0x%s" % tuple(name.split("#")),
282 "type": name.split("#")[1],
283 "name": name.split("#")[0],
284 "objectClass": "winsRecord",
285 "recordType": str(rType),
286 "recordState": str(rState),
287 "nodeType": str(nType),
288 "expireTime": ldb.timestring(ttl),
290 "versionID": str(version_id),
293 samba4_winsdb.add({"dn": "cn=VERSION",
295 "objectClass": "winsMaxVersion",
296 "maxVersion": str(version_id)})
299 def enable_samba3sam(samdb, ldapurl):
300 """Enable Samba 3 LDAP URL database.
302 :param samdb: SAM Database.
303 :param ldapurl: Samba 3 LDAP URL
305 samdb.modify_ldif("""
309 @LIST: samldb,operational,objectguid,rdn_name,samba3sam
312 samdb.add({"dn": "@MAP=samba3sam", "@MAP_URL": ldapurl})
329 "bind interfaces only",
334 "obey pam restrictions",
342 "client NTLMv2 auth",
343 "client lanman auth",
344 "client plaintext auth",
362 "name resolve order",
371 "paranoid server security",
408 def upgrade_smbconf(oldconf, mark):
409 """Remove configuration variables not present in Samba4
411 :param oldconf: Old configuration structure
412 :param mark: Whether removed configuration variables should be
413 kept in the new configuration as "samba3:<name>"
415 data = oldconf.data()
421 for k in smbconf_keep:
422 if smbconf_keep[k] == p:
427 newconf.set(s, p, oldconf.get(s, p))
429 newconf.set(s, "samba3:" + p, oldconf.get(s, p))
433 SAMBA3_PREDEF_NAMES = {
434 'HKLM': registry.HKEY_LOCAL_MACHINE,
438 def import_registry(samba4_registry, samba3_regdb):
439 """Import a Samba 3 registry database into the Samba 4 registry.
441 :param samba4_registry: Samba 4 registry handle.
442 :param samba3_regdb: Samba 3 registry database handle.
444 def ensure_key_exists(keypath):
445 (predef_name, keypath) = keypath.split("/", 1)
446 predef_id = SAMBA3_PREDEF_NAMES[predef_name]
447 keypath = keypath.replace("/", "\\")
448 return samba4_registry.create_key(predef_id, keypath)
450 for key in samba3_regdb.keys():
451 key_handle = ensure_key_exists(key)
452 for subkey in samba3_regdb.subkeys(key):
453 ensure_key_exists(subkey)
454 for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items():
455 key_handle.set_value(value_name, value_type, value_data)
458 def upgrade_from_samba3(samba3, logger, targetdir, session_info=None, useeadb=False):
459 """Upgrade from samba3 database to samba4 AD database
461 :param samba3: samba3 object
462 :param logger: Logger object
463 :param targetdir: samba4 database directory
464 :param session_info: Session information
467 if samba3.lp.get("domain logons"):
468 serverrole = "domain controller"
470 if samba3.lp.get("security") == "user":
471 serverrole = "standalone"
473 serverrole = "member server"
475 domainname = samba3.lp.get("workgroup")
476 realm = samba3.lp.get("realm")
477 netbiosname = samba3.lp.get("netbios name")
481 secrets_db = samba3.get_secrets_db()
483 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)))
486 domainname = secrets_db.domains()[0]
487 logger.warning("No workgroup specified in smb.conf file, assuming '%s'",
491 if serverrole == "domain controller":
492 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.")
494 realm = domainname.upper()
495 logger.warning("No realm specified in smb.conf file, assuming '%s'",
498 # Find machine account and password
502 machinepass = secrets_db.get_machine_password(netbiosname)
506 # We must close the direct pytdb database before the C code loads it
509 # Connect to old password backend
510 passdb.set_secrets_dir(samba3.lp.get("private dir"))
511 s3db = samba3.get_sam_db()
515 domainsid = passdb.get_global_sam_sid()
517 raise Exception("Can't find domain sid for '%s', Exiting." % domainname)
519 # Get machine account, sid, rid
521 machineacct = s3db.getsampwnam('%s$' % netbiosname)
526 machinesid, machinerid = machineacct.user_sid.split()
528 # Export account policy
529 logger.info("Exporting account policy")
530 policy = s3db.get_account_policy()
532 # Export groups from old passdb backend
533 logger.info("Exporting groups")
534 grouplist = s3db.enum_group_mapping()
536 for group in grouplist:
537 sid, rid = group.sid.split()
542 # Get members for each group/alias
543 if group.sid_name_use == lsa.SID_NAME_ALIAS:
544 members = s3db.enum_aliasmem(group.sid)
545 elif group.sid_name_use == lsa.SID_NAME_DOM_GRP:
547 members = s3db.enum_group_members(group.sid)
550 groupmembers[group.nt_name] = members
551 elif group.sid_name_use == lsa.SID_NAME_WKN_GRP:
552 (group_dom_sid, rid) = group.sid.split()
553 if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)):
554 logger.warn("Ignoring 'well known' group '%s' (should already be in AD, and have no members)",
557 # A number of buggy databases mix up well known groups and aliases.
558 members = s3db.enum_aliasmem(group.sid)
560 logger.warn("Ignoring group '%s' with sid_name_use=%d",
561 group.nt_name, group.sid_name_use)
564 # Export users from old passdb backend
565 logger.info("Exporting users")
566 userlist = s3db.search_users(0)
570 for entry in userlist:
571 if machinerid and machinerid == entry['rid']:
573 username = entry['account_name']
574 if entry['rid'] < 1000:
575 logger.info(" Skipping wellknown rid=%d (for username=%s)", entry['rid'], username)
577 if entry['rid'] >= next_rid:
578 next_rid = entry['rid'] + 1
580 user = s3db.getsampwnam(username)
581 acct_type = (user.acct_ctrl & (samr.ACB_NORMAL|samr.ACB_WSTRUST|samr.ACB_SVRTRUST|samr.ACB_DOMTRUST))
582 if (acct_type == samr.ACB_NORMAL or acct_type == samr.ACB_WSTRUST or acct_type == samr.ACB_SVRTRUST):
584 elif acct_type == samr.ACB_DOMTRUST:
585 logger.warn(" Skipping inter-domain trust from domain %s, this trust must be re-created as an AD trust" % username[:-1])
587 elif acct_type == (samr.ACB_NORMAL|samr.ACB_WSTRUST) and username[-1] == '$':
588 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)
589 user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_NORMAL)
591 raise ProvisioningError("""Failed to upgrade due to invalid account %s, account control flags 0x%08X must have exactly one of
592 ACB_NORMAL (N, 0x%08X), ACB_WSTRUST (W 0x%08X), ACB_SVRTRUST (S 0x%08X) or ACB_DOMTRUST (D 0x%08X).
594 Please fix this account before attempting to upgrade again
596 % (user.acct_flags, username,
597 samr.ACB_NORMAL, samr.ACB_WSTRUST, samr.ACB_SVRTRUST, samr.ACB_DOMTRUST))
599 userdata[username] = user
601 uids[username] = s3db.sid_to_id(user.user_sid)[0]
604 uids[username] = pwd.getpwnam(username).pw_uid
608 if not admin_user and username.lower() == 'root':
609 admin_user = username
610 if username.lower() == 'administrator':
611 admin_user = username
613 logger.info("Next rid = %d", next_rid)
615 # Check for same username/groupname
616 group_names = set([g.nt_name for g in grouplist])
617 user_names = set([u['account_name'] for u in userlist])
618 common_names = group_names.intersection(user_names)
620 logger.error("Following names are both user names and group names:")
621 for name in common_names:
622 logger.error(" %s" % name)
623 raise ProvisioningError("Please remove common user/group names before upgrade.")
625 # Check for same user sid/group sid
626 group_sids = set([str(g.sid) for g in grouplist])
627 user_sids = set(["%s-%u" % (domainsid, u['rid']) for u in userlist])
628 common_sids = group_sids.intersection(user_sids)
630 logger.error("Following sids are both user and group sids:")
631 for sid in common_sids:
632 logger.error(" %s" % str(sid))
633 raise ProvisioningError("Please remove duplicate sid entries before upgrade.")
635 if serverrole == "domain controller":
636 dns_backend = "BIND9_FLATFILE"
641 result = provision(logger, session_info, None,
642 targetdir=targetdir, realm=realm, domain=domainname,
643 domainsid=str(domainsid), next_rid=next_rid,
645 dom_for_fun_level=dsdb.DS_DOMAIN_FUNCTION_2003,
646 hostname=netbiosname, machinepass=machinepass,
647 serverrole=serverrole, samdb_fill=FILL_FULL,
648 useeadb=useeadb, dns_backend=dns_backend)
650 # Import WINS database
651 logger.info("Importing WINS database")
655 samba3_winsdb = samba3.get_wins_db()
657 logger.warn('Cannot open wins database, Ignoring: %s', str(e))
660 import_wins(Ldb(result.paths.winsdb), samba3_winsdb)
663 logger.info("Importing Account policy")
664 import_sam_policy(result.samdb, policy, logger)
666 # Migrate IDMAP database
667 logger.info("Importing idmap database")
668 import_idmap(result.idmap, samba3, logger)
670 # Set the s3 context for samba4 configuration
671 new_lp_ctx = s3param.get_context()
672 new_lp_ctx.load(result.lp.configfile)
673 new_lp_ctx.set("private dir", result.lp.get("private dir"))
674 new_lp_ctx.set("state directory", result.lp.get("state directory"))
675 new_lp_ctx.set("lock directory", result.lp.get("lock directory"))
677 # Connect to samba4 backend
678 s4_passdb = passdb.PDB(new_lp_ctx.get("passdb backend"))
680 # Export groups to samba4 backend
681 logger.info("Importing groups")
683 # Ignore uninitialized groups (gid = -1)
684 if g.gid != 0xffffffff:
685 add_idmap_entry(result.idmap, g.sid, g.gid, "GID", logger)
686 add_group_from_mapping_entry(result.samdb, g, logger)
688 # Export users to samba4 backend
689 logger.info("Importing users")
690 for username in userdata:
691 if username.lower() == 'administrator' or username.lower() == 'root':
693 s4_passdb.add_sam_account(userdata[username])
695 add_idmap_entry(result.idmap, userdata[username].user_sid, uids[username], "UID", logger)
697 logger.info("Adding users to groups")
699 if g.nt_name in groupmembers:
700 add_users_to_group(result.samdb, g, groupmembers[g.nt_name], logger)
702 # Set password for administrator
704 logger.info("Setting password for administrator")
705 admin_userdata = s4_passdb.getsampwnam("administrator")
706 admin_userdata.nt_passwd = userdata[admin_user].nt_passwd
707 if userdata[admin_user].lanman_passwd:
708 admin_userdata.lanman_passwd = userdata[admin_user].lanman_passwd
709 admin_userdata.pass_last_set_time = userdata[admin_user].pass_last_set_time
710 if userdata[admin_user].pw_history:
711 admin_userdata.pw_history = userdata[admin_user].pw_history
712 s4_passdb.update_sam_account(admin_userdata)
713 logger.info("Administrator password has been set to password of user '%s'", admin_user)
715 # FIXME: import_registry(registry.Registry(), samba3.get_registry())