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