mdb_util: Better error message if lmdb-utils not installed
[amitay/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 |
574                                        samr.ACB_WSTRUST |
575                                        samr.ACB_SVRTRUST |
576                                        samr.ACB_DOMTRUST))
577         if acct_type == samr.ACB_SVRTRUST:
578             logger.warn("  Demoting BDC account trust for %s, this DC must be elevated to an AD DC using 'samba-tool domain dcpromo'" % username[:-1])
579             user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_SVRTRUST) | samr.ACB_WSTRUST
580
581         elif acct_type == samr.ACB_DOMTRUST:
582             logger.warn("  Skipping inter-domain trust from domain %s, this trust must be re-created as an AD trust" % username[:-1])
583             continue
584
585         elif acct_type == (samr.ACB_WSTRUST) and username[-1] != '$':
586             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)
587             continue
588
589         elif acct_type == (samr.ACB_NORMAL | samr.ACB_WSTRUST) and username[-1] == '$':
590             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)
591             user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_NORMAL)
592
593         elif acct_type == (samr.ACB_NORMAL | samr.ACB_SVRTRUST) and username[-1] == '$':
594             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)
595             user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_NORMAL)
596
597         elif acct_type == 0 and username[-1] != '$':
598             user.acct_ctrl = (user.acct_ctrl | samr.ACB_NORMAL)
599
600         elif (acct_type == samr.ACB_NORMAL or acct_type == samr.ACB_WSTRUST):
601             pass
602
603         else:
604             raise ProvisioningError("""Failed to upgrade due to invalid account %s, account control flags 0x%08X must have exactly one of
605 ACB_NORMAL (N, 0x%08X), ACB_WSTRUST (W 0x%08X), ACB_SVRTRUST (S 0x%08X) or ACB_DOMTRUST (D 0x%08X).
606
607 Please fix this account before attempting to upgrade again
608 """
609                                     % (username, user.acct_ctrl,
610                                        samr.ACB_NORMAL, samr.ACB_WSTRUST, samr.ACB_SVRTRUST, samr.ACB_DOMTRUST))
611
612         userdata[username] = user
613         try:
614             uids[username] = s3db.sid_to_id(user.user_sid)[0]
615         except passdb.error:
616             try:
617                 uids[username] = pwd.getpwnam(username).pw_uid
618             except KeyError:
619                 pass
620
621         if not admin_user and username.lower() == 'root':
622             admin_user = username
623         if username.lower() == 'administrator':
624             admin_user = username
625
626         try:
627             group_memberships = s3db.enum_group_memberships(user)
628             for group in group_memberships:
629                 if str(group) in groupmembers:
630                     if user.user_sid not in groupmembers[str(group)]:
631                         groupmembers[str(group)].append(user.user_sid)
632                 else:
633                     groupmembers[str(group)] = [user.user_sid]
634         except passdb.error as e:
635             logger.warn("Ignoring group memberships of '%s' %s: %s",
636                         username, user.user_sid, e)
637
638     logger.info("Next rid = %d", next_rid)
639
640     # Check for same username/groupname
641     group_names = set([g.nt_name for g in grouplist])
642     user_names = set([u['account_name'] for u in userlist])
643     common_names = group_names.intersection(user_names)
644     if common_names:
645         logger.error("Following names are both user names and group names:")
646         for name in common_names:
647             logger.error("   %s" % name)
648         raise ProvisioningError("Please remove common user/group names before upgrade.")
649
650     # Check for same user sid/group sid
651     group_sids = set([str(g.sid) for g in grouplist])
652     if len(grouplist) != len(group_sids):
653         raise ProvisioningError("Please remove duplicate group sid entries before upgrade.")
654     user_sids = set(["%s-%u" % (domainsid, u['rid']) for u in userlist])
655     if len(userlist) != len(user_sids):
656         raise ProvisioningError("Please remove duplicate user sid entries before upgrade.")
657     common_sids = group_sids.intersection(user_sids)
658     if common_sids:
659         logger.error("Following sids are both user and group sids:")
660         for sid in common_sids:
661             logger.error("   %s" % str(sid))
662         raise ProvisioningError("Please remove duplicate sid entries before upgrade.")
663
664     # Get posix attributes from ldap or the os
665     homes = {}
666     shells = {}
667     pgids = {}
668     if ldap:
669         creds = Credentials()
670         creds.guess(samba3.lp)
671         creds.set_bind_dn(ldapuser)
672         creds.set_password(ldappass)
673         urls = samba3.lp.get("passdb backend").split(":", 1)[1].strip('"')
674         for url in urls.split():
675             try:
676                 ldb_object = Ldb(url, credentials=creds)
677             except ldb.LdbError as e:
678                 raise ProvisioningError("Could not open ldb connection to %s, the error message is: %s" % (url, e))
679             else:
680                 break
681     logger.info("Exporting posix attributes")
682     userlist = s3db.search_users(0)
683     for entry in userlist:
684         username = entry['account_name']
685         if username in uids.keys():
686             try:
687                 if ldap:
688                     homes[username] = get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, username, "homeDirectory")
689                 else:
690                     homes[username] = pwd.getpwnam(username).pw_dir
691             except KeyError:
692                 pass
693             except IndexError:
694                 pass
695
696             try:
697                 if ldap:
698                     shells[username] = get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, username, "loginShell")
699                 else:
700                     shells[username] = pwd.getpwnam(username).pw_shell
701             except KeyError:
702                 pass
703             except IndexError:
704                 pass
705
706             try:
707                 if ldap:
708                     pgids[username] = get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, username, "gidNumber")
709                 else:
710                     pgids[username] = pwd.getpwnam(username).pw_gid
711             except KeyError:
712                 pass
713             except IndexError:
714                 pass
715
716     logger.info("Reading WINS database")
717     samba3_winsdb = None
718     try:
719         samba3_winsdb = samba3.get_wins_db()
720     except IOError as e:
721         logger.warn('Cannot open wins database, Ignoring: %s', str(e))
722
723     if not (serverrole == "ROLE_DOMAIN_BDC" or serverrole == "ROLE_DOMAIN_PDC"):
724         dns_backend = "NONE"
725
726     # If we found an admin user, set a fake pw that we will override.
727     # This avoids us printing out an admin password that we won't actually
728     # set.
729     if admin_user:
730         adminpass = generate_random_password(12, 32)
731     else:
732         adminpass = None
733
734     # Do full provision
735     result = provision(logger, session_info,
736                        targetdir=targetdir, realm=realm, domain=domainname,
737                        domainsid=domainsid, next_rid=next_rid,
738                        dc_rid=machinerid, adminpass=adminpass,
739                        dom_for_fun_level=dsdb.DS_DOMAIN_FUNCTION_2003,
740                        hostname=netbiosname.lower(), machinepass=machinepass,
741                        serverrole=serverrole, samdb_fill=FILL_FULL,
742                        useeadb=useeadb, dns_backend=dns_backend, use_rfc2307=True,
743                        use_ntvfs=use_ntvfs, skip_sysvolacl=True)
744     result.report_logger(logger)
745
746     # Import WINS database
747     logger.info("Importing WINS database")
748
749     if samba3_winsdb:
750         import_wins(Ldb(result.paths.winsdb), samba3_winsdb)
751
752     # Set Account policy
753     logger.info("Importing Account policy")
754     import_sam_policy(result.samdb, policy, logger)
755
756     # Migrate IDMAP database
757     logger.info("Importing idmap database")
758     import_idmap(result.idmap, samba3, logger)
759
760     # Set the s3 context for samba4 configuration
761     new_lp_ctx = s3param.get_context()
762     new_lp_ctx.load(result.lp.configfile)
763     new_lp_ctx.set("private dir", result.lp.get("private dir"))
764     new_lp_ctx.set("state directory", result.lp.get("state directory"))
765     new_lp_ctx.set("lock directory", result.lp.get("lock directory"))
766
767     # Connect to samba4 backend
768     s4_passdb = passdb.PDB(new_lp_ctx.get("passdb backend"))
769
770     # Start a new transaction (should speed this up a little, due to index churn)
771     result.samdb.transaction_start()
772
773     logger.info("Adding groups")
774     try:
775         # Export groups to samba4 backend
776         logger.info("Importing groups")
777         for g in grouplist:
778             # Ignore uninitialized groups (gid = -1)
779             if g.gid != -1:
780                 add_group_from_mapping_entry(result.samdb, g, logger)
781                 add_ad_posix_idmap_entry(result.samdb, g.sid, g.gid, "ID_TYPE_GID", logger)
782                 add_posix_attrs(samdb=result.samdb, sid=g.sid, name=g.nt_name, nisdomain=domainname.lower(), xid_type="ID_TYPE_GID", logger=logger)
783
784     except:
785         # We need this, so that we do not give even more errors due to not cancelling the transaction
786         result.samdb.transaction_cancel()
787         raise
788
789     logger.info("Committing 'add groups' transaction to disk")
790     result.samdb.transaction_commit()
791
792     logger.info("Adding users")
793
794     # Export users to samba4 backend
795     logger.info("Importing users")
796     for username in userdata:
797         if username.lower() == 'administrator':
798             if userdata[username].user_sid != dom_sid(str(domainsid) + "-500"):
799                 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")))
800                 raise ProvisioningError("User 'Administrator' in your existing directory does not have SID ending in -500")
801         if username.lower() == 'root':
802             if userdata[username].user_sid == dom_sid(str(domainsid) + "-500"):
803                 logger.warn('User root has been replaced by Administrator')
804             else:
805                 logger.warn('User root has been kept in the directory, it should be removed in favour of the Administrator user')
806
807         s4_passdb.add_sam_account(userdata[username])
808         if username in uids:
809             add_ad_posix_idmap_entry(result.samdb, userdata[username].user_sid, uids[username], "ID_TYPE_UID", logger)
810             if (username in homes) and (homes[username] is not None) and \
811                (username in shells) and (shells[username] is not None) and \
812                (username in pgids) and (pgids[username] is not None):
813                 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)
814
815     logger.info("Adding users to groups")
816     # Start a new transaction (should speed this up a little, due to index churn)
817     result.samdb.transaction_start()
818
819     try:
820         for g in grouplist:
821             if str(g.sid) in groupmembers:
822                 add_users_to_group(result.samdb, g, groupmembers[str(g.sid)], logger)
823
824     except:
825         # We need this, so that we do not give even more errors due to not cancelling the transaction
826         result.samdb.transaction_cancel()
827         raise
828
829     logger.info("Committing 'add users to groups' transaction to disk")
830     result.samdb.transaction_commit()
831
832     # Set password for administrator
833     if admin_user:
834         logger.info("Setting password for administrator")
835         admin_userdata = s4_passdb.getsampwnam("administrator")
836         admin_userdata.nt_passwd = userdata[admin_user].nt_passwd
837         if userdata[admin_user].lanman_passwd:
838             admin_userdata.lanman_passwd = userdata[admin_user].lanman_passwd
839         admin_userdata.pass_last_set_time = userdata[admin_user].pass_last_set_time
840         if userdata[admin_user].pw_history:
841             admin_userdata.pw_history = userdata[admin_user].pw_history
842         s4_passdb.update_sam_account(admin_userdata)
843         logger.info("Administrator password has been set to password of user '%s'", admin_user)
844
845     if result.server_role == "active directory domain controller":
846         setsysvolacl(result.samdb, result.paths.netlogon, result.paths.sysvol,
847                      result.paths.root_uid, result.paths.root_gid,
848                      security.dom_sid(result.domainsid), result.names.dnsdomain,
849                      result.names.domaindn, result.lp, use_ntvfs)
850
851     # FIXME: import_registry(registry.Registry(), samba3.get_registry())
852     # FIXME: shares