1 # backend code for upgrading from Samba3
2 # Copyright Jelmer Vernooij 2005-2007
3 # Copyright Andrew Bartlett 2011
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.
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.
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/>.
19 """Support code for upgrading from Samba 3 to Samba 4."""
21 __docformat__
= "restructuredText"
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
import dsdb
35 from samba
.ndr
import ndr_pack
36 from samba
import unix2nttime
39 def import_sam_policy(samdb
, policy
, logger
):
40 """Import a Samba 3 policy.
42 :param samdb: Samba4 SAM database
43 :param policy: Samba3 account policy
44 :param logger: Logger object
47 # Following entries are used -
48 # min password length, password history, minimum password age,
49 # maximum password age, lockout duration
51 # Following entries are not used -
52 # reset count minutes, user must logon to change password,
53 # bad lockout minutes, disconnect time
56 m
.dn
= samdb
.get_default_basedn()
57 m
['a01'] = ldb
.MessageElement(str(policy
['min password length']),
58 ldb
.FLAG_MOD_REPLACE
, 'minPwdLength')
59 m
['a02'] = ldb
.MessageElement(str(policy
['password history']),
60 ldb
.FLAG_MOD_REPLACE
, 'pwdHistoryLength')
62 min_pw_age_unix
= policy
['minimum password age']
63 min_pw_age_nt
= int(-min_pw_age_unix
* (1e7
* 60 * 60 * 24))
64 m
['a03'] = ldb
.MessageElement(str(min_pw_age_nt
), ldb
.FLAG_MOD_REPLACE
,
67 max_pw_age_unix
= policy
['maximum password age']
68 if max_pw_age_unix
== -1:
69 max_pw_age_nt
= -0x8000000000000000
71 max_pw_age_nt
= int(-max_pw_age_unix
* (1e7
* 60 * 60 * 24))
73 m
['a04'] = ldb
.MessageElement(str(max_pw_age_nt
), ldb
.FLAG_MOD_REPLACE
,
76 lockout_duration_mins
= policy
['lockout duration']
77 lockout_duration_nt
= unix2nttime(lockout_duration_mins
* 60)
79 m
['a05'] = ldb
.MessageElement(str(lockout_duration_nt
),
80 ldb
.FLAG_MOD_REPLACE
, 'lockoutDuration')
84 except ldb
.LdbError
, e
:
85 logger
.warn("Could not set account policy, (%s)", str(e
))
88 def add_idmap_entry(idmapdb
, sid
, xid
, xid_type
, logger
):
91 :param idmapdb: Samba4 IDMAP database
92 :param sid: user/group sid
93 :param xid: user/group id
94 :param xid_type: type of id (ID_TYPE_UID/ID_TYPE_GID)
95 :param logger: Logger object
98 # First try to see if we already have this entry
100 msg
= idmapdb
.search(expression
='objectSid=%s' % str(sid
))
108 m
['xidNumber'] = ldb
.MessageElement(
109 str(xid
), ldb
.FLAG_MOD_REPLACE
, 'xidNumber')
110 m
['type'] = ldb
.MessageElement(
111 xid_type
, ldb
.FLAG_MOD_REPLACE
, 'type')
113 except ldb
.LdbError
, e
:
115 'Could not modify idmap entry for sid=%s, id=%s, type=%s (%s)',
116 str(sid
), str(xid
), xid_type
, str(e
))
119 idmapdb
.add({"dn": "CN=%s" % str(sid
),
121 "objectClass": "sidMap",
122 "objectSid": ndr_pack(sid
),
124 "xidNumber": str(xid
)})
125 except ldb
.LdbError
, e
:
127 'Could not add idmap entry for sid=%s, id=%s, type=%s (%s)',
128 str(sid
), str(xid
), xid_type
, str(e
))
131 def import_idmap(idmapdb
, samba3
, logger
):
132 """Import idmap data.
134 :param idmapdb: Samba4 IDMAP database
135 :param samba3_idmap: Samba3 IDMAP database to import from
136 :param logger: Logger object
140 samba3_idmap
= samba3
.get_idmap_db()
142 logger
.warn('Cannot open idmap database, Ignoring: %s', str(e
))
145 currentxid
= max(samba3_idmap
.get_user_hwm(), samba3_idmap
.get_group_hwm())
146 lowerbound
= currentxid
150 m
.dn
= ldb
.Dn(idmapdb
, 'CN=CONFIG')
151 m
['lowerbound'] = ldb
.MessageElement(
152 str(lowerbound
), ldb
.FLAG_MOD_REPLACE
, 'lowerBound')
153 m
['xidNumber'] = ldb
.MessageElement(
154 str(currentxid
), ldb
.FLAG_MOD_REPLACE
, 'xidNumber')
157 for id_type
, xid
in samba3_idmap
.ids():
159 xid_type
= 'ID_TYPE_UID'
160 elif id_type
== 'GID':
161 xid_type
= 'ID_TYPE_GID'
163 logger
.warn('Wrong type of entry in idmap (%s), Ignoring', id_type
)
166 sid
= samba3_idmap
.get_sid(xid
, id_type
)
167 add_idmap_entry(idmapdb
, dom_sid(sid
), xid
, xid_type
, logger
)
170 def add_group_from_mapping_entry(samdb
, groupmap
, logger
):
171 """Add or modify group from group mapping entry
173 param samdb: Samba4 SAM database
174 param groupmap: Groupmap entry
175 param logger: Logger object
178 # First try to see if we already have this entry
181 base
='<SID=%s>' % str(groupmap
.sid
), scope
=ldb
.SCOPE_BASE
)
183 except ldb
.LdbError
, (ecode
, emsg
):
184 if ecode
== ldb
.ERR_NO_SUCH_OBJECT
:
187 raise ldb
.LdbError(ecode
, emsg
)
190 logger
.warn('Group already exists sid=%s, groupname=%s existing_groupname=%s, Ignoring.',
191 str(groupmap
.sid
), groupmap
.nt_name
, msg
[0]['sAMAccountName'][0])
193 if groupmap
.sid_name_use
== lsa
.SID_NAME_WKN_GRP
:
194 # In a lot of Samba3 databases, aliases are marked as well known groups
195 (group_dom_sid
, rid
) = groupmap
.sid
.split()
196 if (group_dom_sid
!= security
.dom_sid(security
.SID_BUILTIN
)):
200 m
.dn
= ldb
.Dn(samdb
, "CN=%s,CN=Users,%s" % (groupmap
.nt_name
, samdb
.get_default_basedn()))
201 m
['cn'] = ldb
.MessageElement(groupmap
.nt_name
, ldb
.FLAG_MOD_ADD
, 'cn')
202 m
['objectClass'] = ldb
.MessageElement('group', ldb
.FLAG_MOD_ADD
, 'objectClass')
203 m
['objectSid'] = ldb
.MessageElement(ndr_pack(groupmap
.sid
), ldb
.FLAG_MOD_ADD
,
205 m
['sAMAccountName'] = ldb
.MessageElement(groupmap
.nt_name
, ldb
.FLAG_MOD_ADD
,
209 m
['description'] = ldb
.MessageElement(groupmap
.comment
, ldb
.FLAG_MOD_ADD
,
212 # Fix up incorrect 'well known' groups that are actually builtin (per test above) to be aliases
213 if groupmap
.sid_name_use
== lsa
.SID_NAME_ALIAS
or groupmap
.sid_name_use
== lsa
.SID_NAME_WKN_GRP
:
214 m
['groupType'] = ldb
.MessageElement(str(dsdb
.GTYPE_SECURITY_DOMAIN_LOCAL_GROUP
),
215 ldb
.FLAG_MOD_ADD
, 'groupType')
218 samdb
.add(m
, controls
=["relax:0"])
219 except ldb
.LdbError
, e
:
220 logger
.warn('Could not add group name=%s (%s)', groupmap
.nt_name
, str(e
))
223 def add_users_to_group(samdb
, group
, members
, logger
):
224 """Add user/member to group/alias
226 param samdb: Samba4 SAM database
227 param group: Groupmap object
228 param members: List of member SIDs
229 param logger: Logger object
231 for member_sid
in members
:
233 m
.dn
= ldb
.Dn(samdb
, "<SID=%s>" % str(group
.sid
))
234 m
['a01'] = ldb
.MessageElement("<SID=%s>" % str(member_sid
), ldb
.FLAG_MOD_ADD
, 'member')
238 except ldb
.LdbError
, (ecode
, emsg
):
239 if ecode
== ldb
.ERR_ENTRY_ALREADY_EXISTS
:
240 logger
.debug("skipped re-adding member '%s' to group '%s': %s", member_sid
, group
.sid
, emsg
)
241 elif ecode
== ldb
.ERR_NO_SUCH_OBJECT
:
242 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
))
244 raise ProvisioningError("Could not add member '%s' to group '%s': %s" % (member_sid
, group
.sid
, emsg
))
247 def import_wins(samba4_winsdb
, samba3_winsdb
):
248 """Import settings from a Samba3 WINS database.
250 :param samba4_winsdb: WINS database to import to
251 :param samba3_winsdb: WINS database to import from
256 for (name
, (ttl
, ips
, nb_flags
)) in samba3_winsdb
.items():
259 type = int(name
.split("#", 1)[1], 16)
274 if ttl
> time
.time():
275 rState
= 0x0 # active
277 rState
= 0x1 # released
279 nType
= ((nb_flags
& 0x60) >> 5)
281 samba4_winsdb
.add({"dn": "name=%s,type=0x%s" % tuple(name
.split("#")),
282 "type": name
.split("#")[1],
283 "name": name
.split("#")[0],
284 "objectClass": "winsRecord",
285 "recordType": str(rType
),
286 "recordState": str(rState
),
287 "nodeType": str(nType
),
288 "expireTime": ldb
.timestring(ttl
),
290 "versionID": str(version_id
),
293 samba4_winsdb
.add({"dn": "cn=VERSION",
295 "objectClass": "winsMaxVersion",
296 "maxVersion": str(version_id
)})
299 def enable_samba3sam(samdb
, ldapurl
):
300 """Enable Samba 3 LDAP URL database.
302 :param samdb: SAM Database.
303 :param ldapurl: Samba 3 LDAP URL
305 samdb
.modify_ldif("""
309 @LIST: samldb,operational,objectguid,rdn_name,samba3sam
312 samdb
.add({"dn": "@MAP=samba3sam", "@MAP_URL": ldapurl
})
329 "bind interfaces only",
334 "obey pam restrictions",
342 "client NTLMv2 auth",
343 "client lanman auth",
344 "client plaintext auth",
362 "name resolve order",
371 "paranoid server security",
408 def upgrade_smbconf(oldconf
, mark
):
409 """Remove configuration variables not present in Samba4
411 :param oldconf: Old configuration structure
412 :param mark: Whether removed configuration variables should be
413 kept in the new configuration as "samba3:<name>"
415 data
= oldconf
.data()
421 for k
in smbconf_keep
:
422 if smbconf_keep
[k
] == p
:
427 newconf
.set(s
, p
, oldconf
.get(s
, p
))
429 newconf
.set(s
, "samba3:" + p
, oldconf
.get(s
, p
))
433 SAMBA3_PREDEF_NAMES
= {
434 'HKLM': registry
.HKEY_LOCAL_MACHINE
,
438 def import_registry(samba4_registry
, samba3_regdb
):
439 """Import a Samba 3 registry database into the Samba 4 registry.
441 :param samba4_registry: Samba 4 registry handle.
442 :param samba3_regdb: Samba 3 registry database handle.
444 def ensure_key_exists(keypath
):
445 (predef_name
, keypath
) = keypath
.split("/", 1)
446 predef_id
= SAMBA3_PREDEF_NAMES
[predef_name
]
447 keypath
= keypath
.replace("/", "\\")
448 return samba4_registry
.create_key(predef_id
, keypath
)
450 for key
in samba3_regdb
.keys():
451 key_handle
= ensure_key_exists(key
)
452 for subkey
in samba3_regdb
.subkeys(key
):
453 ensure_key_exists(subkey
)
454 for (value_name
, (value_type
, value_data
)) in samba3_regdb
.values(key
).items():
455 key_handle
.set_value(value_name
, value_type
, value_data
)
458 def upgrade_from_samba3(samba3
, logger
, targetdir
, session_info
=None, useeadb
=False):
459 """Upgrade from samba3 database to samba4 AD database
461 :param samba3: samba3 object
462 :param logger: Logger object
463 :param targetdir: samba4 database directory
464 :param session_info: Session information
466 serverrole
= samba3
.lp
.server_role()
468 domainname
= samba3
.lp
.get("workgroup")
469 realm
= samba3
.lp
.get("realm")
470 netbiosname
= samba3
.lp
.get("netbios name")
474 secrets_db
= samba3
.get_secrets_db()
476 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
)))
479 domainname
= secrets_db
.domains()[0]
480 logger
.warning("No workgroup specified in smb.conf file, assuming '%s'",
484 if serverrole
== "ROLE_DOMAIN_BDC" or serverrole
== "ROLE_DOMAIN_PDC":
485 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.")
487 realm
= domainname
.upper()
488 logger
.warning("No realm specified in smb.conf file, assuming '%s'",
491 # Find machine account and password
495 machinepass
= secrets_db
.get_machine_password(netbiosname
)
499 # We must close the direct pytdb database before the C code loads it
502 # Connect to old password backend
503 passdb
.set_secrets_dir(samba3
.lp
.get("private dir"))
504 s3db
= samba3
.get_sam_db()
508 domainsid
= passdb
.get_global_sam_sid()
510 raise Exception("Can't find domain sid for '%s', Exiting." % domainname
)
512 # Get machine account, sid, rid
514 machineacct
= s3db
.getsampwnam('%s$' % netbiosname
)
519 machinesid
, machinerid
= machineacct
.user_sid
.split()
521 # Export account policy
522 logger
.info("Exporting account policy")
523 policy
= s3db
.get_account_policy()
525 # Export groups from old passdb backend
526 logger
.info("Exporting groups")
527 grouplist
= s3db
.enum_group_mapping()
529 for group
in grouplist
:
530 sid
, rid
= group
.sid
.split()
535 # Get members for each group/alias
536 if group
.sid_name_use
== lsa
.SID_NAME_ALIAS
:
537 members
= s3db
.enum_aliasmem(group
.sid
)
538 elif group
.sid_name_use
== lsa
.SID_NAME_DOM_GRP
:
540 members
= s3db
.enum_group_members(group
.sid
)
543 groupmembers
[group
.nt_name
] = members
544 elif group
.sid_name_use
== lsa
.SID_NAME_WKN_GRP
:
545 (group_dom_sid
, rid
) = group
.sid
.split()
546 if (group_dom_sid
!= security
.dom_sid(security
.SID_BUILTIN
)):
547 logger
.warn("Ignoring 'well known' group '%s' (should already be in AD, and have no members)",
550 # A number of buggy databases mix up well known groups and aliases.
551 members
= s3db
.enum_aliasmem(group
.sid
)
553 logger
.warn("Ignoring group '%s' with sid_name_use=%d",
554 group
.nt_name
, group
.sid_name_use
)
557 # Export users from old passdb backend
558 logger
.info("Exporting users")
559 userlist
= s3db
.search_users(0)
563 for entry
in userlist
:
564 if machinerid
and machinerid
== entry
['rid']:
566 username
= entry
['account_name']
567 if entry
['rid'] < 1000:
568 logger
.info(" Skipping wellknown rid=%d (for username=%s)", entry
['rid'], username
)
570 if entry
['rid'] >= next_rid
:
571 next_rid
= entry
['rid'] + 1
573 user
= s3db
.getsampwnam(username
)
574 acct_type
= (user
.acct_ctrl
& (samr
.ACB_NORMAL|samr
.ACB_WSTRUST|samr
.ACB_SVRTRUST|samr
.ACB_DOMTRUST
))
575 if (acct_type
== samr
.ACB_NORMAL
or acct_type
== samr
.ACB_WSTRUST
or acct_type
== samr
.ACB_SVRTRUST
):
577 elif acct_type
== samr
.ACB_DOMTRUST
:
578 logger
.warn(" Skipping inter-domain trust from domain %s, this trust must be re-created as an AD trust" % username
[:-1])
580 elif acct_type
== (samr
.ACB_NORMAL|samr
.ACB_WSTRUST
) and username
[-1] == '$':
581 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
)
582 user
.acct_ctrl
= (user
.acct_ctrl
& ~samr
.ACB_NORMAL
)
584 raise ProvisioningError("""Failed to upgrade due to invalid account %s, account control flags 0x%08X must have exactly one of
585 ACB_NORMAL (N, 0x%08X), ACB_WSTRUST (W 0x%08X), ACB_SVRTRUST (S 0x%08X) or ACB_DOMTRUST (D 0x%08X).
587 Please fix this account before attempting to upgrade again
589 % (user
.acct_flags
, username
,
590 samr
.ACB_NORMAL
, samr
.ACB_WSTRUST
, samr
.ACB_SVRTRUST
, samr
.ACB_DOMTRUST
))
592 userdata
[username
] = user
594 uids
[username
] = s3db
.sid_to_id(user
.user_sid
)[0]
597 uids
[username
] = pwd
.getpwnam(username
).pw_uid
601 if not admin_user
and username
.lower() == 'root':
602 admin_user
= username
603 if username
.lower() == 'administrator':
604 admin_user
= username
606 logger
.info("Next rid = %d", next_rid
)
608 # Check for same username/groupname
609 group_names
= set([g
.nt_name
for g
in grouplist
])
610 user_names
= set([u
['account_name'] for u
in userlist
])
611 common_names
= group_names
.intersection(user_names
)
613 logger
.error("Following names are both user names and group names:")
614 for name
in common_names
:
615 logger
.error(" %s" % name
)
616 raise ProvisioningError("Please remove common user/group names before upgrade.")
618 # Check for same user sid/group sid
619 group_sids
= set([str(g
.sid
) for g
in grouplist
])
620 if len(grouplist
) != len(group_sids
):
621 raise ProvisioningError("Please remove duplicate group sid entries before upgrade.")
622 user_sids
= set(["%s-%u" % (domainsid
, u
['rid']) for u
in userlist
])
623 if len(userlist
) != len(user_sids
):
624 raise ProvisioningError("Please remove duplicate user sid entries before upgrade.")
625 common_sids
= group_sids
.intersection(user_sids
)
627 logger
.error("Following sids are both user and group sids:")
628 for sid
in common_sids
:
629 logger
.error(" %s" % str(sid
))
630 raise ProvisioningError("Please remove duplicate sid entries before upgrade.")
632 if serverrole
== "ROLE_DOMAIN_BDC" or serverrole
== "ROLE_DOMAIN_PDC":
633 dns_backend
= "BIND9_DLZ"
638 result
= provision(logger
, session_info
, None,
639 targetdir
=targetdir
, realm
=realm
, domain
=domainname
,
640 domainsid
=str(domainsid
), next_rid
=next_rid
,
642 dom_for_fun_level
=dsdb
.DS_DOMAIN_FUNCTION_2003
,
643 hostname
=netbiosname
.lower(), machinepass
=machinepass
,
644 serverrole
=serverrole
, samdb_fill
=FILL_FULL
,
645 useeadb
=useeadb
, dns_backend
=dns_backend
)
647 # Import WINS database
648 logger
.info("Importing WINS database")
652 samba3_winsdb
= samba3
.get_wins_db()
654 logger
.warn('Cannot open wins database, Ignoring: %s', str(e
))
657 import_wins(Ldb(result
.paths
.winsdb
), samba3_winsdb
)
660 logger
.info("Importing Account policy")
661 import_sam_policy(result
.samdb
, policy
, logger
)
663 # Migrate IDMAP database
664 logger
.info("Importing idmap database")
665 import_idmap(result
.idmap
, samba3
, logger
)
667 # Set the s3 context for samba4 configuration
668 new_lp_ctx
= s3param
.get_context()
669 new_lp_ctx
.load(result
.lp
.configfile
)
670 new_lp_ctx
.set("private dir", result
.lp
.get("private dir"))
671 new_lp_ctx
.set("state directory", result
.lp
.get("state directory"))
672 new_lp_ctx
.set("lock directory", result
.lp
.get("lock directory"))
674 # Connect to samba4 backend
675 s4_passdb
= passdb
.PDB(new_lp_ctx
.get("passdb backend"))
677 # Export groups to samba4 backend
678 logger
.info("Importing groups")
680 # Ignore uninitialized groups (gid = -1)
682 add_idmap_entry(result
.idmap
, g
.sid
, g
.gid
, "ID_TYPE_GID", logger
)
683 add_group_from_mapping_entry(result
.samdb
, g
, logger
)
685 # Export users to samba4 backend
686 logger
.info("Importing users")
687 for username
in userdata
:
688 if username
.lower() == 'administrator' or username
.lower() == 'root':
690 s4_passdb
.add_sam_account(userdata
[username
])
692 add_idmap_entry(result
.idmap
, userdata
[username
].user_sid
, uids
[username
], "ID_TYPE_UID", logger
)
694 logger
.info("Adding users to groups")
696 if g
.nt_name
in groupmembers
:
697 add_users_to_group(result
.samdb
, g
, groupmembers
[g
.nt_name
], logger
)
699 # Set password for administrator
701 logger
.info("Setting password for administrator")
702 admin_userdata
= s4_passdb
.getsampwnam("administrator")
703 admin_userdata
.nt_passwd
= userdata
[admin_user
].nt_passwd
704 if userdata
[admin_user
].lanman_passwd
:
705 admin_userdata
.lanman_passwd
= userdata
[admin_user
].lanman_passwd
706 admin_userdata
.pass_last_set_time
= userdata
[admin_user
].pass_last_set_time
707 if userdata
[admin_user
].pw_history
:
708 admin_userdata
.pw_history
= userdata
[admin_user
].pw_history
709 s4_passdb
.update_sam_account(admin_userdata
)
710 logger
.info("Administrator password has been set to password of user '%s'", admin_user
)
712 # FIXME: import_registry(registry.Registry(), samba3.get_registry())