Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 : krb5 set password implementation
4 : Copyright (C) Andrew Tridgell 2001
5 : Copyright (C) Remus Koos 2001 (remuskoos@yahoo.com)
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 : #include "includes.h"
22 : #include "smb_krb5.h"
23 : #include "libads/kerberos_proto.h"
24 : #include "../lib/util/asn1.h"
25 :
26 : #ifdef HAVE_KRB5
27 :
28 : /* Those are defined by kerberos-set-passwd-02.txt and are probably
29 : * not supported by M$ implementation */
30 : #define KRB5_KPASSWD_POLICY_REJECT 8
31 : #define KRB5_KPASSWD_BAD_PRINCIPAL 9
32 : #define KRB5_KPASSWD_ETYPE_NOSUPP 10
33 :
34 : /*
35 : * we've got to be able to distinguish KRB_ERRORs from other
36 : * requests - valid response for CHPW v2 replies.
37 : */
38 :
39 0 : static krb5_error_code kpasswd_err_to_krb5_err(krb5_error_code res_code)
40 : {
41 0 : switch (res_code) {
42 0 : case KRB5_KPASSWD_ACCESSDENIED:
43 0 : return KRB5KDC_ERR_BADOPTION;
44 0 : case KRB5_KPASSWD_INITIAL_FLAG_NEEDED:
45 0 : return KRB5KDC_ERR_BADOPTION;
46 : /* return KV5M_ALT_METHOD; MIT-only define */
47 0 : case KRB5_KPASSWD_ETYPE_NOSUPP:
48 0 : return KRB5KDC_ERR_ETYPE_NOSUPP;
49 0 : case KRB5_KPASSWD_BAD_PRINCIPAL:
50 0 : return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
51 0 : case KRB5_KPASSWD_POLICY_REJECT:
52 : case KRB5_KPASSWD_SOFTERROR:
53 0 : return KRB5KDC_ERR_POLICY;
54 0 : default:
55 0 : return KRB5KRB_ERR_GENERIC;
56 : }
57 : }
58 :
59 6 : ADS_STATUS ads_krb5_set_password(const char *principal,
60 : const char *newpw,
61 : const char *ccname)
62 : {
63 :
64 0 : ADS_STATUS aret;
65 6 : krb5_error_code ret = 0;
66 6 : krb5_context context = NULL;
67 6 : krb5_principal princ = NULL;
68 6 : krb5_ccache ccache = NULL;
69 0 : int result_code;
70 6 : krb5_data result_code_string = { 0 };
71 6 : krb5_data result_string = { 0 };
72 :
73 6 : if (ccname == NULL) {
74 0 : DBG_ERR("Missing ccache for [%s] and config [%s]\n",
75 : principal, getenv("KRB5_CONFIG"));
76 0 : return ADS_ERROR_NT(NT_STATUS_WRONG_CREDENTIAL_HANDLE);
77 : }
78 :
79 6 : ret = smb_krb5_init_context_common(&context);
80 6 : if (ret) {
81 0 : DBG_ERR("kerberos init context failed (%s)\n",
82 : error_message(ret));
83 0 : return ADS_ERROR_KRB5(ret);
84 : }
85 :
86 6 : if (principal) {
87 6 : ret = smb_krb5_parse_name(context, principal, &princ);
88 6 : if (ret) {
89 0 : krb5_free_context(context);
90 0 : DEBUG(1, ("Failed to parse %s (%s)\n", principal,
91 : error_message(ret)));
92 0 : return ADS_ERROR_KRB5(ret);
93 : }
94 : }
95 :
96 6 : ret = krb5_cc_resolve(context, ccname, &ccache);
97 6 : if (ret) {
98 0 : krb5_free_principal(context, princ);
99 0 : krb5_free_context(context);
100 0 : DBG_WARNING("Failed to get creds from [%s] (%s)\n",
101 : ccname, error_message(ret));
102 0 : return ADS_ERROR_KRB5(ret);
103 : }
104 :
105 6 : ret = krb5_set_password_using_ccache(context,
106 : ccache,
107 : discard_const_p(char, newpw),
108 : princ,
109 : &result_code,
110 : &result_code_string,
111 : &result_string);
112 6 : if (ret) {
113 0 : DEBUG(1, ("krb5_set_password failed (%s)\n", error_message(ret)));
114 0 : aret = ADS_ERROR_KRB5(ret);
115 0 : goto done;
116 : }
117 :
118 6 : if (result_code != KRB5_KPASSWD_SUCCESS) {
119 0 : ret = kpasswd_err_to_krb5_err(result_code);
120 0 : DEBUG(1, ("krb5_set_password failed (%s)\n", error_message(ret)));
121 0 : aret = ADS_ERROR_KRB5(ret);
122 0 : goto done;
123 : }
124 :
125 6 : aret = ADS_SUCCESS;
126 :
127 6 : done:
128 6 : smb_krb5_free_data_contents(context, &result_code_string);
129 6 : smb_krb5_free_data_contents(context, &result_string);
130 6 : krb5_free_principal(context, princ);
131 6 : krb5_cc_close(context, ccache);
132 6 : krb5_free_context(context);
133 :
134 6 : return aret;
135 : }
136 :
137 : /*
138 : we use a prompter to avoid a crash bug in the kerberos libs when
139 : dealing with empty passwords
140 : this prompter is just a string copy ...
141 : */
142 : static krb5_error_code
143 0 : kerb_prompter(krb5_context ctx, void *data,
144 : const char *name,
145 : const char *banner,
146 : int num_prompts,
147 : krb5_prompt prompts[])
148 : {
149 0 : if (num_prompts == 0) return 0;
150 :
151 0 : memset(prompts[0].reply->data, 0, prompts[0].reply->length);
152 0 : if (prompts[0].reply->length > 0) {
153 0 : if (data) {
154 0 : strncpy((char *)prompts[0].reply->data,
155 : (const char *)data,
156 0 : prompts[0].reply->length-1);
157 0 : prompts[0].reply->length = strlen((const char *)prompts[0].reply->data);
158 : } else {
159 0 : prompts[0].reply->length = 0;
160 : }
161 : }
162 0 : return 0;
163 : }
164 :
165 6 : static ADS_STATUS ads_krb5_chg_password(const char *principal,
166 : const char *oldpw,
167 : const char *newpw)
168 : {
169 0 : ADS_STATUS aret;
170 0 : krb5_error_code ret;
171 6 : krb5_context context = NULL;
172 0 : krb5_principal princ;
173 6 : krb5_get_init_creds_opt *opts = NULL;
174 0 : krb5_creds creds;
175 6 : char *chpw_princ = NULL, *password;
176 6 : char *realm = NULL;
177 0 : int result_code;
178 6 : krb5_data result_code_string = { 0 };
179 6 : krb5_data result_string = { 0 };
180 6 : smb_krb5_addresses *addr = NULL;
181 :
182 6 : ret = smb_krb5_init_context_common(&context);
183 6 : if (ret) {
184 0 : DBG_ERR("kerberos init context failed (%s)\n",
185 : error_message(ret));
186 0 : return ADS_ERROR_KRB5(ret);
187 : }
188 :
189 6 : if ((ret = smb_krb5_parse_name(context, principal, &princ))) {
190 0 : krb5_free_context(context);
191 0 : DEBUG(1,("Failed to parse %s (%s)\n", principal, error_message(ret)));
192 0 : return ADS_ERROR_KRB5(ret);
193 : }
194 :
195 6 : ret = krb5_get_init_creds_opt_alloc(context, &opts);
196 6 : if (ret != 0) {
197 0 : krb5_free_context(context);
198 0 : DBG_WARNING("krb5_get_init_creds_opt_alloc failed: %s\n",
199 : error_message(ret));
200 0 : return ADS_ERROR_KRB5(ret);
201 : }
202 :
203 6 : krb5_get_init_creds_opt_set_tkt_life(opts, 5 * 60);
204 6 : krb5_get_init_creds_opt_set_renew_life(opts, 0);
205 6 : krb5_get_init_creds_opt_set_forwardable(opts, 0);
206 6 : krb5_get_init_creds_opt_set_proxiable(opts, 0);
207 : #ifdef SAMBA4_USES_HEIMDAL
208 3 : krb5_get_init_creds_opt_set_win2k(context, opts, true);
209 3 : krb5_get_init_creds_opt_set_canonicalize(context, opts, true);
210 : #else /* MIT */
211 : #if 0
212 : /*
213 : * FIXME
214 : *
215 : * Due to an upstream MIT Kerberos bug, this feature is not
216 : * not working. Affection versions (2019-10-09): <= 1.17
217 : *
218 : * Reproducer:
219 : * kinit -C aDmInIsTrAtOr@ACME.COM -S kadmin/changepw@ACME.COM
220 : *
221 : * This is NOT a problem if the service is a krbtgt.
222 : *
223 : * https://bugzilla.samba.org/show_bug.cgi?id=14155
224 : */
225 : krb5_get_init_creds_opt_set_canonicalize(opts, true);
226 : #endif
227 : #endif /* MIT */
228 :
229 : /* note that heimdal will fill in the local addresses if the addresses
230 : * in the creds_init_opt are all empty and then later fail with invalid
231 : * address, sending our local netbios krb5 address - just like windows
232 : * - avoids this - gd */
233 6 : ret = smb_krb5_gen_netbios_krb5_address(&addr, lp_netbios_name());
234 6 : if (ret) {
235 0 : krb5_free_principal(context, princ);
236 0 : krb5_get_init_creds_opt_free(context, opts);
237 0 : krb5_free_context(context);
238 0 : return ADS_ERROR_KRB5(ret);
239 : }
240 6 : krb5_get_init_creds_opt_set_address_list(opts, addr->addrs);
241 :
242 6 : realm = smb_krb5_principal_get_realm(NULL, context, princ);
243 :
244 : /* We have to obtain an INITIAL changepw ticket for changing password */
245 6 : if (asprintf(&chpw_princ, "kadmin/changepw@%s", realm) == -1) {
246 0 : krb5_free_principal(context, princ);
247 0 : krb5_get_init_creds_opt_free(context, opts);
248 0 : smb_krb5_free_addresses(context, addr);
249 0 : krb5_free_context(context);
250 0 : TALLOC_FREE(realm);
251 0 : DEBUG(1, ("ads_krb5_chg_password: asprintf fail\n"));
252 0 : return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
253 : }
254 :
255 6 : TALLOC_FREE(realm);
256 6 : password = SMB_STRDUP(oldpw);
257 6 : ret = krb5_get_init_creds_password(context, &creds, princ, password,
258 : kerb_prompter, NULL,
259 : 0, chpw_princ, opts);
260 6 : krb5_get_init_creds_opt_free(context, opts);
261 6 : smb_krb5_free_addresses(context, addr);
262 6 : SAFE_FREE(chpw_princ);
263 6 : SAFE_FREE(password);
264 :
265 6 : if (ret) {
266 0 : if (ret == KRB5KRB_AP_ERR_BAD_INTEGRITY) {
267 0 : DEBUG(1,("Password incorrect while getting initial ticket\n"));
268 : } else {
269 0 : DEBUG(1,("krb5_get_init_creds_password failed (%s)\n", error_message(ret)));
270 : }
271 0 : krb5_free_principal(context, princ);
272 0 : krb5_free_context(context);
273 0 : return ADS_ERROR_KRB5(ret);
274 : }
275 :
276 6 : ret = krb5_set_password(context,
277 : &creds,
278 : discard_const_p(char, newpw),
279 : NULL,
280 : &result_code,
281 : &result_code_string,
282 : &result_string);
283 :
284 6 : if (ret) {
285 0 : DEBUG(1, ("krb5_change_password failed (%s)\n", error_message(ret)));
286 0 : aret = ADS_ERROR_KRB5(ret);
287 0 : goto done;
288 : }
289 :
290 6 : if (result_code != KRB5_KPASSWD_SUCCESS) {
291 0 : ret = kpasswd_err_to_krb5_err(result_code);
292 0 : DEBUG(1, ("krb5_change_password failed (%s)\n", error_message(ret)));
293 0 : aret = ADS_ERROR_KRB5(ret);
294 0 : goto done;
295 : }
296 :
297 6 : aret = ADS_SUCCESS;
298 :
299 6 : done:
300 6 : smb_krb5_free_data_contents(context, &result_code_string);
301 6 : smb_krb5_free_data_contents(context, &result_string);
302 6 : krb5_free_principal(context, princ);
303 6 : krb5_free_context(context);
304 :
305 6 : return aret;
306 : }
307 :
308 8 : ADS_STATUS kerberos_set_password(const char *auth_principal,
309 : const char *auth_password,
310 : const char *target_principal,
311 : const char *new_password)
312 : {
313 8 : TALLOC_CTX *frame = NULL;
314 8 : krb5_context ctx = NULL;
315 8 : krb5_ccache ccid = NULL;
316 8 : char *ccname = NULL;
317 0 : ADS_STATUS status;
318 0 : int ret;
319 :
320 8 : if (strcmp(auth_principal, target_principal) == 0) {
321 : /*
322 : * kinit is done inside of ads_krb5_chg_password()
323 : * without any ccache, just with raw krb5_creds.
324 : */
325 6 : return ads_krb5_chg_password(target_principal,
326 : auth_password,
327 : new_password);
328 : }
329 :
330 2 : frame = talloc_stackframe();
331 :
332 2 : ret = smb_krb5_init_context_common(&ctx);
333 2 : if (ret != 0) {
334 0 : status = ADS_ERROR_KRB5(ret);
335 0 : goto done;
336 : }
337 :
338 2 : ret = smb_krb5_cc_new_unique_memory(ctx,
339 : frame,
340 : &ccname,
341 : &ccid);
342 2 : if (ret != 0) {
343 0 : status = ADS_ERROR_KRB5(ret);
344 0 : goto done;
345 : }
346 :
347 2 : ret = kerberos_kinit_password(auth_principal,
348 : auth_password,
349 : 0, /* timeoutset */
350 : ccname);
351 2 : if (ret != 0) {
352 0 : DBG_ERR("Failed kinit for principal %s (%s)\n",
353 : auth_principal, error_message(ret));
354 0 : status = ADS_ERROR_KRB5(ret);
355 0 : goto done;
356 : }
357 :
358 2 : status = ads_krb5_set_password(target_principal,
359 : new_password,
360 : ccname);
361 2 : if (!ADS_ERR_OK(status)) {
362 0 : DBG_ERR("Failed to set password for %s as %s: %s\n",
363 : target_principal,
364 : auth_principal,
365 : ads_errstr(status));
366 0 : goto done;
367 : }
368 :
369 2 : done:
370 2 : if (ccid != NULL) {
371 2 : krb5_cc_destroy(ctx, ccid);
372 2 : ccid = NULL;
373 : }
374 2 : if (ctx != NULL) {
375 2 : krb5_free_context(ctx);
376 2 : ctx = NULL;
377 : }
378 2 : TALLOC_FREE(frame);
379 2 : return status;
380 : }
381 :
382 : #endif
|