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