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