fuzzing: fix fuzz_stable_sort_r_unstable comparison
[samba.git] / source3 / libads / krb5_setpw.c
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 static krb5_error_code kpasswd_err_to_krb5_err(krb5_error_code res_code)
40 {
41         switch (res_code) {
42         case KRB5_KPASSWD_ACCESSDENIED:
43                 return KRB5KDC_ERR_BADOPTION;
44         case KRB5_KPASSWD_INITIAL_FLAG_NEEDED:
45                 return KRB5KDC_ERR_BADOPTION;
46                 /* return KV5M_ALT_METHOD; MIT-only define */
47         case KRB5_KPASSWD_ETYPE_NOSUPP:
48                 return KRB5KDC_ERR_ETYPE_NOSUPP;
49         case KRB5_KPASSWD_BAD_PRINCIPAL:
50                 return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
51         case KRB5_KPASSWD_POLICY_REJECT:
52         case KRB5_KPASSWD_SOFTERROR:
53                 return KRB5KDC_ERR_POLICY;
54         default:
55                 return KRB5KRB_ERR_GENERIC;
56         }
57 }
58
59 ADS_STATUS ads_krb5_set_password(const char *principal,
60                                  const char *newpw,
61                                  const char *ccname)
62 {
63
64         ADS_STATUS aret;
65         krb5_error_code ret = 0;
66         krb5_context context = NULL;
67         krb5_principal princ = NULL;
68         krb5_ccache ccache = NULL;
69         int result_code;
70         krb5_data result_code_string = { 0 };
71         krb5_data result_string = { 0 };
72
73         if (ccname == NULL) {
74                 DBG_ERR("Missing ccache for [%s] and config [%s]\n",
75                         principal, getenv("KRB5_CONFIG"));
76                 return ADS_ERROR_NT(NT_STATUS_WRONG_CREDENTIAL_HANDLE);
77         }
78
79         ret = smb_krb5_init_context_common(&context);
80         if (ret) {
81                 DBG_ERR("kerberos init context failed (%s)\n",
82                         error_message(ret));
83                 return ADS_ERROR_KRB5(ret);
84         }
85
86         if (principal) {
87                 ret = smb_krb5_parse_name(context, principal, &princ);
88                 if (ret) {
89                         krb5_free_context(context);
90                         DEBUG(1, ("Failed to parse %s (%s)\n", principal,
91                                   error_message(ret)));
92                         return ADS_ERROR_KRB5(ret);
93                 }
94         }
95
96         ret = krb5_cc_resolve(context, ccname, &ccache);
97         if (ret) {
98                 krb5_free_principal(context, princ);
99                 krb5_free_context(context);
100                 DBG_WARNING("Failed to get creds from [%s] (%s)\n",
101                             ccname, error_message(ret));
102                 return ADS_ERROR_KRB5(ret);
103         }
104
105         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         if (ret) {
113                 DEBUG(1, ("krb5_set_password failed (%s)\n", error_message(ret)));
114                 aret = ADS_ERROR_KRB5(ret);
115                 goto done;
116         }
117
118         if (result_code != KRB5_KPASSWD_SUCCESS) {
119                 ret = kpasswd_err_to_krb5_err(result_code);
120                 DEBUG(1, ("krb5_set_password failed (%s)\n", error_message(ret)));
121                 aret = ADS_ERROR_KRB5(ret);
122                 goto done;
123         }
124
125         aret = ADS_SUCCESS;
126
127  done:
128         smb_krb5_free_data_contents(context, &result_code_string);
129         smb_krb5_free_data_contents(context, &result_string);
130         krb5_free_principal(context, princ);
131         krb5_cc_close(context, ccache);
132         krb5_free_context(context);
133
134         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 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         if (num_prompts == 0) return 0;
150
151         memset(prompts[0].reply->data, 0, prompts[0].reply->length);
152         if (prompts[0].reply->length > 0) {
153                 if (data) {
154                         strncpy((char *)prompts[0].reply->data,
155                                 (const char *)data,
156                                 prompts[0].reply->length-1);
157                         prompts[0].reply->length = strlen((const char *)prompts[0].reply->data);
158                 } else {
159                         prompts[0].reply->length = 0;
160                 }
161         }
162         return 0;
163 }
164
165 static ADS_STATUS ads_krb5_chg_password(const char *principal,
166                                         const char *oldpw,
167                                         const char *newpw)
168 {
169         ADS_STATUS aret;
170         krb5_error_code ret;
171         krb5_context context = NULL;
172         krb5_principal princ;
173         krb5_get_init_creds_opt *opts = NULL;
174         krb5_creds creds;
175         char *chpw_princ = NULL, *password;
176         char *realm = NULL;
177         int result_code;
178         krb5_data result_code_string = { 0 };
179         krb5_data result_string = { 0 };
180         smb_krb5_addresses *addr = NULL;
181
182         ret = smb_krb5_init_context_common(&context);
183         if (ret) {
184                 DBG_ERR("kerberos init context failed (%s)\n",
185                         error_message(ret));
186                 return ADS_ERROR_KRB5(ret);
187         }
188
189         if ((ret = smb_krb5_parse_name(context, principal, &princ))) {
190                 krb5_free_context(context);
191                 DEBUG(1,("Failed to parse %s (%s)\n", principal, error_message(ret)));
192                 return ADS_ERROR_KRB5(ret);
193         }
194
195         ret = krb5_get_init_creds_opt_alloc(context, &opts);
196         if (ret != 0) {
197                 krb5_free_context(context);
198                 DBG_WARNING("krb5_get_init_creds_opt_alloc failed: %s\n",
199                             error_message(ret));
200                 return ADS_ERROR_KRB5(ret);
201         }
202
203         krb5_get_init_creds_opt_set_tkt_life(opts, 5 * 60);
204         krb5_get_init_creds_opt_set_renew_life(opts, 0);
205         krb5_get_init_creds_opt_set_forwardable(opts, 0);
206         krb5_get_init_creds_opt_set_proxiable(opts, 0);
207 #ifdef SAMBA4_USES_HEIMDAL
208         krb5_get_init_creds_opt_set_win2k(context, opts, true);
209         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         ret = smb_krb5_gen_netbios_krb5_address(&addr, lp_netbios_name());
234         if (ret) {
235                 krb5_free_principal(context, princ);
236                 krb5_get_init_creds_opt_free(context, opts);
237                 krb5_free_context(context);
238                 return ADS_ERROR_KRB5(ret);
239         }
240         krb5_get_init_creds_opt_set_address_list(opts, addr->addrs);
241
242         realm = smb_krb5_principal_get_realm(NULL, context, princ);
243
244         /* We have to obtain an INITIAL changepw ticket for changing password */
245         if (asprintf(&chpw_princ, "kadmin/changepw@%s", realm) == -1) {
246                 krb5_free_principal(context, princ);
247                 krb5_get_init_creds_opt_free(context, opts);
248                 smb_krb5_free_addresses(context, addr);
249                 krb5_free_context(context);
250                 TALLOC_FREE(realm);
251                 DEBUG(1, ("ads_krb5_chg_password: asprintf fail\n"));
252                 return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
253         }
254
255         TALLOC_FREE(realm);
256         password = SMB_STRDUP(oldpw);
257         ret = krb5_get_init_creds_password(context, &creds, princ, password,
258                                            kerb_prompter, NULL,
259                                            0, chpw_princ, opts);
260         krb5_get_init_creds_opt_free(context, opts);
261         smb_krb5_free_addresses(context, addr);
262         SAFE_FREE(chpw_princ);
263         SAFE_FREE(password);
264
265         if (ret) {
266                 if (ret == KRB5KRB_AP_ERR_BAD_INTEGRITY) {
267                         DEBUG(1,("Password incorrect while getting initial ticket\n"));
268                 } else {
269                         DEBUG(1,("krb5_get_init_creds_password failed (%s)\n", error_message(ret)));
270                 }
271                 krb5_free_principal(context, princ);
272                 krb5_free_context(context);
273                 return ADS_ERROR_KRB5(ret);
274         }
275
276         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         if (ret) {
285                 DEBUG(1, ("krb5_change_password failed (%s)\n", error_message(ret)));
286                 aret = ADS_ERROR_KRB5(ret);
287                 goto done;
288         }
289
290         if (result_code != KRB5_KPASSWD_SUCCESS) {
291                 ret = kpasswd_err_to_krb5_err(result_code);
292                 DEBUG(1, ("krb5_change_password failed (%s)\n", error_message(ret)));
293                 aret = ADS_ERROR_KRB5(ret);
294                 goto done;
295         }
296
297         aret = ADS_SUCCESS;
298
299  done:
300         smb_krb5_free_data_contents(context, &result_code_string);
301         smb_krb5_free_data_contents(context, &result_string);
302         krb5_free_principal(context, princ);
303         krb5_free_context(context);
304
305         return aret;
306 }
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 {
313         TALLOC_CTX *frame = NULL;
314         krb5_context ctx = NULL;
315         krb5_ccache ccid = NULL;
316         char *ccname = NULL;
317         ADS_STATUS status;
318         int ret;
319
320         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                 return ads_krb5_chg_password(target_principal,
326                                              auth_password,
327                                              new_password);
328         }
329
330         frame = talloc_stackframe();
331
332         ret = smb_krb5_init_context_common(&ctx);
333         if (ret != 0) {
334                 status = ADS_ERROR_KRB5(ret);
335                 goto done;
336         }
337
338         ret = smb_krb5_cc_new_unique_memory(ctx,
339                                             frame,
340                                             &ccname,
341                                             &ccid);
342         if (ret != 0) {
343                 status = ADS_ERROR_KRB5(ret);
344                 goto done;
345         }
346
347         ret = kerberos_kinit_password(auth_principal,
348                                       auth_password,
349                                       0, /* timeoutset */
350                                       ccname);
351         if (ret != 0) {
352                 DBG_ERR("Failed kinit for principal %s (%s)\n",
353                         auth_principal, error_message(ret));
354                 status = ADS_ERROR_KRB5(ret);
355                 goto done;
356         }
357
358         status = ads_krb5_set_password(target_principal,
359                                        new_password,
360                                        ccname);
361         if (!ADS_ERR_OK(status)) {
362                 DBG_ERR("Failed to set password for %s as %s: %s\n",
363                         target_principal,
364                         auth_principal,
365                         ads_errstr(status));
366                 goto done;
367         }
368
369 done:
370         if (ccid != NULL) {
371                 krb5_cc_destroy(ctx, ccid);
372                 ccid = NULL;
373         }
374         if (ctx != NULL) {
375                 krb5_free_context(ctx);
376                 ctx = NULL;
377         }
378         TALLOC_FREE(frame);
379         return status;
380 }
381
382 #endif