s4-classicupgrade: Demote any other 'BDC' accounts back to a member server during...
[samba.git] / source4 / scripting / python / samba / upgrade.py
1 # backend code for upgrading from Samba3
2 # Copyright Jelmer Vernooij 2005-2007
3 # Copyright Andrew Bartlett 2011
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18
19 """Support code for upgrading from Samba 3 to Samba 4."""
20
21 __docformat__ = "restructuredText"
22
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 = int(-min_pw_age_unix * (1e7))
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 == -1 or max_pw_age_unix == 0:
69         max_pw_age_nt = -0x8000000000000000
70     else:
71         max_pw_age_nt = int(-max_pw_age_unix * (1e7))
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_ad_posix_idmap_entry(samdb, sid, xid, xid_type, logger):
89     """Create idmap entry
90
91     :param samdb: Samba4 sam.ldb database
92     :param sid: user/group sid
93     :param xid: user/group id
94     :param xid_type: type of id (ID_TYPE_UID/ID_TYPE_GID)
95     :param logger: Logger object
96     """
97
98     try:
99         m = ldb.Message()
100         m.dn = ldb.Dn(samdb, "<SID=%s>" % str(sid))
101         if xid_type == "ID_TYPE_UID":
102             m['uidNumber'] = ldb.MessageElement(
103                 str(xid), ldb.FLAG_MOD_REPLACE, 'uidNumber')
104             m['objectClass'] = ldb.MessageElement(
105                 "posixAccount", ldb.FLAG_MOD_ADD, 'objectClass')
106         elif xid_type == "ID_TYPE_GID":
107             m['gidNumber'] = ldb.MessageElement(
108                 str(xid), ldb.FLAG_MOD_REPLACE, 'gidNumber')
109             m['objectClass'] = ldb.MessageElement(
110                 "posixGroup", ldb.FLAG_MOD_ADD, 'objectClass')
111
112         samdb.modify(m)
113     except ldb.LdbError, e:
114         logger.warn(
115             'Could not modify AD idmap entry for sid=%s, id=%s, type=%s (%s)',
116             str(sid), str(xid), xid_type, str(e))
117
118 def add_idmap_entry(idmapdb, sid, xid, xid_type, logger):
119     """Create idmap entry
120
121     :param idmapdb: Samba4 IDMAP database
122     :param sid: user/group sid
123     :param xid: user/group id
124     :param xid_type: type of id (ID_TYPE_UID/ID_TYPE_GID)
125     :param logger: Logger object
126     """
127
128     # First try to see if we already have this entry
129     found = False
130     msg = idmapdb.search(expression='objectSid=%s' % str(sid))
131     if msg.count == 1:
132         found = True
133
134     if found:
135         try:
136             m = ldb.Message()
137             m.dn = msg[0]['dn']
138             m['xidNumber'] = ldb.MessageElement(
139                 str(xid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
140             m['type'] = ldb.MessageElement(
141                 xid_type, ldb.FLAG_MOD_REPLACE, 'type')
142             idmapdb.modify(m)
143         except ldb.LdbError, e:
144             logger.warn(
145                 'Could not modify idmap entry for sid=%s, id=%s, type=%s (%s)',
146                 str(sid), str(xid), xid_type, str(e))
147     else:
148         try:
149             idmapdb.add({"dn": "CN=%s" % str(sid),
150                         "cn": str(sid),
151                         "objectClass": "sidMap",
152                         "objectSid": ndr_pack(sid),
153                         "type": xid_type,
154                         "xidNumber": str(xid)})
155         except ldb.LdbError, e:
156             logger.warn(
157                 'Could not add idmap entry for sid=%s, id=%s, type=%s (%s)',
158                 str(sid), str(xid), xid_type, str(e))
159
160
161 def import_idmap(idmapdb, samba3, logger):
162     """Import idmap data.
163
164     :param idmapdb: Samba4 IDMAP database
165     :param samba3_idmap: Samba3 IDMAP database to import from
166     :param logger: Logger object
167     """
168
169     try:
170         samba3_idmap = samba3.get_idmap_db()
171     except IOError, e:
172         logger.warn('Cannot open idmap database, Ignoring: %s', str(e))
173         return
174
175     currentxid = max(samba3_idmap.get_user_hwm(), samba3_idmap.get_group_hwm())
176     lowerbound = currentxid
177     # FIXME: upperbound
178
179     m = ldb.Message()
180     m.dn = ldb.Dn(idmapdb, 'CN=CONFIG')
181     m['lowerbound'] = ldb.MessageElement(
182         str(lowerbound), ldb.FLAG_MOD_REPLACE, 'lowerBound')
183     m['xidNumber'] = ldb.MessageElement(
184         str(currentxid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
185     idmapdb.modify(m)
186
187     for id_type, xid in samba3_idmap.ids():
188         if id_type == 'UID':
189             xid_type = 'ID_TYPE_UID'
190         elif id_type == 'GID':
191             xid_type = 'ID_TYPE_GID'
192         else:
193             logger.warn('Wrong type of entry in idmap (%s), Ignoring', id_type)
194             continue
195
196         sid = samba3_idmap.get_sid(xid, id_type)
197         add_idmap_entry(idmapdb, dom_sid(sid), xid, xid_type, logger)
198
199
200 def add_group_from_mapping_entry(samdb, groupmap, logger):
201     """Add or modify group from group mapping entry
202
203     param samdb: Samba4 SAM database
204     param groupmap: Groupmap entry
205     param logger: Logger object
206     """
207
208     # First try to see if we already have this entry
209     try:
210         msg = samdb.search(
211             base='<SID=%s>' % str(groupmap.sid), scope=ldb.SCOPE_BASE)
212         found = True
213     except ldb.LdbError, (ecode, emsg):
214         if ecode == ldb.ERR_NO_SUCH_OBJECT:
215             found = False
216         else:
217             raise ldb.LdbError(ecode, emsg)
218
219     if found:
220         logger.warn('Group already exists sid=%s, groupname=%s existing_groupname=%s, Ignoring.',
221                             str(groupmap.sid), groupmap.nt_name, msg[0]['sAMAccountName'][0])
222     else:
223         if groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
224             # In a lot of Samba3 databases, aliases are marked as well known groups
225             (group_dom_sid, rid) = groupmap.sid.split()
226             if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)):
227                 return
228
229         m = ldb.Message()
230         m.dn = ldb.Dn(samdb, "CN=%s,CN=Users,%s" % (groupmap.nt_name, samdb.get_default_basedn()))
231         m['cn'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'cn')
232         m['objectClass'] = ldb.MessageElement('group', ldb.FLAG_MOD_ADD, 'objectClass')
233         m['objectSid'] = ldb.MessageElement(ndr_pack(groupmap.sid), ldb.FLAG_MOD_ADD,
234             'objectSid')
235         m['sAMAccountName'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD,
236             'sAMAccountName')
237
238         if groupmap.comment:
239             m['description'] = ldb.MessageElement(groupmap.comment, ldb.FLAG_MOD_ADD,
240                 'description')
241
242         # Fix up incorrect 'well known' groups that are actually builtin (per test above) to be aliases
243         if groupmap.sid_name_use == lsa.SID_NAME_ALIAS or groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
244             m['groupType'] = ldb.MessageElement(str(dsdb.GTYPE_SECURITY_DOMAIN_LOCAL_GROUP),
245                 ldb.FLAG_MOD_ADD, 'groupType')
246
247         try:
248             samdb.add(m, controls=["relax:0"])
249         except ldb.LdbError, e:
250             logger.warn('Could not add group name=%s (%s)', groupmap.nt_name, str(e))
251
252
253 def add_users_to_group(samdb, group, members, logger):
254     """Add user/member to group/alias
255
256     param samdb: Samba4 SAM database
257     param group: Groupmap object
258     param members: List of member SIDs
259     param logger: Logger object
260     """
261     for member_sid in members:
262         m = ldb.Message()
263         m.dn = ldb.Dn(samdb, "<SID=%s>" % str(group.sid))
264         m['a01'] = ldb.MessageElement("<SID=%s>" % str(member_sid), ldb.FLAG_MOD_ADD, 'member')
265
266         try:
267             samdb.modify(m)
268         except ldb.LdbError, (ecode, emsg):
269             if ecode == ldb.ERR_ENTRY_ALREADY_EXISTS:
270                 logger.debug("skipped re-adding member '%s' to group '%s': %s", member_sid, group.sid, emsg)
271             elif ecode == ldb.ERR_NO_SUCH_OBJECT:
272                 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))
273             else:
274                 raise ProvisioningError("Could not add member '%s' to group '%s': %s" % (member_sid, group.sid, emsg))
275
276
277 def import_wins(samba4_winsdb, samba3_winsdb):
278     """Import settings from a Samba3 WINS database.
279
280     :param samba4_winsdb: WINS database to import to
281     :param samba3_winsdb: WINS database to import from
282     """
283
284     version_id = 0
285
286     for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items():
287         version_id += 1
288
289         type = int(name.split("#", 1)[1], 16)
290
291         if type == 0x1C:
292             rType = 0x2
293         elif type & 0x80:
294             if len(ips) > 1:
295                 rType = 0x2
296             else:
297                 rType = 0x1
298         else:
299             if len(ips) > 1:
300                 rType = 0x3
301             else:
302                 rType = 0x0
303
304         if ttl > time.time():
305             rState = 0x0 # active
306         else:
307             rState = 0x1 # released
308
309         nType = ((nb_flags & 0x60) >> 5)
310
311         samba4_winsdb.add({"dn": "name=%s,type=0x%s" % tuple(name.split("#")),
312                            "type": name.split("#")[1],
313                            "name": name.split("#")[0],
314                            "objectClass": "winsRecord",
315                            "recordType": str(rType),
316                            "recordState": str(rState),
317                            "nodeType": str(nType),
318                            "expireTime": ldb.timestring(ttl),
319                            "isStatic": "0",
320                            "versionID": str(version_id),
321                            "address": ips})
322
323     samba4_winsdb.add({"dn": "cn=VERSION",
324                        "cn": "VERSION",
325                        "objectClass": "winsMaxVersion",
326                        "maxVersion": str(version_id)})
327
328
329 def enable_samba3sam(samdb, ldapurl):
330     """Enable Samba 3 LDAP URL database.
331
332     :param samdb: SAM Database.
333     :param ldapurl: Samba 3 LDAP URL
334     """
335     samdb.modify_ldif("""
336 dn: @MODULES
337 changetype: modify
338 replace: @LIST
339 @LIST: samldb,operational,objectguid,rdn_name,samba3sam
340 """)
341
342     samdb.add({"dn": "@MAP=samba3sam", "@MAP_URL": ldapurl})
343
344
345 smbconf_keep = [
346     "dos charset",
347     "unix charset",
348     "display charset",
349     "comment",
350     "path",
351     "directory",
352     "workgroup",
353     "realm",
354     "netbios name",
355     "netbios aliases",
356     "netbios scope",
357     "server string",
358     "interfaces",
359     "bind interfaces only",
360     "security",
361     "auth methods",
362     "encrypt passwords",
363     "null passwords",
364     "obey pam restrictions",
365     "password server",
366     "smb passwd file",
367     "private dir",
368     "passwd chat",
369     "password level",
370     "lanman auth",
371     "ntlm auth",
372     "client NTLMv2 auth",
373     "client lanman auth",
374     "client plaintext auth",
375     "read only",
376     "hosts allow",
377     "hosts deny",
378     "log level",
379     "debuglevel",
380     "log file",
381     "smb ports",
382     "large readwrite",
383     "max protocol",
384     "min protocol",
385     "unicode",
386     "read raw",
387     "write raw",
388     "disable netbios",
389     "nt status support",
390     "max mux",
391     "max xmit",
392     "name resolve order",
393     "max wins ttl",
394     "min wins ttl",
395     "time server",
396     "unix extensions",
397     "use spnego",
398     "server signing",
399     "client signing",
400     "max connections",
401     "paranoid server security",
402     "socket options",
403     "strict sync",
404     "max print jobs",
405     "printable",
406     "print ok",
407     "printer name",
408     "printer",
409     "map system",
410     "map hidden",
411     "map archive",
412     "preferred master",
413     "prefered master",
414     "local master",
415     "browseable",
416     "browsable",
417     "wins server",
418     "wins support",
419     "csc policy",
420     "strict locking",
421     "preload",
422     "auto services",
423     "lock dir",
424     "lock directory",
425     "pid directory",
426     "socket address",
427     "copy",
428     "include",
429     "available",
430     "volume",
431     "fstype",
432     "panic action",
433     "msdfs root",
434     "host msdfs",
435     "winbind separator"]
436
437
438 def upgrade_smbconf(oldconf, mark):
439     """Remove configuration variables not present in Samba4
440
441     :param oldconf: Old configuration structure
442     :param mark: Whether removed configuration variables should be
443         kept in the new configuration as "samba3:<name>"
444     """
445     data = oldconf.data()
446     newconf = LoadParm()
447
448     for s in data:
449         for p in data[s]:
450             keep = False
451             for k in smbconf_keep:
452                 if smbconf_keep[k] == p:
453                     keep = True
454                     break
455
456             if keep:
457                 newconf.set(s, p, oldconf.get(s, p))
458             elif mark:
459                 newconf.set(s, "samba3:" + p, oldconf.get(s, p))
460
461     return newconf
462
463 SAMBA3_PREDEF_NAMES = {
464         'HKLM': registry.HKEY_LOCAL_MACHINE,
465 }
466
467
468 def import_registry(samba4_registry, samba3_regdb):
469     """Import a Samba 3 registry database into the Samba 4 registry.
470
471     :param samba4_registry: Samba 4 registry handle.
472     :param samba3_regdb: Samba 3 registry database handle.
473     """
474     def ensure_key_exists(keypath):
475         (predef_name, keypath) = keypath.split("/", 1)
476         predef_id = SAMBA3_PREDEF_NAMES[predef_name]
477         keypath = keypath.replace("/", "\\")
478         return samba4_registry.create_key(predef_id, keypath)
479
480     for key in samba3_regdb.keys():
481         key_handle = ensure_key_exists(key)
482         for subkey in samba3_regdb.subkeys(key):
483             ensure_key_exists(subkey)
484         for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items():
485             key_handle.set_value(value_name, value_type, value_data)
486
487
488 def upgrade_from_samba3(samba3, logger, targetdir, session_info=None, useeadb=False, dns_backend=None):
489     """Upgrade from samba3 database to samba4 AD database
490
491     :param samba3: samba3 object
492     :param logger: Logger object
493     :param targetdir: samba4 database directory
494     :param session_info: Session information
495     """
496     serverrole = samba3.lp.server_role()
497
498     domainname = samba3.lp.get("workgroup")
499     realm = samba3.lp.get("realm")
500     netbiosname = samba3.lp.get("netbios name")
501
502     if samba3.lp.get("ldapsam:trusted") is None:
503         samba3.lp.set("ldapsam:trusted", "yes")
504
505     # secrets db
506     try:
507         secrets_db = samba3.get_secrets_db()
508     except IOError, e:
509         raise ProvisioningError("Could not open '%s', the Samba3 secrets database: %s.  Perhaps you specified the incorrect smb.conf, --testparm or --dbdir option?" % (samba3.privatedir_path("secrets.tdb"), str(e)))
510
511     if not domainname:
512         domainname = secrets_db.domains()[0]
513         logger.warning("No workgroup specified in smb.conf file, assuming '%s'",
514                 domainname)
515
516     if not realm:
517         if serverrole == "ROLE_DOMAIN_BDC" or serverrole == "ROLE_DOMAIN_PDC":
518             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.")
519         else:
520             realm = domainname.upper()
521             logger.warning("No realm specified in smb.conf file, assuming '%s'",
522                     realm)
523
524     # Find machine account and password
525     next_rid = 1000
526
527     try:
528         machinepass = secrets_db.get_machine_password(netbiosname)
529     except KeyError:
530         machinepass = None
531
532     # We must close the direct pytdb database before the C code loads it
533     secrets_db.close()
534
535     # Connect to old password backend
536     passdb.set_secrets_dir(samba3.lp.get("private dir"))
537     s3db = samba3.get_sam_db()
538
539     # Get domain sid
540     try:
541         domainsid = passdb.get_global_sam_sid()
542     except passdb.error:
543         raise Exception("Can't find domain sid for '%s', Exiting." % domainname)
544
545     # Get machine account, sid, rid
546     try:
547         machineacct = s3db.getsampwnam('%s$' % netbiosname)
548     except passdb.error:
549         machinerid = None
550         machinesid = None
551     else:
552         machinesid, machinerid = machineacct.user_sid.split()
553
554     # Export account policy
555     logger.info("Exporting account policy")
556     policy = s3db.get_account_policy()
557
558     # Export groups from old passdb backend
559     logger.info("Exporting groups")
560     grouplist = s3db.enum_group_mapping()
561     groupmembers = {}
562     for group in grouplist:
563         sid, rid = group.sid.split()
564         if sid == domainsid:
565             if rid >= next_rid:
566                 next_rid = rid + 1
567
568         # Get members for each group/alias
569         if group.sid_name_use == lsa.SID_NAME_ALIAS:
570             try:
571                 members = s3db.enum_aliasmem(group.sid)
572                 groupmembers[str(group.sid)] = members
573             except passdb.error, e:
574                 logger.warn("Ignoring group '%s' %s listed but then not found: %s",
575                             group.nt_name, group.sid, e)
576                 continue
577         elif group.sid_name_use == lsa.SID_NAME_DOM_GRP:
578             try:
579                 members = s3db.enum_group_members(group.sid)
580                 groupmembers[str(group.sid)] = members
581             except passdb.error, e:
582                 logger.warn("Ignoring group '%s' %s listed but then not found: %s",
583                             group.nt_name, group.sid, e)
584                 continue
585         elif group.sid_name_use == lsa.SID_NAME_WKN_GRP:
586             (group_dom_sid, rid) = group.sid.split()
587             if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)):
588                 logger.warn("Ignoring 'well known' group '%s' (should already be in AD, and have no members)",
589                             group.nt_name)
590                 continue
591             # A number of buggy databases mix up well known groups and aliases.
592             try:
593                 members = s3db.enum_aliasmem(group.sid)
594                 groupmembers[str(group.sid)] = members
595             except passdb.error, e:
596                 logger.warn("Ignoring group '%s' %s listed but then not found: %s",
597                             group.nt_name, group.sid, e)
598                 continue
599         else:
600             logger.warn("Ignoring group '%s' %s with sid_name_use=%d",
601                         group.nt_name, group.sid, group.sid_name_use)
602             continue
603
604     # Export users from old passdb backend
605     logger.info("Exporting users")
606     userlist = s3db.search_users(0)
607     userdata = {}
608     uids = {}
609     admin_user = None
610     for entry in userlist:
611         if machinerid and machinerid == entry['rid']:
612             continue
613         username = entry['account_name']
614         if entry['rid'] < 1000:
615             logger.info("  Skipping wellknown rid=%d (for username=%s)", entry['rid'], username)
616             continue
617         if entry['rid'] >= next_rid:
618             next_rid = entry['rid'] + 1
619
620         user = s3db.getsampwnam(username)
621         acct_type = (user.acct_ctrl & (samr.ACB_NORMAL|samr.ACB_WSTRUST|samr.ACB_SVRTRUST|samr.ACB_DOMTRUST))
622         if (acct_type == samr.ACB_NORMAL or acct_type == samr.ACB_WSTRUST):
623             pass
624
625         elif acct_type == samr.ACB_SVRTRUST:
626             logger.warn("  Demoting BDC account trust for %s, this DC must be elevated to an AD DC using 'samba-tool domain promote'" % username[:-1])
627             user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_SVRTRUST) | samr.ACB_WSTRUST
628
629         elif acct_type == samr.ACB_DOMTRUST:
630             logger.warn("  Skipping inter-domain trust from domain %s, this trust must be re-created as an AD trust" % username[:-1])
631
632         elif acct_type == (samr.ACB_NORMAL|samr.ACB_WSTRUST) and username[-1] == '$':
633             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)
634             user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_NORMAL)
635
636         elif acct_type == (samr.ACB_NORMAL|samr.ACB_SVRTRUST) and username[-1] == '$':
637             logger.warn("  Fixing account %s which had both ACB_NORMAL (U) and ACB_SVRTRUST (S) set.  Account will be marked as ACB_WSTRUST (S), i.e. as a domain member" % username)
638             user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_NORMAL)
639
640         else:
641             raise ProvisioningError("""Failed to upgrade due to invalid account %s, account control flags 0x%08X must have exactly one of
642 ACB_NORMAL (N, 0x%08X), ACB_WSTRUST (W 0x%08X), ACB_SVRTRUST (S 0x%08X) or ACB_DOMTRUST (D 0x%08X).
643
644 Please fix this account before attempting to upgrade again
645 """
646                                     % (user.acct_flags, username,
647                                        samr.ACB_NORMAL, samr.ACB_WSTRUST, samr.ACB_SVRTRUST, samr.ACB_DOMTRUST))
648
649         userdata[username] = user
650         try:
651             uids[username] = s3db.sid_to_id(user.user_sid)[0]
652         except passdb.error:
653             try:
654                 uids[username] = pwd.getpwnam(username).pw_uid
655             except KeyError:
656                 pass
657
658         if not admin_user and username.lower() == 'root':
659             admin_user = username
660         if username.lower() == 'administrator':
661             admin_user = username
662
663         try:
664             group_memberships = s3db.enum_group_memberships(user);
665             for group in group_memberships:
666                 if str(group) in groupmembers:
667                     if user.user_sid not in groupmembers[str(group)]:
668                         groupmembers[str(group)].append(user.user_sid)
669                 else:
670                     groupmembers[str(group)] = [user.user_sid];
671         except passdb.error, e:
672             logger.warn("Ignoring group memberships of '%s' %s: %s",
673                         username, user.user_sid, e)
674
675
676     logger.info("Next rid = %d", next_rid)
677
678     # Check for same username/groupname
679     group_names = set([g.nt_name for g in grouplist])
680     user_names = set([u['account_name'] for u in userlist])
681     common_names = group_names.intersection(user_names)
682     if common_names:
683         logger.error("Following names are both user names and group names:")
684         for name in common_names:
685             logger.error("   %s" % name)
686         raise ProvisioningError("Please remove common user/group names before upgrade.")
687
688     # Check for same user sid/group sid
689     group_sids = set([str(g.sid) for g in grouplist])
690     if len(grouplist) != len(group_sids):
691         raise ProvisioningError("Please remove duplicate group sid entries before upgrade.")
692     user_sids = set(["%s-%u" % (domainsid, u['rid']) for u in userlist])
693     if len(userlist) != len(user_sids):
694         raise ProvisioningError("Please remove duplicate user sid entries before upgrade.")
695     common_sids = group_sids.intersection(user_sids)
696     if common_sids:
697         logger.error("Following sids are both user and group sids:")
698         for sid in common_sids:
699             logger.error("   %s" % str(sid))
700         raise ProvisioningError("Please remove duplicate sid entries before upgrade.")
701
702     if not (serverrole == "ROLE_DOMAIN_BDC" or serverrole == "ROLE_DOMAIN_PDC"):
703         dns_backend = "NONE"
704
705     # Do full provision
706     result = provision(logger, session_info, None,
707                        targetdir=targetdir, realm=realm, domain=domainname,
708                        domainsid=str(domainsid), next_rid=next_rid,
709                        dc_rid=machinerid,
710                        dom_for_fun_level=dsdb.DS_DOMAIN_FUNCTION_2003,
711                        hostname=netbiosname.lower(), machinepass=machinepass,
712                        serverrole=serverrole, samdb_fill=FILL_FULL,
713                        useeadb=useeadb, dns_backend=dns_backend, use_rfc2307=True)
714     result.report_logger(logger)
715
716     # Import WINS database
717     logger.info("Importing WINS database")
718
719     samba3_winsdb = None
720     try:
721         samba3_winsdb = samba3.get_wins_db()
722     except IOError, e:
723         logger.warn('Cannot open wins database, Ignoring: %s', str(e))
724
725     if samba3_winsdb:
726         import_wins(Ldb(result.paths.winsdb), samba3_winsdb)
727
728     # Set Account policy
729     logger.info("Importing Account policy")
730     import_sam_policy(result.samdb, policy, logger)
731
732     # Migrate IDMAP database
733     logger.info("Importing idmap database")
734     import_idmap(result.idmap, samba3, logger)
735
736     # Set the s3 context for samba4 configuration
737     new_lp_ctx = s3param.get_context()
738     new_lp_ctx.load(result.lp.configfile)
739     new_lp_ctx.set("private dir", result.lp.get("private dir"))
740     new_lp_ctx.set("state directory", result.lp.get("state directory"))
741     new_lp_ctx.set("lock directory", result.lp.get("lock directory"))
742
743     # Connect to samba4 backend
744     s4_passdb = passdb.PDB(new_lp_ctx.get("passdb backend"))
745
746     # Export groups to samba4 backend
747     logger.info("Importing groups")
748     for g in grouplist:
749         # Ignore uninitialized groups (gid = -1)
750         if g.gid != -1:
751             add_group_from_mapping_entry(result.samdb, g, logger)
752             add_ad_posix_idmap_entry(result.samdb, g.sid, g.gid, "ID_TYPE_GID", logger)
753
754     # Export users to samba4 backend
755     logger.info("Importing users")
756     for username in userdata:
757         if username.lower() == 'administrator':
758             if userdata[username].user_sid != dom_sid(str(domainsid) + "-500"):
759                 raise ProvisioningError("User 'Administrator' in your existing directory does not have SID ending in -500")
760         if username.lower() == 'root':
761             if userdata[username].user_sid == dom_sid(str(domainsid) + "-500"):
762                 logger.warn('User root has been replaced by Administrator')
763             else:
764                 logger.warn('User root has been kept in the directory, it should be removed in favour of the Administrator user')
765
766         s4_passdb.add_sam_account(userdata[username])
767         if username in uids:
768             add_ad_posix_idmap_entry(result.samdb, userdata[username].user_sid, uids[username], "ID_TYPE_UID", logger)
769
770     logger.info("Adding users to groups")
771     for g in grouplist:
772         if str(g.sid) in groupmembers:
773             add_users_to_group(result.samdb, g, groupmembers[str(g.sid)], logger)
774
775     # Set password for administrator
776     if admin_user:
777         logger.info("Setting password for administrator")
778         admin_userdata = s4_passdb.getsampwnam("administrator")
779         admin_userdata.nt_passwd = userdata[admin_user].nt_passwd
780         if userdata[admin_user].lanman_passwd:
781             admin_userdata.lanman_passwd = userdata[admin_user].lanman_passwd
782         admin_userdata.pass_last_set_time = userdata[admin_user].pass_last_set_time
783         if userdata[admin_user].pw_history:
784             admin_userdata.pw_history = userdata[admin_user].pw_history
785         s4_passdb.update_sam_account(admin_userdata)
786         logger.info("Administrator password has been set to password of user '%s'", admin_user)
787
788     # FIXME: import_registry(registry.Registry(), samba3.get_registry())
789     # FIXME: shares