s4-s3-upgrade: Give a better clue when we cannot open secrets.tdb
[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 ldb
23 import time
24 import pwd
25
26 from samba import Ldb, registry
27 from samba.param import LoadParm
28 from samba.provision import provision, FILL_FULL, ProvisioningError
29 from samba.samba3 import passdb
30 from samba.samba3 import param as s3param
31 from samba.dcerpc import lsa, samr, security
32 from samba.dcerpc.security import dom_sid
33 from samba import dsdb
34 from samba.ndr import ndr_pack
35 from samba import unix2nttime
36
37
38 def import_sam_policy(samdb, policy, logger):
39     """Import a Samba 3 policy.
40
41     :param samdb: Samba4 SAM database
42     :param policy: Samba3 account policy
43     :param logger: Logger object
44     """
45
46     # Following entries are used -
47     #    min password length, password history, minimum password age,
48     #    maximum password age, lockout duration
49     #
50     # Following entries are not used -
51     #    reset count minutes, user must logon to change password,
52     #    bad lockout minutes, disconnect time
53
54     m = ldb.Message()
55     m.dn = samdb.get_default_basedn()
56     m['a01'] = ldb.MessageElement(str(policy['min password length']),
57         ldb.FLAG_MOD_REPLACE, 'minPwdLength')
58     m['a02'] = ldb.MessageElement(str(policy['password history']),
59         ldb.FLAG_MOD_REPLACE, 'pwdHistoryLength')
60
61     min_pw_age_unix = policy['minimum password age']
62     min_pw_age_nt = 0 - unix2nttime(min_pw_age_unix)
63     m['a03'] = ldb.MessageElement(str(min_pw_age_nt), ldb.FLAG_MOD_REPLACE,
64         'minPwdAge')
65
66     max_pw_age_unix = policy['maximum password age']
67     if (max_pw_age_unix == 0xFFFFFFFF):
68         max_pw_age_nt = 0
69     else:
70         max_pw_age_nt = unix2nttime(max_pw_age_unix)
71
72     m['a04'] = ldb.MessageElement(str(max_pw_age_nt), ldb.FLAG_MOD_REPLACE,
73                                   'maxPwdAge')
74
75     lockout_duration_mins = policy['lockout duration']
76     lockout_duration_nt = unix2nttime(lockout_duration_mins * 60)
77
78     m['a05'] = ldb.MessageElement(str(lockout_duration_nt),
79         ldb.FLAG_MOD_REPLACE, 'lockoutDuration')
80
81     try:
82         samdb.modify(m)
83     except ldb.LdbError, e:
84         logger.warn("Could not set account policy, (%s)", str(e))
85
86
87 def add_idmap_entry(idmapdb, sid, xid, xid_type, logger):
88     """Create idmap entry
89
90     :param idmapdb: Samba4 IDMAP database
91     :param sid: user/group sid
92     :param xid: user/group id
93     :param xid_type: type of id (UID/GID)
94     :param logger: Logger object
95     """
96
97     # First try to see if we already have this entry
98     found = False
99     msg = idmapdb.search(expression='objectSid=%s' % str(sid))
100     if msg.count == 1:
101         found = True
102
103     if found:
104         try:
105             m = ldb.Message()
106             m.dn = msg[0]['dn']
107             m['xidNumber'] = ldb.MessageElement(
108                 str(xid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
109             m['type'] = ldb.MessageElement(
110                 xid_type, ldb.FLAG_MOD_REPLACE, 'type')
111             idmapdb.modify(m)
112         except ldb.LdbError, e:
113             logger.warn(
114                 'Could not modify idmap entry for sid=%s, id=%s, type=%s (%s)',
115                 str(sid), str(xid), xid_type, str(e))
116     else:
117         try:
118             idmapdb.add({"dn": "CN=%s" % str(sid),
119                         "cn": str(sid),
120                         "objectClass": "sidMap",
121                         "objectSid": ndr_pack(sid),
122                         "type": xid_type,
123                         "xidNumber": str(xid)})
124         except ldb.LdbError, e:
125             logger.warn(
126                 'Could not add idmap entry for sid=%s, id=%s, type=%s (%s)',
127                 str(sid), str(xid), xid_type, str(e))
128
129
130 def import_idmap(idmapdb, samba3, logger):
131     """Import idmap data.
132
133     :param idmapdb: Samba4 IDMAP database
134     :param samba3_idmap: Samba3 IDMAP database to import from
135     :param logger: Logger object
136     """
137
138     try:
139         samba3_idmap = samba3.get_idmap_db()
140     except IOError, e:
141         logger.warn('Cannot open idmap database, Ignoring: %s', str(e))
142         return
143
144     currentxid = max(samba3_idmap.get_user_hwm(), samba3_idmap.get_group_hwm())
145     lowerbound = currentxid
146     # FIXME: upperbound
147
148     m = ldb.Message()
149     m.dn = ldb.Dn(idmapdb, 'CN=CONFIG')
150     m['lowerbound'] = ldb.MessageElement(
151         str(lowerbound), ldb.FLAG_MOD_REPLACE, 'lowerBound')
152     m['xidNumber'] = ldb.MessageElement(
153         str(currentxid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
154     idmapdb.modify(m)
155
156     for id_type, xid in samba3_idmap.ids():
157         if id_type == 'UID':
158             xid_type = 'ID_TYPE_UID'
159         elif id_type == 'GID':
160             xid_type = 'ID_TYPE_GID'
161         else:
162             logger.warn('Wrong type of entry in idmap (%s), Ignoring', id_type)
163             continue
164
165         sid = samba3_idmap.get_sid(xid, id_type)
166         add_idmap_entry(idmapdb, dom_sid(sid), xid, xid_type, logger)
167
168
169 def add_group_from_mapping_entry(samdb, groupmap, logger):
170     """Add or modify group from group mapping entry
171
172     param samdb: Samba4 SAM database
173     param groupmap: Groupmap entry
174     param logger: Logger object
175     """
176
177     # First try to see if we already have this entry
178     try:
179         msg = samdb.search(
180             base='<SID=%s>' % str(groupmap.sid), scope=ldb.SCOPE_BASE)
181         found = True
182     except ldb.LdbError, (ecode, emsg):
183         if ecode == ldb.ERR_NO_SUCH_OBJECT:
184             found = False
185         else:
186             raise ldb.LdbError(ecode, emsg)
187
188     if found:
189         logger.warn('Group already exists sid=%s, groupname=%s existing_groupname=%s, Ignoring.',
190                             str(groupmap.sid), groupmap.nt_name, msg[0]['sAMAccountName'][0])
191     else:
192         if groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
193             # In a lot of Samba3 databases, aliases are marked as well known groups
194             (group_dom_sid, rid) = groupmap.sid.split()
195             if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)):
196                 return
197
198         m = ldb.Message()
199         m.dn = ldb.Dn(samdb, "CN=%s,CN=Users,%s" % (groupmap.nt_name, samdb.get_default_basedn()))
200         m['a01'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'cn')
201         m['a02'] = ldb.MessageElement('group', ldb.FLAG_MOD_ADD, 'objectClass')
202         m['a03'] = ldb.MessageElement(ndr_pack(groupmap.sid), ldb.FLAG_MOD_ADD, 'objectSid')
203         m['a04'] = ldb.MessageElement(groupmap.comment, ldb.FLAG_MOD_ADD, 'description')
204         m['a05'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'sAMAccountName')
205
206         # Fix up incorrect 'well known' groups that are actually builtin (per test above) to be aliases
207         if groupmap.sid_name_use == lsa.SID_NAME_ALIAS or groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
208             m['a06'] = ldb.MessageElement(str(dsdb.GTYPE_SECURITY_DOMAIN_LOCAL_GROUP), ldb.FLAG_MOD_ADD, 'groupType')
209
210         try:
211             samdb.add(m, controls=["relax:0"])
212         except ldb.LdbError, e:
213             logger.warn('Could not add group name=%s (%s)', groupmap.nt_name, str(e))
214
215
216 def add_users_to_group(samdb, group, members, logger):
217     """Add user/member to group/alias
218
219     param samdb: Samba4 SAM database
220     param group: Groupmap object
221     param members: List of member SIDs
222     param logger: Logger object
223     """
224     for member_sid in members:
225         m = ldb.Message()
226         m.dn = ldb.Dn(samdb, "<SID=%s>" % str(group.sid))
227         m['a01'] = ldb.MessageElement("<SID=%s>" % str(member_sid), ldb.FLAG_MOD_ADD, 'member')
228
229         try:
230             samdb.modify(m)
231         except ldb.LdbError, (ecode, emsg):
232             if ecode == ldb.ERR_ENTRY_ALREADY_EXISTS:
233                 logger.info("skipped re-adding member '%s' to group '%s': %s", member_sid, group.sid, emsg)
234             elif ecode == ldb.ERR_NO_SUCH_OBJECT:
235                 raise ProvisioningError("Could not add member '%s' to group '%s' as either group or user record doesn't exist: %s" % (member_sid, group.sid, emsg))
236             else:
237                 raise ProvisioningError("Could not add member '%s' to group '%s': %s" % (member_sid, group.sid, emsg))
238
239
240 def import_wins(samba4_winsdb, samba3_winsdb):
241     """Import settings from a Samba3 WINS database.
242
243     :param samba4_winsdb: WINS database to import to
244     :param samba3_winsdb: WINS database to import from
245     """
246     version_id = 0
247
248     for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items():
249         version_id += 1
250
251         type = int(name.split("#", 1)[1], 16)
252
253         if type == 0x1C:
254             rType = 0x2
255         elif type & 0x80:
256             if len(ips) > 1:
257                 rType = 0x2
258             else:
259                 rType = 0x1
260         else:
261             if len(ips) > 1:
262                 rType = 0x3
263             else:
264                 rType = 0x0
265
266         if ttl > time.time():
267             rState = 0x0 # active
268         else:
269             rState = 0x1 # released
270
271         nType = ((nb_flags & 0x60) >> 5)
272
273         samba4_winsdb.add({"dn": "name=%s,type=0x%s" % tuple(name.split("#")),
274                            "type": name.split("#")[1],
275                            "name": name.split("#")[0],
276                            "objectClass": "winsRecord",
277                            "recordType": str(rType),
278                            "recordState": str(rState),
279                            "nodeType": str(nType),
280                            "expireTime": ldb.timestring(ttl),
281                            "isStatic": "0",
282                            "versionID": str(version_id),
283                            "address": ips})
284
285     samba4_winsdb.add({"dn": "cn=VERSION",
286                        "cn": "VERSION",
287                        "objectClass": "winsMaxVersion",
288                        "maxVersion": str(version_id)})
289
290
291 def enable_samba3sam(samdb, ldapurl):
292     """Enable Samba 3 LDAP URL database.
293
294     :param samdb: SAM Database.
295     :param ldapurl: Samba 3 LDAP URL
296     """
297     samdb.modify_ldif("""
298 dn: @MODULES
299 changetype: modify
300 replace: @LIST
301 @LIST: samldb,operational,objectguid,rdn_name,samba3sam
302 """)
303
304     samdb.add({"dn": "@MAP=samba3sam", "@MAP_URL": ldapurl})
305
306
307 smbconf_keep = [
308     "dos charset",
309     "unix charset",
310     "display charset",
311     "comment",
312     "path",
313     "directory",
314     "workgroup",
315     "realm",
316     "netbios name",
317     "netbios aliases",
318     "netbios scope",
319     "server string",
320     "interfaces",
321     "bind interfaces only",
322     "security",
323     "auth methods",
324     "encrypt passwords",
325     "null passwords",
326     "obey pam restrictions",
327     "password server",
328     "smb passwd file",
329     "private dir",
330     "passwd chat",
331     "password level",
332     "lanman auth",
333     "ntlm auth",
334     "client NTLMv2 auth",
335     "client lanman auth",
336     "client plaintext auth",
337     "read only",
338     "hosts allow",
339     "hosts deny",
340     "log level",
341     "debuglevel",
342     "log file",
343     "smb ports",
344     "large readwrite",
345     "max protocol",
346     "min protocol",
347     "unicode",
348     "read raw",
349     "write raw",
350     "disable netbios",
351     "nt status support",
352     "max mux",
353     "max xmit",
354     "name resolve order",
355     "max wins ttl",
356     "min wins ttl",
357     "time server",
358     "unix extensions",
359     "use spnego",
360     "server signing",
361     "client signing",
362     "max connections",
363     "paranoid server security",
364     "socket options",
365     "strict sync",
366     "max print jobs",
367     "printable",
368     "print ok",
369     "printer name",
370     "printer",
371     "map system",
372     "map hidden",
373     "map archive",
374     "preferred master",
375     "prefered master",
376     "local master",
377     "browseable",
378     "browsable",
379     "wins server",
380     "wins support",
381     "csc policy",
382     "strict locking",
383     "preload",
384     "auto services",
385     "lock dir",
386     "lock directory",
387     "pid directory",
388     "socket address",
389     "copy",
390     "include",
391     "available",
392     "volume",
393     "fstype",
394     "panic action",
395     "msdfs root",
396     "host msdfs",
397     "winbind separator"]
398
399
400 def upgrade_smbconf(oldconf, mark):
401     """Remove configuration variables not present in Samba4
402
403     :param oldconf: Old configuration structure
404     :param mark: Whether removed configuration variables should be
405         kept in the new configuration as "samba3:<name>"
406     """
407     data = oldconf.data()
408     newconf = LoadParm()
409
410     for s in data:
411         for p in data[s]:
412             keep = False
413             for k in smbconf_keep:
414                 if smbconf_keep[k] == p:
415                     keep = True
416                     break
417
418             if keep:
419                 newconf.set(s, p, oldconf.get(s, p))
420             elif mark:
421                 newconf.set(s, "samba3:" + p, oldconf.get(s, p))
422
423     return newconf
424
425 SAMBA3_PREDEF_NAMES = {
426         'HKLM': registry.HKEY_LOCAL_MACHINE,
427 }
428
429
430 def import_registry(samba4_registry, samba3_regdb):
431     """Import a Samba 3 registry database into the Samba 4 registry.
432
433     :param samba4_registry: Samba 4 registry handle.
434     :param samba3_regdb: Samba 3 registry database handle.
435     """
436     def ensure_key_exists(keypath):
437         (predef_name, keypath) = keypath.split("/", 1)
438         predef_id = SAMBA3_PREDEF_NAMES[predef_name]
439         keypath = keypath.replace("/", "\\")
440         return samba4_registry.create_key(predef_id, keypath)
441
442     for key in samba3_regdb.keys():
443         key_handle = ensure_key_exists(key)
444         for subkey in samba3_regdb.subkeys(key):
445             ensure_key_exists(subkey)
446         for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items():
447             key_handle.set_value(value_name, value_type, value_data)
448
449
450 def upgrade_from_samba3(samba3, logger, targetdir, session_info=None, useeadb=False):
451     """Upgrade from samba3 database to samba4 AD database
452
453     :param samba3: samba3 object
454     :param logger: Logger object
455     :param targetdir: samba4 database directory
456     :param session_info: Session information
457     """
458
459     if samba3.lp.get("domain logons"):
460         serverrole = "domain controller"
461     else:
462         if samba3.lp.get("security") == "user":
463             serverrole = "standalone"
464         else:
465             serverrole = "member server"
466
467     domainname = samba3.lp.get("workgroup")
468     realm = samba3.lp.get("realm")
469     netbiosname = samba3.lp.get("netbios name")
470
471     # secrets db
472     try:
473         secrets_db = samba3.get_secrets_db()
474     except IOError, e:
475         raise ProvisioningError("Could not open '%s', the Samba3 secrets database: %s.  Perhaps you specified the incorrect smb.conf, --testparm or --libdir option?" % samba3.privatedir_path("secrets.tdb"), str(e))
476
477     if not domainname:
478         domainname = secrets_db.domains()[0]
479         logger.warning("No workgroup specified in smb.conf file, assuming '%s'",
480                 domainname)
481
482     if not realm:
483         if serverrole == "domain controller":
484             raise ProvisioningError("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 (it is the DNS name of the AD domain you wish to create.")
485         else:
486             realm = domainname.upper()
487             logger.warning("No realm specified in smb.conf file, assuming '%s'",
488                     realm)
489
490     # Find machine account and password
491     next_rid = 1000
492
493     try:
494         machinepass = secrets_db.get_machine_password(netbiosname)
495     except KeyError:
496         machinepass = None
497
498     # We must close the direct pytdb database before the C code loads it
499     secrets_db.close()
500
501     # Connect to old password backend
502     passdb.set_secrets_dir(samba3.lp.get("private dir"))
503     s3db = samba3.get_sam_db()
504
505     # Get domain sid
506     try:
507         domainsid = passdb.get_global_sam_sid()
508     except passdb.error:
509         raise Exception("Can't find domain sid for '%s', Exiting." % domainname)
510
511     # Get machine account, sid, rid
512     try:
513         machineacct = s3db.getsampwnam('%s$' % netbiosname)
514     except passdb.error:
515         machinerid = None
516         machinesid = None
517     else:
518         machinesid, machinerid = machineacct.user_sid.split()
519
520     # Export account policy
521     logger.info("Exporting account policy")
522     policy = s3db.get_account_policy()
523
524     # Export groups from old passdb backend
525     logger.info("Exporting groups")
526     grouplist = s3db.enum_group_mapping()
527     groupmembers = {}
528     for group in grouplist:
529         sid, rid = group.sid.split()
530         if sid == domainsid:
531             if rid >= next_rid:
532                 next_rid = rid + 1
533
534         # Get members for each group/alias
535         if group.sid_name_use == lsa.SID_NAME_ALIAS:
536             members = s3db.enum_aliasmem(group.sid)
537         elif group.sid_name_use == lsa.SID_NAME_DOM_GRP:
538             try:
539                 members = s3db.enum_group_members(group.sid)
540             except passdb.error:
541                 continue
542             groupmembers[group.nt_name] = members
543         elif group.sid_name_use == lsa.SID_NAME_WKN_GRP:
544             (group_dom_sid, rid) = group.sid.split()
545             if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)):
546                 logger.warn("Ignoring 'well known' group '%s' (should already be in AD, and have no members)",
547                             group.nt_name)
548                 continue
549             # A number of buggy databases mix up well known groups and aliases.
550             members = s3db.enum_aliasmem(group.sid)
551         else:
552             logger.warn("Ignoring group '%s' with sid_name_use=%d",
553                         group.nt_name, group.sid_name_use)
554             continue
555
556     # Export users from old passdb backend
557     logger.info("Exporting users")
558     userlist = s3db.search_users(0)
559     userdata = {}
560     uids = {}
561     admin_user = None
562     for entry in userlist:
563         if machinerid and machinerid == entry['rid']:
564             continue
565         username = entry['account_name']
566         if entry['rid'] < 1000:
567             logger.info("  Skipping wellknown rid=%d (for username=%s)", entry['rid'], username)
568             continue
569         if entry['rid'] >= next_rid:
570             next_rid = entry['rid'] + 1
571
572         user = s3db.getsampwnam(username)
573         acct_type = (user.acct_ctrl & (samr.ACB_NORMAL|samr.ACB_WSTRUST|samr.ACB_SVRTRUST|samr.ACB_DOMTRUST))
574         if (acct_type == samr.ACB_NORMAL or acct_type == samr.ACB_WSTRUST or acct_type == samr.ACB_SVRTRUST):
575             pass
576         elif acct_type == samr.ACB_DOMTRUST:
577             logger.warn("  Skipping inter-domain trust from domain %s, this trust must be re-created as an AD trust" % username[:-1])
578             continue
579         elif acct_type == (samr.ACB_NORMAL|samr.ACB_WSTRUST) and username[-1] == '$':
580             logger.warn("  Fixing account %s which had both ACB_NORMAL (U) and ACB_WSTRUST (W) set.  Account will be marked as ACB_WSTRUST (W), i.e. as a domain member" % username)
581             user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_NORMAL)
582         else:
583             raise ProvisioningError("""Failed to upgrade due to invalid account %s, account control flags 0x%08X must have exactly one of
584 ACB_NORMAL (N, 0x%08X), ACB_WSTRUST (W 0x%08X), ACB_SVRTRUST (S 0x%08X) or ACB_DOMTRUST (D 0x%08X).
585
586 Please fix this account before attempting to upgrade again
587 """
588                                     % (user.acct_flags, username,
589                                        samr.ACB_NORMAL, samr.ACB_WSTRUST, samr.ACB_SVRTRUST, samr.ACB_DOMTRUST))
590
591         userdata[username] = user
592         try:
593             uids[username] = s3db.sid_to_id(user.user_sid)[0]
594         except passdb.error:
595             try:
596                 uids[username] = pwd.getpwnam(username).pw_uid
597             except KeyError:
598                 pass
599
600         if not admin_user and username.lower() == 'root':
601             admin_user = username
602         if username.lower() == 'administrator':
603             admin_user = username
604
605     logger.info("Next rid = %d", next_rid)
606
607     # Check for same username/groupname
608     group_names = set([g.nt_name for g in grouplist])
609     user_names = set([u['account_name'] for u in userlist])
610     common_names = group_names.intersection(user_names)
611     if common_names:
612         logger.error("Following names are both user names and group names:")
613         for name in common_names:
614             logger.error("   %s" % name)
615         raise ProvisioningError("Please remove common user/group names before upgrade.")
616
617     # Check for same user sid/group sid
618     group_sids = set([str(g.sid) for g in grouplist])
619     user_sids = set(["%s-%u" % (domainsid, u['rid']) for u in userlist])
620     common_sids = group_sids.intersection(user_sids)
621     if common_sids:
622         logger.error("Following sids are both user and group sids:")
623         for sid in common_sids:
624             logger.error("   %s" % str(sid))
625         raise ProvisioningError("Please remove duplicate sid entries before upgrade.")
626
627     if serverrole == "domain controller":
628         dns_backend = "BIND9_FLATFILE"
629     else:
630         dns_backend = "NONE"
631
632     # Do full provision
633     result = provision(logger, session_info, None,
634                        targetdir=targetdir, realm=realm, domain=domainname,
635                        domainsid=str(domainsid), next_rid=next_rid,
636                        dc_rid=machinerid,
637                        dom_for_fun_level=dsdb.DS_DOMAIN_FUNCTION_2003,
638                        hostname=netbiosname, machinepass=machinepass,
639                        serverrole=serverrole, samdb_fill=FILL_FULL,
640                        useeadb=useeadb, dns_backend=dns_backend)
641
642     # Import WINS database
643     logger.info("Importing WINS database")
644     import_wins(Ldb(result.paths.winsdb), samba3.get_wins_db())
645
646     # Set Account policy
647     logger.info("Importing Account policy")
648     import_sam_policy(result.samdb, policy, logger)
649
650     # Migrate IDMAP database
651     logger.info("Importing idmap database")
652     import_idmap(result.idmap, samba3, logger)
653
654     # Set the s3 context for samba4 configuration
655     new_lp_ctx = s3param.get_context()
656     new_lp_ctx.load(result.lp.configfile)
657     new_lp_ctx.set("private dir", result.lp.get("private dir"))
658     new_lp_ctx.set("state directory", result.lp.get("state directory"))
659     new_lp_ctx.set("lock directory", result.lp.get("lock directory"))
660
661     # Connect to samba4 backend
662     s4_passdb = passdb.PDB(new_lp_ctx.get("passdb backend"))
663
664     # Export groups to samba4 backend
665     logger.info("Importing groups")
666     for g in grouplist:
667         # Ignore uninitialized groups (gid = -1)
668         if g.gid != 0xffffffff:
669             add_idmap_entry(result.idmap, g.sid, g.gid, "GID", logger)
670             add_group_from_mapping_entry(result.samdb, g, logger)
671
672     # Export users to samba4 backend
673     logger.info("Importing users")
674     for username in userdata:
675         if username.lower() == 'administrator' or username.lower() == 'root':
676             continue
677         s4_passdb.add_sam_account(userdata[username])
678         if username in uids:
679             add_idmap_entry(result.idmap, userdata[username].user_sid, uids[username], "UID", logger)
680
681     logger.info("Adding users to groups")
682     for g in grouplist:
683         if g.nt_name in groupmembers:
684             add_users_to_group(result.samdb, g, groupmembers[g.nt_name], logger)
685
686     # Set password for administrator
687     if admin_user:
688         logger.info("Setting password for administrator")
689         admin_userdata = s4_passdb.getsampwnam("administrator")
690         admin_userdata.nt_passwd = userdata[admin_user].nt_passwd
691         if userdata[admin_user].lanman_passwd:
692             admin_userdata.lanman_passwd = userdata[admin_user].lanman_passwd
693         admin_userdata.pass_last_set_time = userdata[admin_user].pass_last_set_time
694         if userdata[admin_user].pw_history:
695             admin_userdata.pw_history = userdata[admin_user].pw_history
696         s4_passdb.update_sam_account(admin_userdata)
697         logger.info("Administrator password has been set to password of user '%s'", admin_user)
698
699     # FIXME: import_registry(registry.Registry(), samba3.get_registry())
700     # FIXME: shares