1ff2d67cd7a54b10dab0241eeb39431422758e45
[krb5.git] / src / lib / krb5 / krb / fast.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  * lib/krb5/krb/fast.c
4  *
5  * Copyright (C) 2009 by the Massachusetts Institute of Technology.
6  * All rights reserved.
7  *
8  * Export of this software from the United States of America may
9  *   require a specific license from the United States Government.
10  *   It is the responsibility of any person or organization contemplating
11  *   export to obtain such a license before exporting.
12  *
13  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
14  * distribute this software and its documentation for any purpose and
15  * without fee is hereby granted, provided that the above copyright
16  * notice appear in all copies and that both that copyright notice and
17  * this permission notice appear in supporting documentation, and that
18  * the name of M.I.T. not be used in advertising or publicity pertaining
19  * to distribution of the software without specific, written prior
20  * permission.  Furthermore if you modify this software you must label
21  * your software as modified software and not distribute it in such a
22  * fashion that it might be confused with the original M.I.T. software.
23  * M.I.T. makes no representations about the suitability of
24  * this software for any purpose.  It is provided "as is" without express
25  * or implied warranty.
26  *
27  *
28  *
29  */
30
31 #include <k5-int.h>
32
33 /*
34  * It is possible to support sending a request that includes both a
35  * FAST and normal version.  This would complicate the
36  * pre-authentication logic significantly.  You would need to maintain
37  * two contexts, one for FAST and one for normal use.  In adition, you
38  * would need to manage the security issues surrounding downgrades.
39  * However trying FAST at all requires an armor key.  Generally in
40  * obtaining the armor key, the client learns enough to know that FAST
41  * is supported.  If not, the client can see FAST in the
42  * preauth_required error's padata and retry with FAST.  So, this
43  * implementation does not support FAST+normal.
44  *
45  * We store the outer version of the request to use .  The caller
46  * stores the inner version.  We handle the encoding of the request
47  * body (and request) and provide encoded request bodies for the
48  * caller to use as these may be used for checksums.  In the AS case
49  * we also evaluate whether to continue a conversation as one of the
50  * important questions there is the presence of a cookie.
51  */
52 #include "fast.h"
53 #include "int-proto.h"
54
55
56 static krb5_error_code
57 fast_armor_ap_request(krb5_context context,
58                       struct krb5int_fast_request_state *state,
59                       krb5_ccache ccache, krb5_principal target_principal)
60 {
61     krb5_error_code retval = 0;
62     krb5_creds creds, *out_creds = NULL;
63     krb5_auth_context authcontext = NULL;
64     krb5_data encoded_authenticator;
65     krb5_fast_armor *armor = NULL;
66     krb5_keyblock *subkey = NULL, *armor_key = NULL;
67     encoded_authenticator.data = NULL;
68     memset(&creds, 0, sizeof(creds));
69     creds.server = target_principal;
70     retval = krb5_cc_get_principal(context, ccache, &creds.client);
71     if (retval == 0)
72         retval = krb5_get_credentials(context, 0, ccache,  &creds, &out_creds);
73     if (retval == 0)
74         retval = krb5_mk_req_extended(context, &authcontext, AP_OPTS_USE_SUBKEY, NULL /*data*/,
75                                       out_creds, &encoded_authenticator);
76     if (retval == 0)
77         retval = krb5_auth_con_getsendsubkey(context, authcontext, &subkey);
78     if (retval == 0)
79         retval = krb5_c_fx_cf2_simple(context, subkey, "subkeyarmor",
80                                       &out_creds->keyblock, "ticketarmor", &armor_key);
81     if (retval == 0) {
82         armor = calloc(1, sizeof(krb5_fast_armor));
83         if (armor == NULL)
84             retval = ENOMEM;
85     }
86     if (retval == 0) {
87         armor->armor_type = KRB5_FAST_ARMOR_AP_REQUEST;
88         armor->armor_value = encoded_authenticator;
89         encoded_authenticator.data = NULL;
90         encoded_authenticator.length = 0;
91         state->armor = armor;
92         armor = NULL;
93         state->armor_key = armor_key;
94         armor_key = NULL;
95     }
96     krb5_free_keyblock(context, armor_key);
97     krb5_free_keyblock(context, subkey);
98     if (out_creds)
99         krb5_free_creds(context, out_creds);
100     /* target_principal is owned by caller. */
101     creds.server = NULL;
102     krb5_free_cred_contents(context, &creds);
103     if (encoded_authenticator.data)
104         krb5_free_data_contents(context, &encoded_authenticator);
105     krb5_auth_con_free(context, authcontext);
106     return retval;
107 }
108
109 krb5_error_code
110 krb5int_fast_prep_req_body(krb5_context context, struct krb5int_fast_request_state *state,
111                            krb5_kdc_req *request, krb5_data **encoded_request_body)
112 {
113     krb5_error_code retval = 0;
114     krb5_data *local_encoded_request_body = NULL;
115     assert(state != NULL);
116     *encoded_request_body = NULL;
117     if (state->armor_key == NULL) {
118         return   encode_krb5_kdc_req_body(request, encoded_request_body);
119     }
120     state->fast_outer_request = *request;
121     state->fast_outer_request.padata = NULL;
122     if (retval == 0)
123         retval = encode_krb5_kdc_req_body(&state->fast_outer_request,
124                                           &local_encoded_request_body);
125     if (retval == 0) {
126         *encoded_request_body = local_encoded_request_body;
127         local_encoded_request_body = NULL;
128     }
129     if (local_encoded_request_body != NULL)
130         krb5_free_data(context, local_encoded_request_body);
131     return retval;
132 }
133
134 krb5_error_code
135 krb5int_fast_as_armor(krb5_context context,
136                       struct krb5int_fast_request_state *state,
137                       krb5_gic_opt_ext *opte,
138                       krb5_kdc_req *request)
139 {
140     krb5_error_code retval = 0;
141     krb5_ccache ccache = NULL;
142     krb5_principal target_principal = NULL;
143     krb5_data *target_realm;
144     krb5_clear_error_message(context);
145     target_realm = krb5_princ_realm(context, request->server);
146     if (opte->opt_private->fast_ccache_name) {
147         state->fast_state_flags |= KRB5INT_FAST_ARMOR_AVAIL;
148         retval = krb5_cc_resolve(context, opte->opt_private->fast_ccache_name,
149                                  &ccache);
150         if (retval == 0) {
151             retval = krb5_tgtname(context, target_realm, target_realm,
152                                   &target_principal);
153         }
154         if (retval == 0) {
155             krb5_data config_data;
156             config_data.data = NULL;
157             retval = krb5_cc_get_config(context, ccache, target_principal,
158                                         KRB5_CONF_FAST_AVAIL, &config_data);
159             if ((retval == 0) && config_data.data )
160                 state->fast_state_flags |= KRB5INT_FAST_DO_FAST;
161             krb5_free_data_contents(context, &config_data);
162             retval = 0;
163         }
164         if (opte->opt_private->fast_flags& KRB5_FAST_REQUIRED)
165             state->fast_state_flags |= KRB5INT_FAST_DO_FAST;
166         if (retval == 0 && (state->fast_state_flags & KRB5INT_FAST_DO_FAST)) {
167             retval = fast_armor_ap_request(context, state, ccache,
168                                            target_principal);
169         }
170         if (retval != 0) {
171             const char * errmsg;
172             errmsg = krb5_get_error_message(context, retval);
173             if (errmsg) {
174                 krb5_set_error_message(context, retval, "%s constructing AP-REQ armor", errmsg);
175                 krb5_free_error_message(context, errmsg);
176             }
177         }
178     }
179     if (ccache)
180         krb5_cc_close(context, ccache);
181     if (target_principal)
182         krb5_free_principal(context, target_principal);
183     return retval;
184 }
185
186
187 krb5_error_code
188 krb5int_fast_prep_req(krb5_context context,
189                       struct krb5int_fast_request_state *state,
190                       krb5_kdc_req *request,
191                       const krb5_data *to_be_checksummed,
192                       kdc_req_encoder_proc encoder,
193                       krb5_data **encoded_request)
194 {
195     krb5_error_code retval = 0;
196     krb5_pa_data *pa_array[2];
197     krb5_pa_data pa[2];
198     krb5_fast_req fast_req;
199     krb5_fast_armored_req *armored_req = NULL;
200     krb5_data *encoded_fast_req = NULL;
201     krb5_data *encoded_armored_req = NULL;
202     krb5_data *local_encoded_result = NULL;
203     krb5_cksumtype cksumtype;
204     krb5_data random_data;
205     char random_buf[4];
206
207
208     assert(state != NULL);
209     assert(state->fast_outer_request.padata == NULL);
210     memset(pa_array, 0, sizeof pa_array);
211     if (state->armor_key == NULL) {
212         return encoder(request, encoded_request);
213     }
214 /* Fill in a fresh random nonce for each inner request*/
215     random_data.length = 4;
216     random_data.data = (char *)random_buf;
217     retval = krb5_c_random_make_octets(context, &random_data);
218     if (retval == 0) {
219         request->nonce = 0x7fffffff & load_32_n(random_buf);
220         state->nonce = request->nonce;
221     }
222     fast_req.req_body =  request;
223     if (fast_req.req_body->padata == NULL) {
224         fast_req.req_body->padata = calloc(1, sizeof(krb5_pa_data *));
225         if (fast_req.req_body->padata == NULL)
226             retval = ENOMEM;
227     }
228     fast_req.fast_options = state->fast_options;
229     if (retval == 0)
230         retval = encode_krb5_fast_req(&fast_req, &encoded_fast_req);
231     if (retval == 0) {
232         armored_req = calloc(1, sizeof(krb5_fast_armored_req));
233         if (armored_req == NULL)
234             retval = ENOMEM;
235     }
236     if (retval == 0)
237         armored_req->armor = state->armor;
238     if (retval == 0)
239         retval = krb5int_c_mandatory_cksumtype(context, state->armor_key->enctype,
240                                                &cksumtype);
241     /* DES enctypes have unkeyed mandatory checksums; need a keyed one. */
242     if (retval == 0 && !krb5_c_is_keyed_cksum(cksumtype))
243         cksumtype = CKSUMTYPE_RSA_MD5_DES;
244     if (retval ==0)
245         retval = krb5_c_make_checksum(context, cksumtype, state->armor_key,
246                                       KRB5_KEYUSAGE_FAST_REQ_CHKSUM, to_be_checksummed,
247                                       &armored_req->req_checksum);
248     if (retval == 0)
249         retval = krb5_encrypt_helper(context, state->armor_key,
250                                      KRB5_KEYUSAGE_FAST_ENC, encoded_fast_req,
251                                      &armored_req->enc_part);
252     if (retval == 0)
253         retval = encode_krb5_pa_fx_fast_request(armored_req, &encoded_armored_req);
254     if (retval==0) {
255         pa[0].pa_type = KRB5_PADATA_FX_FAST;
256         pa[0].contents = (unsigned char *) encoded_armored_req->data;
257         pa[0].length = encoded_armored_req->length;
258         pa_array[0] = &pa[0];
259     }
260     state->fast_outer_request.padata = pa_array;
261     if(retval == 0)
262         retval = encoder(&state->fast_outer_request, &local_encoded_result);
263     if (retval == 0) {
264         *encoded_request = local_encoded_result;
265         local_encoded_result = NULL;
266     }
267     if (encoded_armored_req)
268         krb5_free_data(context, encoded_armored_req);
269     if (armored_req) {
270         armored_req->armor = NULL; /*owned by state*/
271         krb5_free_fast_armored_req(context, armored_req);
272     }
273     if (encoded_fast_req)
274         krb5_free_data(context, encoded_fast_req);
275     if (local_encoded_result)
276         krb5_free_data(context, local_encoded_result);
277     state->fast_outer_request.padata = NULL;
278     return retval;
279 }
280
281 static krb5_error_code
282 decrypt_fast_reply(krb5_context context,
283                    struct krb5int_fast_request_state *state,
284                    krb5_pa_data **in_padata,
285                    krb5_fast_response **response)
286 {
287     krb5_error_code retval = 0;
288     krb5_data scratch;
289     krb5_enc_data *encrypted_response = NULL;
290     krb5_pa_data *fx_reply = NULL;
291     krb5_fast_response *local_resp = NULL;
292     assert(state != NULL);
293     assert(state->armor_key);
294     fx_reply = krb5int_find_pa_data(context, in_padata, KRB5_PADATA_FX_FAST);
295     if (fx_reply == NULL)
296         retval = KRB5_ERR_FAST_REQUIRED;
297     if (retval == 0) {
298         scratch.data = (char *) fx_reply->contents;
299         scratch.length = fx_reply->length;
300         retval = decode_krb5_pa_fx_fast_reply(&scratch, &encrypted_response);
301     }
302     scratch.data = NULL;
303     if (retval == 0) {
304         scratch.data = malloc(encrypted_response->ciphertext.length);
305         if (scratch.data == NULL)
306             retval = ENOMEM;
307         scratch.length = encrypted_response->ciphertext.length;
308     }
309     if (retval == 0)
310         retval = krb5_c_decrypt(context, state->armor_key,
311                                 KRB5_KEYUSAGE_FAST_REP, NULL,
312                                 encrypted_response, &scratch);
313     if (retval != 0) {
314         const char * errmsg;
315         errmsg = krb5_get_error_message(context, retval);
316         krb5_set_error_message(context, retval, "%s while decrypting FAST reply", errmsg);
317         krb5_free_error_message(context, errmsg);
318     }
319     if (retval == 0)
320         retval = decode_krb5_fast_response(&scratch, &local_resp);
321     if (retval == 0) {
322         if (local_resp->nonce != state->nonce) {
323             retval = KRB5_KDCREP_MODIFIED;
324             krb5_set_error_message(context, retval, "nonce modified in FAST response: KDC response modified");
325         }
326     }
327     if (retval == 0) {
328         *response = local_resp;
329         local_resp = NULL;
330     }
331     if (scratch.data)
332         free(scratch.data);
333     if (encrypted_response)
334         krb5_free_enc_data(context, encrypted_response);
335     if (local_resp)
336         krb5_free_fast_response(context, local_resp);
337     return retval;
338 }
339
340 /*
341  * FAST separates two concepts: the set of padata we're using to
342  * decide what pre-auth mechanisms to use and the set of padata we're
343  * making available to mechanisms in order for them to respond to an
344  * error.  The plugin interface in March 2009 does not permit
345  * separating these concepts for the plugins.  This function makes
346  * both available for future revisions to the plugin interface.  It
347  * also re-encodes the padata from the current error as a encoded
348  * typed-data and puts that in the e_data field.  That will allow
349  * existing plugins with the old interface to find the error data.
350  * The output parameter out_padata contains the padata from the error
351  * whenever padata  is available (all the time with fast).
352  */
353 krb5_error_code
354 krb5int_fast_process_error(krb5_context context,
355                            struct krb5int_fast_request_state *state,
356                            krb5_error **err_replyptr,
357                            krb5_pa_data ***out_padata,
358                            krb5_boolean *retry)
359 {
360     krb5_error_code retval = 0;
361     krb5_error *err_reply = *err_replyptr;
362     *out_padata = NULL;
363     *retry = 0;
364     if (state->armor_key) {
365         krb5_pa_data *fx_error_pa;
366         krb5_pa_data **result = NULL;
367         krb5_data scratch, *encoded_td = NULL;
368         krb5_error *fx_error = NULL;
369         krb5_fast_response *fast_response = NULL;
370         retval = decode_krb5_padata_sequence(&err_reply->e_data, &result);
371         if (retval == 0)
372             retval = decrypt_fast_reply(context, state, result, &fast_response);
373         if (retval) {
374             /*This can happen if the KDC does not understand FAST. We
375              * don't expect that, but treating it as the fatal error
376              * indicated by the KDC seems reasonable.
377              */
378             *retry = 0;
379             krb5_free_pa_data(context, result);
380             return 0;
381         }
382         krb5_free_pa_data(context, result);
383         result = NULL;
384         if (retval == 0) {
385             fx_error_pa = krb5int_find_pa_data(context, fast_response->padata, KRB5_PADATA_FX_ERROR);
386             if (fx_error_pa == NULL) {
387                 krb5_set_error_message(context, KRB5KDC_ERR_PREAUTH_FAILED, "Expecting FX_ERROR pa-data inside FAST container");
388                 retval = KRB5KDC_ERR_PREAUTH_FAILED;
389             }
390         }
391         if (retval == 0) {
392             scratch.data = (char *) fx_error_pa->contents;
393             scratch.length = fx_error_pa->length;
394             retval = decode_krb5_error(&scratch, &fx_error);
395         }
396         /*
397          * krb5_pa_data and krb5_typed_data are safe to cast between:
398          * they have the same type fields in the same order.
399          * (krb5_preauthtype is a krb5_int32).  If krb5_typed_data is
400          * ever changed then this will need to be a copy not a cast.
401          */
402         if (retval == 0)
403             retval = encode_krb5_typed_data( (const krb5_typed_data **)fast_response->padata,
404                                              &encoded_td);
405         if (retval == 0) {
406             fx_error->e_data = *encoded_td;
407             free(encoded_td); /*contents owned by fx_error*/
408             encoded_td = NULL;
409             krb5_free_error(context, err_reply);
410             *err_replyptr = fx_error;
411             fx_error = NULL;
412             *out_padata = fast_response->padata;
413             fast_response->padata = NULL;
414             /*
415              * If there is more than the fx_error padata, then we want
416              * to retry the error if a cookie is present
417              */
418             *retry = (*out_padata)[1] != NULL;
419             if (krb5int_find_pa_data(context, *out_padata, KRB5_PADATA_FX_COOKIE) == NULL)
420                 *retry = 0;
421         }
422         if (fx_error)
423             krb5_free_error(context, fx_error);
424         krb5_free_fast_response(context, fast_response);
425     } else { /*not FAST*/
426         *retry = (err_reply->e_data.length > 0);
427         if ((err_reply->error == KDC_ERR_PREAUTH_REQUIRED
428              ||err_reply->error == KDC_ERR_PREAUTH_FAILED) && err_reply->e_data.length) {
429             krb5_pa_data **result = NULL;
430             retval = decode_krb5_padata_sequence(&err_reply->e_data, &result);
431             if (retval == 0)
432                 if (retval == 0) {
433                     *out_padata = result;
434
435                     return 0;
436                 }
437             krb5_free_pa_data(context, result);
438             krb5_set_error_message(context, retval,
439                                    "Error decoding padata in error reply");
440             return retval;
441         }
442     }
443     return retval;
444 }
445
446
447 krb5_error_code
448 krb5int_fast_process_response(krb5_context context,
449                               struct krb5int_fast_request_state *state,
450                               krb5_kdc_rep *resp,
451                               krb5_keyblock **strengthen_key)
452 {
453     krb5_error_code retval = 0;
454     krb5_fast_response *fast_response = NULL;
455     krb5_data *encoded_ticket = NULL;
456     krb5_boolean cksum_valid;
457     krb5_clear_error_message(context);
458     *strengthen_key = NULL;
459     if (state->armor_key == 0)
460         return 0;
461     retval = decrypt_fast_reply(context, state, resp->padata,
462                                 &fast_response);
463     if (retval == 0) {
464         if (fast_response->finished == 0) {
465             retval = KRB5_KDCREP_MODIFIED;
466             krb5_set_error_message(context, retval, "FAST response missing finish message in KDC reply");
467         }
468     }
469     if (retval == 0)
470         retval = encode_krb5_ticket(resp->ticket, &encoded_ticket);
471     if (retval == 0)
472         retval = krb5_c_verify_checksum(context, state->armor_key,
473                                         KRB5_KEYUSAGE_FAST_FINISHED,
474                                         encoded_ticket,
475                                         &fast_response->finished->ticket_checksum,
476                                         &cksum_valid);
477     if (retval == 0 && cksum_valid == 0) {
478         retval = KRB5_KDCREP_MODIFIED;
479         krb5_set_error_message(context, retval, "ticket modified in KDC reply");
480     }
481     if (retval == 0) {
482         krb5_free_principal(context, resp->client);
483         resp->client = fast_response->finished->client;
484         fast_response->finished->client = NULL;
485         *strengthen_key = fast_response->strengthen_key;
486         fast_response->strengthen_key = NULL;
487         krb5_free_pa_data(context, resp->padata);
488         resp->padata = fast_response->padata;
489         fast_response->padata = NULL;
490     }
491     if (fast_response)
492         krb5_free_fast_response(context, fast_response);
493     if (encoded_ticket)
494         krb5_free_data(context, encoded_ticket);
495     return retval;
496 }
497
498 krb5_error_code
499 krb5int_fast_reply_key(krb5_context context,
500                        krb5_keyblock *strengthen_key,
501                        krb5_keyblock *existing_key,
502                        krb5_keyblock *out_key)
503 {
504     krb5_keyblock *key = NULL;
505     krb5_error_code retval = 0;
506     krb5_free_keyblock_contents(context, out_key);
507     if (strengthen_key) {
508         retval = krb5_c_fx_cf2_simple(context, strengthen_key,
509                                       "strengthenkey", existing_key, "replykey", &key);
510         if (retval == 0) {
511             *out_key = *key;
512             free(key);
513         }
514     } else {
515         retval = krb5_copy_keyblock_contents(context, existing_key, out_key);
516     }
517     return retval;
518 }
519
520
521 krb5_error_code
522 krb5int_fast_make_state( krb5_context context, struct krb5int_fast_request_state **state)
523 {
524     struct krb5int_fast_request_state *local_state ;
525     local_state = malloc(sizeof *local_state);
526     if (local_state == NULL)
527         return ENOMEM;
528     memset(local_state, 0, sizeof(*local_state));
529     *state = local_state;
530     return 0;
531 }
532
533 void
534 krb5int_fast_free_state( krb5_context context, struct krb5int_fast_request_state *state)
535 {
536     if (state == NULL)
537         return;
538     /*We are responsible for none of the store in the fast_outer_req*/
539     krb5_free_keyblock(context, state->armor_key);
540     krb5_free_fast_armor(context, state->armor);
541     free(state);
542 }
543
544 krb5_pa_data *
545 krb5int_find_pa_data(krb5_context context, krb5_pa_data *const *padata,
546                      krb5_preauthtype pa_type)
547 {
548     krb5_pa_data * const *tmppa;
549
550     if (padata == NULL)
551         return NULL;
552
553     for (tmppa = padata; *tmppa != NULL; tmppa++) {
554         if ((*tmppa)->pa_type == pa_type)
555             break;
556     }
557
558     return *tmppa;
559 }
560
561
562 krb5_error_code
563 krb5int_fast_verify_nego(krb5_context context,
564                          struct krb5int_fast_request_state *state,
565                          krb5_kdc_rep *rep, krb5_data *request,
566                          krb5_keyblock *decrypting_key,
567                          krb5_boolean *fast_avail)
568 {
569     krb5_error_code retval = 0;
570     krb5_checksum *checksum = NULL;
571     krb5_pa_data *pa;
572     krb5_data scratch;
573     krb5_boolean valid;
574
575     if (rep->enc_part2->flags& TKT_FLG_ENC_PA_REP) {
576         pa = krb5int_find_pa_data(context, rep->enc_part2->enc_padata,
577                                   KRB5_ENCPADATA_REQ_ENC_PA_REP);
578         if (pa == NULL)
579             retval = KRB5_KDCREP_MODIFIED;
580         else {
581             scratch.data = (char *) pa->contents;
582             scratch.length = pa->length;
583         }
584         if (retval == 0)
585             retval = decode_krb5_checksum(&scratch, &checksum);
586         if (retval == 0)
587             retval = krb5_c_verify_checksum(context, decrypting_key,
588                                             KRB5_KEYUSAGE_AS_REQ,
589                                             request, checksum, &valid);
590         if (retval == 0 &&valid == 0)
591             retval = KRB5_KDCREP_MODIFIED;
592         if (retval == 0) {
593             pa = krb5int_find_pa_data(context, rep->enc_part2->enc_padata,
594                                       KRB5_PADATA_FX_FAST);
595             *fast_avail = (pa != NULL);
596         }
597     }
598     if (checksum)
599         krb5_free_checksum(context, checksum);
600     return retval;
601 }
602
603 krb5_boolean
604 krb5int_upgrade_to_fast_p(krb5_context context,
605                           struct krb5int_fast_request_state *state,
606                           krb5_pa_data **padata)
607 {
608     if (state->armor_key != NULL)
609         return 0; /*already using FAST*/
610     if (!(state->fast_state_flags & KRB5INT_FAST_ARMOR_AVAIL))
611         return 0;
612     if (krb5int_find_pa_data(context, padata, KRB5_PADATA_FX_FAST) != NULL) {
613         state->fast_state_flags |= KRB5INT_FAST_DO_FAST;
614         return 1;
615     }
616     return 0;
617 }