1 | #!/usr/bin/env python
|
---|
2 | #
|
---|
3 | # update our servicePrincipalName names from spn_update_list
|
---|
4 | #
|
---|
5 | # Copyright (C) Andrew Tridgell 2010
|
---|
6 | #
|
---|
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.
|
---|
11 | #
|
---|
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.
|
---|
16 | #
|
---|
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/>.
|
---|
19 |
|
---|
20 |
|
---|
21 | import os, sys
|
---|
22 |
|
---|
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'
|
---|
26 |
|
---|
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
|
---|
30 | # the US)
|
---|
31 | os.environ["TZ"] = "GMT"
|
---|
32 |
|
---|
33 | # Find right directory when running from source tree
|
---|
34 | sys.path.insert(0, "bin/python")
|
---|
35 |
|
---|
36 | import samba, ldb
|
---|
37 | import optparse
|
---|
38 | from samba import Ldb
|
---|
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
|
---|
43 |
|
---|
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")
|
---|
49 |
|
---|
50 | credopts = options.CredentialsOptions(parser)
|
---|
51 | parser.add_option_group(credopts)
|
---|
52 |
|
---|
53 | ccachename = None
|
---|
54 |
|
---|
55 | opts, args = parser.parse_args()
|
---|
56 |
|
---|
57 | if len(args) != 0:
|
---|
58 | parser.print_usage()
|
---|
59 | sys.exit(1)
|
---|
60 |
|
---|
61 | lp = sambaopts.get_loadparm()
|
---|
62 | creds = credopts.get_credentials(lp)
|
---|
63 |
|
---|
64 | domain = lp.get("realm")
|
---|
65 | host = lp.get("netbios name")
|
---|
66 |
|
---|
67 |
|
---|
68 | # get the list of substitution vars
|
---|
69 | def get_subst_vars(samdb):
|
---|
70 | global lp
|
---|
71 | vars = {}
|
---|
72 |
|
---|
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
|
---|
81 | return vars
|
---|
82 |
|
---|
83 | try:
|
---|
84 | private_dir = lp.get("private dir")
|
---|
85 | secrets_path = os.path.join(private_dir, lp.get("secrets database"))
|
---|
86 |
|
---|
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"])
|
---|
92 |
|
---|
93 | if len(res) == 1:
|
---|
94 | credentials = Credentials()
|
---|
95 | credentials.set_kerberos_state(DONT_USE_KERBEROS)
|
---|
96 |
|
---|
97 | if "samAccountName" in res[0]:
|
---|
98 | credentials.set_username(res[0]["samAccountName"][0])
|
---|
99 |
|
---|
100 | if "secret" in res[0]:
|
---|
101 | credentials.set_password(res[0]["secret"][0])
|
---|
102 |
|
---|
103 | else:
|
---|
104 | credentials = None
|
---|
105 |
|
---|
106 | samdb = SamDB(url=lp.get("sam database"), session_info=system_session(), credentials=credentials, lp=lp)
|
---|
107 | except ldb.LdbError, (num, msg):
|
---|
108 | print("Unable to open sam database %s : %s" % (lp.get("sam database"), msg))
|
---|
109 | sys.exit(1)
|
---|
110 |
|
---|
111 |
|
---|
112 | # get the substitution dictionary
|
---|
113 | sub_vars = get_subst_vars(samdb)
|
---|
114 |
|
---|
115 | # get the list of SPN entries we should have
|
---|
116 | spn_update_list = lp.private_path('spn_update_list')
|
---|
117 |
|
---|
118 | file = open(spn_update_list, "r")
|
---|
119 |
|
---|
120 | spn_list = []
|
---|
121 |
|
---|
122 | # build the spn list
|
---|
123 | for line in file:
|
---|
124 | line = line.strip()
|
---|
125 | if line == '' or line[0] == "#":
|
---|
126 | continue
|
---|
127 | line = samba.substitute_var(line, sub_vars)
|
---|
128 | spn_list.append(line)
|
---|
129 |
|
---|
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'])
|
---|
136 | sys.exit(1)
|
---|
137 |
|
---|
138 | machine_dn = res[0]["dn"]
|
---|
139 |
|
---|
140 | old_spns = []
|
---|
141 | if "servicePrincipalName" in res[0]:
|
---|
142 | for s in res[0]["servicePrincipalName"]:
|
---|
143 | old_spns.append(s)
|
---|
144 |
|
---|
145 | if opts.verbose:
|
---|
146 | print("Existing SPNs: %s" % old_spns)
|
---|
147 |
|
---|
148 | add_list = []
|
---|
149 |
|
---|
150 | # work out what needs to be added
|
---|
151 | for s in spn_list:
|
---|
152 | in_list = False
|
---|
153 | for s2 in old_spns:
|
---|
154 | if s2.upper() == s.upper():
|
---|
155 | in_list = True
|
---|
156 | break
|
---|
157 | if not in_list:
|
---|
158 | add_list.append(s)
|
---|
159 |
|
---|
160 | if opts.verbose:
|
---|
161 | print("New SPNs: %s" % add_list)
|
---|
162 |
|
---|
163 | if add_list == []:
|
---|
164 | if opts.verbose:
|
---|
165 | print("Nothing to add")
|
---|
166 | sys.exit(0)
|
---|
167 |
|
---|
168 | def local_update(add_list):
|
---|
169 | '''store locally'''
|
---|
170 | global res
|
---|
171 | msg = ldb.Message()
|
---|
172 | msg.dn = res[0]['dn']
|
---|
173 | msg[""] = ldb.MessageElement(add_list,
|
---|
174 | ldb.FLAG_MOD_ADD, "servicePrincipalName")
|
---|
175 | res = samdb.modify(msg)
|
---|
176 |
|
---|
177 | def call_rodc_update(d):
|
---|
178 | '''RODCs need to use the writeSPN DRS call'''
|
---|
179 | global lp, sub_vars
|
---|
180 | from samba import drs_utils
|
---|
181 | from samba.dcerpc import drsuapi, nbt
|
---|
182 | from samba.net import Net
|
---|
183 |
|
---|
184 | if opts.verbose:
|
---|
185 | print("Using RODC SPN update")
|
---|
186 |
|
---|
187 | creds = credopts.get_credentials(lp)
|
---|
188 | creds.set_machine_account(lp)
|
---|
189 |
|
---|
190 | net = Net(creds=creds, lp=lp)
|
---|
191 | try:
|
---|
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))
|
---|
195 | sys.exit(1)
|
---|
196 | server = cldap_ret.pdc_dns_name
|
---|
197 | try:
|
---|
198 | binding_options = "seal"
|
---|
199 | if 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))
|
---|
205 | sys.exit(1)
|
---|
206 | req1 = drsuapi.DsWriteAccountSpnRequest1()
|
---|
207 | req1.operation = drsuapi.DRSUAPI_DS_SPN_OPERATION_ADD
|
---|
208 | req1.object_dn = str(machine_dn)
|
---|
209 | req1.count = 0
|
---|
210 | spn_names = []
|
---|
211 | for n in add_list:
|
---|
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
|
---|
214 | continue
|
---|
215 | ns = drsuapi.DsNameString()
|
---|
216 | ns.str = n
|
---|
217 | spn_names.append(ns)
|
---|
218 | req1.count = req1.count + 1
|
---|
219 | if spn_names == []:
|
---|
220 | return
|
---|
221 | req1.spn_names = spn_names
|
---|
222 | (level, res) = drs.DsWriteAccountSpn(drs_handle, 1, req1)
|
---|
223 |
|
---|
224 | if samdb.am_rodc():
|
---|
225 | call_rodc_update(add_list)
|
---|
226 | else:
|
---|
227 | local_update(add_list)
|
---|