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 *kdc_host, const char *principal,
60 : const char *newpw, int time_offset)
61 : {
62 :
63 0 : ADS_STATUS aret;
64 6 : krb5_error_code ret = 0;
65 6 : krb5_context context = NULL;
66 6 : krb5_principal princ = NULL;
67 6 : krb5_ccache ccache = NULL;
68 0 : int result_code;
69 6 : krb5_data result_code_string = { 0 };
70 6 : krb5_data result_string = { 0 };
71 :
72 6 : ret = smb_krb5_init_context_common(&context);
73 6 : if (ret) {
74 0 : DBG_ERR("kerberos init context failed (%s)\n",
75 : error_message(ret));
76 0 : return ADS_ERROR_KRB5(ret);
77 : }
78 :
79 6 : if (principal) {
80 6 : ret = smb_krb5_parse_name(context, principal, &princ);
81 6 : if (ret) {
82 0 : krb5_free_context(context);
83 0 : DEBUG(1, ("Failed to parse %s (%s)\n", principal,
84 : error_message(ret)));
85 0 : return ADS_ERROR_KRB5(ret);
86 : }
87 : }
88 :
89 6 : if (time_offset != 0) {
90 0 : krb5_set_real_time(context, time(NULL) + time_offset, 0);
91 : }
92 :
93 6 : ret = krb5_cc_default(context, &ccache);
94 6 : if (ret) {
95 0 : krb5_free_principal(context, princ);
96 0 : krb5_free_context(context);
97 0 : DEBUG(1,("Failed to get default creds (%s)\n", error_message(ret)));
98 0 : return ADS_ERROR_KRB5(ret);
99 : }
100 :
101 6 : ret = krb5_set_password_using_ccache(context,
102 : ccache,
103 : discard_const_p(char, newpw),
104 : princ,
105 : &result_code,
106 : &result_code_string,
107 : &result_string);
108 6 : if (ret) {
109 0 : DEBUG(1, ("krb5_set_password failed (%s)\n", error_message(ret)));
110 0 : aret = ADS_ERROR_KRB5(ret);
111 0 : goto done;
112 : }
113 :
114 6 : if (result_code != KRB5_KPASSWD_SUCCESS) {
115 0 : ret = kpasswd_err_to_krb5_err(result_code);
116 0 : DEBUG(1, ("krb5_set_password failed (%s)\n", error_message(ret)));
117 0 : aret = ADS_ERROR_KRB5(ret);
118 0 : goto done;
119 : }
120 :
121 6 : aret = ADS_SUCCESS;
122 :
123 6 : done:
124 6 : smb_krb5_free_data_contents(context, &result_code_string);
125 6 : smb_krb5_free_data_contents(context, &result_string);
126 6 : krb5_free_principal(context, princ);
127 6 : krb5_cc_close(context, ccache);
128 6 : krb5_free_context(context);
129 :
130 6 : return aret;
131 : }
132 :
133 : /*
134 : we use a prompter to avoid a crash bug in the kerberos libs when
135 : dealing with empty passwords
136 : this prompter is just a string copy ...
137 : */
138 : static krb5_error_code
139 0 : kerb_prompter(krb5_context ctx, void *data,
140 : const char *name,
141 : const char *banner,
142 : int num_prompts,
143 : krb5_prompt prompts[])
144 : {
145 0 : if (num_prompts == 0) return 0;
146 :
147 0 : memset(prompts[0].reply->data, 0, prompts[0].reply->length);
148 0 : if (prompts[0].reply->length > 0) {
149 0 : if (data) {
150 0 : strncpy((char *)prompts[0].reply->data,
151 : (const char *)data,
152 0 : prompts[0].reply->length-1);
153 0 : prompts[0].reply->length = strlen((const char *)prompts[0].reply->data);
154 : } else {
155 0 : prompts[0].reply->length = 0;
156 : }
157 : }
158 0 : return 0;
159 : }
160 :
161 6 : static ADS_STATUS ads_krb5_chg_password(const char *kdc_host,
162 : const char *principal,
163 : const char *oldpw,
164 : const char *newpw,
165 : int time_offset)
166 : {
167 0 : ADS_STATUS aret;
168 0 : krb5_error_code ret;
169 6 : krb5_context context = NULL;
170 0 : krb5_principal princ;
171 6 : krb5_get_init_creds_opt *opts = NULL;
172 0 : krb5_creds creds;
173 6 : char *chpw_princ = NULL, *password;
174 6 : char *realm = NULL;
175 0 : int result_code;
176 6 : krb5_data result_code_string = { 0 };
177 6 : krb5_data result_string = { 0 };
178 6 : smb_krb5_addresses *addr = NULL;
179 :
180 6 : ret = smb_krb5_init_context_common(&context);
181 6 : if (ret) {
182 0 : DBG_ERR("kerberos init context failed (%s)\n",
183 : error_message(ret));
184 0 : return ADS_ERROR_KRB5(ret);
185 : }
186 :
187 6 : if ((ret = smb_krb5_parse_name(context, principal, &princ))) {
188 0 : krb5_free_context(context);
189 0 : DEBUG(1,("Failed to parse %s (%s)\n", principal, error_message(ret)));
190 0 : return ADS_ERROR_KRB5(ret);
191 : }
192 :
193 6 : ret = krb5_get_init_creds_opt_alloc(context, &opts);
194 6 : if (ret != 0) {
195 0 : krb5_free_context(context);
196 0 : DBG_WARNING("krb5_get_init_creds_opt_alloc failed: %s\n",
197 : error_message(ret));
198 0 : return ADS_ERROR_KRB5(ret);
199 : }
200 :
201 6 : krb5_get_init_creds_opt_set_tkt_life(opts, 5 * 60);
202 6 : krb5_get_init_creds_opt_set_renew_life(opts, 0);
203 6 : krb5_get_init_creds_opt_set_forwardable(opts, 0);
204 6 : krb5_get_init_creds_opt_set_proxiable(opts, 0);
205 : #ifdef SAMBA4_USES_HEIMDAL
206 3 : krb5_get_init_creds_opt_set_win2k(context, opts, true);
207 3 : krb5_get_init_creds_opt_set_canonicalize(context, opts, true);
208 : #else /* MIT */
209 : #if 0
210 : /*
211 : * FIXME
212 : *
213 : * Due to an upstream MIT Kerberos bug, this feature is not
214 : * not working. Affection versions (2019-10-09): <= 1.17
215 : *
216 : * Reproducer:
217 : * kinit -C aDmInIsTrAtOr@ACME.COM -S kadmin/changepw@ACME.COM
218 : *
219 : * This is NOT a problem if the service is a krbtgt.
220 : *
221 : * https://bugzilla.samba.org/show_bug.cgi?id=14155
222 : */
223 : krb5_get_init_creds_opt_set_canonicalize(opts, true);
224 : #endif
225 : #endif /* MIT */
226 :
227 : /* note that heimdal will fill in the local addresses if the addresses
228 : * in the creds_init_opt are all empty and then later fail with invalid
229 : * address, sending our local netbios krb5 address - just like windows
230 : * - avoids this - gd */
231 6 : ret = smb_krb5_gen_netbios_krb5_address(&addr, lp_netbios_name());
232 6 : if (ret) {
233 0 : krb5_free_principal(context, princ);
234 0 : krb5_get_init_creds_opt_free(context, opts);
235 0 : krb5_free_context(context);
236 0 : return ADS_ERROR_KRB5(ret);
237 : }
238 6 : krb5_get_init_creds_opt_set_address_list(opts, addr->addrs);
239 :
240 6 : realm = smb_krb5_principal_get_realm(NULL, context, princ);
241 :
242 : /* We have to obtain an INITIAL changepw ticket for changing password */
243 6 : if (asprintf(&chpw_princ, "kadmin/changepw@%s", realm) == -1) {
244 0 : krb5_free_principal(context, princ);
245 0 : krb5_get_init_creds_opt_free(context, opts);
246 0 : smb_krb5_free_addresses(context, addr);
247 0 : krb5_free_context(context);
248 0 : TALLOC_FREE(realm);
249 0 : DEBUG(1, ("ads_krb5_chg_password: asprintf fail\n"));
250 0 : return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
251 : }
252 :
253 6 : TALLOC_FREE(realm);
254 6 : password = SMB_STRDUP(oldpw);
255 6 : ret = krb5_get_init_creds_password(context, &creds, princ, password,
256 : kerb_prompter, NULL,
257 : 0, chpw_princ, opts);
258 6 : krb5_get_init_creds_opt_free(context, opts);
259 6 : smb_krb5_free_addresses(context, addr);
260 6 : SAFE_FREE(chpw_princ);
261 6 : SAFE_FREE(password);
262 :
263 6 : if (ret) {
264 0 : if (ret == KRB5KRB_AP_ERR_BAD_INTEGRITY) {
265 0 : DEBUG(1,("Password incorrect while getting initial ticket\n"));
266 : } else {
267 0 : DEBUG(1,("krb5_get_init_creds_password failed (%s)\n", error_message(ret)));
268 : }
269 0 : krb5_free_principal(context, princ);
270 0 : krb5_free_context(context);
271 0 : return ADS_ERROR_KRB5(ret);
272 : }
273 :
274 6 : ret = krb5_set_password(context,
275 : &creds,
276 : discard_const_p(char, newpw),
277 : NULL,
278 : &result_code,
279 : &result_code_string,
280 : &result_string);
281 :
282 6 : if (ret) {
283 0 : DEBUG(1, ("krb5_change_password failed (%s)\n", error_message(ret)));
284 0 : aret = ADS_ERROR_KRB5(ret);
285 0 : goto done;
286 : }
287 :
288 6 : if (result_code != KRB5_KPASSWD_SUCCESS) {
289 0 : ret = kpasswd_err_to_krb5_err(result_code);
290 0 : DEBUG(1, ("krb5_change_password failed (%s)\n", error_message(ret)));
291 0 : aret = ADS_ERROR_KRB5(ret);
292 0 : goto done;
293 : }
294 :
295 6 : aret = ADS_SUCCESS;
296 :
297 6 : done:
298 6 : smb_krb5_free_data_contents(context, &result_code_string);
299 6 : smb_krb5_free_data_contents(context, &result_string);
300 6 : krb5_free_principal(context, princ);
301 6 : krb5_free_context(context);
302 :
303 6 : return aret;
304 : }
305 :
306 8 : ADS_STATUS kerberos_set_password(const char *kpasswd_server,
307 : const char *auth_principal,
308 : const char *auth_password,
309 : const char *target_principal,
310 : const char *new_password, int time_offset)
311 : {
312 0 : int ret;
313 :
314 8 : if ((ret = kerberos_kinit_password(auth_principal, auth_password, time_offset, NULL))) {
315 0 : DEBUG(1,("Failed kinit for principal %s (%s)\n", auth_principal, error_message(ret)));
316 0 : return ADS_ERROR_KRB5(ret);
317 : }
318 :
319 8 : if (!strcmp(auth_principal, target_principal)) {
320 6 : return ads_krb5_chg_password(kpasswd_server, target_principal,
321 : auth_password, new_password,
322 : time_offset);
323 : } else {
324 2 : return ads_krb5_set_password(kpasswd_server, target_principal,
325 : new_password, time_offset);
326 : }
327 : }
328 :
329 : #endif
|