1 /*
2  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 /*
9 ** set password functions added by Paul W. Nelson, Thursby Software Systems, Inc.
10 */
11 #include <string.h>
12 
13 #include "k5-int.h"
14 /* #include "krb5_err.h" gtb */
15 #include "auth_con.h"
16 
17 
18 krb5_error_code
19 krb5int_mk_chpw_req(krb5_context context, krb5_auth_context auth_context, krb5_data *ap_req, char *passwd, krb5_data *packet)
20 {
21     krb5_error_code ret = 0;
22     krb5_data clearpw;
23     krb5_data cipherpw;
24     krb5_replay_data replay;
25     char *ptr;
26 
27     cipherpw.data = NULL;
28 
29     if ((ret = krb5_auth_con_setflags(context, auth_context,
30 				      KRB5_AUTH_CONTEXT_DO_SEQUENCE)))
31 	  goto cleanup;
32 
33     clearpw.length = strlen(passwd);
34     clearpw.data = passwd;
35 
36     if ((ret = krb5_mk_priv(context, auth_context,
37 			    &clearpw, &cipherpw, &replay)))
38       goto cleanup;
39 
40     packet->length = 6 + ap_req->length + cipherpw.length;
41     packet->data = (char *) malloc(packet->length);
42     if (packet->data == NULL)
43 	  {
44 	    ret = ENOMEM;
45 	    goto cleanup;
46 	  }
47     ptr = packet->data;
48 
49     /* length */
50 
51     *ptr++ = (packet->length>>8) & 0xff;
52     *ptr++ = packet->length & 0xff;
53 
54     /* version == 0x0001 big-endian */
55 
56     *ptr++ = 0;
57     *ptr++ = 1;
58 
59     /* ap_req length, big-endian */
60 
61     *ptr++ = (ap_req->length>>8) & 0xff;
62     *ptr++ = ap_req->length & 0xff;
63 
64     /* ap-req data */
65 
66     memcpy(ptr, ap_req->data, ap_req->length);
67     ptr += ap_req->length;
68 
69     /* krb-priv of password */
70 
71     memcpy(ptr, cipherpw.data, cipherpw.length);
72 
73 cleanup:
74     if(cipherpw.data != NULL)  /* allocated by krb5_mk_priv */
75       free(cipherpw.data);
76 
77     return(ret);
78 }
79 
80 krb5_error_code
81 krb5int_rd_chpw_rep(krb5_context context, krb5_auth_context auth_context, krb5_data *packet, int *result_code, krb5_data *result_data)
82 {
83     char *ptr;
84     int plen, vno;
85     krb5_data ap_rep;
86     krb5_ap_rep_enc_part *ap_rep_enc;
87     krb5_error_code ret;
88     krb5_data cipherresult;
89     krb5_data clearresult;
90     krb5_error *krberror;
91     krb5_replay_data replay;
92     krb5_keyblock *tmp;
93 
94     if (packet->length < 4)
95 	/* either this, or the server is printing bad messages,
96 	   or the caller passed in garbage */
97 	return(KRB5KRB_AP_ERR_MODIFIED);
98 
99     ptr = packet->data;
100 
101     /* verify length */
102 
103     plen = (*ptr++ & 0xff);
104     plen = (plen<<8) | (*ptr++ & 0xff);
105 
106     if (plen != packet->length)
107 	return(KRB5KRB_AP_ERR_MODIFIED);
108 
109     /* verify version number */
110 
111     vno = (*ptr++ & 0xff);
112     vno = (vno<<8) | (*ptr++ & 0xff);
113 
114     if (vno != 1)
115 	return(KRB5KDC_ERR_BAD_PVNO);
116 
117     /* read, check ap-rep length */
118 
119     ap_rep.length = (*ptr++ & 0xff);
120     ap_rep.length = (ap_rep.length<<8) | (*ptr++ & 0xff);
121 
122     if (ptr + ap_rep.length >= packet->data + packet->length)
123 	return(KRB5KRB_AP_ERR_MODIFIED);
124 
125     if (ap_rep.length) {
126 	/* verify ap_rep */
127 	ap_rep.data = ptr;
128 	ptr += ap_rep.length;
129 
130 	/*
131 	 * Save send_subkey to later smash recv_subkey.
132 	 */
133 	ret = krb5_auth_con_getsendsubkey(context, auth_context, &tmp);
134 	if (ret)
135 	    return ret;
136 
137 	ret = krb5_rd_rep(context, auth_context, &ap_rep, &ap_rep_enc);
138 	if (ret) {
139 	    krb5_free_keyblock(context, tmp);
140 	    return(ret);
141 	}
142 
143 	krb5_free_ap_rep_enc_part(context, ap_rep_enc);
144 
145 	/* extract and decrypt the result */
146 
147 	cipherresult.data = ptr;
148 	cipherresult.length = (packet->data + packet->length) - ptr;
149 
150 	/*
151 	 * Smash recv_subkey to be send_subkey, per spec.
152 	 */
153 	ret = krb5_auth_con_setrecvsubkey(context, auth_context, tmp);
154 	krb5_free_keyblock(context, tmp);
155 	if (ret)
156 	    return ret;
157 
158 	ret = krb5_rd_priv(context, auth_context, &cipherresult, &clearresult,
159 			   &replay);
160 
161 	if (ret)
162 	    return(ret);
163     } else {
164 	cipherresult.data = ptr;
165 	cipherresult.length = (packet->data + packet->length) - ptr;
166 
167 	if ((ret = krb5_rd_error(context, &cipherresult, &krberror)))
168 	    return(ret);
169 
170 	clearresult = krberror->e_data;
171     }
172 
173     if (clearresult.length < 2) {
174 	ret = KRB5KRB_AP_ERR_MODIFIED;
175 	goto cleanup;
176     }
177 
178     ptr = clearresult.data;
179 
180     *result_code = (*ptr++ & 0xff);
181     *result_code = (*result_code<<8) | (*ptr++ & 0xff);
182 
183     if ((*result_code < KRB5_KPASSWD_SUCCESS) ||
184 	(*result_code > KRB5_KPASSWD_INITIAL_FLAG_NEEDED)) {
185 	ret = KRB5KRB_AP_ERR_MODIFIED;
186 	goto cleanup;
187     }
188 
189     /* all success replies should be authenticated/encrypted */
190 
191     if ((ap_rep.length == 0) && (*result_code == KRB5_KPASSWD_SUCCESS)) {
192 	ret = KRB5KRB_AP_ERR_MODIFIED;
193 	goto cleanup;
194     }
195 
196     result_data->length = (clearresult.data + clearresult.length) - ptr;
197 
198     if (result_data->length) {
199 	result_data->data = (char *) malloc(result_data->length);
200 	if (result_data->data == NULL) {
201 	    ret = ENOMEM;
202 	    goto cleanup;
203 	}
204 	memcpy(result_data->data, ptr, result_data->length);
205     } else {
206 	result_data->data = NULL;
207     }
208 
209     ret = 0;
210 
211 cleanup:
212     if (ap_rep.length) {
213 	krb5_xfree(clearresult.data);
214     } else {
215 	krb5_free_error(context, krberror);
216     }
217 
218     return(ret);
219 }
220 
221 krb5_error_code KRB5_CALLCONV
222 krb5_chpw_result_code_string(krb5_context context, int result_code, char **code_string)
223 {
224    switch (result_code) {
225    case KRB5_KPASSWD_MALFORMED:
226       *code_string = "Malformed request error";
227       break;
228    case KRB5_KPASSWD_HARDERROR:
229       *code_string = "Server error";
230       break;
231    case KRB5_KPASSWD_AUTHERROR:
232       *code_string = "Authentication error";
233       break;
234    case KRB5_KPASSWD_SOFTERROR:
235       *code_string = "Password change rejected";
236       break;
237    default:
238       *code_string = "Password change failed";
239       break;
240    }
241 
242    return(0);
243 }
244 
245 krb5_error_code
246 krb5int_mk_setpw_req(
247      krb5_context context,
248      krb5_auth_context auth_context,
249      krb5_data *ap_req,
250      krb5_principal targprinc,
251      char *passwd,
252      krb5_data *packet )
253 {
254     krb5_error_code ret;
255     krb5_data	cipherpw;
256     krb5_data	*encoded_setpw;
257 
258     char *ptr;
259 
260      cipherpw.data = NULL;
261      cipherpw.length = 0;
262 
263     if ((ret = krb5_auth_con_setflags(context, auth_context,
264 				      KRB5_AUTH_CONTEXT_DO_SEQUENCE)))
265 		return(ret);
266 
267     ret = encode_krb5_setpw_req(targprinc, passwd, &encoded_setpw);
268     if (ret) {
269 	return ret;
270     }
271 
272     if ( (ret = krb5_mk_priv(context, auth_context, encoded_setpw, &cipherpw, NULL)) != 0) {
273 	krb5_free_data( context, encoded_setpw);
274 	return(ret);
275     }
276     krb5_free_data( context, encoded_setpw);
277 
278 
279     packet->length = 6 + ap_req->length + cipherpw.length;
280     packet->data = (char *) malloc(packet->length);
281     if (packet->data  == NULL) {
282 	ret = ENOMEM;
283 	goto cleanup;
284     }
285     ptr = packet->data;
286 /*
287 ** build the packet -
288 */
289 /* put in the length */
290     *ptr++ = (packet->length>>8) & 0xff;
291     *ptr++ = packet->length & 0xff;
292 /* put in the version */
293     *ptr++ = (char)0xff;
294     *ptr++ = (char)0x80;
295 /* the ap_req length is big endian */
296     *ptr++ = (ap_req->length>>8) & 0xff;
297     *ptr++ = ap_req->length & 0xff;
298 /* put in the request data */
299     memcpy(ptr, ap_req->data, ap_req->length);
300     ptr += ap_req->length;
301 /*
302 ** put in the "private" password data -
303 */
304     memcpy(ptr, cipherpw.data, cipherpw.length);
305     ret = 0;
306  cleanup:
307     if (cipherpw.data)
308 	krb5_free_data_contents(context, &cipherpw);
309     if ((ret != 0) && packet->data) {
310 	free( packet->data);
311 	packet->data = NULL;
312     }
313     return ret;
314 }
315 
316 krb5_error_code
317 krb5int_rd_setpw_rep( krb5_context context, krb5_auth_context auth_context, krb5_data *packet,
318      int *result_code, krb5_data *result_data )
319 {
320     char *ptr;
321     unsigned int message_length, version_number;
322     krb5_data ap_rep;
323     krb5_ap_rep_enc_part *ap_rep_enc;
324     krb5_error_code ret;
325     krb5_data cipherresult;
326     krb5_data clearresult;
327     krb5_keyblock *tmpkey;
328 /*
329 ** validate the packet length -
330 */
331     if (packet->length < 4)
332 	return(KRB5KRB_AP_ERR_MODIFIED);
333 
334     ptr = packet->data;
335 
336 /*
337 ** see if it is an error
338 */
339     if (krb5_is_krb_error(packet)) {
340 	krb5_error *krberror;
341 	if ((ret = krb5_rd_error(context, packet, &krberror)))
342 	    return(ret);
343 	if (krberror->e_data.data  == NULL) {
344 	    ret = ERROR_TABLE_BASE_krb5 + (krb5_error_code) krberror->error;
345 	    krb5_free_error(context, krberror);
346 	    return (ret);
347 	}
348 	clearresult = krberror->e_data;
349 	krberror->e_data.data  = NULL; /*So we can free it later*/
350 	krberror->e_data.length = 0;
351 	krb5_free_error(context, krberror);
352 
353     } else { /* Not an error*/
354 
355 /*
356 ** validate the message length -
357 ** length is big endian
358 */
359 	message_length = (((ptr[0] << 8)&0xff) | (ptr[1]&0xff));
360 	ptr += 2;
361 /*
362 ** make sure the message length and packet length agree -
363 */
364 	if (message_length != packet->length)
365 	    return(KRB5KRB_AP_ERR_MODIFIED);
366 /*
367 ** get the version number -
368 */
369 	version_number = (((ptr[0] << 8)&0xff) | (ptr[1]&0xff));
370 	ptr += 2;
371 /*
372 ** make sure we support the version returned -
373 */
374 /*
375 ** set password version is 0xff80, change password version is 1
376 */
377 	if (version_number != 0xff80 && version_number != 1)
378 	    return(KRB5KDC_ERR_BAD_PVNO);
379 /*
380 ** now fill in ap_rep with the reply -
381 */
382 /*
383 ** get the reply length -
384 */
385 	ap_rep.length = (((ptr[0] << 8)&0xff) | (ptr[1]&0xff));
386 	ptr += 2;
387 /*
388 ** validate ap_rep length agrees with the packet length -
389 */
390 	if (ptr + ap_rep.length >= packet->data + packet->length)
391 	    return(KRB5KRB_AP_ERR_MODIFIED);
392 /*
393 ** if data was returned, set the ap_rep ptr -
394 */
395 	if( ap_rep.length ) {
396 	    ap_rep.data = ptr;
397 	    ptr += ap_rep.length;
398 
399 	    /*
400 	     * Save send_subkey to later smash recv_subkey.
401 	     */
402 	    ret = krb5_auth_con_getsendsubkey(context, auth_context, &tmpkey);
403 	    if (ret)
404 		return ret;
405 
406 	    ret = krb5_rd_rep(context, auth_context, &ap_rep, &ap_rep_enc);
407 	    if (ret) {
408 		krb5_free_keyblock(context, tmpkey);
409 		return(ret);
410 	    }
411 
412 	    krb5_free_ap_rep_enc_part(context, ap_rep_enc);
413 /*
414 ** now decrypt the result -
415 */
416 	    cipherresult.data = ptr;
417 	    cipherresult.length = (packet->data + packet->length) - ptr;
418 
419 	    /*
420 	     * Smash recv_subkey to be send_subkey, per spec.
421 	     */
422 	    ret = krb5_auth_con_setrecvsubkey(context, auth_context, tmpkey);
423 	    krb5_free_keyblock(context, tmpkey);
424 	    if (ret)
425 		return ret;
426 
427 	    ret = krb5_rd_priv(context, auth_context, &cipherresult, &clearresult,
428 			       NULL);
429 	    if (ret)
430 		return(ret);
431 	} /*We got an ap_rep*/
432 	else
433 	    return (KRB5KRB_AP_ERR_MODIFIED);
434     } /*Response instead of error*/
435 
436 /*
437 ** validate the cleartext length
438 */
439     if (clearresult.length < 2) {
440 	ret = KRB5KRB_AP_ERR_MODIFIED;
441 	goto cleanup;
442     }
443 /*
444 ** now decode the result -
445 */
446     ptr = clearresult.data;
447 
448     *result_code = (((ptr[0] << 8)&0xff) | (ptr[1]&0xff));
449     ptr += 2;
450 
451 /*
452 ** result code 5 is access denied
453 */
454     if ((*result_code < KRB5_KPASSWD_SUCCESS) || (*result_code > 5))
455     {
456 	ret = KRB5KRB_AP_ERR_MODIFIED;
457 	goto cleanup;
458     }
459 /*
460 ** all success replies should be authenticated/encrypted
461 */
462     if( (ap_rep.length == 0) && (*result_code == KRB5_KPASSWD_SUCCESS) )
463     {
464 	ret = KRB5KRB_AP_ERR_MODIFIED;
465 	goto cleanup;
466     }
467 
468     if (result_data) {
469 	result_data->length = (clearresult.data + clearresult.length) - ptr;
470 
471 	if (result_data->length)
472 	{
473 	    result_data->data = (char *) malloc(result_data->length);
474 	    if (result_data->data)
475 		memcpy(result_data->data, ptr, result_data->length);
476 	}
477 	else
478 	    result_data->data = NULL;
479     }
480     ret = 0;
481 
482  cleanup:
483     krb5_free_data_contents(context, &clearresult);
484     return(ret);
485 }
486 
487 krb5_error_code
488 krb5int_setpw_result_code_string( krb5_context context, int result_code, const char **code_string )
489 {
490    switch (result_code)
491    {
492    case KRB5_KPASSWD_MALFORMED:
493       *code_string = "Malformed request error";
494       break;
495    case KRB5_KPASSWD_HARDERROR:
496       *code_string = "Server error";
497       break;
498    case KRB5_KPASSWD_AUTHERROR:
499       *code_string = "Authentication error";
500       break;
501    case KRB5_KPASSWD_SOFTERROR:
502       *code_string = "Password change rejected";
503       break;
504    case 5: /* access denied */
505       *code_string = "Access denied";
506       break;
507    case 6:	/* bad version */
508       *code_string = "Wrong protocol version";
509       break;
510    case 7: /* initial flag is needed */
511       *code_string = "Initial password required";
512       break;
513    case 0:
514 	  *code_string = "Success";
515    default:
516       *code_string = "Password change failed";
517       break;
518    }
519 
520    return(0);
521 }
522 
523