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
33 def import_sam_policy(samldb, policy, dn):
34 """Import a Samba 3 policy database."""
35 samldb.modify_ldif("""
44 samba3ResetCountMinutes: %d
45 samba3UserMustLogonToChangePassword: %d
46 samba3BadLockoutMinutes: %d
47 samba3DisconnectTime: %d
49 """ % (dn, policy.min_password_length,
50 policy.password_history, policy.minimum_password_age,
51 policy.maximum_password_age, policy.lockout_duration,
52 policy.reset_count_minutes, policy.user_must_logon_to_change_password,
53 policy.bad_lockout_minutes, policy.disconnect_time))
56 def import_sam_account(samldb,acc,domaindn,domainsid):
57 """Import a Samba 3 SAM account.
59 :param samldb: Samba 4 SAM Database handle
60 :param acc: Samba 3 account
61 :param domaindn: Domain DN
62 :param domainsid: Domain SID."""
63 if acc.nt_username is None or acc.nt_username == "":
64 acc.nt_username = acc.username
66 if acc.fullname is None:
68 acc.fullname = pwd.getpwnam(acc.username)[4].split(",")[0]
72 if acc.fullname is None:
73 acc.fullname = acc.username
75 assert acc.fullname is not None
76 assert acc.nt_username is not None
79 "dn": "cn=%s,%s" % (acc.fullname, domaindn),
80 "objectClass": ["top", "user"],
81 "lastLogon": str(acc.logon_time),
82 "lastLogoff": str(acc.logoff_time),
83 "unixName": acc.username,
84 "sAMAccountName": acc.nt_username,
85 "cn": acc.nt_username,
86 "description": acc.acct_desc,
87 "primaryGroupID": str(acc.group_rid),
88 "badPwdcount": str(acc.bad_password_count),
89 "logonCount": str(acc.logon_count),
90 "samba3Domain": acc.domain,
91 "samba3DirDrive": acc.dir_drive,
92 "samba3MungedDial": acc.munged_dial,
93 "samba3Homedir": acc.homedir,
94 "samba3LogonScript": acc.logon_script,
95 "samba3ProfilePath": acc.profile_path,
96 "samba3Workstations": acc.workstations,
97 "samba3KickOffTime": str(acc.kickoff_time),
98 "samba3BadPwdTime": str(acc.bad_password_time),
99 "samba3PassLastSetTime": str(acc.pass_last_set_time),
100 "samba3PassCanChangeTime": str(acc.pass_can_change_time),
101 "samba3PassMustChangeTime": str(acc.pass_must_change_time),
102 "objectSid": "%s-%d" % (domainsid, acc.user_rid),
103 "lmPwdHash:": acc.lm_password,
104 "ntPwdHash:": acc.nt_password,
108 def import_sam_group(samldb, sid, gid, sid_name_use, nt_name, comment, domaindn):
109 """Upgrade a SAM group.
111 :param samldb: SAM database.
112 :param gid: Group GID
113 :param sid_name_use: SID name use
114 :param nt_name: NT Group Name
115 :param comment: NT Group Comment
116 :param domaindn: Domain DN
119 if sid_name_use == 5: # Well-known group
122 if nt_name in ("Domain Guests", "Domain Users", "Domain Admins"):
126 gr = grp.getgrnam(nt_name)
128 gr = grp.getgrgid(gid)
133 unixname = gr.gr_name
135 assert unixname is not None
138 "dn": "cn=%s,%s" % (nt_name, domaindn),
139 "objectClass": ["top", "group"],
140 "description": comment,
143 "unixName": unixname,
144 "samba3SidNameUse": str(sid_name_use)
148 def import_idmap(samdb,samba3_idmap,domaindn):
149 """Import idmap data.
151 :param samdb: SamDB handle.
152 :param samba3_idmap: Samba 3 IDMAP database to import from
153 :param domaindn: Domain DN.
157 "userHwm": str(samba3_idmap.get_user_hwm()),
158 "groupHwm": str(samba3_idmap.get_group_hwm())})
160 for uid in samba3_idmap.uids():
161 samdb.add({"dn": "SID=%s,%s" % (samba3_idmap.get_user_sid(uid), domaindn),
162 "SID": samba3_idmap.get_user_sid(uid),
166 for gid in samba3_idmap.uids():
167 samdb.add({"dn": "SID=%s,%s" % (samba3_idmap.get_group_sid(gid), domaindn),
168 "SID": samba3_idmap.get_group_sid(gid),
173 def import_wins(samba4_winsdb, samba3_winsdb):
174 """Import settings from a Samba3 WINS database.
176 :param samba4_winsdb: WINS database to import to
177 :param samba3_winsdb: WINS database to import from
181 for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items():
184 type = int(name.split("#", 1)[1], 16)
199 if ttl > time.time():
200 rState = 0x0 # active
202 rState = 0x1 # released
204 nType = ((nb_flags & 0x60)>>5)
206 samba4_winsdb.add({"dn": "name=%s,type=0x%s" % tuple(name.split("#")),
207 "type": name.split("#")[1],
208 "name": name.split("#")[0],
209 "objectClass": "winsRecord",
210 "recordType": str(rType),
211 "recordState": str(rState),
212 "nodeType": str(nType),
213 "expireTime": ldb.timestring(ttl),
215 "versionID": str(version_id),
218 samba4_winsdb.add({"dn": "cn=VERSION",
220 "objectClass": "winsMaxVersion",
221 "maxVersion": str(version_id)})
223 def enable_samba3sam(samdb, ldapurl):
224 """Enable Samba 3 LDAP URL database.
226 :param samdb: SAM Database.
227 :param ldapurl: Samba 3 LDAP URL
229 samdb.modify_ldif("""
233 @LIST: samldb,operational,objectguid,rdn_name,samba3sam
236 samdb.add({"dn": "@MAP=samba3sam", "@MAP_URL": ldapurl})
253 "bind interfaces only",
258 "obey pam restrictions",
266 "client NTLMv2 auth",
267 "client lanman auth",
268 "client plaintext auth",
286 "name resolve order",
295 "paranoid server security",
331 def upgrade_smbconf(oldconf,mark):
332 """Remove configuration variables not present in Samba4
334 :param oldconf: Old configuration structure
335 :param mark: Whether removed configuration variables should be
336 kept in the new configuration as "samba3:<name>"
338 data = oldconf.data()
344 for k in smbconf_keep:
345 if smbconf_keep[k] == p:
350 newconf.set(s, p, oldconf.get(s, p))
352 newconf.set(s, "samba3:"+p, oldconf.get(s,p))
356 SAMBA3_PREDEF_NAMES = {
357 'HKLM': registry.HKEY_LOCAL_MACHINE,
360 def import_registry(samba4_registry, samba3_regdb):
361 """Import a Samba 3 registry database into the Samba 4 registry.
363 :param samba4_registry: Samba 4 registry handle.
364 :param samba3_regdb: Samba 3 registry database handle.
366 def ensure_key_exists(keypath):
367 (predef_name, keypath) = keypath.split("/", 1)
368 predef_id = SAMBA3_PREDEF_NAMES[predef_name]
369 keypath = keypath.replace("/", "\\")
370 return samba4_registry.create_key(predef_id, keypath)
372 for key in samba3_regdb.keys():
373 key_handle = ensure_key_exists(key)
374 for subkey in samba3_regdb.subkeys(key):
375 ensure_key_exists(subkey)
376 for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items():
377 key_handle.set_value(value_name, value_type, value_data)
380 def upgrade_from_samba3(samba3, logger, session_info, smbconf, targetdir):
381 """Upgrade from samba3 database to samba4 AD database
384 # Read samba3 smb.conf
385 oldconf = s3param.get_context();
386 oldconf.load(smbconf)
388 if oldconf.get("domain logons"):
389 serverrole = "domain controller"
391 if oldconf.get("security") == "user":
392 serverrole = "standalone"
394 serverrole = "member server"
396 domainname = oldconf.get("workgroup")
397 realm = oldconf.get("realm")
398 netbiosname = oldconf.get("netbios name")
401 secrets_db = samba3.get_secrets_db()
404 domainname = secrets_db.domains()[0]
405 logger.warning("No domain specified in smb.conf file, assuming '%s'",
409 if oldconf.get("domain logons"):
410 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).")
413 realm = domainname.upper()
414 logger.warning("No realm specified in smb.conf file, assuming '%s'",
417 # Find machine account and password
424 machinepass = secrets_db.get_machine_password(netbiosname)
428 # We must close the direct pytdb database before the C code loads it
431 # We must load the group mapping into memory before the passdb code touches it
432 groupdb = samba3.get_groupmapping_db()
433 for sid in groupdb.groupsids():
434 (gid, sid_name_use, nt_name, comment) = groupdb.get_group(sid)
435 # FIXME: import_sam_group(samdb, sid, gid, sid_name_use, nt_name, comment, domaindn)
438 passdb.set_secrets_dir(samba3.libdir)
441 domainsid = str(passdb.get_global_sam_sid())
446 machineacct = old_passdb.getsampwnam('%s$' % netbiosname)
447 machinesid, machinerid = machineacct.user_sid.split()
451 if domainsid is None:
452 logger.warning("Can't find domain secrets for '%s'; using random SID",
455 # Import users from old passdb backend
456 old_passdb = passdb.PDB(oldconf.get('passdb backend'))
457 userlist = old_passdb.search_users(0)
459 for entry in userlist:
460 if machinerid and machinerid == entry['rid']:
462 username = entry['account_name']
463 if entry['rid'] < 1000:
464 print("Skipping wellknown rid=%d (for username=%s)\n" % (entry['rid'], username))
466 if entry['rid'] >= next_rid:
467 next_rid = entry['rid'] + 1
469 userdata[username] = old_passdb.getsampwnam(username)
472 result = provision(logger, session_info, None,
473 targetdir=targetdir, realm=realm, domain=domainname,
474 domainsid=domainsid, next_rid=next_rid,
476 hostname=netbiosname, machinepass=machinepass,
477 serverrole=serverrole, samdb_fill=FILL_FULL)
479 import_wins(Ldb(result.paths.winsdb), samba3.get_wins_db())
481 # FIXME: import_registry(registry.Registry(), samba3.get_registry())
483 # FIXME: import_idmap(samdb,samba3.get_idmap_db(),domaindn)
487 # Export users to samba4 backend
488 new_smbconf = result.lp.configfile
489 newconf = s3param.get_context()
490 newconf.load(new_smbconf)
492 new_passdb = passdb.PDB('samba4')
494 for username in userdata:
495 print "adding user %s" % username
496 new_passdb.add_sam_account(userdata[username])