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'] = samdb
.domain_dns_name()
74 vars['DNSFOREST'] = samdb
.forest_dns_name()
75 vars['HOSTNAME'] = samdb
.host_dns_name()
76 vars['NETBIOSNAME'] = lp
.get('netbios name').upper()
77 vars['WORKGROUP'] = lp
.get('workgroup')
78 vars['NTDSGUID'] = samdb
.get_ntds_GUID()
79 res
= samdb
.search(base
=samdb
.get_default_basedn(), scope
=ldb
.SCOPE_BASE
, attrs
=["objectGUID"])
80 guid
= samdb
.schema_format_value("objectGUID", res
[0]['objectGUID'][0])
81 vars['DOMAINGUID'] = guid
85 private_dir
= lp
.get("private dir")
86 secrets_path
= os
.path
.join(private_dir
, "secrets.ldb")
88 secrets_db
= Ldb(url
=secrets_path
, session_info
=system_session(),
89 credentials
=creds
, lp
=lp
)
90 res
= secrets_db
.search(base
=None,
91 expression
="(&(objectclass=ldapSecret)(cn=SAMDB Credentials))",
92 attrs
=["samAccountName", "secret"])
95 credentials
= Credentials()
96 credentials
.set_kerberos_state(DONT_USE_KERBEROS
)
98 if "samAccountName" in res
[0]:
99 credentials
.set_username(res
[0]["samAccountName"][0])
101 if "secret" in res
[0]:
102 credentials
.set_password(res
[0]["secret"][0])
107 samdb
= SamDB(url
=lp
.samdb_url(), session_info
=system_session(), credentials
=credentials
, lp
=lp
)
108 except ldb
.LdbError
, (num
, msg
):
109 print("Unable to open sam database %s : %s" % (lp
.samdb_url(), msg
))
113 # get the substitution dictionary
114 sub_vars
= get_subst_vars(samdb
)
116 # get the list of SPN entries we should have
117 spn_update_list
= lp
.private_path('spn_update_list')
119 file = open(spn_update_list
, "r")
126 if line
== '' or line
[0] == "#":
128 line
= samba
.substitute_var(line
, sub_vars
)
129 spn_list
.append(line
)
131 # get the current list of SPNs in our sam
132 res
= samdb
.search(base
=samdb
.get_default_basedn(),
133 expression
='(&(objectClass=computer)(samaccountname=%s$))' % sub_vars
['NETBIOSNAME'],
134 attrs
=["servicePrincipalName"])
135 if not res
or len(res
) != 1:
136 print("Failed to find computer object for %s$" % sub_vars
['NETBIOSNAME'])
139 machine_dn
= res
[0]["dn"]
142 if "servicePrincipalName" in res
[0]:
143 for s
in res
[0]["servicePrincipalName"]:
147 print("Existing SPNs: %s" % old_spns
)
151 # work out what needs to be added
155 if s2
.upper() == s
.upper():
162 print("New SPNs: %s" % add_list
)
166 print("Nothing to add")
169 def local_update(add_list
):
173 msg
.dn
= res
[0]['dn']
174 msg
[""] = ldb
.MessageElement(add_list
,
175 ldb
.FLAG_MOD_ADD
, "servicePrincipalName")
176 res
= samdb
.modify(msg
)
178 def call_rodc_update(d
):
179 '''RODCs need to use the writeSPN DRS call'''
181 from samba
import drs_utils
182 from samba
.dcerpc
import drsuapi
, nbt
183 from samba
.net
import Net
186 print("Using RODC SPN update")
188 creds
= credopts
.get_credentials(lp
)
189 creds
.set_machine_account(lp
)
191 net
= Net(creds
=creds
, lp
=lp
)
193 cldap_ret
= net
.finddc(domain
=domain
, flags
=nbt
.NBT_SERVER_DS | nbt
.NBT_SERVER_WRITABLE
)
194 except Exception, reason
:
195 print("Unable to find writeable DC for domain '%s' to send DRS writeSPN to : %s" % (domain
, reason
))
197 server
= cldap_ret
.pdc_dns_name
199 binding_options
= "seal"
200 if int(lp
.get("log level")) >= 5:
201 binding_options
+= ",print"
202 drs
= drsuapi
.drsuapi('ncacn_ip_tcp:%s[%s]' % (server
, binding_options
), lp
, creds
)
203 (drs_handle
, supported_extensions
) = drs_utils
.drs_DsBind(drs
)
204 except Exception, reason
:
205 print("Unable to connect to DC '%s' for domain '%s' : %s" % (server
, domain
, reason
))
207 req1
= drsuapi
.DsWriteAccountSpnRequest1()
208 req1
.operation
= drsuapi
.DRSUAPI_DS_SPN_OPERATION_ADD
209 req1
.object_dn
= str(machine_dn
)
213 if n
.find('E3514235-4B06-11D1-AB04-00C04FC2DCD2') != -1:
214 # this one isn't allowed for RODCs, but we don't know why yet
216 ns
= drsuapi
.DsNameString()
219 req1
.count
= req1
.count
+ 1
222 req1
.spn_names
= spn_names
223 (level
, res
) = drs
.DsWriteAccountSpn(drs_handle
, 1, req1
)
226 call_rodc_update(add_list
)
228 local_update(add_list
)