summaryrefslogtreecommitdiffstats
path: root/src/lib
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 /src/lib
parentc250f6743a623ab5086c56324060ebf99c740379 (diff)
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
Diffstat (limited to 'src/lib')
-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
4 files changed, 179 insertions, 6 deletions
diff --git a/src/lib/krb5/krb/chpw.c b/src/lib/krb5/krb/chpw.c
index 7e43dcf1a..beb77cb98 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 68d28fe9d..b25eb6da0 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 cb73a1471..6319c72a8 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 d5922d2d1..bfc211535 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