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