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