Skip to content

Commit

Permalink
Add API to interpret changepw result strings
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ghudson committed May 9, 2012
1 parent c47680c commit d1e0c61
Show file tree
Hide file tree
Showing 10 changed files with 399 additions and 14 deletions.
1 change: 1 addition & 0 deletions doc/rst_source/krb_appldev/refs/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions src/clients/kpasswd/kpasswd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
26 changes: 26 additions & 0 deletions src/include/krb5/krb5.hin
Original file line number Diff line number Diff line change
Expand Up @@ -4962,6 +4962,32 @@ krb5_set_password_using_ccache(krb5_context context, krb5_ccache ccache,
int *result_code, krb5_data *result_code_string,
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.
*
Expand Down
166 changes: 166 additions & 0 deletions src/lib/krb5/krb/chpw.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <string.h>

#include "k5-int.h"
#include "k5-unicode.h"
#include "int-proto.h"
#include "auth_con.h"

Expand Down Expand Up @@ -349,3 +350,168 @@ krb5int_mk_setpw_req(krb5_context context,
}
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;
}
15 changes: 9 additions & 6 deletions src/lib/krb5/krb/gic_pwd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions src/lib/krb5/libkrb5.exports
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/lib/krb5_32.def
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 12 additions & 4 deletions src/tests/misc/Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,25 @@ 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 \
$(srcdir)/test_cxx_gss.cpp \
$(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
Expand All @@ -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)

Expand All @@ -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

Loading

0 comments on commit d1e0c61

Please sign in to comment.