98d8761045cc8d6f946393ada2c4217cf2bd2b5f
[samba.git] / 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, ProvisioningError, setsysvolacl
30 from samba.provision.common import FILL_FULL
31 from samba.samba3 import passdb
32 from samba.samba3 import param as s3param
33 from samba.dcerpc import lsa, samr, security
34 from samba.dcerpc.security import dom_sid
35 from samba.credentials import Credentials
36 from samba import dsdb
37 from samba.ndr import ndr_pack
38 from samba import unix2nttime
39 from samba import generate_random_password
40
41
42 def import_sam_policy(samdb, policy, logger):
43     """Import a Samba 3 policy.
44
45     :param samdb: Samba4 SAM database
46     :param policy: Samba3 account policy
47     :param logger: Logger object
48     """
49
50     # Following entries are used -
51     #    min password length, password history, minimum password age,
52     #    maximum password age, lockout duration
53     #
54     # Following entries are not used -
55     #    reset count minutes, user must logon to change password,
56     #    bad lockout minutes, disconnect time
57
58     m = ldb.Message()
59     m.dn = samdb.get_default_basedn()
60
61     if 'min password length' in policy:
62         m['a01'] = ldb.MessageElement(str(policy['min password length']),
63                                       ldb.FLAG_MOD_REPLACE, 'minPwdLength')
64
65     if 'password history' in policy:
66         m['a02'] = ldb.MessageElement(str(policy['password history']),
67                                       ldb.FLAG_MOD_REPLACE, 'pwdHistoryLength')
68
69     if 'minimum password age' in policy:
70         min_pw_age_unix = policy['minimum password age']
71         min_pw_age_nt = int(-min_pw_age_unix * (1e7))
72         m['a03'] = ldb.MessageElement(str(min_pw_age_nt), ldb.FLAG_MOD_REPLACE,
73                                       'minPwdAge')
74
75     if 'maximum password age' in policy:
76         max_pw_age_unix = policy['maximum password age']
77         if max_pw_age_unix == -1 or max_pw_age_unix == 0:
78             max_pw_age_nt = -0x8000000000000000
79         else:
80             max_pw_age_nt = int(-max_pw_age_unix * (1e7))
81
82         m['a04'] = ldb.MessageElement(str(max_pw_age_nt), ldb.FLAG_MOD_REPLACE,
83                                       'maxPwdAge')
84
85     if 'lockout duration' in policy:
86         lockout_duration_mins = policy['lockout duration']
87         lockout_duration_nt = unix2nttime(lockout_duration_mins * 60)
88
89         m['a05'] = ldb.MessageElement(str(lockout_duration_nt),
90                                       ldb.FLAG_MOD_REPLACE, 'lockoutDuration')
91
92     try:
93         samdb.modify(m)
94     except ldb.LdbError as e:
95         logger.warn("Could not set account policy, (%s)", str(e))
96
97
98 def add_posix_attrs(logger, samdb, sid, name, nisdomain, xid_type, home=None,
99                     shell=None, pgid=None):
100     """Add posix attributes for the user/group
101
102     :param samdb: Samba4 sam.ldb database
103     :param sid: user/group sid
104     :param sid: user/group name
105     :param nisdomain: name of the (fake) NIS domain
106     :param xid_type: type of id (ID_TYPE_UID/ID_TYPE_GID)
107     :param home: user homedir (Unix homepath)
108     :param shell: user shell
109     :param pgid: users primary group id
110     """
111
112     try:
113         m = ldb.Message()
114         m.dn = ldb.Dn(samdb, "<SID=%s>" % str(sid))
115         if xid_type == "ID_TYPE_UID":
116             m['unixHomeDirectory'] = ldb.MessageElement(
117                 str(home), ldb.FLAG_MOD_REPLACE, 'unixHomeDirectory')
118             m['loginShell'] = ldb.MessageElement(
119                 str(shell), ldb.FLAG_MOD_REPLACE, 'loginShell')
120             m['gidNumber'] = ldb.MessageElement(
121                 str(pgid), ldb.FLAG_MOD_REPLACE, 'gidNumber')
122
123         m['msSFU30NisDomain'] = ldb.MessageElement(
124             str(nisdomain), ldb.FLAG_MOD_REPLACE, 'msSFU30NisDomain')
125
126         samdb.modify(m)
127     except ldb.LdbError as e:
128         logger.warn(
129             'Could not add posix attrs for AD entry for sid=%s, (%s)',
130             str(sid), str(e))
131
132
133 def add_ad_posix_idmap_entry(samdb, sid, xid, xid_type, logger):
134     """Create idmap entry
135
136     :param samdb: Samba4 sam.ldb database
137     :param sid: user/group sid
138     :param xid: user/group id
139     :param xid_type: type of id (ID_TYPE_UID/ID_TYPE_GID)
140     :param logger: Logger object
141     """
142
143     try:
144         m = ldb.Message()
145         m.dn = ldb.Dn(samdb, "<SID=%s>" % str(sid))
146         if xid_type == "ID_TYPE_UID":
147             m['uidNumber'] = ldb.MessageElement(
148                 str(xid), ldb.FLAG_MOD_REPLACE, 'uidNumber')
149             m['objectClass'] = ldb.MessageElement(
150                 "posixAccount", ldb.FLAG_MOD_ADD, 'objectClass')
151         elif xid_type == "ID_TYPE_GID":
152             m['gidNumber'] = ldb.MessageElement(
153                 str(xid), ldb.FLAG_MOD_REPLACE, 'gidNumber')
154             m['objectClass'] = ldb.MessageElement(
155                 "posixGroup", ldb.FLAG_MOD_ADD, 'objectClass')
156
157         samdb.modify(m)
158     except ldb.LdbError as e:
159         logger.warn(
160             'Could not modify AD idmap entry for sid=%s, id=%s, type=%s (%s)',
161             str(sid), str(xid), xid_type, str(e))
162
163
164 def add_idmap_entry(idmapdb, sid, xid, xid_type, logger):
165     """Create idmap entry
166
167     :param idmapdb: Samba4 IDMAP database
168     :param sid: user/group sid
169     :param xid: user/group id
170     :param xid_type: type of id (ID_TYPE_UID/ID_TYPE_GID)
171     :param logger: Logger object
172     """
173
174     # First try to see if we already have this entry
175     found = False
176     msg = idmapdb.search(expression='objectSid=%s' % str(sid))
177     if msg.count == 1:
178         found = True
179
180     if found:
181         try:
182             m = ldb.Message()
183             m.dn = msg[0]['dn']
184             m['xidNumber'] = ldb.MessageElement(
185                 str(xid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
186             m['type'] = ldb.MessageElement(
187                 xid_type, ldb.FLAG_MOD_REPLACE, 'type')
188             idmapdb.modify(m)
189         except ldb.LdbError as e:
190             logger.warn(
191                 'Could not modify idmap entry for sid=%s, id=%s, type=%s (%s)',
192                 str(sid), str(xid), xid_type, str(e))
193     else:
194         try:
195             idmapdb.add({"dn": "CN=%s" % str(sid),
196                          "cn": str(sid),
197                          "objectClass": "sidMap",
198                          "objectSid": ndr_pack(sid),
199                          "type": xid_type,
200                          "xidNumber": str(xid)})
201         except ldb.LdbError as e:
202             logger.warn(
203                 'Could not add idmap entry for sid=%s, id=%s, type=%s (%s)',
204                 str(sid), str(xid), xid_type, str(e))
205
206
207 def import_idmap(idmapdb, samba3, logger):
208     """Import idmap data.
209
210     :param idmapdb: Samba4 IDMAP database
211     :param samba3_idmap: Samba3 IDMAP database to import from
212     :param logger: Logger object
213     """
214
215     try:
216         samba3_idmap = samba3.get_idmap_db()
217     except IOError as e:
218         logger.warn('Cannot open idmap database, Ignoring: %s', str(e))
219         return
220
221     currentxid = max(samba3_idmap.get_user_hwm(), samba3_idmap.get_group_hwm())
222     lowerbound = currentxid
223     # FIXME: upperbound
224
225     m = ldb.Message()
226     m.dn = ldb.Dn(idmapdb, 'CN=CONFIG')
227     m['lowerbound'] = ldb.MessageElement(
228         str(lowerbound), ldb.FLAG_MOD_REPLACE, 'lowerBound')
229     m['xidNumber'] = ldb.MessageElement(
230         str(currentxid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
231     idmapdb.modify(m)
232
233     for id_type, xid in samba3_idmap.ids():
234         if id_type == 'UID':
235             xid_type = 'ID_TYPE_UID'
236         elif id_type == 'GID':
237             xid_type = 'ID_TYPE_GID'
238         else:
239             logger.warn('Wrong type of entry in idmap (%s), Ignoring', id_type)
240             continue
241
242         sid = samba3_idmap.get_sid(xid, id_type)
243         add_idmap_entry(idmapdb, dom_sid(sid), xid, xid_type, logger)
244
245
246 def add_group_from_mapping_entry(samdb, groupmap, logger):
247     """Add or modify group from group mapping entry
248
249     param samdb: Samba4 SAM database
250     param groupmap: Groupmap entry
251     param logger: Logger object
252     """
253
254     # First try to see if we already have this entry
255     try:
256         msg = samdb.search(
257             base='<SID=%s>' % str(groupmap.sid), scope=ldb.SCOPE_BASE)
258         found = True
259     except ldb.LdbError as e1:
260         (ecode, emsg) = e1.args
261         if ecode == ldb.ERR_NO_SUCH_OBJECT:
262             found = False
263         else:
264             raise ldb.LdbError(ecode, emsg)
265
266     if found:
267         logger.warn('Group already exists sid=%s, groupname=%s existing_groupname=%s, Ignoring.',
268                     str(groupmap.sid), groupmap.nt_name, msg[0]['sAMAccountName'][0])
269     else:
270         if groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
271             # In a lot of Samba3 databases, aliases are marked as well known groups
272             (group_dom_sid, rid) = groupmap.sid.split()
273             if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)):
274                 return
275
276         m = ldb.Message()
277         # We avoid using the format string to avoid needing to escape the CN values
278         m.dn = ldb.Dn(samdb, "CN=X,CN=Users")
279         m.dn.set_component(0, "CN", groupmap.nt_name)
280         m.dn.add_base(samdb.get_default_basedn())
281         m['objectClass'] = ldb.MessageElement('group', ldb.FLAG_MOD_ADD, 'objectClass')
282         m['objectSid'] = ldb.MessageElement(ndr_pack(groupmap.sid), ldb.FLAG_MOD_ADD,
283                                             'objectSid')
284         m['sAMAccountName'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD,
285                                                  'sAMAccountName')
286
287         if groupmap.comment:
288             m['description'] = ldb.MessageElement(groupmap.comment, ldb.FLAG_MOD_ADD,
289                                                   'description')
290
291         # Fix up incorrect 'well known' groups that are actually builtin (per test above) to be aliases
292         if groupmap.sid_name_use == lsa.SID_NAME_ALIAS or groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
293             m['groupType'] = ldb.MessageElement(str(dsdb.GTYPE_SECURITY_DOMAIN_LOCAL_GROUP),
294                                                 ldb.FLAG_MOD_ADD, 'groupType')
295
296         try:
297             samdb.add(m, controls=["relax:0"])
298         except ldb.LdbError as e:
299             logger.warn('Could not add group name=%s (%s)', groupmap.nt_name, str(e))
300
301
302 def add_users_to_group(samdb, group, members, logger):
303     """Add user/member to group/alias
304
305     param samdb: Samba4 SAM database
306     param group: Groupmap object
307     param members: List of member SIDs
308     param logger: Logger object
309     """
310     for member_sid in members:
311         m = ldb.Message()
312         m.dn = ldb.Dn(samdb, "<SID=%s>" % str(group.sid))
313         m['a01'] = ldb.MessageElement("<SID=%s>" % str(member_sid), ldb.FLAG_MOD_ADD, 'member')
314
315         try:
316             samdb.modify(m)
317         except ldb.LdbError as e:
318             (ecode, emsg) = e.args
319             if ecode == ldb.ERR_ENTRY_ALREADY_EXISTS:
320                 logger.debug("skipped re-adding member '%s' to group '%s': %s", member_sid, group.sid, emsg)
321             elif ecode == ldb.ERR_NO_SUCH_OBJECT:
322                 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))
323             else:
324                 raise ProvisioningError("Could not add member '%s' to group '%s': %s" % (member_sid, group.sid, emsg))
325
326
327 def import_wins(samba4_winsdb, samba3_winsdb):
328     """Import settings from a Samba3 WINS database.
329
330     :param samba4_winsdb: WINS database to import to
331     :param samba3_winsdb: WINS database to import from
332     """
333
334     version_id = 0
335
336     for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items():
337         version_id += 1
338
339         type = int(name.split("#", 1)[1], 16)
340
341         if type == 0x1C:
342             rType = 0x2
343         elif type & 0x80:
344             if len(ips) > 1:
345                 rType = 0x2
346             else:
347                 rType = 0x1
348         else:
349             if len(ips) > 1:
350                 rType = 0x3
351             else:
352                 rType = 0x0
353
354         if ttl > time.time():
355             rState = 0x0  # active
356         else:
357             rState = 0x1  # released
358
359         nType = ((nb_flags & 0x60) >> 5)
360
361         samba4_winsdb.add({"dn": "name=%s,type=0x%s" % tuple(name.split("#")),
362                            "type": name.split("#")[1],
363                            "name": name.split("#")[0],
364                            "objectClass": "winsRecord",
365                            "recordType": str(rType),
366                            "recordState": str(rState),
367                            "nodeType": str(nType),
368                            "expireTime": ldb.timestring(ttl),
369                            "isStatic": "0",
370                            "versionID": str(version_id),
371                            "address": ips})
372
373     samba4_winsdb.add({"dn": "cn=VERSION",
374                        "cn": "VERSION",
375                        "objectClass": "winsMaxVersion",
376                        "maxVersion": str(version_id)})
377
378
379 SAMBA3_PREDEF_NAMES = {
380         'HKLM': registry.HKEY_LOCAL_MACHINE,
381 }
382
383
384 def import_registry(samba4_registry, samba3_regdb):
385     """Import a Samba 3 registry database into the Samba 4 registry.
386
387     :param samba4_registry: Samba 4 registry handle.
388     :param samba3_regdb: Samba 3 registry database handle.
389     """
390     def ensure_key_exists(keypath):
391         (predef_name, keypath) = keypath.split("/", 1)
392         predef_id = SAMBA3_PREDEF_NAMES[predef_name]
393         keypath = keypath.replace("/", "\\")
394         return samba4_registry.create_key(predef_id, keypath)
395
396     for key in samba3_regdb.keys():
397         key_handle = ensure_key_exists(key)
398         for subkey in samba3_regdb.subkeys(key):
399             ensure_key_exists(subkey)
400         for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items():
401             key_handle.set_value(value_name, value_type, value_data)
402
403
404 def get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, user, attr):
405     """Get posix attributes from a samba3 ldap backend
406     :param ldbs: a list of ldb connection objects
407     :param base_dn: the base_dn of the connection
408     :param user: the user to get the attribute for
409     :param attr: the attribute to be retrieved
410     """
411     try:
412         msg = ldb_object.search(base_dn, scope=ldb.SCOPE_SUBTREE,
413                                 expression=("(&(objectClass=posixAccount)(uid=%s))"
414                                             % (user)), attrs=[attr])
415     except ldb.LdbError as e:
416         raise ProvisioningError("Failed to retrieve attribute %s for user %s, the error is: %s" % (attr, user, e))
417     else:
418         if msg.count <= 1:
419             # This will raise KeyError (which is what we want) if there isn't a entry for this user
420             return msg[0][attr][0]
421         else:
422             logger.warning("LDAP entry for user %s contains more than one %s", user, attr)
423             raise KeyError
424
425
426 def upgrade_from_samba3(samba3, logger, targetdir, session_info=None,
427                         useeadb=False, dns_backend=None, use_ntvfs=False):
428     """Upgrade from samba3 database to samba4 AD database
429
430     :param samba3: samba3 object
431     :param logger: Logger object
432     :param targetdir: samba4 database directory
433     :param session_info: Session information
434     """
435     serverrole = samba3.lp.server_role()
436
437     domainname = samba3.lp.get("workgroup")
438     realm = samba3.lp.get("realm")
439     netbiosname = samba3.lp.get("netbios name")
440
441     if samba3.lp.get("ldapsam:trusted") is None:
442         samba3.lp.set("ldapsam:trusted", "yes")
443
444     # secrets db
445     try:
446         secrets_db = samba3.get_secrets_db()
447     except IOError as e:
448         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)))
449
450     if not domainname:
451         domainname = secrets_db.domains()[0]
452         logger.warning("No workgroup specified in smb.conf file, assuming '%s'",
453                        domainname)
454
455     if not realm:
456         if serverrole == "ROLE_DOMAIN_BDC" or serverrole == "ROLE_DOMAIN_PDC":
457             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.")
458         else:
459             realm = domainname.upper()
460             logger.warning("No realm specified in smb.conf file, assuming '%s'",
461                            realm)
462
463     # Find machine account and password
464     next_rid = 1000
465
466     try:
467         machinepass = secrets_db.get_machine_password(netbiosname)
468     except KeyError:
469         machinepass = None
470
471     if samba3.lp.get("passdb backend").split(":")[0].strip() == "ldapsam":
472         base_dn = samba3.lp.get("ldap suffix")
473         ldapuser = samba3.lp.get("ldap admin dn")
474         ldappass = secrets_db.get_ldap_bind_pw(ldapuser)
475         if ldappass is None:
476             raise ProvisioningError("ldapsam passdb backend detected but no LDAP Bind PW found in secrets.tdb for user %s.  Please point this tool at the secrets.tdb that was used by the previous installation.")
477         ldappass = ldappass.strip('\x00')
478         ldap = True
479     else:
480         ldapuser = None
481         ldappass = None
482         ldap = False
483
484     # We must close the direct pytdb database before the C code loads it
485     secrets_db.close()
486
487     # Connect to old password backend
488     passdb.set_secrets_dir(samba3.lp.get("private dir"))
489     s3db = samba3.get_sam_db()
490
491     # Get domain sid
492     try:
493         domainsid = passdb.get_global_sam_sid()
494     except passdb.error:
495         raise Exception("Can't find domain sid for '%s', Exiting." % domainname)
496
497     # Get machine account, sid, rid
498     try:
499         machineacct = s3db.getsampwnam('%s$' % netbiosname)
500     except passdb.error:
501         machinerid = None
502         machinesid = None
503     else:
504         machinesid, machinerid = machineacct.user_sid.split()
505
506     # Export account policy
507     logger.info("Exporting account policy")
508     policy = s3db.get_account_policy()
509
510     # Export groups from old passdb backend
511     logger.info("Exporting groups")
512     grouplist = s3db.enum_group_mapping()
513     groupmembers = {}
514     for group in grouplist:
515         sid, rid = group.sid.split()
516         if sid == domainsid:
517             if rid >= next_rid:
518                 next_rid = rid + 1
519
520         # Get members for each group/alias
521         if group.sid_name_use == lsa.SID_NAME_ALIAS:
522             try:
523                 members = s3db.enum_aliasmem(group.sid)
524                 groupmembers[str(group.sid)] = members
525             except passdb.error as e:
526                 logger.warn("Ignoring group '%s' %s listed but then not found: %s",
527                             group.nt_name, group.sid, e)
528                 continue
529         elif group.sid_name_use == lsa.SID_NAME_DOM_GRP:
530             try:
531                 members = s3db.enum_group_members(group.sid)
532                 groupmembers[str(group.sid)] = members
533             except passdb.error as e:
534                 logger.warn("Ignoring group '%s' %s listed but then not found: %s",
535                             group.nt_name, group.sid, e)
536                 continue
537         elif group.sid_name_use == lsa.SID_NAME_WKN_GRP:
538             (group_dom_sid, rid) = group.sid.split()
539             if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)):
540                 logger.warn("Ignoring 'well known' group '%s' (should already be in AD, and have no members)",
541                             group.nt_name)
542                 continue
543             # A number of buggy databases mix up well known groups and aliases.
544             try:
545                 members = s3db.enum_aliasmem(group.sid)
546                 groupmembers[str(group.sid)] = members
547             except passdb.error as e:
548                 logger.warn("Ignoring group '%s' %s listed but then not found: %s",
549                             group.nt_name, group.sid, e)
550                 continue
551         else:
552             logger.warn("Ignoring group '%s' %s with sid_name_use=%d",
553                         group.nt_name, group.sid, 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_SVRTRUST:
575             logger.warn("  Demoting BDC account trust for %s, this DC must be elevated to an AD DC using 'samba-tool domain dcpromo'" % username[:-1])
576             user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_SVRTRUST) | samr.ACB_WSTRUST
577
578         elif acct_type == samr.ACB_DOMTRUST:
579             logger.warn("  Skipping inter-domain trust from domain %s, this trust must be re-created as an AD trust" % username[:-1])
580             continue
581
582         elif acct_type == (samr.ACB_WSTRUST) and username[-1] != '$':
583             logger.warn("  Skipping account %s that has ACB_WSTRUST (W) set but does not end in $.  This account can not have worked, and is probably left over from a misconfiguration." % username)
584             continue
585
586         elif acct_type == (samr.ACB_NORMAL |samr.ACB_WSTRUST) and username[-1] == '$':
587             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)
588             user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_NORMAL)
589
590         elif acct_type == (samr.ACB_NORMAL |samr.ACB_SVRTRUST) and username[-1] == '$':
591             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)
592             user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_NORMAL)
593
594         elif acct_type == 0 and username[-1] != '$':
595             user.acct_ctrl = (user.acct_ctrl | samr.ACB_NORMAL)
596
597         elif (acct_type == samr.ACB_NORMAL or acct_type == samr.ACB_WSTRUST):
598             pass
599
600         else:
601             raise ProvisioningError("""Failed to upgrade due to invalid account %s, account control flags 0x%08X must have exactly one of
602 ACB_NORMAL (N, 0x%08X), ACB_WSTRUST (W 0x%08X), ACB_SVRTRUST (S 0x%08X) or ACB_DOMTRUST (D 0x%08X).
603
604 Please fix this account before attempting to upgrade again
605 """
606                                     % (username, user.acct_ctrl,
607                                        samr.ACB_NORMAL, samr.ACB_WSTRUST, samr.ACB_SVRTRUST, samr.ACB_DOMTRUST))
608
609         userdata[username] = user
610         try:
611             uids[username] = s3db.sid_to_id(user.user_sid)[0]
612         except passdb.error:
613             try:
614                 uids[username] = pwd.getpwnam(username).pw_uid
615             except KeyError:
616                 pass
617
618         if not admin_user and username.lower() == 'root':
619             admin_user = username
620         if username.lower() == 'administrator':
621             admin_user = username
622
623         try:
624             group_memberships = s3db.enum_group_memberships(user);
625             for group in group_memberships:
626                 if str(group) in groupmembers:
627                     if user.user_sid not in groupmembers[str(group)]:
628                         groupmembers[str(group)].append(user.user_sid)
629                 else:
630                     groupmembers[str(group)] = [user.user_sid];
631         except passdb.error as e:
632             logger.warn("Ignoring group memberships of '%s' %s: %s",
633                         username, user.user_sid, e)
634
635     logger.info("Next rid = %d", next_rid)
636
637     # Check for same username/groupname
638     group_names = set([g.nt_name for g in grouplist])
639     user_names = set([u['account_name'] for u in userlist])
640     common_names = group_names.intersection(user_names)
641     if common_names:
642         logger.error("Following names are both user names and group names:")
643         for name in common_names:
644             logger.error("   %s" % name)
645         raise ProvisioningError("Please remove common user/group names before upgrade.")
646
647     # Check for same user sid/group sid
648     group_sids = set([str(g.sid) for g in grouplist])
649     if len(grouplist) != len(group_sids):
650         raise ProvisioningError("Please remove duplicate group sid entries before upgrade.")
651     user_sids = set(["%s-%u" % (domainsid, u['rid']) for u in userlist])
652     if len(userlist) != len(user_sids):
653         raise ProvisioningError("Please remove duplicate user sid entries before upgrade.")
654     common_sids = group_sids.intersection(user_sids)
655     if common_sids:
656         logger.error("Following sids are both user and group sids:")
657         for sid in common_sids:
658             logger.error("   %s" % str(sid))
659         raise ProvisioningError("Please remove duplicate sid entries before upgrade.")
660
661     # Get posix attributes from ldap or the os
662     homes = {}
663     shells = {}
664     pgids = {}
665     if ldap:
666         creds = Credentials()
667         creds.guess(samba3.lp)
668         creds.set_bind_dn(ldapuser)
669         creds.set_password(ldappass)
670         urls = samba3.lp.get("passdb backend").split(":", 1)[1].strip('"')
671         for url in urls.split():
672             try:
673                 ldb_object = Ldb(url, credentials=creds)
674             except ldb.LdbError as e:
675                 raise ProvisioningError("Could not open ldb connection to %s, the error message is: %s" % (url, e))
676             else:
677                 break
678     logger.info("Exporting posix attributes")
679     userlist = s3db.search_users(0)
680     for entry in userlist:
681         username = entry['account_name']
682         if username in uids.keys():
683             try:
684                 if ldap:
685                     homes[username] = get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, username, "homeDirectory")
686                 else:
687                     homes[username] = pwd.getpwnam(username).pw_dir
688             except KeyError:
689                 pass
690             except IndexError:
691                 pass
692
693             try:
694                 if ldap:
695                     shells[username] = get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, username, "loginShell")
696                 else:
697                     shells[username] = pwd.getpwnam(username).pw_shell
698             except KeyError:
699                 pass
700             except IndexError:
701                 pass
702
703             try:
704                 if ldap:
705                     pgids[username] = get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, username, "gidNumber")
706                 else:
707                     pgids[username] = pwd.getpwnam(username).pw_gid
708             except KeyError:
709                 pass
710             except IndexError:
711                 pass
712
713     logger.info("Reading WINS database")
714     samba3_winsdb = None
715     try:
716         samba3_winsdb = samba3.get_wins_db()
717     except IOError as e:
718         logger.warn('Cannot open wins database, Ignoring: %s', str(e))
719
720     if not (serverrole == "ROLE_DOMAIN_BDC" or serverrole == "ROLE_DOMAIN_PDC"):
721         dns_backend = "NONE"
722
723     # If we found an admin user, set a fake pw that we will override.
724     # This avoids us printing out an admin password that we won't actually
725     # set.
726     if admin_user:
727         adminpass = generate_random_password(12, 32)
728     else:
729         adminpass = None
730
731     # Do full provision
732     result = provision(logger, session_info,
733                        targetdir=targetdir, realm=realm, domain=domainname,
734                        domainsid=domainsid, next_rid=next_rid,
735                        dc_rid=machinerid, adminpass=adminpass,
736                        dom_for_fun_level=dsdb.DS_DOMAIN_FUNCTION_2003,
737                        hostname=netbiosname.lower(), machinepass=machinepass,
738                        serverrole=serverrole, samdb_fill=FILL_FULL,
739                        useeadb=useeadb, dns_backend=dns_backend, use_rfc2307=True,
740                        use_ntvfs=use_ntvfs, skip_sysvolacl=True)
741     result.report_logger(logger)
742
743     # Import WINS database
744     logger.info("Importing WINS database")
745
746     if samba3_winsdb:
747         import_wins(Ldb(result.paths.winsdb), samba3_winsdb)
748
749     # Set Account policy
750     logger.info("Importing Account policy")
751     import_sam_policy(result.samdb, policy, logger)
752
753     # Migrate IDMAP database
754     logger.info("Importing idmap database")
755     import_idmap(result.idmap, samba3, logger)
756
757     # Set the s3 context for samba4 configuration
758     new_lp_ctx = s3param.get_context()
759     new_lp_ctx.load(result.lp.configfile)
760     new_lp_ctx.set("private dir", result.lp.get("private dir"))
761     new_lp_ctx.set("state directory", result.lp.get("state directory"))
762     new_lp_ctx.set("lock directory", result.lp.get("lock directory"))
763
764     # Connect to samba4 backend
765     s4_passdb = passdb.PDB(new_lp_ctx.get("passdb backend"))
766
767     # Start a new transaction (should speed this up a little, due to index churn)
768     result.samdb.transaction_start()
769
770     logger.info("Adding groups")
771     try:
772         # Export groups to samba4 backend
773         logger.info("Importing groups")
774         for g in grouplist:
775             # Ignore uninitialized groups (gid = -1)
776             if g.gid != -1:
777                 add_group_from_mapping_entry(result.samdb, g, logger)
778                 add_ad_posix_idmap_entry(result.samdb, g.sid, g.gid, "ID_TYPE_GID", logger)
779                 add_posix_attrs(samdb=result.samdb, sid=g.sid, name=g.nt_name, nisdomain=domainname.lower(), xid_type="ID_TYPE_GID", logger=logger)
780
781     except:
782         # We need this, so that we do not give even more errors due to not cancelling the transaction
783         result.samdb.transaction_cancel()
784         raise
785
786     logger.info("Committing 'add groups' transaction to disk")
787     result.samdb.transaction_commit()
788
789     logger.info("Adding users")
790
791     # Export users to samba4 backend
792     logger.info("Importing users")
793     for username in userdata:
794         if username.lower() == 'administrator':
795             if userdata[username].user_sid != dom_sid(str(domainsid) + "-500"):
796                 logger.error("User 'Administrator' in your existing directory has SID %s, expected it to be %s" % (userdata[username].user_sid, dom_sid(str(domainsid) + "-500")))
797                 raise ProvisioningError("User 'Administrator' in your existing directory does not have SID ending in -500")
798         if username.lower() == 'root':
799             if userdata[username].user_sid == dom_sid(str(domainsid) + "-500"):
800                 logger.warn('User root has been replaced by Administrator')
801             else:
802                 logger.warn('User root has been kept in the directory, it should be removed in favour of the Administrator user')
803
804         s4_passdb.add_sam_account(userdata[username])
805         if username in uids:
806             add_ad_posix_idmap_entry(result.samdb, userdata[username].user_sid, uids[username], "ID_TYPE_UID", logger)
807             if (username in homes) and (homes[username] is not None) and \
808                (username in shells) and (shells[username] is not None) and \
809                (username in pgids) and (pgids[username] is not None):
810                 add_posix_attrs(samdb=result.samdb, sid=userdata[username].user_sid, name=username, nisdomain=domainname.lower(), xid_type="ID_TYPE_UID", home=homes[username], shell=shells[username], pgid=pgids[username], logger=logger)
811
812
813     logger.info("Adding users to groups")
814     # Start a new transaction (should speed this up a little, due to index churn)
815     result.samdb.transaction_start()
816
817     try:
818         for g in grouplist:
819             if str(g.sid) in groupmembers:
820                 add_users_to_group(result.samdb, g, groupmembers[str(g.sid)], logger)
821
822     except:
823         # We need this, so that we do not give even more errors due to not cancelling the transaction
824         result.samdb.transaction_cancel()
825         raise
826
827     logger.info("Committing 'add users to groups' transaction to disk")
828     result.samdb.transaction_commit()
829
830     # Set password for administrator
831     if admin_user:
832         logger.info("Setting password for administrator")
833         admin_userdata = s4_passdb.getsampwnam("administrator")
834         admin_userdata.nt_passwd = userdata[admin_user].nt_passwd
835         if userdata[admin_user].lanman_passwd:
836             admin_userdata.lanman_passwd = userdata[admin_user].lanman_passwd
837         admin_userdata.pass_last_set_time = userdata[admin_user].pass_last_set_time
838         if userdata[admin_user].pw_history:
839             admin_userdata.pw_history = userdata[admin_user].pw_history
840         s4_passdb.update_sam_account(admin_userdata)
841         logger.info("Administrator password has been set to password of user '%s'", admin_user)
842
843     if result.server_role == "active directory domain controller":
844         setsysvolacl(result.samdb, result.paths.netlogon, result.paths.sysvol,
845                      result.paths.root_uid, result.paths.root_gid,
846                      security.dom_sid(result.domainsid), result.names.dnsdomain,
847                      result.names.domaindn, result.lp, use_ntvfs)
848
849     # FIXME: import_registry(registry.Registry(), samba3.get_registry())
850     # FIXME: shares