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