provision: No longer use the wheel group in new AD Domains
[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         logger.warning("Failed to retrieve attribute %s for user %s, the error is: %s", attr, user, e)
543     else:
544         if msg.count == 1:
545             return msg[0][attr][0]
546         else:
547             logger.warning("LDAP entry for user %s contains more than one %s", user, attr)
548             return None
549
550
551 def upgrade_from_samba3(samba3, logger, targetdir, session_info=None,
552         useeadb=False, dns_backend=None, use_ntvfs=False):
553     """Upgrade from samba3 database to samba4 AD database
554
555     :param samba3: samba3 object
556     :param logger: Logger object
557     :param targetdir: samba4 database directory
558     :param session_info: Session information
559     """
560     serverrole = samba3.lp.server_role()
561
562     domainname = samba3.lp.get("workgroup")
563     realm = samba3.lp.get("realm")
564     netbiosname = samba3.lp.get("netbios name")
565
566     if samba3.lp.get("ldapsam:trusted") is None:
567         samba3.lp.set("ldapsam:trusted", "yes")
568
569     # secrets db
570     try:
571         secrets_db = samba3.get_secrets_db()
572     except IOError, e:
573         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)))
574
575     if not domainname:
576         domainname = secrets_db.domains()[0]
577         logger.warning("No workgroup specified in smb.conf file, assuming '%s'",
578                 domainname)
579
580     if not realm:
581         if serverrole == "ROLE_DOMAIN_BDC" or serverrole == "ROLE_DOMAIN_PDC":
582             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.")
583         else:
584             realm = domainname.upper()
585             logger.warning("No realm specified in smb.conf file, assuming '%s'",
586                     realm)
587
588     # Find machine account and password
589     next_rid = 1000
590
591     try:
592         machinepass = secrets_db.get_machine_password(netbiosname)
593     except KeyError:
594         machinepass = None
595
596     if samba3.lp.get("passdb backend").split(":")[0].strip() == "ldapsam":
597         base_dn =  samba3.lp.get("ldap suffix")
598         ldapuser = samba3.lp.get("ldap admin dn")
599         ldappass = (secrets_db.get_ldap_bind_pw(ldapuser)).strip('\x00')
600         ldap = True
601     else:
602         ldapuser = None
603         ldappass = None
604         ldap = False
605
606     # We must close the direct pytdb database before the C code loads it
607     secrets_db.close()
608
609     # Connect to old password backend
610     passdb.set_secrets_dir(samba3.lp.get("private dir"))
611     s3db = samba3.get_sam_db()
612
613     # Get domain sid
614     try:
615         domainsid = passdb.get_global_sam_sid()
616     except passdb.error:
617         raise Exception("Can't find domain sid for '%s', Exiting." % domainname)
618
619     # Get machine account, sid, rid
620     try:
621         machineacct = s3db.getsampwnam('%s$' % netbiosname)
622     except passdb.error:
623         machinerid = None
624         machinesid = None
625     else:
626         machinesid, machinerid = machineacct.user_sid.split()
627
628     # Export account policy
629     logger.info("Exporting account policy")
630     policy = s3db.get_account_policy()
631
632     # Export groups from old passdb backend
633     logger.info("Exporting groups")
634     grouplist = s3db.enum_group_mapping()
635     groupmembers = {}
636     for group in grouplist:
637         sid, rid = group.sid.split()
638         if sid == domainsid:
639             if rid >= next_rid:
640                 next_rid = rid + 1
641
642         # Get members for each group/alias
643         if group.sid_name_use == lsa.SID_NAME_ALIAS:
644             try:
645                 members = s3db.enum_aliasmem(group.sid)
646                 groupmembers[str(group.sid)] = members
647             except passdb.error, e:
648                 logger.warn("Ignoring group '%s' %s listed but then not found: %s",
649                             group.nt_name, group.sid, e)
650                 continue
651         elif group.sid_name_use == lsa.SID_NAME_DOM_GRP:
652             try:
653                 members = s3db.enum_group_members(group.sid)
654                 groupmembers[str(group.sid)] = members
655             except passdb.error, e:
656                 logger.warn("Ignoring group '%s' %s listed but then not found: %s",
657                             group.nt_name, group.sid, e)
658                 continue
659         elif group.sid_name_use == lsa.SID_NAME_WKN_GRP:
660             (group_dom_sid, rid) = group.sid.split()
661             if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)):
662                 logger.warn("Ignoring 'well known' group '%s' (should already be in AD, and have no members)",
663                             group.nt_name)
664                 continue
665             # A number of buggy databases mix up well known groups and aliases.
666             try:
667                 members = s3db.enum_aliasmem(group.sid)
668                 groupmembers[str(group.sid)] = members
669             except passdb.error, e:
670                 logger.warn("Ignoring group '%s' %s listed but then not found: %s",
671                             group.nt_name, group.sid, e)
672                 continue
673         else:
674             logger.warn("Ignoring group '%s' %s with sid_name_use=%d",
675                         group.nt_name, group.sid, group.sid_name_use)
676             continue
677
678     # Export users from old passdb backend
679     logger.info("Exporting users")
680     userlist = s3db.search_users(0)
681     userdata = {}
682     uids = {}
683     admin_user = None
684     for entry in userlist:
685         if machinerid and machinerid == entry['rid']:
686             continue
687         username = entry['account_name']
688         if entry['rid'] < 1000:
689             logger.info("  Skipping wellknown rid=%d (for username=%s)", entry['rid'], username)
690             continue
691         if entry['rid'] >= next_rid:
692             next_rid = entry['rid'] + 1
693
694         user = s3db.getsampwnam(username)
695         acct_type = (user.acct_ctrl & (samr.ACB_NORMAL|samr.ACB_WSTRUST|samr.ACB_SVRTRUST|samr.ACB_DOMTRUST))
696         if (acct_type == samr.ACB_NORMAL or acct_type == samr.ACB_WSTRUST):
697             pass
698
699         elif acct_type == samr.ACB_SVRTRUST:
700             logger.warn("  Demoting BDC account trust for %s, this DC must be elevated to an AD DC using 'samba-tool domain promote'" % username[:-1])
701             user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_SVRTRUST) | samr.ACB_WSTRUST
702
703         elif acct_type == samr.ACB_DOMTRUST:
704             logger.warn("  Skipping inter-domain trust from domain %s, this trust must be re-created as an AD trust" % username[:-1])
705
706         elif acct_type == (samr.ACB_NORMAL|samr.ACB_WSTRUST) and username[-1] == '$':
707             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)
708             user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_NORMAL)
709
710         elif acct_type == (samr.ACB_NORMAL|samr.ACB_SVRTRUST) and username[-1] == '$':
711             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)
712             user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_NORMAL)
713
714         else:
715             raise ProvisioningError("""Failed to upgrade due to invalid account %s, account control flags 0x%08X must have exactly one of
716 ACB_NORMAL (N, 0x%08X), ACB_WSTRUST (W 0x%08X), ACB_SVRTRUST (S 0x%08X) or ACB_DOMTRUST (D 0x%08X).
717
718 Please fix this account before attempting to upgrade again
719 """
720                                     % (user.acct_flags, username,
721                                        samr.ACB_NORMAL, samr.ACB_WSTRUST, samr.ACB_SVRTRUST, samr.ACB_DOMTRUST))
722
723         userdata[username] = user
724         try:
725             uids[username] = s3db.sid_to_id(user.user_sid)[0]
726         except passdb.error:
727             try:
728                 uids[username] = pwd.getpwnam(username).pw_uid
729             except KeyError:
730                 pass
731
732         if not admin_user and username.lower() == 'root':
733             admin_user = username
734         if username.lower() == 'administrator':
735             admin_user = username
736
737         try:
738             group_memberships = s3db.enum_group_memberships(user);
739             for group in group_memberships:
740                 if str(group) in groupmembers:
741                     if user.user_sid not in groupmembers[str(group)]:
742                         groupmembers[str(group)].append(user.user_sid)
743                 else:
744                     groupmembers[str(group)] = [user.user_sid];
745         except passdb.error, e:
746             logger.warn("Ignoring group memberships of '%s' %s: %s",
747                         username, user.user_sid, e)
748
749     logger.info("Next rid = %d", next_rid)
750
751     # Check for same username/groupname
752     group_names = set([g.nt_name for g in grouplist])
753     user_names = set([u['account_name'] for u in userlist])
754     common_names = group_names.intersection(user_names)
755     if common_names:
756         logger.error("Following names are both user names and group names:")
757         for name in common_names:
758             logger.error("   %s" % name)
759         raise ProvisioningError("Please remove common user/group names before upgrade.")
760
761     # Check for same user sid/group sid
762     group_sids = set([str(g.sid) for g in grouplist])
763     if len(grouplist) != len(group_sids):
764         raise ProvisioningError("Please remove duplicate group sid entries before upgrade.")
765     user_sids = set(["%s-%u" % (domainsid, u['rid']) for u in userlist])
766     if len(userlist) != len(user_sids):
767         raise ProvisioningError("Please remove duplicate user sid entries before upgrade.")
768     common_sids = group_sids.intersection(user_sids)
769     if common_sids:
770         logger.error("Following sids are both user and group sids:")
771         for sid in common_sids:
772             logger.error("   %s" % str(sid))
773         raise ProvisioningError("Please remove duplicate sid entries before upgrade.")
774
775     # Get posix attributes from ldap or the os
776     homes = {}
777     shells = {}
778     pgids = {}
779     if ldap:
780         creds = Credentials()
781         creds.guess(samba3.lp)
782         creds.set_bind_dn(ldapuser)
783         creds.set_password(ldappass)
784         urls = samba3.lp.get("passdb backend").split(":",1)[1].strip('"')
785         for url in urls.split():
786             try:
787                 ldb_object = Ldb(url, credentials=creds)
788             except ldb.LdbError, e:
789                 logger.warning("Could not open ldb connection to %s, the error message is: %s", url, e)
790             else:
791                 break
792     logger.info("Exporting posix attributes")
793     userlist = s3db.search_users(0)
794     for entry in userlist:
795         username = entry['account_name']
796         if username in uids.keys():
797             if ldap:
798                 homes[username] = get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, username, "homeDirectory")
799                 shells[username] = get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, username, "loginShell")
800                 pgids[username] = get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, username, "gidNumber")
801             else:
802                 try:
803                     homes[username] = pwd.getpwnam(username).pw_dir
804                 except KeyError:
805                     pass
806                 try:
807                     shells[username] = pwd.getpwnam(username).pw_shell
808                 except KeyError:
809                     pass
810                 try:
811                     pgids[username] = pwd.getpwnam(username).pw_gid
812                 except KeyError:
813                     pass
814
815     logger.info("Reading WINS database")
816     samba3_winsdb = None
817     try:
818         samba3_winsdb = samba3.get_wins_db()
819     except IOError, e:
820         logger.warn('Cannot open wins database, Ignoring: %s', str(e))
821
822     if not (serverrole == "ROLE_DOMAIN_BDC" or serverrole == "ROLE_DOMAIN_PDC"):
823         dns_backend = "NONE"
824
825     # Do full provision
826     result = provision(logger, session_info, None,
827                        targetdir=targetdir, realm=realm, domain=domainname,
828                        domainsid=str(domainsid), next_rid=next_rid,
829                        dc_rid=machinerid,
830                        dom_for_fun_level=dsdb.DS_DOMAIN_FUNCTION_2003,
831                        hostname=netbiosname.lower(), machinepass=machinepass,
832                        serverrole=serverrole, samdb_fill=FILL_FULL,
833                        useeadb=useeadb, dns_backend=dns_backend, use_rfc2307=True,
834                        use_ntvfs=use_ntvfs, skip_sysvolacl=True)
835     result.report_logger(logger)
836
837     # Import WINS database
838     logger.info("Importing WINS database")
839
840     if samba3_winsdb:
841         import_wins(Ldb(result.paths.winsdb), samba3_winsdb)
842
843     # Set Account policy
844     logger.info("Importing Account policy")
845     import_sam_policy(result.samdb, policy, logger)
846
847     # Migrate IDMAP database
848     logger.info("Importing idmap database")
849     import_idmap(result.idmap, samba3, logger)
850
851     # Set the s3 context for samba4 configuration
852     new_lp_ctx = s3param.get_context()
853     new_lp_ctx.load(result.lp.configfile)
854     new_lp_ctx.set("private dir", result.lp.get("private dir"))
855     new_lp_ctx.set("state directory", result.lp.get("state directory"))
856     new_lp_ctx.set("lock directory", result.lp.get("lock directory"))
857
858     # Connect to samba4 backend
859     s4_passdb = passdb.PDB(new_lp_ctx.get("passdb backend"))
860
861     # Export groups to samba4 backend
862     logger.info("Importing groups")
863     for g in grouplist:
864         # Ignore uninitialized groups (gid = -1)
865         if g.gid != -1:
866             add_group_from_mapping_entry(result.samdb, g, logger)
867             add_ad_posix_idmap_entry(result.samdb, g.sid, g.gid, "ID_TYPE_GID", logger)
868             add_posix_attrs(samdb=result.samdb, sid=g.sid, name=g.nt_name, nisdomain=domainname.lower(), xid_type="ID_TYPE_GID", logger=logger)
869
870     # Export users to samba4 backend
871     logger.info("Importing users")
872     for username in userdata:
873         if username.lower() == 'administrator':
874             if userdata[username].user_sid != dom_sid(str(domainsid) + "-500"):
875                 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")))
876                 raise ProvisioningError("User 'Administrator' in your existing directory does not have SID ending in -500")
877         if username.lower() == 'root':
878             if userdata[username].user_sid == dom_sid(str(domainsid) + "-500"):
879                 logger.warn('User root has been replaced by Administrator')
880             else:
881                 logger.warn('User root has been kept in the directory, it should be removed in favour of the Administrator user')
882
883         s4_passdb.add_sam_account(userdata[username])
884         if username in uids:
885             add_ad_posix_idmap_entry(result.samdb, userdata[username].user_sid, uids[username], "ID_TYPE_UID", logger)
886             if (username in homes) and (homes[username] is not None) and \
887                (username in shells) and (shells[username] is not None) and \
888                (username in pgids) and (pgids[username] is not None):
889                 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)
890
891     logger.info("Adding users to groups")
892     for g in grouplist:
893         if str(g.sid) in groupmembers:
894             add_users_to_group(result.samdb, g, groupmembers[str(g.sid)], logger)
895
896     # Set password for administrator
897     if admin_user:
898         logger.info("Setting password for administrator")
899         admin_userdata = s4_passdb.getsampwnam("administrator")
900         admin_userdata.nt_passwd = userdata[admin_user].nt_passwd
901         if userdata[admin_user].lanman_passwd:
902             admin_userdata.lanman_passwd = userdata[admin_user].lanman_passwd
903         admin_userdata.pass_last_set_time = userdata[admin_user].pass_last_set_time
904         if userdata[admin_user].pw_history:
905             admin_userdata.pw_history = userdata[admin_user].pw_history
906         s4_passdb.update_sam_account(admin_userdata)
907         logger.info("Administrator password has been set to password of user '%s'", admin_user)
908
909     if result.server_role == "active directory domain controller":
910         setsysvolacl(result.samdb, result.paths.netlogon, result.paths.sysvol,
911                 result.paths.root_uid, result.paths.root_gid,
912                 security.dom_sid(result.domainsid), result.names.dnsdomain,
913                 result.names.domaindn, result.lp, use_ntvfs)
914
915     # FIXME: import_registry(registry.Registry(), samba3.get_registry())
916     # FIXME: shares