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