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