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