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