aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGreg Hudson <ghudson@mit.edu>2010-08-12 17:41:41 +0000
committerGreg Hudson <ghudson@mit.edu>2010-08-12 17:41:41 +0000
commit06bdc5c1cd257e7e85d8d29833ca54dd55b3a4f2 (patch)
treeb7353e6ae8e6d93b1f58a4b31e29be204c89bd08
parent1c0791bb201ff274dbbf8aa644bfffc200f15f08 (diff)
downloadkrb5-06bdc5c1cd257e7e85d8d29833ca54dd55b3a4f2.zip
krb5-06bdc5c1cd257e7e85d8d29833ca54dd55b3a4f2.tar.gz
krb5-06bdc5c1cd257e7e85d8d29833ca54dd55b3a4f2.tar.bz2
Add GIC option for password/account expiration callback
Add a new GIC option to specify a callback to receive password and account expiration times found in an AS reply. See also: http://k5wiki.kerberos.org/wiki/Projects/Password_expiration_API ticket: 6755 git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@24241 dc483132-0cff-0310-8789-dd5450dbe970
-rw-r--r--src/include/k5-int.h2
-rw-r--r--src/include/krb5/krb5.hin44
-rw-r--r--src/lib/krb5/krb/Makefile.in6
-rw-r--r--src/lib/krb5/krb/gic_opt.c19
-rw-r--r--src/lib/krb5/krb/gic_pwd.c73
-rw-r--r--src/lib/krb5/krb/t_expire_warn.c90
-rw-r--r--src/lib/krb5/krb/t_expire_warn.py62
-rw-r--r--src/lib/krb5/libkrb5.exports1
8 files changed, 273 insertions, 24 deletions
diff --git a/src/include/k5-int.h b/src/include/k5-int.h
index 19bf26b..bb078c0 100644
--- a/src/include/k5-int.h
+++ b/src/include/k5-int.h
@@ -1142,6 +1142,8 @@ typedef struct _krb5_gic_opt_private {
char * fast_ccache_name;
krb5_ccache out_ccache;
krb5_flags fast_flags;
+ krb5_expire_callback_func *expire_cb;
+ void *expire_data;
} krb5_gic_opt_private;
/*
diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin
index 7d7e425..f49ef95 100644
--- a/src/include/krb5/krb5.hin
+++ b/src/include/krb5/krb5.hin
@@ -1003,6 +1003,8 @@ krb5_verify_checksum(krb5_context context, krb5_cksumtype ctype,
#define KRB5_LRQ_ONE_LAST_REQ (-5)
#define KRB5_LRQ_ALL_PW_EXPTIME 6
#define KRB5_LRQ_ONE_PW_EXPTIME (-6)
+#define KRB5_LRQ_ALL_ACCT_EXPTIME 7
+#define KRB5_LRQ_ONE_ACCT_EXPTIME (-7)
/* PADATA types */
#define KRB5_PADATA_NONE 0
@@ -2352,6 +2354,48 @@ krb5_get_init_creds_opt_get_fast_flags(krb5_context context,
/* Fast flags*/
#define KRB5_FAST_REQUIRED 1l<<0 /*!< Require KDC to support FAST*/
+typedef void
+krb5_expire_callback_func(krb5_context context, void *data,
+ krb5_timestamp password_expiration,
+ krb5_timestamp account_expiration,
+ krb5_boolean is_last_req);
+
+/**
+ * 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:
+ *
+ * If @a is_last_req is true, then the KDC reply contained last-req entries
+ * which unambiguously indicated the password expiration, account expiration,
+ * or both. (If either value was not present, the corresponding argument will
+ * be 0.) Furthermore, a non-zero @a password_expiration should be taken as a
+ * suggestion from the KDC that a warning be displayed.
+ *
+ * If @a is_last_req is false, then @a account_expiration will be 0 and @a
+ * password_expiration will contain the expiration time of either the password
+ * or account, or 0 if no expiration time was indicated in the KDC reply. The
+ * callback should independently decide whether to display a password
+ * expiration warning.
+ *
+ * Note that @a cb may be invoked even if credentials are being acquired for
+ * the kadmin/changepw service in order to change the password. It is the
+ * caller's responsibility to avoid displaying a password expiry warning in
+ * this case.
+ *
+ * Setting an expire callback with this API will cause
+ * krb5_get_init_creds_password() not to send password expiry warnings to the
+ * prompter, as it ordinarily may.
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_get_init_creds_opt_set_expire_callback(krb5_context context,
+ krb5_get_init_creds_opt *opt,
+ krb5_expire_callback_func cb,
+ void *data);
+
krb5_error_code KRB5_CALLCONV
krb5_get_init_creds_password(krb5_context context, krb5_creds *creds,
krb5_principal client, char *password,
diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in
index e52200d..895d444 100644
--- a/src/lib/krb5/krb/Makefile.in
+++ b/src/lib/krb5/krb/Makefile.in
@@ -383,6 +383,9 @@ t_princ: $(T_PRINC_OBJS) $(KRB5_BASE_DEPLIBS)
t_etypes: $(T_ETYPES_OBJS) $(KRB5_BASE_DEPLIBS)
$(CC_LINK) -o t_etypes $(T_ETYPES_OBJS) $(KRB5_BASE_LIBS)
+t_expire_warn: t_expire_warn.o $(KRB5_BASE_DEPLIBS)
+ $(CC_LINK) -o $@ t_expire_warn.o $(KRB5_BASE_LIBS)
+
TEST_PROGS= t_walk_rtree t_kerb t_ser t_deltat t_expand t_authdata t_pac \
t_princ t_etypes
@@ -423,6 +426,9 @@ check-unix:: $(TEST_PROGS)
$(RUN_SETUP) $(VALGRIND) ./t_princ
$(RUN_SETUP) $(VALGRIND) ./t_etypes
+check-pytests:: t_expire_warn
+ $(RUNPYTEST) $(srcdir)/t_expire_warn.py $(PYTESTFLAGS)
+
clean::
$(RM) $(OUTPRE)t_walk_rtree$(EXEEXT) $(OUTPRE)t_walk_rtree.$(OBJEXT) \
$(OUTPRE)t_kerb$(EXEEXT) $(OUTPRE)t_kerb.$(OBJEXT) \
diff --git a/src/lib/krb5/krb/gic_opt.c b/src/lib/krb5/krb/gic_opt.c
index ab29740..36f4f00 100644
--- a/src/lib/krb5/krb/gic_opt.c
+++ b/src/lib/krb5/krb/gic_opt.c
@@ -480,3 +480,22 @@ krb5_get_init_creds_opt_get_fast_flags(krb5_context context,
*out_flags = opte->opt_private->fast_flags;
return retval;
}
+
+krb5_error_code KRB5_CALLCONV
+krb5_get_init_creds_opt_set_expire_callback(krb5_context context,
+ krb5_get_init_creds_opt *opt,
+ krb5_expire_callback_func cb,
+ void *data)
+{
+ krb5_error_code retval = 0;
+ krb5_gic_opt_ext *opte;
+
+ retval = krb5int_gic_opt_to_opte(context, opt, &opte, 0,
+ "krb5_get_init_creds_opt_set_"
+ "expire_callback");
+ if (retval)
+ return retval;
+ opte->opt_private->expire_cb = cb;
+ opte->opt_private->expire_data = data;
+ return retval;
+}
diff --git a/src/lib/krb5/krb/gic_pwd.c b/src/lib/krb5/krb/gic_pwd.c
index af873be..1e0b741 100644
--- a/src/lib/krb5/krb/gic_pwd.c
+++ b/src/lib/krb5/krb/gic_pwd.c
@@ -106,48 +106,69 @@ krb5_init_creds_set_password(krb5_context context,
/* Return the password expiry time indicated by enc_part2. Set *is_last_req
* if the information came from a last_req value. */
-static krb5_timestamp
-get_expiry_time(krb5_enc_kdc_rep_part *enc_part2, krb5_boolean *is_last_req)
+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++) {
- if ((*last_req)->lr_type == KRB5_LRQ_ALL_PW_EXPTIME ||
- (*last_req)->lr_type == KRB5_LRQ_ONE_PW_EXPTIME) {
+ 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;
- return (*last_req)->value;
+ *acct_exp = (*last_req)->value;
}
}
}
- return enc_part2->key_exp;
+
+ /* 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 to prompter if as_reply indicates that the
- * password is going to expiry soon. */
+/*
+ * 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_prompter_fct prompter, void *data,
+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_timestamp exp_time, now;
+ krb5_timestamp pw_exp, acct_exp, now;
krb5_boolean is_last_req;
krb5_deltat delta;
+ krb5_gic_opt_ext *opte;
char ts[256], banner[1024];
- /* Don't warn if the password is being changed. */
- if (in_tkt_service && strcmp(in_tkt_service, "kadmin/changepw") == 0)
- return;
+ get_expiry_times(as_reply->enc_part2, &pw_exp, &acct_exp, &is_last_req);
- /* Get the current time and password expiry time. */
- if (as_reply->enc_part2 == NULL)
- return;
- ret = krb5_timeofday(context, &now);
- if (ret != 0)
+ ret = krb5int_gic_opt_to_opte(context, options, &opte, 0, "");
+ if (ret == 0 && opte->opt_private->expire_cb != NULL) {
+ krb5_expire_callback_func *cb = opte->opt_private->expire_cb;
+ void *cb_data = opte->opt_private->expire_data;
+
+ /* Invoke the expire callback and don't send prompter warnings. */
+ (*cb)(context, cb_data, pw_exp, acct_exp, is_last_req);
return;
- exp_time = get_expiry_time(as_reply->enc_part2, &is_last_req);
- if (exp_time == 0)
+ }
+
+ /* Don't warn if the password is being changed. */
+ if (in_tkt_service && strcmp(in_tkt_service, "kadmin/changepw") == 0)
return;
/*
@@ -155,18 +176,21 @@ warn_pw_expiry(krb5_context context, krb5_prompter_fct prompter, void *data,
* 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 &&
- (exp_time < now || (exp_time - now) > 7 * 24 * 60 * 60))
+ (pw_exp < now || (pw_exp - now) > 7 * 24 * 60 * 60))
return;
if (!prompter)
return;
- ret = krb5_timestamp_to_string(exp_time, ts, sizeof(ts));
+ ret = krb5_timestamp_to_string(pw_exp, ts, sizeof(ts));
if (ret != 0)
return;
- delta = exp_time - now;
+ delta = pw_exp - now;
if (delta < 3600) {
snprintf(banner, sizeof(banner),
"Warning: Your password will expire in less than one hour "
@@ -418,7 +442,8 @@ krb5_get_init_creds_password(krb5_context context,
cleanup:
if (ret == 0)
- warn_pw_expiry(context, prompter, data, in_tkt_service, as_reply);
+ warn_pw_expiry(context, options, prompter, data, in_tkt_service,
+ as_reply);
if (chpw_opts)
krb5_get_init_creds_opt_free(context, chpw_opts);
diff --git a/src/lib/krb5/krb/t_expire_warn.c b/src/lib/krb5/krb/t_expire_warn.c
new file mode 100644
index 0000000..6e8d87c
--- /dev/null
+++ b/src/lib/krb5/krb/t_expire_warn.c
@@ -0,0 +1,90 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * lib/krb5/krb/t_expire_warn.c
+ *
+ * Copyright (C) 2010 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ *
+ *
+ * Test harness for password expiration warnings.
+ */
+
+#include "k5-int.h"
+
+static int exp_dummy, prompt_dummy;
+
+static krb5_error_code
+prompter_cb(krb5_context ctx, void *data, const char *name,
+ const char *banner, int num_prompts, krb5_prompt prompts[])
+{
+ /* Not expecting any actual prompts, only banners. */
+ assert(num_prompts == 0);
+ assert(banner != NULL);
+ printf("Prompter: %s\n", banner);
+ return 0;
+}
+
+static void
+expire_cb(krb5_context ctx, void *data, krb5_timestamp password_expiration,
+ krb5_timestamp account_expiration, krb5_boolean is_last_req)
+{
+ printf("password_expiration = %ld\n", (long)password_expiration);
+ printf("account_expiration = %ld\n", (long)account_expiration);
+ printf("is_last_req = %d\n", (int)is_last_req);
+}
+
+int
+main(int argc, char **argv)
+{
+ krb5_context ctx;
+ krb5_get_init_creds_opt *opt;
+ char *user, *password, *service = NULL;
+ krb5_boolean use_cb;
+ krb5_principal client;
+ krb5_creds creds;
+
+ if (argc < 4) {
+ fprintf(stderr, "Usage: %s username password {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];
+
+ assert(krb5_init_context(&ctx) == 0);
+ assert(krb5_get_init_creds_opt_alloc(ctx, &opt) == 0);
+ if (use_cb) {
+ assert(krb5_get_init_creds_opt_set_expire_callback(ctx, opt, expire_cb,
+ &exp_dummy) == 0);
+ }
+ 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);
+ return 0;
+}
diff --git a/src/lib/krb5/krb/t_expire_warn.py b/src/lib/krb5/krb/t_expire_warn.py
new file mode 100644
index 0000000..dc49a4c
--- /dev/null
+++ b/src/lib/krb5/krb/t_expire_warn.py
@@ -0,0 +1,62 @@
+# Copyright (C) 2010 by the Massachusetts Institute of Technology.
+# All rights reserved.
+#
+# Export of this software from the United States of America may
+# require a specific license from the United States Government.
+# It is the responsibility of any person or organization contemplating
+# export to obtain such a license before exporting.
+#
+# WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+# distribute this software and its documentation for any purpose and
+# without fee is hereby granted, provided that the above copyright
+# notice appear in all copies and that both that copyright notice and
+# this permission notice appear in supporting documentation, and that
+# the name of M.I.T. not be used in advertising or publicity pertaining
+# to distribution of the software without specific, written prior
+# permission. Furthermore if you modify this software you must label
+# your software as modified software and not distribute it in such a
+# fashion that it might be confused with the original M.I.T. software.
+# M.I.T. makes no representations about the suitability of
+# this software for any purpose. It is provided "as is" without express
+# or implied warranty.
+
+#!/usr/bin/python
+from k5test import *
+
+# Create a bare-bones KDC.
+realm = K5Realm(create_user=False, create_host=False, start_kadmind=False)
+
+# Create principals with various password expirations.
+realm.run_kadminl('addprinc -pw pass noexpire')
+realm.run_kadminl('addprinc -pw pass -pwexpire "30 minutes" minutes')
+realm.run_kadminl('addprinc -pw pass -pwexpire "12 hours" 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_as_client(['./t_expire_warn', 'noexpire', 'pass', '0'])
+if output:
+ fail('Unexpected output for noexpire')
+output = realm.run_as_client(['./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_as_client(['./t_expire_warn', 'hours', 'pass', '0'])
+if ' hours on ' not in output:
+ fail('Expected warning not seen for hours')
+output = realm.run_as_client(['./t_expire_warn', 'days', 'pass', '0'])
+if ' days on ' not in output:
+ fail('Expected warning not seen for days')
+
+# 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_as_client(['./t_expire_warn', 'noexpire', 'pass', '1'])
+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_as_client(['./t_expire_warn', 'days', 'pass', '1'])
+if 'password_expiration = ' not in output or \
+ 'password_expiration = 0\n' in output:
+ fail('Expected non-zero password expiration not seen for days')
+
+success('Password expiration warning tests.')
diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports
index 2bd5972..af661ed 100644
--- a/src/lib/krb5/libkrb5.exports
+++ b/src/lib/krb5/libkrb5.exports
@@ -346,6 +346,7 @@ krb5_get_init_creds_opt_set_anonymous
krb5_get_init_creds_opt_set_canonicalize
krb5_get_init_creds_opt_set_change_password_prompt
krb5_get_init_creds_opt_set_etype_list
+krb5_get_init_creds_opt_set_expire_callback
krb5_get_init_creds_opt_set_fast_ccache_name
krb5_get_init_creds_opt_set_fast_flags
krb5_get_init_creds_opt_set_forwardable