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
30 from samba.samba3 import passdb
31 from samba.samba3 import param as s3param
32 from samba.dcerpc import lsa
33 from samba.dcerpc.security import dom_sid
34 from samba import dsdb
35 from samba.ndr import ndr_pack
38 def import_sam_policy(samldb, policy, dn):
39 """Import a Samba 3 policy database."""
40 samldb.modify_ldif("""
49 samba3ResetCountMinutes: %d
50 samba3UserMustLogonToChangePassword: %d
51 samba3BadLockoutMinutes: %d
52 samba3DisconnectTime: %d
54 """ % (dn, policy.min_password_length,
55 policy.password_history, policy.minimum_password_age,
56 policy.maximum_password_age, policy.lockout_duration,
57 policy.reset_count_minutes, policy.user_must_logon_to_change_password,
58 policy.bad_lockout_minutes, policy.disconnect_time))
61 def import_sam_account(samldb,acc,domaindn,domainsid):
62 """Import a Samba 3 SAM account.
64 :param samldb: Samba 4 SAM Database handle
65 :param acc: Samba 3 account
66 :param domaindn: Domain DN
67 :param domainsid: Domain SID."""
68 if acc.nt_username is None or acc.nt_username == "":
69 acc.nt_username = acc.username
71 if acc.fullname is None:
73 acc.fullname = pwd.getpwnam(acc.username)[4].split(",")[0]
77 if acc.fullname is None:
78 acc.fullname = acc.username
80 assert acc.fullname is not None
81 assert acc.nt_username is not None
84 "dn": "cn=%s,%s" % (acc.fullname, domaindn),
85 "objectClass": ["top", "user"],
86 "lastLogon": str(acc.logon_time),
87 "lastLogoff": str(acc.logoff_time),
88 "unixName": acc.username,
89 "sAMAccountName": acc.nt_username,
90 "cn": acc.nt_username,
91 "description": acc.acct_desc,
92 "primaryGroupID": str(acc.group_rid),
93 "badPwdcount": str(acc.bad_password_count),
94 "logonCount": str(acc.logon_count),
95 "samba3Domain": acc.domain,
96 "samba3DirDrive": acc.dir_drive,
97 "samba3MungedDial": acc.munged_dial,
98 "samba3Homedir": acc.homedir,
99 "samba3LogonScript": acc.logon_script,
100 "samba3ProfilePath": acc.profile_path,
101 "samba3Workstations": acc.workstations,
102 "samba3KickOffTime": str(acc.kickoff_time),
103 "samba3BadPwdTime": str(acc.bad_password_time),
104 "samba3PassLastSetTime": str(acc.pass_last_set_time),
105 "samba3PassCanChangeTime": str(acc.pass_can_change_time),
106 "samba3PassMustChangeTime": str(acc.pass_must_change_time),
107 "objectSid": "%s-%d" % (domainsid, acc.user_rid),
108 "lmPwdHash:": acc.lm_password,
109 "ntPwdHash:": acc.nt_password,
113 def import_sam_group(samldb, sid, gid, sid_name_use, nt_name, comment, domaindn):
114 """Upgrade a SAM group.
116 :param samldb: SAM database.
117 :param gid: Group GID
118 :param sid_name_use: SID name use
119 :param nt_name: NT Group Name
120 :param comment: NT Group Comment
121 :param domaindn: Domain DN
124 if sid_name_use == 5: # Well-known group
127 if nt_name in ("Domain Guests", "Domain Users", "Domain Admins"):
131 gr = grp.getgrnam(nt_name)
133 gr = grp.getgrgid(gid)
138 unixname = gr.gr_name
140 assert unixname is not None
143 "dn": "cn=%s,%s" % (nt_name, domaindn),
144 "objectClass": ["top", "group"],
145 "description": comment,
148 "unixName": unixname,
149 "samba3SidNameUse": str(sid_name_use)
153 def add_idmap_entry(idmapdb, sid, xid, xid_type, logger):
154 """Create idmap entry
156 :param idmapdb: Samba4 IDMAP database
157 :param sid: user/group sid
158 :param xid: user/group id
159 :param xid_type: type of id (UID/GID)
160 :param logger: Logger object
163 # First try to see if we already have this entry
166 msg = idmapdb.search(expression='objectSid=%s' % str(sid))
177 m.dn = ldb.Dn(idmapdb, msg[0]['dn'])
178 m['xidNumber'] = ldb.MessageElement(str(xid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
179 m['type'] = ldb.MessageElement(xid_type, ldb.FLAG_MOD_REPLACE, 'type')
181 except ldb.LdbError, e:
182 logger.warn('Could not modify idmap entry for sid=%s, id=%s, type=%s (%s)',
183 str(sid), str(xid), xid_type, str(e))
188 idmapdb.add({"dn": "CN=%s" % str(sid),
190 "objectClass": "sidMap",
191 "objectSid": ndr_pack(sid),
193 "xidNumber": str(xid)})
194 except ldb.LdbError, e:
195 logger.warn('Could not add idmap entry for sid=%s, id=%s, type=%s (%s)',
196 str(sid), str(xid), xid_type, str(e))
199 def import_idmap(idmapdb, samba3_idmap, logger):
200 """Import idmap data.
202 :param idmapdb: Samba4 IDMAP database
203 :param samba3_idmap: Samba3 IDMAP database to import from
204 :param logger: Logger object
206 currentxid = max(samba3_idmap.get_user_hwm(), samba3_idmap.get_group_hwm())
207 lowerbound = currentxid
211 m.dn = ldb.Dn(idmapdb, 'CN=CONFIG')
212 m['lowerbound'] = ldb.MessageElement(str(lowerbound), ldb.FLAG_MOD_REPLACE, 'lowerBound')
213 m['xidNumber'] = ldb.MessageElement(str(currentxid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
216 for id_type, xid in samba3_idmap.ids():
218 xid_type = 'ID_TYPE_UID'
219 elif id_type == 'GID':
220 xid_type = 'ID_TYPE_GID'
222 logger.warn('Wrong type of entry in idmap (%s), Ignoring', id_type)
225 sid = samba3_idmap.get_sid(xid, id_type)
226 add_idmap_entry(idmapdb, dom_sid(sid), xid, xid_type, logger)
229 def add_group_from_mapping_entry(samdb, groupmap, logger):
230 """Add or modify group from group mapping entry
232 param samdb: Samba4 SAM database
233 param groupmap: Groupmap entry
234 param logger: Logger object
237 # First try to see if we already have this entry
239 msg = samdb.search(base='<SID=%s>' % str(groupmap.sid), scope=ldb.SCOPE_BASE)
241 except ldb.LdbError, (ecode, emsg):
242 if ecode == ldb.ERR_NO_SUCH_OBJECT:
245 raise ldb.LdbError(ecode, emsg)
250 logger.warn('Group already exists sid=%s, groupname=%s existing_groupname=%s, Ignoring.',
251 str(groupmap.sid), groupmap.nt_name, msg[0]['sAMAccountName'][0])
253 if groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
257 m.dn = ldb.Dn(samdb, "CN=%s,CN=Users,%s" % (groupmap.nt_name, samdb.get_default_basedn()))
258 m['a01'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'cn')
259 m['a02'] = ldb.MessageElement('group', ldb.FLAG_MOD_ADD, 'objectClass')
260 m['a03'] = ldb.MessageElement(ndr_pack(groupmap.sid), ldb.FLAG_MOD_ADD, 'objectSid')
261 m['a04'] = ldb.MessageElement(groupmap.comment, ldb.FLAG_MOD_ADD, 'description')
262 m['a05'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'sAMAccountName')
264 if groupmap.sid_name_use == lsa.SID_NAME_ALIAS:
265 m['a06'] = ldb.MessageElement(str(dsdb.GTYPE_SECURITY_DOMAIN_LOCAL_GROUP), ldb.FLAG_MOD_ADD, 'groupType')
268 samdb.add(m, controls=["relax:0"])
269 except ldb.LdbError, e:
270 logger.warn('Could not add group name=%s (%s)', groupmap.nt_name, str(e))
273 def add_users_to_group(samdb, group, members, logger):
274 """Add user/member to group/alias
276 param samdb: Samba4 SAM database
277 param group: Groupmap object
278 param members: List of member SIDs
279 param logger: Logger object
281 for member_sid in members:
283 m.dn = ldb.Dn(samdb, "<SID=%s" % str(group.sid))
284 m['a01'] = ldb.MessageElement("<SID=%s>" % str(member_sid), ldb.FLAG_MOD_REPLACE, 'member')
288 except ldb.LdbError, e:
289 logger.warn("Could not add member to group '%s'", groupmap.nt_name)
294 def import_wins(samba4_winsdb, samba3_winsdb):
295 """Import settings from a Samba3 WINS database.
297 :param samba4_winsdb: WINS database to import to
298 :param samba3_winsdb: WINS database to import from
302 for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items():
305 type = int(name.split("#", 1)[1], 16)
320 if ttl > time.time():
321 rState = 0x0 # active
323 rState = 0x1 # released
325 nType = ((nb_flags & 0x60)>>5)
327 samba4_winsdb.add({"dn": "name=%s,type=0x%s" % tuple(name.split("#")),
328 "type": name.split("#")[1],
329 "name": name.split("#")[0],
330 "objectClass": "winsRecord",
331 "recordType": str(rType),
332 "recordState": str(rState),
333 "nodeType": str(nType),
334 "expireTime": ldb.timestring(ttl),
336 "versionID": str(version_id),
339 samba4_winsdb.add({"dn": "cn=VERSION",
341 "objectClass": "winsMaxVersion",
342 "maxVersion": str(version_id)})
344 def enable_samba3sam(samdb, ldapurl):
345 """Enable Samba 3 LDAP URL database.
347 :param samdb: SAM Database.
348 :param ldapurl: Samba 3 LDAP URL
350 samdb.modify_ldif("""
354 @LIST: samldb,operational,objectguid,rdn_name,samba3sam
357 samdb.add({"dn": "@MAP=samba3sam", "@MAP_URL": ldapurl})
374 "bind interfaces only",
379 "obey pam restrictions",
387 "client NTLMv2 auth",
388 "client lanman auth",
389 "client plaintext auth",
407 "name resolve order",
416 "paranoid server security",
452 def upgrade_smbconf(oldconf,mark):
453 """Remove configuration variables not present in Samba4
455 :param oldconf: Old configuration structure
456 :param mark: Whether removed configuration variables should be
457 kept in the new configuration as "samba3:<name>"
459 data = oldconf.data()
465 for k in smbconf_keep:
466 if smbconf_keep[k] == p:
471 newconf.set(s, p, oldconf.get(s, p))
473 newconf.set(s, "samba3:"+p, oldconf.get(s,p))
477 SAMBA3_PREDEF_NAMES = {
478 'HKLM': registry.HKEY_LOCAL_MACHINE,
481 def import_registry(samba4_registry, samba3_regdb):
482 """Import a Samba 3 registry database into the Samba 4 registry.
484 :param samba4_registry: Samba 4 registry handle.
485 :param samba3_regdb: Samba 3 registry database handle.
487 def ensure_key_exists(keypath):
488 (predef_name, keypath) = keypath.split("/", 1)
489 predef_id = SAMBA3_PREDEF_NAMES[predef_name]
490 keypath = keypath.replace("/", "\\")
491 return samba4_registry.create_key(predef_id, keypath)
493 for key in samba3_regdb.keys():
494 key_handle = ensure_key_exists(key)
495 for subkey in samba3_regdb.subkeys(key):
496 ensure_key_exists(subkey)
497 for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items():
498 key_handle.set_value(value_name, value_type, value_data)
501 def upgrade_from_samba3(samba3, logger, session_info, smbconf, targetdir):
502 """Upgrade from samba3 database to samba4 AD database
505 # Read samba3 smb.conf
506 oldconf = s3param.get_context();
507 oldconf.load(smbconf)
509 if oldconf.get("domain logons"):
510 serverrole = "domain controller"
512 if oldconf.get("security") == "user":
513 serverrole = "standalone"
515 serverrole = "member server"
517 domainname = oldconf.get("workgroup")
518 realm = oldconf.get("realm")
519 netbiosname = oldconf.get("netbios name")
522 secrets_db = samba3.get_secrets_db()
525 domainname = secrets_db.domains()[0]
526 logger.warning("No domain specified in smb.conf file, assuming '%s'",
530 if oldconf.get("domain logons"):
531 logger.warning("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 (generally it's the upcased DNS domainname).")
534 realm = domainname.upper()
535 logger.warning("No realm specified in smb.conf file, assuming '%s'",
538 # Find machine account and password
545 machinepass = secrets_db.get_machine_password(netbiosname)
549 # We must close the direct pytdb database before the C code loads it
552 passdb.set_secrets_dir(samba3.privatedir)
556 domainsid = passdb.get_global_sam_sid()
558 raise Exception("Can't find domain sid for '%s', Exiting." % domainname)
560 # Get machine account, sid, rid
562 machineacct = old_passdb.getsampwnam('%s$' % netbiosname)
563 machinesid, machinerid = machineacct.user_sid.split()
567 # Connect to old password backend
568 old_passdb = passdb.PDB(oldconf.get('passdb backend'))
570 # Import groups from old passdb backend
571 logger.info("Exporting groups")
572 grouplist = old_passdb.enum_group_mapping()
574 for group in grouplist:
575 sid, rid = group.sid.split()
580 # Get members for each group/alias
581 if group.sid_name_use == lsa.SID_NAME_ALIAS or group.sid_name_use == lsa.SID_NAME_WKN_GRP:
582 members = old_passdb.enum_aliasmem(group.sid)
583 elif group.sid_name_use == lsa.SID_NAME_DOM_GRP:
585 members = old_passdb.enum_group_members(group.sid)
589 logger.warn("Ignoring group '%s' with sid_name_use=%d",
590 group.nt_name, group.sid_name_use)
592 groupmembers[group.nt_name] = members
595 # Import users from old passdb backend
596 logger.info("Exporting users")
597 userlist = old_passdb.search_users(0)
601 for entry in userlist:
602 if machinerid and machinerid == entry['rid']:
604 username = entry['account_name']
605 if entry['rid'] < 1000:
606 logger.info(" Skipping wellknown rid=%d (for username=%s)", entry['rid'], username)
608 if entry['rid'] >= next_rid:
609 next_rid = entry['rid'] + 1
611 userdata[username] = old_passdb.getsampwnam(username)
613 uids[username] = old_passdb.sid_to_id(userdata[username].user_sid)[0]
616 uids[username] = pwd.getpwnam(username).pw_uid
620 if not admin_user and username.lower() == 'root':
621 admin_user = username
622 if username.lower() == 'administrator':
623 admin_user = username
626 logger.info("Next rid = %d", next_rid)
629 result = provision(logger, session_info, None,
630 targetdir=targetdir, realm=realm, domain=domainname,
631 domainsid=str(domainsid), next_rid=next_rid,
633 hostname=netbiosname, machinepass=machinepass,
634 serverrole=serverrole, samdb_fill=FILL_FULL)
636 logger.info("Import WINS")
637 import_wins(Ldb(result.paths.winsdb), samba3.get_wins_db())
639 new_smbconf = result.lp.configfile
640 newconf = s3param.get_context()
641 newconf.load(new_smbconf)
644 logger.info("Migrating idmap database")
645 import_idmap(result.idmap, samba3.get_idmap_db(), logger)
647 # Connect to samba4 backend
648 new_passdb = passdb.PDB('samba4')
650 # Export groups to samba4 backend
651 logger.info("Importing groups")
653 # Ignore uninitialized groups (gid = -1)
654 if g.gid != 0xffffffff:
655 add_idmap_entry(result.idmap, g.sid, g.gid, "GID", logger)
656 add_group_from_mapping_entry(result.samdb, g, logger)
658 # Export users to samba4 backend
659 logger.info("Importing users")
660 for username in userdata:
661 if username.lower() == 'administrator' or username.lower() == 'root':
663 new_passdb.add_sam_account(userdata[username])
665 add_idmap_entry(result.idmap, userdata[username].user_sid, uids[username], "UID", logger)
667 logger.info("Adding users to groups")
669 if g.nt_name in groupmembers:
670 add_users_to_group(result.samdb, g, groupmembers[g.nt_name], logger)
672 # Set password for administrator
674 logger.info("Setting password for administrator")
675 admin_userdata = new_passdb.getsampwnam("administrator")
676 admin_userdata.nt_passwd = userdata[admin_user].nt_passwd
677 if userdata[admin_user].lanman_passwd:
678 admin_userdata.lanman_passwd = userdata[admin_user].lanman_passwd
679 admin_userdata.pass_last_set_time = userdata[admin_user].pass_last_set_time
680 if userdata[admin_user].pw_history:
681 admin_userdata.pw_history = userdata[admin_user].pw_history
682 new_passdb.update_sam_account(admin_userdata)
683 logger.info("Administrator password has been set to password of user '%s'", admin_user)
685 # FIXME: import_registry(registry.Registry(), samba3.get_registry())