]>
Commit | Line | Data |
---|---|---|
9de3ca7e | 1 | /* |
24286d93 | 2 | * aim_rxhandlers.c |
3 | * | |
4 | * This file contains most all of the incoming packet handlers, along | |
5 | * with aim_rxdispatch(), the Rx dispatcher. Queue/list management is | |
6 | * actually done in aim_rxqueue.c. | |
7 | * | |
9de3ca7e | 8 | */ |
9 | ||
24286d93 | 10 | #include <aim.h> |
9de3ca7e | 11 | |
24286d93 | 12 | /* |
13 | * Bleck functions get called when there's no non-bleck functions | |
14 | * around to cleanup the mess... | |
15 | */ | |
9de3ca7e | 16 | int bleck(struct command_rx_struct *workingPtr, ...) |
17 | { | |
18 | u_short family; | |
19 | u_short subtype; | |
20 | family = (workingPtr->data[0] << 8) + workingPtr->data[1]; | |
21 | subtype = (workingPtr->data[2] << 8) + workingPtr->data[3]; | |
24286d93 | 22 | printf("bleck: null handler for %04x/%04x\n", family, subtype); |
9de3ca7e | 23 | return 1; |
24 | } | |
25 | ||
26 | int aim_conn_addhandler(struct aim_conn_t *conn, | |
27 | u_short family, | |
28 | u_short type, | |
29 | rxcallback_t newhandler, | |
30 | u_short flags) | |
31 | { | |
32 | struct aim_rxcblist_t *new,*cur; | |
33 | ||
34 | if (!conn) | |
35 | return -1; | |
36 | ||
37 | #if debug > 0 | |
38 | printf("aim_conn_addhandler: adding for %04x/%04x\n", family, type); | |
39 | #endif | |
40 | ||
41 | new = (struct aim_rxcblist_t *)calloc(1, sizeof(struct aim_rxcblist_t)); | |
42 | new->family = family; | |
43 | new->type = type; | |
44 | new->flags = flags; | |
45 | if (!newhandler) | |
46 | new->handler = &bleck; | |
47 | else | |
48 | new->handler = newhandler; | |
49 | new->next = NULL; | |
50 | ||
51 | cur = conn->handlerlist; | |
52 | if (!cur) | |
53 | conn->handlerlist = new; | |
54 | else | |
55 | { | |
56 | while (cur->next) | |
57 | cur = cur->next; | |
58 | cur->next = new; | |
59 | } | |
60 | ||
61 | return 0; | |
62 | } | |
63 | ||
64 | int aim_clearhandlers(struct aim_conn_t *conn) | |
65 | { | |
66 | struct aim_rxcblist_t *cur,*tmp; | |
67 | if (!conn) | |
68 | return -1; | |
69 | ||
70 | cur = conn->handlerlist; | |
71 | while(cur) | |
72 | { | |
73 | tmp = cur->next; | |
74 | free(cur); | |
75 | cur = tmp; | |
76 | } | |
77 | return 0; | |
78 | } | |
79 | ||
80 | rxcallback_t aim_callhandler(struct aim_conn_t *conn, | |
81 | u_short family, | |
82 | u_short type) | |
83 | { | |
84 | struct aim_rxcblist_t *cur; | |
85 | ||
86 | if (!conn) | |
87 | return NULL; | |
88 | ||
89 | #if debug > 0 | |
90 | printf("aim_callhandler: calling for %04x/%04x\n", family, type); | |
91 | #endif | |
92 | ||
93 | cur = conn->handlerlist; | |
94 | while(cur) | |
95 | { | |
96 | if ( (cur->family == family) && (cur->type == type) ) | |
97 | return cur->handler; | |
98 | cur = cur->next; | |
99 | } | |
100 | ||
101 | if (type==0xffff) | |
102 | return NULL; | |
103 | return aim_callhandler(conn, family, 0xffff); | |
104 | } | |
105 | ||
106 | int aim_callhandler_noparam(struct aim_conn_t *conn, | |
107 | u_short family, | |
108 | u_short type, | |
109 | struct command_rx_struct *ptr) | |
110 | { | |
111 | rxcallback_t userfunc = NULL; | |
112 | userfunc = aim_callhandler(conn, family, type); | |
113 | if (userfunc) | |
114 | return userfunc(ptr); | |
115 | return 0; | |
116 | } | |
117 | ||
118 | /* | |
119 | aim_rxdispatch() | |
120 | ||
121 | Basically, heres what this should do: | |
122 | 1) Determine correct packet handler for this packet | |
123 | 2) Mark the packet handled (so it can be dequeued in purge_queue()) | |
124 | 3) Send the packet to the packet handler | |
125 | 4) Go to next packet in the queue and start over | |
126 | 5) When done, run purge_queue() to purge handled commands | |
127 | ||
128 | Note that any unhandlable packets should probably be left in the | |
129 | queue. This is the best way to prevent data loss. This means | |
130 | that a single packet may get looked at by this function multiple | |
131 | times. This is more good than bad! This behavior may change. | |
132 | ||
133 | Aren't queue's fun? | |
134 | ||
135 | TODO: Get rid of all the ugly if's. | |
136 | TODO: Clean up. | |
137 | TODO: More support for mid-level handlers. | |
138 | TODO: Allow for NULL handlers. | |
139 | ||
140 | */ | |
141 | int aim_rxdispatch(void) | |
142 | { | |
143 | int i = 0; | |
144 | struct command_rx_struct *workingPtr = NULL; | |
145 | ||
146 | if (aim_queue_incoming == NULL) | |
147 | /* this shouldn't really happen, unless the main loop's select is broke */ | |
148 | printf("parse_generic: incoming packet queue empty.\n"); | |
149 | else | |
150 | { | |
151 | workingPtr = aim_queue_incoming; | |
152 | for (i = 0; workingPtr != NULL; i++) | |
153 | { | |
154 | switch(workingPtr->conn->type) | |
155 | { | |
156 | case AIM_CONN_TYPE_AUTH: | |
157 | if ( (workingPtr->data[0] == 0x00) && | |
158 | (workingPtr->data[1] == 0x00) && | |
159 | (workingPtr->data[2] == 0x00) && | |
160 | (workingPtr->data[3] == 0x01) ) | |
161 | { | |
162 | #if debug > 0 | |
24286d93 | 163 | printf("got connection ack on auth line\n"); |
9de3ca7e | 164 | #endif |
165 | workingPtr->handled = 1; | |
166 | } | |
167 | else | |
168 | { | |
169 | /* any user callbacks will be called from here */ | |
170 | workingPtr->handled = aim_authparse(workingPtr); | |
171 | } | |
172 | break; | |
173 | case AIM_CONN_TYPE_BOS: | |
174 | { | |
175 | u_short family; | |
176 | u_short subtype; | |
177 | family = (workingPtr->data[0] << 8) + workingPtr->data[1]; | |
178 | subtype = (workingPtr->data[2] << 8) + workingPtr->data[3]; | |
179 | switch (family) | |
180 | { | |
181 | case 0x0000: /* not really a family, but it works */ | |
182 | if (subtype == 0x0001) | |
183 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x0000, 0x0001, workingPtr); | |
184 | else | |
185 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_UNKNOWN, workingPtr); | |
186 | break; | |
187 | case 0x0001: /* Family: General */ | |
188 | switch (subtype) | |
189 | { | |
190 | case 0x0001: | |
191 | workingPtr->handled = aim_parse_generalerrs(workingPtr); | |
192 | break; | |
193 | case 0x0003: | |
194 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x0001, 0x0003, workingPtr); | |
195 | break; | |
196 | case 0x0005: | |
197 | workingPtr->handled = aim_handleredirect_middle(workingPtr); | |
198 | break; | |
199 | case 0x0007: | |
200 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x0001, 0x0007, workingPtr); | |
201 | break; | |
202 | case 0x000a: | |
203 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x0001, 0x000a, workingPtr); | |
204 | break; | |
205 | case 0x000f: | |
206 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x0001, 0x000f, workingPtr); | |
207 | break; | |
208 | case 0x0013: | |
209 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x0001, 0x0013, workingPtr); | |
210 | break; | |
211 | default: | |
212 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, AIM_CB_FAM_GEN, AIM_CB_GEN_DEFAULT, workingPtr); | |
213 | } | |
214 | break; | |
215 | case 0x0002: /* Family: Location */ | |
216 | switch (subtype) | |
217 | { | |
218 | case 0x0001: | |
219 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x0002, 0x0001, workingPtr); | |
220 | break; | |
221 | case 0x0003: | |
222 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x0002, 0x0003, workingPtr); | |
223 | break; | |
224 | case 0x0006: | |
225 | workingPtr->handled = aim_parse_userinfo_middle(workingPtr); | |
226 | break; | |
227 | default: | |
228 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, AIM_CB_FAM_LOC, AIM_CB_LOC_DEFAULT, workingPtr); | |
229 | } | |
230 | break; | |
231 | case 0x0003: /* Family: Buddy List */ | |
232 | switch (subtype) | |
233 | { | |
234 | case 0x0001: | |
235 | workingPtr->handled = aim_parse_generalerrs(workingPtr); | |
236 | break; | |
237 | case 0x0003: | |
238 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x0003, 0x0003, workingPtr); | |
239 | break; | |
240 | case 0x000b: /* oncoming buddy */ | |
241 | workingPtr->handled = aim_parse_oncoming_middle(workingPtr); | |
242 | break; | |
243 | case 0x000c: /* offgoing buddy */ | |
244 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x0003, 0x000c, workingPtr); | |
245 | break; | |
246 | default: | |
247 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, AIM_CB_FAM_BUD, AIM_CB_BUD_DEFAULT, workingPtr); | |
248 | } | |
249 | break; | |
250 | case 0x0004: /* Family: Messeging */ | |
251 | switch (subtype) | |
252 | { | |
253 | case 0x0001: | |
254 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x0004, 0x0001, workingPtr); | |
255 | break; | |
256 | case 0x0005: | |
257 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x0004, 0x0005, workingPtr); | |
258 | break; | |
259 | case 0x0007: | |
260 | workingPtr->handled = aim_parse_incoming_im_middle(workingPtr); | |
261 | break; | |
262 | case 0x000a: | |
263 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x0004, 0x000a, workingPtr); | |
264 | break; | |
265 | default: | |
266 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, AIM_CB_FAM_MSG, AIM_CB_MSG_DEFAULT, workingPtr); | |
267 | } | |
268 | break; | |
269 | case 0x0009: | |
270 | if (subtype == 0x0001) | |
271 | workingPtr->handled = aim_parse_generalerrs(workingPtr); | |
272 | else if (subtype == 0x0003) | |
273 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x0009, 0x0003, workingPtr); | |
274 | else | |
275 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, AIM_CB_FAM_BOS, AIM_CB_BOS_DEFAULT, workingPtr); | |
276 | break; | |
277 | case 0x000a: /* Family: User lookup */ | |
278 | switch (subtype) | |
279 | { | |
280 | case 0x0001: | |
281 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x000a, 0x0001, workingPtr); | |
282 | break; | |
283 | case 0x0003: | |
284 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x000a, 0x0003, workingPtr); | |
285 | break; | |
286 | default: | |
287 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, AIM_CB_FAM_LOK, AIM_CB_LOK_DEFAULT, workingPtr); | |
288 | } | |
289 | break; | |
290 | case 0x000b: | |
291 | if (subtype == 0x0001) | |
292 | workingPtr->handled = aim_parse_generalerrs(workingPtr); | |
293 | else if (subtype == 0x0002) | |
294 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, 0x000b, 0x0002, workingPtr); | |
295 | else | |
296 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, AIM_CB_FAM_STS, AIM_CB_STS_DEFAULT, workingPtr); | |
297 | break; | |
298 | default: | |
299 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_UNKNOWN, workingPtr); | |
300 | break; | |
301 | } | |
302 | } | |
303 | break; | |
304 | case AIM_CONN_TYPE_CHATNAV: | |
305 | { | |
306 | u_short family; | |
307 | u_short subtype; | |
308 | family = (workingPtr->data[0] << 8) + workingPtr->data[1]; | |
309 | subtype = (workingPtr->data[2] << 8) + workingPtr->data[3]; | |
310 | if ( (workingPtr->data[0] == 0x00) && | |
311 | (workingPtr->data[1] == 0x02) && | |
312 | (workingPtr->data[2] == 0x00) && | |
313 | (workingPtr->data[3] == 0x06) ) | |
314 | { | |
315 | workingPtr->handled = 1; | |
316 | aim_conn_setstatus(workingPtr->conn, AIM_CONN_STATUS_READY); | |
317 | } | |
318 | else | |
319 | { | |
320 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, family, subtype, workingPtr); | |
321 | } | |
322 | } | |
323 | break; | |
324 | case AIM_CONN_TYPE_CHAT: | |
24286d93 | 325 | printf("\nAHH! Dont know what to do with CHAT stuff yet!\n"); |
9de3ca7e | 326 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_DEFAULT, workingPtr); |
327 | break; | |
328 | default: | |
24286d93 | 329 | printf("\nAHHHHH! UNKNOWN CONNECTION TYPE! (0x%02x)\n\n", workingPtr->conn->type); |
9de3ca7e | 330 | workingPtr->handled = aim_callhandler_noparam(workingPtr->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_UNKNOWN, workingPtr); |
331 | break; | |
332 | } | |
333 | /* move to next command */ | |
334 | workingPtr = workingPtr->next; | |
335 | } | |
336 | } | |
337 | ||
338 | aim_queue_incoming = aim_purge_rxqueue(aim_queue_incoming); | |
339 | ||
340 | return 0; | |
341 | } | |
342 | ||
343 | /* | |
344 | * TODO: check and cure memory leakage in this function. | |
345 | */ | |
346 | int aim_authparse(struct command_rx_struct *command) | |
347 | { | |
348 | rxcallback_t userfunc = NULL; | |
349 | int iserror = 0; | |
350 | struct aim_tlv_t *tlv = NULL; | |
351 | char *errorurl = NULL; | |
352 | short errorcode = 0x00; | |
353 | u_int z = 0; | |
354 | ||
355 | if ( (command->data[0] == 0x00) && | |
356 | (command->data[1] == 0x01) && | |
357 | (command->data[2] == 0x00) && | |
358 | (command->data[3] == 0x03) ) | |
359 | { | |
360 | /* "server ready" -- can be ignored */ | |
361 | userfunc = aim_callhandler(command->conn, AIM_CB_FAM_GEN, AIM_CB_GEN_SERVERREADY); | |
362 | } | |
363 | else if ( (command->data[0] == 0x00) && | |
364 | (command->data[1] == 0x07) && | |
365 | (command->data[2] == 0x00) && | |
366 | (command->data[3] == 0x05) ) | |
367 | { | |
368 | /* "information change reply" */ | |
369 | userfunc = aim_callhandler(command->conn, AIM_CB_FAM_ADM, AIM_CB_ADM_INFOCHANGE_REPLY); | |
370 | } | |
371 | else | |
372 | { | |
373 | /* anything else -- usually used for login; just parse as pure TLVs */ | |
374 | ||
375 | /* | |
376 | * Free up the loginstruct first. | |
377 | */ | |
378 | if (aim_logininfo.screen_name) | |
379 | { | |
380 | free(aim_logininfo.screen_name); | |
381 | aim_logininfo.screen_name = NULL; | |
382 | } | |
383 | if (aim_logininfo.BOSIP) | |
384 | { | |
385 | free(aim_logininfo.BOSIP); | |
386 | aim_logininfo.BOSIP = NULL; | |
387 | } | |
388 | if (aim_logininfo.cookie) | |
389 | { | |
390 | free(aim_logininfo.cookie); | |
391 | aim_logininfo.cookie = NULL; | |
392 | } | |
393 | if (aim_logininfo.email) | |
394 | { | |
395 | free(aim_logininfo.email); | |
396 | aim_logininfo.email = NULL; | |
397 | } | |
398 | aim_logininfo.regstatus = 0; | |
399 | ||
400 | /* all this block does is figure out if it's an | |
401 | error or a success, nothing more */ | |
402 | while (z < command->commandlen) | |
403 | { | |
404 | tlv = aim_grabtlvstr(&(command->data[z])); | |
405 | switch(tlv->type) | |
406 | { | |
407 | case 0x0001: /* screen name */ | |
408 | aim_logininfo.screen_name = tlv->value; | |
409 | z += 2 + 2 + tlv->length; | |
410 | free(tlv); | |
411 | tlv = NULL; | |
412 | break; | |
413 | case 0x0004: /* error URL */ | |
414 | errorurl = tlv->value; | |
415 | z += 2 + 2 + tlv->length; | |
416 | free(tlv); | |
417 | tlv = NULL; | |
418 | break; | |
419 | case 0x0005: /* BOS IP */ | |
420 | aim_logininfo.BOSIP = tlv->value; | |
421 | z += 2 + 2 + tlv->length; | |
422 | free(tlv); | |
423 | tlv = NULL; | |
424 | break; | |
425 | case 0x0006: /* auth cookie */ | |
426 | aim_logininfo.cookie = tlv->value; | |
427 | z += 2 + 2 + tlv->length; | |
428 | free(tlv); | |
429 | tlv=NULL; | |
430 | break; | |
431 | case 0x0011: /* email addy */ | |
432 | aim_logininfo.email = tlv->value; | |
433 | z += 2 + 2 + tlv->length; | |
434 | free(tlv); | |
435 | tlv = NULL; | |
436 | break; | |
437 | case 0x0013: /* registration status */ | |
438 | aim_logininfo.regstatus = *(tlv->value); | |
439 | z += 2 + 2 + tlv->length; | |
440 | aim_freetlv(&tlv); | |
441 | break; | |
442 | case 0x0008: /* error code */ | |
443 | errorcode = *(tlv->value); | |
444 | z += 2 + 2 + tlv->length; | |
445 | aim_freetlv(&tlv); | |
446 | iserror = 1; | |
447 | break; | |
448 | default: | |
449 | z += 2 + 2 + tlv->length; | |
450 | aim_freetlv(&tlv); | |
451 | /* dunno */ | |
452 | } | |
453 | } | |
454 | ||
455 | if (iserror && | |
456 | errorurl) | |
457 | { | |
458 | userfunc = aim_callhandler(command->conn, AIM_CB_FAM_GEN, AIM_CB_GEN_ERROR); | |
459 | if (userfunc) | |
460 | return userfunc(command, &aim_logininfo, errorurl, errorcode); | |
461 | return 0; | |
462 | } | |
463 | else if (aim_logininfo.screen_name && | |
464 | aim_logininfo.cookie && aim_logininfo.BOSIP) | |
465 | { | |
466 | userfunc = aim_callhandler(command->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_AUTHSUCCESS); | |
467 | if (userfunc) | |
468 | return userfunc(command, &aim_logininfo); | |
469 | return 0; | |
470 | } | |
471 | else | |
472 | userfunc = aim_callhandler(command->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_AUTHOTHER); | |
473 | } | |
474 | ||
475 | if (userfunc) | |
476 | return userfunc(command); | |
477 | printf("handler not available!\n"); | |
478 | return 0; | |
479 | } | |
480 | ||
481 | /* | |
482 | * TODO: check for and cure any memory leaks here. | |
483 | */ | |
484 | int aim_handleredirect_middle(struct command_rx_struct *command, ...) | |
485 | { | |
486 | struct aim_tlv_t *tlv = NULL; | |
487 | u_int z = 10; | |
488 | int serviceid = 0x00; | |
489 | char *cookie = NULL; | |
490 | char *ip = NULL; | |
491 | rxcallback_t userfunc = NULL; | |
492 | ||
493 | while (z < command->commandlen) | |
494 | { | |
495 | tlv = aim_grabtlvstr(&(command->data[z])); | |
496 | switch(tlv->type) | |
497 | { | |
498 | case 0x000d: /* service id */ | |
499 | aim_freetlv(&tlv); | |
500 | /* regrab as an int */ | |
501 | tlv = aim_grabtlv(&(command->data[z])); | |
502 | serviceid = (tlv->value[0] << 8) + tlv->value[1]; /* hehe */ | |
503 | z += 2 + 2 + tlv->length; | |
504 | aim_freetlv(&tlv); | |
505 | break; | |
506 | case 0x0005: /* service server IP */ | |
507 | ip = tlv->value; | |
508 | z += 2 + 2 + tlv->length; | |
509 | free(tlv); | |
510 | tlv = NULL; | |
511 | break; | |
512 | case 0x0006: /* auth cookie */ | |
513 | cookie = tlv->value; | |
514 | z += 2 + 2 + tlv->length; | |
515 | free(tlv); | |
516 | tlv = NULL; | |
517 | break; | |
518 | default: | |
519 | /* dunno */ | |
520 | z += 2 + 2 + tlv->length; | |
521 | aim_freetlv(&tlv); | |
522 | } | |
523 | } | |
524 | userfunc = aim_callhandler(command->conn, 0x0001, 0x0005); | |
525 | if (userfunc) | |
526 | return userfunc(command, serviceid, ip, cookie); | |
527 | return 0; | |
528 | } | |
529 | ||
530 | int aim_parse_unknown(struct command_rx_struct *command, ...) | |
531 | { | |
532 | u_int i = 0; | |
533 | ||
534 | printf("\nRecieved unknown packet:"); | |
535 | ||
536 | for (i = 0; i < command->commandlen; i++) | |
537 | { | |
538 | if ((i % 8) == 0) | |
539 | printf("\n\t"); | |
540 | ||
541 | printf("0x%2x ", command->data[i]); | |
542 | } | |
543 | ||
544 | printf("\n\n"); | |
545 | ||
546 | return 1; | |
547 | } | |
548 | ||
549 | ||
550 | /* | |
551 | * aim_parse_generalerrs() | |
552 | * | |
553 | * Middle handler for 0x0001 snac of each family. | |
554 | * | |
555 | */ | |
556 | int aim_parse_generalerrs(struct command_rx_struct *command, ...) | |
557 | { | |
558 | u_short family; | |
559 | u_short subtype; | |
560 | family = (command->data[0] << 8) + command->data[1]; | |
561 | subtype = (command->data[2] << 8) + command->data[3]; | |
562 | ||
563 | switch(family) | |
564 | { | |
565 | default: | |
566 | /* Unknown family */ | |
567 | return aim_callhandler_noparam(command->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_UNKNOWN, command); | |
568 | } | |
569 | ||
570 | return 1; | |
571 | } | |
572 | ||
573 | ||
574 |