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