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(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,
60 m['a03'] = ldb.MessageElement(str(policy['minimum password age']), ldb.FLAG_MOD_REPLACE,
62 m['a04'] = ldb.MessageElement(str(policy['maximum password age']), ldb.FLAG_MOD_REPLACE,
64 m['a05'] = ldb.MessageElement(str(policy['lockout duration']), ldb.FLAG_MOD_REPLACE,
69 except ldb.LdbError, e:
70 logger.warn("Could not set account policy, (%s)", str(e))
73 def add_idmap_entry(idmapdb, sid, xid, xid_type, logger):
76 :param idmapdb: Samba4 IDMAP database
77 :param sid: user/group sid
78 :param xid: user/group id
79 :param xid_type: type of id (UID/GID)
80 :param logger: Logger object
83 # First try to see if we already have this entry
85 msg = idmapdb.search(expression='objectSid=%s' % str(sid))
92 m.dn = ldb.Dn(idmapdb, msg[0]['dn'])
93 m['xidNumber'] = ldb.MessageElement(str(xid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
94 m['type'] = ldb.MessageElement(xid_type, ldb.FLAG_MOD_REPLACE, 'type')
96 except ldb.LdbError, e:
97 logger.warn('Could not modify idmap entry for sid=%s, id=%s, type=%s (%s)',
98 str(sid), str(xid), xid_type, str(e))
101 idmapdb.add({"dn": "CN=%s" % str(sid),
103 "objectClass": "sidMap",
104 "objectSid": ndr_pack(sid),
106 "xidNumber": str(xid)})
107 except ldb.LdbError, e:
108 logger.warn('Could not add idmap entry for sid=%s, id=%s, type=%s (%s)',
109 str(sid), str(xid), xid_type, str(e))
112 def import_idmap(idmapdb, samba3_idmap, logger):
113 """Import idmap data.
115 :param idmapdb: Samba4 IDMAP database
116 :param samba3_idmap: Samba3 IDMAP database to import from
117 :param logger: Logger object
119 currentxid = max(samba3_idmap.get_user_hwm(), samba3_idmap.get_group_hwm())
120 lowerbound = currentxid
124 m.dn = ldb.Dn(idmapdb, 'CN=CONFIG')
125 m['lowerbound'] = ldb.MessageElement(str(lowerbound), ldb.FLAG_MOD_REPLACE, 'lowerBound')
126 m['xidNumber'] = ldb.MessageElement(str(currentxid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
129 for id_type, xid in samba3_idmap.ids():
131 xid_type = 'ID_TYPE_UID'
132 elif id_type == 'GID':
133 xid_type = 'ID_TYPE_GID'
135 logger.warn('Wrong type of entry in idmap (%s), Ignoring', id_type)
138 sid = samba3_idmap.get_sid(xid, id_type)
139 add_idmap_entry(idmapdb, dom_sid(sid), xid, xid_type, logger)
142 def add_group_from_mapping_entry(samdb, groupmap, logger):
143 """Add or modify group from group mapping entry
145 param samdb: Samba4 SAM database
146 param groupmap: Groupmap entry
147 param logger: Logger object
150 # First try to see if we already have this entry
152 msg = samdb.search(base='<SID=%s>' % str(groupmap.sid), scope=ldb.SCOPE_BASE)
154 except ldb.LdbError, (ecode, emsg):
155 if ecode == ldb.ERR_NO_SUCH_OBJECT:
158 raise ldb.LdbError(ecode, emsg)
161 logger.warn('Group already exists sid=%s, groupname=%s existing_groupname=%s, Ignoring.',
162 str(groupmap.sid), groupmap.nt_name, msg[0]['sAMAccountName'][0])
164 if groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
168 m.dn = ldb.Dn(samdb, "CN=%s,CN=Users,%s" % (groupmap.nt_name, samdb.get_default_basedn()))
169 m['a01'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'cn')
170 m['a02'] = ldb.MessageElement('group', ldb.FLAG_MOD_ADD, 'objectClass')
171 m['a03'] = ldb.MessageElement(ndr_pack(groupmap.sid), ldb.FLAG_MOD_ADD, 'objectSid')
172 m['a04'] = ldb.MessageElement(groupmap.comment, ldb.FLAG_MOD_ADD, 'description')
173 m['a05'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'sAMAccountName')
175 if groupmap.sid_name_use == lsa.SID_NAME_ALIAS:
176 m['a06'] = ldb.MessageElement(str(dsdb.GTYPE_SECURITY_DOMAIN_LOCAL_GROUP), ldb.FLAG_MOD_ADD, 'groupType')
179 samdb.add(m, controls=["relax:0"])
180 except ldb.LdbError, e:
181 logger.warn('Could not add group name=%s (%s)', groupmap.nt_name, str(e))
184 def add_users_to_group(samdb, group, members, logger):
185 """Add user/member to group/alias
187 param samdb: Samba4 SAM database
188 param group: Groupmap object
189 param members: List of member SIDs
190 param logger: Logger object
192 for member_sid in members:
194 m.dn = ldb.Dn(samdb, "<SID=%s" % str(group.sid))
195 m['a01'] = ldb.MessageElement("<SID=%s>" % str(member_sid), ldb.FLAG_MOD_REPLACE, 'member')
199 except ldb.LdbError, e:
200 logger.warn("Could not add member to group '%s'", groupmap.nt_name)
203 def import_wins(samba4_winsdb, samba3_winsdb):
204 """Import settings from a Samba3 WINS database.
206 :param samba4_winsdb: WINS database to import to
207 :param samba3_winsdb: WINS database to import from
211 for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items():
214 type = int(name.split("#", 1)[1], 16)
229 if ttl > time.time():
230 rState = 0x0 # active
232 rState = 0x1 # released
234 nType = ((nb_flags & 0x60)>>5)
236 samba4_winsdb.add({"dn": "name=%s,type=0x%s" % tuple(name.split("#")),
237 "type": name.split("#")[1],
238 "name": name.split("#")[0],
239 "objectClass": "winsRecord",
240 "recordType": str(rType),
241 "recordState": str(rState),
242 "nodeType": str(nType),
243 "expireTime": ldb.timestring(ttl),
245 "versionID": str(version_id),
248 samba4_winsdb.add({"dn": "cn=VERSION",
250 "objectClass": "winsMaxVersion",
251 "maxVersion": str(version_id)})
253 def enable_samba3sam(samdb, ldapurl):
254 """Enable Samba 3 LDAP URL database.
256 :param samdb: SAM Database.
257 :param ldapurl: Samba 3 LDAP URL
259 samdb.modify_ldif("""
263 @LIST: samldb,operational,objectguid,rdn_name,samba3sam
266 samdb.add({"dn": "@MAP=samba3sam", "@MAP_URL": ldapurl})
283 "bind interfaces only",
288 "obey pam restrictions",
296 "client NTLMv2 auth",
297 "client lanman auth",
298 "client plaintext auth",
316 "name resolve order",
325 "paranoid server security",
361 def upgrade_smbconf(oldconf,mark):
362 """Remove configuration variables not present in Samba4
364 :param oldconf: Old configuration structure
365 :param mark: Whether removed configuration variables should be
366 kept in the new configuration as "samba3:<name>"
368 data = oldconf.data()
374 for k in smbconf_keep:
375 if smbconf_keep[k] == p:
380 newconf.set(s, p, oldconf.get(s, p))
382 newconf.set(s, "samba3:"+p, oldconf.get(s,p))
386 SAMBA3_PREDEF_NAMES = {
387 'HKLM': registry.HKEY_LOCAL_MACHINE,
390 def import_registry(samba4_registry, samba3_regdb):
391 """Import a Samba 3 registry database into the Samba 4 registry.
393 :param samba4_registry: Samba 4 registry handle.
394 :param samba3_regdb: Samba 3 registry database handle.
396 def ensure_key_exists(keypath):
397 (predef_name, keypath) = keypath.split("/", 1)
398 predef_id = SAMBA3_PREDEF_NAMES[predef_name]
399 keypath = keypath.replace("/", "\\")
400 return samba4_registry.create_key(predef_id, keypath)
402 for key in samba3_regdb.keys():
403 key_handle = ensure_key_exists(key)
404 for subkey in samba3_regdb.subkeys(key):
405 ensure_key_exists(subkey)
406 for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items():
407 key_handle.set_value(value_name, value_type, value_data)
410 def upgrade_from_samba3(samba3, logger, targetdir, session_info=None):
411 """Upgrade from samba3 database to samba4 AD database
413 :param samba3: samba3 object
414 :param logger: Logger object
415 :param targetdir: samba4 database directory
416 :param session_info: Session information
419 if samba3.lp.get("domain logons"):
420 serverrole = "domain controller"
422 if samba3.lp.get("security") == "user":
423 serverrole = "standalone"
425 serverrole = "member server"
427 domainname = samba3.lp.get("workgroup")
428 realm = samba3.lp.get("realm")
429 netbiosname = samba3.lp.get("netbios name")
432 secrets_db = samba3.get_secrets_db()
435 domainname = secrets_db.domains()[0]
436 logger.warning("No workgroup specified in smb.conf file, assuming '%s'",
440 if serverrole == "domain controller":
441 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).")
444 realm = domainname.upper()
445 logger.warning("No realm specified in smb.conf file, assuming '%s'",
448 # Find machine account and password
455 machinepass = secrets_db.get_machine_password(netbiosname)
459 # We must close the direct pytdb database before the C code loads it
462 # Connect to old password backend
463 passdb.set_secrets_dir(samba3.lp.get("private dir"))
464 s3db = samba3.get_sam_db()
468 domainsid = passdb.get_global_sam_sid()
470 raise Exception("Can't find domain sid for '%s', Exiting." % domainname)
472 # Get machine account, sid, rid
474 machineacct = s3db.getsampwnam('%s$' % netbiosname)
475 machinesid, machinerid = machineacct.user_sid.split()
479 # Export account policy
480 logger.info("Exporting account policy")
481 policy = s3db.get_account_policy()
483 # Export groups from old passdb backend
484 logger.info("Exporting groups")
485 grouplist = s3db.enum_group_mapping()
487 for group in grouplist:
488 sid, rid = group.sid.split()
493 # Get members for each group/alias
494 if group.sid_name_use == lsa.SID_NAME_ALIAS or group.sid_name_use == lsa.SID_NAME_WKN_GRP:
495 members = s3db.enum_aliasmem(group.sid)
496 elif group.sid_name_use == lsa.SID_NAME_DOM_GRP:
498 members = s3db.enum_group_members(group.sid)
502 logger.warn("Ignoring group '%s' with sid_name_use=%d",
503 group.nt_name, group.sid_name_use)
505 groupmembers[group.nt_name] = members
508 # Export users from old passdb backend
509 logger.info("Exporting users")
510 userlist = s3db.search_users(0)
514 for entry in userlist:
515 if machinerid and machinerid == entry['rid']:
517 username = entry['account_name']
518 if entry['rid'] < 1000:
519 logger.info(" Skipping wellknown rid=%d (for username=%s)", entry['rid'], username)
521 if entry['rid'] >= next_rid:
522 next_rid = entry['rid'] + 1
524 userdata[username] = s3db.getsampwnam(username)
526 uids[username] = s3db.sid_to_id(userdata[username].user_sid)[0]
529 uids[username] = pwd.getpwnam(username).pw_uid
533 if not admin_user and username.lower() == 'root':
534 admin_user = username
535 if username.lower() == 'administrator':
536 admin_user = username
538 logger.info("Next rid = %d", next_rid)
541 result = provision(logger, session_info, None,
542 targetdir=targetdir, realm=realm, domain=domainname,
543 domainsid=str(domainsid), next_rid=next_rid,
545 hostname=netbiosname, machinepass=machinepass,
546 serverrole=serverrole, samdb_fill=FILL_FULL)
548 # Import WINS database
549 logger.info("Importing WINS database")
550 import_wins(Ldb(result.paths.winsdb), samba3.get_wins_db())
553 logger.info("Importing Account policy")
554 import_sam_policy(result.samdb, policy, logger)
556 # Migrate IDMAP database
557 logger.info("Importing idmap database")
558 import_idmap(result.idmap, samba3.get_idmap_db(), logger)
560 # Set the s3 context for samba4 configuration
561 new_lp_ctx = s3param.get_context()
562 new_lp_ctx.load(result.lp.configfile)
563 new_lp_ctx.set("private dir", result.lp.get("private dir"))
564 new_lp_ctx.set("state directory", result.lp.get("state directory"))
565 new_lp_ctx.set("lock directory", result.lp.get("lock directory"))
567 # Connect to samba4 backend
568 s4_passdb = passdb.PDB(new_lp_ctx.get("passdb backend"))
570 # Export groups to samba4 backend
571 logger.info("Importing groups")
573 # Ignore uninitialized groups (gid = -1)
574 if g.gid != 0xffffffff:
575 add_idmap_entry(result.idmap, g.sid, g.gid, "GID", logger)
576 add_group_from_mapping_entry(result.samdb, g, logger)
578 # Export users to samba4 backend
579 logger.info("Importing users")
580 for username in userdata:
581 if username.lower() == 'administrator' or username.lower() == 'root':
583 s4_passdb.add_sam_account(userdata[username])
585 add_idmap_entry(result.idmap, userdata[username].user_sid, uids[username], "UID", logger)
587 logger.info("Adding users to groups")
589 if g.nt_name in groupmembers:
590 add_users_to_group(result.samdb, g, groupmembers[g.nt_name], logger)
592 # Set password for administrator
594 logger.info("Setting password for administrator")
595 admin_userdata = s4_passdb.getsampwnam("administrator")
596 admin_userdata.nt_passwd = userdata[admin_user].nt_passwd
597 if userdata[admin_user].lanman_passwd:
598 admin_userdata.lanman_passwd = userdata[admin_user].lanman_passwd
599 admin_userdata.pass_last_set_time = userdata[admin_user].pass_last_set_time
600 if userdata[admin_user].pw_history:
601 admin_userdata.pw_history = userdata[admin_user].pw_history
602 s4_passdb.update_sam_account(admin_userdata)
603 logger.info("Administrator password has been set to password of user '%s'", admin_user)
605 # FIXME: import_registry(registry.Registry(), samba3.get_registry())