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