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