Rework provision scripts for more testing
[jelmer/samba4-debian.git] / source / 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, lp, paths):
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     domaindn = provision(setup_dir=setup_dir, message=message, 
248                          samdb_fill=FILL_DRS, paths=paths, session_info=session_info, 
249                          credentials=credentials, realm=realm, 
250                          domain=domainname, domainsid=domainsid, domainguid=domainguid, 
251                          machinepass=machinepass, serverrole=serverrole)
252
253     samdb = SamDB(paths.samdb, credentials=credentials, lp=lp, session_info=session_info)
254
255     import_wins(Ldb(paths.winsdb), samba3.get_wins_db())
256
257     # FIXME: import_registry(registry.Registry(), samba3.get_registry())
258
259     # FIXME: import_idmap(samdb,samba3.get_idmap_db(),domaindn)
260     
261     groupdb = samba3.get_groupmapping_db()
262     for sid in groupdb.groupsids():
263         (gid, sid_name_use, nt_name, comment) = groupdb.get_group(sid)
264         # FIXME: import_sam_group(samdb, sid, gid, sid_name_use, nt_name, comment, domaindn)
265
266     # FIXME: Aliases
267
268     passdb = samba3.get_sam_db()
269     for name in passdb:
270         user = passdb[name]
271         #FIXME: import_sam_account(samdb, user, domaindn, domainsid)
272
273     if hasattr(passdb, 'ldap_url'):
274         message("Enabling Samba3 LDAP mappings for SAM database")
275
276         enable_samba3sam(samdb, passdb.ldap_url)
277
278
279 def enable_samba3sam(samdb, ldapurl):
280     """Enable Samba 3 LDAP URL database.
281
282     :param samdb: SAM Database.
283     :param ldapurl: Samba 3 LDAP URL
284     """
285     samdb.modify_ldif("""
286 dn: @MODULES
287 changetype: modify
288 replace: @LIST
289 @LIST: samldb,operational,objectguid,rdn_name,samba3sam
290 """)
291
292     samdb.add({"dn": "@MAP=samba3sam", "@MAP_URL": ldapurl})
293
294
295 smbconf_keep = [
296     "dos charset", 
297     "unix charset",
298     "display charset",
299     "comment",
300     "path",
301     "directory",
302     "workgroup",
303     "realm",
304     "netbios name",
305     "netbios aliases",
306     "netbios scope",
307     "server string",
308     "interfaces",
309     "bind interfaces only",
310     "security",
311     "auth methods",
312     "encrypt passwords",
313     "null passwords",
314     "obey pam restrictions",
315     "password server",
316     "smb passwd file",
317     "private dir",
318     "passwd chat",
319     "password level",
320     "lanman auth",
321     "ntlm auth",
322     "client NTLMv2 auth",
323     "client lanman auth",
324     "client plaintext auth",
325     "read only",
326     "hosts allow",
327     "hosts deny",
328     "log level",
329     "debuglevel",
330     "log file",
331     "smb ports",
332     "large readwrite",
333     "max protocol",
334     "min protocol",
335     "unicode",
336     "read raw",
337     "write raw",
338     "disable netbios",
339     "nt status support",
340     "announce version",
341     "announce as",
342     "max mux",
343     "max xmit",
344     "name resolve order",
345     "max wins ttl",
346     "min wins ttl",
347     "time server",
348     "unix extensions",
349     "use spnego",
350     "server signing",
351     "client signing",
352     "max connections",
353     "paranoid server security",
354     "socket options",
355     "strict sync",
356     "max print jobs",
357     "printable",
358     "print ok",
359     "printer name",
360     "printer",
361     "map system",
362     "map hidden",
363     "map archive",
364     "preferred master",
365     "prefered master",
366     "local master",
367     "browseable",
368     "browsable",
369     "wins server",
370     "wins support",
371     "csc policy",
372     "strict locking",
373     "preload",
374     "auto services",
375     "lock dir",
376     "lock directory",
377     "pid directory",
378     "socket address",
379     "copy",
380     "include",
381     "available",
382     "volume",
383     "fstype",
384     "panic action",
385     "msdfs root",
386     "host msdfs",
387     "winbind separator"]
388
389 def upgrade_smbconf(oldconf,mark):
390     """Remove configuration variables not present in Samba4
391
392     :param oldconf: Old configuration structure
393     :param mark: Whether removed configuration variables should be 
394         kept in the new configuration as "samba3:<name>"
395     """
396     data = oldconf.data()
397     newconf = param_init()
398
399     for s in data:
400         for p in data[s]:
401             keep = False
402             for k in smbconf_keep:
403                 if smbconf_keep[k] == p:
404                     keep = True
405                     break
406
407             if keep:
408                 newconf.set(s, p, oldconf.get(s, p))
409             elif mark:
410                 newconf.set(s, "samba3:"+p, oldconf.get(s,p))
411
412     return newconf
413
414 SAMBA3_PREDEF_NAMES = {
415         'HKLM': registry.HKEY_LOCAL_MACHINE,
416 }
417
418 def import_registry(samba4_registry, samba3_regdb):
419     """Import a Samba 3 registry database into the Samba 4 registry.
420
421     :param samba4_registry: Samba 4 registry handle.
422     :param samba3_regdb: Samba 3 registry database handle.
423     """
424     def ensure_key_exists(keypath):
425         (predef_name, keypath) = keypath.split("/", 1)
426         predef_id = SAMBA3_PREDEF_NAMES[predef_name]
427         keypath = keypath.replace("/", "\\")
428         return samba4_registry.create_key(predef_id, keypath)
429
430     for key in samba3_regdb.keys():
431         key_handle = ensure_key_exists(key)
432         for subkey in samba3_regdb.subkeys(key):
433             ensure_key_exists(subkey)
434         for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items():
435             key_handle.set_value(value_name, value_type, value_data)
436
437