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