Updated through tag hostap_2_5 from git://w1.fi/hostap.git
[mech_eap.git] / libeap / src / wps / wps_attr_parse.c
1 /*
2  * Wi-Fi Protected Setup - attribute parsing
3  * Copyright (c) 2008, Jouni Malinen <j@w1.fi>
4  *
5  * This software may be distributed under the terms of the BSD license.
6  * See README for more details.
7  */
8
9 #include "includes.h"
10
11 #include "common.h"
12 #include "wps_defs.h"
13 #include "wps_attr_parse.h"
14
15 #ifndef CONFIG_WPS_STRICT
16 #define WPS_WORKAROUNDS
17 #endif /* CONFIG_WPS_STRICT */
18
19
20 static int wps_set_vendor_ext_wfa_subelem(struct wps_parse_attr *attr,
21                                           u8 id, u8 len, const u8 *pos)
22 {
23         wpa_printf(MSG_EXCESSIVE, "WPS: WFA subelement id=%u len=%u",
24                    id, len);
25         switch (id) {
26         case WFA_ELEM_VERSION2:
27                 if (len != 1) {
28                         wpa_printf(MSG_DEBUG, "WPS: Invalid Version2 length "
29                                    "%u", len);
30                         return -1;
31                 }
32                 attr->version2 = pos;
33                 break;
34         case WFA_ELEM_AUTHORIZEDMACS:
35                 attr->authorized_macs = pos;
36                 attr->authorized_macs_len = len;
37                 break;
38         case WFA_ELEM_NETWORK_KEY_SHAREABLE:
39                 if (len != 1) {
40                         wpa_printf(MSG_DEBUG, "WPS: Invalid Network Key "
41                                    "Shareable length %u", len);
42                         return -1;
43                 }
44                 attr->network_key_shareable = pos;
45                 break;
46         case WFA_ELEM_REQUEST_TO_ENROLL:
47                 if (len != 1) {
48                         wpa_printf(MSG_DEBUG, "WPS: Invalid Request to Enroll "
49                                    "length %u", len);
50                         return -1;
51                 }
52                 attr->request_to_enroll = pos;
53                 break;
54         case WFA_ELEM_SETTINGS_DELAY_TIME:
55                 if (len != 1) {
56                         wpa_printf(MSG_DEBUG, "WPS: Invalid Settings Delay "
57                                    "Time length %u", len);
58                         return -1;
59                 }
60                 attr->settings_delay_time = pos;
61                 break;
62         case WFA_ELEM_REGISTRAR_CONFIGURATION_METHODS:
63                 if (len != 2) {
64                         wpa_printf(MSG_DEBUG, "WPS: Invalid Registrar Configuration Methods length %u",
65                                    len);
66                         return -1;
67                 }
68                 attr->registrar_configuration_methods = pos;
69                 break;
70         default:
71                 wpa_printf(MSG_MSGDUMP, "WPS: Skipped unknown WFA Vendor "
72                            "Extension subelement %u", id);
73                 break;
74         }
75
76         return 0;
77 }
78
79
80 static int wps_parse_vendor_ext_wfa(struct wps_parse_attr *attr, const u8 *pos,
81                                     u16 len)
82 {
83         const u8 *end = pos + len;
84         u8 id, elen;
85
86         while (pos + 2 <= end) {
87                 id = *pos++;
88                 elen = *pos++;
89                 if (pos + elen > end)
90                         break;
91                 if (wps_set_vendor_ext_wfa_subelem(attr, id, elen, pos) < 0)
92                         return -1;
93                 pos += elen;
94         }
95
96         return 0;
97 }
98
99
100 static int wps_parse_vendor_ext(struct wps_parse_attr *attr, const u8 *pos,
101                                 u16 len)
102 {
103         u32 vendor_id;
104
105         if (len < 3) {
106                 wpa_printf(MSG_DEBUG, "WPS: Skip invalid Vendor Extension");
107                 return 0;
108         }
109
110         vendor_id = WPA_GET_BE24(pos);
111         switch (vendor_id) {
112         case WPS_VENDOR_ID_WFA:
113                 return wps_parse_vendor_ext_wfa(attr, pos + 3, len - 3);
114         }
115
116         /* Handle unknown vendor extensions */
117
118         wpa_printf(MSG_MSGDUMP, "WPS: Unknown Vendor Extension (Vendor ID %u)",
119                    vendor_id);
120
121         if (len > WPS_MAX_VENDOR_EXT_LEN) {
122                 wpa_printf(MSG_DEBUG, "WPS: Too long Vendor Extension (%u)",
123                            len);
124                 return -1;
125         }
126
127         if (attr->num_vendor_ext >= MAX_WPS_PARSE_VENDOR_EXT) {
128                 wpa_printf(MSG_DEBUG, "WPS: Skipped Vendor Extension "
129                            "attribute (max %d vendor extensions)",
130                            MAX_WPS_PARSE_VENDOR_EXT);
131                 return -1;
132         }
133         attr->vendor_ext[attr->num_vendor_ext] = pos;
134         attr->vendor_ext_len[attr->num_vendor_ext] = len;
135         attr->num_vendor_ext++;
136
137         return 0;
138 }
139
140
141 static int wps_set_attr(struct wps_parse_attr *attr, u16 type,
142                         const u8 *pos, u16 len)
143 {
144         switch (type) {
145         case ATTR_VERSION:
146                 if (len != 1) {
147                         wpa_printf(MSG_DEBUG, "WPS: Invalid Version length %u",
148                                    len);
149                         return -1;
150                 }
151                 attr->version = pos;
152                 break;
153         case ATTR_MSG_TYPE:
154                 if (len != 1) {
155                         wpa_printf(MSG_DEBUG, "WPS: Invalid Message Type "
156                                    "length %u", len);
157                         return -1;
158                 }
159                 attr->msg_type = pos;
160                 break;
161         case ATTR_ENROLLEE_NONCE:
162                 if (len != WPS_NONCE_LEN) {
163                         wpa_printf(MSG_DEBUG, "WPS: Invalid Enrollee Nonce "
164                                    "length %u", len);
165                         return -1;
166                 }
167                 attr->enrollee_nonce = pos;
168                 break;
169         case ATTR_REGISTRAR_NONCE:
170                 if (len != WPS_NONCE_LEN) {
171                         wpa_printf(MSG_DEBUG, "WPS: Invalid Registrar Nonce "
172                                    "length %u", len);
173                         return -1;
174                 }
175                 attr->registrar_nonce = pos;
176                 break;
177         case ATTR_UUID_E:
178                 if (len != WPS_UUID_LEN) {
179                         wpa_printf(MSG_DEBUG, "WPS: Invalid UUID-E length %u",
180                                    len);
181                         return -1;
182                 }
183                 attr->uuid_e = pos;
184                 break;
185         case ATTR_UUID_R:
186                 if (len != WPS_UUID_LEN) {
187                         wpa_printf(MSG_DEBUG, "WPS: Invalid UUID-R length %u",
188                                    len);
189                         return -1;
190                 }
191                 attr->uuid_r = pos;
192                 break;
193         case ATTR_AUTH_TYPE_FLAGS:
194                 if (len != 2) {
195                         wpa_printf(MSG_DEBUG, "WPS: Invalid Authentication "
196                                    "Type Flags length %u", len);
197                         return -1;
198                 }
199                 attr->auth_type_flags = pos;
200                 break;
201         case ATTR_ENCR_TYPE_FLAGS:
202                 if (len != 2) {
203                         wpa_printf(MSG_DEBUG, "WPS: Invalid Encryption Type "
204                                    "Flags length %u", len);
205                         return -1;
206                 }
207                 attr->encr_type_flags = pos;
208                 break;
209         case ATTR_CONN_TYPE_FLAGS:
210                 if (len != 1) {
211                         wpa_printf(MSG_DEBUG, "WPS: Invalid Connection Type "
212                                    "Flags length %u", len);
213                         return -1;
214                 }
215                 attr->conn_type_flags = pos;
216                 break;
217         case ATTR_CONFIG_METHODS:
218                 if (len != 2) {
219                         wpa_printf(MSG_DEBUG, "WPS: Invalid Config Methods "
220                                    "length %u", len);
221                         return -1;
222                 }
223                 attr->config_methods = pos;
224                 break;
225         case ATTR_SELECTED_REGISTRAR_CONFIG_METHODS:
226                 if (len != 2) {
227                         wpa_printf(MSG_DEBUG, "WPS: Invalid Selected "
228                                    "Registrar Config Methods length %u", len);
229                         return -1;
230                 }
231                 attr->sel_reg_config_methods = pos;
232                 break;
233         case ATTR_PRIMARY_DEV_TYPE:
234                 if (len != WPS_DEV_TYPE_LEN) {
235                         wpa_printf(MSG_DEBUG, "WPS: Invalid Primary Device "
236                                    "Type length %u", len);
237                         return -1;
238                 }
239                 attr->primary_dev_type = pos;
240                 break;
241         case ATTR_RF_BANDS:
242                 if (len != 1) {
243                         wpa_printf(MSG_DEBUG, "WPS: Invalid RF Bands length "
244                                    "%u", len);
245                         return -1;
246                 }
247                 attr->rf_bands = pos;
248                 break;
249         case ATTR_ASSOC_STATE:
250                 if (len != 2) {
251                         wpa_printf(MSG_DEBUG, "WPS: Invalid Association State "
252                                    "length %u", len);
253                         return -1;
254                 }
255                 attr->assoc_state = pos;
256                 break;
257         case ATTR_CONFIG_ERROR:
258                 if (len != 2) {
259                         wpa_printf(MSG_DEBUG, "WPS: Invalid Configuration "
260                                    "Error length %u", len);
261                         return -1;
262                 }
263                 attr->config_error = pos;
264                 break;
265         case ATTR_DEV_PASSWORD_ID:
266                 if (len != 2) {
267                         wpa_printf(MSG_DEBUG, "WPS: Invalid Device Password "
268                                    "ID length %u", len);
269                         return -1;
270                 }
271                 attr->dev_password_id = pos;
272                 break;
273         case ATTR_OOB_DEVICE_PASSWORD:
274                 if (len < WPS_OOB_PUBKEY_HASH_LEN + 2 ||
275                     len > WPS_OOB_PUBKEY_HASH_LEN + 2 +
276                     WPS_OOB_DEVICE_PASSWORD_LEN ||
277                     (len < WPS_OOB_PUBKEY_HASH_LEN + 2 +
278                      WPS_OOB_DEVICE_PASSWORD_MIN_LEN &&
279                      WPA_GET_BE16(pos + WPS_OOB_PUBKEY_HASH_LEN) !=
280                      DEV_PW_NFC_CONNECTION_HANDOVER)) {
281                         wpa_printf(MSG_DEBUG, "WPS: Invalid OOB Device "
282                                    "Password length %u", len);
283                         return -1;
284                 }
285                 attr->oob_dev_password = pos;
286                 attr->oob_dev_password_len = len;
287                 break;
288         case ATTR_OS_VERSION:
289                 if (len != 4) {
290                         wpa_printf(MSG_DEBUG, "WPS: Invalid OS Version length "
291                                    "%u", len);
292                         return -1;
293                 }
294                 attr->os_version = pos;
295                 break;
296         case ATTR_WPS_STATE:
297                 if (len != 1) {
298                         wpa_printf(MSG_DEBUG, "WPS: Invalid Wi-Fi Protected "
299                                    "Setup State length %u", len);
300                         return -1;
301                 }
302                 attr->wps_state = pos;
303                 break;
304         case ATTR_AUTHENTICATOR:
305                 if (len != WPS_AUTHENTICATOR_LEN) {
306                         wpa_printf(MSG_DEBUG, "WPS: Invalid Authenticator "
307                                    "length %u", len);
308                         return -1;
309                 }
310                 attr->authenticator = pos;
311                 break;
312         case ATTR_R_HASH1:
313                 if (len != WPS_HASH_LEN) {
314                         wpa_printf(MSG_DEBUG, "WPS: Invalid R-Hash1 length %u",
315                                    len);
316                         return -1;
317                 }
318                 attr->r_hash1 = pos;
319                 break;
320         case ATTR_R_HASH2:
321                 if (len != WPS_HASH_LEN) {
322                         wpa_printf(MSG_DEBUG, "WPS: Invalid R-Hash2 length %u",
323                                    len);
324                         return -1;
325                 }
326                 attr->r_hash2 = pos;
327                 break;
328         case ATTR_E_HASH1:
329                 if (len != WPS_HASH_LEN) {
330                         wpa_printf(MSG_DEBUG, "WPS: Invalid E-Hash1 length %u",
331                                    len);
332                         return -1;
333                 }
334                 attr->e_hash1 = pos;
335                 break;
336         case ATTR_E_HASH2:
337                 if (len != WPS_HASH_LEN) {
338                         wpa_printf(MSG_DEBUG, "WPS: Invalid E-Hash2 length %u",
339                                    len);
340                         return -1;
341                 }
342                 attr->e_hash2 = pos;
343                 break;
344         case ATTR_R_SNONCE1:
345                 if (len != WPS_SECRET_NONCE_LEN) {
346                         wpa_printf(MSG_DEBUG, "WPS: Invalid R-SNonce1 length "
347                                    "%u", len);
348                         return -1;
349                 }
350                 attr->r_snonce1 = pos;
351                 break;
352         case ATTR_R_SNONCE2:
353                 if (len != WPS_SECRET_NONCE_LEN) {
354                         wpa_printf(MSG_DEBUG, "WPS: Invalid R-SNonce2 length "
355                                    "%u", len);
356                         return -1;
357                 }
358                 attr->r_snonce2 = pos;
359                 break;
360         case ATTR_E_SNONCE1:
361                 if (len != WPS_SECRET_NONCE_LEN) {
362                         wpa_printf(MSG_DEBUG, "WPS: Invalid E-SNonce1 length "
363                                    "%u", len);
364                         return -1;
365                 }
366                 attr->e_snonce1 = pos;
367                 break;
368         case ATTR_E_SNONCE2:
369                 if (len != WPS_SECRET_NONCE_LEN) {
370                         wpa_printf(MSG_DEBUG, "WPS: Invalid E-SNonce2 length "
371                                    "%u", len);
372                         return -1;
373                 }
374                 attr->e_snonce2 = pos;
375                 break;
376         case ATTR_KEY_WRAP_AUTH:
377                 if (len != WPS_KWA_LEN) {
378                         wpa_printf(MSG_DEBUG, "WPS: Invalid Key Wrap "
379                                    "Authenticator length %u", len);
380                         return -1;
381                 }
382                 attr->key_wrap_auth = pos;
383                 break;
384         case ATTR_AUTH_TYPE:
385                 if (len != 2) {
386                         wpa_printf(MSG_DEBUG, "WPS: Invalid Authentication "
387                                    "Type length %u", len);
388                         return -1;
389                 }
390                 attr->auth_type = pos;
391                 break;
392         case ATTR_ENCR_TYPE:
393                 if (len != 2) {
394                         wpa_printf(MSG_DEBUG, "WPS: Invalid Encryption "
395                                    "Type length %u", len);
396                         return -1;
397                 }
398                 attr->encr_type = pos;
399                 break;
400         case ATTR_NETWORK_INDEX:
401                 if (len != 1) {
402                         wpa_printf(MSG_DEBUG, "WPS: Invalid Network Index "
403                                    "length %u", len);
404                         return -1;
405                 }
406                 attr->network_idx = pos;
407                 break;
408         case ATTR_NETWORK_KEY_INDEX:
409                 if (len != 1) {
410                         wpa_printf(MSG_DEBUG, "WPS: Invalid Network Key Index "
411                                    "length %u", len);
412                         return -1;
413                 }
414                 attr->network_key_idx = pos;
415                 break;
416         case ATTR_MAC_ADDR:
417                 if (len != ETH_ALEN) {
418                         wpa_printf(MSG_DEBUG, "WPS: Invalid MAC Address "
419                                    "length %u", len);
420                         return -1;
421                 }
422                 attr->mac_addr = pos;
423                 break;
424         case ATTR_SELECTED_REGISTRAR:
425                 if (len != 1) {
426                         wpa_printf(MSG_DEBUG, "WPS: Invalid Selected Registrar"
427                                    " length %u", len);
428                         return -1;
429                 }
430                 attr->selected_registrar = pos;
431                 break;
432         case ATTR_REQUEST_TYPE:
433                 if (len != 1) {
434                         wpa_printf(MSG_DEBUG, "WPS: Invalid Request Type "
435                                    "length %u", len);
436                         return -1;
437                 }
438                 attr->request_type = pos;
439                 break;
440         case ATTR_RESPONSE_TYPE:
441                 if (len != 1) {
442                         wpa_printf(MSG_DEBUG, "WPS: Invalid Response Type "
443                                    "length %u", len);
444                         return -1;
445                 }
446                 attr->response_type = pos;
447                 break;
448         case ATTR_MANUFACTURER:
449                 attr->manufacturer = pos;
450                 if (len > WPS_MANUFACTURER_MAX_LEN)
451                         attr->manufacturer_len = WPS_MANUFACTURER_MAX_LEN;
452                 else
453                         attr->manufacturer_len = len;
454                 break;
455         case ATTR_MODEL_NAME:
456                 attr->model_name = pos;
457                 if (len > WPS_MODEL_NAME_MAX_LEN)
458                         attr->model_name_len = WPS_MODEL_NAME_MAX_LEN;
459                 else
460                         attr->model_name_len = len;
461                 break;
462         case ATTR_MODEL_NUMBER:
463                 attr->model_number = pos;
464                 if (len > WPS_MODEL_NUMBER_MAX_LEN)
465                         attr->model_number_len = WPS_MODEL_NUMBER_MAX_LEN;
466                 else
467                         attr->model_number_len = len;
468                 break;
469         case ATTR_SERIAL_NUMBER:
470                 attr->serial_number = pos;
471                 if (len > WPS_SERIAL_NUMBER_MAX_LEN)
472                         attr->serial_number_len = WPS_SERIAL_NUMBER_MAX_LEN;
473                 else
474                         attr->serial_number_len = len;
475                 break;
476         case ATTR_DEV_NAME:
477                 if (len > WPS_DEV_NAME_MAX_LEN) {
478                         wpa_printf(MSG_DEBUG,
479                                    "WPS: Ignore too long Device Name (len=%u)",
480                                    len);
481                         break;
482                 }
483                 attr->dev_name = pos;
484                 attr->dev_name_len = len;
485                 break;
486         case ATTR_PUBLIC_KEY:
487                 /*
488                  * The Public Key attribute is supposed to be exactly 192 bytes
489                  * in length. Allow couple of bytes shorter one to try to
490                  * interoperate with implementations that do not use proper
491                  * zero-padding.
492                  */
493                 if (len < 190 || len > 192) {
494                         wpa_printf(MSG_DEBUG,
495                                    "WPS: Ignore Public Key with unexpected length %u",
496                                    len);
497                         break;
498                 }
499                 attr->public_key = pos;
500                 attr->public_key_len = len;
501                 break;
502         case ATTR_ENCR_SETTINGS:
503                 attr->encr_settings = pos;
504                 attr->encr_settings_len = len;
505                 break;
506         case ATTR_CRED:
507                 if (attr->num_cred >= MAX_CRED_COUNT) {
508                         wpa_printf(MSG_DEBUG, "WPS: Skipped Credential "
509                                    "attribute (max %d credentials)",
510                                    MAX_CRED_COUNT);
511                         break;
512                 }
513                 attr->cred[attr->num_cred] = pos;
514                 attr->cred_len[attr->num_cred] = len;
515                 attr->num_cred++;
516                 break;
517         case ATTR_SSID:
518                 if (len > SSID_MAX_LEN) {
519                         wpa_printf(MSG_DEBUG,
520                                    "WPS: Ignore too long SSID (len=%u)", len);
521                         break;
522                 }
523                 attr->ssid = pos;
524                 attr->ssid_len = len;
525                 break;
526         case ATTR_NETWORK_KEY:
527                 attr->network_key = pos;
528                 attr->network_key_len = len;
529                 break;
530         case ATTR_AP_SETUP_LOCKED:
531                 if (len != 1) {
532                         wpa_printf(MSG_DEBUG, "WPS: Invalid AP Setup Locked "
533                                    "length %u", len);
534                         return -1;
535                 }
536                 attr->ap_setup_locked = pos;
537                 break;
538         case ATTR_REQUESTED_DEV_TYPE:
539                 if (len != WPS_DEV_TYPE_LEN) {
540                         wpa_printf(MSG_DEBUG, "WPS: Invalid Requested Device "
541                                    "Type length %u", len);
542                         return -1;
543                 }
544                 if (attr->num_req_dev_type >= MAX_REQ_DEV_TYPE_COUNT) {
545                         wpa_printf(MSG_DEBUG, "WPS: Skipped Requested Device "
546                                    "Type attribute (max %u types)",
547                                    MAX_REQ_DEV_TYPE_COUNT);
548                         break;
549                 }
550                 attr->req_dev_type[attr->num_req_dev_type] = pos;
551                 attr->num_req_dev_type++;
552                 break;
553         case ATTR_SECONDARY_DEV_TYPE_LIST:
554                 if (len > WPS_SEC_DEV_TYPE_MAX_LEN ||
555                     (len % WPS_DEV_TYPE_LEN) > 0) {
556                         wpa_printf(MSG_DEBUG, "WPS: Invalid Secondary Device "
557                                    "Type length %u", len);
558                         return -1;
559                 }
560                 attr->sec_dev_type_list = pos;
561                 attr->sec_dev_type_list_len = len;
562                 break;
563         case ATTR_VENDOR_EXT:
564                 if (wps_parse_vendor_ext(attr, pos, len) < 0)
565                         return -1;
566                 break;
567         case ATTR_AP_CHANNEL:
568                 if (len != 2) {
569                         wpa_printf(MSG_DEBUG, "WPS: Invalid AP Channel "
570                                    "length %u", len);
571                         return -1;
572                 }
573                 attr->ap_channel = pos;
574                 break;
575         default:
576                 wpa_printf(MSG_DEBUG, "WPS: Unsupported attribute type 0x%x "
577                            "len=%u", type, len);
578                 break;
579         }
580
581         return 0;
582 }
583
584
585 int wps_parse_msg(const struct wpabuf *msg, struct wps_parse_attr *attr)
586 {
587         const u8 *pos, *end;
588         u16 type, len;
589 #ifdef WPS_WORKAROUNDS
590         u16 prev_type = 0;
591 #endif /* WPS_WORKAROUNDS */
592
593         os_memset(attr, 0, sizeof(*attr));
594         pos = wpabuf_head(msg);
595         end = pos + wpabuf_len(msg);
596
597         while (pos < end) {
598                 if (end - pos < 4) {
599                         wpa_printf(MSG_DEBUG, "WPS: Invalid message - "
600                                    "%lu bytes remaining",
601                                    (unsigned long) (end - pos));
602                         return -1;
603                 }
604
605                 type = WPA_GET_BE16(pos);
606                 pos += 2;
607                 len = WPA_GET_BE16(pos);
608                 pos += 2;
609                 wpa_printf(MSG_EXCESSIVE, "WPS: attr type=0x%x len=%u",
610                            type, len);
611                 if (len > end - pos) {
612                         wpa_printf(MSG_DEBUG, "WPS: Attribute overflow");
613                         wpa_hexdump_buf(MSG_MSGDUMP, "WPS: Message data", msg);
614 #ifdef WPS_WORKAROUNDS
615                         /*
616                          * Some deployed APs seem to have a bug in encoding of
617                          * Network Key attribute in the Credential attribute
618                          * where they add an extra octet after the Network Key
619                          * attribute at least when open network is being
620                          * provisioned.
621                          */
622                         if ((type & 0xff00) != 0x1000 &&
623                             prev_type == ATTR_NETWORK_KEY) {
624                                 wpa_printf(MSG_DEBUG, "WPS: Workaround - try "
625                                            "to skip unexpected octet after "
626                                            "Network Key");
627                                 pos -= 3;
628                                 continue;
629                         }
630 #endif /* WPS_WORKAROUNDS */
631                         return -1;
632                 }
633
634 #ifdef WPS_WORKAROUNDS
635                 if (type == 0 && len == 0) {
636                         /*
637                          * Mac OS X 10.6 seems to be adding 0x00 padding to the
638                          * end of M1. Skip those to avoid interop issues.
639                          */
640                         int i;
641                         for (i = 0; i < end - pos; i++) {
642                                 if (pos[i])
643                                         break;
644                         }
645                         if (i == end - pos) {
646                                 wpa_printf(MSG_DEBUG, "WPS: Workaround - skip "
647                                            "unexpected message padding");
648                                 break;
649                         }
650                 }
651 #endif /* WPS_WORKAROUNDS */
652
653                 if (wps_set_attr(attr, type, pos, len) < 0)
654                         return -1;
655
656 #ifdef WPS_WORKAROUNDS
657                 prev_type = type;
658 #endif /* WPS_WORKAROUNDS */
659                 pos += len;
660         }
661
662         return 0;
663 }