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
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, 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
121 samba3_idmap = samba3.get_idmap_db()
122 except IOError as (errno, strerror):
123 logger.warn('Cannot open idmap database, Ignoring: ({0}): {1}'.format(errno, strerror))
126 currentxid = max(samba3_idmap.get_user_hwm(), samba3_idmap.get_group_hwm())
127 lowerbound = currentxid
131 m.dn = ldb.Dn(idmapdb, 'CN=CONFIG')
132 m['lowerbound'] = ldb.MessageElement(str(lowerbound), ldb.FLAG_MOD_REPLACE, 'lowerBound')
133 m['xidNumber'] = ldb.MessageElement(str(currentxid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
136 for id_type, xid in samba3_idmap.ids():
138 xid_type = 'ID_TYPE_UID'
139 elif id_type == 'GID':
140 xid_type = 'ID_TYPE_GID'
142 logger.warn('Wrong type of entry in idmap (%s), Ignoring', id_type)
145 sid = samba3_idmap.get_sid(xid, id_type)
146 add_idmap_entry(idmapdb, dom_sid(sid), xid, xid_type, logger)
149 def add_group_from_mapping_entry(samdb, groupmap, logger):
150 """Add or modify group from group mapping entry
152 param samdb: Samba4 SAM database
153 param groupmap: Groupmap entry
154 param logger: Logger object
157 # First try to see if we already have this entry
159 msg = samdb.search(base='<SID=%s>' % str(groupmap.sid), scope=ldb.SCOPE_BASE)
161 except ldb.LdbError, (ecode, emsg):
162 if ecode == ldb.ERR_NO_SUCH_OBJECT:
165 raise ldb.LdbError(ecode, emsg)
168 logger.warn('Group already exists sid=%s, groupname=%s existing_groupname=%s, Ignoring.',
169 str(groupmap.sid), groupmap.nt_name, msg[0]['sAMAccountName'][0])
171 if groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
175 m.dn = ldb.Dn(samdb, "CN=%s,CN=Users,%s" % (groupmap.nt_name, samdb.get_default_basedn()))
176 m['a01'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'cn')
177 m['a02'] = ldb.MessageElement('group', ldb.FLAG_MOD_ADD, 'objectClass')
178 m['a03'] = ldb.MessageElement(ndr_pack(groupmap.sid), ldb.FLAG_MOD_ADD, 'objectSid')
179 m['a04'] = ldb.MessageElement(groupmap.comment, ldb.FLAG_MOD_ADD, 'description')
180 m['a05'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'sAMAccountName')
182 if groupmap.sid_name_use == lsa.SID_NAME_ALIAS:
183 m['a06'] = ldb.MessageElement(str(dsdb.GTYPE_SECURITY_DOMAIN_LOCAL_GROUP), ldb.FLAG_MOD_ADD, 'groupType')
186 samdb.add(m, controls=["relax:0"])
187 except ldb.LdbError, e:
188 logger.warn('Could not add group name=%s (%s)', groupmap.nt_name, str(e))
191 def add_users_to_group(samdb, group, members, logger):
192 """Add user/member to group/alias
194 param samdb: Samba4 SAM database
195 param group: Groupmap object
196 param members: List of member SIDs
197 param logger: Logger object
199 for member_sid in members:
201 m.dn = ldb.Dn(samdb, "<SID=%s" % str(group.sid))
202 m['a01'] = ldb.MessageElement("<SID=%s>" % str(member_sid), ldb.FLAG_MOD_REPLACE, 'member')
206 except ldb.LdbError, e:
207 logger.warn("Could not add member to group '%s'", groupmap.nt_name)
210 def import_wins(samba4_winsdb, samba3_winsdb):
211 """Import settings from a Samba3 WINS database.
213 :param samba4_winsdb: WINS database to import to
214 :param samba3_winsdb: WINS database to import from
218 for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items():
221 type = int(name.split("#", 1)[1], 16)
236 if ttl > time.time():
237 rState = 0x0 # active
239 rState = 0x1 # released
241 nType = ((nb_flags & 0x60)>>5)
243 samba4_winsdb.add({"dn": "name=%s,type=0x%s" % tuple(name.split("#")),
244 "type": name.split("#")[1],
245 "name": name.split("#")[0],
246 "objectClass": "winsRecord",
247 "recordType": str(rType),
248 "recordState": str(rState),
249 "nodeType": str(nType),
250 "expireTime": ldb.timestring(ttl),
252 "versionID": str(version_id),
255 samba4_winsdb.add({"dn": "cn=VERSION",
257 "objectClass": "winsMaxVersion",
258 "maxVersion": str(version_id)})
260 def enable_samba3sam(samdb, ldapurl):
261 """Enable Samba 3 LDAP URL database.
263 :param samdb: SAM Database.
264 :param ldapurl: Samba 3 LDAP URL
266 samdb.modify_ldif("""
270 @LIST: samldb,operational,objectguid,rdn_name,samba3sam
273 samdb.add({"dn": "@MAP=samba3sam", "@MAP_URL": ldapurl})
290 "bind interfaces only",
295 "obey pam restrictions",
303 "client NTLMv2 auth",
304 "client lanman auth",
305 "client plaintext auth",
323 "name resolve order",
332 "paranoid server security",
368 def upgrade_smbconf(oldconf,mark):
369 """Remove configuration variables not present in Samba4
371 :param oldconf: Old configuration structure
372 :param mark: Whether removed configuration variables should be
373 kept in the new configuration as "samba3:<name>"
375 data = oldconf.data()
381 for k in smbconf_keep:
382 if smbconf_keep[k] == p:
387 newconf.set(s, p, oldconf.get(s, p))
389 newconf.set(s, "samba3:"+p, oldconf.get(s,p))
393 SAMBA3_PREDEF_NAMES = {
394 'HKLM': registry.HKEY_LOCAL_MACHINE,
397 def import_registry(samba4_registry, samba3_regdb):
398 """Import a Samba 3 registry database into the Samba 4 registry.
400 :param samba4_registry: Samba 4 registry handle.
401 :param samba3_regdb: Samba 3 registry database handle.
403 def ensure_key_exists(keypath):
404 (predef_name, keypath) = keypath.split("/", 1)
405 predef_id = SAMBA3_PREDEF_NAMES[predef_name]
406 keypath = keypath.replace("/", "\\")
407 return samba4_registry.create_key(predef_id, keypath)
409 for key in samba3_regdb.keys():
410 key_handle = ensure_key_exists(key)
411 for subkey in samba3_regdb.subkeys(key):
412 ensure_key_exists(subkey)
413 for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items():
414 key_handle.set_value(value_name, value_type, value_data)
417 def upgrade_from_samba3(samba3, logger, targetdir, session_info=None, useeadb=False):
418 """Upgrade from samba3 database to samba4 AD database
420 :param samba3: samba3 object
421 :param logger: Logger object
422 :param targetdir: samba4 database directory
423 :param session_info: Session information
426 if samba3.lp.get("domain logons"):
427 serverrole = "domain controller"
429 if samba3.lp.get("security") == "user":
430 serverrole = "standalone"
432 serverrole = "member server"
434 domainname = samba3.lp.get("workgroup")
435 realm = samba3.lp.get("realm")
436 netbiosname = samba3.lp.get("netbios name")
439 secrets_db = samba3.get_secrets_db()
442 domainname = secrets_db.domains()[0]
443 logger.warning("No workgroup specified in smb.conf file, assuming '%s'",
447 if serverrole == "domain controller":
448 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.")
450 realm = domainname.upper()
451 logger.warning("No realm specified in smb.conf file, assuming '%s'",
454 # Find machine account and password
461 machinepass = secrets_db.get_machine_password(netbiosname)
465 # We must close the direct pytdb database before the C code loads it
468 # Connect to old password backend
469 passdb.set_secrets_dir(samba3.lp.get("private dir"))
470 s3db = samba3.get_sam_db()
474 domainsid = passdb.get_global_sam_sid()
476 raise Exception("Can't find domain sid for '%s', Exiting." % domainname)
478 # Get machine account, sid, rid
480 machineacct = s3db.getsampwnam('%s$' % netbiosname)
481 machinesid, machinerid = machineacct.user_sid.split()
485 # Export account policy
486 logger.info("Exporting account policy")
487 policy = s3db.get_account_policy()
489 # Export groups from old passdb backend
490 logger.info("Exporting groups")
491 grouplist = s3db.enum_group_mapping()
493 for group in grouplist:
494 sid, rid = group.sid.split()
499 # Get members for each group/alias
500 if group.sid_name_use == lsa.SID_NAME_ALIAS:
501 members = s3db.enum_aliasmem(group.sid)
502 elif group.sid_name_use == lsa.SID_NAME_DOM_GRP:
504 members = s3db.enum_group_members(group.sid)
507 elif group.sid_name_use == lsa.SID_NAME_WKN_GRP:
508 logger.warn("Ignoring 'well known' group '%s' (should already be in AD, and have no members)",
509 group.nt_name, group.sid_name_use)
512 logger.warn("Ignoring group '%s' with sid_name_use=%d",
513 group.nt_name, group.sid_name_use)
515 groupmembers[group.nt_name] = members
518 # Export users from old passdb backend
519 logger.info("Exporting users")
520 userlist = s3db.search_users(0)
524 for entry in userlist:
525 if machinerid and machinerid == entry['rid']:
527 username = entry['account_name']
528 if entry['rid'] < 1000:
529 logger.info(" Skipping wellknown rid=%d (for username=%s)", entry['rid'], username)
531 if entry['rid'] >= next_rid:
532 next_rid = entry['rid'] + 1
534 userdata[username] = s3db.getsampwnam(username)
536 uids[username] = s3db.sid_to_id(userdata[username].user_sid)[0]
539 uids[username] = pwd.getpwnam(username).pw_uid
543 if not admin_user and username.lower() == 'root':
544 admin_user = username
545 if username.lower() == 'administrator':
546 admin_user = username
548 logger.info("Next rid = %d", next_rid)
551 result = provision(logger, session_info, None,
552 targetdir=targetdir, realm=realm, domain=domainname,
553 domainsid=str(domainsid), next_rid=next_rid,
555 hostname=netbiosname, machinepass=machinepass,
556 serverrole=serverrole, samdb_fill=FILL_FULL,
559 # Import WINS database
560 logger.info("Importing WINS database")
561 import_wins(Ldb(result.paths.winsdb), samba3.get_wins_db())
564 logger.info("Importing Account policy")
565 import_sam_policy(result.samdb, policy, logger)
567 # Migrate IDMAP database
568 logger.info("Importing idmap database")
569 import_idmap(result.idmap, samba3, logger)
571 # Set the s3 context for samba4 configuration
572 new_lp_ctx = s3param.get_context()
573 new_lp_ctx.load(result.lp.configfile)
574 new_lp_ctx.set("private dir", result.lp.get("private dir"))
575 new_lp_ctx.set("state directory", result.lp.get("state directory"))
576 new_lp_ctx.set("lock directory", result.lp.get("lock directory"))
578 # Connect to samba4 backend
579 s4_passdb = passdb.PDB(new_lp_ctx.get("passdb backend"))
581 # Export groups to samba4 backend
582 logger.info("Importing groups")
584 # Ignore uninitialized groups (gid = -1)
585 if g.gid != 0xffffffff:
586 add_idmap_entry(result.idmap, g.sid, g.gid, "GID", logger)
587 add_group_from_mapping_entry(result.samdb, g, logger)
589 # Export users to samba4 backend
590 logger.info("Importing users")
591 for username in userdata:
592 if username.lower() == 'administrator' or username.lower() == 'root':
594 s4_passdb.add_sam_account(userdata[username])
596 add_idmap_entry(result.idmap, userdata[username].user_sid, uids[username], "UID", logger)
598 logger.info("Adding users to groups")
600 if g.nt_name in groupmembers:
601 add_users_to_group(result.samdb, g, groupmembers[g.nt_name], logger)
603 # Set password for administrator
605 logger.info("Setting password for administrator")
606 admin_userdata = s4_passdb.getsampwnam("administrator")
607 admin_userdata.nt_passwd = userdata[admin_user].nt_passwd
608 if userdata[admin_user].lanman_passwd:
609 admin_userdata.lanman_passwd = userdata[admin_user].lanman_passwd
610 admin_userdata.pass_last_set_time = userdata[admin_user].pass_last_set_time
611 if userdata[admin_user].pw_history:
612 admin_userdata.pw_history = userdata[admin_user].pw_history
613 s4_passdb.update_sam_account(admin_userdata)
614 logger.info("Administrator password has been set to password of user '%s'", admin_user)
616 # FIXME: import_registry(registry.Registry(), samba3.get_registry())