1 # backend code for upgrading from Samba3
2 # Copyright Jelmer Vernooij 2005-2007
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 """Support code for upgrading from Samba 3 to Samba 4."""
20 __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
38 def import_sam_policy(samdb, policy, logger):
39 """Import a Samba 3 policy.
41 :param samdb: Samba4 SAM database
42 :param policy: Samba3 account policy
43 :param logger: Logger object
46 # Following entries are used -
47 # min password length, password history, minimum password age,
48 # maximum password age, lockout duration
50 # Following entries are not used -
51 # reset count minutes, user must logon to change password,
52 # bad lockout minutes, disconnect time
55 m.dn = samdb.get_default_basedn()
56 m['a01'] = ldb.MessageElement(str(policy['min password length']), ldb.FLAG_MOD_REPLACE,
58 m['a02'] = ldb.MessageElement(str(policy['password history']), ldb.FLAG_MOD_REPLACE,
61 min_pw_age_unix = policy['minimum password age']
62 min_pw_age_nt = 0 - unix2nttime(min_pw_age_unix)
63 m['a03'] = ldb.MessageElement(str(min_pw_age_nt), ldb.FLAG_MOD_REPLACE, 'minPwdAge')
65 max_pw_age_unix = policy['maximum password age']
66 if (max_pw_age_unix == 0xFFFFFFFF):
69 max_pw_age_nt = unix2nttime(max_pw_age_unix)
71 m['a04'] = ldb.MessageElement(str(max_pw_age_nt), ldb.FLAG_MOD_REPLACE,
74 lockout_duration_mins = policy['lockout duration']
75 lockout_duration_nt = unix2nttime(lockout_duration_mins * 60)
77 m['a05'] = ldb.MessageElement(str(lockout_duration_nt), ldb.FLAG_MOD_REPLACE,
82 except ldb.LdbError, e:
83 logger.warn("Could not set account policy, (%s)", str(e))
86 def add_idmap_entry(idmapdb, sid, xid, xid_type, logger):
89 :param idmapdb: Samba4 IDMAP database
90 :param sid: user/group sid
91 :param xid: user/group id
92 :param xid_type: type of id (UID/GID)
93 :param logger: Logger object
96 # First try to see if we already have this entry
98 msg = idmapdb.search(expression='objectSid=%s' % str(sid))
106 m['xidNumber'] = ldb.MessageElement(str(xid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
107 m['type'] = ldb.MessageElement(xid_type, ldb.FLAG_MOD_REPLACE, 'type')
109 except ldb.LdbError, e:
110 logger.warn('Could not modify idmap entry for sid=%s, id=%s, type=%s (%s)',
111 str(sid), str(xid), xid_type, str(e))
114 idmapdb.add({"dn": "CN=%s" % str(sid),
116 "objectClass": "sidMap",
117 "objectSid": ndr_pack(sid),
119 "xidNumber": str(xid)})
120 except ldb.LdbError, e:
121 logger.warn('Could not add idmap entry for sid=%s, id=%s, type=%s (%s)',
122 str(sid), str(xid), xid_type, str(e))
125 def import_idmap(idmapdb, samba3, logger):
126 """Import idmap data.
128 :param idmapdb: Samba4 IDMAP database
129 :param samba3_idmap: Samba3 IDMAP database to import from
130 :param logger: Logger object
134 samba3_idmap = samba3.get_idmap_db()
136 logger.warn('Cannot open idmap database, Ignoring: %s', str(e))
139 currentxid = max(samba3_idmap.get_user_hwm(), samba3_idmap.get_group_hwm())
140 lowerbound = currentxid
144 m.dn = ldb.Dn(idmapdb, 'CN=CONFIG')
145 m['lowerbound'] = ldb.MessageElement(str(lowerbound), ldb.FLAG_MOD_REPLACE, 'lowerBound')
146 m['xidNumber'] = ldb.MessageElement(str(currentxid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
149 for id_type, xid in samba3_idmap.ids():
151 xid_type = 'ID_TYPE_UID'
152 elif id_type == 'GID':
153 xid_type = 'ID_TYPE_GID'
155 logger.warn('Wrong type of entry in idmap (%s), Ignoring', id_type)
158 sid = samba3_idmap.get_sid(xid, id_type)
159 add_idmap_entry(idmapdb, dom_sid(sid), xid, xid_type, logger)
162 def add_group_from_mapping_entry(samdb, groupmap, logger):
163 """Add or modify group from group mapping entry
165 param samdb: Samba4 SAM database
166 param groupmap: Groupmap entry
167 param logger: Logger object
170 # First try to see if we already have this entry
172 msg = samdb.search(base='<SID=%s>' % str(groupmap.sid), scope=ldb.SCOPE_BASE)
174 except ldb.LdbError, (ecode, emsg):
175 if ecode == ldb.ERR_NO_SUCH_OBJECT:
178 raise ldb.LdbError(ecode, emsg)
181 logger.warn('Group already exists sid=%s, groupname=%s existing_groupname=%s, Ignoring.',
182 str(groupmap.sid), groupmap.nt_name, msg[0]['sAMAccountName'][0])
184 if groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
185 # In a lot of Samba3 databases, aliases are marked as well known groups
186 (group_dom_sid, rid) = group.sid.split()
187 if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)):
191 m.dn = ldb.Dn(samdb, "CN=%s,CN=Users,%s" % (groupmap.nt_name, samdb.get_default_basedn()))
192 m['a01'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'cn')
193 m['a02'] = ldb.MessageElement('group', ldb.FLAG_MOD_ADD, 'objectClass')
194 m['a03'] = ldb.MessageElement(ndr_pack(groupmap.sid), ldb.FLAG_MOD_ADD, 'objectSid')
195 m['a04'] = ldb.MessageElement(groupmap.comment, ldb.FLAG_MOD_ADD, 'description')
196 m['a05'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'sAMAccountName')
198 # Fix up incorrect 'well known' groups that are actually builtin (per test above) to be aliases
199 if groupmap.sid_name_use == lsa.SID_NAME_ALIAS or groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
200 m['a06'] = ldb.MessageElement(str(dsdb.GTYPE_SECURITY_DOMAIN_LOCAL_GROUP), ldb.FLAG_MOD_ADD, 'groupType')
203 samdb.add(m, controls=["relax:0"])
204 except ldb.LdbError, e:
205 logger.warn('Could not add group name=%s (%s)', groupmap.nt_name, str(e))
208 def add_users_to_group(samdb, group, members, logger):
209 """Add user/member to group/alias
211 param samdb: Samba4 SAM database
212 param group: Groupmap object
213 param members: List of member SIDs
214 param logger: Logger object
216 for member_sid in members:
218 m.dn = ldb.Dn(samdb, "<SID=%s>" % str(group.sid))
219 m['a01'] = ldb.MessageElement("<SID=%s>" % str(member_sid), ldb.FLAG_MOD_ADD, 'member')
223 except ldb.LdbError, (ecode, emsg):
224 if ecode == ldb.ERR_ENTRY_ALREADY_EXISTS:
225 logger.info("skipped re-adding member '%s' to group '%s': %s", member_sid, group.sid, emsg)
226 elif ecode == ldb.ERR_NO_SUCH_OBJECT:
227 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))
229 raise ProvisioningError("Could not add member '%s' to group '%s': %s" % (member_sid, group.sid, emsg))
232 def import_wins(samba4_winsdb, samba3_winsdb):
233 """Import settings from a Samba3 WINS database.
235 :param samba4_winsdb: WINS database to import to
236 :param samba3_winsdb: WINS database to import from
240 for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items():
243 type = int(name.split("#", 1)[1], 16)
258 if ttl > time.time():
259 rState = 0x0 # active
261 rState = 0x1 # released
263 nType = ((nb_flags & 0x60)>>5)
265 samba4_winsdb.add({"dn": "name=%s,type=0x%s" % tuple(name.split("#")),
266 "type": name.split("#")[1],
267 "name": name.split("#")[0],
268 "objectClass": "winsRecord",
269 "recordType": str(rType),
270 "recordState": str(rState),
271 "nodeType": str(nType),
272 "expireTime": ldb.timestring(ttl),
274 "versionID": str(version_id),
277 samba4_winsdb.add({"dn": "cn=VERSION",
279 "objectClass": "winsMaxVersion",
280 "maxVersion": str(version_id)})
282 def enable_samba3sam(samdb, ldapurl):
283 """Enable Samba 3 LDAP URL database.
285 :param samdb: SAM Database.
286 :param ldapurl: Samba 3 LDAP URL
288 samdb.modify_ldif("""
292 @LIST: samldb,operational,objectguid,rdn_name,samba3sam
295 samdb.add({"dn": "@MAP=samba3sam", "@MAP_URL": ldapurl})
312 "bind interfaces only",
317 "obey pam restrictions",
325 "client NTLMv2 auth",
326 "client lanman auth",
327 "client plaintext auth",
345 "name resolve order",
354 "paranoid server security",
390 def upgrade_smbconf(oldconf,mark):
391 """Remove configuration variables not present in Samba4
393 :param oldconf: Old configuration structure
394 :param mark: Whether removed configuration variables should be
395 kept in the new configuration as "samba3:<name>"
397 data = oldconf.data()
403 for k in smbconf_keep:
404 if smbconf_keep[k] == p:
409 newconf.set(s, p, oldconf.get(s, p))
411 newconf.set(s, "samba3:"+p, oldconf.get(s,p))
415 SAMBA3_PREDEF_NAMES = {
416 'HKLM': registry.HKEY_LOCAL_MACHINE,
419 def import_registry(samba4_registry, samba3_regdb):
420 """Import a Samba 3 registry database into the Samba 4 registry.
422 :param samba4_registry: Samba 4 registry handle.
423 :param samba3_regdb: Samba 3 registry database handle.
425 def ensure_key_exists(keypath):
426 (predef_name, keypath) = keypath.split("/", 1)
427 predef_id = SAMBA3_PREDEF_NAMES[predef_name]
428 keypath = keypath.replace("/", "\\")
429 return samba4_registry.create_key(predef_id, keypath)
431 for key in samba3_regdb.keys():
432 key_handle = ensure_key_exists(key)
433 for subkey in samba3_regdb.subkeys(key):
434 ensure_key_exists(subkey)
435 for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items():
436 key_handle.set_value(value_name, value_type, value_data)
439 def upgrade_from_samba3(samba3, logger, targetdir, session_info=None, useeadb=False):
440 """Upgrade from samba3 database to samba4 AD database
442 :param samba3: samba3 object
443 :param logger: Logger object
444 :param targetdir: samba4 database directory
445 :param session_info: Session information
448 if samba3.lp.get("domain logons"):
449 serverrole = "domain controller"
451 if samba3.lp.get("security") == "user":
452 serverrole = "standalone"
454 serverrole = "member server"
456 domainname = samba3.lp.get("workgroup")
457 realm = samba3.lp.get("realm")
458 netbiosname = samba3.lp.get("netbios name")
461 secrets_db = samba3.get_secrets_db()
464 domainname = secrets_db.domains()[0]
465 logger.warning("No workgroup specified in smb.conf file, assuming '%s'",
469 if serverrole == "domain controller":
470 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.")
472 realm = domainname.upper()
473 logger.warning("No realm specified in smb.conf file, assuming '%s'",
476 # Find machine account and password
483 machinepass = secrets_db.get_machine_password(netbiosname)
487 # We must close the direct pytdb database before the C code loads it
490 # Connect to old password backend
491 passdb.set_secrets_dir(samba3.lp.get("private dir"))
492 s3db = samba3.get_sam_db()
496 domainsid = passdb.get_global_sam_sid()
498 raise Exception("Can't find domain sid for '%s', Exiting." % domainname)
500 # Get machine account, sid, rid
502 machineacct = s3db.getsampwnam('%s$' % netbiosname)
503 machinesid, machinerid = machineacct.user_sid.split()
507 # Export account policy
508 logger.info("Exporting account policy")
509 policy = s3db.get_account_policy()
511 # Export groups from old passdb backend
512 logger.info("Exporting groups")
513 grouplist = s3db.enum_group_mapping()
515 for group in grouplist:
516 sid, rid = group.sid.split()
521 # Get members for each group/alias
522 if group.sid_name_use == lsa.SID_NAME_ALIAS:
523 members = s3db.enum_aliasmem(group.sid)
524 elif group.sid_name_use == lsa.SID_NAME_DOM_GRP:
526 members = s3db.enum_group_members(group.sid)
529 groupmembers[group.nt_name] = members
530 elif group.sid_name_use == lsa.SID_NAME_WKN_GRP:
531 (group_dom_sid, rid) = group.sid.split()
532 if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)):
533 logger.warn("Ignoring 'well known' group '%s' (should already be in AD, and have no members)",
536 # A number of buggy databases mix up well known groups and aliases.
537 members = s3db.enum_aliasmem(group.sid)
539 logger.warn("Ignoring group '%s' with sid_name_use=%d",
540 group.nt_name, group.sid_name_use)
544 # Export users from old passdb backend
545 logger.info("Exporting users")
546 userlist = s3db.search_users(0)
550 for entry in userlist:
551 if machinerid and machinerid == entry['rid']:
553 username = entry['account_name']
554 if entry['rid'] < 1000:
555 logger.info(" Skipping wellknown rid=%d (for username=%s)", entry['rid'], username)
557 if entry['rid'] >= next_rid:
558 next_rid = entry['rid'] + 1
560 user = s3db.getsampwnam(username)
561 acct_type = (user.acct_ctrl & (samr.ACB_NORMAL|samr.ACB_WSTRUST|samr.ACB_SVRTRUST|samr.ACB_DOMTRUST))
562 if (acct_type == samr.ACB_NORMAL or acct_type == samr.ACB_WSTRUST or acct_type == samr.ACB_SVRTRUST):
564 elif acct_type == samr.ACB_DOMTRUST:
565 logger.warn(" Skipping inter-domain trust from domain %s, this trust must be re-created as an AD trust" % username[:-1])
567 elif acct_type == (samr.ACB_NORMAL|samr.ACB_WSTRUST) and username[-1] == '$':
568 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)
569 user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_NORMAL)
571 raise ProvisioningError("""Failed to upgrade due to invalid account %s, account control flags 0x%08X must have exactly one of
572 ACB_NORMAL (N, 0x%08X), ACB_WSTRUST (W 0x%08X), ACB_SVRTRUST (S 0x%08X) or ACB_DOMTRUST (D 0x%08X).
574 Please fix this account before attempting to upgrade again
576 % (user.acct_flags, username,
577 samr.ACB_NORMAL, samr.ACB_WSTRUST, samr.ACB_SVRTRUST, samr.ACB_DOMTRUST))
579 userdata[username] = user
581 uids[username] = s3db.sid_to_id(user.user_sid)[0]
584 uids[username] = pwd.getpwnam(username).pw_uid
588 if not admin_user and username.lower() == 'root':
589 admin_user = username
590 if username.lower() == 'administrator':
591 admin_user = username
593 logger.info("Next rid = %d", next_rid)
595 # Check for same username/groupname
596 group_names = set(map(lambda g: g.nt_name, grouplist))
597 user_names = set(map(lambda u: u['account_name'], userlist))
598 common_names = group_names.intersection(user_names)
600 logger.error("Following names are both user names and group names:")
601 for name in common_names:
602 logger.error(" %s" % name)
603 raise ProvisioningError("Please remove common user/group names before upgrade.")
605 # Check for same user sid/group sid
606 group_sids = set(map(lambda g: str(g.sid), grouplist))
607 user_sids = set(map(lambda u: "%s-%u" % (domainsid, u['rid']), userlist))
608 common_sids = group_sids.intersection(user_sids)
610 logger.error("Following sids are both user and group sids:")
611 for sid in common_sids:
612 logger.error(" %s" % str(sid))
613 raise ProvisioningError("Please remove duplicate sid entries before upgrade.")
616 result = provision(logger, session_info, None,
617 targetdir=targetdir, realm=realm, domain=domainname,
618 domainsid=str(domainsid), next_rid=next_rid,
620 hostname=netbiosname, machinepass=machinepass,
621 serverrole=serverrole, samdb_fill=FILL_FULL,
624 # Import WINS database
625 logger.info("Importing WINS database")
626 import_wins(Ldb(result.paths.winsdb), samba3.get_wins_db())
629 logger.info("Importing Account policy")
630 import_sam_policy(result.samdb, policy, logger)
632 # Migrate IDMAP database
633 logger.info("Importing idmap database")
634 import_idmap(result.idmap, samba3, logger)
636 # Set the s3 context for samba4 configuration
637 new_lp_ctx = s3param.get_context()
638 new_lp_ctx.load(result.lp.configfile)
639 new_lp_ctx.set("private dir", result.lp.get("private dir"))
640 new_lp_ctx.set("state directory", result.lp.get("state directory"))
641 new_lp_ctx.set("lock directory", result.lp.get("lock directory"))
643 # Connect to samba4 backend
644 s4_passdb = passdb.PDB(new_lp_ctx.get("passdb backend"))
646 # Export groups to samba4 backend
647 logger.info("Importing groups")
649 # Ignore uninitialized groups (gid = -1)
650 if g.gid != 0xffffffff:
651 add_idmap_entry(result.idmap, g.sid, g.gid, "GID", logger)
652 add_group_from_mapping_entry(result.samdb, g, logger)
654 # Export users to samba4 backend
655 logger.info("Importing users")
656 for username in userdata:
657 if username.lower() == 'administrator' or username.lower() == 'root':
659 s4_passdb.add_sam_account(userdata[username])
661 add_idmap_entry(result.idmap, userdata[username].user_sid, uids[username], "UID", logger)
663 logger.info("Adding users to groups")
665 if g.nt_name in groupmembers:
666 add_users_to_group(result.samdb, g, groupmembers[g.nt_name], logger)
668 # Set password for administrator
670 logger.info("Setting password for administrator")
671 admin_userdata = s4_passdb.getsampwnam("administrator")
672 admin_userdata.nt_passwd = userdata[admin_user].nt_passwd
673 if userdata[admin_user].lanman_passwd:
674 admin_userdata.lanman_passwd = userdata[admin_user].lanman_passwd
675 admin_userdata.pass_last_set_time = userdata[admin_user].pass_last_set_time
676 if userdata[admin_user].pw_history:
677 admin_userdata.pw_history = userdata[admin_user].pw_history
678 s4_passdb.update_sam_account(admin_userdata)
679 logger.info("Administrator password has been set to password of user '%s'", admin_user)
681 # FIXME: import_registry(registry.Registry(), samba3.get_registry())