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