~ubuntu-branches/ubuntu/hardy/wpasupplicant/hardy

« back to all changes in this revision

Viewing changes to eap_psk.c

  • Committer: Bazaar Package Importer
  • Author(s): Reinhard Tartler, Alexander Sack
  • Date: 2007-08-26 16:06:57 UTC
  • mfrom: (1.1.9 upstream)
  • Revision ID: james.westby@ubuntu.com-20070826160657-2m8pxoweuxe8f93t
Tags: 0.6.0+0.5.8-0ubuntu1
* New upstream release
* remove patch 11_erroneous_manpage_ref, applied upstream
* remove patch 25_wpas_dbus_unregister_iface_fix, applied upstream

[ Alexander Sack ]
* bumping upstream version to replace development version 0.6.0 with
  this package from stable release branch.
* attempt to fix wierd timeout and high latency issues by going
  back to stable upstream version (0.5.9) (LP: #140763,
  LP: #141233).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * EAP peer method: EAP-PSK (RFC 4764)
 
3
 * Copyright (c) 2004-2007, Jouni Malinen <j@w1.fi>
 
4
 *
 
5
 * This program is free software; you can redistribute it and/or modify
 
6
 * it under the terms of the GNU General Public License version 2 as
 
7
 * published by the Free Software Foundation.
 
8
 *
 
9
 * Alternatively, this software may be distributed under the terms of BSD
 
10
 * license.
 
11
 *
 
12
 * See README and COPYING for more details.
 
13
 *
 
14
 * Note: EAP-PSK is an EAP authentication method and as such, completely
 
15
 * different from WPA-PSK. This file is not needed for WPA-PSK functionality.
 
16
 */
 
17
 
 
18
#include "includes.h"
 
19
 
 
20
#include "common.h"
 
21
#include "eap_i.h"
 
22
#include "config_ssid.h"
 
23
#include "md5.h"
 
24
#include "aes_wrap.h"
 
25
#include "eap_psk_common.h"
 
26
 
 
27
 
 
28
struct eap_psk_data {
 
29
        enum { PSK_INIT, PSK_MAC_SENT, PSK_DONE } state;
 
30
        u8 rand_p[EAP_PSK_RAND_LEN];
 
31
        u8 ak[EAP_PSK_AK_LEN], kdk[EAP_PSK_KDK_LEN], tek[EAP_PSK_TEK_LEN];
 
32
        u8 *id_s, *id_p;
 
33
        size_t id_s_len, id_p_len;
 
34
        u8 msk[EAP_MSK_LEN];
 
35
        u8 emsk[EAP_EMSK_LEN];
 
36
};
 
37
 
 
38
 
 
39
static void * eap_psk_init(struct eap_sm *sm)
 
40
{
 
41
        struct wpa_ssid *config = eap_get_config(sm);
 
42
        struct eap_psk_data *data;
 
43
 
 
44
        if (config == NULL || !config->eappsk) {
 
45
                wpa_printf(MSG_INFO, "EAP-PSK: pre-shared key not configured");
 
46
                return NULL;
 
47
        }
 
48
 
 
49
        data = os_zalloc(sizeof(*data));
 
50
        if (data == NULL)
 
51
                return NULL;
 
52
        eap_psk_key_setup(config->eappsk, data->ak, data->kdk);
 
53
        wpa_hexdump_key(MSG_DEBUG, "EAP-PSK: AK", data->ak, EAP_PSK_AK_LEN);
 
54
        wpa_hexdump_key(MSG_DEBUG, "EAP-PSK: KDK", data->kdk, EAP_PSK_KDK_LEN);
 
55
        data->state = PSK_INIT;
 
56
 
 
57
        if (config->nai) {
 
58
                data->id_p = os_malloc(config->nai_len);
 
59
                if (data->id_p)
 
60
                        os_memcpy(data->id_p, config->nai, config->nai_len);
 
61
                data->id_p_len = config->nai_len;
 
62
        }
 
63
        if (data->id_p == NULL) {
 
64
                wpa_printf(MSG_INFO, "EAP-PSK: could not get own identity");
 
65
                os_free(data);
 
66
                return NULL;
 
67
        }
 
68
 
 
69
        return data;
 
70
}
 
71
 
 
72
 
 
73
static void eap_psk_deinit(struct eap_sm *sm, void *priv)
 
74
{
 
75
        struct eap_psk_data *data = priv;
 
76
        os_free(data->id_s);
 
77
        os_free(data->id_p);
 
78
        os_free(data);
 
79
}
 
80
 
 
81
 
 
82
static u8 * eap_psk_process_1(struct eap_psk_data *data,
 
83
                              struct eap_method_ret *ret,
 
84
                              const u8 *reqData, size_t reqDataLen,
 
85
                              size_t *respDataLen)
 
86
{
 
87
        const struct eap_psk_hdr_1 *hdr1;
 
88
        struct eap_psk_hdr_2 *hdr2;
 
89
        u8 *resp, *buf, *pos;
 
90
        size_t buflen;
 
91
 
 
92
        wpa_printf(MSG_DEBUG, "EAP-PSK: in INIT state");
 
93
 
 
94
        hdr1 = (const struct eap_psk_hdr_1 *) reqData;
 
95
        if (reqDataLen < sizeof(*hdr1) ||
 
96
            be_to_host16(hdr1->length) < sizeof(*hdr1) ||
 
97
            be_to_host16(hdr1->length) > reqDataLen) {
 
98
                wpa_printf(MSG_INFO, "EAP-PSK: Invalid first message "
 
99
                           "length (%lu %d; expected %lu or more)",
 
100
                           (unsigned long) reqDataLen,
 
101
                           be_to_host16(hdr1->length),
 
102
                           (unsigned long) sizeof(*hdr1));
 
103
                ret->ignore = TRUE;
 
104
                return NULL;
 
105
        }
 
106
        wpa_printf(MSG_DEBUG, "EAP-PSK: Flags=0x%x", hdr1->flags);
 
107
        if (EAP_PSK_FLAGS_GET_T(hdr1->flags) != 0) {
 
108
                wpa_printf(MSG_INFO, "EAP-PSK: Unexpected T=%d (expected 0)",
 
109
                           EAP_PSK_FLAGS_GET_T(hdr1->flags));
 
110
                ret->methodState = METHOD_DONE;
 
111
                ret->decision = DECISION_FAIL;
 
112
                return NULL;
 
113
        }
 
114
        wpa_hexdump(MSG_DEBUG, "EAP-PSK: RAND_S", hdr1->rand_s,
 
115
                    EAP_PSK_RAND_LEN);
 
116
        os_free(data->id_s);
 
117
        data->id_s_len = be_to_host16(hdr1->length) - sizeof(*hdr1);
 
118
        data->id_s = os_malloc(data->id_s_len);
 
119
        if (data->id_s == NULL) {
 
120
                wpa_printf(MSG_ERROR, "EAP-PSK: Failed to allocate memory for "
 
121
                           "ID_S (len=%lu)", (unsigned long) data->id_s_len);
 
122
                ret->ignore = TRUE;
 
123
                return NULL;
 
124
        }
 
125
        os_memcpy(data->id_s, (u8 *) (hdr1 + 1), data->id_s_len);
 
126
        wpa_hexdump_ascii(MSG_DEBUG, "EAP-PSK: ID_S",
 
127
                          data->id_s, data->id_s_len);
 
128
 
 
129
        if (hostapd_get_rand(data->rand_p, EAP_PSK_RAND_LEN)) {
 
130
                wpa_printf(MSG_ERROR, "EAP-PSK: Failed to get random data");
 
131
                ret->ignore = TRUE;
 
132
                return NULL;
 
133
        }
 
134
 
 
135
        *respDataLen = sizeof(*hdr2) + data->id_p_len;
 
136
        resp = os_malloc(*respDataLen);
 
137
        if (resp == NULL)
 
138
                return NULL;
 
139
        hdr2 = (struct eap_psk_hdr_2 *) resp;
 
140
        hdr2->code = EAP_CODE_RESPONSE;
 
141
        hdr2->identifier = hdr1->identifier;
 
142
        hdr2->length = host_to_be16(*respDataLen);
 
143
        hdr2->type = EAP_TYPE_PSK;
 
144
        hdr2->flags = EAP_PSK_FLAGS_SET_T(1); /* T=1 */
 
145
        os_memcpy(hdr2->rand_s, hdr1->rand_s, EAP_PSK_RAND_LEN);
 
146
        os_memcpy(hdr2->rand_p, data->rand_p, EAP_PSK_RAND_LEN);
 
147
        os_memcpy((u8 *) (hdr2 + 1), data->id_p, data->id_p_len);
 
148
        /* MAC_P = OMAC1-AES-128(AK, ID_P||ID_S||RAND_S||RAND_P) */
 
149
        buflen = data->id_p_len + data->id_s_len + 2 * EAP_PSK_RAND_LEN;
 
150
        buf = os_malloc(buflen);
 
151
        if (buf == NULL) {
 
152
                os_free(resp);
 
153
                return NULL;
 
154
        }
 
155
        os_memcpy(buf, data->id_p, data->id_p_len);
 
156
        pos = buf + data->id_p_len;
 
157
        os_memcpy(pos, data->id_s, data->id_s_len);
 
158
        pos += data->id_s_len;
 
159
        os_memcpy(pos, hdr1->rand_s, EAP_PSK_RAND_LEN);
 
160
        pos += EAP_PSK_RAND_LEN;
 
161
        os_memcpy(pos, data->rand_p, EAP_PSK_RAND_LEN);
 
162
        omac1_aes_128(data->ak, buf, buflen, hdr2->mac_p);
 
163
        os_free(buf);
 
164
        wpa_hexdump(MSG_DEBUG, "EAP-PSK: RAND_P", hdr2->rand_p,
 
165
                    EAP_PSK_RAND_LEN);
 
166
        wpa_hexdump(MSG_DEBUG, "EAP-PSK: MAC_P", hdr2->mac_p, EAP_PSK_MAC_LEN);
 
167
        wpa_hexdump_ascii(MSG_DEBUG, "EAP-PSK: ID_P",
 
168
                          (u8 *) (hdr2 + 1), data->id_p_len);
 
169
 
 
170
        data->state = PSK_MAC_SENT;
 
171
 
 
172
        return resp;
 
173
}
 
174
 
 
175
 
 
176
static u8 * eap_psk_process_3(struct eap_psk_data *data,
 
177
                              struct eap_method_ret *ret,
 
178
                              const u8 *reqData, size_t reqDataLen,
 
179
                              size_t *respDataLen)
 
180
{
 
181
        const struct eap_psk_hdr_3 *hdr3;
 
182
        struct eap_psk_hdr_4 *hdr4;
 
183
        u8 *resp, *buf, *rpchannel, nonce[16], *decrypted;
 
184
        const u8 *pchannel, *tag, *msg;
 
185
        u8 mac[EAP_PSK_MAC_LEN];
 
186
        size_t buflen, left, data_len;
 
187
        int failed = 0;
 
188
 
 
189
        wpa_printf(MSG_DEBUG, "EAP-PSK: in MAC_SENT state");
 
190
 
 
191
        hdr3 = (const struct eap_psk_hdr_3 *) reqData;
 
192
        left = be_to_host16(hdr3->length);
 
193
        if (left < sizeof(*hdr3) || reqDataLen < left) {
 
194
                wpa_printf(MSG_INFO, "EAP-PSK: Invalid third message "
 
195
                           "length (%lu %d; expected %lu)",
 
196
                           (unsigned long) reqDataLen,
 
197
                           be_to_host16(hdr3->length),
 
198
                           (unsigned long) sizeof(*hdr3));
 
199
                ret->ignore = TRUE;
 
200
                return NULL;
 
201
        }
 
202
        left -= sizeof(*hdr3);
 
203
        pchannel = (const u8 *) (hdr3 + 1);
 
204
        wpa_printf(MSG_DEBUG, "EAP-PSK: Flags=0x%x", hdr3->flags);
 
205
        if (EAP_PSK_FLAGS_GET_T(hdr3->flags) != 2) {
 
206
                wpa_printf(MSG_INFO, "EAP-PSK: Unexpected T=%d (expected 2)",
 
207
                           EAP_PSK_FLAGS_GET_T(hdr3->flags));
 
208
                ret->methodState = METHOD_DONE;
 
209
                ret->decision = DECISION_FAIL;
 
210
                return NULL;
 
211
        }
 
212
        wpa_hexdump(MSG_DEBUG, "EAP-PSK: RAND_S", hdr3->rand_s,
 
213
                    EAP_PSK_RAND_LEN);
 
214
        wpa_hexdump(MSG_DEBUG, "EAP-PSK: MAC_S", hdr3->mac_s, EAP_PSK_MAC_LEN);
 
215
        wpa_hexdump(MSG_DEBUG, "EAP-PSK: PCHANNEL", pchannel, left);
 
216
 
 
217
        if (left < 4 + 16 + 1) {
 
218
                wpa_printf(MSG_INFO, "EAP-PSK: Too short PCHANNEL data in "
 
219
                           "third message (len=%lu, expected 21)",
 
220
                           (unsigned long) left);
 
221
                ret->ignore = TRUE;
 
222
                return NULL;
 
223
        }
 
224
 
 
225
        /* MAC_S = OMAC1-AES-128(AK, ID_S||RAND_P) */
 
226
        buflen = data->id_s_len + EAP_PSK_RAND_LEN;
 
227
        buf = os_malloc(buflen);
 
228
        if (buf == NULL)
 
229
                return NULL;
 
230
        os_memcpy(buf, data->id_s, data->id_s_len);
 
231
        os_memcpy(buf + data->id_s_len, data->rand_p, EAP_PSK_RAND_LEN);
 
232
        omac1_aes_128(data->ak, buf, buflen, mac);
 
233
        os_free(buf);
 
234
        if (os_memcmp(mac, hdr3->mac_s, EAP_PSK_MAC_LEN) != 0) {
 
235
                wpa_printf(MSG_WARNING, "EAP-PSK: Invalid MAC_S in third "
 
236
                           "message");
 
237
                ret->methodState = METHOD_DONE;
 
238
                ret->decision = DECISION_FAIL;
 
239
                return NULL;
 
240
        }
 
241
        wpa_printf(MSG_DEBUG, "EAP-PSK: MAC_S verified successfully");
 
242
 
 
243
        eap_psk_derive_keys(data->kdk, data->rand_p, data->tek,
 
244
                            data->msk, data->emsk);
 
245
        wpa_hexdump_key(MSG_DEBUG, "EAP-PSK: TEK", data->tek, EAP_PSK_TEK_LEN);
 
246
        wpa_hexdump_key(MSG_DEBUG, "EAP-PSK: MSK", data->msk, EAP_MSK_LEN);
 
247
        wpa_hexdump_key(MSG_DEBUG, "EAP-PSK: EMSK", data->emsk, EAP_EMSK_LEN);
 
248
 
 
249
        os_memset(nonce, 0, 12);
 
250
        os_memcpy(nonce + 12, pchannel, 4);
 
251
        pchannel += 4;
 
252
        left -= 4;
 
253
 
 
254
        tag = pchannel;
 
255
        pchannel += 16;
 
256
        left -= 16;
 
257
 
 
258
        msg = pchannel;
 
259
 
 
260
        wpa_hexdump(MSG_MSGDUMP, "EAP-PSK: PCHANNEL - nonce",
 
261
                    nonce, sizeof(nonce));
 
262
        wpa_hexdump(MSG_MSGDUMP, "EAP-PSK: PCHANNEL - hdr", reqData, 5);
 
263
        wpa_hexdump(MSG_MSGDUMP, "EAP-PSK: PCHANNEL - cipher msg", msg, left);
 
264
 
 
265
        decrypted = os_malloc(left);
 
266
        if (decrypted == NULL) {
 
267
                ret->methodState = METHOD_DONE;
 
268
                ret->decision = DECISION_FAIL;
 
269
                return NULL;
 
270
        }
 
271
        os_memcpy(decrypted, msg, left);
 
272
 
 
273
        if (aes_128_eax_decrypt(data->tek, nonce, sizeof(nonce),
 
274
                                reqData, 22, decrypted, left, tag)) {
 
275
                wpa_printf(MSG_WARNING, "EAP-PSK: PCHANNEL decryption failed");
 
276
                os_free(decrypted);
 
277
                return NULL;
 
278
        }
 
279
        wpa_hexdump(MSG_DEBUG, "EAP-PSK: Decrypted PCHANNEL message",
 
280
                    decrypted, left);
 
281
 
 
282
        /* Verify R flag */
 
283
        switch (decrypted[0] >> 6) {
 
284
        case EAP_PSK_R_FLAG_CONT:
 
285
                wpa_printf(MSG_DEBUG, "EAP-PSK: R flag - CONT - unsupported");
 
286
                failed = 1;
 
287
                break;
 
288
        case EAP_PSK_R_FLAG_DONE_SUCCESS:
 
289
                wpa_printf(MSG_DEBUG, "EAP-PSK: R flag - DONE_SUCCESS");
 
290
                break;
 
291
        case EAP_PSK_R_FLAG_DONE_FAILURE:
 
292
                wpa_printf(MSG_DEBUG, "EAP-PSK: R flag - DONE_FAILURE");
 
293
                wpa_printf(MSG_INFO, "EAP-PSK: Authentication server rejected "
 
294
                           "authentication");
 
295
                failed = 1;
 
296
                break;
 
297
        }
 
298
 
 
299
        *respDataLen = sizeof(*hdr4) + 4 + 16 + 1;
 
300
        resp = os_malloc(*respDataLen + 1);
 
301
        if (resp == NULL) {
 
302
                os_free(decrypted);
 
303
                return NULL;
 
304
        }
 
305
        hdr4 = (struct eap_psk_hdr_4 *) resp;
 
306
        hdr4->code = EAP_CODE_RESPONSE;
 
307
        hdr4->identifier = hdr3->identifier;
 
308
        hdr4->length = host_to_be16(*respDataLen);
 
309
        hdr4->type = EAP_TYPE_PSK;
 
310
        hdr4->flags = EAP_PSK_FLAGS_SET_T(3); /* T=3 */
 
311
        os_memcpy(hdr4->rand_s, hdr3->rand_s, EAP_PSK_RAND_LEN);
 
312
        rpchannel = (u8 *) (hdr4 + 1);
 
313
 
 
314
        /* nonce++ */
 
315
        inc_byte_array(nonce, sizeof(nonce));
 
316
        os_memcpy(rpchannel, nonce + 12, 4);
 
317
 
 
318
        data_len = 1;
 
319
        if (decrypted[0] & EAP_PSK_E_FLAG) {
 
320
                wpa_printf(MSG_DEBUG, "EAP-PSK: Unsupported E (Ext) flag");
 
321
                failed = 1;
 
322
                rpchannel[4 + 16] = (EAP_PSK_R_FLAG_DONE_FAILURE << 6) |
 
323
                        EAP_PSK_E_FLAG;
 
324
                if (left > 1) {
 
325
                        /* Add empty EXT_Payload with same EXT_Type */
 
326
                        (*respDataLen)++;
 
327
                        hdr4->length = host_to_be16(*respDataLen);
 
328
                        rpchannel[4 + 16 + 1] = decrypted[1];
 
329
                        data_len++;
 
330
                }
 
331
        } else if (failed)
 
332
                rpchannel[4 + 16] = EAP_PSK_R_FLAG_DONE_FAILURE << 6;
 
333
        else
 
334
                rpchannel[4 + 16] = EAP_PSK_R_FLAG_DONE_SUCCESS << 6;
 
335
 
 
336
        wpa_hexdump(MSG_DEBUG, "EAP-PSK: reply message (plaintext)",
 
337
                    rpchannel + 4 + 16, data_len);
 
338
        aes_128_eax_encrypt(data->tek, nonce, sizeof(nonce), resp, 22,
 
339
                            rpchannel + 4 + 16, data_len, rpchannel + 4);
 
340
        wpa_hexdump(MSG_DEBUG, "EAP-PSK: reply message (PCHANNEL)",
 
341
                    rpchannel, 4 + 16 + data_len);
 
342
 
 
343
        wpa_printf(MSG_DEBUG, "EAP-PSK: Completed %ssuccessfully",
 
344
                   failed ? "un" : "");
 
345
        data->state = PSK_DONE;
 
346
        ret->methodState = METHOD_DONE;
 
347
        ret->decision = failed ? DECISION_FAIL : DECISION_UNCOND_SUCC;
 
348
 
 
349
        os_free(decrypted);
 
350
 
 
351
        return resp;
 
352
}
 
353
 
 
354
 
 
355
static u8 * eap_psk_process(struct eap_sm *sm, void *priv,
 
356
                            struct eap_method_ret *ret,
 
357
                            const u8 *reqData, size_t reqDataLen,
 
358
                            size_t *respDataLen)
 
359
{
 
360
        struct eap_psk_data *data = priv;
 
361
        const u8 *pos;
 
362
        u8 *resp = NULL;
 
363
        size_t len;
 
364
 
 
365
        pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_PSK,
 
366
                               reqData, reqDataLen, &len);
 
367
        if (pos == NULL) {
 
368
                ret->ignore = TRUE;
 
369
                return NULL;
 
370
        }
 
371
        len += sizeof(struct eap_hdr) + 1;
 
372
 
 
373
        ret->ignore = FALSE;
 
374
        ret->methodState = METHOD_MAY_CONT;
 
375
        ret->decision = DECISION_FAIL;
 
376
        ret->allowNotifications = TRUE;
 
377
 
 
378
        switch (data->state) {
 
379
        case PSK_INIT:
 
380
                resp = eap_psk_process_1(data, ret, reqData, len,
 
381
                                         respDataLen);
 
382
                break;
 
383
        case PSK_MAC_SENT:
 
384
                resp = eap_psk_process_3(data, ret, reqData, len,
 
385
                                         respDataLen);
 
386
                break;
 
387
        case PSK_DONE:
 
388
                wpa_printf(MSG_DEBUG, "EAP-PSK: in DONE state - ignore "
 
389
                           "unexpected message");
 
390
                ret->ignore = TRUE;
 
391
                return NULL;
 
392
        }
 
393
 
 
394
        if (ret->methodState == METHOD_DONE) {
 
395
                ret->allowNotifications = FALSE;
 
396
        }
 
397
 
 
398
        return resp;
 
399
}
 
400
 
 
401
 
 
402
static Boolean eap_psk_isKeyAvailable(struct eap_sm *sm, void *priv)
 
403
{
 
404
        struct eap_psk_data *data = priv;
 
405
        return data->state == PSK_DONE;
 
406
}
 
407
 
 
408
 
 
409
static u8 * eap_psk_getKey(struct eap_sm *sm, void *priv, size_t *len)
 
410
{
 
411
        struct eap_psk_data *data = priv;
 
412
        u8 *key;
 
413
 
 
414
        if (data->state != PSK_DONE)
 
415
                return NULL;
 
416
 
 
417
        key = os_malloc(EAP_MSK_LEN);
 
418
        if (key == NULL)
 
419
                return NULL;
 
420
 
 
421
        *len = EAP_MSK_LEN;
 
422
        os_memcpy(key, data->msk, EAP_MSK_LEN);
 
423
 
 
424
        return key;
 
425
}
 
426
 
 
427
 
 
428
static u8 * eap_psk_get_emsk(struct eap_sm *sm, void *priv, size_t *len)
 
429
{
 
430
        struct eap_psk_data *data = priv;
 
431
        u8 *key;
 
432
 
 
433
        if (data->state != PSK_DONE)
 
434
                return NULL;
 
435
 
 
436
        key = os_malloc(EAP_EMSK_LEN);
 
437
        if (key == NULL)
 
438
                return NULL;
 
439
 
 
440
        *len = EAP_EMSK_LEN;
 
441
        os_memcpy(key, data->emsk, EAP_EMSK_LEN);
 
442
 
 
443
        return key;
 
444
}
 
445
 
 
446
 
 
447
int eap_peer_psk_register(void)
 
448
{
 
449
        struct eap_method *eap;
 
450
        int ret;
 
451
 
 
452
        eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION,
 
453
                                    EAP_VENDOR_IETF, EAP_TYPE_PSK, "PSK");
 
454
        if (eap == NULL)
 
455
                return -1;
 
456
 
 
457
        eap->init = eap_psk_init;
 
458
        eap->deinit = eap_psk_deinit;
 
459
        eap->process = eap_psk_process;
 
460
        eap->isKeyAvailable = eap_psk_isKeyAvailable;
 
461
        eap->getKey = eap_psk_getKey;
 
462
        eap->get_emsk = eap_psk_get_emsk;
 
463
 
 
464
        ret = eap_peer_method_register(eap);
 
465
        if (ret)
 
466
                eap_peer_method_free(eap);
 
467
        return ret;
 
468
}