1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 ** set password functions added by Paul W. Nelson, Thursby Software Systems, Inc.
4 */
5
6 #include "k5-int.h"
7 #include "k5-unicode.h"
8 #include "int-proto.h"
9 #include "auth_con.h"
10
11
12 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)13 krb5int_mk_chpw_req(krb5_context context,
14 krb5_auth_context auth_context,
15 krb5_data *ap_req,
16 const char *passwd,
17 krb5_data *packet)
18 {
19 krb5_error_code ret = 0;
20 krb5_data clearpw;
21 krb5_data cipherpw;
22 krb5_replay_data replay;
23 char *ptr;
24
25 cipherpw.data = NULL;
26
27 if ((ret = krb5_auth_con_setflags(context, auth_context,
28 KRB5_AUTH_CONTEXT_DO_SEQUENCE)))
29 goto cleanup;
30
31 clearpw = string2data((char *)passwd);
32
33 if ((ret = krb5_mk_priv(context, auth_context,
34 &clearpw, &cipherpw, &replay)))
35 goto cleanup;
36
37 packet->length = 6 + ap_req->length + cipherpw.length;
38 packet->data = (char *) malloc(packet->length);
39 if (packet->data == NULL) {
40 ret = ENOMEM;
41 goto cleanup;
42 }
43 ptr = packet->data;
44
45 /* length */
46
47 store_16_be(packet->length, ptr);
48 ptr += 2;
49
50 /* version == 0x0001 big-endian */
51
52 *ptr++ = 0;
53 *ptr++ = 1;
54
55 /* ap_req length, big-endian */
56
57 store_16_be(ap_req->length, ptr);
58 ptr += 2;
59
60 /* ap-req data */
61
62 memcpy(ptr, ap_req->data, ap_req->length);
63 ptr += ap_req->length;
64
65 /* krb-priv of password */
66
67 memcpy(ptr, cipherpw.data, cipherpw.length);
68
69 cleanup:
70 if (cipherpw.data != NULL) /* allocated by krb5_mk_priv */
71 free(cipherpw.data);
72
73 return(ret);
74 }
75
76 /* Decode error_packet as a KRB-ERROR message and retrieve its e-data into
77 * *edata_out. */
78 static krb5_error_code
get_error_edata(krb5_context context,const krb5_data * error_packet,krb5_data ** edata_out)79 get_error_edata(krb5_context context, const krb5_data *error_packet,
80 krb5_data **edata_out)
81 {
82 krb5_error_code ret;
83 krb5_error *krberror = NULL;
84
85 *edata_out = NULL;
86
87 ret = krb5_rd_error(context, error_packet, &krberror);
88 if (ret)
89 return ret;
90
91 if (krberror->e_data.data == NULL) {
92 /* Return a krb5 error code based on the error number. */
93 ret = ERROR_TABLE_BASE_krb5 + (krb5_error_code)krberror->error;
94 goto cleanup;
95 }
96
97 ret = krb5_copy_data(context, &krberror->e_data, edata_out);
98
99 cleanup:
100 krb5_free_error(context, krberror);
101 return ret;
102 }
103
104 /* Decode a reply to produce the clear-text output. */
105 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)106 get_clear_result(krb5_context context, krb5_auth_context auth_context,
107 const krb5_data *packet, krb5_data **clear_out,
108 krb5_boolean *is_error_out)
109 {
110 krb5_error_code ret;
111 char *ptr, *end = packet->data + packet->length;
112 unsigned int plen, vno, aplen;
113 krb5_data ap_rep, cipher, error;
114 krb5_ap_rep_enc_part *ap_rep_enc;
115 krb5_replay_data replay;
116 krb5_key send_subkey = NULL;
117 krb5_data clear = empty_data();
118
119 *clear_out = NULL;
120 *is_error_out = FALSE;
121
122 /* Check for an unframed KRB-ERROR (expected for RFC 3244 requests; also
123 * received from MS AD for version 1 requests). */
124 if (krb5_is_krb_error(packet)) {
125 *is_error_out = TRUE;
126 return get_error_edata(context, packet, clear_out);
127 }
128
129 if (packet->length < 6)
130 return KRB5KRB_AP_ERR_MODIFIED;
131
132 /* Decode and verify the length. */
133 ptr = packet->data;
134 plen = (*ptr++ & 0xff);
135 plen = (plen << 8) | (*ptr++ & 0xff);
136 if (plen != packet->length)
137 return KRB5KRB_AP_ERR_MODIFIED;
138
139 /* Decode and verify the version number. */
140 vno = (*ptr++ & 0xff);
141 vno = (vno << 8) | (*ptr++ & 0xff);
142 if (vno != 1 && vno != 0xff80)
143 return KRB5KDC_ERR_BAD_PVNO;
144
145 /* Decode and check the AP-REP length. */
146 aplen = (*ptr++ & 0xff);
147 aplen = (aplen << 8) | (*ptr++ & 0xff);
148 if (aplen > end - ptr)
149 return KRB5KRB_AP_ERR_MODIFIED;
150
151 /* A zero-length AP-REQ indicates a framed KRB-ERROR response. (Expected
152 * for protocol version 1; specified but unusual for RFC 3244 requests.) */
153 if (aplen == 0) {
154 *is_error_out = TRUE;
155 error = make_data(ptr, end - ptr);
156 return get_error_edata(context, &error, clear_out);
157 }
158
159 /* We have an AP-REP. Save send_subkey to later smash recv_subkey. */
160 ret = krb5_auth_con_getsendsubkey_k(context, auth_context, &send_subkey);
161 if (ret)
162 return ret;
163
164 /* Verify the AP-REP. */
165 ap_rep = make_data(ptr, aplen);
166 ptr += ap_rep.length;
167 ret = krb5_rd_rep(context, auth_context, &ap_rep, &ap_rep_enc);
168 if (ret)
169 goto cleanup;
170 krb5_free_ap_rep_enc_part(context, ap_rep_enc);
171
172 /* Smash recv_subkey to be send_subkey, per spec. */
173 ret = krb5_auth_con_setrecvsubkey_k(context, auth_context, send_subkey);
174 if (ret)
175 goto cleanup;
176
177 /* Extract and decrypt the result. */
178 cipher = make_data(ptr, end - ptr);
179 ret = krb5_rd_priv(context, auth_context, &cipher, &clear, &replay);
180 if (ret)
181 goto cleanup;
182
183 ret = krb5_copy_data(context, &clear, clear_out);
184 if (ret)
185 goto cleanup;
186 *is_error_out = FALSE;
187
188 cleanup:
189 krb5_k_free_key(context, send_subkey);
190 krb5_free_data_contents(context, &clear);
191 return ret;
192 }
193
194 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)195 krb5int_rd_chpw_rep(krb5_context context, krb5_auth_context auth_context,
196 krb5_data *packet, int *result_code_out,
197 krb5_data *result_data_out)
198 {
199 krb5_error_code ret;
200 krb5_data result_data, *clear = NULL;
201 krb5_boolean is_error;
202 char *ptr;
203 int result_code;
204
205 *result_code_out = 0;
206 *result_data_out = empty_data();
207
208 ret = get_clear_result(context, auth_context, packet, &clear, &is_error);
209 if (ret)
210 return ret;
211
212 if (clear->length < 2) {
213 ret = KRB5KRB_AP_ERR_MODIFIED;
214 goto cleanup;
215 }
216
217 /* Decode and check the result code. */
218 ptr = clear->data;
219 result_code = (*ptr++ & 0xff);
220 result_code = (result_code << 8) | (*ptr++ & 0xff);
221 if (result_code < KRB5_KPASSWD_SUCCESS ||
222 result_code > KRB5_KPASSWD_INITIAL_FLAG_NEEDED) {
223 ret = KRB5KRB_AP_ERR_MODIFIED;
224 goto cleanup;
225 }
226
227 /* Successful replies must not come from errors. */
228 if (is_error && result_code == KRB5_KPASSWD_SUCCESS) {
229 ret = KRB5KRB_AP_ERR_MODIFIED;
230 goto cleanup;
231 }
232
233 result_data = make_data(ptr, clear->data + clear->length - ptr);
234 ret = krb5int_copy_data_contents(context, &result_data, result_data_out);
235 if (ret)
236 goto cleanup;
237 *result_code_out = result_code;
238
239 cleanup:
240 krb5_free_data(context, clear);
241 return ret;
242 }
243
244 krb5_error_code KRB5_CALLCONV
krb5_chpw_result_code_string(krb5_context context,int result_code,char ** code_string)245 krb5_chpw_result_code_string(krb5_context context, int result_code,
246 char **code_string)
247 {
248 switch (result_code) {
249 case KRB5_KPASSWD_MALFORMED:
250 *code_string = _("Malformed request error");
251 break;
252 case KRB5_KPASSWD_HARDERROR:
253 *code_string = _("Server error");
254 break;
255 case KRB5_KPASSWD_AUTHERROR:
256 *code_string = _("Authentication error");
257 break;
258 case KRB5_KPASSWD_SOFTERROR:
259 *code_string = _("Password change rejected");
260 break;
261 case KRB5_KPASSWD_ACCESSDENIED:
262 *code_string = _("Access denied");
263 break;
264 case KRB5_KPASSWD_BAD_VERSION:
265 *code_string = _("Wrong protocol version");
266 break;
267 case KRB5_KPASSWD_INITIAL_FLAG_NEEDED:
268 *code_string = _("Initial password required");
269 break;
270 case 0:
271 *code_string = _("Success");
272 break;
273 default:
274 *code_string = _("Password change failed");
275 break;
276 }
277
278 return 0;
279 }
280
281 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)282 krb5int_mk_setpw_req(krb5_context context,
283 krb5_auth_context auth_context,
284 krb5_data *ap_req,
285 krb5_principal targprinc,
286 const char *passwd,
287 krb5_data *packet)
288 {
289 krb5_error_code ret;
290 krb5_data cipherpw;
291 krb5_data *encoded_setpw;
292 struct krb5_setpw_req req;
293
294 char *ptr;
295
296 cipherpw.data = NULL;
297 cipherpw.length = 0;
298
299 if ((ret = krb5_auth_con_setflags(context, auth_context,
300 KRB5_AUTH_CONTEXT_DO_SEQUENCE)))
301 return(ret);
302
303 req.target = targprinc;
304 req.password = string2data((char *)passwd);
305 ret = encode_krb5_setpw_req(&req, &encoded_setpw);
306 if (ret) {
307 return ret;
308 }
309
310 if ((ret = krb5_mk_priv(context, auth_context, encoded_setpw, &cipherpw, NULL)) != 0) {
311 krb5_free_data(context, encoded_setpw);
312 return(ret);
313 }
314 krb5_free_data(context, encoded_setpw);
315
316
317 packet->length = 6 + ap_req->length + cipherpw.length;
318 packet->data = (char *) malloc(packet->length);
319 if (packet->data == NULL) {
320 ret = ENOMEM;
321 goto cleanup;
322 }
323 ptr = packet->data;
324 /*
325 ** build the packet -
326 */
327 /* put in the length */
328 store_16_be(packet->length, ptr);
329 ptr += 2;
330 /* put in the version */
331 *ptr++ = (char)0xff;
332 *ptr++ = (char)0x80;
333 /* the ap_req length is big endian */
334 store_16_be(ap_req->length, ptr);
335 ptr += 2;
336 /* put in the request data */
337 memcpy(ptr, ap_req->data, ap_req->length);
338 ptr += ap_req->length;
339 /*
340 ** put in the "private" password data -
341 */
342 memcpy(ptr, cipherpw.data, cipherpw.length);
343 ret = 0;
344 cleanup:
345 if (cipherpw.data)
346 krb5_free_data_contents(context, &cipherpw);
347 if ((ret != 0) && packet->data) {
348 free(packet->data);
349 packet->data = NULL;
350 }
351 return ret;
352 }
353
354 /*
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.
360 */
361 struct ad_policy_info {
362 uint16_t zero_bytes;
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 */
368 };
369
370 #define AD_POLICY_INFO_LENGTH 30
371 #define AD_POLICY_TIME_TO_DAYS (86400ULL * 10000000ULL)
372
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
379
380 /* If buf already contains one or more sentences, add spaces to separate them
381 * from the next sentence. */
382 static void
add_spaces(struct k5buf * buf)383 add_spaces(struct k5buf *buf)
384 {
385 if (buf->len > 0)
386 k5_buf_add(buf, " ");
387 }
388
389 static krb5_error_code
decode_ad_policy_info(const krb5_data * data,char ** msg_out)390 decode_ad_policy_info(const krb5_data *data, char **msg_out)
391 {
392 struct ad_policy_info policy;
393 uint64_t password_days;
394 const char *p;
395 struct k5buf buf;
396
397 *msg_out = NULL;
398 if (data->length != AD_POLICY_INFO_LENGTH)
399 return 0;
400
401 p = data->data;
402 policy.zero_bytes = load_16_be(p);
403 p += 2;
404
405 /* first two bytes are zeros */
406 if (policy.zero_bytes != 0)
407 return 0;
408
409 /* Read in the rest of structure */
410 policy.min_length_password = load_32_be(p);
411 p += 4;
412 policy.password_history = load_32_be(p);
413 p += 4;
414 policy.password_properties = load_32_be(p);
415 p += 4;
416 policy.expire = load_64_be(p);
417 p += 8;
418 policy.min_passwordage = load_64_be(p);
419 p += 8;
420
421 /* Check that we processed exactly the expected number of bytes. */
422 assert(p == data->data + AD_POLICY_INFO_LENGTH);
423
424 k5_buf_init_dynamic(&buf);
425
426 /*
427 * Update src/tests/misc/test_chpw_message.c if changing these strings!
428 */
429
430 if (policy.password_properties & AD_POLICY_COMPLEX) {
431 k5_buf_add(&buf, _("The password must include numbers or symbols. "
432 "Don't include any part of your name in the "
433 "password."));
434 }
435 if (policy.min_length_password > 0) {
436 add_spaces(&buf);
437 k5_buf_add_fmt(&buf, ngettext("The password must contain at least %d "
438 "character.",
439 "The password must contain at least %d "
440 "characters.",
441 policy.min_length_password),
442 policy.min_length_password);
443 }
444 if (policy.password_history) {
445 add_spaces(&buf);
446 k5_buf_add_fmt(&buf, ngettext("The password must be different from "
447 "the previous password.",
448 "The password must be different from "
449 "the previous %d passwords.",
450 policy.password_history),
451 policy.password_history);
452 }
453 if (policy.min_passwordage) {
454 password_days = policy.min_passwordage / AD_POLICY_TIME_TO_DAYS;
455 if (password_days == 0)
456 password_days = 1;
457 add_spaces(&buf);
458 k5_buf_add_fmt(&buf, ngettext("The password can only be changed once "
459 "a day.",
460 "The password can only be changed every "
461 "%d days.", (int)password_days),
462 (int)password_days);
463 }
464
465 if (k5_buf_status(&buf) != 0)
466 return ENOMEM;
467
468 if (buf.len > 0)
469 *msg_out = buf.data;
470 else
471 k5_buf_free(&buf);
472 return 0;
473 }
474
475 krb5_error_code KRB5_CALLCONV
krb5_chpw_message(krb5_context context,const krb5_data * server_string,char ** message_out)476 krb5_chpw_message(krb5_context context, const krb5_data *server_string,
477 char **message_out)
478 {
479 krb5_error_code ret;
480 krb5_data *string;
481 char *msg;
482
483 *message_out = NULL;
484
485 /* If server_string contains an AD password policy, construct a message
486 * based on that. */
487 ret = decode_ad_policy_info(server_string, &msg);
488 if (ret == 0 && msg != NULL) {
489 *message_out = msg;
490 return 0;
491 }
492
493 /* If server_string contains a valid UTF-8 string, return that. */
494 if (server_string->length > 0 &&
495 memchr(server_string->data, 0, server_string->length) == NULL &&
496 krb5int_utf8_normalize(server_string, &string,
497 KRB5_UTF8_APPROX) == 0) {
498 *message_out = string->data; /* already null terminated */
499 free(string);
500 return 0;
501 }
502
503 /* server_string appears invalid, so try to be helpful. */
504 msg = strdup(_("Try a more complex password, or contact your "
505 "administrator."));
506 if (msg == NULL)
507 return ENOMEM;
508
509 *message_out = msg;
510 return 0;
511 }
512