1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 #include "k5-int.h"
3 #include "com_err.h"
4 #include "init_creds_ctx.h"
5 #include "int-proto.h"
6 #include "os-proto.h"
7
8 krb5_error_code
krb5_get_as_key_password(krb5_context context,krb5_principal client,krb5_enctype etype,krb5_prompter_fct prompter,void * prompter_data,krb5_data * salt,krb5_data * params,krb5_keyblock * as_key,void * gak_data,k5_response_items * ritems)9 krb5_get_as_key_password(krb5_context context,
10 krb5_principal client,
11 krb5_enctype etype,
12 krb5_prompter_fct prompter,
13 void *prompter_data,
14 krb5_data *salt,
15 krb5_data *params,
16 krb5_keyblock *as_key,
17 void *gak_data,
18 k5_response_items *ritems)
19 {
20 struct gak_password *gp = gak_data;
21 krb5_error_code ret;
22 krb5_data defsalt;
23 char *clientstr;
24 char promptstr[1024], pwbuf[1024];
25 krb5_data pw;
26 krb5_prompt prompt;
27 krb5_prompt_type prompt_type;
28 const char *rpass;
29
30 /* If we need to get the AS key via the responder, ask for it. */
31 if (as_key == NULL) {
32 if (gp->password != NULL)
33 return 0;
34
35 return k5_response_items_ask_question(ritems,
36 KRB5_RESPONDER_QUESTION_PASSWORD,
37 "");
38 }
39
40 /* If there's already a key of the correct etype, we're done.
41 If the etype is wrong, free the existing key, and make
42 a new one.
43
44 XXX This was the old behavior, and was wrong in hw preauth
45 cases. Is this new behavior -- always asking -- correct in all
46 cases? */
47
48 if (as_key->length) {
49 if (as_key->enctype != etype) {
50 krb5_free_keyblock_contents (context, as_key);
51 as_key->length = 0;
52 }
53 }
54
55 if (gp->password == NULL) {
56 /* Check the responder for the password. */
57 rpass = k5_response_items_get_answer(ritems,
58 KRB5_RESPONDER_QUESTION_PASSWORD);
59 if (rpass != NULL) {
60 ret = alloc_data(&gp->storage, strlen(rpass));
61 if (ret)
62 return ret;
63 memcpy(gp->storage.data, rpass, strlen(rpass));
64 gp->password = &gp->storage;
65 }
66 }
67
68 if (gp->password == NULL) {
69 if (prompter == NULL)
70 return(EIO);
71
72 if ((ret = krb5_unparse_name(context, client, &clientstr)))
73 return(ret);
74
75 snprintf(promptstr, sizeof(promptstr), _("Password for %s"),
76 clientstr);
77 free(clientstr);
78
79 pw = make_data(pwbuf, sizeof(pwbuf));
80 prompt.prompt = promptstr;
81 prompt.hidden = 1;
82 prompt.reply = &pw;
83 prompt_type = KRB5_PROMPT_TYPE_PASSWORD;
84
85 /* PROMPTER_INVOCATION */
86 k5_set_prompt_types(context, &prompt_type);
87 ret = (*prompter)(context, prompter_data, NULL, NULL, 1, &prompt);
88 k5_set_prompt_types(context, 0);
89 if (ret)
90 return(ret);
91
92 ret = krb5int_copy_data_contents(context, &pw, &gp->storage);
93 zap(pw.data, pw.length);
94 if (ret)
95 return ret;
96 gp->password = &gp->storage;
97 }
98
99 if (salt == NULL) {
100 if ((ret = krb5_principal2salt(context, client, &defsalt)))
101 return(ret);
102
103 salt = &defsalt;
104 } else {
105 defsalt.length = 0;
106 }
107
108 ret = krb5_c_string_to_key_with_params(context, etype, gp->password, salt,
109 params->data?params:NULL, as_key);
110
111 if (defsalt.length)
112 free(defsalt.data);
113
114 return(ret);
115 }
116
117 krb5_error_code KRB5_CALLCONV
krb5_init_creds_set_password(krb5_context context,krb5_init_creds_context ctx,const char * password)118 krb5_init_creds_set_password(krb5_context context,
119 krb5_init_creds_context ctx,
120 const char *password)
121 {
122 char *s;
123
124 s = strdup(password);
125 if (s == NULL)
126 return ENOMEM;
127
128 zapfree(ctx->gakpw.storage.data, ctx->gakpw.storage.length);
129 ctx->gakpw.storage = string2data(s);
130 ctx->gakpw.password = &ctx->gakpw.storage;
131 ctx->gak_fct = krb5_get_as_key_password;
132 ctx->gak_data = &ctx->gakpw;
133 return 0;
134 }
135
136 /*
137 * Create a temporary options structure for getting a kadmin/changepw ticket,
138 * based on the appplication-specified options. Propagate all application
139 * options which affect preauthentication, but not options which affect the
140 * resulting ticket or how it is stored. Set lifetime and flags appropriate
141 * for a ticket which we will use immediately and then discard.
142 *
143 * The caller should free the result with free().
144 */
145 static krb5_error_code
make_chpw_options(krb5_context context,krb5_get_init_creds_opt * in,krb5_get_init_creds_opt ** out)146 make_chpw_options(krb5_context context, krb5_get_init_creds_opt *in,
147 krb5_get_init_creds_opt **out)
148 {
149 krb5_get_init_creds_opt *opt;
150
151 *out = NULL;
152 opt = k5_gic_opt_shallow_copy(in);
153 if (opt == NULL)
154 return ENOMEM;
155
156 /* Get a non-forwardable, non-proxiable, short-lifetime ticket. */
157 krb5_get_init_creds_opt_set_tkt_life(opt, 5 * 60);
158 krb5_get_init_creds_opt_set_renew_life(opt, 0);
159 krb5_get_init_creds_opt_set_forwardable(opt, 0);
160 krb5_get_init_creds_opt_set_proxiable(opt, 0);
161
162 /* Unset options which should only apply to the actual ticket. */
163 opt->flags &= ~KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST;
164 opt->flags &= ~KRB5_GET_INIT_CREDS_OPT_ANONYMOUS;
165
166 /* The output ccache should only be used for the actual ticket. */
167 krb5_get_init_creds_opt_set_out_ccache(context, opt, NULL);
168
169 *out = opt;
170 return 0;
171 }
172
173 krb5_error_code KRB5_CALLCONV
krb5_get_init_creds_password(krb5_context context,krb5_creds * creds,krb5_principal client,const char * password,krb5_prompter_fct prompter,void * data,krb5_deltat start_time,const char * in_tkt_service,krb5_get_init_creds_opt * options)174 krb5_get_init_creds_password(krb5_context context,
175 krb5_creds *creds,
176 krb5_principal client,
177 const char *password,
178 krb5_prompter_fct prompter,
179 void *data,
180 krb5_deltat start_time,
181 const char *in_tkt_service,
182 krb5_get_init_creds_opt *options)
183 {
184 krb5_error_code ret;
185 krb5_kdc_rep *as_reply;
186 int tries;
187 krb5_creds chpw_creds;
188 krb5_get_init_creds_opt *chpw_opts = NULL;
189 struct gak_password gakpw;
190 krb5_data pw0, pw1;
191 char banner[1024], pw0array[1024], pw1array[1024];
192 krb5_prompt prompt[2];
193 krb5_prompt_type prompt_types[sizeof(prompt)/sizeof(prompt[0])];
194 char *message;
195
196 as_reply = NULL;
197 memset(&chpw_creds, 0, sizeof(chpw_creds));
198 memset(&gakpw, 0, sizeof(gakpw));
199
200 if (password != NULL) {
201 pw0 = string2data((char *)password);
202 gakpw.password = &pw0;
203 }
204
205 ret = k5_get_init_creds(context, creds, client, prompter, data, start_time,
206 in_tkt_service, options, krb5_get_as_key_password,
207 &gakpw, &as_reply);
208 if (!ret)
209 goto cleanup;
210
211 /* If the error is not password expired, or if it is but there's no
212 * prompter, return this error. */
213 if (ret != KRB5KDC_ERR_KEY_EXP || prompter == NULL)
214 goto cleanup;
215
216 /* historically the default has been to prompt for password change.
217 * if the change password prompt option has not been set, we continue
218 * to prompt. Prompting is only disabled if the option has been set
219 * and the value has been set to false.
220 */
221 if (options && !(options->flags & KRB5_GET_INIT_CREDS_OPT_CHG_PWD_PRMPT))
222 goto cleanup;
223 TRACE_GIC_PWD_EXPIRED(context);
224
225 /* ok, we have an expired password. Give the user a few chances
226 to change it */
227
228 ret = make_chpw_options(context, options, &chpw_opts);
229 if (ret)
230 goto cleanup;
231 ret = k5_get_init_creds(context, &chpw_creds, client, prompter, data,
232 start_time, "kadmin/changepw", chpw_opts,
233 krb5_get_as_key_password, &gakpw, NULL);
234 if (ret)
235 goto cleanup;
236
237 pw0.data = pw0array;
238 pw0.data[0] = '\0';
239 pw0.length = sizeof(pw0array);
240 prompt[0].prompt = _("Enter new password");
241 prompt[0].hidden = 1;
242 prompt[0].reply = &pw0;
243 prompt_types[0] = KRB5_PROMPT_TYPE_NEW_PASSWORD;
244
245 pw1.data = pw1array;
246 pw1.data[0] = '\0';
247 pw1.length = sizeof(pw1array);
248 prompt[1].prompt = _("Enter it again");
249 prompt[1].hidden = 1;
250 prompt[1].reply = &pw1;
251 prompt_types[1] = KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN;
252
253 strlcpy(banner, _("Password expired. You must change it now."),
254 sizeof(banner));
255
256 for (tries = 3; tries; tries--) {
257 TRACE_GIC_PWD_CHANGEPW(context, tries);
258 pw0.length = sizeof(pw0array);
259 pw1.length = sizeof(pw1array);
260
261 /* PROMPTER_INVOCATION */
262 k5_set_prompt_types(context, prompt_types);
263 ret = (*prompter)(context, data, 0, banner,
264 sizeof(prompt)/sizeof(prompt[0]), prompt);
265 k5_set_prompt_types(context, 0);
266 if (ret)
267 goto cleanup;
268
269 if (strcmp(pw0.data, pw1.data) != 0) {
270 ret = KRB5_LIBOS_BADPWDMATCH;
271 snprintf(banner, sizeof(banner),
272 _("%s. Please try again."), error_message(ret));
273 } else if (pw0.length == 0) {
274 ret = KRB5_CHPW_PWDNULL;
275 snprintf(banner, sizeof(banner),
276 _("%s. Please try again."), error_message(ret));
277 } else {
278 int result_code;
279 krb5_data code_string;
280 krb5_data result_string;
281
282 if ((ret = krb5_change_password(context, &chpw_creds, pw0array,
283 &result_code, &code_string,
284 &result_string)))
285 goto cleanup;
286
287 /* the change succeeded. go on */
288
289 if (result_code == 0) {
290 free(code_string.data);
291 free(result_string.data);
292 break;
293 }
294
295 /* set this in case the retry loop falls through */
296
297 ret = KRB5_CHPW_FAIL;
298
299 if (result_code != KRB5_KPASSWD_SOFTERROR) {
300 free(code_string.data);
301 free(result_string.data);
302 goto cleanup;
303 }
304
305 /* the error was soft, so try again */
306
307 if (krb5_chpw_message(context, &result_string, &message) != 0)
308 message = NULL;
309
310 /* 100 is I happen to know that no code_string will be longer
311 than 100 chars */
312
313 if (message != NULL && strlen(message) > (sizeof(banner) - 100))
314 message[sizeof(banner) - 100] = '\0';
315
316 snprintf(banner, sizeof(banner),
317 _("%.*s%s%s. Please try again.\n"),
318 (int) code_string.length, code_string.data,
319 message ? ": " : "", message ? message : "");
320
321 free(message);
322 free(code_string.data);
323 free(result_string.data);
324 }
325 }
326
327 if (ret)
328 goto cleanup;
329
330 /* The password change was successful. Try one last time to get an initial
331 * ticket. */
332 TRACE_GIC_PWD_CHANGED(context);
333 gakpw.password = &pw0;
334 ret = k5_get_init_creds(context, creds, client, prompter, data,
335 start_time, in_tkt_service, options,
336 krb5_get_as_key_password, &gakpw, &as_reply);
337 if (ret)
338 goto cleanup;
339
340 cleanup:
341 free(chpw_opts);
342 zapfree(gakpw.storage.data, gakpw.storage.length);
343 memset(pw0array, 0, sizeof(pw0array));
344 memset(pw1array, 0, sizeof(pw1array));
345 krb5_free_cred_contents(context, &chpw_creds);
346 if (as_reply)
347 krb5_free_kdc_rep(context, as_reply);
348
349 return(ret);
350 }
351
352 /*
353 Rewrites get_in_tkt in terms of newer get_init_creds API.
354 Attempts to get an initial ticket for creds->client to use server
355 creds->server, (realm is taken from creds->client), with options
356 options, and using creds->times.starttime, creds->times.endtime,
357 creds->times.renew_till as from, till, and rtime.
358 creds->times.renew_till is ignored unless the RENEWABLE option is requested.
359
360 If addrs is non-NULL, it is used for the addresses requested. If it is
361 null, the system standard addresses are used.
362
363 If password is non-NULL, it is converted using the cryptosystem entry
364 point for a string conversion routine, seeded with the client's name.
365 If password is passed as NULL, the password is read from the terminal,
366 and then converted into a key.
367
368 A successful call will place the ticket in the credentials cache ccache.
369
370 returns system errors, encryption errors
371 */
372 krb5_error_code KRB5_CALLCONV
krb5_get_in_tkt_with_password(krb5_context context,krb5_flags options,krb5_address * const * addrs,krb5_enctype * ktypes,krb5_preauthtype * pre_auth_types,const char * password,krb5_ccache ccache,krb5_creds * creds,krb5_kdc_rep ** ret_as_reply)373 krb5_get_in_tkt_with_password(krb5_context context, krb5_flags options,
374 krb5_address *const *addrs, krb5_enctype *ktypes,
375 krb5_preauthtype *pre_auth_types,
376 const char *password, krb5_ccache ccache,
377 krb5_creds *creds, krb5_kdc_rep **ret_as_reply)
378 {
379 krb5_error_code retval;
380 struct gak_password gakpw;
381 krb5_data pw;
382 char * server;
383 krb5_principal server_princ, client_princ;
384 krb5_get_init_creds_opt *opts = NULL;
385
386 memset(&gakpw, 0, sizeof(gakpw));
387 if (password != NULL) {
388 pw = string2data((char *)password);
389 gakpw.password = &pw;
390 }
391 retval = k5_populate_gic_opt(context, &opts, options, addrs, ktypes,
392 pre_auth_types, creds);
393 if (retval)
394 return (retval);
395 retval = krb5_unparse_name( context, creds->server, &server);
396 if (retval) {
397 krb5_get_init_creds_opt_free(context, opts);
398 return (retval);
399 }
400 server_princ = creds->server;
401 client_princ = creds->client;
402 retval = k5_get_init_creds(context, creds, creds->client,
403 krb5_prompter_posix, NULL, 0, server, opts,
404 krb5_get_as_key_password, &gakpw, ret_as_reply);
405 krb5_free_unparsed_name( context, server);
406 krb5_get_init_creds_opt_free(context, opts);
407 zapfree(gakpw.storage.data, gakpw.storage.length);
408 if (retval) {
409 return (retval);
410 }
411 krb5_free_principal( context, creds->server);
412 krb5_free_principal( context, creds->client);
413 creds->client = client_princ;
414 creds->server = server_princ;
415 /* store it in the ccache! */
416 if (ccache)
417 if ((retval = krb5_cc_store_cred(context, ccache, creds)))
418 return (retval);
419 return retval;
420 }
421