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