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