xref: /freebsd/crypto/krb5/src/lib/krb5/krb/gic_pwd.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
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