1 /*
2 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. */
3
4 #include "k5-int.h"
5 #include "com_err.h"
6 #include <admin.h>
7 #include <locale.h>
8 #include <syslog.h>
9
10 /* Solaris Kerberos:
11 *
12 * Change Password functionality is handled by the libkadm5clnt.so.1 library in
13 * Solaris Kerberos. In order to avoid a circular dependency between that lib
14 * and the kerberos mech lib, we use the #pragma weak compiler directive.
15 * This way, when applications link with the libkadm5clnt.so.1 lib the circular
16 * dependancy between the two libs will be resolved.
17 */
18
19 #pragma weak kadm5_get_cpw_host_srv_name
20 #pragma weak kadm5_init_with_password
21 #pragma weak kadm5_chpass_principal_util
22
23 extern kadm5_ret_t kadm5_get_cpw_host_srv_name(krb5_context, const char *,
24 char **);
25 extern kadm5_ret_t kadm5_init_with_password(char *, char *, char *,
26 kadm5_config_params *, krb5_ui_4, krb5_ui_4, char **,
27 void **);
28 extern kadm5_ret_t kadm5_chpass_principal_util(void *, krb5_principal,
29 char *, char **, char *, unsigned int);
30
31 /*
32 * Solaris Kerberos:
33 * See the function's definition for the description of this interface.
34 */
35 krb5_error_code __krb5_get_init_creds_password(krb5_context,
36 krb5_creds *, krb5_principal, char *, krb5_prompter_fct, void *,
37 krb5_deltat, char *, krb5_get_init_creds_opt *, krb5_kdc_rep **);
38
39 static 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)40 krb5_get_as_key_password(
41 krb5_context context,
42 krb5_principal client,
43 krb5_enctype etype,
44 krb5_prompter_fct prompter,
45 void *prompter_data,
46 krb5_data *salt,
47 krb5_data *params,
48 krb5_keyblock *as_key,
49 void *gak_data)
50 {
51 krb5_data *password;
52 krb5_error_code ret;
53 krb5_data defsalt;
54 char *clientstr;
55 char promptstr[1024];
56 krb5_prompt prompt;
57 krb5_prompt_type prompt_type;
58
59 password = (krb5_data *) gak_data;
60
61 /* If there's already a key of the correct etype, we're done.
62 If the etype is wrong, free the existing key, and make
63 a new one.
64
65 XXX This was the old behavior, and was wrong in hw preauth
66 cases. Is this new behavior -- always asking -- correct in all
67 cases? */
68
69 if (as_key->length) {
70 if (as_key->enctype != etype) {
71 krb5_free_keyblock_contents (context, as_key);
72 as_key->length = 0;
73 }
74 }
75
76 if (password->data[0] == '\0') {
77 if (prompter == NULL)
78 prompter = krb5_prompter_posix; /* Solaris Kerberos */
79
80 if ((ret = krb5_unparse_name(context, client, &clientstr)))
81 return(ret);
82
83 strcpy(promptstr, "Password for ");
84 strncat(promptstr, clientstr, sizeof(promptstr)-strlen(promptstr)-1);
85 promptstr[sizeof(promptstr)-1] = '\0';
86
87 free(clientstr);
88
89 prompt.prompt = promptstr;
90 prompt.hidden = 1;
91 prompt.reply = password;
92 prompt_type = KRB5_PROMPT_TYPE_PASSWORD;
93
94 /* PROMPTER_INVOCATION */
95 krb5int_set_prompt_types(context, &prompt_type);
96 if ((ret = (((*prompter)(context, prompter_data, NULL, NULL,
97 1, &prompt))))) {
98 krb5int_set_prompt_types(context, 0);
99 return(ret);
100 }
101 krb5int_set_prompt_types(context, 0);
102 }
103
104 if ((salt->length == -1 || salt->length == SALT_TYPE_AFS_LENGTH) && (salt->data == NULL)) {
105 if ((ret = krb5_principal2salt(context, client, &defsalt)))
106 return(ret);
107
108 salt = &defsalt;
109 } else {
110 defsalt.length = 0;
111 }
112
113 ret = krb5_c_string_to_key_with_params(context, etype, password, salt,
114 params->data?params:NULL, as_key);
115
116 if (defsalt.length)
117 krb5_xfree(defsalt.data);
118
119 return(ret);
120 }
121
122 krb5_error_code KRB5_CALLCONV
krb5_get_init_creds_password(krb5_context context,krb5_creds * creds,krb5_principal client,char * password,krb5_prompter_fct prompter,void * data,krb5_deltat start_time,char * in_tkt_service,krb5_get_init_creds_opt * options)123 krb5_get_init_creds_password(krb5_context context,
124 krb5_creds *creds,
125 krb5_principal client,
126 char *password,
127 krb5_prompter_fct prompter,
128 void *data,
129 krb5_deltat start_time,
130 char *in_tkt_service,
131 krb5_get_init_creds_opt *options)
132 {
133 /*
134 * Solaris Kerberos:
135 * We call our own private function that returns the as_reply back to
136 * the caller. This structure contains information, such as
137 * key-expiration and last-req fields. Entities such as pam_krb5 can
138 * use this information to provide account/password expiration warnings.
139 * The original "prompter" interface is not granular enough for PAM,
140 * as it will perform all passes w/o coordination with other modules.
141 */
142 return (__krb5_get_init_creds_password(context, creds, client, password,
143 prompter, data, start_time, in_tkt_service, options, NULL));
144 }
145
146 /*
147 * Solaris Kerberos:
148 * See krb5_get_init_creds_password()'s comments for the justification of this
149 * private function. Caller must free ptr_as_reply if non-NULL.
150 */
151 krb5_error_code KRB5_CALLCONV
__krb5_get_init_creds_password(krb5_context context,krb5_creds * creds,krb5_principal client,char * password,krb5_prompter_fct prompter,void * data,krb5_deltat start_time,char * in_tkt_service,krb5_get_init_creds_opt * options,krb5_kdc_rep ** ptr_as_reply)152 __krb5_get_init_creds_password(
153 krb5_context context,
154 krb5_creds *creds,
155 krb5_principal client,
156 char *password,
157 krb5_prompter_fct prompter,
158 void *data,
159 krb5_deltat start_time,
160 char *in_tkt_service,
161 krb5_get_init_creds_opt *options,
162 krb5_kdc_rep **ptr_as_reply)
163 {
164 krb5_error_code ret, ret2;
165 int use_master;
166 krb5_kdc_rep *as_reply;
167 int tries;
168 krb5_creds chpw_creds;
169 krb5_data pw0, pw1;
170 char banner[1024], pw0array[1024], pw1array[1024];
171 krb5_prompt prompt[2];
172 krb5_prompt_type prompt_types[sizeof(prompt)/sizeof(prompt[0])];
173 krb5_gic_opt_ext *opte = NULL;
174 krb5_gic_opt_ext *chpw_opte = NULL;
175
176 char admin_realm[1024], *cpw_service=NULL, *princ_str=NULL;
177 kadm5_config_params params;
178 void *server_handle;
179 const char *err_msg_1 = NULL;
180
181 use_master = 0;
182 as_reply = NULL;
183 memset(&chpw_creds, 0, sizeof(chpw_creds));
184
185 pw0.data = pw0array;
186
187 if (password && password[0]) {
188 if ((pw0.length = strlen(password)) > sizeof(pw0array)) {
189 ret = EINVAL;
190 goto cleanup;
191 }
192 strcpy(pw0.data, password);
193 } else {
194 pw0.data[0] = '\0';
195 pw0.length = sizeof(pw0array);
196 }
197
198 pw1.data = pw1array;
199 pw1.data[0] = '\0';
200 pw1.length = sizeof(pw1array);
201
202 ret = krb5int_gic_opt_to_opte(context, options, &opte, 1,
203 "krb5_get_init_creds_password");
204 if (ret)
205 goto cleanup;
206
207 /* first try: get the requested tkt from any kdc */
208
209 ret = krb5_get_init_creds(context, creds, client, prompter, data,
210 start_time, in_tkt_service, opte,
211 krb5_get_as_key_password, (void *) &pw0,
212 &use_master, &as_reply);
213 /* check for success */
214
215 if (ret == 0)
216 goto cleanup;
217
218 /* If all the kdc's are unavailable, or if the error was due to a
219 user interrupt, or preauth errored out, fail */
220
221 if ((ret == KRB5_KDC_UNREACH) ||
222 (ret == KRB5_PREAUTH_FAILED) ||
223 (ret == KRB5_LIBOS_PWDINTR) ||
224 (ret == KRB5_REALM_CANT_RESOLVE))
225 goto cleanup;
226
227 /* if the reply did not come from the master kdc, try again with
228 the master kdc */
229
230 if (!use_master) {
231 use_master = 1;
232
233 if (as_reply) {
234 krb5_free_kdc_rep( context, as_reply);
235 as_reply = NULL;
236 }
237
238 err_msg_1 = krb5_get_error_message(context, ret);
239
240 ret2 = krb5_get_init_creds(context, creds, client, prompter, data,
241 start_time, in_tkt_service, opte,
242 krb5_get_as_key_password, (void *) &pw0,
243 &use_master, &as_reply);
244
245 if (ret2 == 0) {
246 ret = 0;
247 goto cleanup;
248 }
249
250 /* if the master is unreachable, return the error from the
251 slave we were able to contact or reset the use_master flag */
252
253 if ((ret2 != KRB5_KDC_UNREACH) &&
254 (ret2 != KRB5_REALM_CANT_RESOLVE) &&
255 (ret2 != KRB5_REALM_UNKNOWN)) {
256 ret = ret2;
257 } else {
258 use_master = 0;
259 /* Solaris - if 2nd try failed, reset 1st err msg */
260 if (ret2 && err_msg_1) {
261 krb5_set_error_message(context, ret, err_msg_1);
262 }
263 }
264 }
265
266 /* Solaris Kerberos: 163 resync */
267 /* #ifdef USE_LOGIN_LIBRARY */
268 if (ret == KRB5KDC_ERR_KEY_EXP)
269 goto cleanup; /* Login library will deal appropriately with this error */
270 /* #endif */
271
272 /* at this point, we have an error from the master. if the error
273 is not password expired, or if it is but there's no prompter,
274 return this error */
275
276 if ((ret != KRB5KDC_ERR_KEY_EXP) ||
277 (prompter == NULL))
278 goto cleanup;
279
280 /* historically the default has been to prompt for password change.
281 * if the change password prompt option has not been set, we continue
282 * to prompt. Prompting is only disabled if the option has been set
283 * and the value has been set to false.
284 */
285 if (!(options->flags & KRB5_GET_INIT_CREDS_OPT_CHG_PWD_PRMPT))
286 goto cleanup;
287
288 /* ok, we have an expired password. Give the user a few chances
289 to change it */
290
291
292 /*
293 * Solaris Kerberos:
294 * Get the correct change password service principal name to use.
295 * This is necessary because SEAM based admin servers require
296 * a slightly different service principal name than MIT/MS servers.
297 */
298
299 memset((char *) ¶ms, 0, sizeof (params));
300
301 snprintf(admin_realm, sizeof (admin_realm),
302 krb5_princ_realm(context, client)->data);
303 params.mask |= KADM5_CONFIG_REALM;
304 params.realm = admin_realm;
305
306 ret=kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service);
307
308 if (ret != KADM5_OK) {
309 syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
310 "Kerberos mechanism library: Unable to get change password "
311 "service name for realm %s\n"), admin_realm);
312 goto cleanup;
313 } else {
314 ret=0;
315 }
316
317 /* extract the string version of the principal */
318 if ((ret = krb5_unparse_name(context, client, &princ_str)))
319 goto cleanup;
320
321 ret = kadm5_init_with_password(princ_str, pw0array, cpw_service,
322 ¶ms, KADM5_STRUCT_VERSION, KADM5_API_VERSION_2, NULL,
323 &server_handle);
324
325 if (ret != 0) {
326 goto cleanup;
327 }
328
329 prompt[0].prompt = "Enter new password";
330 prompt[0].hidden = 1;
331 prompt[0].reply = &pw0;
332 prompt_types[0] = KRB5_PROMPT_TYPE_NEW_PASSWORD;
333
334 prompt[1].prompt = "Enter it again";
335 prompt[1].hidden = 1;
336 prompt[1].reply = &pw1;
337 prompt_types[1] = KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN;
338
339 strcpy(banner, "Password expired. You must change it now.");
340
341 for (tries = 3; tries; tries--) {
342 pw0.length = sizeof(pw0array);
343 pw1.length = sizeof(pw1array);
344
345 /* PROMPTER_INVOCATION */
346 krb5int_set_prompt_types(context, prompt_types);
347 if ((ret = ((*prompter)(context, data, 0, banner,
348 sizeof(prompt)/sizeof(prompt[0]), prompt))))
349 goto cleanup;
350 krb5int_set_prompt_types(context, 0);
351
352
353 if (strcmp(pw0.data, pw1.data) != 0) {
354 ret = KRB5_LIBOS_BADPWDMATCH;
355 sprintf(banner, "%s. Please try again.", error_message(ret));
356 } else if (pw0.length == 0) {
357 ret = KRB5_CHPW_PWDNULL;
358 sprintf(banner, "%s. Please try again.", error_message(ret));
359 } else {
360 int result_code;
361 krb5_data code_string;
362 krb5_data result_string;
363
364 if ((ret = krb5_change_password(context, &chpw_creds, pw0array,
365 &result_code, &code_string,
366 &result_string)))
367 goto cleanup;
368
369 /* the change succeeded. go on */
370
371 if (result_code == 0) {
372 krb5_xfree(result_string.data);
373 break;
374 }
375
376 /* set this in case the retry loop falls through */
377
378 ret = KRB5_CHPW_FAIL;
379
380 if (result_code != KRB5_KPASSWD_SOFTERROR) {
381 krb5_xfree(result_string.data);
382 goto cleanup;
383 }
384
385 /* the error was soft, so try again */
386
387 /* 100 is I happen to know that no code_string will be longer
388 than 100 chars */
389
390 if (result_string.length > (sizeof(banner)-100))
391 result_string.length = sizeof(banner)-100;
392
393 sprintf(banner, "%.*s%s%.*s. Please try again.\n",
394 (int) code_string.length, code_string.data,
395 result_string.length ? ": " : "",
396 (int) result_string.length,
397 result_string.data ? result_string.data : "");
398
399 krb5_xfree(code_string.data);
400 krb5_xfree(result_string.data);
401 }
402 }
403
404 if (ret)
405 goto cleanup;
406
407 /* the password change was successful. Get an initial ticket
408 from the master. this is the last try. the return from this
409 is final. */
410
411 ret = krb5_get_init_creds(context, creds, client, prompter, data,
412 start_time, in_tkt_service, opte,
413 krb5_get_as_key_password, (void *) &pw0,
414 &use_master, &as_reply);
415
416 cleanup:
417 if (err_msg_1)
418 free((void *)err_msg_1);
419
420 krb5int_set_prompt_types(context, 0);
421 /* if getting the password was successful, then check to see if the
422 password is about to expire, and warn if so */
423
424 if (ret == 0) {
425 krb5_timestamp now;
426 krb5_last_req_entry **last_req;
427 int hours;
428
429 /* XXX 7 days should be configurable. This is all pretty ad hoc,
430 and could probably be improved if I was willing to screw around
431 with timezones, etc. */
432
433 if (prompter &&
434 (in_tkt_service && cpw_service &&
435 (strcmp(in_tkt_service, cpw_service) != 0)) &&
436 ((ret = krb5_timeofday(context, &now)) == 0) &&
437 as_reply->enc_part2->key_exp &&
438 ((hours = ((as_reply->enc_part2->key_exp-now)/(60*60))) <= 7*24) &&
439 (hours >= 0)) {
440 if (hours < 1)
441 sprintf(banner,
442 "Warning: Your password will expire in less than one hour.");
443 else if (hours <= 48)
444 sprintf(banner, "Warning: Your password will expire in %d hour%s.",
445 hours, (hours == 1)?"":"s");
446 else
447 sprintf(banner, "Warning: Your password will expire in %d days.",
448 hours/24);
449
450 /* ignore an error here */
451 /* PROMPTER_INVOCATION */
452 (*prompter)(context, data, 0, banner, 0, 0);
453 } else if (prompter &&
454 (!in_tkt_service ||
455 (strcmp(in_tkt_service, "kadmin/changepw") != 0)) &&
456 as_reply->enc_part2 && as_reply->enc_part2->last_req) {
457 /*
458 * Check the last_req fields
459 */
460
461 for (last_req = as_reply->enc_part2->last_req; *last_req; last_req++)
462 if ((*last_req)->lr_type == KRB5_LRQ_ALL_PW_EXPTIME ||
463 (*last_req)->lr_type == KRB5_LRQ_ONE_PW_EXPTIME) {
464 krb5_deltat delta;
465 char ts[256];
466
467 if ((ret = krb5_timeofday(context, &now)))
468 break;
469
470 if ((ret = krb5_timestamp_to_string((*last_req)->value,
471 ts, sizeof(ts))))
472 break;
473
474 delta = (*last_req)->value - now;
475
476 if (delta < 3600)
477 sprintf(banner,
478 "Warning: Your password will expire in less than one "
479 "hour on %s", ts);
480 else if (delta < 86400*2)
481 sprintf(banner,
482 "Warning: Your password will expire in %d hour%s on %s",
483 delta / 3600, delta < 7200 ? "" : "s", ts);
484 else
485 sprintf(banner,
486 "Warning: Your password will expire in %d days on %s",
487 delta / 86400, ts);
488 /* ignore an error here */
489 /* PROMPTER_INVOCATION */
490 (*prompter)(context, data, 0, banner, 0, 0);
491 }
492 }
493 }
494
495 free(cpw_service);
496 free(princ_str);
497 if (opte && krb5_gic_opt_is_shadowed(opte))
498 krb5_get_init_creds_opt_free(context, (krb5_get_init_creds_opt *)opte);
499 memset(pw0array, 0, sizeof(pw0array));
500 memset(pw1array, 0, sizeof(pw1array));
501 krb5_free_cred_contents(context, &chpw_creds);
502 /*
503 * Solaris Kerberos:
504 * Argument, ptr_as_reply, being returned to caller if success and non-NULL.
505 */
506 if (as_reply != NULL) {
507 if (ptr_as_reply == NULL)
508 krb5_free_kdc_rep(context, as_reply);
509 else
510 *ptr_as_reply = as_reply;
511 }
512
513 return(ret);
514 }
krb5int_populate_gic_opt(krb5_context context,krb5_gic_opt_ext ** opte,krb5_flags options,krb5_address * const * addrs,krb5_enctype * ktypes,krb5_preauthtype * pre_auth_types,krb5_creds * creds)515 krb5_error_code krb5int_populate_gic_opt (
516 krb5_context context, krb5_gic_opt_ext **opte,
517 krb5_flags options, krb5_address * const *addrs, krb5_enctype *ktypes,
518 krb5_preauthtype *pre_auth_types, krb5_creds *creds)
519 {
520 int i;
521 krb5_int32 starttime;
522 krb5_get_init_creds_opt *opt;
523
524
525 krb5_get_init_creds_opt_init(opt);
526 if (addrs)
527 krb5_get_init_creds_opt_set_address_list(opt, (krb5_address **) addrs);
528 if (ktypes) {
529 for (i=0; ktypes[i]; i++);
530 if (i)
531 krb5_get_init_creds_opt_set_etype_list(opt, ktypes, i);
532 }
533 if (pre_auth_types) {
534 for (i=0; pre_auth_types[i]; i++);
535 if (i)
536 krb5_get_init_creds_opt_set_preauth_list(opt, pre_auth_types, i);
537 }
538 if (options&KDC_OPT_FORWARDABLE)
539 krb5_get_init_creds_opt_set_forwardable(opt, 1);
540 else krb5_get_init_creds_opt_set_forwardable(opt, 0);
541 if (options&KDC_OPT_PROXIABLE)
542 krb5_get_init_creds_opt_set_proxiable(opt, 1);
543 else krb5_get_init_creds_opt_set_proxiable(opt, 0);
544 if (creds && creds->times.endtime) {
545 krb5_timeofday(context, &starttime);
546 if (creds->times.starttime) starttime = creds->times.starttime;
547 krb5_get_init_creds_opt_set_tkt_life(opt, creds->times.endtime - starttime);
548 }
549 return krb5int_gic_opt_to_opte(context, opt, opte, 0,
550 "krb5int_populate_gic_opt");
551 }
552
553 /*
554 Rewrites get_in_tkt in terms of newer get_init_creds API.
555 Attempts to get an initial ticket for creds->client to use server
556 creds->server, (realm is taken from creds->client), with options
557 options, and using creds->times.starttime, creds->times.endtime,
558 creds->times.renew_till as from, till, and rtime.
559 creds->times.renew_till is ignored unless the RENEWABLE option is requested.
560
561 If addrs is non-NULL, it is used for the addresses requested. If it is
562 null, the system standard addresses are used.
563
564 If password is non-NULL, it is converted using the cryptosystem entry
565 point for a string conversion routine, seeded with the client's name.
566 If password is passed as NULL, the password is read from the terminal,
567 and then converted into a key.
568
569 A succesful call will place the ticket in the credentials cache ccache.
570
571 returns system errors, encryption errors
572 */
573 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)574 krb5_get_in_tkt_with_password(krb5_context context, krb5_flags options,
575 krb5_address *const *addrs, krb5_enctype *ktypes,
576 krb5_preauthtype *pre_auth_types,
577 const char *password, krb5_ccache ccache,
578 krb5_creds *creds, krb5_kdc_rep **ret_as_reply)
579 {
580 krb5_error_code retval;
581 krb5_data pw0;
582 char pw0array[1024];
583 char * server;
584 krb5_principal server_princ, client_princ;
585 int use_master = 0;
586 krb5_gic_opt_ext *opte = NULL;
587
588 pw0array[0] = '\0';
589 pw0.data = pw0array;
590 if (password) {
591 pw0.length = strlen(password);
592 if (pw0.length > sizeof(pw0array))
593 return EINVAL;
594 strncpy(pw0.data, password, sizeof(pw0array));
595 if (pw0.length == 0)
596 pw0.length = sizeof(pw0array);
597 } else {
598 pw0.length = sizeof(pw0array);
599 }
600 retval = krb5int_populate_gic_opt(context, &opte,
601 options, addrs, ktypes,
602 pre_auth_types, creds);
603 if (retval)
604 return (retval);
605 retval = krb5_unparse_name( context, creds->server, &server);
606 if (retval) {
607 return (retval);
608 krb5_get_init_creds_opt_free(context, (krb5_get_init_creds_opt *)opte);
609 }
610 server_princ = creds->server;
611 client_princ = creds->client;
612 retval = krb5_get_init_creds (context,
613 creds, creds->client,
614 krb5_prompter_posix, NULL,
615 0, server, opte,
616 krb5_get_as_key_password, &pw0,
617 &use_master, ret_as_reply);
618 krb5_free_unparsed_name( context, server);
619 krb5_get_init_creds_opt_free(context, (krb5_get_init_creds_opt *)opte);
620 if (retval) {
621 return (retval);
622 }
623 if (creds->server)
624 krb5_free_principal( context, creds->server);
625 if (creds->client)
626 krb5_free_principal( context, creds->client);
627 creds->client = client_princ;
628 creds->server = server_princ;
629 /* store it in the ccache! */
630 if (ccache)
631 if ((retval = krb5_cc_store_cred(context, ccache, creds)))
632 return (retval);
633 return retval;
634 }
635
636