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