Merge branch 'v4-0-test' of ssh://git.samba.org/data/git/samba into v4-0-wsgi
[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 __docformat__ = "restructuredText"
11
12 from provision import findnss, provision, FILL_DRS
13 import grp
14 import ldb
15 import time
16 import pwd
17 import uuid
18 import registry
19 from samba import Ldb
20 from samba.samdb import SamDB
21
22 def import_sam_policy(samldb, samba3_policy, domaindn):
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
97 def import_sam_group(samldb, sid, gid, sid_name_use, nt_name, comment, domaindn):
98     """Upgrade a SAM group.
99     
100     :param samldb: SAM database.
101     :param gid: Group GID
102     :param sid_name_use: SID name use
103     :param nt_name: NT Group Name
104     :param comment: NT Group Comment
105     :param domaindn: Domain DN
106     """
107
108     if sid_name_use == 5: # Well-known group
109         return None
110
111     if nt_name in ("Domain Guests", "Domain Users", "Domain Admins"):
112         return None
113     
114     if gid == -1:
115         gr = grp.getgrnam(nt_name)
116     else:
117         gr = grp.getgrgid(gid)
118
119     if gr is None:
120         unixname = "UNKNOWN"
121     else:
122         unixname = gr.gr_name
123
124     assert unixname is not None
125     
126     samldb.add({
127         "dn": "cn=%s,%s" % (nt_name, domaindn),
128         "objectClass": ["top", "group"],
129         "description": comment,
130         "cn": nt_name, 
131         "objectSid": sid,
132         "unixName": unixname,
133         "samba3SidNameUse": str(sid_name_use)
134         })
135
136
137 def import_idmap(samdb,samba3_idmap,domaindn):
138     """Import idmap data.
139
140     :param samdb: SamDB handle.
141     :param samba3_idmap: Samba 3 IDMAP database to import from
142     :param domaindn: Domain DN.
143     """
144     samdb.add({
145         "dn": domaindn,
146         "userHwm": str(samba3_idmap.get_user_hwm()),
147         "groupHwm": str(samba3_idmap.get_group_hwm())})
148
149     for uid in samba3_idmap.uids():
150         samdb.add({"dn": "SID=%s,%s" % (samba3_idmap.get_user_sid(uid), domaindn),
151                           "SID": samba3_idmap.get_user_sid(uid),
152                           "type": "user",
153                           "unixID": str(uid)})
154
155     for gid in samba3_idmap.uids():
156         samdb.add({"dn": "SID=%s,%s" % (samba3_idmap.get_group_sid(gid), domaindn),
157                           "SID": samba3_idmap.get_group_sid(gid),
158                           "type": "group",
159                           "unixID": str(gid)})
160
161
162 def import_wins(samba4_winsdb, samba3_winsdb):
163     """Import settings from a Samba3 WINS database.
164     
165     :param samba4_winsdb: WINS database to import to
166     :param samba3_winsdb: WINS database to import from
167     """
168     version_id = 0
169
170     for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items():
171         version_id+=1
172
173         type = int(name.split("#", 1)[1], 16)
174
175         if type == 0x1C:
176             rType = 0x2
177         elif type & 0x80:
178             if len(ips) > 1:
179                 rType = 0x2
180             else:
181                 rType = 0x1
182         else:
183             if len(ips) > 1:
184                 rType = 0x3
185             else:
186                 rType = 0x0
187
188         if ttl > time.time():
189             rState = 0x0 # active
190         else:
191             rState = 0x1 # released
192
193         nType = ((nb_flags & 0x60)>>5)
194
195         samba4_winsdb.add({"dn": "name=%s,type=0x%s" % tuple(name.split("#")),
196                            "type": name.split("#")[1],
197                            "name": name.split("#")[0],
198                            "objectClass": "winsRecord",
199                            "recordType": str(rType),
200                            "recordState": str(rState),
201                            "nodeType": str(nType),
202                            "expireTime": ldb.timestring(ttl),
203                            "isStatic": "0",
204                            "versionID": str(version_id),
205                            "address": ips})
206
207     samba4_winsdb.add({"dn": "cn=VERSION",
208                        "cn": "VERSION",
209                        "objectClass": "winsMaxVersion",
210                        "maxVersion": str(version_id)})
211
212 def upgrade_provision(samba3, setup_dir, message, credentials, session_info, smbconf, targetdir):
213     oldconf = samba3.get_conf()
214
215     if oldconf.get("domain logons") == "True":
216         serverrole = "domain controller"
217     else:
218         if oldconf.get("security") == "user":
219             serverrole = "standalone"
220         else:
221             serverrole = "member server"
222
223     domainname = oldconf.get("workgroup")
224     if domainname:
225         domainname = str(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
239     domainguid = secrets_db.get_domain_guid(domainname)
240     domainsid = secrets_db.get_sid(domainname)
241     if domainsid is None:
242         message("Can't find domain secrets for '%s'; using random SID\n" % domainname)
243     
244     if netbiosname is not None:
245         machinepass = secrets_db.get_machine_password(netbiosname)
246     else:
247         machinepass = None
248     
249     result = provision(setup_dir=setup_dir, message=message, 
250                        samdb_fill=FILL_DRS, smbconf=smbconf, session_info=session_info, 
251                        credentials=credentials, realm=realm, 
252                        domain=domainname, domainsid=domainsid, domainguid=domainguid, 
253                        machinepass=machinepass, serverrole=serverrole, targetdir=targetdir)
254
255     import_wins(Ldb(result.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(result.samdb, user, domaindn, domainsid)
272
273     if hasattr(passdb, 'ldap_url'):
274         message("Enabling Samba3 LDAP mappings for SAM database")
275
276         enable_samba3sam(result.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