summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGreg Hudson <ghudson@mit.edu>2012-05-09 17:40:38 +0000
committerGreg Hudson <ghudson@mit.edu>2012-05-09 17:40:38 +0000
commit13a0013f55e9bea8384234f5caa1a0b444749daf (patch)
tree421085bed0a911b9a5de9ea06fbd401f37d5904b
parentc250f6743a623ab5086c56324060ebf99c740379 (diff)
downloadkrb5-13a0013f55e9bea8384234f5caa1a0b444749daf.tar.gz
krb5-13a0013f55e9bea8384234f5caa1a0b444749daf.tar.xz
krb5-13a0013f55e9bea8384234f5caa1a0b444749daf.zip
Add API to interpret changepw result strings
Active Directory returns structured policy information in the nominally UTF-8 result string field of a password change reply. Add a new API krb5_chpw_message() to convert a result string into a displayable message, interpreting policy information if present. Patch from stefw@gnome.org with changes. ticket: 7128 git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@25857 dc483132-0cff-0310-8789-dd5450dbe970
-rw-r--r--doc/rst_source/krb_appldev/refs/api/index.rst1
-rw-r--r--src/clients/kpasswd/kpasswd.c10
-rw-r--r--src/include/krb5/krb5.hin26
-rw-r--r--src/lib/krb5/krb/chpw.c166
-rw-r--r--src/lib/krb5/krb/gic_pwd.c15
-rw-r--r--src/lib/krb5/libkrb5.exports1
-rw-r--r--src/lib/krb5_32.def3
-rw-r--r--src/tests/misc/Makefile.in16
-rw-r--r--src/tests/misc/test_chpw_message.c174
-rw-r--r--src/util/collected-client-lib/libcollected.exports1
10 files changed, 399 insertions, 14 deletions
diff --git a/doc/rst_source/krb_appldev/refs/api/index.rst b/doc/rst_source/krb_appldev/refs/api/index.rst
index 8735b91a39..743de67c41 100644
--- a/doc/rst_source/krb_appldev/refs/api/index.rst
+++ b/doc/rst_source/krb_appldev/refs/api/index.rst
@@ -23,6 +23,7 @@ Frequently used public interfaces
krb5_cc_new_unique.rst
krb5_cc_resolve.rst
krb5_change_password.rst
+ krb5_chpw_message.rst
krb5_free_context.rst
krb5_free_error_message.rst
krb5_free_principal.rst
diff --git a/src/clients/kpasswd/kpasswd.c b/src/clients/kpasswd/kpasswd.c
index b4b6eadd4d..7aed0f1ac8 100644
--- a/src/clients/kpasswd/kpasswd.c
+++ b/src/clients/kpasswd/kpasswd.c
@@ -53,6 +53,7 @@ int main(int argc, char *argv[])
krb5_ccache ccache;
krb5_get_init_creds_opt *opts = NULL;
krb5_creds creds;
+ char *message;
char pw[1024];
unsigned int pwlen;
@@ -154,11 +155,12 @@ int main(int argc, char *argv[])
}
if (result_code) {
- printf("%.*s%s%.*s\n",
+ if (krb5_chpw_message(context, &result_string, &message) != 0)
+ message = NULL;
+ printf("%.*s%s%s\n",
(int) result_code_string.length, result_code_string.data,
- result_string.length?": ":"",
- (int) result_string.length,
- result_string.data ? result_string.data : "");
+ message ? ": " : "", message ? message : NULL);
+ krb5_free_string(context, message);
krb5_get_init_creds_opt_free(context, opts);
exit(2);
}
diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin
index c267622dbb..741477caf8 100644
--- a/src/include/krb5/krb5.hin
+++ b/src/include/krb5/krb5.hin
@@ -4963,6 +4963,32 @@ krb5_set_password_using_ccache(krb5_context context, krb5_ccache ccache,
krb5_data *result_string);
/**
+ * Get a result message for changing or setting a password.
+ *
+ * @param [in] context Library context
+ * @param [in] server_string Data returned from the remote system
+ * @param [out] message_out A message displayable to the user
+ *
+ * This function processes the @a server_string returned in the @a
+ * result_string parameter of krb5_change_password(), krb5_set_password(), and
+ * related functions, and returns a displayable string. If @a server_string
+ * contains Active Directory structured policy information, it will be
+ * converted into human-readable text.
+ *
+ * Use krb5_free_string() to free @a message_out when it is no longer needed.
+ *
+ * @retval
+ * 0 Success
+ * @return
+ * Kerberos error codes
+ *
+ * @version First introduced in 1.11
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_chpw_message(krb5_context context, const krb5_data *server_string,
+ char **message_out);
+
+/**
* Retrieve configuration profile from the context.
*
* @param [in] context Library context
diff --git a/src/lib/krb5/krb/chpw.c b/src/lib/krb5/krb/chpw.c
index 7e43dcf1a1..beb77cb986 100644
--- a/src/lib/krb5/krb/chpw.c
+++ b/src/lib/krb5/krb/chpw.c
@@ -5,6 +5,7 @@
#include <string.h>
#include "k5-int.h"
+#include "k5-unicode.h"
#include "int-proto.h"
#include "auth_con.h"
@@ -349,3 +350,168 @@ cleanup:
}
return ret;
}
+
+/*
+ * Active Directory policy information is communicated in the result string
+ * field as a packed 30-byte sequence, starting with two zero bytes (so that
+ * the string appears as zero-length when interpreted as UTF-8). The bytes
+ * correspond to the fields in the following structure, with each field in
+ * big-endian byte order.
+ */
+struct ad_policy_info {
+ uint16_t zero_bytes;
+ uint32_t min_length_password;
+ uint32_t password_history;
+ uint32_t password_properties; /* see defines below */
+ uint64_t expire; /* in seconds * 10,000,000 */
+ uint64_t min_passwordage; /* in seconds * 10,000,000 */
+};
+
+#define AD_POLICY_INFO_LENGTH 30
+#define AD_POLICY_TIME_TO_DAYS (86400ULL * 10000000ULL)
+
+#define AD_POLICY_COMPLEX 0x00000001
+#define AD_POLICY_NO_ANON_CHANGE 0x00000002
+#define AD_POLICY_NO_CLEAR_CHANGE 0x00000004
+#define AD_POLICY_LOCKOUT_ADMINS 0x00000008
+#define AD_POLICY_STORE_CLEARTEXT 0x00000010
+#define AD_POLICY_REFUSE_CHANGE 0x00000020
+
+/* If buf already contains one or more sentences, add spaces to separate them
+ * from the next sentence. */
+static void
+add_spaces(struct k5buf *buf)
+{
+ if (krb5int_buf_len(buf) > 0)
+ krb5int_buf_add(buf, " ");
+}
+
+static krb5_error_code
+decode_ad_policy_info(const krb5_data *data, char **msg_out)
+{
+ struct ad_policy_info policy;
+ uint64_t password_days;
+ const char *p;
+ char *msg;
+ struct k5buf buf;
+
+ *msg_out = NULL;
+ if (data->length != AD_POLICY_INFO_LENGTH)
+ return 0;
+
+ p = data->data;
+ policy.zero_bytes = load_16_be(p);
+ p += 2;
+
+ /* first two bytes are zeros */
+ if (policy.zero_bytes != 0)
+ return 0;
+
+ /* Read in the rest of structure */
+ policy.min_length_password = load_32_be(p);
+ p += 4;
+ policy.password_history = load_32_be(p);
+ p += 4;
+ policy.password_properties = load_32_be(p);
+ p += 4;
+ policy.expire = load_64_be(p);
+ p += 8;
+ policy.min_passwordage = load_64_be(p);
+ p += 8;
+
+ /* Check that we processed exactly the expected number of bytes. */
+ assert(p == data->data + AD_POLICY_INFO_LENGTH);
+
+ krb5int_buf_init_dynamic(&buf);
+
+ /*
+ * Update src/tests/misc/test_chpw_message.c if changing these strings!
+ */
+
+ if (policy.password_properties & AD_POLICY_COMPLEX) {
+ krb5int_buf_add(&buf,
+ _("The password must include numbers or symbols. "
+ "Don't include any part of your name in the "
+ "password."));
+ }
+ if (policy.min_length_password > 0) {
+ add_spaces(&buf);
+ krb5int_buf_add_fmt(&buf,
+ ngettext("The password must contain at least %d "
+ "character.",
+ "The password must contain at least %d "
+ "characters.",
+ policy.min_length_password),
+ policy.min_length_password);
+ }
+ if (policy.password_history) {
+ add_spaces(&buf);
+ krb5int_buf_add_fmt(&buf,
+ ngettext("The password must be different from the "
+ "previous password.",
+ "The password must be different from the "
+ "previous %d passwords.",
+ policy.password_history),
+ policy.password_history);
+ }
+ if (policy.min_passwordage) {
+ password_days = policy.min_passwordage / AD_POLICY_TIME_TO_DAYS;
+ if (password_days == 0)
+ password_days = 1;
+ add_spaces(&buf);
+ krb5int_buf_add_fmt(&buf,
+ ngettext("The password can only be changed once a "
+ "day.",
+ "The password can only be changed every "
+ "%d days.", (int)password_days),
+ (int)password_days);
+ }
+
+ msg = krb5int_buf_data(&buf);
+ if (msg == NULL)
+ return ENOMEM;
+
+ if (*msg != '\0')
+ *msg_out = msg;
+ else
+ free(msg);
+ return 0;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_chpw_message(krb5_context context, const krb5_data *server_string,
+ char **message_out)
+{
+ krb5_error_code ret;
+ krb5_data *string;
+ char *msg;
+
+ *message_out = NULL;
+
+ /* If server_string contains an AD password policy, construct a message
+ * based on that. */
+ ret = decode_ad_policy_info(server_string, &msg);
+ if (ret == 0 && msg != NULL) {
+ *message_out = msg;
+ return 0;
+ }
+
+ /* If server_string contains a valid UTF-8 string, return that. */
+ if (server_string->length > 0 &&
+ memchr(server_string->data, 0, server_string->length) == NULL &&
+ krb5int_utf8_normalize(server_string, &string,
+ KRB5_UTF8_APPROX) == 0) {
+ *message_out = string->data; /* already null terminated */
+ free(string);
+ return 0;
+ }
+
+ /* server_string appears invalid, so try to be helpful. */
+ msg = strdup(_("Try a more complex password, or contact your "
+ "administrator."));
+ if (msg == NULL)
+ return ENOMEM;
+
+ *message_out = msg;
+ return 0;
+}
diff --git a/src/lib/krb5/krb/gic_pwd.c b/src/lib/krb5/krb/gic_pwd.c
index 68d28fe9d3..b25eb6da02 100644
--- a/src/lib/krb5/krb/gic_pwd.c
+++ b/src/lib/krb5/krb/gic_pwd.c
@@ -235,6 +235,7 @@ krb5_get_init_creds_password(krb5_context context,
char banner[1024], pw0array[1024], pw1array[1024];
krb5_prompt prompt[2];
krb5_prompt_type prompt_types[sizeof(prompt)/sizeof(prompt[0])];
+ char *message;
use_master = 0;
as_reply = NULL;
@@ -413,19 +414,21 @@ krb5_get_init_creds_password(krb5_context context,
/* the error was soft, so try again */
+ if (krb5_chpw_message(context, &result_string, &message) != 0)
+ message = NULL;
+
/* 100 is I happen to know that no code_string will be longer
than 100 chars */
- if (result_string.length > (sizeof(banner)-100))
- result_string.length = sizeof(banner)-100;
+ if (message != NULL && strlen(message) > (sizeof(banner) - 100))
+ message[sizeof(banner) - 100] = '\0';
snprintf(banner, sizeof(banner),
- _("%.*s%s%.*s. Please try again.\n"),
+ _("%.*s%s%s. Please try again.\n"),
(int) code_string.length, code_string.data,
- result_string.length ? ": " : "",
- (int) result_string.length,
- result_string.data ? result_string.data : "");
+ message ? ": " : "", message ? message : "");
+ free(message);
free(code_string.data);
free(result_string.data);
}
diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports
index cb73a14714..6319c72a85 100644
--- a/src/lib/krb5/libkrb5.exports
+++ b/src/lib/krb5/libkrb5.exports
@@ -213,6 +213,7 @@ krb5_change_cache
krb5_change_password
krb5_check_clockskew
krb5_check_transited_list
+krb5_chpw_message
krb5_chpw_result_code_string
krb5_clear_error_message
krb5_copy_addr
diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def
index d5922d2d15..bfc211535a 100644
--- a/src/lib/krb5_32.def
+++ b/src/lib/krb5_32.def
@@ -421,3 +421,6 @@ EXPORTS
krb5_pac_sign @395
krb5_find_authdata @396
krb5_check_clockskew @397
+
+; new in 1.11
+ krb5_chpw_message @398
diff --git a/src/tests/misc/Makefile.in b/src/tests/misc/Makefile.in
index 5a4b329bc2..8a601bf0da 100644
--- a/src/tests/misc/Makefile.in
+++ b/src/tests/misc/Makefile.in
@@ -4,9 +4,13 @@ RUN_SETUP = @KRB5_RUN_ENV@
PROG_LIBPATH=-L$(TOPLIBD)
PROG_RPATH=$(KRB5_LIBDIR)
-OBJS=test_getpw.o
+OBJS=\
+ test_getpw.o \
+ test_chpw_message.o
+
SRCS=\
$(srcdir)/test_getpw.c \
+ $(srcdir)/test_chpw_message.c \
$(srcdir)/test_getsockname.c \
$(srcdir)/test_cxx_krb5.cpp \
$(srcdir)/test_cxx_k5int.cpp \
@@ -14,10 +18,11 @@ SRCS=\
$(srcdir)/test_cxx_rpc.cpp \
$(srcdir)/test_cxx_kadm5.cpp
-all:: test_getpw
+all:: test_getpw test_chpw_message
-check:: test_getpw test_cxx_krb5 test_cxx_gss test_cxx_rpc test_cxx_k5int test_cxx_kadm5
+check:: test_getpw test_chpw_message test_cxx_krb5 test_cxx_gss test_cxx_rpc test_cxx_k5int test_cxx_kadm5
$(RUN_SETUP) $(VALGRIND) ./test_getpw
+ $(RUN_SETUP) $(VALGRIND) ./test_chpw_message
$(RUN_SETUP) $(VALGRIND) ./test_cxx_krb5
$(RUN_SETUP) $(VALGRIND) ./test_cxx_k5int
$(RUN_SETUP) $(VALGRIND) ./test_cxx_gss
@@ -27,6 +32,9 @@ check:: test_getpw test_cxx_krb5 test_cxx_gss test_cxx_rpc test_cxx_k5int test_c
test_getpw: $(OUTPRE)test_getpw.$(OBJEXT) $(SUPPORT_DEPLIB)
$(CC_LINK) $(ALL_CFLAGS) -o test_getpw $(OUTPRE)test_getpw.$(OBJEXT) $(SUPPORT_LIB)
+test_chpw_message: $(OUTPRE)test_chpw_message.$(OBJEXT) $(SUPPORT_DEPLIB)
+ $(CC_LINK) $(ALL_CFLAGS) -o test_chpw_message $(OUTPRE)test_chpw_message.$(OBJEXT) $(KRB5_BASE_LIBS) $(LIBS)
+
test_getsockname: $(OUTPRE)test_getsockname.$(OBJEXT)
$(CC_LINK) $(ALL_CFLAGS) -o test_getsockname $(OUTPRE)test_getsockname.$(OBJEXT) $(LIBS)
@@ -49,5 +57,5 @@ test_cxx_kadm5.$(OBJEXT): test_cxx_kadm5.cpp
install::
clean::
- $(RM) test_getpw test_cxx_krb5 test_cxx_gss test_cxx_k5int test_cxx_rpc test_cxx_kadm5 *.o
+ $(RM) test_getpw test_chpw_message test_cxx_krb5 test_cxx_gss test_cxx_k5int test_cxx_rpc test_cxx_kadm5 *.o
diff --git a/src/tests/misc/test_chpw_message.c b/src/tests/misc/test_chpw_message.c
new file mode 100644
index 0000000000..bf3169a250
--- /dev/null
+++ b/src/tests/misc/test_chpw_message.c
@@ -0,0 +1,174 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* tests/misc/test_getpw.c */
+/*
+ * Copyright (C) 2012 by the Red Hat Inc.
+ * 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.
+ */
+
+#include "autoconf.h"
+#include "krb5.h"
+
+#include <sys/types.h>
+#include <assert.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static krb5_data result_utf8 = {
+ 0, 23, "This is a valid string.",
+};
+
+static krb5_data result_invalid_utf8 = {
+ 0, 19, "\0This is not valid.",
+};
+
+static krb5_data result_ad_complex = {
+ 0, 30,
+ "\0\0" /* zero bytes */
+ "\0\0\0\0" /* min length */
+ "\0\0\0\0" /* history */
+ "\0\0\0\1" /* properties, complex */
+ "\0\0\0\0\0\0\0\0" /* expire */
+ "\0\0\0\0\0\0\0\0" /* min age */
+};
+
+static krb5_data result_ad_length = {
+ 0, 30,
+ "\0\0" /* zero bytes */
+ "\0\0\0\x0d" /* min length, 13 charaters */
+ "\0\0\0\0" /* history */
+ "\0\0\0\0" /* properties */
+ "\0\0\0\0\0\0\0\0" /* expire */
+ "\0\0\0\0\0\0\0\0" /* min age */
+};
+
+static krb5_data result_ad_history = {
+ 0, 30,
+ "\0\0" /* zero bytes */
+ "\0\0\0\0" /* min length */
+ "\0\0\0\x09" /* history, 9 passwords */
+ "\0\0\0\0" /* properties */
+ "\0\0\0\0\0\0\0\0" /* expire */
+ "\0\0\0\0\0\0\0\0" /* min age */
+};
+
+static krb5_data result_ad_age = {
+ 0, 30,
+ "\0\0" /* zero bytes */
+ "\0\0\0\0" /* min length */
+ "\0\0\0\0" /* history, 9 passwords */
+ "\0\0\0\0" /* properties */
+ "\0\0\0\0\0\0\0\0" /* expire */
+ "\0\0\x01\x92\x54\xd3\x80\0" /* min age, 2 days */
+};
+
+static krb5_data result_ad_all = {
+ 0, 30,
+ "\0\0" /* zero bytes */
+ "\0\0\0\x05" /* min length, 5 characters */
+ "\0\0\0\x0D" /* history, 13 passwords */
+ "\0\0\0\x01" /* properties, complex */
+ "\0\0\0\0\0\0\0\0" /* expire */
+ "\0\0\0\xc9\x2a\x69\xc0\0" /* min age, 1 day */
+};
+
+static void
+check(krb5_error_code code)
+{
+ if (code != 0) {
+ com_err("t_vfy_increds", code, NULL);
+ abort();
+ }
+}
+
+static void
+check_msg(const char *real, const char *expected)
+{
+ if (strstr(real, expected) == NULL) {
+ fprintf(stderr, "Expected to see: %s\n", expected);
+ abort();
+ }
+}
+
+int
+main(void)
+{
+ krb5_context context;
+ char *msg;
+
+ setlocale(LC_MESSAGES, "C");
+
+ check(krb5_init_context(&context));
+
+ /* Valid utf-8 data in the result should be returned as is */
+ check(krb5_chpw_message(context, &result_utf8, &msg));
+ printf(" UTF8 valid: %s\n", msg);
+ check_msg(msg, "This is a valid string.");
+ free(msg);
+
+ /* Invalid data should have a generic message. */
+ check(krb5_chpw_message(context, &result_invalid_utf8, &msg));
+ printf(" UTF8 invalid: %s\n", msg);
+ check_msg(msg, "contact your administrator");
+ free(msg);
+
+ /* AD data with complex data requirement */
+ check(krb5_chpw_message(context, &result_ad_complex, &msg));
+ printf(" AD complex: %s\n", msg);
+ check_msg(msg, "The password must include numbers or symbols.");
+ check_msg(msg, "Don't include any part of your name in the password.");
+ free(msg);
+
+ /* AD data with min password length */
+ check(krb5_chpw_message(context, &result_ad_length, &msg));
+ printf(" AD length: %s\n", msg);
+ check_msg(msg, "The password must contain at least 13 characters.");
+ free(msg);
+
+ /* AD data with history requirements */
+ check(krb5_chpw_message(context, &result_ad_history, &msg));
+ printf(" AD history: %s\n", msg);
+ check_msg(msg, "The password must be different from the previous 9 "
+ "passwords.");
+ free(msg);
+
+ /* AD data with minimum age */
+ check(krb5_chpw_message(context, &result_ad_age, &msg));
+ printf(" AD min age: %s\n", msg);
+ check_msg(msg, "The password can only be changed every 2 days.");
+ free(msg);
+
+ /* AD data with all */
+ check(krb5_chpw_message(context, &result_ad_all, &msg));
+ printf(" AD all: %s\n", msg);
+ check_msg(msg, "The password can only be changed once a day.");
+ check_msg(msg, "The password must be different from the previous 13 "
+ "passwords.");
+ check_msg(msg, "The password must contain at least 5 characters.");
+ check_msg(msg, "The password must include numbers or symbols.");
+ check_msg(msg, "Don't include any part of your name in the password.");
+ free(msg);
+
+ krb5_free_context(context);
+ exit(0);
+}
diff --git a/src/util/collected-client-lib/libcollected.exports b/src/util/collected-client-lib/libcollected.exports
index fb91133fbb..6eb668dc5a 100644
--- a/src/util/collected-client-lib/libcollected.exports
+++ b/src/util/collected-client-lib/libcollected.exports
@@ -147,6 +147,7 @@ krb5_sname_to_principal
krb5_change_password
krb5_set_password
krb5_set_password_using_ccache
+krb5_chpw_message
krb5_get_profile
krb5_mk_safe
krb5_mk_priv