/src/samba/source3/libads/krb5_setpw.c
Line | Count | Source |
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 | 0 | #define KRB5_KPASSWD_POLICY_REJECT 8 |
31 | 0 | #define KRB5_KPASSWD_BAD_PRINCIPAL 9 |
32 | 0 | #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 | | static krb5_error_code kpasswd_err_to_krb5_err(krb5_error_code res_code) |
40 | 0 | { |
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 | 0 | case KRB5_KPASSWD_SOFTERROR: |
53 | 0 | return KRB5KDC_ERR_POLICY; |
54 | 0 | default: |
55 | 0 | return KRB5KRB_ERR_GENERIC; |
56 | 0 | } |
57 | 0 | } |
58 | | |
59 | | ADS_STATUS ads_krb5_set_password(const char *principal, |
60 | | const char *newpw, |
61 | | const char *ccname) |
62 | 0 | { |
63 | |
|
64 | 0 | ADS_STATUS aret; |
65 | 0 | krb5_error_code ret = 0; |
66 | 0 | krb5_context context = NULL; |
67 | 0 | krb5_principal princ = NULL; |
68 | 0 | krb5_ccache ccache = NULL; |
69 | 0 | int result_code; |
70 | 0 | krb5_data result_code_string = { 0 }; |
71 | 0 | krb5_data result_string = { 0 }; |
72 | |
|
73 | 0 | if (ccname == NULL) { |
74 | 0 | DBG_ERR("Missing ccache for [%s] and config [%s]\n", |
75 | 0 | principal, getenv("KRB5_CONFIG")); |
76 | 0 | return ADS_ERROR_NT(NT_STATUS_WRONG_CREDENTIAL_HANDLE); |
77 | 0 | } |
78 | | |
79 | 0 | ret = smb_krb5_init_context_common(&context); |
80 | 0 | if (ret) { |
81 | 0 | DBG_ERR("kerberos init context failed (%s)\n", |
82 | 0 | error_message(ret)); |
83 | 0 | return ADS_ERROR_KRB5(ret); |
84 | 0 | } |
85 | | |
86 | 0 | if (principal) { |
87 | 0 | ret = smb_krb5_parse_name(context, principal, &princ); |
88 | 0 | if (ret) { |
89 | 0 | krb5_free_context(context); |
90 | 0 | DEBUG(1, ("Failed to parse %s (%s)\n", principal, |
91 | 0 | error_message(ret))); |
92 | 0 | return ADS_ERROR_KRB5(ret); |
93 | 0 | } |
94 | 0 | } |
95 | | |
96 | 0 | ret = krb5_cc_resolve(context, ccname, &ccache); |
97 | 0 | 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 | 0 | ccname, error_message(ret)); |
102 | 0 | return ADS_ERROR_KRB5(ret); |
103 | 0 | } |
104 | | |
105 | 0 | ret = krb5_set_password_using_ccache(context, |
106 | 0 | ccache, |
107 | 0 | discard_const_p(char, newpw), |
108 | 0 | princ, |
109 | 0 | &result_code, |
110 | 0 | &result_code_string, |
111 | 0 | &result_string); |
112 | 0 | 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 | 0 | } |
117 | | |
118 | 0 | 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 | 0 | } |
124 | | |
125 | 0 | aret = ADS_SUCCESS; |
126 | |
|
127 | 0 | done: |
128 | 0 | smb_krb5_free_data_contents(context, &result_code_string); |
129 | 0 | smb_krb5_free_data_contents(context, &result_string); |
130 | 0 | krb5_free_principal(context, princ); |
131 | 0 | krb5_cc_close(context, ccache); |
132 | 0 | krb5_free_context(context); |
133 | |
|
134 | 0 | return aret; |
135 | 0 | } |
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 | | kerb_prompter(krb5_context ctx, void *data, |
144 | | const char *name, |
145 | | const char *banner, |
146 | | int num_prompts, |
147 | | krb5_prompt prompts[]) |
148 | 0 | { |
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 | 0 | (const char *)data, |
156 | 0 | prompts[0].reply->length-1); |
157 | 0 | prompts[0].reply->length = strlen((const char *)prompts[0].reply->data); |
158 | 0 | } else { |
159 | 0 | prompts[0].reply->length = 0; |
160 | 0 | } |
161 | 0 | } |
162 | 0 | return 0; |
163 | 0 | } |
164 | | |
165 | | static ADS_STATUS ads_krb5_chg_password(const char *principal, |
166 | | const char *oldpw, |
167 | | const char *newpw) |
168 | 0 | { |
169 | 0 | ADS_STATUS aret; |
170 | 0 | krb5_error_code ret; |
171 | 0 | krb5_context context = NULL; |
172 | 0 | krb5_principal princ; |
173 | 0 | krb5_get_init_creds_opt *opts = NULL; |
174 | 0 | krb5_creds creds; |
175 | 0 | char *chpw_princ = NULL, *password; |
176 | 0 | char *realm = NULL; |
177 | 0 | int result_code; |
178 | 0 | krb5_data result_code_string = { 0 }; |
179 | 0 | krb5_data result_string = { 0 }; |
180 | 0 | smb_krb5_addresses *addr = NULL; |
181 | |
|
182 | 0 | ret = smb_krb5_init_context_common(&context); |
183 | 0 | if (ret) { |
184 | 0 | DBG_ERR("kerberos init context failed (%s)\n", |
185 | 0 | error_message(ret)); |
186 | 0 | return ADS_ERROR_KRB5(ret); |
187 | 0 | } |
188 | | |
189 | 0 | 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 | 0 | } |
194 | | |
195 | 0 | ret = krb5_get_init_creds_opt_alloc(context, &opts); |
196 | 0 | if (ret != 0) { |
197 | 0 | krb5_free_context(context); |
198 | 0 | DBG_WARNING("krb5_get_init_creds_opt_alloc failed: %s\n", |
199 | 0 | error_message(ret)); |
200 | 0 | return ADS_ERROR_KRB5(ret); |
201 | 0 | } |
202 | | |
203 | 0 | krb5_get_init_creds_opt_set_tkt_life(opts, 5 * 60); |
204 | 0 | krb5_get_init_creds_opt_set_renew_life(opts, 0); |
205 | 0 | krb5_get_init_creds_opt_set_forwardable(opts, 0); |
206 | 0 | krb5_get_init_creds_opt_set_proxiable(opts, 0); |
207 | 0 | #ifdef SAMBA4_USES_HEIMDAL |
208 | 0 | krb5_get_init_creds_opt_set_win2k(context, opts, true); |
209 | 0 | 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 | 0 | ret = smb_krb5_gen_netbios_krb5_address(&addr, lp_netbios_name()); |
234 | 0 | 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 | 0 | } |
240 | 0 | krb5_get_init_creds_opt_set_address_list(opts, addr->addrs); |
241 | |
|
242 | 0 | realm = smb_krb5_principal_get_realm(NULL, context, princ); |
243 | | |
244 | | /* We have to obtain an INITIAL changepw ticket for changing password */ |
245 | 0 | 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 | 0 | } |
254 | | |
255 | 0 | TALLOC_FREE(realm); |
256 | 0 | password = SMB_STRDUP(oldpw); |
257 | 0 | ret = krb5_get_init_creds_password(context, &creds, princ, password, |
258 | 0 | kerb_prompter, NULL, |
259 | 0 | 0, chpw_princ, opts); |
260 | 0 | krb5_get_init_creds_opt_free(context, opts); |
261 | 0 | smb_krb5_free_addresses(context, addr); |
262 | 0 | SAFE_FREE(chpw_princ); |
263 | 0 | SAFE_FREE(password); |
264 | |
|
265 | 0 | if (ret) { |
266 | 0 | if (ret == KRB5KRB_AP_ERR_BAD_INTEGRITY) { |
267 | 0 | DEBUG(1,("Password incorrect while getting initial ticket\n")); |
268 | 0 | } else { |
269 | 0 | DEBUG(1,("krb5_get_init_creds_password failed (%s)\n", error_message(ret))); |
270 | 0 | } |
271 | 0 | krb5_free_principal(context, princ); |
272 | 0 | krb5_free_context(context); |
273 | 0 | return ADS_ERROR_KRB5(ret); |
274 | 0 | } |
275 | | |
276 | 0 | ret = krb5_set_password(context, |
277 | 0 | &creds, |
278 | 0 | discard_const_p(char, newpw), |
279 | 0 | NULL, |
280 | 0 | &result_code, |
281 | 0 | &result_code_string, |
282 | 0 | &result_string); |
283 | |
|
284 | 0 | 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 | 0 | } |
289 | | |
290 | 0 | 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 | 0 | } |
296 | | |
297 | 0 | aret = ADS_SUCCESS; |
298 | |
|
299 | 0 | done: |
300 | 0 | smb_krb5_free_data_contents(context, &result_code_string); |
301 | 0 | smb_krb5_free_data_contents(context, &result_string); |
302 | 0 | krb5_free_principal(context, princ); |
303 | 0 | krb5_free_context(context); |
304 | |
|
305 | 0 | return aret; |
306 | 0 | } |
307 | | |
308 | | 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 | 0 | { |
313 | 0 | TALLOC_CTX *frame = NULL; |
314 | 0 | krb5_context ctx = NULL; |
315 | 0 | krb5_ccache ccid = NULL; |
316 | 0 | char *ccname = NULL; |
317 | 0 | ADS_STATUS status; |
318 | 0 | int ret; |
319 | |
|
320 | 0 | 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 | 0 | return ads_krb5_chg_password(target_principal, |
326 | 0 | auth_password, |
327 | 0 | new_password); |
328 | 0 | } |
329 | | |
330 | 0 | frame = talloc_stackframe(); |
331 | |
|
332 | 0 | ret = smb_krb5_init_context_common(&ctx); |
333 | 0 | if (ret != 0) { |
334 | 0 | status = ADS_ERROR_KRB5(ret); |
335 | 0 | goto done; |
336 | 0 | } |
337 | | |
338 | 0 | ret = smb_krb5_cc_new_unique_memory(ctx, |
339 | 0 | frame, |
340 | 0 | &ccname, |
341 | 0 | &ccid); |
342 | 0 | if (ret != 0) { |
343 | 0 | status = ADS_ERROR_KRB5(ret); |
344 | 0 | goto done; |
345 | 0 | } |
346 | | |
347 | 0 | ret = kerberos_kinit_password(auth_principal, |
348 | 0 | auth_password, |
349 | 0 | ccname); |
350 | 0 | if (ret != 0) { |
351 | 0 | DBG_ERR("Failed kinit for principal %s (%s)\n", |
352 | 0 | auth_principal, error_message(ret)); |
353 | 0 | status = ADS_ERROR_KRB5(ret); |
354 | 0 | goto done; |
355 | 0 | } |
356 | | |
357 | 0 | status = ads_krb5_set_password(target_principal, |
358 | 0 | new_password, |
359 | 0 | ccname); |
360 | 0 | if (!ADS_ERR_OK(status)) { |
361 | 0 | DBG_ERR("Failed to set password for %s as %s: %s\n", |
362 | 0 | target_principal, |
363 | 0 | auth_principal, |
364 | 0 | ads_errstr(status)); |
365 | 0 | goto done; |
366 | 0 | } |
367 | | |
368 | 0 | done: |
369 | 0 | if (ccid != NULL) { |
370 | 0 | krb5_cc_destroy(ctx, ccid); |
371 | 0 | ccid = NULL; |
372 | 0 | } |
373 | 0 | if (ctx != NULL) { |
374 | 0 | krb5_free_context(ctx); |
375 | 0 | ctx = NULL; |
376 | 0 | } |
377 | | TALLOC_FREE(frame); |
378 | 0 | return status; |
379 | 0 | } |
380 | | |
381 | | #endif |