whitespace
[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     *ptr++ = (packet->length>> 8) & 0xff;
47     *ptr++ = packet->length & 0xff;
48
49     /* version == 0x0001 big-endian */
50
51     *ptr++ = 0;
52     *ptr++ = 1;
53
54     /* ap_req length, big-endian */
55
56     *ptr++ = (ap_req->length>>8) & 0xff;
57     *ptr++ = ap_req->length & 0xff;
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
273     char *ptr;
274
275     cipherpw.data = NULL;
276     cipherpw.length = 0;
277      
278     if ((ret = krb5_auth_con_setflags(context, auth_context,
279                                       KRB5_AUTH_CONTEXT_DO_SEQUENCE)))
280         return(ret);
281
282     ret = encode_krb5_setpw_req(targprinc, passwd, &encoded_setpw);
283     if (ret) {
284         return ret;
285     }
286
287     if ((ret = krb5_mk_priv(context, auth_context, encoded_setpw, &cipherpw, NULL)) != 0) {
288         krb5_free_data(context, encoded_setpw);
289         return(ret);
290     }
291     krb5_free_data(context, encoded_setpw);
292     
293
294     packet->length = 6 + ap_req->length + cipherpw.length;
295     packet->data = (char *) malloc(packet->length);
296     if (packet->data  == NULL) {
297         ret = ENOMEM;
298         goto cleanup;
299     }
300     ptr = packet->data;
301     /*
302     ** build the packet -
303     */
304     /* put in the length */
305     *ptr++ = (packet->length>>8) & 0xff;
306     *ptr++ = packet->length & 0xff;
307     /* put in the version */
308     *ptr++ = (char)0xff;
309     *ptr++ = (char)0x80;
310     /* the ap_req length is big endian */
311     *ptr++ = (ap_req->length>>8) & 0xff;
312     *ptr++ = ap_req->length & 0xff;
313     /* put in the request data */
314     memcpy(ptr, ap_req->data, ap_req->length);
315     ptr += ap_req->length;
316     /*
317     ** put in the "private" password data -
318     */
319     memcpy(ptr, cipherpw.data, cipherpw.length);
320     ret = 0;
321 cleanup:
322     if (cipherpw.data)
323         krb5_free_data_contents(context, &cipherpw);
324     if ((ret != 0) && packet->data) {
325         free(packet->data);
326         packet->data = NULL;
327     }
328     return ret;
329 }
330
331 krb5_error_code 
332 krb5int_rd_setpw_rep(krb5_context context, krb5_auth_context auth_context,
333                      krb5_data *packet,
334                      int *result_code, krb5_data *result_data)
335 {
336     char *ptr;
337     unsigned int message_length, version_number;
338     krb5_data ap_rep;
339     krb5_ap_rep_enc_part *ap_rep_enc;
340     krb5_error_code ret;
341     krb5_data cipherresult;
342     krb5_data clearresult;
343     krb5_keyblock *tmpkey;
344     /*
345     ** validate the packet length -
346     */
347     if (packet->length < 4)
348         return(KRB5KRB_AP_ERR_MODIFIED);
349
350     ptr = packet->data;
351
352     /*
353     ** see if it is an error
354     */
355     if (krb5_is_krb_error(packet)) {
356         krb5_error *krberror;
357         if ((ret = krb5_rd_error(context, packet, &krberror)))
358             return(ret);
359         if (krberror->e_data.data  == NULL) {
360             ret = ERROR_TABLE_BASE_krb5 + (krb5_error_code) krberror->error;
361             krb5_free_error(context, krberror);
362             return (ret);
363         }
364         clearresult = krberror->e_data;
365         krberror->e_data.data  = NULL; /*So we can free it later*/
366         krberror->e_data.length = 0;
367         krb5_free_error(context, krberror);
368
369     } else { /* Not an error*/
370
371         /*
372         ** validate the message length -
373         ** length is big endian 
374         */
375         message_length = (((ptr[0] << 8)&0xff) | (ptr[1]&0xff));
376         ptr += 2;
377         /*
378         ** make sure the message length and packet length agree -
379         */
380         if (message_length != packet->length)
381             return(KRB5KRB_AP_ERR_MODIFIED);
382         /*
383         ** get the version number -
384         */
385         version_number = (((ptr[0] << 8)&0xff) | (ptr[1]&0xff));
386         ptr += 2;
387         /*
388         ** make sure we support the version returned -
389         */
390         /*
391         ** set password version is 0xff80, change password version is 1
392         */
393         if (version_number != 1 && version_number != 0xff80)
394             return(KRB5KDC_ERR_BAD_PVNO);
395         /*
396         ** now fill in ap_rep with the reply -
397         */
398         /*
399         ** get the reply length -
400         */
401         ap_rep.length = (((ptr[0] << 8)&0xff) | (ptr[1]&0xff));
402         ptr += 2;
403         /*
404         ** validate ap_rep length agrees with the packet length -
405         */
406         if (ptr + ap_rep.length >= packet->data + packet->length)
407             return(KRB5KRB_AP_ERR_MODIFIED);
408         /*
409         ** if data was returned, set the ap_rep ptr -
410         */
411         if (ap_rep.length) {
412             ap_rep.data = ptr;
413             ptr += ap_rep.length;
414
415             /*
416              * Save send_subkey to later smash recv_subkey.
417              */
418             ret = krb5_auth_con_getsendsubkey(context, auth_context, &tmpkey);
419             if (ret)
420                 return ret;
421
422             ret = krb5_rd_rep(context, auth_context, &ap_rep, &ap_rep_enc);
423             if (ret) {
424                 krb5_free_keyblock(context, tmpkey);
425                 return(ret);
426             }
427
428             krb5_free_ap_rep_enc_part(context, ap_rep_enc);
429             /*
430             ** now decrypt the result -
431             */
432             cipherresult.data = ptr;
433             cipherresult.length = (packet->data + packet->length) - ptr;
434
435             /*
436              * Smash recv_subkey to be send_subkey, per spec.
437              */
438             ret = krb5_auth_con_setrecvsubkey(context, auth_context, tmpkey);
439             krb5_free_keyblock(context, tmpkey);
440             if (ret)
441                 return ret;
442
443             ret = krb5_rd_priv(context, auth_context, &cipherresult, &clearresult,
444                                NULL);
445             if (ret)
446                 return(ret);
447         } /*We got an ap_rep*/
448         else
449             return (KRB5KRB_AP_ERR_MODIFIED);
450     } /*Response instead of error*/
451
452     /*
453     ** validate the cleartext length 
454     */
455     if (clearresult.length < 2) {
456         ret = KRB5KRB_AP_ERR_MODIFIED;
457         goto cleanup;
458     }
459     /*
460     ** now decode the result -
461     */
462     ptr = clearresult.data;
463
464     *result_code = (((ptr[0] << 8)&0xff) | (ptr[1]&0xff));
465     ptr += 2;
466
467     /*
468     ** result code 5 is access denied
469     */
470     if ((*result_code < KRB5_KPASSWD_SUCCESS) || (*result_code > 5)) {
471         ret = KRB5KRB_AP_ERR_MODIFIED;
472         goto cleanup;
473     }
474     /*
475     ** all success replies should be authenticated/encrypted
476     */
477     if ((ap_rep.length == 0) && (*result_code == KRB5_KPASSWD_SUCCESS)) {
478         ret = KRB5KRB_AP_ERR_MODIFIED;
479         goto cleanup;
480     }
481
482     if (result_data) {
483         result_data->length = (clearresult.data + clearresult.length) - ptr;
484
485         if (result_data->length) {
486             result_data->data = (char *) malloc(result_data->length);
487             if (result_data->data)
488                 memcpy(result_data->data, ptr, result_data->length);
489         } else
490             result_data->data = NULL;
491     }
492     ret = 0;
493
494  cleanup:
495     krb5_free_data_contents(context, &clearresult);
496     return(ret);
497 }
498
499 krb5_error_code 
500 krb5int_setpw_result_code_string(krb5_context context, int result_code,
501                                  const char **code_string)
502 {
503     switch (result_code) {
504     case KRB5_KPASSWD_MALFORMED:
505         *code_string = "Malformed request error";
506         break;
507     case KRB5_KPASSWD_HARDERROR:
508         *code_string = "Server error";
509         break;
510     case KRB5_KPASSWD_AUTHERROR:
511         *code_string = "Authentication error";
512         break;
513     case KRB5_KPASSWD_SOFTERROR:
514         *code_string = "Password change rejected";
515         break;
516     case 5: /* access denied */
517         *code_string = "Access denied";
518         break;
519     case 6:     /* bad version */
520         *code_string = "Wrong protocol version";
521         break;
522     case 7: /* initial flag is needed */
523         *code_string = "Initial password required";
524         break;
525     case 0:
526         *code_string = "Success";
527     default:
528         *code_string = "Password change failed";
529         break;
530     }
531
532     return(0);
533 }
534