diff --git a/SOURCES/Do-expiration-warnings-for-all-init_creds-APIs.patch b/SOURCES/Do-expiration-warnings-for-all-init_creds-APIs.patch new file mode 100644 index 0000000..bdff560 --- /dev/null +++ b/SOURCES/Do-expiration-warnings-for-all-init_creds-APIs.patch @@ -0,0 +1,433 @@ +From ef3a3cf3ad8b389864f6f6b0535a40ee7e3bb945 Mon Sep 17 00:00:00 2001 +From: Sumit Bose +Date: Fri, 28 Feb 2020 10:11:49 +0100 +Subject: [PATCH] Do expiration warnings for all init_creds APIs + +Move the password expiration warning code from gic_pwd.c to +get_in_tkt.c. Call it from init_creds_step_reply() on successful +completion. + +[ghudson@mit.edu: added test case; simplified doc comment; moved call +site to init_creds_step_reply(); rewrote commit message] + +ticket: 8893 (new) +(cherry picked from commit e1efb890f7ac31b32c68ab816ef118dbfb5a8c7e) +(cherry picked from commit 9d452dc135ba0fad9470f096938a5dbfbacdbbe1) +[rharwood@redhat.com: fuzz around expected_msg] +--- + src/include/krb5/krb5.hin | 9 ++- + src/lib/krb5/krb/get_in_tkt.c | 112 ++++++++++++++++++++++++++++++ + src/lib/krb5/krb/gic_pwd.c | 110 ----------------------------- + src/lib/krb5/krb/t_expire_warn.c | 47 +++++++++---- + src/lib/krb5/krb/t_expire_warn.py | 29 ++++---- + 5 files changed, 166 insertions(+), 141 deletions(-) + +diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin +index 28557659e..606ceed3a 100644 +--- a/src/include/krb5/krb5.hin ++++ b/src/include/krb5/krb5.hin +@@ -7209,11 +7209,10 @@ typedef void + * + * Set a callback to receive password and account expiration times. + * +- * This option only applies to krb5_get_init_creds_password(). @a cb will be +- * invoked if and only if credentials are successfully acquired. The callback +- * will receive the @a context from the krb5_get_init_creds_password() call and +- * the @a data argument supplied with this API. The remaining arguments should +- * be interpreted as follows: ++ * @a cb will be invoked if and only if credentials are successfully acquired. ++ * The callback will receive the @a context from the calling function and the ++ * @a data argument supplied with this API. The remaining arguments should be ++ * interpreted as follows: + * + * If @a is_last_req is true, then the KDC reply contained last-req entries + * which unambiguously indicated the password expiration, account expiration, +diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c +index c7d7bfe74..2f57df0ff 100644 +--- a/src/lib/krb5/krb/get_in_tkt.c ++++ b/src/lib/krb5/krb/get_in_tkt.c +@@ -1495,6 +1495,116 @@ accept_method_data(krb5_context context, krb5_init_creds_context ctx) + ctx->method_padata); + } + ++/* Return the password expiry time indicated by enc_part2. Set *is_last_req ++ * if the information came from a last_req value. */ ++static void ++get_expiry_times(krb5_enc_kdc_rep_part *enc_part2, krb5_timestamp *pw_exp, ++ krb5_timestamp *acct_exp, krb5_boolean *is_last_req) ++{ ++ krb5_last_req_entry **last_req; ++ krb5_int32 lr_type; ++ ++ *pw_exp = 0; ++ *acct_exp = 0; ++ *is_last_req = FALSE; ++ ++ /* Look for last-req entries for password or account expiration. */ ++ if (enc_part2->last_req) { ++ for (last_req = enc_part2->last_req; *last_req; last_req++) { ++ lr_type = (*last_req)->lr_type; ++ if (lr_type == KRB5_LRQ_ALL_PW_EXPTIME || ++ lr_type == KRB5_LRQ_ONE_PW_EXPTIME) { ++ *is_last_req = TRUE; ++ *pw_exp = (*last_req)->value; ++ } else if (lr_type == KRB5_LRQ_ALL_ACCT_EXPTIME || ++ lr_type == KRB5_LRQ_ONE_ACCT_EXPTIME) { ++ *is_last_req = TRUE; ++ *acct_exp = (*last_req)->value; ++ } ++ } ++ } ++ ++ /* If we didn't find any, use the ambiguous key_exp field. */ ++ if (*is_last_req == FALSE) ++ *pw_exp = enc_part2->key_exp; ++} ++ ++/* ++ * Send an appropriate warning prompter if as_reply indicates that the password ++ * is going to expire soon. If an expire callback was provided, use that ++ * instead. ++ */ ++static void ++warn_pw_expiry(krb5_context context, krb5_get_init_creds_opt *options, ++ krb5_prompter_fct prompter, void *data, ++ const char *in_tkt_service, krb5_kdc_rep *as_reply) ++{ ++ krb5_error_code ret; ++ krb5_expire_callback_func expire_cb; ++ void *expire_data; ++ krb5_timestamp pw_exp, acct_exp, now; ++ krb5_boolean is_last_req; ++ krb5_deltat delta; ++ char ts[256], banner[1024]; ++ ++ if (as_reply == NULL || as_reply->enc_part2 == NULL) ++ return; ++ ++ get_expiry_times(as_reply->enc_part2, &pw_exp, &acct_exp, &is_last_req); ++ ++ k5_gic_opt_get_expire_cb(options, &expire_cb, &expire_data); ++ if (expire_cb != NULL) { ++ /* Invoke the expire callback and don't send prompter warnings. */ ++ (*expire_cb)(context, expire_data, pw_exp, acct_exp, is_last_req); ++ return; ++ } ++ ++ /* Don't warn if no password expiry value was sent. */ ++ if (pw_exp == 0) ++ return; ++ ++ /* Don't warn if the password is being changed. */ ++ if (in_tkt_service && strcmp(in_tkt_service, "kadmin/changepw") == 0) ++ return; ++ ++ /* ++ * If the expiry time came from a last_req field, assume the KDC wants us ++ * to warn. Otherwise, warn only if the expiry time is less than a week ++ * from now. ++ */ ++ ret = krb5_timeofday(context, &now); ++ if (ret != 0) ++ return; ++ if (!is_last_req && ++ (ts_after(now, pw_exp) || ts_delta(pw_exp, now) > 7 * 24 * 60 * 60)) ++ return; ++ ++ if (!prompter) ++ return; ++ ++ ret = krb5_timestamp_to_string(pw_exp, ts, sizeof(ts)); ++ if (ret != 0) ++ return; ++ ++ delta = ts_delta(pw_exp, now); ++ if (delta < 3600) { ++ snprintf(banner, sizeof(banner), ++ _("Warning: Your password will expire in less than one hour " ++ "on %s"), ts); ++ } else if (delta < 86400 * 2) { ++ snprintf(banner, sizeof(banner), ++ _("Warning: Your password will expire in %d hour%s on %s"), ++ delta / 3600, delta < 7200 ? "" : "s", ts); ++ } else { ++ snprintf(banner, sizeof(banner), ++ _("Warning: Your password will expire in %d days on %s"), ++ delta / 86400, ts); ++ } ++ ++ /* PROMPTER_INVOCATION */ ++ (*prompter)(context, data, 0, banner, 0, 0); ++} ++ + static krb5_error_code + init_creds_step_reply(krb5_context context, + krb5_init_creds_context ctx, +@@ -1700,6 +1810,8 @@ init_creds_step_reply(krb5_context context, + + /* success */ + ctx->complete = TRUE; ++ warn_pw_expiry(context, ctx->opt, ctx->prompter, ctx->prompter_data, ++ ctx->in_tkt_service, ctx->reply); + + cleanup: + krb5_free_pa_data(context, kdc_padata); +diff --git a/src/lib/krb5/krb/gic_pwd.c b/src/lib/krb5/krb/gic_pwd.c +index 3565a7c4c..f9befac3e 100644 +--- a/src/lib/krb5/krb/gic_pwd.c ++++ b/src/lib/krb5/krb/gic_pwd.c +@@ -133,113 +133,6 @@ krb5_init_creds_set_password(krb5_context context, + return 0; + } + +-/* Return the password expiry time indicated by enc_part2. Set *is_last_req +- * if the information came from a last_req value. */ +-static void +-get_expiry_times(krb5_enc_kdc_rep_part *enc_part2, krb5_timestamp *pw_exp, +- krb5_timestamp *acct_exp, krb5_boolean *is_last_req) +-{ +- krb5_last_req_entry **last_req; +- krb5_int32 lr_type; +- +- *pw_exp = 0; +- *acct_exp = 0; +- *is_last_req = FALSE; +- +- /* Look for last-req entries for password or account expiration. */ +- if (enc_part2->last_req) { +- for (last_req = enc_part2->last_req; *last_req; last_req++) { +- lr_type = (*last_req)->lr_type; +- if (lr_type == KRB5_LRQ_ALL_PW_EXPTIME || +- lr_type == KRB5_LRQ_ONE_PW_EXPTIME) { +- *is_last_req = TRUE; +- *pw_exp = (*last_req)->value; +- } else if (lr_type == KRB5_LRQ_ALL_ACCT_EXPTIME || +- lr_type == KRB5_LRQ_ONE_ACCT_EXPTIME) { +- *is_last_req = TRUE; +- *acct_exp = (*last_req)->value; +- } +- } +- } +- +- /* If we didn't find any, use the ambiguous key_exp field. */ +- if (*is_last_req == FALSE) +- *pw_exp = enc_part2->key_exp; +-} +- +-/* +- * Send an appropriate warning prompter if as_reply indicates that the password +- * is going to expire soon. If an expire callback was provided, use that +- * instead. +- */ +-static void +-warn_pw_expiry(krb5_context context, krb5_get_init_creds_opt *options, +- krb5_prompter_fct prompter, void *data, +- const char *in_tkt_service, krb5_kdc_rep *as_reply) +-{ +- krb5_error_code ret; +- krb5_expire_callback_func expire_cb; +- void *expire_data; +- krb5_timestamp pw_exp, acct_exp, now; +- krb5_boolean is_last_req; +- krb5_deltat delta; +- char ts[256], banner[1024]; +- +- get_expiry_times(as_reply->enc_part2, &pw_exp, &acct_exp, &is_last_req); +- +- k5_gic_opt_get_expire_cb(options, &expire_cb, &expire_data); +- if (expire_cb != NULL) { +- /* Invoke the expire callback and don't send prompter warnings. */ +- (*expire_cb)(context, expire_data, pw_exp, acct_exp, is_last_req); +- return; +- } +- +- /* Don't warn if no password expiry value was sent. */ +- if (pw_exp == 0) +- return; +- +- /* Don't warn if the password is being changed. */ +- if (in_tkt_service && strcmp(in_tkt_service, "kadmin/changepw") == 0) +- return; +- +- /* +- * If the expiry time came from a last_req field, assume the KDC wants us +- * to warn. Otherwise, warn only if the expiry time is less than a week +- * from now. +- */ +- ret = krb5_timeofday(context, &now); +- if (ret != 0) +- return; +- if (!is_last_req && +- (ts_after(now, pw_exp) || ts_delta(pw_exp, now) > 7 * 24 * 60 * 60)) +- return; +- +- if (!prompter) +- return; +- +- ret = krb5_timestamp_to_string(pw_exp, ts, sizeof(ts)); +- if (ret != 0) +- return; +- +- delta = ts_delta(pw_exp, now); +- if (delta < 3600) { +- snprintf(banner, sizeof(banner), +- _("Warning: Your password will expire in less than one hour " +- "on %s"), ts); +- } else if (delta < 86400*2) { +- snprintf(banner, sizeof(banner), +- _("Warning: Your password will expire in %d hour%s on %s"), +- delta / 3600, delta < 7200 ? "" : "s", ts); +- } else { +- snprintf(banner, sizeof(banner), +- _("Warning: Your password will expire in %d days on %s"), +- delta / 86400, ts); +- } +- +- /* PROMPTER_INVOCATION */ +- (*prompter)(context, data, 0, banner, 0, 0); +-} +- + /* + * Create a temporary options structure for getting a kadmin/changepw ticket, + * based on the appplication-specified options. Propagate all application +@@ -496,9 +389,6 @@ krb5_get_init_creds_password(krb5_context context, + goto cleanup; + + cleanup: +- if (ret == 0) +- warn_pw_expiry(context, options, prompter, data, in_tkt_service, +- as_reply); + free(chpw_opts); + zapfree(gakpw.storage.data, gakpw.storage.length); + memset(pw0array, 0, sizeof(pw0array)); +diff --git a/src/lib/krb5/krb/t_expire_warn.c b/src/lib/krb5/krb/t_expire_warn.c +index 1e59acba1..dc8dc8fb3 100644 +--- a/src/lib/krb5/krb/t_expire_warn.c ++++ b/src/lib/krb5/krb/t_expire_warn.c +@@ -28,6 +28,13 @@ + + static int exp_dummy, prompt_dummy; + ++static void ++check(krb5_error_code code) ++{ ++ if (code != 0) ++ abort(); ++} ++ + static krb5_error_code + prompter_cb(krb5_context ctx, void *data, const char *name, + const char *banner, int num_prompts, krb5_prompt prompts[]) +@@ -52,36 +59,48 @@ int + main(int argc, char **argv) + { + krb5_context ctx; ++ krb5_init_creds_context icctx; + krb5_get_init_creds_opt *opt; + char *user, *password, *service = NULL; +- krb5_boolean use_cb; ++ krb5_boolean use_cb, stepwise; + krb5_principal client; + krb5_creds creds; + +- if (argc < 4) { +- fprintf(stderr, "Usage: %s username password {1|0} [service]\n", ++ if (argc < 5) { ++ fprintf(stderr, "Usage: %s username password {1|0} {1|0} [service]\n", + argv[0]); + return 1; + } + user = argv[1]; + password = argv[2]; + use_cb = atoi(argv[3]); +- if (argc >= 5) +- service = argv[4]; ++ stepwise = atoi(argv[4]); ++ if (argc >= 6) ++ service = argv[5]; + +- assert(krb5_init_context(&ctx) == 0); +- assert(krb5_get_init_creds_opt_alloc(ctx, &opt) == 0); ++ check(krb5_init_context(&ctx)); ++ check(krb5_get_init_creds_opt_alloc(ctx, &opt)); + if (use_cb) { +- assert(krb5_get_init_creds_opt_set_expire_callback(ctx, opt, expire_cb, +- &exp_dummy) == 0); ++ check(krb5_get_init_creds_opt_set_expire_callback(ctx, opt, expire_cb, ++ &exp_dummy)); ++ } ++ check(krb5_parse_name(ctx, user, &client)); ++ if (stepwise) { ++ check(krb5_init_creds_init(ctx, client, prompter_cb, &prompt_dummy, 0, ++ opt, &icctx)); ++ krb5_init_creds_set_password(ctx, icctx, password); ++ if (service != NULL) ++ check(krb5_init_creds_set_service(ctx, icctx, service)); ++ check(krb5_init_creds_get(ctx, icctx)); ++ krb5_init_creds_free(ctx, icctx); ++ } else { ++ check(krb5_get_init_creds_password(ctx, &creds, client, password, ++ prompter_cb, &prompt_dummy, 0, ++ service, opt)); ++ krb5_free_cred_contents(ctx, &creds); + } +- assert(krb5_parse_name(ctx, user, &client) == 0); +- assert(krb5_get_init_creds_password(ctx, &creds, client, password, +- prompter_cb, &prompt_dummy, 0, service, +- opt) == 0); + krb5_get_init_creds_opt_free(ctx, opt); + krb5_free_principal(ctx, client); +- krb5_free_cred_contents(ctx, &creds); + krb5_free_context(ctx); + return 0; + } +diff --git a/src/lib/krb5/krb/t_expire_warn.py b/src/lib/krb5/krb/t_expire_warn.py +index e021379ab..d4926d67d 100755 +--- a/src/lib/krb5/krb/t_expire_warn.py ++++ b/src/lib/krb5/krb/t_expire_warn.py +@@ -36,28 +36,33 @@ realm.run([kadminl, 'addprinc', '-pw', 'pass', '-pwexpire', '12 hours', + realm.run([kadminl, 'addprinc', '-pw', 'pass', '-pwexpire', '3 days', 'days']) + + # Check for expected prompter warnings when no expire callback is used. +-output = realm.run(['./t_expire_warn', 'noexpire', 'pass', '0']) ++output = realm.run(['./t_expire_warn', 'noexpire', 'pass', '0', '0']) + if output: + fail('Unexpected output for noexpire') +-output = realm.run(['./t_expire_warn', 'minutes', 'pass', '0']) +-if ' less than one hour on ' not in output: +- fail('Expected warning not seen for minutes') +-output = realm.run(['./t_expire_warn', 'hours', 'pass', '0']) +-if ' hours on ' not in output: +- fail('Expected warning not seen for hours') +-output = realm.run(['./t_expire_warn', 'days', 'pass', '0']) +-if ' days on ' not in output: +- fail('Expected warning not seen for days') ++realm.run(['./t_expire_warn', 'minutes', 'pass', '0', '0'], ++ expected_msg=' less than one hour on ') ++realm.run(['./t_expire_warn', 'hours', 'pass', '0', '0'], ++ expected_msg=' hours on ') ++realm.run(['./t_expire_warn', 'days', 'pass', '0', '0'], ++ expected_msg=' days on ') ++# Try one case with the stepwise interface. ++realm.run(['./t_expire_warn', 'days', 'pass', '0', '1'], ++ expected_msg=' days on ') + + # Check for expected expire callback behavior. These tests are + # carefully agnostic about whether the KDC supports last_req fields, + # and could be made more specific if last_req support is added. +-output = realm.run(['./t_expire_warn', 'noexpire', 'pass', '1']) ++output = realm.run(['./t_expire_warn', 'noexpire', 'pass', '1', '0']) + if 'password_expiration = 0\n' not in output or \ + 'account_expiration = 0\n' not in output or \ + 'is_last_req = ' not in output: + fail('Expected callback output not seen for noexpire') +-output = realm.run(['./t_expire_warn', 'days', 'pass', '1']) ++output = realm.run(['./t_expire_warn', 'days', 'pass', '1', '0']) ++if 'password_expiration = ' not in output or \ ++ 'password_expiration = 0\n' in output: ++ fail('Expected non-zero password expiration not seen for days') ++# Try one case with the stepwise interface. ++output = realm.run(['./t_expire_warn', 'days', 'pass', '1', '1']) + if 'password_expiration = ' not in output or \ + 'password_expiration = 0\n' in output: + fail('Expected non-zero password expiration not seen for days') diff --git a/SOURCES/Fix-LDAP-policy-enforcement-of-pw_expiration.patch b/SOURCES/Fix-LDAP-policy-enforcement-of-pw_expiration.patch new file mode 100644 index 0000000..cf00e1f --- /dev/null +++ b/SOURCES/Fix-LDAP-policy-enforcement-of-pw_expiration.patch @@ -0,0 +1,293 @@ +From dc475c320677d767d33603afb1bf4067fd16cfb1 Mon Sep 17 00:00:00 2001 +From: Robbie Harwood +Date: Tue, 17 Dec 2019 17:37:41 -0500 +Subject: [PATCH] Fix LDAP policy enforcement of pw_expiration + +In the LDAP backend, the change mask is used to determine what LDAP +attributes to update. As a result, password expiration was not set +from policy when running during addprinc, among other issues. +However, when the mask did not contain KADM5_PRINCIPAL, pw_expiration +would be applied regardless, which meant that (for instance) changing +the password would cause the password application to be applied. + +Remove the check for KADM5_PRINCIPAL, and fix the mask to contain +KADM5_PW_EXPIRATION where appropriate. Add a regression test to +t_kdb.py. + +[ghudson@mit.edu: also set KADM5_ATTRIBUTES for randkey and setkey +since they both unset KRB5_KDB_REQUIRES_PWCHANGE; edited comments and +commit message] + +(cherry picked from commit 6b004dd5739bded71be4290c11e7ac3a816c7e09) + +ticket: 8861 +version_fixed: 1.17.2 + +(cherry picked from commit 90a13e6d9763c510339c7a40fbc4ecb30041f421) +--- + src/lib/kadm5/srv/svr_principal.c | 89 +++++++++---------- + .../kdb/ldap/libkdb_ldap/ldap_principal2.c | 13 --- + src/tests/t_kdb.py | 17 ++++ + 3 files changed, 60 insertions(+), 59 deletions(-) + +diff --git a/src/lib/kadm5/srv/svr_principal.c b/src/lib/kadm5/srv/svr_principal.c +index a44d53f03..50db8bbcd 100644 +--- a/src/lib/kadm5/srv/svr_principal.c ++++ b/src/lib/kadm5/srv/svr_principal.c +@@ -356,6 +356,11 @@ kadm5_create_principal_3(void *server_handle, + kdb = calloc(1, sizeof(*kdb)); + if (kdb == NULL) + return ENOMEM; ++ ++ /* In all cases the principal entry is new and key data is set; let the ++ * database provider know. */ ++ kdb->mask = mask | KADM5_KEY_DATA | KADM5_PRINCIPAL; ++ + memset(&adb, 0, sizeof(osa_princ_ent_rec)); + + /* +@@ -405,14 +410,12 @@ kadm5_create_principal_3(void *server_handle, + kdb->expiration = handle->params.expiration; + + kdb->pw_expiration = 0; +- if (have_polent) { +- if(polent.pw_max_life) +- kdb->pw_expiration = ts_incr(now, polent.pw_max_life); +- else +- kdb->pw_expiration = 0; +- } +- if ((mask & KADM5_PW_EXPIRATION)) ++ if (mask & KADM5_PW_EXPIRATION) { + kdb->pw_expiration = entry->pw_expiration; ++ } else if (have_polent && polent.pw_max_life) { ++ kdb->mask |= KADM5_PW_EXPIRATION; ++ kdb->pw_expiration = ts_incr(now, polent.pw_max_life); ++ } + + kdb->last_success = 0; + kdb->last_failed = 0; +@@ -499,9 +502,6 @@ kadm5_create_principal_3(void *server_handle, + adb.policy = entry->policy; + } + +- /* In all cases key and the principal data is set, let the database provider know */ +- kdb->mask = mask | KADM5_KEY_DATA | KADM5_PRINCIPAL ; +- + /* store the new db entry */ + ret = kdb_put_entry(handle, kdb, &adb); + +@@ -597,6 +597,9 @@ kadm5_modify_principal(void *server_handle, + if (ret) + return(ret); + ++ /* Let the mask propagate to the database provider. */ ++ kdb->mask = mask; ++ + /* + * This is pretty much the same as create ... + */ +@@ -612,11 +615,15 @@ kadm5_modify_principal(void *server_handle, + free(adb.policy); + adb.policy = strdup(entry->policy); + } +- if (have_pol) { ++ ++ if (mask & KADM5_PW_EXPIRATION) { ++ kdb->pw_expiration = entry->pw_expiration; ++ } else if (have_pol) { + /* set pw_max_life based on new policy */ ++ kdb->mask |= KADM5_PW_EXPIRATION; + if (pol.pw_max_life) { + ret = krb5_dbe_lookup_last_pwd_change(handle->context, kdb, +- &(kdb->pw_expiration)); ++ &kdb->pw_expiration); + if (ret) + goto done; + kdb->pw_expiration = ts_incr(kdb->pw_expiration, pol.pw_max_life); +@@ -638,8 +645,6 @@ kadm5_modify_principal(void *server_handle, + kdb->max_life = entry->max_life; + if ((mask & KADM5_PRINC_EXPIRE_TIME)) + kdb->expiration = entry->princ_expire_time; +- if (mask & KADM5_PW_EXPIRATION) +- kdb->pw_expiration = entry->pw_expiration; + if (mask & KADM5_MAX_RLIFE) + kdb->max_renewable_life = entry->max_renewable_life; + +@@ -678,9 +683,6 @@ kadm5_modify_principal(void *server_handle, + kdb->fail_auth_count = 0; + } + +- /* let the mask propagate to the database provider */ +- kdb->mask = mask; +- + ret = k5_kadm5_hook_modify(handle->context, handle->hook_handles, + KADM5_HOOK_STAGE_PRECOMMIT, entry, mask); + if (ret) +@@ -1358,6 +1360,11 @@ kadm5_chpass_principal_3(void *server_handle, + if ((ret = kdb_get_entry(handle, principal, &kdb, &adb))) + return(ret); + ++ /* We will always be changing the key data, attributes, auth failure count, ++ * and password expiration time. */ ++ kdb->mask = KADM5_KEY_DATA | KADM5_ATTRIBUTES | KADM5_FAIL_AUTH_COUNT | ++ KADM5_PW_EXPIRATION; ++ + ret = apply_keysalt_policy(handle, adb.policy, n_ks_tuple, ks_tuple, + &new_n_ks_tuple, &new_ks_tuple); + if (ret) +@@ -1403,6 +1410,7 @@ kadm5_chpass_principal_3(void *server_handle, + if (ret) + goto done; + ++ kdb->pw_expiration = 0; + if ((adb.aux_attributes & KADM5_POLICY)) { + /* the policy was loaded before */ + +@@ -1435,10 +1443,6 @@ kadm5_chpass_principal_3(void *server_handle, + + if (pol.pw_max_life) + kdb->pw_expiration = ts_incr(now, pol.pw_max_life); +- else +- kdb->pw_expiration = 0; +- } else { +- kdb->pw_expiration = 0; + } + + #ifdef USE_PASSWORD_SERVER +@@ -1477,11 +1481,6 @@ kadm5_chpass_principal_3(void *server_handle, + /* unlock principal on this KDC */ + kdb->fail_auth_count = 0; + +- /* key data and attributes changed, let the database provider know */ +- kdb->mask = KADM5_KEY_DATA | KADM5_ATTRIBUTES | +- KADM5_FAIL_AUTH_COUNT; +- /* | KADM5_CPW_FUNCTION */ +- + if (hist_added) + kdb->mask |= KADM5_KEY_HIST; + +@@ -1556,6 +1555,11 @@ kadm5_randkey_principal_3(void *server_handle, + if ((ret = kdb_get_entry(handle, principal, &kdb, &adb))) + return(ret); + ++ /* We will always be changing the key data, attributes, auth failure count, ++ * and password expiration time. */ ++ kdb->mask = KADM5_KEY_DATA | KADM5_ATTRIBUTES | KADM5_FAIL_AUTH_COUNT | ++ KADM5_PW_EXPIRATION; ++ + ret = apply_keysalt_policy(handle, adb.policy, n_ks_tuple, ks_tuple, + &new_n_ks_tuple, &new_ks_tuple); + if (ret) +@@ -1593,14 +1597,10 @@ kadm5_randkey_principal_3(void *server_handle, + if (ret) + goto done; + } +- if (have_pol) { +- if (pol.pw_max_life) +- kdb->pw_expiration = ts_incr(now, pol.pw_max_life); +- else +- kdb->pw_expiration = 0; +- } else { +- kdb->pw_expiration = 0; +- } ++ ++ kdb->pw_expiration = 0; ++ if (have_pol && pol.pw_max_life) ++ kdb->pw_expiration = ts_incr(now, pol.pw_max_life); + + ret = krb5_dbe_update_last_pwd_change(handle->context, kdb, now); + if (ret) +@@ -1618,10 +1618,6 @@ kadm5_randkey_principal_3(void *server_handle, + goto done; + } + +- /* key data changed, let the database provider know */ +- kdb->mask = KADM5_KEY_DATA | KADM5_FAIL_AUTH_COUNT; +- /* | KADM5_RANDKEY_USED */; +- + ret = k5_kadm5_hook_chpass(handle->context, handle->hook_handles, + KADM5_HOOK_STAGE_PRECOMMIT, principal, keepold, + new_n_ks_tuple, new_ks_tuple, NULL); +@@ -1872,6 +1868,11 @@ kadm5_setkey_principal_4(void *server_handle, krb5_principal principal, + if (ret) + return ret; + ++ /* We will always be changing the key data, attributes, auth failure count, ++ * and password expiration time. */ ++ kdb->mask = KADM5_KEY_DATA | KADM5_ATTRIBUTES | KADM5_FAIL_AUTH_COUNT | ++ KADM5_PW_EXPIRATION; ++ + if (kvno == 0) { + /* Pick the next kvno. */ + for (i = 0; i < kdb->n_key_data; i++) { +@@ -1973,14 +1974,10 @@ kadm5_setkey_principal_4(void *server_handle, krb5_principal principal, + if (ret) + goto done; + } +- if (have_pol) { +- if (pol.pw_max_life) +- kdb->pw_expiration = ts_incr(now, pol.pw_max_life); +- else +- kdb->pw_expiration = 0; +- } else { +- kdb->pw_expiration = 0; +- } ++ ++ kdb->pw_expiration = 0; ++ if (have_pol && pol.pw_max_life) ++ kdb->pw_expiration = ts_incr(now, pol.pw_max_life); + + ret = krb5_dbe_update_last_pwd_change(handle->context, kdb, now); + if (ret) +diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c +index b7c9212cb..8b98a4482 100644 +--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c ++++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c +@@ -1233,19 +1233,6 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry, + goto cleanup; + } + +- if (!(entry->mask & KADM5_PRINCIPAL)) { +- memset(strval, 0, sizeof(strval)); +- if ((strval[0]=getstringtime(entry->pw_expiration)) == NULL) +- goto cleanup; +- if ((st=krb5_add_str_mem_ldap_mod(&mods, +- "krbpasswordexpiration", +- LDAP_MOD_REPLACE, strval)) != 0) { +- free (strval[0]); +- goto cleanup; +- } +- free (strval[0]); +- } +- + /* Update last password change whenever a new key is set */ + { + krb5_timestamp last_pw_changed; +diff --git a/src/tests/t_kdb.py b/src/tests/t_kdb.py +index 319687ff3..38a2fd020 100755 +--- a/src/tests/t_kdb.py ++++ b/src/tests/t_kdb.py +@@ -467,6 +467,23 @@ else: + realm.run([kadminl, 'modprinc', '-pwexpire', '2040-02-03', 'user']) + realm.run([kadminl, 'getprinc', 'user'], expected_msg=' 2040\n') + ++# Regression test for #8861 (pw_expiration policy enforcement). ++mark('pw_expiration propogation') ++# Create a policy with a max life and verify its application. ++realm.run([kadminl, 'addpol', '-maxlife', '1s', 'pw_e']) ++realm.run([kadminl, 'addprinc', '-policy', 'pw_e', '-pw', 'password', ++ 'pwuser']) ++out = realm.run([kadminl, 'getprinc', 'pwuser'], ++ expected_msg='Password expiration date: ') ++if 'Password expiration date: [never]' in out: ++ fail('pw_expiration not applied at principal creation') ++# Unset the policy max life and verify its application during password ++# change. ++realm.run([kadminl, 'modpol', '-maxlife', '0', 'pw_e']) ++realm.run([kadminl, 'cpw', '-pw', 'password_', 'pwuser']) ++realm.run([kadminl, 'getprinc', 'pwuser'], ++ expected_msg='Password expiration date: [never]') ++ + realm.stop() + + # Briefly test dump and load. diff --git a/SOURCES/Remove-vestigial-svr_principal.c-code.patch b/SOURCES/Remove-vestigial-svr_principal.c-code.patch new file mode 100644 index 0000000..d0f1fc8 --- /dev/null +++ b/SOURCES/Remove-vestigial-svr_principal.c-code.patch @@ -0,0 +1,121 @@ +From 591964cbcec69e2539a1657f8872b55ed782d844 Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Wed, 17 May 2017 15:21:34 -0400 +Subject: [PATCH] Remove vestigial svr_principal.c code + +In kadm5_chpass_principal_3(), kadm5_randkey_principal_3(), and +kadm5_setv4key_principal(), remove the disabled code to enforce +pw_min_life (which is enforced in kadmind as noted in the comments), +as well as the unnecessary last_pwd lookups beforehand. + +(cherry picked from commit 274f751937a7a713fffd61290c0ce15e890f4b50) +--- + src/lib/kadm5/srv/svr_principal.c | 60 ++----------------------------- + 1 file changed, 2 insertions(+), 58 deletions(-) + +diff --git a/src/lib/kadm5/srv/svr_principal.c b/src/lib/kadm5/srv/svr_principal.c +index 73733d371..a44d53f03 100644 +--- a/src/lib/kadm5/srv/svr_principal.c ++++ b/src/lib/kadm5/srv/svr_principal.c +@@ -1333,7 +1333,7 @@ kadm5_chpass_principal_3(void *server_handle, + kadm5_policy_ent_rec pol; + osa_princ_ent_rec adb; + krb5_db_entry *kdb; +- int ret, ret2, last_pwd, hist_added; ++ int ret, ret2, hist_added; + krb5_boolean have_pol = FALSE; + kadm5_server_handle_t handle = server_handle; + osa_pw_hist_ent hist; +@@ -1406,24 +1406,6 @@ kadm5_chpass_principal_3(void *server_handle, + if ((adb.aux_attributes & KADM5_POLICY)) { + /* the policy was loaded before */ + +- ret = krb5_dbe_lookup_last_pwd_change(handle->context, kdb, &last_pwd); +- if (ret) +- goto done; +- +-#if 0 +- /* +- * The spec says this check is overridden if the caller has +- * modify privilege. The admin server therefore makes this +- * check itself (in chpass_principal_wrapper, misc.c). A +- * local caller implicitly has all authorization bits. +- */ +- if ((now - last_pwd) < pol.pw_min_life && +- !(kdb->attributes & KRB5_KDB_REQUIRES_PWCHANGE)) { +- ret = KADM5_PASS_TOOSOON; +- goto done; +- } +-#endif +- + ret = check_pw_reuse(handle->context, hist_keyblocks, + kdb->n_key_data, kdb->key_data, + 1, &hist); +@@ -1553,7 +1535,7 @@ kadm5_randkey_principal_3(void *server_handle, + osa_princ_ent_rec adb; + krb5_timestamp now; + kadm5_policy_ent_rec pol; +- int ret, last_pwd, n_new_keys; ++ int ret, n_new_keys; + krb5_boolean have_pol = FALSE; + kadm5_server_handle_t handle = server_handle; + krb5_keyblock *act_mkey; +@@ -1612,24 +1594,6 @@ kadm5_randkey_principal_3(void *server_handle, + goto done; + } + if (have_pol) { +- ret = krb5_dbe_lookup_last_pwd_change(handle->context, kdb, &last_pwd); +- if (ret) +- goto done; +- +-#if 0 +- /* +- * The spec says this check is overridden if the caller has +- * modify privilege. The admin server therefore makes this +- * check itself (in chpass_principal_wrapper, misc.c). A +- * local caller implicitly has all authorization bits. +- */ +- if((now - last_pwd) < pol.pw_min_life && +- !(kdb->attributes & KRB5_KDB_REQUIRES_PWCHANGE)) { +- ret = KADM5_PASS_TOOSOON; +- goto done; +- } +-#endif +- + if (pol.pw_max_life) + kdb->pw_expiration = ts_incr(now, pol.pw_max_life); + else +@@ -1698,9 +1662,6 @@ kadm5_setv4key_principal(void *server_handle, + krb5_keysalt keysalt; + int i, kvno, ret; + krb5_boolean have_pol = FALSE; +-#if 0 +- int last_pwd; +-#endif + kadm5_server_handle_t handle = server_handle; + krb5_key_data tmp_key_data; + krb5_keyblock *act_mkey; +@@ -1763,23 +1724,6 @@ kadm5_setv4key_principal(void *server_handle, + goto done; + } + if (have_pol) { +-#if 0 +- /* +- * The spec says this check is overridden if the caller has +- * modify privilege. The admin server therefore makes this +- * check itself (in chpass_principal_wrapper, misc.c). A +- * local caller implicitly has all authorization bits. +- */ +- if (ret = krb5_dbe_lookup_last_pwd_change(handle->context, +- kdb, &last_pwd)) +- goto done; +- if((now - last_pwd) < pol.pw_min_life && +- !(kdb->attributes & KRB5_KDB_REQUIRES_PWCHANGE)) { +- ret = KADM5_PASS_TOOSOON; +- goto done; +- } +-#endif +- + if (pol.pw_max_life) + kdb->pw_expiration = ts_incr(now, pol.pw_max_life); + else diff --git a/SPECS/krb5.spec b/SPECS/krb5.spec index 249e0ca..e72c876 100644 --- a/SPECS/krb5.spec +++ b/SPECS/krb5.spec @@ -12,7 +12,7 @@ Summary: The Kerberos network authentication system Name: krb5 Version: 1.15.1 -Release: 46%{?dist} +Release: 50%{?dist} # - Maybe we should explode from the now-available-to-everybody tarball instead? # http://web.mit.edu/kerberos/dist/krb5/1.13/krb5-1.13.2-signed.tar @@ -137,6 +137,9 @@ Patch224: Address-some-optimized-out-memset-calls.patch Patch225: Add-the-certauth-dbmatch-module.patch Patch226: Correct-error-handling-bug-in-prior-commit.patch Patch227: Add-PKINIT-test-case-for-generic-client-cert.patch +Patch228: Do-expiration-warnings-for-all-init_creds-APIs.patch +Patch229: Remove-vestigial-svr_principal.c-code.patch +Patch230: Fix-LDAP-policy-enforcement-of-pw_expiration.patch License: MIT URL: http://web.mit.edu/kerberos/www/ @@ -407,6 +410,9 @@ ONLY by kerberos itself. Do not depend on this package. %patch225 -p1 -b .Add-the-certauth-dbmatch-module %patch226 -p1 -b .Correct-error-handling-bug-in-prior-commit %patch227 -p1 -b .Add-PKINIT-test-case-for-generic-client-cert +%patch228 -p1 -b .Do-expiration-warnings-for-all-init_creds-APIs +%patch229 -p1 -b .Remove-vestigial-svr_principal.c-code +%patch230 -p1 -b .Fix-LDAP-policy-enforcement-of-pw_expiration ln NOTICE LICENSE @@ -537,12 +543,11 @@ popd %{__cc} -fPIC -shared -o noport.so -Wall -Wextra $RPM_SOURCE_DIR/noport.c %check -# Alright, this much is still a work in progress. -%if %{?__isa_bits:%{__isa_bits}}%{!?__isa_bits:32} == 64 -if hostname | grep -q build ; then - sleep 600 -fi -%endif + +# The brew builders for this architecture are wildly unreliable. We +# run the full upstream suite separately on all arches anyway - this +# is just a smoke test. +%ifnarch s390x # Set things up to use the test wrappers. NSS_WRAPPER_HOSTNAME=test.example.com ; export NSS_WRAPPER_HOSTNAME @@ -563,6 +568,8 @@ keyctl session - make -C src/appl check TMPDIR=%{_tmppath} make -C src/clients check TMPDIR=%{_tmppath} keyctl session - make -C src/util check TMPDIR=%{_tmppath} +%endif # ifarch s390x + %install [ "$RPM_BUILD_ROOT" != '/' ] && rm -rf -- $RPM_BUILD_ROOT @@ -913,6 +920,22 @@ exit 0 %{_libdir}/libkadm5srv_mit.so.* %changelog +* Mon Apr 06 2020 Robbie Harwood - 1.15.1-50 +- Disable smoke tests on s390x and remove sleep +- Resolves: #1782492 + +* Mon Apr 06 2020 Robbie Harwood - 1.15.1-49 +- Fix LDAP policy enforcement of pw_expiration +- Resolves: #1782492 + +* Mon Apr 06 2020 Robbie Harwood - 1.15.1-48 +- Fix LDAP policy enforcement of pw_expiration +- Resolves: #1782492 + +* Mon Apr 06 2020 Robbie Harwood - 1.15.1-47 +- Do expiration warnings for all init_creds APIs +- Resolves: #1733289 + * Mon Jul 29 2019 Robbie Harwood - 1.15.1-46 - Add pkinit_cert_match support - Resolves: #1656126