#include "k5-int.h"
#include "k5-unicode.h"
#include "int-proto.h"
#include "auth_con.h"
krb5_error_code
krb5int_mk_chpw_req(krb5_context context,
krb5_auth_context auth_context,
krb5_data *ap_req,
const char *passwd,
krb5_data *packet)
{
krb5_error_code ret = 0;
krb5_data clearpw;
krb5_data cipherpw;
krb5_replay_data replay;
char *ptr;
cipherpw.data = NULL;
if ((ret = krb5_auth_con_setflags(context, auth_context,
KRB5_AUTH_CONTEXT_DO_SEQUENCE)))
goto cleanup;
clearpw = string2data((char *)passwd);
if ((ret = krb5_mk_priv(context, auth_context,
&clearpw, &cipherpw, &replay)))
goto cleanup;
packet->length = 6 + ap_req->length + cipherpw.length;
packet->data = (char *) malloc(packet->length);
if (packet->data == NULL) {
ret = ENOMEM;
goto cleanup;
}
ptr = packet->data;
store_16_be(packet->length, ptr);
ptr += 2;
*ptr++ = 0;
*ptr++ = 1;
store_16_be(ap_req->length, ptr);
ptr += 2;
memcpy(ptr, ap_req->data, ap_req->length);
ptr += ap_req->length;
memcpy(ptr, cipherpw.data, cipherpw.length);
cleanup:
if (cipherpw.data != NULL)
free(cipherpw.data);
return(ret);
}
static krb5_error_code
get_error_edata(krb5_context context, const krb5_data *error_packet,
krb5_data **edata_out)
{
krb5_error_code ret;
krb5_error *krberror = NULL;
*edata_out = NULL;
ret = krb5_rd_error(context, error_packet, &krberror);
if (ret)
return ret;
if (krberror->e_data.data == NULL) {
ret = ERROR_TABLE_BASE_krb5 + (krb5_error_code)krberror->error;
goto cleanup;
}
ret = krb5_copy_data(context, &krberror->e_data, edata_out);
cleanup:
krb5_free_error(context, krberror);
return ret;
}
static krb5_error_code
get_clear_result(krb5_context context, krb5_auth_context auth_context,
const krb5_data *packet, krb5_data **clear_out,
krb5_boolean *is_error_out)
{
krb5_error_code ret;
char *ptr, *end = packet->data + packet->length;
unsigned int plen, vno, aplen;
krb5_data ap_rep, cipher, error;
krb5_ap_rep_enc_part *ap_rep_enc;
krb5_replay_data replay;
krb5_key send_subkey = NULL;
krb5_data clear = empty_data();
*clear_out = NULL;
*is_error_out = FALSE;
if (krb5_is_krb_error(packet)) {
*is_error_out = TRUE;
return get_error_edata(context, packet, clear_out);
}
if (packet->length < 6)
return KRB5KRB_AP_ERR_MODIFIED;
ptr = packet->data;
plen = (*ptr++ & 0xff);
plen = (plen << 8) | (*ptr++ & 0xff);
if (plen != packet->length)
return KRB5KRB_AP_ERR_MODIFIED;
vno = (*ptr++ & 0xff);
vno = (vno << 8) | (*ptr++ & 0xff);
if (vno != 1 && vno != 0xff80)
return KRB5KDC_ERR_BAD_PVNO;
aplen = (*ptr++ & 0xff);
aplen = (aplen << 8) | (*ptr++ & 0xff);
if (aplen > end - ptr)
return KRB5KRB_AP_ERR_MODIFIED;
if (aplen == 0) {
*is_error_out = TRUE;
error = make_data(ptr, end - ptr);
return get_error_edata(context, &error, clear_out);
}
ret = krb5_auth_con_getsendsubkey_k(context, auth_context, &send_subkey);
if (ret)
return ret;
ap_rep = make_data(ptr, aplen);
ptr += ap_rep.length;
ret = krb5_rd_rep(context, auth_context, &ap_rep, &ap_rep_enc);
if (ret)
goto cleanup;
krb5_free_ap_rep_enc_part(context, ap_rep_enc);
ret = krb5_auth_con_setrecvsubkey_k(context, auth_context, send_subkey);
if (ret)
goto cleanup;
cipher = make_data(ptr, end - ptr);
ret = krb5_rd_priv(context, auth_context, &cipher, &clear, &replay);
if (ret)
goto cleanup;
ret = krb5_copy_data(context, &clear, clear_out);
if (ret)
goto cleanup;
*is_error_out = FALSE;
cleanup:
krb5_k_free_key(context, send_subkey);
krb5_free_data_contents(context, &clear);
return ret;
}
krb5_error_code
krb5int_rd_chpw_rep(krb5_context context, krb5_auth_context auth_context,
krb5_data *packet, int *result_code_out,
krb5_data *result_data_out)
{
krb5_error_code ret;
krb5_data result_data, *clear = NULL;
krb5_boolean is_error;
char *ptr;
int result_code;
*result_code_out = 0;
*result_data_out = empty_data();
ret = get_clear_result(context, auth_context, packet, &clear, &is_error);
if (ret)
return ret;
if (clear->length < 2) {
ret = KRB5KRB_AP_ERR_MODIFIED;
goto cleanup;
}
ptr = clear->data;
result_code = (*ptr++ & 0xff);
result_code = (result_code << 8) | (*ptr++ & 0xff);
if (result_code < KRB5_KPASSWD_SUCCESS ||
result_code > KRB5_KPASSWD_INITIAL_FLAG_NEEDED) {
ret = KRB5KRB_AP_ERR_MODIFIED;
goto cleanup;
}
if (is_error && result_code == KRB5_KPASSWD_SUCCESS) {
ret = KRB5KRB_AP_ERR_MODIFIED;
goto cleanup;
}
result_data = make_data(ptr, clear->data + clear->length - ptr);
ret = krb5int_copy_data_contents(context, &result_data, result_data_out);
if (ret)
goto cleanup;
*result_code_out = result_code;
cleanup:
krb5_free_data(context, clear);
return ret;
}
krb5_error_code KRB5_CALLCONV
krb5_chpw_result_code_string(krb5_context context, int result_code,
char **code_string)
{
switch (result_code) {
case KRB5_KPASSWD_MALFORMED:
*code_string = _("Malformed request error");
break;
case KRB5_KPASSWD_HARDERROR:
*code_string = _("Server error");
break;
case KRB5_KPASSWD_AUTHERROR:
*code_string = _("Authentication error");
break;
case KRB5_KPASSWD_SOFTERROR:
*code_string = _("Password change rejected");
break;
case KRB5_KPASSWD_ACCESSDENIED:
*code_string = _("Access denied");
break;
case KRB5_KPASSWD_BAD_VERSION:
*code_string = _("Wrong protocol version");
break;
case KRB5_KPASSWD_INITIAL_FLAG_NEEDED:
*code_string = _("Initial password required");
break;
case 0:
*code_string = _("Success");
break;
default:
*code_string = _("Password change failed");
break;
}
return 0;
}
krb5_error_code
krb5int_mk_setpw_req(krb5_context context,
krb5_auth_context auth_context,
krb5_data *ap_req,
krb5_principal targprinc,
const char *passwd,
krb5_data *packet)
{
krb5_error_code ret;
krb5_data cipherpw;
krb5_data *encoded_setpw;
struct krb5_setpw_req req;
char *ptr;
cipherpw.data = NULL;
cipherpw.length = 0;
if ((ret = krb5_auth_con_setflags(context, auth_context,
KRB5_AUTH_CONTEXT_DO_SEQUENCE)))
return(ret);
req.target = targprinc;
req.password = string2data((char *)passwd);
ret = encode_krb5_setpw_req(&req, &encoded_setpw);
if (ret) {
return ret;
}
if ((ret = krb5_mk_priv(context, auth_context, encoded_setpw, &cipherpw, NULL)) != 0) {
krb5_free_data(context, encoded_setpw);
return(ret);
}
krb5_free_data(context, encoded_setpw);
packet->length = 6 + ap_req->length + cipherpw.length;
packet->data = (char *) malloc(packet->length);
if (packet->data == NULL) {
ret = ENOMEM;
goto cleanup;
}
ptr = packet->data;
store_16_be(packet->length, ptr);
ptr += 2;
*ptr++ = (char)0xff;
*ptr++ = (char)0x80;
store_16_be(ap_req->length, ptr);
ptr += 2;
memcpy(ptr, ap_req->data, ap_req->length);
ptr += ap_req->length;
memcpy(ptr, cipherpw.data, cipherpw.length);
ret = 0;
cleanup:
if (cipherpw.data)
krb5_free_data_contents(context, &cipherpw);
if ((ret != 0) && packet->data) {
free(packet->data);
packet->data = NULL;
}
return ret;
}
struct ad_policy_info {
uint16_t zero_bytes;
uint32_t min_length_password;
uint32_t password_history;
uint32_t password_properties;
uint64_t expire;
uint64_t min_passwordage;
};
#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
static void
add_spaces(struct k5buf *buf)
{
if (buf->len > 0)
k5_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;
struct k5buf buf;
char *msg;
*msg_out = NULL;
if (data->length != AD_POLICY_INFO_LENGTH)
return 0;
p = data->data;
policy.zero_bytes = load_16_be(p);
p += 2;
if (policy.zero_bytes != 0)
return 0;
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;
assert(p == data->data + AD_POLICY_INFO_LENGTH);
k5_buf_init_dynamic(&buf);
if (policy.password_properties & AD_POLICY_COMPLEX) {
k5_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);
k5_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);
k5_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);
k5_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 = k5_buf_cstring(&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;
char *msg;
*message_out = NULL;
ret = decode_ad_policy_info(server_string, &msg);
if (ret == 0 && msg != NULL) {
*message_out = msg;
return 0;
}
if (server_string->length > 0 &&
memchr(server_string->data, 0, server_string->length) == NULL &&
k5_utf8_validate(server_string)) {
*message_out = k5memdup0(server_string->data, server_string->length,
&ret);
return (*message_out == NULL) ? ENOMEM : 0;
}
msg = strdup(_("Try a more complex password, or contact your "
"administrator."));
if (msg == NULL)
return ENOMEM;
*message_out = msg;
return 0;
}