5bcc4294ba94ccf4a0ee0079bd2ea95983163356
[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     "announce version",
283     "announce as",
284     "max mux",
285     "max xmit",
286     "name resolve order",
287     "max wins ttl",
288     "min wins ttl",
289     "time server",
290     "unix extensions",
291     "use spnego",
292     "server signing",
293     "client signing",
294     "max connections",
295     "paranoid server security",
296     "socket options",
297     "strict sync",
298     "max print jobs",
299     "printable",
300     "print ok",
301     "printer name",
302     "printer",
303     "map system",
304     "map hidden",
305     "map archive",
306     "preferred master",
307     "prefered master",
308     "local master",
309     "browseable",
310     "browsable",
311     "wins server",
312     "wins support",
313     "csc policy",
314     "strict locking",
315     "preload",
316     "auto services",
317     "lock dir",
318     "lock directory",
319     "pid directory",
320     "socket address",
321     "copy",
322     "include",
323     "available",
324     "volume",
325     "fstype",
326     "panic action",
327     "msdfs root",
328     "host msdfs",
329     "winbind separator"]
330
331 def upgrade_smbconf(oldconf,mark):
332     """Remove configuration variables not present in Samba4
333
334     :param oldconf: Old configuration structure
335     :param mark: Whether removed configuration variables should be
336         kept in the new configuration as "samba3:<name>"
337     """
338     data = oldconf.data()
339     newconf = LoadParm()
340
341     for s in data:
342         for p in data[s]:
343             keep = False
344             for k in smbconf_keep:
345                 if smbconf_keep[k] == p:
346                     keep = True
347                     break
348
349             if keep:
350                 newconf.set(s, p, oldconf.get(s, p))
351             elif mark:
352                 newconf.set(s, "samba3:"+p, oldconf.get(s,p))
353
354     return newconf
355
356 SAMBA3_PREDEF_NAMES = {
357         'HKLM': registry.HKEY_LOCAL_MACHINE,
358 }
359
360 def import_registry(samba4_registry, samba3_regdb):
361     """Import a Samba 3 registry database into the Samba 4 registry.
362
363     :param samba4_registry: Samba 4 registry handle.
364     :param samba3_regdb: Samba 3 registry database handle.
365     """
366     def ensure_key_exists(keypath):
367         (predef_name, keypath) = keypath.split("/", 1)
368         predef_id = SAMBA3_PREDEF_NAMES[predef_name]
369         keypath = keypath.replace("/", "\\")
370         return samba4_registry.create_key(predef_id, keypath)
371
372     for key in samba3_regdb.keys():
373         key_handle = ensure_key_exists(key)
374         for subkey in samba3_regdb.subkeys(key):
375             ensure_key_exists(subkey)
376         for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items():
377             key_handle.set_value(value_name, value_type, value_data)
378
379
380 def upgrade_provision(samba3, logger, credentials, session_info,
381                       smbconf, targetdir):
382     oldconf = samba3.get_conf()
383
384     if oldconf.get("domain logons") == "True":
385         serverrole = "domain controller"
386     else:
387         if oldconf.get("security") == "user":
388             serverrole = "standalone"
389         else:
390             serverrole = "member server"
391
392     domainname = oldconf.get("workgroup")
393     realm = oldconf.get("realm")
394     netbiosname = oldconf.get("netbios name")
395
396     secrets_db = samba3.get_secrets_db()
397
398     if domainname is None:
399         domainname = secrets_db.domains()[0]
400         logger.warning("No domain specified in smb.conf file, assuming '%s'",
401                 domainname)
402
403     if realm is None:
404         if oldconf.get("domain logons") == "True":
405             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).")
406             return
407         else:
408             realm = domainname.upper()
409             logger.warning("No realm specified in smb.conf file, assuming '%s'",
410                     realm)
411
412     domainguid = secrets_db.get_domain_guid(domainname)
413     domainsid = secrets_db.get_sid(domainname)
414     if domainsid is None:
415         logger.warning("Can't find domain secrets for '%s'; using random SID",
416             domainname)
417
418     if netbiosname is not None:
419         machinepass = secrets_db.get_machine_password(netbiosname)
420     else:
421         machinepass = None
422
423     result = provision(logger=logger,
424                        session_info=session_info, credentials=credentials,
425                        targetdir=targetdir, realm=realm, domain=domainname,
426                        domainguid=domainguid, domainsid=domainsid,
427                        hostname=netbiosname, machinepass=machinepass,
428                        serverrole=serverrole)
429
430     import_wins(Ldb(result.paths.winsdb), samba3.get_wins_db())
431
432     # FIXME: import_registry(registry.Registry(), samba3.get_registry())
433
434     # FIXME: import_idmap(samdb,samba3.get_idmap_db(),domaindn)
435
436     groupdb = samba3.get_groupmapping_db()
437     for sid in groupdb.groupsids():
438         (gid, sid_name_use, nt_name, comment) = groupdb.get_group(sid)
439         # FIXME: import_sam_group(samdb, sid, gid, sid_name_use, nt_name, comment, domaindn)
440
441     # FIXME: Aliases
442
443     passdb = samba3.get_sam_db()
444     for name in passdb:
445         user = passdb[name]
446         #FIXME: import_sam_account(result.samdb, user, domaindn, domainsid)
447
448     if hasattr(passdb, 'ldap_url'):
449         logger.info("Enabling Samba3 LDAP mappings for SAM database")
450
451         enable_samba3sam(result.samdb, passdb.ldap_url)
452