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 # First try to see if we already have this entry
159 msg = idmapdb.search(expression='objectSid=%s' % str(sid))
170 m.dn = ldb.Dn(idmapdb, msg[0]['dn'])
171 m['xidNumber'] = ldb.MessageElement(str(xid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
172 m['type'] = ldb.MessageElement(xid_type, ldb.FLAG_MOD_REPLACE, 'type')
174 except ldb.LdbError, e:
175 logger.warn('Could not modify idmap entry for sid=%s, id=%s, type=%s (%s)',
176 str(sid), str(xid), xid_type, str(e))
181 idmapdb.add({"dn": "CN=%s" % str(sid),
183 "objectClass": "sidMap",
184 "objectSid": ndr_pack(sid),
186 "xidNumber": str(xid)})
187 except ldb.LdbError, e:
188 logger.warn('Could not add idmap entry for sid=%s, id=%s, type=%s (%s)',
189 str(sid), str(xid), xid_type, str(e))
194 def import_idmap(idmapdb, samba3_idmap, logger):
195 """Import idmap data.
197 :param samba3_idmap: Samba 3 IDMAP database to import from
200 currentxid = max(samba3_idmap.get_user_hwm(), samba3_idmap.get_group_hwm())
201 lowerbound = currentxid
205 m.dn = ldb.Dn(idmapdb, 'CN=CONFIG')
206 m['lowerbound'] = ldb.MessageElement(str(lowerbound), ldb.FLAG_MOD_REPLACE, 'lowerBound')
207 m['xidNumber'] = ldb.MessageElement(str(currentxid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
210 for id_type, xid in samba3_idmap.ids():
212 xid_type = 'ID_TYPE_UID'
213 elif id_type == 'GID':
214 xid_type = 'ID_TYPE_GID'
216 logger.warn('Wrong type of entry in idmap (%s), Ignoring', id_type)
219 sid = samba3_idmap.get_sid(xid, id_type)
220 add_idmap_entry(idmapdb, dom_sid(sid), xid, xid_type, logger)
223 def add_group_from_mapping_entry(samdb, groupmap, logger):
224 """Add or modify group from group mapping entry"""
226 # First try to see if we already have this entry
228 msg = samdb.search(base='<SID=%s>' % str(groupmap.sid), scope=ldb.SCOPE_BASE)
230 except ldb.LdbError, (ecode, emsg):
231 if ecode == ldb.ERR_NO_SUCH_OBJECT:
234 raise ldb.LdbError(ecode, emsg)
239 logger.warn('Group already exists sid=%s, groupname=%s existing_groupname=%s, Ignoring.',
240 str(groupmap.sid), groupmap.nt_name, msg[0]['sAMAccountName'][0])
242 if groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
246 m.dn = ldb.Dn(samdb, "CN=%s,CN=Users,%s" % (groupmap.nt_name, samdb.get_default_basedn()))
247 m['a01'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'cn')
248 m['a02'] = ldb.MessageElement('group', ldb.FLAG_MOD_ADD, 'objectClass')
249 m['a03'] = ldb.MessageElement(ndr_pack(groupmap.sid), ldb.FLAG_MOD_ADD, 'objectSid')
250 m['a04'] = ldb.MessageElement(groupmap.comment, ldb.FLAG_MOD_ADD, 'description')
251 m['a05'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'sAMAccountName')
253 if groupmap.sid_name_use == lsa.SID_NAME_ALIAS:
254 m['a06'] = ldb.MessageElement(str(dsdb.GTYPE_SECURITY_DOMAIN_LOCAL_GROUP), ldb.FLAG_MOD_ADD, 'groupType')
257 samdb.add(m, controls=["relax:0"])
258 except ldb.LdbError, e:
259 logger.warn('Could not add group name=%s (%s)', groupmap.nt_name, str(e))
264 def add_users_to_group(samdb, group, members):
265 """Add user/member to group/alias"""
267 for member_sid in members:
269 m.dn = ldb.Dn(samdb, "<SID=%s" % str(group.sid))
270 m['a01'] = ldb.MessageElement("<SID=%s>" % str(member_sid), ldb.FLAG_MOD_REPLACE, 'member')
274 except ldb.LdbError, e:
275 logger.warn("Could not add member to group '%s'", groupmap.nt_name)
280 def import_wins(samba4_winsdb, samba3_winsdb):
281 """Import settings from a Samba3 WINS database.
283 :param samba4_winsdb: WINS database to import to
284 :param samba3_winsdb: WINS database to import from
288 for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items():
291 type = int(name.split("#", 1)[1], 16)
306 if ttl > time.time():
307 rState = 0x0 # active
309 rState = 0x1 # released
311 nType = ((nb_flags & 0x60)>>5)
313 samba4_winsdb.add({"dn": "name=%s,type=0x%s" % tuple(name.split("#")),
314 "type": name.split("#")[1],
315 "name": name.split("#")[0],
316 "objectClass": "winsRecord",
317 "recordType": str(rType),
318 "recordState": str(rState),
319 "nodeType": str(nType),
320 "expireTime": ldb.timestring(ttl),
322 "versionID": str(version_id),
325 samba4_winsdb.add({"dn": "cn=VERSION",
327 "objectClass": "winsMaxVersion",
328 "maxVersion": str(version_id)})
330 def enable_samba3sam(samdb, ldapurl):
331 """Enable Samba 3 LDAP URL database.
333 :param samdb: SAM Database.
334 :param ldapurl: Samba 3 LDAP URL
336 samdb.modify_ldif("""
340 @LIST: samldb,operational,objectguid,rdn_name,samba3sam
343 samdb.add({"dn": "@MAP=samba3sam", "@MAP_URL": ldapurl})
360 "bind interfaces only",
365 "obey pam restrictions",
373 "client NTLMv2 auth",
374 "client lanman auth",
375 "client plaintext auth",
393 "name resolve order",
402 "paranoid server security",
438 def upgrade_smbconf(oldconf,mark):
439 """Remove configuration variables not present in Samba4
441 :param oldconf: Old configuration structure
442 :param mark: Whether removed configuration variables should be
443 kept in the new configuration as "samba3:<name>"
445 data = oldconf.data()
451 for k in smbconf_keep:
452 if smbconf_keep[k] == p:
457 newconf.set(s, p, oldconf.get(s, p))
459 newconf.set(s, "samba3:"+p, oldconf.get(s,p))
463 SAMBA3_PREDEF_NAMES = {
464 'HKLM': registry.HKEY_LOCAL_MACHINE,
467 def import_registry(samba4_registry, samba3_regdb):
468 """Import a Samba 3 registry database into the Samba 4 registry.
470 :param samba4_registry: Samba 4 registry handle.
471 :param samba3_regdb: Samba 3 registry database handle.
473 def ensure_key_exists(keypath):
474 (predef_name, keypath) = keypath.split("/", 1)
475 predef_id = SAMBA3_PREDEF_NAMES[predef_name]
476 keypath = keypath.replace("/", "\\")
477 return samba4_registry.create_key(predef_id, keypath)
479 for key in samba3_regdb.keys():
480 key_handle = ensure_key_exists(key)
481 for subkey in samba3_regdb.subkeys(key):
482 ensure_key_exists(subkey)
483 for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items():
484 key_handle.set_value(value_name, value_type, value_data)
487 def upgrade_from_samba3(samba3, logger, session_info, smbconf, targetdir):
488 """Upgrade from samba3 database to samba4 AD database
491 # Read samba3 smb.conf
492 oldconf = s3param.get_context();
493 oldconf.load(smbconf)
495 if oldconf.get("domain logons"):
496 serverrole = "domain controller"
498 if oldconf.get("security") == "user":
499 serverrole = "standalone"
501 serverrole = "member server"
503 domainname = oldconf.get("workgroup")
504 realm = oldconf.get("realm")
505 netbiosname = oldconf.get("netbios name")
508 secrets_db = samba3.get_secrets_db()
511 domainname = secrets_db.domains()[0]
512 logger.warning("No domain specified in smb.conf file, assuming '%s'",
516 if oldconf.get("domain logons"):
517 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).")
520 realm = domainname.upper()
521 logger.warning("No realm specified in smb.conf file, assuming '%s'",
524 # Find machine account and password
531 machinepass = secrets_db.get_machine_password(netbiosname)
535 # We must close the direct pytdb database before the C code loads it
538 passdb.set_secrets_dir(samba3.privatedir)
542 domainsid = passdb.get_global_sam_sid()
544 raise Exception("Can't find domain sid for '%s', Exiting." % domainname)
546 # Get machine account, sid, rid
548 machineacct = old_passdb.getsampwnam('%s$' % netbiosname)
549 machinesid, machinerid = machineacct.user_sid.split()
553 # Connect to old password backend
554 old_passdb = passdb.PDB(oldconf.get('passdb backend'))
556 # Import groups from old passdb backend
557 logger.info("Exporting groups")
558 grouplist = old_passdb.enum_group_mapping()
560 for group in grouplist:
561 sid, rid = group.sid.split()
566 # Get members for each group/alias
567 if group.sid_name_use == lsa.SID_NAME_ALIAS or group.sid_name_use == lsa.SID_NAME_WKN_GRP:
568 members = old_passdb.enum_aliasmem(group.sid)
569 elif group.sid_name_use == lsa.SID_NAME_DOM_GRP:
571 members = old_passdb.enum_group_members(group.sid)
575 logger.warn("Ignoring group '%s' with sid_name_use=%d",
576 group.nt_name, group.sid_name_use)
578 groupmembers[group.nt_name] = members
581 # Import users from old passdb backend
582 logger.info("Exporting users")
583 userlist = old_passdb.search_users(0)
586 for entry in userlist:
587 if machinerid and machinerid == entry['rid']:
589 username = entry['account_name']
590 if entry['rid'] < 1000:
591 logger.info(" Skipping wellknown rid=%d (for username=%s)", entry['rid'], username)
593 if entry['rid'] >= next_rid:
594 next_rid = entry['rid'] + 1
596 userdata[username] = old_passdb.getsampwnam(username)
598 uids[username] = old_passdb.sid_to_id(userdata[username].user_sid)[0]
601 uids[username] = pwd.getpwnam(username).pw_uid
605 logger.info("Next rid = %d", next_rid)
608 result = provision(logger, session_info, None,
609 targetdir=targetdir, realm=realm, domain=domainname,
610 domainsid=str(domainsid), next_rid=next_rid,
612 hostname=netbiosname, machinepass=machinepass,
613 serverrole=serverrole, samdb_fill=FILL_FULL)
615 logger.info("Import WINS")
616 import_wins(Ldb(result.paths.winsdb), samba3.get_wins_db())
618 new_smbconf = result.lp.configfile
619 newconf = s3param.get_context()
620 newconf.load(new_smbconf)
623 logger.info("Migrating idmap database")
624 import_idmap(result.idmap, samba3.get_idmap_db(), logger)
626 # Connect to samba4 backend
627 new_passdb = passdb.PDB('samba4')
629 # Export groups to samba4 backend
630 logger.info("Importing groups")
632 # Ignore uninitialized groups (gid = -1)
633 if g.gid != 0xffffffff:
634 add_idmap_entry(result.idmap, g.sid, g.gid, "GID", logger)
635 add_group_from_mapping_entry(result.samdb, g, logger)
637 # Export users to samba4 backend
638 logger.info("Importing users")
639 for username in userdata:
640 new_passdb.add_sam_account(userdata[username])
642 add_idmap_entry(result.idmap, userdata[username].user_sid, uids[username], "UID", logger)
644 logger.info("Adding users to groups")
646 if g.nt_name in groupmembers:
647 add_users_to_group(result.samdb, g, groupmembers[g.nt_name])
649 # FIXME: import_registry(registry.Registry(), samba3.get_registry())