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