Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 :
4 : Samba kpasswd implementation
5 :
6 : Copyright (c) 2016 Andreas Schneider <asn@samba.org>
7 :
8 : This program is free software; you can redistribute it and/or modify
9 : it under the terms of the GNU General Public License as published by
10 : the Free Software Foundation; either version 3 of the License, or
11 : (at your option) any later version.
12 :
13 : This program is distributed in the hope that it will be useful,
14 : but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : GNU General Public License for more details.
17 :
18 : You should have received a copy of the GNU General Public License
19 : along with this program. If not, see <http://www.gnu.org/licenses/>.
20 : */
21 :
22 : #include "includes.h"
23 : #include "samba/service_task.h"
24 : #include "param/param.h"
25 : #include "auth/auth.h"
26 : #include "auth/gensec/gensec.h"
27 : #include "gensec_krb5_helpers.h"
28 : #include "kdc/kdc-server.h"
29 : #include "kdc/kpasswd_glue.h"
30 : #include "kdc/kpasswd-service.h"
31 : #include "kdc/kpasswd-helper.h"
32 : #include "../lib/util/asn1.h"
33 :
34 : #define RFC3244_VERSION 0xff80
35 :
36 : krb5_error_code decode_krb5_setpw_req(const krb5_data *code,
37 : krb5_data **password_out,
38 : krb5_principal *target_out);
39 :
40 : /*
41 : * A fallback for when MIT refuses to parse a setpw structure without the
42 : * (optional) target principal and realm
43 : */
44 0 : static bool decode_krb5_setpw_req_simple(TALLOC_CTX *mem_ctx,
45 : const DATA_BLOB *decoded_data,
46 : DATA_BLOB *clear_data)
47 : {
48 0 : struct asn1_data *asn1 = NULL;
49 : bool ret;
50 :
51 0 : asn1 = asn1_init(mem_ctx, 3);
52 0 : if (asn1 == NULL) {
53 0 : return false;
54 : }
55 :
56 0 : ret = asn1_load(asn1, *decoded_data);
57 0 : if (!ret) {
58 0 : goto out;
59 : }
60 :
61 0 : ret = asn1_start_tag(asn1, ASN1_SEQUENCE(0));
62 0 : if (!ret) {
63 0 : goto out;
64 : }
65 0 : ret = asn1_start_tag(asn1, ASN1_CONTEXT(0));
66 0 : if (!ret) {
67 0 : goto out;
68 : }
69 0 : ret = asn1_read_OctetString(asn1, mem_ctx, clear_data);
70 0 : if (!ret) {
71 0 : goto out;
72 : }
73 :
74 0 : ret = asn1_end_tag(asn1);
75 0 : if (!ret) {
76 0 : goto out;
77 : }
78 0 : ret = asn1_end_tag(asn1);
79 :
80 0 : out:
81 0 : asn1_free(asn1);
82 :
83 0 : return ret;
84 : }
85 :
86 4 : static krb5_error_code kpasswd_change_password(struct kdc_server *kdc,
87 : TALLOC_CTX *mem_ctx,
88 : const struct gensec_security *gensec_security,
89 : struct auth_session_info *session_info,
90 : DATA_BLOB *password,
91 : DATA_BLOB *kpasswd_reply,
92 : const char **error_string)
93 : {
94 : NTSTATUS status;
95 4 : NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
96 : enum samPwdChangeReason reject_reason;
97 4 : const char *reject_string = NULL;
98 : struct samr_DomInfo1 *dominfo;
99 : bool ok;
100 : int ret;
101 :
102 : /*
103 : * We're doing a password change (rather than a password set), so check
104 : * that we were given an initial ticket.
105 : */
106 4 : ret = gensec_krb5_initial_ticket(gensec_security);
107 4 : if (ret != 1) {
108 0 : *error_string = "Expected an initial ticket";
109 0 : return KRB5_KPASSWD_INITIAL_FLAG_NEEDED;
110 : }
111 :
112 4 : status = samdb_kpasswd_change_password(mem_ctx,
113 4 : kdc->task->lp_ctx,
114 4 : kdc->task->event_ctx,
115 : session_info,
116 : password,
117 : &reject_reason,
118 : &dominfo,
119 : &reject_string,
120 : &result);
121 4 : if (!NT_STATUS_IS_OK(status)) {
122 0 : ok = kpasswd_make_error_reply(mem_ctx,
123 : KRB5_KPASSWD_ACCESSDENIED,
124 : reject_string,
125 : kpasswd_reply);
126 0 : if (!ok) {
127 0 : *error_string = "Failed to create reply";
128 0 : return KRB5_KPASSWD_HARDERROR;
129 : }
130 : /* We want to send an an authenticated packet. */
131 0 : return 0;
132 : }
133 :
134 4 : ok = kpasswd_make_pwchange_reply(mem_ctx,
135 : result,
136 : reject_reason,
137 : dominfo,
138 : kpasswd_reply);
139 4 : if (!ok) {
140 0 : *error_string = "Failed to create reply";
141 0 : return KRB5_KPASSWD_HARDERROR;
142 : }
143 :
144 4 : return 0;
145 : }
146 :
147 2 : static krb5_error_code kpasswd_set_password(struct kdc_server *kdc,
148 : TALLOC_CTX *mem_ctx,
149 : const struct gensec_security *gensec_security,
150 : struct auth_session_info *session_info,
151 : DATA_BLOB *decoded_data,
152 : DATA_BLOB *kpasswd_reply,
153 : const char **error_string)
154 : {
155 2 : krb5_context context = kdc->smb_krb5_context->krb5_context;
156 : DATA_BLOB clear_data;
157 : krb5_data k_dec_data;
158 2 : krb5_data *k_clear_data = NULL;
159 2 : krb5_principal target_principal = NULL;
160 : krb5_error_code code;
161 : DATA_BLOB password;
162 2 : char *target_realm = NULL;
163 2 : char *target_name = NULL;
164 2 : char *target_principal_string = NULL;
165 2 : bool is_service_principal = false;
166 : bool ok;
167 : size_t num_components;
168 2 : enum samPwdChangeReason reject_reason = SAM_PWD_CHANGE_NO_ERROR;
169 2 : struct samr_DomInfo1 *dominfo = NULL;
170 : NTSTATUS status;
171 :
172 2 : k_dec_data.length = decoded_data->length;
173 2 : k_dec_data.data = (char *)decoded_data->data;
174 :
175 2 : code = decode_krb5_setpw_req(&k_dec_data,
176 : &k_clear_data,
177 : &target_principal);
178 2 : if (code == 0) {
179 2 : clear_data.data = (uint8_t *)k_clear_data->data;
180 2 : clear_data.length = k_clear_data->length;
181 : } else {
182 0 : target_principal = NULL;
183 :
184 : /*
185 : * The MIT decode failed, so fall back to trying the simple
186 : * case, without target_principal.
187 : */
188 0 : ok = decode_krb5_setpw_req_simple(mem_ctx,
189 : decoded_data,
190 : &clear_data);
191 0 : if (!ok) {
192 0 : DBG_WARNING("decode_krb5_setpw_req failed: %s\n",
193 : error_message(code));
194 0 : ok = kpasswd_make_error_reply(mem_ctx,
195 : KRB5_KPASSWD_MALFORMED,
196 : "Failed to decode packet",
197 : kpasswd_reply);
198 0 : if (!ok) {
199 0 : *error_string = "Failed to create reply";
200 0 : return KRB5_KPASSWD_HARDERROR;
201 : }
202 0 : return 0;
203 : }
204 : }
205 :
206 2 : ok = convert_string_talloc_handle(mem_ctx,
207 2 : lpcfg_iconv_handle(kdc->task->lp_ctx),
208 : CH_UTF8,
209 : CH_UTF16,
210 2 : clear_data.data,
211 : clear_data.length,
212 : (void **)&password.data,
213 : &password.length);
214 2 : if (k_clear_data != NULL) {
215 2 : krb5_free_data(context, k_clear_data);
216 : }
217 2 : if (!ok) {
218 0 : DBG_WARNING("String conversion failed\n");
219 0 : *error_string = "String conversion failed";
220 0 : return KRB5_KPASSWD_HARDERROR;
221 : }
222 :
223 2 : if (target_principal != NULL) {
224 2 : target_realm = smb_krb5_principal_get_realm(
225 : mem_ctx, context, target_principal);
226 2 : code = krb5_unparse_name_flags(context,
227 : target_principal,
228 : KRB5_PRINCIPAL_UNPARSE_NO_REALM,
229 : &target_name);
230 2 : if (code != 0) {
231 0 : DBG_WARNING("Failed to parse principal\n");
232 0 : *error_string = "String conversion failed";
233 0 : return KRB5_KPASSWD_HARDERROR;
234 : }
235 : }
236 :
237 2 : if ((target_name != NULL && target_realm == NULL) ||
238 2 : (target_name == NULL && target_realm != NULL)) {
239 0 : krb5_free_principal(context, target_principal);
240 0 : TALLOC_FREE(target_realm);
241 0 : SAFE_FREE(target_name);
242 :
243 0 : ok = kpasswd_make_error_reply(mem_ctx,
244 : KRB5_KPASSWD_MALFORMED,
245 : "Realm and principal must be "
246 : "both present, or neither "
247 : "present",
248 : kpasswd_reply);
249 0 : if (!ok) {
250 0 : *error_string = "Failed to create reply";
251 0 : return KRB5_KPASSWD_HARDERROR;
252 : }
253 0 : return 0;
254 : }
255 :
256 2 : if (target_name != NULL && target_realm != NULL) {
257 2 : TALLOC_FREE(target_realm);
258 2 : SAFE_FREE(target_name);
259 : } else {
260 0 : krb5_free_principal(context, target_principal);
261 0 : TALLOC_FREE(target_realm);
262 0 : SAFE_FREE(target_name);
263 :
264 0 : return kpasswd_change_password(kdc,
265 : mem_ctx,
266 : gensec_security,
267 : session_info,
268 : &password,
269 : kpasswd_reply,
270 : error_string);
271 : }
272 :
273 2 : num_components = krb5_princ_size(context, target_principal);
274 2 : if (num_components >= 2) {
275 0 : is_service_principal = true;
276 0 : code = krb5_unparse_name_flags(context,
277 : target_principal,
278 : KRB5_PRINCIPAL_UNPARSE_SHORT,
279 : &target_principal_string);
280 : } else {
281 2 : code = krb5_unparse_name(context,
282 : target_principal,
283 : &target_principal_string);
284 : }
285 2 : krb5_free_principal(context, target_principal);
286 2 : if (code != 0) {
287 0 : ok = kpasswd_make_error_reply(mem_ctx,
288 : KRB5_KPASSWD_MALFORMED,
289 : "Failed to parse principal",
290 : kpasswd_reply);
291 0 : if (!ok) {
292 0 : *error_string = "Failed to create reply";
293 0 : return KRB5_KPASSWD_HARDERROR;
294 : }
295 : }
296 :
297 2 : status = kpasswd_samdb_set_password(mem_ctx,
298 2 : kdc->task->event_ctx,
299 2 : kdc->task->lp_ctx,
300 : session_info,
301 : is_service_principal,
302 : target_principal_string,
303 : &password,
304 : &reject_reason,
305 : &dominfo);
306 2 : if (!NT_STATUS_IS_OK(status)) {
307 0 : DBG_ERR("kpasswd_samdb_set_password failed - %s\n",
308 : nt_errstr(status));
309 : }
310 :
311 2 : ok = kpasswd_make_pwchange_reply(mem_ctx,
312 : status,
313 : reject_reason,
314 : dominfo,
315 : kpasswd_reply);
316 2 : if (!ok) {
317 0 : *error_string = "Failed to create reply";
318 0 : return KRB5_KPASSWD_HARDERROR;
319 : }
320 :
321 2 : return 0;
322 : }
323 :
324 6 : krb5_error_code kpasswd_handle_request(struct kdc_server *kdc,
325 : TALLOC_CTX *mem_ctx,
326 : struct gensec_security *gensec_security,
327 : uint16_t verno,
328 : DATA_BLOB *decoded_data,
329 : DATA_BLOB *kpasswd_reply,
330 : const char **error_string)
331 : {
332 : struct auth_session_info *session_info;
333 : NTSTATUS status;
334 : krb5_error_code code;
335 :
336 6 : status = gensec_session_info(gensec_security,
337 : mem_ctx,
338 : &session_info);
339 6 : if (!NT_STATUS_IS_OK(status)) {
340 0 : *error_string = talloc_asprintf(mem_ctx,
341 : "gensec_session_info failed - "
342 : "%s",
343 : nt_errstr(status));
344 0 : return KRB5_KPASSWD_HARDERROR;
345 : }
346 :
347 : /*
348 : * Since the kpasswd service shares its keys with the krbtgt, we might
349 : * have received a TGT rather than a kpasswd ticket. We need to check
350 : * the ticket type to ensure that TGTs cannot be misused in this manner.
351 : */
352 6 : code = kpasswd_check_non_tgt(session_info,
353 : error_string);
354 6 : if (code != 0) {
355 0 : DBG_WARNING("%s\n", *error_string);
356 0 : return code;
357 : }
358 :
359 6 : switch(verno) {
360 4 : case 1: {
361 : DATA_BLOB password;
362 : bool ok;
363 :
364 4 : ok = convert_string_talloc_handle(mem_ctx,
365 4 : lpcfg_iconv_handle(kdc->task->lp_ctx),
366 : CH_UTF8,
367 : CH_UTF16,
368 4 : (const char *)decoded_data->data,
369 : decoded_data->length,
370 : (void **)&password.data,
371 : &password.length);
372 4 : if (!ok) {
373 0 : *error_string = "String conversion failed!";
374 0 : DBG_WARNING("%s\n", *error_string);
375 0 : return KRB5_KPASSWD_HARDERROR;
376 : }
377 :
378 4 : return kpasswd_change_password(kdc,
379 : mem_ctx,
380 : gensec_security,
381 : session_info,
382 : &password,
383 : kpasswd_reply,
384 : error_string);
385 : }
386 2 : case RFC3244_VERSION: {
387 2 : return kpasswd_set_password(kdc,
388 : mem_ctx,
389 : gensec_security,
390 : session_info,
391 : decoded_data,
392 : kpasswd_reply,
393 : error_string);
394 : }
395 0 : default:
396 0 : return KRB5_KPASSWD_BAD_VERSION;
397 : }
398 :
399 : return 0;
400 : }
|