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
165 msg = idmapdb.search(expression='objectSid=%s' % str(sid))
172 m.dn = ldb.Dn(idmapdb, msg[0]['dn'])
173 m['xidNumber'] = ldb.MessageElement(str(xid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
174 m['type'] = ldb.MessageElement(xid_type, ldb.FLAG_MOD_REPLACE, 'type')
176 except ldb.LdbError, e:
177 logger.warn('Could not modify idmap entry for sid=%s, id=%s, type=%s (%s)',
178 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))
192 def import_idmap(idmapdb, samba3_idmap, logger):
193 """Import idmap data.
195 :param idmapdb: Samba4 IDMAP database
196 :param samba3_idmap: Samba3 IDMAP database to import from
197 :param logger: Logger object
199 currentxid = max(samba3_idmap.get_user_hwm(), samba3_idmap.get_group_hwm())
200 lowerbound = currentxid
204 m.dn = ldb.Dn(idmapdb, 'CN=CONFIG')
205 m['lowerbound'] = ldb.MessageElement(str(lowerbound), ldb.FLAG_MOD_REPLACE, 'lowerBound')
206 m['xidNumber'] = ldb.MessageElement(str(currentxid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
209 for id_type, xid in samba3_idmap.ids():
211 xid_type = 'ID_TYPE_UID'
212 elif id_type == 'GID':
213 xid_type = 'ID_TYPE_GID'
215 logger.warn('Wrong type of entry in idmap (%s), Ignoring', id_type)
218 sid = samba3_idmap.get_sid(xid, id_type)
219 add_idmap_entry(idmapdb, dom_sid(sid), xid, xid_type, logger)
222 def add_group_from_mapping_entry(samdb, groupmap, logger):
223 """Add or modify group from group mapping entry
225 param samdb: Samba4 SAM database
226 param groupmap: Groupmap entry
227 param logger: Logger object
230 # First try to see if we already have this entry
232 msg = samdb.search(base='<SID=%s>' % str(groupmap.sid), scope=ldb.SCOPE_BASE)
234 except ldb.LdbError, (ecode, emsg):
235 if ecode == ldb.ERR_NO_SUCH_OBJECT:
238 raise ldb.LdbError(ecode, emsg)
241 logger.warn('Group already exists sid=%s, groupname=%s existing_groupname=%s, Ignoring.',
242 str(groupmap.sid), groupmap.nt_name, msg[0]['sAMAccountName'][0])
244 if groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
248 m.dn = ldb.Dn(samdb, "CN=%s,CN=Users,%s" % (groupmap.nt_name, samdb.get_default_basedn()))
249 m['a01'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'cn')
250 m['a02'] = ldb.MessageElement('group', ldb.FLAG_MOD_ADD, 'objectClass')
251 m['a03'] = ldb.MessageElement(ndr_pack(groupmap.sid), ldb.FLAG_MOD_ADD, 'objectSid')
252 m['a04'] = ldb.MessageElement(groupmap.comment, ldb.FLAG_MOD_ADD, 'description')
253 m['a05'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'sAMAccountName')
255 if groupmap.sid_name_use == lsa.SID_NAME_ALIAS:
256 m['a06'] = ldb.MessageElement(str(dsdb.GTYPE_SECURITY_DOMAIN_LOCAL_GROUP), ldb.FLAG_MOD_ADD, 'groupType')
259 samdb.add(m, controls=["relax:0"])
260 except ldb.LdbError, e:
261 logger.warn('Could not add group name=%s (%s)', groupmap.nt_name, str(e))
264 def add_users_to_group(samdb, group, members, logger):
265 """Add user/member to group/alias
267 param samdb: Samba4 SAM database
268 param group: Groupmap object
269 param members: List of member SIDs
270 param logger: Logger object
272 for member_sid in members:
274 m.dn = ldb.Dn(samdb, "<SID=%s" % str(group.sid))
275 m['a01'] = ldb.MessageElement("<SID=%s>" % str(member_sid), ldb.FLAG_MOD_REPLACE, 'member')
279 except ldb.LdbError, e:
280 logger.warn("Could not add member to group '%s'", groupmap.nt_name)
283 def import_wins(samba4_winsdb, samba3_winsdb):
284 """Import settings from a Samba3 WINS database.
286 :param samba4_winsdb: WINS database to import to
287 :param samba3_winsdb: WINS database to import from
291 for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items():
294 type = int(name.split("#", 1)[1], 16)
309 if ttl > time.time():
310 rState = 0x0 # active
312 rState = 0x1 # released
314 nType = ((nb_flags & 0x60)>>5)
316 samba4_winsdb.add({"dn": "name=%s,type=0x%s" % tuple(name.split("#")),
317 "type": name.split("#")[1],
318 "name": name.split("#")[0],
319 "objectClass": "winsRecord",
320 "recordType": str(rType),
321 "recordState": str(rState),
322 "nodeType": str(nType),
323 "expireTime": ldb.timestring(ttl),
325 "versionID": str(version_id),
328 samba4_winsdb.add({"dn": "cn=VERSION",
330 "objectClass": "winsMaxVersion",
331 "maxVersion": str(version_id)})
333 def enable_samba3sam(samdb, ldapurl):
334 """Enable Samba 3 LDAP URL database.
336 :param samdb: SAM Database.
337 :param ldapurl: Samba 3 LDAP URL
339 samdb.modify_ldif("""
343 @LIST: samldb,operational,objectguid,rdn_name,samba3sam
346 samdb.add({"dn": "@MAP=samba3sam", "@MAP_URL": ldapurl})
363 "bind interfaces only",
368 "obey pam restrictions",
376 "client NTLMv2 auth",
377 "client lanman auth",
378 "client plaintext auth",
396 "name resolve order",
405 "paranoid server security",
441 def upgrade_smbconf(oldconf,mark):
442 """Remove configuration variables not present in Samba4
444 :param oldconf: Old configuration structure
445 :param mark: Whether removed configuration variables should be
446 kept in the new configuration as "samba3:<name>"
448 data = oldconf.data()
454 for k in smbconf_keep:
455 if smbconf_keep[k] == p:
460 newconf.set(s, p, oldconf.get(s, p))
462 newconf.set(s, "samba3:"+p, oldconf.get(s,p))
466 SAMBA3_PREDEF_NAMES = {
467 'HKLM': registry.HKEY_LOCAL_MACHINE,
470 def import_registry(samba4_registry, samba3_regdb):
471 """Import a Samba 3 registry database into the Samba 4 registry.
473 :param samba4_registry: Samba 4 registry handle.
474 :param samba3_regdb: Samba 3 registry database handle.
476 def ensure_key_exists(keypath):
477 (predef_name, keypath) = keypath.split("/", 1)
478 predef_id = SAMBA3_PREDEF_NAMES[predef_name]
479 keypath = keypath.replace("/", "\\")
480 return samba4_registry.create_key(predef_id, keypath)
482 for key in samba3_regdb.keys():
483 key_handle = ensure_key_exists(key)
484 for subkey in samba3_regdb.subkeys(key):
485 ensure_key_exists(subkey)
486 for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items():
487 key_handle.set_value(value_name, value_type, value_data)
490 def upgrade_from_samba3(samba3, logger, session_info, smbconf, targetdir):
491 """Upgrade from samba3 database to samba4 AD database
494 # Read samba3 smb.conf
495 oldconf = s3param.get_context();
496 oldconf.load(smbconf)
498 if oldconf.get("domain logons"):
499 serverrole = "domain controller"
501 if oldconf.get("security") == "user":
502 serverrole = "standalone"
504 serverrole = "member server"
506 domainname = oldconf.get("workgroup")
507 realm = oldconf.get("realm")
508 netbiosname = oldconf.get("netbios name")
511 secrets_db = samba3.get_secrets_db()
514 domainname = secrets_db.domains()[0]
515 logger.warning("No domain specified in smb.conf file, assuming '%s'",
519 if oldconf.get("domain logons"):
520 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).")
523 realm = domainname.upper()
524 logger.warning("No realm specified in smb.conf file, assuming '%s'",
527 # Find machine account and password
534 machinepass = secrets_db.get_machine_password(netbiosname)
538 # We must close the direct pytdb database before the C code loads it
541 passdb.set_secrets_dir(samba3.privatedir)
545 domainsid = passdb.get_global_sam_sid()
547 raise Exception("Can't find domain sid for '%s', Exiting." % domainname)
549 # Get machine account, sid, rid
551 machineacct = old_passdb.getsampwnam('%s$' % netbiosname)
552 machinesid, machinerid = machineacct.user_sid.split()
556 # Connect to old password backend
557 old_passdb = passdb.PDB(oldconf.get('passdb backend'))
559 # Import groups from old passdb backend
560 logger.info("Exporting groups")
561 grouplist = old_passdb.enum_group_mapping()
563 for group in grouplist:
564 sid, rid = group.sid.split()
569 # Get members for each group/alias
570 if group.sid_name_use == lsa.SID_NAME_ALIAS or group.sid_name_use == lsa.SID_NAME_WKN_GRP:
571 members = old_passdb.enum_aliasmem(group.sid)
572 elif group.sid_name_use == lsa.SID_NAME_DOM_GRP:
574 members = old_passdb.enum_group_members(group.sid)
578 logger.warn("Ignoring group '%s' with sid_name_use=%d",
579 group.nt_name, group.sid_name_use)
581 groupmembers[group.nt_name] = members
584 # Import users from old passdb backend
585 logger.info("Exporting users")
586 userlist = old_passdb.search_users(0)
590 for entry in userlist:
591 if machinerid and machinerid == entry['rid']:
593 username = entry['account_name']
594 if entry['rid'] < 1000:
595 logger.info(" Skipping wellknown rid=%d (for username=%s)", entry['rid'], username)
597 if entry['rid'] >= next_rid:
598 next_rid = entry['rid'] + 1
600 userdata[username] = old_passdb.getsampwnam(username)
602 uids[username] = old_passdb.sid_to_id(userdata[username].user_sid)[0]
605 uids[username] = pwd.getpwnam(username).pw_uid
609 if not admin_user and username.lower() == 'root':
610 admin_user = username
611 if username.lower() == 'administrator':
612 admin_user = username
615 logger.info("Next rid = %d", next_rid)
618 result = provision(logger, session_info, None,
619 targetdir=targetdir, realm=realm, domain=domainname,
620 domainsid=str(domainsid), next_rid=next_rid,
622 hostname=netbiosname, machinepass=machinepass,
623 serverrole=serverrole, samdb_fill=FILL_FULL)
625 logger.info("Import WINS")
626 import_wins(Ldb(result.paths.winsdb), samba3.get_wins_db())
628 new_smbconf = result.lp.configfile
629 newconf = s3param.get_context()
630 newconf.load(new_smbconf)
633 logger.info("Migrating idmap database")
634 import_idmap(result.idmap, samba3.get_idmap_db(), logger)
636 # Connect to samba4 backend
637 new_passdb = passdb.PDB('samba4')
639 # Export groups to samba4 backend
640 logger.info("Importing groups")
642 # Ignore uninitialized groups (gid = -1)
643 if g.gid != 0xffffffff:
644 add_idmap_entry(result.idmap, g.sid, g.gid, "GID", logger)
645 add_group_from_mapping_entry(result.samdb, g, logger)
647 # Export users to samba4 backend
648 logger.info("Importing users")
649 for username in userdata:
650 if username.lower() == 'administrator' or username.lower() == 'root':
652 new_passdb.add_sam_account(userdata[username])
654 add_idmap_entry(result.idmap, userdata[username].user_sid, uids[username], "UID", logger)
656 logger.info("Adding users to groups")
658 if g.nt_name in groupmembers:
659 add_users_to_group(result.samdb, g, groupmembers[g.nt_name], logger)
661 # Set password for administrator
663 logger.info("Setting password for administrator")
664 admin_userdata = new_passdb.getsampwnam("administrator")
665 admin_userdata.nt_passwd = userdata[admin_user].nt_passwd
666 if userdata[admin_user].lanman_passwd:
667 admin_userdata.lanman_passwd = userdata[admin_user].lanman_passwd
668 admin_userdata.pass_last_set_time = userdata[admin_user].pass_last_set_time
669 if userdata[admin_user].pw_history:
670 admin_userdata.pw_history = userdata[admin_user].pw_history
671 new_passdb.update_sam_account(admin_userdata)
672 logger.info("Administrator password has been set to password of user '%s'", admin_user)
674 # FIXME: import_registry(registry.Registry(), samba3.get_registry())