r26591: Get the first bits of samba3dump to work again.
[kai/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 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(lp, samba3):
259     domainname = samba3.configuration.get("workgroup")
260     
261     if domainname is None:
262         domainname = samba3.secrets.domains[0].name
263         print "No domain specified in smb.conf file, assuming '%s'\n" % domainname
264     
265     domsec = samba3.find_domainsecrets(domainname)
266     hostsec = samba3.find_domainsecrets(hostname())
267     realm = samba3.configuration.get("realm")
268
269     if realm is None:
270         realm = domainname
271         print "No realm specified in smb.conf file, assuming '%s'\n" % realm
272     random_init(local)
273
274     subobj.realm        = realm
275     subobj.domain       = domainname
276
277     if domsec is not None:
278         subobj.DOMAINGUID   = domsec.guid
279         subobj.DOMAINSID    = domsec.sid
280     else:
281         print "Can't find domain secrets for '%s'; using random SID and GUID\n" % domainname
282         subobj.DOMAINGUID = uuid.random()
283         subobj.DOMAINSID = randsid()
284     
285     if hostsec:
286         hostguid = hostsec.guid
287     subobj.krbtgtpass   = randpass(12)
288     subobj.machinepass  = randpass(12)
289     subobj.adminpass    = randpass(12)
290     subobj.datestring   = datestring()
291     subobj.root         = findnss(pwd.getpwnam, "root")[4]
292     subobj.nobody       = findnss(pwd.getpwnam, "nobody")[4]
293     subobj.nogroup      = findnss(grp.getgrnam, "nogroup", "nobody")[2]
294     subobj.wheel        = findnss(grp.getgrnam, "wheel", "root")[2]
295     subobj.users        = findnss(grp.getgrnam, "users", "guest", "other")[2]
296     subobj.dnsdomain    = subobj.realm.lower()
297     subobj.dnsname      = "%s.%s" % (subobj.hostname.lower(), subobj.dnsdomain)
298     subobj.basedn       = "DC=" + ",DC=".join(subobj.realm.split("."))
299     rdn_list = subobj.dnsdomain.split(".")
300     subobj.domaindn     = "DC=" + ",DC=".join(rdn_list)
301     subobj.domaindn_ldb = "users.ldb"
302     subobj.rootdn       = subobj.domaindn
303
304     modules_list        = ["rootdse",
305                     "kludge_acl",
306                     "paged_results",
307                     "server_sort",
308                     "extended_dn",
309                     "asq",
310                     "samldb",
311                     "password_hash",
312                     "operational",
313                     "objectclass",
314                     "rdn_name",
315                     "show_deleted",
316                     "partition"]
317     subobj.modules_list = ",".join(modules_list)
318
319     return subobj
320
321 smbconf_keep = [
322     "dos charset", 
323     "unix charset",
324     "display charset",
325     "comment",
326     "path",
327     "directory",
328     "workgroup",
329     "realm",
330     "netbios name",
331     "netbios aliases",
332     "netbios scope",
333     "server string",
334     "interfaces",
335     "bind interfaces only",
336     "security",
337     "auth methods",
338     "encrypt passwords",
339     "null passwords",
340     "obey pam restrictions",
341     "password server",
342     "smb passwd file",
343     "private dir",
344     "passwd chat",
345     "password level",
346     "lanman auth",
347     "ntlm auth",
348     "client NTLMv2 auth",
349     "client lanman auth",
350     "client plaintext auth",
351     "read only",
352     "hosts allow",
353     "hosts deny",
354     "log level",
355     "debuglevel",
356     "log file",
357     "smb ports",
358     "large readwrite",
359     "max protocol",
360     "min protocol",
361     "unicode",
362     "read raw",
363     "write raw",
364     "disable netbios",
365     "nt status support",
366     "announce version",
367     "announce as",
368     "max mux",
369     "max xmit",
370     "name resolve order",
371     "max wins ttl",
372     "min wins ttl",
373     "time server",
374     "unix extensions",
375     "use spnego",
376     "server signing",
377     "client signing",
378     "max connections",
379     "paranoid server security",
380     "socket options",
381     "strict sync",
382     "max print jobs",
383     "printable",
384     "print ok",
385     "printer name",
386     "printer",
387     "map system",
388     "map hidden",
389     "map archive",
390     "preferred master",
391     "prefered master",
392     "local master",
393     "browseable",
394     "browsable",
395     "wins server",
396     "wins support",
397     "csc policy",
398     "strict locking",
399     "preload",
400     "auto services",
401     "lock dir",
402     "lock directory",
403     "pid directory",
404     "socket address",
405     "copy",
406     "include",
407     "available",
408     "volume",
409     "fstype",
410     "panic action",
411     "msdfs root",
412     "host msdfs",
413     "winbind separator"]
414
415 def upgrade_smbconf(oldconf,mark):
416     """Remove configuration variables not present in Samba4
417
418     :param oldconf: Old configuration structure
419     :param mark: Whether removed configuration variables should be 
420         kept in the new configuration as "samba3:<name>"
421     """
422     data = oldconf.data()
423     newconf = param_init()
424
425     for s in data:
426         for p in data[s]:
427             keep = False
428             for k in smbconf_keep:
429                 if smbconf_keep[k] == p:
430                     keep = True
431                     break
432
433             if keep:
434                 newconf.set(s, p, oldconf.get(s, p))
435             elif mark:
436                 newconf.set(s, "samba3:"+p, oldconf.get(s,p))
437
438     if oldconf.get("domain logons") == "True":
439         newconf.set("server role", "domain controller")
440     else:
441         if oldconf.get("security") == "user":
442             newconf.set("server role", "standalone")
443         else:
444             newconf.set("server role", "member server")
445
446     return newconf
447
448 def upgrade(subobj, samba3, message, paths, session_info, credentials):
449     ret = 0
450     samdb = Ldb(paths.samdb, session_info=session_info, credentials=credentials)
451
452     message("Writing configuration")
453     newconf = upgrade_smbconf(samba3.configuration,True)
454     newconf.save(paths.smbconf)
455
456     message("Importing account policies")
457     samdb.modify_ldif(upgrade_sam_policy(samba3,subobj.BASEDN))
458     regdb = Ldb(paths.hklm)
459
460     regdb.modify("""
461 dn: value=RefusePasswordChange,key=Parameters,key=Netlogon,key=Services,key=CurrentControlSet,key=System,HIVE=NONE
462 replace: type
463 type: 4
464 replace: data
465 data: %d
466 """ % policy.refuse_machine_password_change)
467
468     message("Importing users")
469     for account in samba3.samaccounts:
470         msg = "... " + account.username
471         ldif = upgrade_sam_account(samdb, accounts,subobj.BASEDN,subobj.DOMAINSID)
472         try:
473             samdb.add(ldif)
474         except LdbError, e:
475             # FIXME: Ignore 'Record exists' errors
476             msg += "... error: " + str(e)
477             ret += 1; 
478         message(msg)
479
480     message("Importing groups")
481     for mapping in samba3.groupmappings:
482         msg = "... " + mapping.nt_name
483         ldif = upgrade_sam_group(mapping, subobj.BASEDN)
484         if ldif is not None:
485             try:
486                 samdb.add(ldif)
487             except LdbError, e:
488                 # FIXME: Ignore 'Record exists' errors
489                 msg += "... error: " + str(e)
490                 ret += 1
491         message(msg)
492
493     message("Importing registry data")
494     for hive in ["hkcr","hkcu","hklm","hkpd","hku","hkpt"]:
495         message("... " + hive)
496         regdb = Ldb(paths[hive])
497         ldif = upgrade_registry(samba3.registry, hive, regdb)
498         for j in ldif:
499             msg = "... ... " + j
500             try:
501                 regdb.add(ldif[j])
502             except LdbError, e:
503                 # FIXME: Ignore 'Record exists' errors
504                 msg += "... error: " + str(e)
505                 ret += 1
506             message(msg)
507
508     message("Importing WINS data")
509     winsdb = Ldb(paths.winsdb)
510     ldb_erase(winsdb)
511
512     ldif = upgrade_wins(samba3)
513     winsdb.add(ldif)
514
515     # figure out ldapurl, if applicable
516     ldapurl = None
517     pdb = samba3.configuration.get_list("passdb backend")
518     if pdb is not None:
519         for backend in pdb:
520             if len(backend) >= 7 and backend[0:7] == "ldapsam":
521                 ldapurl = backend[7:]
522
523     # URL was not specified in passdb backend but ldap /is/ used
524     if ldapurl == "":
525         ldapurl = "ldap://%s" % samba3.configuration.get("ldap server")
526
527     # Enable samba3sam module if original passdb backend was ldap
528     if ldapurl is not None:
529         message("Enabling Samba3 LDAP mappings for SAM database")
530
531         enable_samba3sam(samdb)
532
533     return ret
534
535 def upgrade_verify(subobj, samba3, paths, message):
536     message("Verifying account policies")
537
538     samldb = Ldb(paths.samdb)
539
540     for account in samba3.samaccounts:
541         msg = samldb.search("(&(sAMAccountName=" + account.nt_username + ")(objectclass=user))")
542         assert(len(msg) >= 1)
543     
544     # FIXME
545
546
547
548 def enable_samba3sam(samdb):
549     samdb.modify("""
550 dn: @MODULES
551 changetype: modify
552 replace: @LIST
553 @LIST: samldb,operational,objectguid,rdn_name,samba3sam
554 """)
555
556     samdb.add({"dn": "@MAP=samba3sam", "@MAP_URL": ldapurl})