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