1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
3 ** set password functions added by Paul W. Nelson, Thursby Software Systems, Inc.
8 #include "k5-unicode.h"
14 krb5int_mk_chpw_req(krb5_context context,
15 krb5_auth_context auth_context,
20 krb5_error_code ret = 0;
23 krb5_replay_data replay;
28 if ((ret = krb5_auth_con_setflags(context, auth_context,
29 KRB5_AUTH_CONTEXT_DO_SEQUENCE)))
32 clearpw.length = strlen(passwd);
33 clearpw.data = passwd;
35 if ((ret = krb5_mk_priv(context, auth_context,
36 &clearpw, &cipherpw, &replay)))
39 packet->length = 6 + ap_req->length + cipherpw.length;
40 packet->data = (char *) malloc(packet->length);
41 if (packet->data == NULL) {
49 store_16_be(packet->length, ptr);
52 /* version == 0x0001 big-endian */
57 /* ap_req length, big-endian */
59 store_16_be(ap_req->length, ptr);
64 memcpy(ptr, ap_req->data, ap_req->length);
65 ptr += ap_req->length;
67 /* krb-priv of password */
69 memcpy(ptr, cipherpw.data, cipherpw.length);
72 if (cipherpw.data != NULL) /* allocated by krb5_mk_priv */
78 /* Decode error_packet as a KRB-ERROR message and retrieve its e-data into
80 static krb5_error_code
81 get_error_edata(krb5_context context, const krb5_data *error_packet,
82 krb5_data **edata_out)
85 krb5_error *krberror = NULL;
89 ret = krb5_rd_error(context, error_packet, &krberror);
93 if (krberror->e_data.data == NULL) {
94 /* Return a krb5 error code based on the error number. */
95 ret = ERROR_TABLE_BASE_krb5 + (krb5_error_code)krberror->error;
99 ret = krb5_copy_data(context, &krberror->e_data, edata_out);
102 krb5_free_error(context, krberror);
106 /* Decode a reply to produce the clear-text output. */
107 static krb5_error_code
108 get_clear_result(krb5_context context, krb5_auth_context auth_context,
109 const krb5_data *packet, krb5_data **clear_out,
110 krb5_boolean *is_error_out)
113 char *ptr, *end = packet->data + packet->length;
114 unsigned int plen, vno, aplen;
115 krb5_data ap_rep, cipher, error;
116 krb5_ap_rep_enc_part *ap_rep_enc;
117 krb5_replay_data replay;
118 krb5_key send_subkey = NULL;
119 krb5_data clear = empty_data();
122 *is_error_out = FALSE;
124 /* Check for an unframed KRB-ERROR (expected for RFC 3244 requests; also
125 * received from MS AD for version 1 requests). */
126 if (krb5_is_krb_error(packet)) {
127 *is_error_out = TRUE;
128 return get_error_edata(context, packet, clear_out);
131 if (packet->length < 6)
132 return KRB5KRB_AP_ERR_MODIFIED;
134 /* Decode and verify the length. */
136 plen = (*ptr++ & 0xff);
137 plen = (plen << 8) | (*ptr++ & 0xff);
138 if (plen != packet->length)
139 return KRB5KRB_AP_ERR_MODIFIED;
141 /* Decode and verify the version number. */
142 vno = (*ptr++ & 0xff);
143 vno = (vno << 8) | (*ptr++ & 0xff);
144 if (vno != 1 && vno != 0xff80)
145 return KRB5KDC_ERR_BAD_PVNO;
147 /* Decode and check the AP-REP length. */
148 aplen = (*ptr++ & 0xff);
149 aplen = (aplen << 8) | (*ptr++ & 0xff);
150 if (aplen > end - ptr)
151 return KRB5KRB_AP_ERR_MODIFIED;
153 /* A zero-length AP-REQ indicates a framed KRB-ERROR response. (Expected
154 * for protocol version 1; specified but unusual for RFC 3244 requests.) */
156 *is_error_out = TRUE;
157 error = make_data(ptr, end - ptr);
158 return get_error_edata(context, &error, clear_out);
161 /* We have an AP-REP. Save send_subkey to later smash recv_subkey. */
162 ret = krb5_auth_con_getsendsubkey_k(context, auth_context, &send_subkey);
166 /* Verify the AP-REP. */
167 ap_rep = make_data(ptr, aplen);
168 ptr += ap_rep.length;
169 ret = krb5_rd_rep(context, auth_context, &ap_rep, &ap_rep_enc);
172 krb5_free_ap_rep_enc_part(context, ap_rep_enc);
174 /* Smash recv_subkey to be send_subkey, per spec. */
175 ret = krb5_auth_con_setrecvsubkey_k(context, auth_context, send_subkey);
179 /* Extract and decrypt the result. */
180 cipher = make_data(ptr, end - ptr);
181 ret = krb5_rd_priv(context, auth_context, &cipher, &clear, &replay);
185 ret = krb5_copy_data(context, &clear, clear_out);
188 *is_error_out = FALSE;
191 krb5_k_free_key(context, send_subkey);
192 krb5_free_data_contents(context, &clear);
197 krb5int_rd_chpw_rep(krb5_context context, krb5_auth_context auth_context,
198 krb5_data *packet, int *result_code_out,
199 krb5_data *result_data_out)
202 krb5_data result_data, *clear = NULL;
203 krb5_boolean is_error;
207 *result_code_out = 0;
208 *result_data_out = empty_data();
210 ret = get_clear_result(context, auth_context, packet, &clear, &is_error);
214 if (clear->length < 2) {
215 ret = KRB5KRB_AP_ERR_MODIFIED;
219 /* Decode and check the result code. */
221 result_code = (*ptr++ & 0xff);
222 result_code = (result_code << 8) | (*ptr++ & 0xff);
223 if (result_code < KRB5_KPASSWD_SUCCESS ||
224 result_code > KRB5_KPASSWD_INITIAL_FLAG_NEEDED) {
225 ret = KRB5KRB_AP_ERR_MODIFIED;
229 /* Successful replies must not come from errors. */
230 if (is_error && result_code == KRB5_KPASSWD_SUCCESS) {
231 ret = KRB5KRB_AP_ERR_MODIFIED;
235 result_data = make_data(ptr, clear->data + clear->length - ptr);
236 ret = krb5int_copy_data_contents(context, &result_data, result_data_out);
239 *result_code_out = result_code;
242 krb5_free_data(context, clear);
246 krb5_error_code KRB5_CALLCONV
247 krb5_chpw_result_code_string(krb5_context context, int result_code,
250 switch (result_code) {
251 case KRB5_KPASSWD_MALFORMED:
252 *code_string = _("Malformed request error");
254 case KRB5_KPASSWD_HARDERROR:
255 *code_string = _("Server error");
257 case KRB5_KPASSWD_AUTHERROR:
258 *code_string = _("Authentication error");
260 case KRB5_KPASSWD_SOFTERROR:
261 *code_string = _("Password change rejected");
263 case KRB5_KPASSWD_ACCESSDENIED:
264 *code_string = _("Access denied");
266 case KRB5_KPASSWD_BAD_VERSION:
267 *code_string = _("Wrong protocol version");
269 case KRB5_KPASSWD_INITIAL_FLAG_NEEDED:
270 *code_string = _("Initial password required");
273 *code_string = _("Password change failed");
281 krb5int_mk_setpw_req(krb5_context context,
282 krb5_auth_context auth_context,
284 krb5_principal targprinc,
290 krb5_data *encoded_setpw;
291 struct krb5_setpw_req req;
295 cipherpw.data = NULL;
298 if ((ret = krb5_auth_con_setflags(context, auth_context,
299 KRB5_AUTH_CONTEXT_DO_SEQUENCE)))
302 req.target = targprinc;
303 req.password.data = passwd;
304 req.password.length = strlen(passwd);
305 ret = encode_krb5_setpw_req(&req, &encoded_setpw);
310 if ((ret = krb5_mk_priv(context, auth_context, encoded_setpw, &cipherpw, NULL)) != 0) {
311 krb5_free_data(context, encoded_setpw);
314 krb5_free_data(context, encoded_setpw);
317 packet->length = 6 + ap_req->length + cipherpw.length;
318 packet->data = (char *) malloc(packet->length);
319 if (packet->data == NULL) {
325 ** build the packet -
327 /* put in the length */
328 store_16_be(packet->length, ptr);
330 /* put in the version */
333 /* the ap_req length is big endian */
334 store_16_be(ap_req->length, ptr);
336 /* put in the request data */
337 memcpy(ptr, ap_req->data, ap_req->length);
338 ptr += ap_req->length;
340 ** put in the "private" password data -
342 memcpy(ptr, cipherpw.data, cipherpw.length);
346 krb5_free_data_contents(context, &cipherpw);
347 if ((ret != 0) && packet->data) {
355 * Active Directory policy information is communicated in the result string
356 * field as a packed 30-byte sequence, starting with two zero bytes (so that
357 * the string appears as zero-length when interpreted as UTF-8). The bytes
358 * correspond to the fields in the following structure, with each field in
359 * big-endian byte order.
361 struct ad_policy_info {
363 uint32_t min_length_password;
364 uint32_t password_history;
365 uint32_t password_properties; /* see defines below */
366 uint64_t expire; /* in seconds * 10,000,000 */
367 uint64_t min_passwordage; /* in seconds * 10,000,000 */
370 #define AD_POLICY_INFO_LENGTH 30
371 #define AD_POLICY_TIME_TO_DAYS (86400ULL * 10000000ULL)
373 #define AD_POLICY_COMPLEX 0x00000001
374 #define AD_POLICY_NO_ANON_CHANGE 0x00000002
375 #define AD_POLICY_NO_CLEAR_CHANGE 0x00000004
376 #define AD_POLICY_LOCKOUT_ADMINS 0x00000008
377 #define AD_POLICY_STORE_CLEARTEXT 0x00000010
378 #define AD_POLICY_REFUSE_CHANGE 0x00000020
380 /* If buf already contains one or more sentences, add spaces to separate them
381 * from the next sentence. */
383 add_spaces(struct k5buf *buf)
385 if (krb5int_buf_len(buf) > 0)
386 krb5int_buf_add(buf, " ");
389 static krb5_error_code
390 decode_ad_policy_info(const krb5_data *data, char **msg_out)
392 struct ad_policy_info policy;
393 uint64_t password_days;
399 if (data->length != AD_POLICY_INFO_LENGTH)
403 policy.zero_bytes = load_16_be(p);
406 /* first two bytes are zeros */
407 if (policy.zero_bytes != 0)
410 /* Read in the rest of structure */
411 policy.min_length_password = load_32_be(p);
413 policy.password_history = load_32_be(p);
415 policy.password_properties = load_32_be(p);
417 policy.expire = load_64_be(p);
419 policy.min_passwordage = load_64_be(p);
422 /* Check that we processed exactly the expected number of bytes. */
423 assert(p == data->data + AD_POLICY_INFO_LENGTH);
425 krb5int_buf_init_dynamic(&buf);
428 * Update src/tests/misc/test_chpw_message.c if changing these strings!
431 if (policy.password_properties & AD_POLICY_COMPLEX) {
432 krb5int_buf_add(&buf,
433 _("The password must include numbers or symbols. "
434 "Don't include any part of your name in the "
437 if (policy.min_length_password > 0) {
439 krb5int_buf_add_fmt(&buf,
440 ngettext("The password must contain at least %d "
442 "The password must contain at least %d "
444 policy.min_length_password),
445 policy.min_length_password);
447 if (policy.password_history) {
449 krb5int_buf_add_fmt(&buf,
450 ngettext("The password must be different from the "
451 "previous password.",
452 "The password must be different from the "
453 "previous %d passwords.",
454 policy.password_history),
455 policy.password_history);
457 if (policy.min_passwordage) {
458 password_days = policy.min_passwordage / AD_POLICY_TIME_TO_DAYS;
459 if (password_days == 0)
462 krb5int_buf_add_fmt(&buf,
463 ngettext("The password can only be changed once a "
465 "The password can only be changed every "
466 "%d days.", (int)password_days),
470 msg = krb5int_buf_data(&buf);
481 krb5_error_code KRB5_CALLCONV
482 krb5_chpw_message(krb5_context context, const krb5_data *server_string,
491 /* If server_string contains an AD password policy, construct a message
493 ret = decode_ad_policy_info(server_string, &msg);
494 if (ret == 0 && msg != NULL) {
499 /* If server_string contains a valid UTF-8 string, return that. */
500 if (server_string->length > 0 &&
501 memchr(server_string->data, 0, server_string->length) == NULL &&
502 krb5int_utf8_normalize(server_string, &string,
503 KRB5_UTF8_APPROX) == 0) {
504 *message_out = string->data; /* already null terminated */
509 /* server_string appears invalid, so try to be helpful. */
510 msg = strdup(_("Try a more complex password, or contact your "