source: protocols/oscar/ssi.c @ 69982f8

Last change on this file since 69982f8 was 5ebff60, checked in by dequis <dx@…>, at 2015-02-20T22:50:54Z

Reindent everything to K&R style with tabs

Used uncrustify, with the configuration file in ./doc/uncrustify.cfg

Commit author set to "Indent <please@…>" so that it's easier to
skip while doing git blame.

  • Property mode set to 100644
File size: 35.9 KB
Line 
1/*
2 * Server-Side/Stored Information.
3 *
4 * Relatively new facility that allows storing of certain types of information,
5 * such as a users buddy list, permit/deny list, and permit/deny preferences,
6 * to be stored on the server, so that they can be accessed from any client.
7 *
8 * We keep a copy of the ssi data in sess->ssi, because the data needs to be
9 * accessed for various reasons.  So all the "aim_ssi_itemlist_bleh" functions
10 * near the top just manage the local data.
11 *
12 * The SNAC sending and receiving functions are lower down in the file, and
13 * they're simpler.  They are in the order of the subtypes they deal with,
14 * starting with the request rights function (subtype 0x0002), then parse
15 * rights (subtype 0x0003), then--well, you get the idea.
16 *
17 * This is entirely too complicated.
18 * You don't know the half of it.
19 *
20 * XXX - Test for memory leaks
21 * XXX - Better parsing of rights, and use the rights info to limit adds
22 *
23 */
24
25#include <aim.h>
26#include "ssi.h"
27
28/**
29 * Locally add a new item to the given item list.
30 *
31 * @param list A pointer to a pointer to the current list of items.
32 * @param parent A pointer to the parent group, or NULL if the item should have no
33 *        parent group (ie. the group ID# should be 0).
34 * @param name A null terminated string of the name of the new item, or NULL if the
35 *        item should have no name.
36 * @param type The type of the item, 0x0001 for a contact, 0x0002 for a group, etc.
37 * @return The newly created item.
38 */
39static struct aim_ssi_item *aim_ssi_itemlist_add(struct aim_ssi_item **list, struct aim_ssi_item *parent, char *name,
40                                                 guint16 type)
41{
42        int i;
43        struct aim_ssi_item *cur, *newitem;
44
45        if (!(newitem = g_new0(struct aim_ssi_item, 1))) {
46                return NULL;
47        }
48
49        /* Set the name */
50        if (name) {
51                if (!(newitem->name = (char *) g_malloc((strlen(name) + 1) * sizeof(char)))) {
52                        g_free(newitem);
53                        return NULL;
54                }
55                strcpy(newitem->name, name);
56        } else {
57                newitem->name = NULL;
58        }
59
60        /* Set the group ID# and the buddy ID# */
61        newitem->gid = 0x0000;
62        newitem->bid = 0x0000;
63        if (type == AIM_SSI_TYPE_GROUP) {
64                if (name) {
65                        do {
66                                newitem->gid += 0x0001;
67                                for (cur = *list, i = 0; ((cur) && (!i)); cur = cur->next) {
68                                        if ((cur->gid == newitem->gid) && (cur->gid == newitem->gid)) {
69                                                i = 1;
70                                        }
71                                }
72                        } while (i);
73                }
74        } else {
75                if (parent) {
76                        newitem->gid = parent->gid;
77                }
78                do {
79                        newitem->bid += 0x0001;
80                        for (cur = *list, i = 0; ((cur) && (!i)); cur = cur->next) {
81                                if ((cur->bid == newitem->bid) && (cur->gid == newitem->gid)) {
82                                        i = 1;
83                                }
84                        }
85                } while (i);
86        }
87
88        /* Set the rest */
89        newitem->type = type;
90        newitem->data = NULL;
91        newitem->next = *list;
92        *list = newitem;
93
94        return newitem;
95}
96
97/**
98 * Locally rebuild the 0x00c8 TLV in the additional data of the given group.
99 *
100 * @param list A pointer to a pointer to the current list of items.
101 * @param parentgroup A pointer to the group who's additional data you want to rebuild.
102 * @return Return 0 if no errors, otherwise return the error number.
103 */
104static int aim_ssi_itemlist_rebuildgroup(struct aim_ssi_item **list, struct aim_ssi_item *parentgroup)
105{
106        int newlen; //, i;
107        struct aim_ssi_item *cur;
108
109        /* Free the old additional data */
110        if (parentgroup->data) {
111                aim_freetlvchain((aim_tlvlist_t **) &parentgroup->data);
112                parentgroup->data = NULL;
113        }
114
115        /* Find the length for the new additional data */
116        newlen = 0;
117        if (parentgroup->gid == 0x0000) {
118                for (cur = *list; cur; cur = cur->next) {
119                        if ((cur->gid != 0x0000) && (cur->type == AIM_SSI_TYPE_GROUP)) {
120                                newlen += 2;
121                        }
122                }
123        } else {
124                for (cur = *list; cur; cur = cur->next) {
125                        if ((cur->gid == parentgroup->gid) && (cur->type == AIM_SSI_TYPE_BUDDY)) {
126                                newlen += 2;
127                        }
128                }
129        }
130
131        /* Rebuild the additional data */
132        if (newlen > 0) {
133                guint8 *newdata;
134
135                if (!(newdata = (guint8 *) g_malloc((newlen) * sizeof(guint8)))) {
136                        return -ENOMEM;
137                }
138                newlen = 0;
139                if (parentgroup->gid == 0x0000) {
140                        for (cur = *list; cur; cur = cur->next) {
141                                if ((cur->gid != 0x0000) && (cur->type == AIM_SSI_TYPE_GROUP)) {
142                                        newlen += aimutil_put16(newdata + newlen, cur->gid);
143                                }
144                        }
145                } else {
146                        for (cur = *list; cur; cur = cur->next) {
147                                if ((cur->gid == parentgroup->gid) && (cur->type == AIM_SSI_TYPE_BUDDY)) {
148                                        newlen += aimutil_put16(newdata + newlen, cur->bid);
149                                }
150                        }
151                }
152                aim_addtlvtochain_raw((aim_tlvlist_t **) &(parentgroup->data), 0x00c8, newlen, newdata);
153
154                g_free(newdata);
155        }
156
157        return 0;
158}
159
160/**
161 * Locally free all of the stored buddy list information.
162 *
163 * @param sess The oscar session.
164 * @return Return 0 if no errors, otherwise return the error number.
165 */
166static int aim_ssi_freelist(aim_session_t *sess)
167{
168        struct aim_ssi_item *cur, *delitem;
169
170        cur = sess->ssi.items;
171        while (cur) {
172                if (cur->name) {
173                        g_free(cur->name);
174                }
175                if (cur->data) {
176                        aim_freetlvchain((aim_tlvlist_t **) &cur->data);
177                }
178                delitem = cur;
179                cur = cur->next;
180                g_free(delitem);
181        }
182
183        sess->ssi.items = NULL;
184        sess->ssi.revision = 0;
185        sess->ssi.timestamp = (time_t) 0;
186
187        return 0;
188}
189
190/**
191 * Locally find an item given a group ID# and a buddy ID#.
192 *
193 * @param list A pointer to the current list of items.
194 * @param gid The group ID# of the desired item.
195 * @param bid The buddy ID# of the desired item.
196 * @return Return a pointer to the item if found, else return NULL;
197 */
198struct aim_ssi_item *aim_ssi_itemlist_find(struct aim_ssi_item *list, guint16 gid, guint16 bid)
199{
200        struct aim_ssi_item *cur;
201
202        for (cur = list; cur; cur = cur->next) {
203                if ((cur->gid == gid) && (cur->bid == bid)) {
204                        return cur;
205                }
206        }
207        return NULL;
208}
209
210/**
211 * Locally find an item given a group name, screen name, and type.  If group name
212 * and screen name are null, then just return the first item of the given type.
213 *
214 * @param list A pointer to the current list of items.
215 * @param gn The group name of the desired item.
216 * @param bn The buddy name of the desired item.
217 * @param type The type of the desired item.
218 * @return Return a pointer to the item if found, else return NULL;
219 */
220struct aim_ssi_item *aim_ssi_itemlist_finditem(struct aim_ssi_item *list, char *gn, char *sn, guint16 type)
221{
222        struct aim_ssi_item *cur;
223
224        if (!list) {
225                return NULL;
226        }
227
228        if (gn && sn) { /* For finding buddies in groups */
229                for (cur = list; cur; cur = cur->next) {
230                        if ((cur->type == type) && (cur->name) && !(aim_sncmp(cur->name, sn))) {
231                                struct aim_ssi_item *curg;
232                                for (curg = list; curg; curg = curg->next) {
233                                        if ((curg->type == AIM_SSI_TYPE_GROUP) && (curg->gid == cur->gid) &&
234                                            (curg->name) && !(aim_sncmp(curg->name, gn))) {
235                                                return cur;
236                                        }
237                                }
238                        }
239                }
240
241        } else if (sn) { /* For finding groups, permits, denies, and ignores */
242                for (cur = list; cur; cur = cur->next) {
243                        if ((cur->type == type) && (cur->name) && !(aim_sncmp(cur->name, sn))) {
244                                return cur;
245                        }
246                }
247
248                /* For stuff without names--permit deny setting, visibility mask, etc. */
249        } else { for (cur = list; cur; cur = cur->next) {
250                         if (cur->type == type) {
251                                 return cur;
252                         }
253                 }
254        }
255
256        return NULL;
257}
258
259/**
260 * Locally find the parent item of the given buddy name.
261 *
262 * @param list A pointer to the current list of items.
263 * @param bn The buddy name of the desired item.
264 * @return Return a pointer to the item if found, else return NULL;
265 */
266struct aim_ssi_item *aim_ssi_itemlist_findparent(struct aim_ssi_item *list, char *sn)
267{
268        struct aim_ssi_item *cur, *curg;
269
270        if (!list || !sn) {
271                return NULL;
272        }
273        if (!(cur = aim_ssi_itemlist_finditem(list, NULL, sn, AIM_SSI_TYPE_BUDDY))) {
274                return NULL;
275        }
276        for (curg = list; curg; curg = curg->next) {
277                if ((curg->type == AIM_SSI_TYPE_GROUP) && (curg->gid == cur->gid)) {
278                        return curg;
279                }
280        }
281        return NULL;
282}
283
284/**
285 * Locally find the permit/deny setting item, and return the setting.
286 *
287 * @param list A pointer to the current list of items.
288 * @return Return the current SSI permit deny setting, or 0 if no setting was found.
289 */
290int aim_ssi_getpermdeny(struct aim_ssi_item *list)
291{
292        struct aim_ssi_item *cur = aim_ssi_itemlist_finditem(list, NULL, NULL, AIM_SSI_TYPE_PDINFO);
293
294        if (cur) {
295                aim_tlvlist_t *tlvlist = cur->data;
296                if (tlvlist) {
297                        aim_tlv_t *tlv = aim_gettlv(tlvlist, 0x00ca, 1);
298                        if (tlv && tlv->value) {
299                                return aimutil_get8(tlv->value);
300                        }
301                }
302        }
303        return 0;
304}
305
306/**
307 * Add the given packet to the holding queue.  We totally need to send SSI SNACs one at
308 * a time, so we have a local queue where packets get put before they are sent, and
309 * then we send stuff one at a time, nice and orderly-like.
310 *
311 * @param sess The oscar session.
312 * @param conn The bos connection for this session.
313 * @param fr The newly created SNAC that you want to send.
314 * @return Return 0 if no errors, otherwise return the error number.
315 */
316static int aim_ssi_enqueue(aim_session_t *sess, aim_conn_t *conn, aim_frame_t *fr)
317{
318        aim_frame_t *cur;
319
320        if (!sess || !conn || !fr) {
321                return -EINVAL;
322        }
323
324        fr->next = NULL;
325        if (sess->ssi.holding_queue == NULL) {
326                sess->ssi.holding_queue = fr;
327                if (!sess->ssi.waiting_for_ack) {
328                        aim_ssi_modbegin(sess, conn);
329                }
330        } else {
331                for (cur = sess->ssi.holding_queue; cur->next; cur = cur->next) {
332                        ;
333                }
334                cur->next = fr;
335        }
336
337        return 0;
338}
339
340/**
341 * Send the next SNAC from the holding queue.  This is called
342 * automatically when an ack from an add, mod, or del is received.
343 * If the queue is empty, it sends the modend SNAC.
344 *
345 * @param sess The oscar session.
346 * @param conn The bos connection for this session.
347 * @return Return 0 if no errors, otherwise return the error number.
348 */
349static int aim_ssi_dispatch(aim_session_t *sess, aim_conn_t *conn)
350{
351        aim_frame_t *cur;
352
353        if (!sess || !conn) {
354                return -EINVAL;
355        }
356
357        if (!sess->ssi.waiting_for_ack) {
358                if (sess->ssi.holding_queue) {
359                        sess->ssi.waiting_for_ack = 1;
360                        cur = sess->ssi.holding_queue->next;
361                        sess->ssi.holding_queue->next = NULL;
362                        aim_tx_enqueue(sess, sess->ssi.holding_queue);
363                        sess->ssi.holding_queue = cur;
364                } else {
365                        aim_ssi_modend(sess, conn);
366                }
367        }
368
369        return 0;
370}
371
372/**
373 * Add an array of screen names to the given group.
374 *
375 * @param sess The oscar session.
376 * @param conn The bos connection for this session.
377 * @param gn The name of the group to which you want to add these names.
378 * @param sn An array of null terminated strings of the names you want to add.
379 * @param num The number of screen names you are adding (size of the sn array).
380 * @param flags 1 - Add with TLV(0x66)
381 * @return Return 0 if no errors, otherwise return the error number.
382 */
383int aim_ssi_addbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num, unsigned int flags)
384{
385        struct aim_ssi_item *parentgroup, **newitems;
386        guint16 i;
387
388        if (!sess || !conn || !gn || !sn || !num) {
389                return -EINVAL;
390        }
391
392        /* Look up the parent group */
393        if (!(parentgroup = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn, AIM_SSI_TYPE_GROUP))) {
394                aim_ssi_addgroups(sess, conn, &gn, 1);
395                if (!(parentgroup = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn, AIM_SSI_TYPE_GROUP))) {
396                        return -ENOMEM;
397                }
398        }
399
400        /* Allocate an array of pointers to each of the new items */
401        if (!(newitems = g_new0(struct aim_ssi_item *, num))) {
402                return -ENOMEM;
403        }
404
405        /* Add items to the local list, and index them in the array */
406        for (i = 0; i < num; i++) {
407                if (!(newitems[i] = aim_ssi_itemlist_add(&sess->ssi.items, parentgroup, sn[i], AIM_SSI_TYPE_BUDDY))) {
408                        g_free(newitems);
409                        return -ENOMEM;
410                } else if (flags & 1) {
411                        aim_tlvlist_t *tl = NULL;
412                        aim_addtlvtochain_noval(&tl, 0x66);
413                        newitems[i]->data = tl;
414                }
415        }
416
417        /* Send the add item SNAC */
418        if ((i = aim_ssi_addmoddel(sess, conn, newitems, num, AIM_CB_SSI_ADD))) {
419                g_free(newitems);
420                return -i;
421        }
422
423        /* Free the array of pointers to each of the new items */
424        g_free(newitems);
425
426        /* Rebuild the additional data in the parent group */
427        if ((i = aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup))) {
428                return i;
429        }
430
431        /* Send the mod item SNAC */
432        if ((i = aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD))) {
433                return i;
434        }
435
436        /* Begin sending SSI SNACs */
437        if (!(i = aim_ssi_dispatch(sess, conn))) {
438                return i;
439        }
440
441        return 0;
442}
443
444/**
445 * Add the master group (the group containing all groups).  This is called by
446 * aim_ssi_addgroups, if necessary.
447 *
448 * @param sess The oscar session.
449 * @param conn The bos connection for this session.
450 * @return Return 0 if no errors, otherwise return the error number.
451 */
452int aim_ssi_addmastergroup(aim_session_t *sess, aim_conn_t *conn)
453{
454        struct aim_ssi_item *newitem;
455
456        if (!sess || !conn) {
457                return -EINVAL;
458        }
459
460        /* Add the item to the local list, and keep a pointer to it */
461        if (!(newitem = aim_ssi_itemlist_add(&sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_GROUP))) {
462                return -ENOMEM;
463        }
464
465        /* If there are any existing groups (technically there shouldn't be, but */
466        /* just in case) then add their group ID#'s to the additional data */
467        aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, newitem);
468
469        /* Send the add item SNAC */
470        aim_ssi_addmoddel(sess, conn, &newitem, 1, AIM_CB_SSI_ADD);
471
472        /* Begin sending SSI SNACs */
473        aim_ssi_dispatch(sess, conn);
474
475        return 0;
476}
477
478/**
479 * Add an array of groups to the list.
480 *
481 * @param sess The oscar session.
482 * @param conn The bos connection for this session.
483 * @param gn An array of null terminated strings of the names you want to add.
484 * @param num The number of groups names you are adding (size of the sn array).
485 * @return Return 0 if no errors, otherwise return the error number.
486 */
487int aim_ssi_addgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num)
488{
489        struct aim_ssi_item *parentgroup, **newitems;
490        guint16 i;
491
492        if (!sess || !conn || !gn || !num) {
493                return -EINVAL;
494        }
495
496        /* Look up the parent group */
497        if (!(parentgroup = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) {
498                aim_ssi_addmastergroup(sess, conn);
499                if (!(parentgroup = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) {
500                        return -ENOMEM;
501                }
502        }
503
504        /* Allocate an array of pointers to each of the new items */
505        if (!(newitems = g_new0(struct aim_ssi_item *, num))) {
506                return -ENOMEM;
507        }
508
509        /* Add items to the local list, and index them in the array */
510        for (i = 0; i < num; i++) {
511                if (!(newitems[i] = aim_ssi_itemlist_add(&sess->ssi.items, parentgroup, gn[i], AIM_SSI_TYPE_GROUP))) {
512                        g_free(newitems);
513                        return -ENOMEM;
514                }
515        }
516
517        /* Send the add item SNAC */
518        if ((i = aim_ssi_addmoddel(sess, conn, newitems, num, AIM_CB_SSI_ADD))) {
519                g_free(newitems);
520                return -i;
521        }
522
523        /* Free the array of pointers to each of the new items */
524        g_free(newitems);
525
526        /* Rebuild the additional data in the parent group */
527        if ((i = aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup))) {
528                return i;
529        }
530
531        /* Send the mod item SNAC */
532        if ((i = aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD))) {
533                return i;
534        }
535
536        /* Begin sending SSI SNACs */
537        if (!(i = aim_ssi_dispatch(sess, conn))) {
538                return i;
539        }
540
541        return 0;
542}
543
544/**
545 * Add an array of a certain type of item to the list.  This can be used for
546 * permit buddies, deny buddies, ICQ's ignore buddies, and probably other
547 * types, also.
548 *
549 * @param sess The oscar session.
550 * @param conn The bos connection for this session.
551 * @param sn An array of null terminated strings of the names you want to add.
552 * @param num The number of groups names you are adding (size of the sn array).
553 * @param type The type of item you want to add.  See the AIM_SSI_TYPE_BLEH
554 *        #defines in aim.h.
555 * @return Return 0 if no errors, otherwise return the error number.
556 */
557int aim_ssi_addpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type)
558{
559        struct aim_ssi_item **newitems;
560        guint16 i;
561
562        if (!sess || !conn || !sn || !num) {
563                return -EINVAL;
564        }
565
566        /* Allocate an array of pointers to each of the new items */
567        if (!(newitems = g_new0(struct aim_ssi_item *, num))) {
568                return -ENOMEM;
569        }
570
571        /* Add items to the local list, and index them in the array */
572        for (i = 0; i < num; i++) {
573                if (!(newitems[i] = aim_ssi_itemlist_add(&sess->ssi.items, NULL, sn[i], type))) {
574                        g_free(newitems);
575                        return -ENOMEM;
576                }
577        }
578
579        /* Send the add item SNAC */
580        if ((i = aim_ssi_addmoddel(sess, conn, newitems, num, AIM_CB_SSI_ADD))) {
581                g_free(newitems);
582                return -i;
583        }
584
585        /* Free the array of pointers to each of the new items */
586        g_free(newitems);
587
588        /* Begin sending SSI SNACs */
589        if (!(i = aim_ssi_dispatch(sess, conn))) {
590                return i;
591        }
592
593        return 0;
594}
595
596/**
597 * Move a buddy from one group to another group.  This basically just deletes the
598 * buddy and re-adds it.
599 *
600 * @param sess The oscar session.
601 * @param conn The bos connection for this session.
602 * @param oldgn The group that the buddy is currently in.
603 * @param newgn The group that the buddy should be moved in to.
604 * @param sn The name of the buddy to be moved.
605 * @return Return 0 if no errors, otherwise return the error number.
606 */
607int aim_ssi_movebuddy(aim_session_t *sess, aim_conn_t *conn, char *oldgn, char *newgn, char *sn)
608{
609        struct aim_ssi_item **groups, *buddy, *cur;
610        guint16 i;
611
612        if (!sess || !conn || !oldgn || !newgn || !sn) {
613                return -EINVAL;
614        }
615
616        /* Look up the buddy */
617        if (!(buddy = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, sn, AIM_SSI_TYPE_BUDDY))) {
618                return -ENOMEM;
619        }
620
621        /* Allocate an array of pointers to the two groups */
622        if (!(groups = g_new0(struct aim_ssi_item *, 2))) {
623                return -ENOMEM;
624        }
625
626        /* Look up the old parent group */
627        if (!(groups[0] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, oldgn, AIM_SSI_TYPE_GROUP))) {
628                g_free(groups);
629                return -ENOMEM;
630        }
631
632        /* Look up the new parent group */
633        if (!(groups[1] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, newgn, AIM_SSI_TYPE_GROUP))) {
634                g_free(groups);
635                return -ENOMEM;
636        }
637
638        /* Send the delete item SNAC */
639        aim_ssi_addmoddel(sess, conn, &buddy, 1, AIM_CB_SSI_DEL);
640
641        /* Put the buddy in the new group */
642        buddy->gid = groups[1]->gid;
643
644        /* Assign a new buddy ID#, because the new group might already have a buddy with this ID# */
645        buddy->bid = 0;
646        do {
647                buddy->bid += 0x0001;
648                for (cur = sess->ssi.items, i = 0; ((cur) && (!i)); cur = cur->next) {
649                        if ((cur->bid == buddy->bid) && (cur->gid == buddy->gid) && (cur->type == AIM_SSI_TYPE_BUDDY) &&
650                            (cur->name) && aim_sncmp(cur->name, buddy->name)) {
651                                i = 1;
652                        }
653                }
654        } while (i);
655
656        /* Rebuild the additional data in the two parent groups */
657        aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, groups[0]);
658        aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, groups[1]);
659
660        /* Send the add item SNAC */
661        aim_ssi_addmoddel(sess, conn, &buddy, 1, AIM_CB_SSI_ADD);
662
663        /* Send the mod item SNAC */
664        aim_ssi_addmoddel(sess, conn, groups, 2, AIM_CB_SSI_MOD);
665
666        /* Free the temporary array */
667        g_free(groups);
668
669        /* Begin sending SSI SNACs */
670        aim_ssi_dispatch(sess, conn);
671
672        return 0;
673}
674
675/**
676 * Delete an array of screen names from the given group.
677 *
678 * @param sess The oscar session.
679 * @param conn The bos connection for this session.
680 * @param gn The name of the group from which you want to delete these names.
681 * @param sn An array of null terminated strings of the names you want to delete.
682 * @param num The number of screen names you are deleting (size of the sn array).
683 * @return Return 0 if no errors, otherwise return the error number.
684 */
685int aim_ssi_delbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num)
686{
687        struct aim_ssi_item *cur, *parentgroup, **delitems;
688        int i;
689
690        if (!sess || !conn || !gn || !sn || !num) {
691                return -EINVAL;
692        }
693
694        /* Look up the parent group */
695        if (!(parentgroup = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn, AIM_SSI_TYPE_GROUP))) {
696                return -EINVAL;
697        }
698
699        /* Allocate an array of pointers to each of the items to be deleted */
700        delitems = g_new0(struct aim_ssi_item *, num);
701
702        /* Make the delitems array a pointer to the aim_ssi_item structs to be deleted */
703        for (i = 0; i < num; i++) {
704                if (!(delitems[i] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, sn[i], AIM_SSI_TYPE_BUDDY))) {
705                        g_free(delitems);
706                        return -EINVAL;
707                }
708
709                /* Remove the delitems from the item list */
710                if (sess->ssi.items == delitems[i]) {
711                        sess->ssi.items = sess->ssi.items->next;
712                } else {
713                        for (cur = sess->ssi.items; (cur->next && (cur->next != delitems[i])); cur = cur->next) {
714                                ;
715                        }
716                        if (cur->next) {
717                                cur->next = cur->next->next;
718                        }
719                }
720        }
721
722        /* Send the del item SNAC */
723        aim_ssi_addmoddel(sess, conn, delitems, num, AIM_CB_SSI_DEL);
724
725        /* Free the items */
726        for (i = 0; i < num; i++) {
727                if (delitems[i]->name) {
728                        g_free(delitems[i]->name);
729                }
730                if (delitems[i]->data) {
731                        aim_freetlvchain((aim_tlvlist_t **) &delitems[i]->data);
732                }
733                g_free(delitems[i]);
734        }
735        g_free(delitems);
736
737        /* Rebuild the additional data in the parent group */
738        aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup);
739
740        /* Send the mod item SNAC */
741        aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD);
742
743        /* Delete the group, but only if it's empty */
744        if (!parentgroup->data) {
745                aim_ssi_delgroups(sess, conn, &parentgroup->name, 1);
746        }
747
748        /* Begin sending SSI SNACs */
749        aim_ssi_dispatch(sess, conn);
750
751        return 0;
752}
753
754/**
755 * Delete the master group from the item list.  There can be only one.
756 * Er, so just find the one master group and delete it.
757 *
758 * @param sess The oscar session.
759 * @param conn The bos connection for this session.
760 * @return Return 0 if no errors, otherwise return the error number.
761 */
762int aim_ssi_delmastergroup(aim_session_t *sess, aim_conn_t *conn)
763{
764        struct aim_ssi_item *cur, *delitem;
765
766        if (!sess || !conn) {
767                return -EINVAL;
768        }
769
770        /* Make delitem a pointer to the aim_ssi_item to be deleted */
771        if (!(delitem = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) {
772                return -EINVAL;
773        }
774
775        /* Remove delitem from the item list */
776        if (sess->ssi.items == delitem) {
777                sess->ssi.items = sess->ssi.items->next;
778        } else {
779                for (cur = sess->ssi.items; (cur->next && (cur->next != delitem)); cur = cur->next) {
780                        ;
781                }
782                if (cur->next) {
783                        cur->next = cur->next->next;
784                }
785        }
786
787        /* Send the del item SNAC */
788        aim_ssi_addmoddel(sess, conn, &delitem, 1, AIM_CB_SSI_DEL);
789
790        /* Free the item */
791        if (delitem->name) {
792                g_free(delitem->name);
793        }
794        if (delitem->data) {
795                aim_freetlvchain((aim_tlvlist_t **) &delitem->data);
796        }
797        g_free(delitem);
798
799        /* Begin sending SSI SNACs */
800        aim_ssi_dispatch(sess, conn);
801
802        return 0;
803}
804
805/**
806 * Delete an array of groups.
807 *
808 * @param sess The oscar session.
809 * @param conn The bos connection for this session.
810 * @param gn An array of null terminated strings of the groups you want to delete.
811 * @param num The number of groups you are deleting (size of the gn array).
812 * @return Return 0 if no errors, otherwise return the error number.
813 */
814int aim_ssi_delgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num)
815{
816        struct aim_ssi_item *cur, *parentgroup, **delitems;
817        int i;
818
819        if (!sess || !conn || !gn || !num) {
820                return -EINVAL;
821        }
822
823        /* Look up the parent group */
824        if (!(parentgroup = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) {
825                return -EINVAL;
826        }
827
828        /* Allocate an array of pointers to each of the items to be deleted */
829        delitems = g_new0(struct aim_ssi_item *, num);
830
831        /* Make the delitems array a pointer to the aim_ssi_item structs to be deleted */
832        for (i = 0; i < num; i++) {
833                if (!(delitems[i] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn[i], AIM_SSI_TYPE_GROUP))) {
834                        g_free(delitems);
835                        return -EINVAL;
836                }
837
838                /* Remove the delitems from the item list */
839                if (sess->ssi.items == delitems[i]) {
840                        sess->ssi.items = sess->ssi.items->next;
841                } else {
842                        for (cur = sess->ssi.items; (cur->next && (cur->next != delitems[i])); cur = cur->next) {
843                                ;
844                        }
845                        if (cur->next) {
846                                cur->next = cur->next->next;
847                        }
848                }
849        }
850
851        /* Send the del item SNAC */
852        aim_ssi_addmoddel(sess, conn, delitems, num, AIM_CB_SSI_DEL);
853
854        /* Free the items */
855        for (i = 0; i < num; i++) {
856                if (delitems[i]->name) {
857                        g_free(delitems[i]->name);
858                }
859                if (delitems[i]->data) {
860                        aim_freetlvchain((aim_tlvlist_t **) &delitems[i]->data);
861                }
862                g_free(delitems[i]);
863        }
864        g_free(delitems);
865
866        /* Rebuild the additional data in the parent group */
867        aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup);
868
869        /* Send the mod item SNAC */
870        aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD);
871
872        /* Delete the group, but only if it's empty */
873        if (!parentgroup->data) {
874                aim_ssi_delmastergroup(sess, conn);
875        }
876
877        /* Begin sending SSI SNACs */
878        aim_ssi_dispatch(sess, conn);
879
880        return 0;
881}
882
883/**
884 * Delete an array of a certain type of item from the list.  This can be
885 * used for permit buddies, deny buddies, ICQ's ignore buddies, and
886 * probably other types, also.
887 *
888 * @param sess The oscar session.
889 * @param conn The bos connection for this session.
890 * @param sn An array of null terminated strings of the items you want to delete.
891 * @param num The number of items you are deleting (size of the sn array).
892 * @return Return 0 if no errors, otherwise return the error number.
893 */
894int aim_ssi_delpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type)
895{
896        struct aim_ssi_item *cur, **delitems;
897        int i;
898
899        if (!sess || !conn || !sn || !num || (type != AIM_SSI_TYPE_PERMIT && type != AIM_SSI_TYPE_DENY)) {
900                return -EINVAL;
901        }
902
903        /* Allocate an array of pointers to each of the items to be deleted */
904        delitems = g_new0(struct aim_ssi_item *, num);
905
906        /* Make the delitems array a pointer to the aim_ssi_item structs to be deleted */
907        for (i = 0; i < num; i++) {
908                if (!(delitems[i] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, sn[i], type))) {
909                        g_free(delitems);
910                        return -EINVAL;
911                }
912
913                /* Remove the delitems from the item list */
914                if (sess->ssi.items == delitems[i]) {
915                        sess->ssi.items = sess->ssi.items->next;
916                } else {
917                        for (cur = sess->ssi.items; (cur->next && (cur->next != delitems[i])); cur = cur->next) {
918                                ;
919                        }
920                        if (cur->next) {
921                                cur->next = cur->next->next;
922                        }
923                }
924        }
925
926        /* Send the del item SNAC */
927        aim_ssi_addmoddel(sess, conn, delitems, num, AIM_CB_SSI_DEL);
928
929        /* Free the items */
930        for (i = 0; i < num; i++) {
931                if (delitems[i]->name) {
932                        g_free(delitems[i]->name);
933                }
934                if (delitems[i]->data) {
935                        aim_freetlvchain((aim_tlvlist_t **) &delitems[i]->data);
936                }
937                g_free(delitems[i]);
938        }
939        g_free(delitems);
940
941        /* Begin sending SSI SNACs */
942        aim_ssi_dispatch(sess, conn);
943
944        return 0;
945}
946
947/*
948 * Request SSI Rights.
949 */
950int aim_ssi_reqrights(aim_session_t *sess, aim_conn_t *conn)
951{
952        return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, AIM_CB_SSI_REQRIGHTS);
953}
954
955/*
956 * SSI Rights Information.
957 */
958static int parserights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
959{
960        int ret = 0;
961        aim_rxcallback_t userfunc;
962
963        if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) {
964                ret = userfunc(sess, rx);
965        }
966
967        return ret;
968}
969
970int aim_ssi_reqalldata(aim_session_t *sess, aim_conn_t *conn)
971{
972        aim_frame_t *fr;
973        aim_snacid_t snacid;
974
975        if (!sess || !conn) {
976                return -EINVAL;
977        }
978
979        if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10))) {
980                return -ENOMEM;
981        }
982
983        snacid = aim_cachesnac(sess, AIM_CB_FAM_SSI, AIM_CB_SSI_REQFULLLIST, 0x0000, NULL, 0);
984
985        aim_putsnac(&fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_REQFULLLIST, 0x0000, snacid);
986
987        aim_tx_enqueue(sess, fr);
988
989        return 0;
990}
991
992/*
993 * SSI Data.
994 */
995static int parsedata(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
996{
997        int ret = 0;
998        aim_rxcallback_t userfunc;
999        struct aim_ssi_item *cur = NULL;
1000        guint8 fmtver; /* guess */
1001        guint16 revision;
1002        guint32 timestamp;
1003
1004        /* When you set the version for the SSI family to 2-4, the beginning of this changes.
1005         * Instead of the version and then the revision, there is "0x0006" and then a type
1006         * 0x0001 TLV containing the 2 byte SSI family version that you sent earlier.  Also,
1007         * the SNAC flags go from 0x0000 to 0x8000.  I guess the 0x0006 is the length of the
1008         * TLV(s) that follow.  The rights SNAC does the same thing, with the differing flag
1009         * and everything.
1010         */
1011
1012        fmtver = aimbs_get8(bs); /* Version of ssi data.  Should be 0x00 */
1013        revision = aimbs_get16(bs); /* # of times ssi data has been modified */
1014        if (revision != 0) {
1015                sess->ssi.revision = revision;
1016        }
1017
1018        for (cur = sess->ssi.items; cur && cur->next; cur = cur->next) {
1019                ;
1020        }
1021
1022        while (aim_bstream_empty(bs) > 4) { /* last four bytes are stamp */
1023                guint16 namelen, tbslen;
1024
1025                if (!sess->ssi.items) {
1026                        if (!(sess->ssi.items = g_new0(struct aim_ssi_item, 1))) {
1027                                return -ENOMEM;
1028                        }
1029                        cur = sess->ssi.items;
1030                } else {
1031                        if (!(cur->next = g_new0(struct aim_ssi_item, 1))) {
1032                                return -ENOMEM;
1033                        }
1034                        cur = cur->next;
1035                }
1036
1037                if ((namelen = aimbs_get16(bs))) {
1038                        cur->name = aimbs_getstr(bs, namelen);
1039                }
1040                cur->gid = aimbs_get16(bs);
1041                cur->bid = aimbs_get16(bs);
1042                cur->type = aimbs_get16(bs);
1043
1044                if ((tbslen = aimbs_get16(bs))) {
1045                        aim_bstream_t tbs;
1046
1047                        aim_bstream_init(&tbs, bs->data + bs->offset /* XXX */, tbslen);
1048                        cur->data = (void *) aim_readtlvchain(&tbs);
1049                        aim_bstream_advance(bs, tbslen);
1050                }
1051        }
1052
1053        timestamp = aimbs_get32(bs);
1054        if (timestamp != 0) {
1055                sess->ssi.timestamp = timestamp;
1056        }
1057        sess->ssi.received_data = 1;
1058
1059        if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) {
1060                ret = userfunc(sess, rx, fmtver, sess->ssi.revision, sess->ssi.timestamp, sess->ssi.items);
1061        }
1062
1063        return ret;
1064}
1065
1066/*
1067 * SSI Data Enable Presence.
1068 *
1069 * Should be sent after receiving 13/6 or 13/f to tell the server you
1070 * are ready to begin using the list.  It will promptly give you the
1071 * presence information for everyone in your list and put your permit/deny
1072 * settings into effect.
1073 *
1074 */
1075int aim_ssi_enable(aim_session_t *sess, aim_conn_t *conn)
1076{
1077        return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, 0x0007);
1078}
1079
1080/*
1081 * Stuff for SSI authorizations. The code used to work with the old im_ch4
1082 * messages, but those are supposed to be obsolete. This is probably
1083 * ICQ-specific.
1084 */
1085
1086/**
1087 * Request authorization to add someone to the server-side buddy list.
1088 *
1089 * @param sess The oscar session.
1090 * @param conn The bos connection for this session.
1091 * @param uin The contact's ICQ UIN.
1092 * @param reason The reason string to send with the request.
1093 * @return Return 0 if no errors, otherwise return the error number.
1094 */
1095int aim_ssi_auth_request(aim_session_t *sess, aim_conn_t *conn, char *uin, char *reason)
1096{
1097        aim_frame_t *fr;
1098        aim_snacid_t snacid;
1099        int snaclen;
1100
1101        snaclen = 10 + 1 + strlen(uin) + 2 + strlen(reason) + 2;
1102
1103        if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, snaclen))) {
1104                return -ENOMEM;
1105        }
1106
1107        snacid = aim_cachesnac(sess, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREQ, 0x0000, NULL, 0);
1108        aim_putsnac(&fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREQ, 0x0000, snacid);
1109
1110        aimbs_put8(&fr->data, strlen(uin));
1111        aimbs_putraw(&fr->data, (guint8 *) uin, strlen(uin));
1112        aimbs_put16(&fr->data, strlen(reason));
1113        aimbs_putraw(&fr->data, (guint8 *) reason, strlen(reason));
1114        aimbs_put16(&fr->data, 0);
1115
1116        aim_tx_enqueue(sess, fr);
1117
1118        return(0);
1119}
1120
1121/**
1122 * Reply to an authorization request to add someone to the server-side buddy list.
1123 *
1124 * @param sess The oscar session.
1125 * @param conn The bos connection for this session.
1126 * @param uin The contact's ICQ UIN.
1127 * @param yesno 1 == Permit, 0 == Deny
1128 * @param reason The reason string to send with the request.
1129 * @return Return 0 if no errors, otherwise return the error number.
1130 */
1131int aim_ssi_auth_reply(aim_session_t *sess, aim_conn_t *conn, char *uin, int yesno, char *reason)
1132{
1133        aim_frame_t *fr;
1134        aim_snacid_t snacid;
1135        int snaclen;
1136
1137        snaclen = 10 + 1 + strlen(uin) + 3 + strlen(reason);
1138
1139        if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, snaclen))) {
1140                return -ENOMEM;
1141        }
1142
1143        snacid = aim_cachesnac(sess, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREP, 0x0000, NULL, 0);
1144        aim_putsnac(&fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREP, 0x0000, snacid);
1145
1146        aimbs_put8(&fr->data, strlen(uin));
1147        aimbs_putraw(&fr->data, (guint8 *) uin, strlen(uin));
1148        aimbs_put8(&fr->data, yesno);
1149        aimbs_put16(&fr->data, strlen(reason));
1150        aimbs_putraw(&fr->data, (guint8 *) reason, strlen(reason));
1151
1152        aim_tx_enqueue(sess, fr);
1153
1154        return(0);
1155}
1156
1157
1158/*
1159 * SSI Add/Mod/Del Item(s).
1160 *
1161 * Sends the SNAC to add, modify, or delete an item from the server-stored
1162 * information.  These 3 SNACs all have an identical structure.  The only
1163 * difference is the subtype that is set for the SNAC.
1164 *
1165 */
1166int aim_ssi_addmoddel(aim_session_t *sess, aim_conn_t *conn, struct aim_ssi_item **items, unsigned int num,
1167                      guint16 subtype)
1168{
1169        aim_frame_t *fr;
1170        aim_snacid_t snacid;
1171        int i, snaclen, listlen;
1172        char *list = NULL;
1173
1174        if (!sess || !conn || !items || !num) {
1175                return -EINVAL;
1176        }
1177
1178        snaclen = 10; /* For family, subtype, flags, and SNAC ID */
1179        listlen = 0;
1180        for (i = 0; i < num; i++) {
1181                snaclen += 10; /* For length, GID, BID, type, and length */
1182                if (items[i]->name) {
1183                        snaclen += strlen(items[i]->name);
1184
1185                        if (subtype == AIM_CB_SSI_ADD) {
1186                                list = g_realloc(list, listlen + strlen(items[i]->name) + 1);
1187                                strcpy(list + listlen, items[i]->name);
1188                                listlen += strlen(items[i]->name) + 1;
1189                        }
1190                } else {
1191                        if (subtype == AIM_CB_SSI_ADD) {
1192                                list = g_realloc(list, listlen + 1);
1193                                list[listlen] = '\0';
1194                                listlen++;
1195                        }
1196                }
1197                if (items[i]->data) {
1198                        snaclen += aim_sizetlvchain((aim_tlvlist_t **) &items[i]->data);
1199                }
1200        }
1201
1202        if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, snaclen))) {
1203                return -ENOMEM;
1204        }
1205
1206        snacid = aim_cachesnac(sess, AIM_CB_FAM_SSI, subtype, 0x0000, list, list ? listlen : 0);
1207        aim_putsnac(&fr->data, AIM_CB_FAM_SSI, subtype, 0x0000, snacid);
1208
1209        g_free(list);
1210
1211        for (i = 0; i < num; i++) {
1212                aimbs_put16(&fr->data, items[i]->name ? strlen(items[i]->name) : 0);
1213                if (items[i]->name) {
1214                        aimbs_putraw(&fr->data, (guint8 *) items[i]->name, strlen(items[i]->name));
1215                }
1216                aimbs_put16(&fr->data, items[i]->gid);
1217                aimbs_put16(&fr->data, items[i]->bid);
1218                aimbs_put16(&fr->data, items[i]->type);
1219                aimbs_put16(&fr->data, items[i]->data ? aim_sizetlvchain((aim_tlvlist_t **) &items[i]->data) : 0);
1220                if (items[i]->data) {
1221                        aim_writetlvchain(&fr->data, (aim_tlvlist_t **) &items[i]->data);
1222                }
1223        }
1224
1225        aim_ssi_enqueue(sess, conn, fr);
1226
1227        return 0;
1228}
1229
1230/*
1231 * SSI Add/Mod/Del Ack.
1232 *
1233 * Response to add, modify, or delete SNAC (sent with aim_ssi_addmoddel).
1234 *
1235 */
1236static int parseack(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
1237{
1238        int ret = 0;
1239        aim_rxcallback_t userfunc;
1240        aim_snac_t *origsnac;
1241
1242        sess->ssi.waiting_for_ack = 0;
1243        aim_ssi_dispatch(sess, rx->conn);
1244
1245        origsnac = aim_remsnac(sess, snac->id);
1246
1247        if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) {
1248                ret = userfunc(sess, rx, origsnac);
1249        }
1250
1251        if (origsnac) {
1252                g_free(origsnac->data);
1253                g_free(origsnac);
1254        }
1255
1256        return ret;
1257}
1258
1259/*
1260 * SSI Begin Data Modification.
1261 *
1262 * Tells the server you're going to start modifying data.
1263 *
1264 */
1265int aim_ssi_modbegin(aim_session_t *sess, aim_conn_t *conn)
1266{
1267        return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, AIM_CB_SSI_EDITSTART);
1268}
1269
1270/*
1271 * SSI End Data Modification.
1272 *
1273 * Tells the server you're done modifying data.
1274 *
1275 */
1276int aim_ssi_modend(aim_session_t *sess, aim_conn_t *conn)
1277{
1278        return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, AIM_CB_SSI_EDITSTOP);
1279}
1280
1281/*
1282 * SSI Data Unchanged.
1283 *
1284 * Response to aim_ssi_reqdata() if the server-side data is not newer than
1285 * posted local stamp/revision.
1286 *
1287 */
1288static int parsedataunchanged(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac,
1289                              aim_bstream_t *bs)
1290{
1291        int ret = 0;
1292        aim_rxcallback_t userfunc;
1293
1294        sess->ssi.received_data = 1;
1295
1296        if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) {
1297                ret = userfunc(sess, rx);
1298        }
1299
1300        return ret;
1301}
1302
1303static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
1304{
1305
1306        if (snac->subtype == AIM_CB_SSI_RIGHTSINFO) {
1307                return parserights(sess, mod, rx, snac, bs);
1308        } else if (snac->subtype == AIM_CB_SSI_LIST) {
1309                return parsedata(sess, mod, rx, snac, bs);
1310        } else if (snac->subtype == AIM_CB_SSI_SRVACK) {
1311                return parseack(sess, mod, rx, snac, bs);
1312        } else if (snac->subtype == AIM_CB_SSI_NOLIST) {
1313                return parsedataunchanged(sess, mod, rx, snac, bs);
1314        }
1315
1316        return 0;
1317}
1318
1319static void ssi_shutdown(aim_session_t *sess, aim_module_t *mod)
1320{
1321        aim_ssi_freelist(sess);
1322
1323        return;
1324}
1325
1326int ssi_modfirst(aim_session_t *sess, aim_module_t *mod)
1327{
1328
1329        mod->family = AIM_CB_FAM_SSI;
1330        mod->version = 0x0003;
1331        mod->toolid = 0x0110;
1332        mod->toolversion = 0x0629;
1333        mod->flags = 0;
1334        strncpy(mod->name, "ssi", sizeof(mod->name));
1335        mod->snachandler = snachandler;
1336        mod->shutdown = ssi_shutdown;
1337
1338        return 0;
1339}
Note: See TracBrowser for help on using the repository browser.