Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 :
4 : Samba kpasswd implementation
5 :
6 : Copyright (c) 2005 Andrew Bartlett <abartlet@samba.org>
7 : Copyright (c) 2016 Andreas Schneider <asn@samba.org>
8 :
9 : This program is free software; you can redistribute it and/or modify
10 : it under the terms of the GNU General Public License as published by
11 : the Free Software Foundation; either version 3 of the License, or
12 : (at your option) any later version.
13 :
14 : This program is distributed in the hope that it will be useful,
15 : but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : GNU General Public License for more details.
18 :
19 : You should have received a copy of the GNU General Public License
20 : along with this program. If not, see <http://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include "includes.h"
24 : #include "samba/service_task.h"
25 : #include "tsocket/tsocket.h"
26 : #include "auth/credentials/credentials.h"
27 : #include "auth/auth.h"
28 : #include "auth/gensec/gensec.h"
29 : #include "kdc/kdc-server.h"
30 : #include "kdc/kpasswd-service.h"
31 : #include "kdc/kpasswd-helper.h"
32 : #include "param/param.h"
33 :
34 : #define HEADER_LEN 6
35 : #ifndef RFC3244_VERSION
36 : #define RFC3244_VERSION 0xff80
37 : #endif
38 :
39 38 : kdc_code kpasswd_process(struct kdc_server *kdc,
40 : TALLOC_CTX *mem_ctx,
41 : DATA_BLOB *request,
42 : DATA_BLOB *reply,
43 : struct tsocket_address *remote_addr,
44 : struct tsocket_address *local_addr,
45 : int datagram)
46 : {
47 : uint16_t len;
48 : uint16_t verno;
49 : uint16_t ap_req_len;
50 : uint16_t enc_data_len;
51 38 : DATA_BLOB ap_req_blob = data_blob_null;
52 38 : DATA_BLOB ap_rep_blob = data_blob_null;
53 38 : DATA_BLOB enc_data_blob = data_blob_null;
54 38 : DATA_BLOB dec_data_blob = data_blob_null;
55 38 : DATA_BLOB kpasswd_dec_reply = data_blob_null;
56 38 : const char *error_string = NULL;
57 38 : krb5_error_code error_code = 0;
58 : struct cli_credentials *server_credentials;
59 : struct gensec_security *gensec_security;
60 : #ifndef SAMBA4_USES_HEIMDAL
61 : struct sockaddr_storage remote_ss;
62 : #endif
63 : struct sockaddr_storage local_ss;
64 : ssize_t socklen;
65 : TALLOC_CTX *tmp_ctx;
66 38 : kdc_code rc = KDC_ERROR;
67 38 : krb5_error_code code = 0;
68 : NTSTATUS status;
69 : int rv;
70 : bool is_inet;
71 : bool ok;
72 :
73 38 : if (kdc->am_rodc) {
74 0 : return KDC_PROXY_REQUEST;
75 : }
76 :
77 38 : tmp_ctx = talloc_new(mem_ctx);
78 38 : if (tmp_ctx == NULL) {
79 0 : return KDC_ERROR;
80 : }
81 :
82 38 : is_inet = tsocket_address_is_inet(remote_addr, "ip");
83 38 : if (!is_inet) {
84 0 : DBG_WARNING("Invalid remote IP address");
85 0 : goto done;
86 : }
87 :
88 : #ifndef SAMBA4_USES_HEIMDAL
89 : /*
90 : * FIXME: Heimdal fails to to do a krb5_rd_req() in gensec_krb5 if we
91 : * set the remote address.
92 : */
93 :
94 : /* remote_addr */
95 6 : socklen = tsocket_address_bsd_sockaddr(remote_addr,
96 : (struct sockaddr *)&remote_ss,
97 : sizeof(struct sockaddr_storage));
98 6 : if (socklen < 0) {
99 0 : DBG_WARNING("Invalid remote IP address");
100 0 : goto done;
101 : }
102 : #endif
103 :
104 : /* local_addr */
105 38 : socklen = tsocket_address_bsd_sockaddr(local_addr,
106 : (struct sockaddr *)&local_ss,
107 : sizeof(struct sockaddr_storage));
108 38 : if (socklen < 0) {
109 0 : DBG_WARNING("Invalid local IP address");
110 0 : goto done;
111 : }
112 :
113 38 : if (request->length <= HEADER_LEN) {
114 0 : DBG_WARNING("Request truncated\n");
115 0 : goto done;
116 : }
117 :
118 38 : len = RSVAL(request->data, 0);
119 38 : if (request->length != len) {
120 0 : DBG_WARNING("Request length does not match\n");
121 0 : goto done;
122 : }
123 :
124 38 : verno = RSVAL(request->data, 2);
125 38 : if (verno != 1 && verno != RFC3244_VERSION) {
126 0 : DBG_WARNING("Unsupported version: 0x%04x\n", verno);
127 : }
128 :
129 38 : ap_req_len = RSVAL(request->data, 4);
130 38 : if ((ap_req_len >= len) || ((ap_req_len + HEADER_LEN) >= len)) {
131 0 : DBG_WARNING("AP_REQ truncated\n");
132 0 : goto done;
133 : }
134 :
135 38 : ap_req_blob = data_blob_const(&request->data[HEADER_LEN], ap_req_len);
136 :
137 38 : enc_data_len = len - ap_req_len;
138 38 : enc_data_blob = data_blob_const(&request->data[HEADER_LEN + ap_req_len],
139 : enc_data_len);
140 :
141 38 : server_credentials = cli_credentials_init(tmp_ctx);
142 38 : if (server_credentials == NULL) {
143 0 : DBG_ERR("Failed to initialize server credentials!\n");
144 0 : goto done;
145 : }
146 :
147 : /*
148 : * We want the credentials subsystem to use the krb5 context we already
149 : * have, rather than a new context.
150 : *
151 : * On this context the KDB plugin has been loaded, so we can access
152 : * dsdb.
153 : */
154 38 : status = cli_credentials_set_krb5_context(server_credentials,
155 : kdc->smb_krb5_context);
156 38 : if (!NT_STATUS_IS_OK(status)) {
157 0 : goto done;
158 : }
159 :
160 38 : ok = cli_credentials_set_conf(server_credentials, kdc->task->lp_ctx);
161 38 : if (!ok) {
162 0 : goto done;
163 : }
164 :
165 : /*
166 : * After calling cli_credentials_set_conf(), explicitly set the realm
167 : * with CRED_SPECIFIED. We need to do this so the result of
168 : * principal_from_credentials() called from the gensec layer is
169 : * CRED_SPECIFIED rather than CRED_SMB_CONF, avoiding a fallback to
170 : * match-by-key (very undesirable in this case).
171 : */
172 38 : ok = cli_credentials_set_realm(server_credentials,
173 38 : lpcfg_realm(kdc->task->lp_ctx),
174 : CRED_SPECIFIED);
175 38 : if (!ok) {
176 0 : goto done;
177 : }
178 :
179 38 : ok = cli_credentials_set_username(server_credentials,
180 : "kadmin/changepw",
181 : CRED_SPECIFIED);
182 38 : if (!ok) {
183 0 : goto done;
184 : }
185 :
186 : /* Check that the server principal is indeed CRED_SPECIFIED. */
187 : {
188 38 : char *principal = NULL;
189 : enum credentials_obtained obtained;
190 :
191 38 : principal = cli_credentials_get_principal_and_obtained(server_credentials,
192 : tmp_ctx,
193 : &obtained);
194 38 : if (obtained < CRED_SPECIFIED) {
195 0 : goto done;
196 : }
197 :
198 38 : TALLOC_FREE(principal);
199 : }
200 :
201 70 : rv = cli_credentials_set_keytab_name(server_credentials,
202 38 : kdc->task->lp_ctx,
203 : kdc->kpasswd_keytab_name,
204 : CRED_SPECIFIED);
205 38 : if (rv != 0) {
206 0 : DBG_ERR("Failed to set credentials keytab name\n");
207 0 : goto done;
208 : }
209 :
210 102 : status = samba_server_gensec_start(tmp_ctx,
211 38 : kdc->task->event_ctx,
212 38 : kdc->task->msg_ctx,
213 38 : kdc->task->lp_ctx,
214 : server_credentials,
215 : "kpasswd",
216 : &gensec_security);
217 38 : if (!NT_STATUS_IS_OK(status)) {
218 0 : goto done;
219 : }
220 :
221 38 : status = gensec_set_local_address(gensec_security, local_addr);
222 38 : if (!NT_STATUS_IS_OK(status)) {
223 0 : goto done;
224 : }
225 :
226 : #ifndef SAMBA4_USES_HEIMDAL
227 6 : status = gensec_set_remote_address(gensec_security, remote_addr);
228 6 : if (!NT_STATUS_IS_OK(status)) {
229 0 : goto done;
230 : }
231 : #endif
232 :
233 : /* We want the GENSEC wrap calls to generate PRIV tokens */
234 38 : gensec_want_feature(gensec_security, GENSEC_FEATURE_SEAL);
235 :
236 : /* Use the krb5 gesec mechanism so we can load DB modules */
237 38 : status = gensec_start_mech_by_name(gensec_security, "krb5");
238 38 : if (!NT_STATUS_IS_OK(status)) {
239 0 : goto done;
240 : }
241 :
242 : /*
243 : * Accept the AP-REQ and generate the AP-REP we need for the reply
244 : *
245 : * We only allow KRB5 and make sure the backend to is RPC/IPC free.
246 : *
247 : * See gensec_krb5_update_internal() as GENSEC_SERVER.
248 : *
249 : * It allows gensec_update() not to block.
250 : *
251 : * If that changes in future we need to use
252 : * gensec_update_send/recv here!
253 : */
254 38 : status = gensec_update(gensec_security, tmp_ctx,
255 : ap_req_blob, &ap_rep_blob);
256 38 : if (!NT_STATUS_IS_OK(status) &&
257 0 : !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
258 0 : ap_rep_blob = data_blob_null;
259 0 : error_code = KRB5_KPASSWD_HARDERROR;
260 0 : error_string = talloc_asprintf(tmp_ctx,
261 : "gensec_update failed - %s\n",
262 : nt_errstr(status));
263 0 : DBG_ERR("%s", error_string);
264 0 : goto reply;
265 : }
266 :
267 38 : status = gensec_unwrap(gensec_security,
268 : tmp_ctx,
269 : &enc_data_blob,
270 : &dec_data_blob);
271 38 : if (!NT_STATUS_IS_OK(status)) {
272 0 : ap_rep_blob = data_blob_null;
273 0 : error_code = KRB5_KPASSWD_HARDERROR;
274 0 : error_string = talloc_asprintf(tmp_ctx,
275 : "gensec_unwrap failed - %s\n",
276 : nt_errstr(status));
277 0 : DBG_ERR("%s", error_string);
278 0 : goto reply;
279 : }
280 :
281 38 : code = kpasswd_handle_request(kdc,
282 : tmp_ctx,
283 : gensec_security,
284 : verno,
285 : &dec_data_blob,
286 : &kpasswd_dec_reply,
287 : &error_string);
288 38 : if (code != 0) {
289 0 : ap_rep_blob = data_blob_null;
290 0 : error_code = code;
291 0 : goto reply;
292 : }
293 :
294 38 : status = gensec_wrap(gensec_security,
295 : tmp_ctx,
296 : &kpasswd_dec_reply,
297 : &enc_data_blob);
298 38 : if (!NT_STATUS_IS_OK(status)) {
299 0 : ap_rep_blob = data_blob_null;
300 0 : error_code = KRB5_KPASSWD_HARDERROR;
301 0 : error_string = talloc_asprintf(tmp_ctx,
302 : "gensec_wrap failed - %s\n",
303 : nt_errstr(status));
304 0 : DBG_ERR("%s", error_string);
305 0 : goto reply;
306 : }
307 :
308 70 : reply:
309 38 : if (error_code != 0) {
310 : krb5_data k_enc_data;
311 : krb5_data k_dec_data;
312 : const char *principal_string;
313 : krb5_principal server_principal;
314 :
315 0 : if (error_string == NULL) {
316 0 : DBG_ERR("Invalid error string! This should not happen\n");
317 0 : goto done;
318 : }
319 :
320 0 : ok = kpasswd_make_error_reply(tmp_ctx,
321 : error_code,
322 : error_string,
323 : &dec_data_blob);
324 0 : if (!ok) {
325 0 : DBG_ERR("Failed to create error reply\n");
326 0 : goto done;
327 : }
328 :
329 0 : k_dec_data.length = dec_data_blob.length;
330 0 : k_dec_data.data = (char *)dec_data_blob.data;
331 :
332 0 : principal_string = cli_credentials_get_principal(server_credentials,
333 : tmp_ctx);
334 0 : if (principal_string == NULL) {
335 0 : goto done;
336 : }
337 :
338 0 : code = smb_krb5_parse_name(kdc->smb_krb5_context->krb5_context,
339 : principal_string,
340 : &server_principal);
341 0 : if (code != 0) {
342 0 : DBG_ERR("Failed to create principal: %s\n",
343 : error_message(code));
344 0 : goto done;
345 : }
346 :
347 0 : code = smb_krb5_mk_error(kdc->smb_krb5_context->krb5_context,
348 0 : KRB5KDC_ERR_NONE + error_code,
349 : NULL, /* e_text */
350 : &k_dec_data,
351 : NULL, /* client */
352 : server_principal,
353 : &k_enc_data);
354 0 : krb5_free_principal(kdc->smb_krb5_context->krb5_context,
355 : server_principal);
356 0 : if (code != 0) {
357 0 : DBG_ERR("Failed to create krb5 error reply: %s\n",
358 : error_message(code));
359 0 : goto done;
360 : }
361 :
362 0 : enc_data_blob = data_blob_talloc(tmp_ctx,
363 : k_enc_data.data,
364 : k_enc_data.length);
365 0 : if (enc_data_blob.data == NULL) {
366 0 : DBG_ERR("Failed to allocate memory for error reply\n");
367 0 : goto done;
368 : }
369 : }
370 :
371 38 : *reply = data_blob_talloc(mem_ctx,
372 : NULL,
373 : HEADER_LEN + ap_rep_blob.length + enc_data_blob.length);
374 38 : if (reply->data == NULL) {
375 0 : goto done;
376 : }
377 38 : RSSVAL(reply->data, 0, reply->length);
378 38 : RSSVAL(reply->data, 2, 1);
379 38 : RSSVAL(reply->data, 4, ap_rep_blob.length);
380 70 : memcpy(reply->data + HEADER_LEN,
381 38 : ap_rep_blob.data,
382 : ap_rep_blob.length);
383 70 : memcpy(reply->data + HEADER_LEN + ap_rep_blob.length,
384 38 : enc_data_blob.data,
385 : enc_data_blob.length);
386 :
387 38 : rc = KDC_OK;
388 38 : done:
389 38 : talloc_free(tmp_ctx);
390 38 : return rc;
391 : }
|