r26607: Fix reading of values and subkeys in Samba 3 registry files.
[amitay/samba.git] / source4 / scripting / python / samba / upgrade.py
1 #!/usr/bin/python
2 #
3 #       backend code for upgrading from Samba3
4 #       Copyright Jelmer Vernooij 2005-2007
5 #       Released under the GNU GPL v3 or later
6 #
7
8 """Support code for upgrading from Samba 3 to Samba 4."""
9
10 from provision import findnss, provision
11 import grp
12 import pwd
13 import uuid
14 import registry
15
16 # Where prefix is any of:
17 # - HKLM
18 #   HKU
19 #   HKCR
20 #   HKPD
21 #   HKPT
22 #
23
24 def upgrade_sam_policy(policy,dn):
25     ldif = """
26 dn: %s
27 changetype: modify
28 replace: minPwdLength
29 minPwdLength: %d
30 pwdHistoryLength: %d
31 minPwdAge: %d
32 maxPwdAge: %d
33 lockoutDuration: %d
34 samba3ResetCountMinutes: %d
35 samba3UserMustLogonToChangePassword: %d
36 samba3BadLockoutMinutes: %d
37 samba3DisconnectTime: %d
38
39 """ % (dn, policy.min_password_length, 
40     policy.password_history, policy.minimum_password_age,
41     policy.maximum_password_age, policy.lockout_duration,
42     policy.reset_count_minutes, policy.user_must_logon_to_change_password,
43     policy.bad_lockout_minutes, policy.disconnect_time)
44     
45     return ldif
46
47 def upgrade_sam_account(ldb,acc,domaindn,domainsid):
48     """Upgrade a SAM account."""
49     if acc.nt_username is None or acc.nt_username == "":
50         acc.nt_username = acc.username
51
52     if acc.fullname is None:
53         acc.fullname = pwd.getpwnam(acc.fullname)[4]
54
55     acc.fullname = acc.fullname.split(",")[0]
56
57     if acc.fullname is None:
58         acc.fullname = acc.username
59     
60     assert acc.fullname is not None
61     assert acc.nt_username is not None
62
63     ldif = """dn: cn=%s,%s
64 objectClass: top
65 objectClass: user
66 lastLogon: %d
67 lastLogoff: %d
68 unixName: %s
69 sAMAccountName: %s
70 cn: %s
71 description: %s
72 primaryGroupID: %d
73 badPwdcount: %d
74 logonCount: %d
75 samba3Domain: %s
76 samba3DirDrive: %s
77 samba3MungedDial: %s
78 samba3Homedir: %s
79 samba3LogonScript: %s
80 samba3ProfilePath: %s
81 samba3Workstations: %s
82 samba3KickOffTime: %d
83 samba3BadPwdTime: %d
84 samba3PassLastSetTime: %d
85 samba3PassCanChangeTime: %d
86 samba3PassMustChangeTime: %d
87 objectSid: %s-%d
88 lmPwdHash:: %s
89 ntPwdHash:: %s
90
91 """ % (ldb.dn_escape(acc.fullname), domaindn, acc.logon_time, acc.logoff_time, acc.username, acc.nt_username, acc.nt_username, 
92 acc.acct_desc, acc.group_rid, acc.bad_password_count, acc.logon_count,
93 acc.domain, acc.dir_drive, acc.munged_dial, acc.homedir, acc.logon_script, 
94 acc.profile_path, acc.workstations, acc.kickoff_time, acc.bad_password_time, 
95 acc.pass_last_set_time, acc.pass_can_change_time, acc.pass_must_change_time, domainsid, acc.user_rid,
96     ldb.encode(acc.lm_pw), ldb.encode(acc.nt_pw))
97
98     return ldif
99
100 def upgrade_sam_group(group,domaindn):
101     """Upgrade a SAM group."""
102     if group.sid_name_use == 5: # Well-known group
103         return None
104
105     if group.nt_name in ("Domain Guests", "Domain Users", "Domain Admins"):
106         return None
107     
108     if group.gid == -1:
109         gr = grp.getgrnam(grp.nt_name)
110     else:
111         gr = grp.getgrgid(grp.gid)
112
113     if gr is None:
114         group.unixname = "UNKNOWN"
115     else:
116         group.unixname = gr.gr_name
117
118     assert group.unixname is not None
119     
120     ldif = """dn: cn=%s,%s
121 objectClass: top
122 objectClass: group
123 description: %s
124 cn: %s
125 objectSid: %s
126 unixName: %s
127 samba3SidNameUse: %d
128 """ % (group.nt_name, domaindn, 
129 group.comment, group.nt_name, group.sid, group.unixname, group.sid_name_use)
130
131     return ldif
132
133 def import_idmap(samba4_idmap,samba3_idmap,domaindn):
134     samba4_idmap.add({
135         "dn": domaindn,
136         "userHwm": str(samba3_idmap.get_user_hwm()),
137         "groupHwm": str(samba3_idmap.get_group_hwm())})
138
139     for uid in samba3_idmap.uids():
140         samba4_idmap.add({"dn": "SID=%s,%s" % (samba3_idmap.get_user_sid(uid), domaindn),
141                           "SID": samba3_idmap.get_user_sid(uid),
142                           "type": "user",
143                           "unixID": str(uid)})
144
145     for gid in samba3_idmap.uids():
146         samba4_idmap.add({"dn": "SID=%s,%s" % (samba3_idmap.get_group_sid(gid), domaindn),
147                           "SID": samba3_idmap.get_group_sid(gid),
148                           "type": "group",
149                           "unixID": str(gid)})
150
151
152 def import_wins(samba4_winsdb, samba3_winsdb):
153     """Import settings from a Samba3 WINS database."""
154     version_id = 0
155     import time
156
157     for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items():
158         version_id+=1
159
160         numIPs = len(e.ips)
161
162         type = int(name.split("#", 1)[1], 16)
163
164         if type == 0x1C:
165             rType = 0x2
166         elif type & 0x80:
167             if len(ips) > 1:
168                 rType = 0x2
169             else:
170                 rType = 0x1
171         else:
172             if len(ips) > 1:
173                 rType = 0x3
174             else:
175                 rType = 0x0
176
177         if ttl > time.time():
178             rState = 0x0 # active
179         else:
180             rState = 0x1 # released
181
182         nType = ((nb_flags & 0x60)>>5)
183
184         samba4_winsdb.add({"dn": "name=%s,type=0x%s" % name.split("#"),
185                            "type": name.split("#")[1],
186                            "name": name.split("#")[0],
187                            "objectClass": "winsRecord",
188                            "recordType": str(rType),
189                            "recordState": str(rState),
190                            "nodeType": str(nType),
191                            "expireTime": ldb.ldaptime(ttl),
192                            "isStatic": "0",
193                            "versionID": str(version_id),
194                            "address": ips})
195
196     samba4_winsdb.add({"dn": "CN=VERSION",
197                        "objectClass": "winsMaxVersion",
198                        "maxVersion": str(version_id)})
199
200 def upgrade_provision(samba3, setup_dir, message, credentials, session_info, lp, paths):
201     oldconf = samba3.get_conf()
202
203     if oldconf.get("domain logons") == "True":
204         serverrole = "domain controller"
205     else:
206         if oldconf.get("security") == "user":
207             serverrole = "standalone"
208         else:
209             serverrole = "member server"
210
211     lp.set("server role", serverrole)
212     domainname = oldconf.get("workgroup")
213     if domainname:
214         domainname = str(domainname)
215     lp.set("workgroup", domainname)
216     realm = oldconf.get("realm")
217     netbiosname = oldconf.get("netbios name")
218
219     secrets_db = samba3.get_secrets_db()
220     
221     if domainname is None:
222         domainname = secrets_db.domains()[0]
223         message("No domain specified in smb.conf file, assuming '%s'" % domainname)
224     
225     if realm is None:
226         realm = domainname.lower()
227         message("No realm specified in smb.conf file, assuming '%s'\n" % realm)
228     lp.set("realm", realm)
229
230     domainguid = secrets_db.get_domain_guid(domainname)
231     domainsid = secrets_db.get_sid(domainname)
232     if domainsid is None:
233         message("Can't find domain secrets for '%s'; using random SID\n" % domainname)
234     
235     if netbiosname is not None:
236         machinepass = secrets_db.get_machine_password(netbiosname)
237     else:
238         machinepass = None
239     
240     provision(lp=lp, setup_dir=setup_dir, message=message, blank=True, ldapbackend=None, paths=paths, session_info=session_info, 
241               credentials=credentials, realm=realm, domain=domainname, 
242               domainsid=domainsid, domainguid=domainguid, machinepass=machinepass, serverrole=serverrole)
243
244 smbconf_keep = [
245     "dos charset", 
246     "unix charset",
247     "display charset",
248     "comment",
249     "path",
250     "directory",
251     "workgroup",
252     "realm",
253     "netbios name",
254     "netbios aliases",
255     "netbios scope",
256     "server string",
257     "interfaces",
258     "bind interfaces only",
259     "security",
260     "auth methods",
261     "encrypt passwords",
262     "null passwords",
263     "obey pam restrictions",
264     "password server",
265     "smb passwd file",
266     "private dir",
267     "passwd chat",
268     "password level",
269     "lanman auth",
270     "ntlm auth",
271     "client NTLMv2 auth",
272     "client lanman auth",
273     "client plaintext auth",
274     "read only",
275     "hosts allow",
276     "hosts deny",
277     "log level",
278     "debuglevel",
279     "log file",
280     "smb ports",
281     "large readwrite",
282     "max protocol",
283     "min protocol",
284     "unicode",
285     "read raw",
286     "write raw",
287     "disable netbios",
288     "nt status support",
289     "announce version",
290     "announce as",
291     "max mux",
292     "max xmit",
293     "name resolve order",
294     "max wins ttl",
295     "min wins ttl",
296     "time server",
297     "unix extensions",
298     "use spnego",
299     "server signing",
300     "client signing",
301     "max connections",
302     "paranoid server security",
303     "socket options",
304     "strict sync",
305     "max print jobs",
306     "printable",
307     "print ok",
308     "printer name",
309     "printer",
310     "map system",
311     "map hidden",
312     "map archive",
313     "preferred master",
314     "prefered master",
315     "local master",
316     "browseable",
317     "browsable",
318     "wins server",
319     "wins support",
320     "csc policy",
321     "strict locking",
322     "preload",
323     "auto services",
324     "lock dir",
325     "lock directory",
326     "pid directory",
327     "socket address",
328     "copy",
329     "include",
330     "available",
331     "volume",
332     "fstype",
333     "panic action",
334     "msdfs root",
335     "host msdfs",
336     "winbind separator"]
337
338 def upgrade_smbconf(oldconf,mark):
339     """Remove configuration variables not present in Samba4
340
341     :param oldconf: Old configuration structure
342     :param mark: Whether removed configuration variables should be 
343         kept in the new configuration as "samba3:<name>"
344     """
345     data = oldconf.data()
346     newconf = param_init()
347
348     for s in data:
349         for p in data[s]:
350             keep = False
351             for k in smbconf_keep:
352                 if smbconf_keep[k] == p:
353                     keep = True
354                     break
355
356             if keep:
357                 newconf.set(s, p, oldconf.get(s, p))
358             elif mark:
359                 newconf.set(s, "samba3:"+p, oldconf.get(s,p))
360
361     return newconf
362
363 SAMBA3_PREDEF_NAMES = {
364         'HKLM': registry.HKEY_LOCAL_MACHINE,
365 }
366
367 def import_registry(samba4_registry, samba3_regdb):
368     """Import a Samba 3 registry database into the Samba 4 registry.
369
370     :param samba4_registry: Samba 4 registry handle.
371     :param samba3_regdb: Samba 3 registry database handle.
372     """
373     def ensure_key_exists(keypath):
374         (predef_name, keypath) = keypath.split("/", 1)
375         predef_id = SAMBA3_PREDEF_NAMES[predef_name]
376         keypath = keypath.replace("/", "\\")
377         return samba4_registry.create_key(predef_id, keypath)
378
379     for key in samba3_regdb.keys():
380         key_handle = ensure_key_exists(key)
381         for subkey in samba3_regdb.subkeys(key):
382             ensure_key_exists(subkey)
383         for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items():
384             key_handle.set_value(value_name, value_type, value_data)
385
386
387 def upgrade(subobj, samba3, message, paths, session_info, credentials):
388     ret = 0
389     samdb = Ldb(paths.samdb, session_info=session_info, credentials=credentials)
390
391     message("Writing configuration")
392     newconf = upgrade_smbconf(samba3.configuration,True)
393     newconf.save(paths.smbconf)
394
395     message("Importing account policies")
396     samdb.modify_ldif(upgrade_sam_policy(samba3,subobj.BASEDN))
397     regdb = Ldb(paths.hklm)
398
399     regdb.modify("""
400 dn: value=RefusePasswordChange,key=Parameters,key=Netlogon,key=Services,key=CurrentControlSet,key=System,HIVE=NONE
401 replace: type
402 type: 4
403 replace: data
404 data: %d
405 """ % policy.refuse_machine_password_change)
406
407     message("Importing users")
408     for account in samba3.samaccounts:
409         msg = "... " + account.username
410         ldif = upgrade_sam_account(samdb, accounts,subobj.BASEDN,subobj.DOMAINSID)
411         try:
412             samdb.add(ldif)
413         except LdbError, e:
414             # FIXME: Ignore 'Record exists' errors
415             msg += "... error: " + str(e)
416             ret += 1; 
417         message(msg)
418
419     message("Importing groups")
420     for mapping in samba3.groupmappings:
421         msg = "... " + mapping.nt_name
422         ldif = upgrade_sam_group(mapping, subobj.BASEDN)
423         if ldif is not None:
424             try:
425                 samdb.add(ldif)
426             except LdbError, e:
427                 # FIXME: Ignore 'Record exists' errors
428                 msg += "... error: " + str(e)
429                 ret += 1
430         message(msg)
431
432     message("Importing WINS data")
433     winsdb = Ldb(paths.winsdb)
434     ldb_erase(winsdb)
435
436     ldif = upgrade_wins(samba3)
437     winsdb.add(ldif)
438
439     # figure out ldapurl, if applicable
440     ldapurl = None
441     pdb = samba3.configuration.get_list("passdb backend")
442     if pdb is not None:
443         for backend in pdb:
444             if len(backend) >= 7 and backend[0:7] == "ldapsam":
445                 ldapurl = backend[7:]
446
447     # URL was not specified in passdb backend but ldap /is/ used
448     if ldapurl == "":
449         ldapurl = "ldap://%s" % samba3.configuration.get("ldap server")
450
451     # Enable samba3sam module if original passdb backend was ldap
452     if ldapurl is not None:
453         message("Enabling Samba3 LDAP mappings for SAM database")
454
455         enable_samba3sam(samdb)
456
457     return ret
458
459
460 def enable_samba3sam(samdb):
461     samdb.modify("""
462 dn: @MODULES
463 changetype: modify
464 replace: @LIST
465 @LIST: samldb,operational,objectguid,rdn_name,samba3sam
466 """)
467
468     samdb.add({"dn": "@MAP=samba3sam", "@MAP_URL": ldapurl})