3 # update our servicePrincipalName names from spn_update_list
5 # Copyright (C) Andrew Tridgell 2010
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # ensure we get messages out immediately, so they get in the samba logs,
24 # and don't get swallowed by a timeout
25 os
.environ
['PYTHONUNBUFFERED'] = '1'
27 # forcing GMT avoids a problem in some timezones with kerberos. Both MIT
28 # heimdal can get mutual authentication errors due to the 24 second difference
29 # between UTC and GMT when using some zone files (eg. the PDT zone from
31 os
.environ
["TZ"] = "GMT"
33 # Find right directory when running from source tree
34 sys
.path
.insert(0, "bin/python")
39 from samba
import getopt
as options
40 from samba
.auth
import system_session
41 from samba
.samdb
import SamDB
42 from samba
.credentials
import Credentials
, DONT_USE_KERBEROS
44 parser
= optparse
.OptionParser("samba_spnupdate")
45 sambaopts
= options
.SambaOptions(parser
)
46 parser
.add_option_group(sambaopts
)
47 parser
.add_option_group(options
.VersionOptions(parser
))
48 parser
.add_option("--verbose", action
="store_true")
50 credopts
= options
.CredentialsOptions(parser
)
51 parser
.add_option_group(credopts
)
55 opts
, args
= parser
.parse_args()
61 lp
= sambaopts
.get_loadparm()
62 creds
= credopts
.get_credentials(lp
)
64 domain
= lp
.get("realm")
65 host
= lp
.get("netbios name")
68 # get the list of substitution vars
69 def get_subst_vars(samdb
):
73 vars['DNSDOMAIN'] = lp
.get('realm').lower()
74 vars['HOSTNAME'] = lp
.get('netbios name').lower() + "." + vars['DNSDOMAIN']
75 vars['NETBIOSNAME'] = lp
.get('netbios name').upper()
76 vars['WORKGROUP'] = lp
.get('workgroup')
77 vars['NTDSGUID'] = samdb
.get_ntds_GUID()
78 res
= samdb
.search(base
=None, scope
=ldb
.SCOPE_BASE
, attrs
=["objectGUID"])
79 guid
= samdb
.schema_format_value("objectGUID", res
[0]['objectGUID'][0])
80 vars['DOMAINGUID'] = guid
84 private_dir
= lp
.get("private dir")
85 secrets_path
= os
.path
.join(private_dir
, "secrets.ldb")
87 secrets_db
= Ldb(url
=secrets_path
, session_info
=system_session(),
88 credentials
=creds
, lp
=lp
)
89 res
= secrets_db
.search(base
=None,
90 expression
="(&(objectclass=ldapSecret)(cn=SAMDB Credentials))",
91 attrs
=["samAccountName", "secret"])
94 credentials
= Credentials()
95 credentials
.set_kerberos_state(DONT_USE_KERBEROS
)
97 if "samAccountName" in res
[0]:
98 credentials
.set_username(res
[0]["samAccountName"][0])
100 if "secret" in res
[0]:
101 credentials
.set_password(res
[0]["secret"][0])
106 samdb
= SamDB(url
=lp
.samdb_url(), session_info
=system_session(), credentials
=credentials
, lp
=lp
)
107 except ldb
.LdbError
, (num
, msg
):
108 print("Unable to open sam database %s : %s" % (lp
.samdb_url(), msg
))
112 # get the substitution dictionary
113 sub_vars
= get_subst_vars(samdb
)
115 # get the list of SPN entries we should have
116 spn_update_list
= lp
.private_path('spn_update_list')
118 file = open(spn_update_list
, "r")
125 if line
== '' or line
[0] == "#":
127 line
= samba
.substitute_var(line
, sub_vars
)
128 spn_list
.append(line
)
130 # get the current list of SPNs in our sam
131 res
= samdb
.search(base
="",
132 expression
='(&(objectClass=computer)(samaccountname=%s$))' % sub_vars
['NETBIOSNAME'],
133 attrs
=["servicePrincipalName"])
134 if not res
or len(res
) != 1:
135 print("Failed to find computer object for %s$" % sub_vars
['NETBIOSNAME'])
138 machine_dn
= res
[0]["dn"]
141 if "servicePrincipalName" in res
[0]:
142 for s
in res
[0]["servicePrincipalName"]:
146 print("Existing SPNs: %s" % old_spns
)
150 # work out what needs to be added
154 if s2
.upper() == s
.upper():
161 print("New SPNs: %s" % add_list
)
165 print("Nothing to add")
168 def local_update(add_list
):
172 msg
.dn
= res
[0]['dn']
173 msg
[""] = ldb
.MessageElement(add_list
,
174 ldb
.FLAG_MOD_ADD
, "servicePrincipalName")
175 res
= samdb
.modify(msg
)
177 def call_rodc_update(d
):
178 '''RODCs need to use the writeSPN DRS call'''
180 from samba
import drs_utils
181 from samba
.dcerpc
import drsuapi
, nbt
182 from samba
.net
import Net
185 print("Using RODC SPN update")
187 creds
= credopts
.get_credentials(lp
)
188 creds
.set_machine_account(lp
)
190 net
= Net(creds
=creds
, lp
=lp
)
192 cldap_ret
= net
.finddc(domain
, nbt
.NBT_SERVER_DS | nbt
.NBT_SERVER_WRITABLE
)
193 except Exception, reason
:
194 print("Unable to find writeable DC for domain '%s' to send DRS writeSPN to : %s" % (domain
, reason
))
196 server
= cldap_ret
.pdc_dns_name
198 binding_options
= "seal"
199 if int(lp
.get("log level")) >= 5:
200 binding_options
+= ",print"
201 drs
= drsuapi
.drsuapi('ncacn_ip_tcp:%s[%s]' % (server
, binding_options
), lp
, creds
)
202 (drs_handle
, supported_extensions
) = drs_utils
.drs_DsBind(drs
)
203 except Exception, reason
:
204 print("Unable to connect to DC '%s' for domain '%s' : %s" % (server
, domain
, reason
))
206 req1
= drsuapi
.DsWriteAccountSpnRequest1()
207 req1
.operation
= drsuapi
.DRSUAPI_DS_SPN_OPERATION_ADD
208 req1
.object_dn
= str(machine_dn
)
212 if n
.find('E3514235-4B06-11D1-AB04-00C04FC2DCD2') != -1:
213 # this one isn't allowed for RODCs, but we don't know why yet
215 ns
= drsuapi
.DsNameString()
218 req1
.count
= req1
.count
+ 1
221 req1
.spn_names
= spn_names
222 (level
, res
) = drs
.DsWriteAccountSpn(drs_handle
, 1, req1
)
225 call_rodc_update(add_list
)
227 local_update(add_list
)