2 * Purple's oscar protocol plugin
3 * This file is the legal property of its developers.
4 * Please see the AUTHORS file distributed alongside this file.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
22 * Family 0x0013 - Server-Side/Stored Information.
24 * Relatively new facility that allows certain types of information, such as
25 * a user's buddy list, permit/deny list, and permit/deny preferences, to be
26 * stored on the server, so that they can be accessed from any client.
28 * We keep 2 copies of SSI data:
29 * 1) An exact copy of what is stored on the AIM servers.
30 * 2) A local copy that we make changes to, and then send diffs
31 * between this and the exact copy to keep them in sync.
33 * All the "aim_ssi_itemlist_bleh" functions near the top just modify the list
34 * that is given to them (i.e. they don't send SNACs).
36 * The SNAC sending and receiving functions are lower down in the file, and
37 * they're simpler. They are in the order of the subtypes they deal with,
38 * starting with the request rights function (subtype 0x0002), then parse
39 * rights (subtype 0x0003), then--well, you get the idea.
41 * This is entirely too complicated.
42 * You don't know the half of it.
49 static int aim_ssi_addmoddel(OscarData
*od
);
52 * List types based on http://dev.aol.com/aim/oscar/#FEEDBAG (archive.org)
53 * and http://iserverd.khstu.ru/oscar/ssi_item.html
55 * @param type The type of a list item as integer number, as provided by an aim_ssi_item struct.
56 * @return Returns the name of the item type as a character string.
59 aim_ssi_type_to_string(guint16 type
)
66 static const struct TypeStringPair type_strings
[] = {
69 { 0x0002, "Permit/Visible" },
70 { 0x0003, "Deny/Invisible" },
72 { 0x0005, "PresencePrefs" },
73 { 0x0006, "Non-Buddy Info" },
74 { 0x0009, "ClientPrefs" },
75 { 0x000e, "ICQDeny/Ignore" },
76 { 0x0014, "Buddy Icon" },
77 { 0x0015, "Recent Buddies" },
78 { 0x0019, "Non-Buddy" },
79 { 0x001d, "Vanity Info" },
80 { 0x0020, "ICQ-MDir" },
81 { 0x0029, "Facebook" },
84 for (i
= 0; i
< G_N_ELEMENTS(type_strings
); i
++) {
85 if (type_strings
[i
].type
== type
) {
86 return type_strings
[i
].string
;
92 /** For debug log output: Appends a line containing information about a given list item to a string.
94 * @param str String to which the line will be appended.
95 * @param prefix A string which will be prepended to the line.
96 * @param item List item from which information is extracted.
99 aim_ssi_item_debug_append(GString
*str
, char *prefix
, struct aim_ssi_item
*item
)
101 g_string_append_printf(str
,
102 "%s gid=0x%04hx, bid=0x%04hx, list_type=0x%04hx [%s], name=%s.\n",
103 prefix
, item
->gid
, item
->bid
, item
->type
, aim_ssi_type_to_string(item
->type
),
104 item
->name
? item
->name
: "(null)");
108 * Locally rebuild the 0x00c8 TLV in the additional data of the given group.
110 * @param list A pointer to a pointer to the current list of items.
111 * @param name A null terminated string containing the group name, or NULL
112 * if you want to modify the master group.
113 * @return Return a pointer to the modified item.
116 aim_ssi_itemlist_rebuildgroup(struct aim_ssi_item
*list
, const char *name
)
119 struct aim_ssi_item
*cur
, *group
;
122 if (!(group
= aim_ssi_itemlist_finditem(list
, name
, NULL
, AIM_SSI_TYPE_GROUP
)))
125 /* Find the length for the new additional data */
127 if (group
->gid
== 0x0000) {
128 for (cur
=list
; cur
; cur
=cur
->next
)
129 if ((cur
->type
== AIM_SSI_TYPE_GROUP
) && (cur
->gid
!= 0x0000))
132 for (cur
=list
; cur
; cur
=cur
->next
)
133 if ((cur
->gid
== group
->gid
) && (cur
->type
== AIM_SSI_TYPE_BUDDY
))
137 /* Build the new TLV list */
141 newdata
= (guint8
*)g_malloc((newlen
)*sizeof(guint8
));
143 if (group
->gid
== 0x0000) {
144 for (cur
=list
; cur
; cur
=cur
->next
)
145 if ((cur
->type
== AIM_SSI_TYPE_GROUP
) && (cur
->gid
!= 0x0000))
146 newlen
+= aimutil_put16(newdata
+newlen
, cur
->gid
);
148 for (cur
=list
; cur
; cur
=cur
->next
)
149 if ((cur
->gid
== group
->gid
) && (cur
->type
== AIM_SSI_TYPE_BUDDY
))
150 newlen
+= aimutil_put16(newdata
+newlen
, cur
->bid
);
152 aim_tlvlist_replace_raw(&group
->data
, 0x00c8, newlen
, newdata
);
159 * Locally add a new item to the given item list.
161 * @param list A pointer to a pointer to the current list of items.
162 * @param name A null terminated string of the name of the new item, or NULL if the
163 * item should have no name.
164 * @param gid The group ID# you want the new item to have, or 0xFFFF if we should pick something.
165 * @param bid The buddy ID# you want the new item to have, or 0xFFFF if we should pick something.
166 * @param type The type of the item, 0x0000 for a contact, 0x0001 for a group, etc.
167 * @param data The additional data for the new item.
168 * @return A pointer to the newly created item.
170 static struct aim_ssi_item
*aim_ssi_itemlist_add(struct aim_ssi_item
**list
, const char *name
, guint16 gid
, guint16 bid
, guint16 type
, GSList
*data
)
173 struct aim_ssi_item
*cur
, *new;
175 new = g_new(struct aim_ssi_item
, 1);
178 new->name
= g_strdup(name
);
180 /* Set the group ID# and buddy ID# */
183 if (type
== AIM_SSI_TYPE_GROUP
) {
184 if ((new->gid
== 0xFFFF) && name
) {
188 for (cur
= *list
; cur
!= NULL
; cur
= cur
->next
)
189 if ((cur
->type
== AIM_SSI_TYPE_GROUP
) && (cur
->gid
== new->gid
)) {
195 } else if (new->gid
== 0x0000) {
197 * This is weird, but apparently items in the root group can't
198 * have a buddy ID equal to any group ID. You'll get error
199 * 0x0003 when trying to add, which is "item already exists"
201 if (new->bid
== 0xFFFF) {
205 for (cur
= *list
; cur
!= NULL
; cur
= cur
->next
)
206 if (cur
->bid
== new->bid
|| cur
->gid
== new->bid
) {
213 if (new->bid
== 0xFFFF) {
217 for (cur
= *list
; cur
!= NULL
; cur
= cur
->next
)
218 if (cur
->bid
== new->bid
&& cur
->gid
== new->gid
) {
229 /* Set the TLV list */
230 new->data
= aim_tlvlist_copy(data
);
232 /* Add the item to the list in the correct numerical position. Fancy, eh? */
234 if ((new->gid
< (*list
)->gid
) || ((new->gid
== (*list
)->gid
) && (new->bid
< (*list
)->bid
))) {
238 struct aim_ssi_item
*prev
;
239 for ((prev
=*list
, cur
=(*list
)->next
); (cur
&& ((new->gid
> cur
->gid
) || ((new->gid
== cur
->gid
) && (new->bid
> cur
->bid
)))); prev
=cur
, cur
=cur
->next
);
240 new->next
= prev
->next
;
252 * Locally delete an item from the given item list.
254 * @param list A pointer to a pointer to the current list of items.
255 * @param del A pointer to the item you want to remove from the list.
256 * @return Return 0 if no errors, otherwise return the error number.
258 static int aim_ssi_itemlist_del(struct aim_ssi_item
**list
, struct aim_ssi_item
*del
)
260 if (!(*list
) || !del
)
263 /* Remove the item from the list */
265 *list
= (*list
)->next
;
267 struct aim_ssi_item
*cur
;
268 for (cur
=*list
; (cur
->next
&& (cur
->next
!=del
)); cur
=cur
->next
);
270 cur
->next
= del
->next
;
273 /* Free the removed item */
275 aim_tlvlist_free(del
->data
);
282 * Compare two items to see if they have the same data.
284 * @param cur1 A pointer to a pointer to the first item.
285 * @param cur2 A pointer to a pointer to the second item.
286 * @return Return 0 if no differences, or a number if there are differences.
288 static int aim_ssi_itemlist_cmp(struct aim_ssi_item
*cur1
, struct aim_ssi_item
*cur2
)
293 if (cur1
->data
&& !cur2
->data
)
296 if (!cur1
->data
&& cur2
->data
)
299 if ((cur1
->data
&& cur2
->data
) && (aim_tlvlist_cmp(cur1
->data
, cur2
->data
)))
302 if (cur1
->name
&& !cur2
->name
)
305 if (!cur1
->name
&& cur2
->name
)
308 if (cur1
->name
&& cur2
->name
&& oscar_util_name_compare(cur1
->name
, cur2
->name
))
311 if (cur1
->gid
!= cur2
->gid
)
314 if (cur1
->bid
!= cur2
->bid
)
317 if (cur1
->type
!= cur2
->type
)
323 static gboolean
aim_ssi_itemlist_valid(struct aim_ssi_item
*list
, struct aim_ssi_item
*item
)
325 struct aim_ssi_item
*cur
;
326 for (cur
=list
; cur
; cur
=cur
->next
)
333 * Locally find an item given a group ID# and a buddy ID#.
335 * @param list A pointer to the current list of items.
336 * @param gid The group ID# of the desired item.
337 * @param bid The buddy ID# of the desired item.
338 * @return Return a pointer to the item if found, else return NULL;
340 struct aim_ssi_item
*aim_ssi_itemlist_find(struct aim_ssi_item
*list
, guint16 gid
, guint16 bid
)
342 struct aim_ssi_item
*cur
;
343 for (cur
=list
; cur
; cur
=cur
->next
)
344 if ((cur
->gid
== gid
) && (cur
->bid
== bid
))
350 * Locally find an item given a group name, buddy name, and type. If group name
351 * and buddy name are null, then just return the first item of the given type.
353 * @param list A pointer to the current list of items.
354 * @param gn The group name of the desired item.
355 * @param bn The buddy name of the desired item.
356 * @param type The type of the desired item.
357 * @return Return a pointer to the item if found, else return NULL.
359 struct aim_ssi_item
*aim_ssi_itemlist_finditem(struct aim_ssi_item
*list
, const char *gn
, const char *bn
, guint16 type
)
361 struct aim_ssi_item
*cur
;
365 if (gn
&& bn
) { /* For finding buddies in groups */
366 for (cur
=list
; cur
; cur
=cur
->next
)
367 if ((cur
->type
== type
) && (cur
->name
) && !(oscar_util_name_compare(cur
->name
, bn
))) {
368 struct aim_ssi_item
*curg
;
369 for (curg
=list
; curg
; curg
=curg
->next
)
370 if ((curg
->type
== AIM_SSI_TYPE_GROUP
) && (curg
->gid
== cur
->gid
) && (curg
->name
) && !(oscar_util_name_compare(curg
->name
, gn
)))
374 } else if (gn
) { /* For finding groups */
375 for (cur
=list
; cur
; cur
=cur
->next
) {
376 if ((cur
->type
== type
) && (cur
->bid
== 0x0000) && (cur
->name
) && !(oscar_util_name_compare(cur
->name
, gn
))) {
381 } else if (bn
) { /* For finding permits, denies, and ignores */
382 for (cur
=list
; cur
; cur
=cur
->next
) {
383 if ((cur
->type
== type
) && (cur
->name
) && !(oscar_util_name_compare(cur
->name
, bn
))) {
388 /* For stuff without names--permit deny setting, visibility mask, etc. */
389 } else for (cur
=list
; cur
; cur
=cur
->next
) {
390 if ((cur
->type
== type
) && (!cur
->name
))
398 * Check if the given buddy exists in any group in the buddy list.
400 * @param list A pointer to the current list of items.
401 * @param bn The group name of the desired item.
402 * @return Return a pointer to the name of the item if found, else return NULL;
404 struct aim_ssi_item
*aim_ssi_itemlist_exists(struct aim_ssi_item
*list
, const char *bn
)
408 return aim_ssi_itemlist_finditem(list
, NULL
, bn
, AIM_SSI_TYPE_BUDDY
);
412 * Locally find the parent item of the given buddy name.
414 * @param list A pointer to the current list of items.
415 * @param bn The buddy name of the desired item.
416 * @return Return a pointer to the name of the item if found, else return NULL;
418 char *aim_ssi_itemlist_findparentname(struct aim_ssi_item
*list
, const char *bn
)
420 struct aim_ssi_item
*cur
, *curg
;
423 if (!(cur
= aim_ssi_itemlist_exists(list
, bn
)))
425 if (!(curg
= aim_ssi_itemlist_find(list
, cur
->gid
, 0x0000)))
431 * Locally find the permit/deny setting item, and return the setting.
433 * @param list A pointer to the current list of items.
434 * @return Return the current SSI permit deny setting, or 0 if no setting was found.
436 int aim_ssi_getpermdeny(struct aim_ssi_item
*list
)
438 struct aim_ssi_item
*cur
= aim_ssi_itemlist_finditem(list
, NULL
, NULL
, AIM_SSI_TYPE_PDINFO
);
440 aim_tlv_t
*tlv
= aim_tlv_gettlv(cur
->data
, 0x00ca, 1);
441 if (tlv
&& tlv
->value
)
442 return aimutil_get8(tlv
->value
);
448 * Locally find the presence flag item, and return the setting. The returned setting is a
449 * bitmask of the preferences. See the AIM_SSI_PRESENCE_FLAG_* #defines in oscar.h.
451 * @param list A pointer to the current list of items.
452 * @return Return the current set of preferences.
454 guint32
aim_ssi_getpresence(struct aim_ssi_item
*list
)
456 struct aim_ssi_item
*cur
= aim_ssi_itemlist_finditem(list
, NULL
, NULL
, AIM_SSI_TYPE_PRESENCEPREFS
);
458 aim_tlv_t
*tlv
= aim_tlv_gettlv(cur
->data
, 0x00c9, 1);
459 if (tlv
&& tlv
->length
)
460 return aimutil_get32(tlv
->value
);
466 * Locally find the alias of the given buddy.
468 * @param list A pointer to the current list of items.
469 * @param gn The group of the buddy.
470 * @param bn The name of the buddy.
471 * @return A pointer to a NULL terminated string that is the buddy's
472 * alias, or NULL if the buddy has no alias. You should free
473 * this returned value!
475 char *aim_ssi_getalias(struct aim_ssi_item
*list
, const char *gn
, const char *bn
)
477 struct aim_ssi_item
*cur
= aim_ssi_itemlist_finditem(list
, gn
, bn
, AIM_SSI_TYPE_BUDDY
);
479 aim_tlv_t
*tlv
= aim_tlv_gettlv(cur
->data
, 0x0131, 1);
480 if (tlv
&& tlv
->length
)
481 return g_strndup((const gchar
*)tlv
->value
, tlv
->length
);
487 * Locally find the comment of the given buddy.
489 * @param list A pointer to the current list of items.
490 * @param gn The group of the buddy.
491 * @param bn The name of the buddy.
492 * @return A pointer to a NULL terminated string that is the buddy's
493 * comment, or NULL if the buddy has no comment. You should free
494 * this returned value!
496 char *aim_ssi_getcomment(struct aim_ssi_item
*list
, const char *gn
, const char *bn
)
498 struct aim_ssi_item
*cur
= aim_ssi_itemlist_finditem(list
, gn
, bn
, AIM_SSI_TYPE_BUDDY
);
500 aim_tlv_t
*tlv
= aim_tlv_gettlv(cur
->data
, 0x013c, 1);
501 if (tlv
&& tlv
->length
) {
502 return g_strndup((const gchar
*)tlv
->value
, tlv
->length
);
509 * Locally find if you are waiting for authorization for a buddy.
511 * @param list A pointer to the current list of items.
512 * @param gn The group of the buddy.
513 * @param bn The name of the buddy.
514 * @return 1 if you are waiting for authorization; 0 if you are not
516 gboolean
aim_ssi_waitingforauth(struct aim_ssi_item
*list
, const char *gn
, const char *bn
)
518 struct aim_ssi_item
*cur
= aim_ssi_itemlist_finditem(list
, gn
, bn
, AIM_SSI_TYPE_BUDDY
);
520 if (aim_tlv_gettlv(cur
->data
, 0x0066, 1))
527 * If there are changes, then create temporary items and
530 * @param od The oscar session.
531 * @return Return 0 if no errors, otherwise return the error number.
533 static int aim_ssi_sync(OscarData
*od
)
535 struct aim_ssi_item
*cur1
, *cur2
;
536 struct aim_ssi_tmp
*cur
, *new;
538 GString
*debugstr
= g_string_new("");
541 * The variable "n" is used to limit the number of addmoddel's that
542 * are performed in a single SNAC. It will hopefully keep the size
543 * of the SNAC below the maximum SNAC size.
549 /* If we're waiting for an ack, we shouldn't do anything else */
550 if (od
->ssi
.waiting_for_ack
)
554 * Compare the 2 lists and create an aim_ssi_tmp for each difference.
555 * We should only send either additions, modifications, or deletions
556 * before waiting for an acknowledgement. So first do deletions, then
557 * additions, then modifications. Also, both the official and the local
558 * list should be in ascending numerical order for the group ID#s and the
559 * buddy ID#s, which makes things more efficient. I think.
563 if (!od
->ssi
.pending
) {
564 for (cur1
=od
->ssi
.official
; cur1
&& (n
< 15); cur1
=cur1
->next
) {
565 if (!aim_ssi_itemlist_find(od
->ssi
.local
, cur1
->gid
, cur1
->bid
)) {
567 new = g_new(struct aim_ssi_tmp
, 1);
568 new->action
= SNAC_SUBTYPE_FEEDBAG_DEL
;
573 if (od
->ssi
.pending
) {
574 for (cur
=od
->ssi
.pending
; cur
->next
; cur
=cur
->next
);
577 od
->ssi
.pending
= new;
578 aim_ssi_item_debug_append(debugstr
, "Deleting item ", cur1
);
584 if (!od
->ssi
.pending
) {
585 for (cur1
=od
->ssi
.local
; cur1
&& (n
< 15); cur1
=cur1
->next
) {
586 if (!aim_ssi_itemlist_find(od
->ssi
.official
, cur1
->gid
, cur1
->bid
)) {
588 new = g_new(struct aim_ssi_tmp
, 1);
589 new->action
= SNAC_SUBTYPE_FEEDBAG_ADD
;
594 if (od
->ssi
.pending
) {
595 for (cur
=od
->ssi
.pending
; cur
->next
; cur
=cur
->next
);
598 od
->ssi
.pending
= new;
599 aim_ssi_item_debug_append(debugstr
, "Adding item ", cur1
);
605 if (!od
->ssi
.pending
) {
606 for (cur1
=od
->ssi
.local
; cur1
&& (n
< 15); cur1
=cur1
->next
) {
607 cur2
= aim_ssi_itemlist_find(od
->ssi
.official
, cur1
->gid
, cur1
->bid
);
608 if (cur2
&& (aim_ssi_itemlist_cmp(cur1
, cur2
))) {
610 new = g_new(struct aim_ssi_tmp
, 1);
611 new->action
= SNAC_SUBTYPE_FEEDBAG_MOD
;
616 if (od
->ssi
.pending
) {
617 for (cur
=od
->ssi
.pending
; cur
->next
; cur
=cur
->next
);
620 od
->ssi
.pending
= new;
621 aim_ssi_item_debug_append(debugstr
, "Modifying item ", cur1
);
625 if (debugstr
->len
> 0) {
626 purple_debug_info("oscar", "%s", debugstr
->str
);
627 if (purple_debug_is_verbose()) {
628 g_string_truncate(debugstr
, 0);
629 for (cur1
= od
->ssi
.local
; cur1
; cur1
= cur1
->next
)
630 aim_ssi_item_debug_append(debugstr
, "\t", cur1
);
631 purple_debug_misc("oscar", "Dumping item list of account %s:\n%s",
632 purple_connection_get_account(od
->gc
)->username
, debugstr
->str
);
635 g_string_free(debugstr
, TRUE
);
637 /* We're out of stuff to do, so tell the AIM servers we're done and exit */
638 if (!od
->ssi
.pending
) {
639 if (od
->ssi
.in_transaction
) {
641 od
->ssi
.in_transaction
= FALSE
;
646 /* If this is the first in a series of add/mod/del
647 * requests then send the "begin transaction" message. */
648 if (!od
->ssi
.in_transaction
)
650 aim_ssi_modbegin(od
);
651 od
->ssi
.in_transaction
= TRUE
;
654 /* Make sure we don't send anything else between now
655 * and when we receive the ack for the following operation */
656 od
->ssi
.waiting_for_ack
= TRUE
;
658 /* Now go mail off our data and wait 4 to 6 weeks */
659 return aim_ssi_addmoddel(od
);;
665 * This doesn't remove it from the server, that's different.
667 * @param od The oscar odion.
668 * @return Return 0 if no errors, otherwise return the error number.
671 aim_ssi_freelist(OscarData
*od
)
673 struct aim_ssi_item
*cur
, *del
;
674 struct aim_ssi_tmp
*curtmp
, *deltmp
;
676 cur
= od
->ssi
.official
;
681 aim_tlvlist_free(del
->data
);
690 aim_tlvlist_free(del
->data
);
694 curtmp
= od
->ssi
.pending
;
697 curtmp
= curtmp
->next
;
701 od
->ssi
.numitems
= 0;
702 od
->ssi
.official
= NULL
;
703 od
->ssi
.local
= NULL
;
704 od
->ssi
.pending
= NULL
;
705 od
->ssi
.timestamp
= (time_t)0;
709 * This "cleans" the ssi list. It does the following:
710 * 1) Makes sure all buddies, permits, and denies have names.
711 * 2) Makes sure that all buddies are in a group that exist.
712 * 3) Deletes any empty groups
714 * @param od The oscar odion.
715 * @return Return 0 if no errors, otherwise return the error number.
717 int aim_ssi_cleanlist(OscarData
*od
)
719 struct aim_ssi_item
*cur
, *next
;
724 /* Delete any buddies, permits, or denies with empty names. */
725 /* If there are any buddies directly in the master group, add them to a real group. */
726 /* DESTROY any buddies that are directly in the master group. */
727 /* Do the same for buddies that are in a non-existant group. */
728 /* This will kind of mess up if you hit the item limit, but this function isn't too critical */
733 if (cur
->type
== AIM_SSI_TYPE_BUDDY
)
734 aim_ssi_delbuddy(od
, NULL
, NULL
);
735 else if (cur
->type
== AIM_SSI_TYPE_PERMIT
|| cur
->type
== AIM_SSI_TYPE_DENY
|| cur
->type
== AIM_SSI_TYPE_ICQDENY
)
736 aim_ssi_del_from_private_list(od
, NULL
, cur
->type
);
737 } else if ((cur
->type
== AIM_SSI_TYPE_BUDDY
) && ((cur
->gid
== 0x0000) || (!aim_ssi_itemlist_find(od
->ssi
.local
, cur
->gid
, 0x0000)))) {
738 char *alias
= aim_ssi_getalias(od
->ssi
.local
, NULL
, cur
->name
);
739 aim_ssi_addbuddy(od
, cur
->name
, "orphans", NULL
, alias
, NULL
, NULL
, FALSE
);
740 aim_ssi_delbuddy(od
, cur
->name
, NULL
);
746 /* Make sure there aren't any duplicate buddies in a group, or duplicate permits or denies */
749 if ((cur
->type
== AIM_SSI_TYPE_BUDDY
) || (cur
->type
== AIM_SSI_TYPE_PERMIT
) || (cur
->type
== AIM_SSI_TYPE_DENY
))
751 struct aim_ssi_item
*cur2
, *next2
;
755 if ((cur
->type
== cur2
->type
) && (cur
->gid
== cur2
->gid
) && (cur
->name
!= NULL
) && (cur2
->name
!= NULL
) && (!oscar_util_name_compare(cur
->name
, cur2
->name
))) {
756 aim_ssi_itemlist_del(&od
->ssi
.local
, cur2
);
764 /* If we've made any changes then sync our list with the server's */
765 return aim_ssi_sync(od
);
769 * Add a buddy to the list.
771 * @param od The oscar odion.
772 * @param name The name of the item.
773 * @param group The group of the item.
774 * @param data A TLV list to use as the additional data for this item.
775 * @param alias The alias/nickname of the item, or NULL.
776 * @param comment The buddy comment for the item, or NULL.
777 * @param smsnum The locally assigned SMS number, or NULL.
778 * @return Return 0 if no errors, otherwise return the error number.
780 int aim_ssi_addbuddy(OscarData
*od
, const char *name
, const char *group
, GSList
*data
, const char *alias
, const char *comment
, const char *smsnum
, gboolean needauth
)
782 struct aim_ssi_item
*parent
;
784 if (!od
|| !name
|| !group
)
787 /* Find the parent */
788 if (!(parent
= aim_ssi_itemlist_finditem(od
->ssi
.local
, group
, NULL
, AIM_SSI_TYPE_GROUP
))) {
789 /* Find the parent's parent (the master group) */
790 if (aim_ssi_itemlist_find(od
->ssi
.local
, 0x0000, 0x0000) == NULL
)
791 aim_ssi_itemlist_add(&od
->ssi
.local
, NULL
, 0x0000, 0x0000, AIM_SSI_TYPE_GROUP
, NULL
);
794 parent
= aim_ssi_itemlist_add(&od
->ssi
.local
, group
, 0xFFFF, 0x0000, AIM_SSI_TYPE_GROUP
, NULL
);
796 /* Modify the parent's parent (the master group) */
797 aim_ssi_itemlist_rebuildgroup(od
->ssi
.local
, NULL
);
800 /* Create a TLV list for the new buddy */
802 aim_tlvlist_add_noval(&data
, 0x0066);
804 aim_tlvlist_add_str(&data
, 0x0131, alias
);
806 aim_tlvlist_add_str(&data
, 0x013a, smsnum
);
808 aim_tlvlist_add_str(&data
, 0x013c, comment
);
810 /* Add that bad boy */
811 aim_ssi_itemlist_add(&od
->ssi
.local
, name
, parent
->gid
, 0xFFFF, AIM_SSI_TYPE_BUDDY
, data
);
812 aim_tlvlist_free(data
);
814 /* Modify the parent group */
815 aim_ssi_itemlist_rebuildgroup(od
->ssi
.local
, group
);
817 /* Sync our local list with the server list */
818 return aim_ssi_sync(od
);
822 aim_ssi_add_to_private_list(OscarData
*od
, const char* name
, guint16 list_type
)
824 if (!od
|| !name
|| !od
->ssi
.received_data
)
827 if (aim_ssi_itemlist_find(od
->ssi
.local
, 0x0000, 0x0000) == NULL
)
828 aim_ssi_itemlist_add(&od
->ssi
.local
, NULL
, 0x0000, 0x0000, AIM_SSI_TYPE_GROUP
, NULL
);
830 aim_ssi_itemlist_add(&od
->ssi
.local
, name
, 0x0000, 0xFFFF, list_type
, NULL
);
831 return aim_ssi_sync(od
);
835 aim_ssi_del_from_private_list(OscarData
* od
, const char* name
, guint16 list_type
)
837 struct aim_ssi_item
*del
;
842 if (!(del
= aim_ssi_itemlist_finditem(od
->ssi
.local
, NULL
, name
, list_type
)))
845 aim_ssi_itemlist_del(&od
->ssi
.local
, del
);
846 return aim_ssi_sync(od
);
850 * Deletes a buddy from the list.
852 * @param od The oscar odion.
853 * @param name The name of the item, or NULL.
854 * @param group The group of the item, or NULL.
855 * @return Return 0 if no errors, otherwise return the error number.
857 int aim_ssi_delbuddy(OscarData
*od
, const char *name
, const char *group
)
859 struct aim_ssi_item
*del
;
865 if (!(del
= aim_ssi_itemlist_finditem(od
->ssi
.local
, group
, name
, AIM_SSI_TYPE_BUDDY
)))
868 /* Remove the item from the list */
869 aim_ssi_itemlist_del(&od
->ssi
.local
, del
);
871 /* Modify the parent group */
872 aim_ssi_itemlist_rebuildgroup(od
->ssi
.local
, group
);
874 /* Sync our local list with the server list */
875 return aim_ssi_sync(od
);
879 * Deletes a group from the list.
881 * @param od The oscar odion.
882 * @param group The name of the group.
883 * @return Return 0 if no errors, otherwise return the error number.
885 int aim_ssi_delgroup(OscarData
*od
, const char *group
)
887 struct aim_ssi_item
*del
;
894 if (!(del
= aim_ssi_itemlist_finditem(od
->ssi
.local
, group
, NULL
, AIM_SSI_TYPE_GROUP
)))
897 /* Don't delete the group if it's not empty */
898 tlv
= aim_tlv_gettlv(del
->data
, 0x00c8, 1);
899 if (tlv
&& tlv
->length
> 0)
902 /* Remove the item from the list */
903 aim_ssi_itemlist_del(&od
->ssi
.local
, del
);
905 /* Modify the parent group */
906 aim_ssi_itemlist_rebuildgroup(od
->ssi
.local
, group
);
908 /* Sync our local list with the server list */
909 return aim_ssi_sync(od
);
913 * Move a buddy from one group to another group. This basically just deletes the
914 * buddy and re-adds it.
916 * @param od The oscar odion.
917 * @param oldgn The group that the buddy is currently in.
918 * @param newgn The group that the buddy should be moved in to.
919 * @param bn The name of the buddy to be moved.
920 * @return Return 0 if no errors, otherwise return the error number.
922 int aim_ssi_movebuddy(OscarData
*od
, const char *oldgn
, const char *newgn
, const char *bn
)
924 struct aim_ssi_item
*buddy
;
928 buddy
= aim_ssi_itemlist_finditem(od
->ssi
.local
, oldgn
, bn
, AIM_SSI_TYPE_BUDDY
);
932 /* Make a copy of the buddy's TLV list */
933 data
= aim_tlvlist_copy(buddy
->data
);
935 /* Delete the old item */
936 aim_ssi_delbuddy(od
, bn
, oldgn
);
938 /* Add the new item using the EXACT SAME TLV list */
939 aim_ssi_addbuddy(od
, bn
, newgn
, data
, NULL
, NULL
, NULL
, FALSE
);
945 * Change the alias stored on the server for a given buddy.
947 * @param od The oscar odion.
948 * @param gn The group that the buddy is currently in.
949 * @param bn The name of the buddy.
950 * @param alias The new alias for the buddy, or NULL if you want to remove
952 * @return Return 0 if no errors, otherwise return the error number.
954 int aim_ssi_aliasbuddy(OscarData
*od
, const char *gn
, const char *bn
, const char *alias
)
956 struct aim_ssi_item
*tmp
;
958 if (!od
|| !gn
|| !bn
)
961 if (!(tmp
= aim_ssi_itemlist_finditem(od
->ssi
.local
, gn
, bn
, AIM_SSI_TYPE_BUDDY
)))
964 /* Either add or remove the 0x0131 TLV from the TLV chain */
965 if ((alias
!= NULL
) && (strlen(alias
) > 0))
966 aim_tlvlist_replace_str(&tmp
->data
, 0x0131, alias
);
968 aim_tlvlist_remove(&tmp
->data
, 0x0131);
970 /* Sync our local list with the server list */
971 return aim_ssi_sync(od
);
975 * Change the comment stored on the server for a given buddy.
977 * @param od The oscar odion.
978 * @param gn The group that the buddy is currently in.
979 * @param bn The name of the buddy.
980 * @param alias The new comment for the buddy, or NULL if you want to remove
982 * @return Return 0 if no errors, otherwise return the error number.
984 int aim_ssi_editcomment(OscarData
*od
, const char *gn
, const char *bn
, const char *comment
)
986 struct aim_ssi_item
*tmp
;
988 if (!od
|| !gn
|| !bn
)
991 if (!(tmp
= aim_ssi_itemlist_finditem(od
->ssi
.local
, gn
, bn
, AIM_SSI_TYPE_BUDDY
)))
994 /* Either add or remove the 0x0131 TLV from the TLV chain */
995 if ((comment
!= NULL
) && (strlen(comment
) > 0))
996 aim_tlvlist_replace_str(&tmp
->data
, 0x013c, comment
);
998 aim_tlvlist_remove(&tmp
->data
, 0x013c);
1000 /* Sync our local list with the server list */
1001 return aim_ssi_sync(od
);
1007 * @param od The oscar odion.
1008 * @param oldgn The old group name.
1009 * @param newgn The new group name.
1010 * @return Return 0 if no errors, otherwise return the error number.
1012 int aim_ssi_rename_group(OscarData
*od
, const char *oldgn
, const char *newgn
)
1014 struct aim_ssi_item
*group
;
1016 if (!od
|| !oldgn
|| !newgn
)
1019 if (!(group
= aim_ssi_itemlist_finditem(od
->ssi
.local
, oldgn
, NULL
, AIM_SSI_TYPE_GROUP
)))
1022 g_free(group
->name
);
1023 group
->name
= g_strdup(newgn
);
1025 /* Sync our local list with the server list */
1026 return aim_ssi_sync(od
);
1030 * Stores your permit/deny setting on the server, and starts using it.
1032 * @param od The oscar odion.
1033 * @param permdeny Your permit/deny setting. For ICQ accounts, it actually affects your visibility
1034 * and has nothing to do with blocking. Can be one of the following:
1035 * 1 - Allow all users
1036 * 2 - Block all users
1037 * 3 - Allow only the users below
1038 * 4 - Block only the users below
1039 * 5 - Allow only users on my buddy list
1040 * @return Return 0 if no errors, otherwise return the error number.
1042 int aim_ssi_setpermdeny(OscarData
*od
, guint8 permdeny
)
1044 struct aim_ssi_item
*tmp
;
1046 if (!od
|| !od
->ssi
.received_data
)
1049 /* Find the PDINFO item, or add it if it does not exist */
1050 if (!(tmp
= aim_ssi_itemlist_finditem(od
->ssi
.local
, NULL
, NULL
, AIM_SSI_TYPE_PDINFO
))) {
1051 /* Make sure the master group exists */
1052 if (aim_ssi_itemlist_find(od
->ssi
.local
, 0x0000, 0x0000) == NULL
)
1053 aim_ssi_itemlist_add(&od
->ssi
.local
, NULL
, 0x0000, 0x0000, AIM_SSI_TYPE_GROUP
, NULL
);
1055 tmp
= aim_ssi_itemlist_add(&od
->ssi
.local
, NULL
, 0x0000, 0xFFFF, AIM_SSI_TYPE_PDINFO
, NULL
);
1058 /* Need to add the 0x00ca TLV to the TLV chain */
1059 aim_tlvlist_replace_8(&tmp
->data
, 0x00ca, permdeny
);
1061 /* Sync our local list with the server list */
1062 return aim_ssi_sync(od
);
1066 * Set buddy icon information
1068 * @param od The oscar odion.
1069 * @param iconcsum The MD5 checksum of the icon you are using.
1070 * @param iconcsumlen Length of the MD5 checksum given above. Should be 0x10 bytes.
1071 * @return Return 0 if no errors, otherwise return the error number.
1073 int aim_ssi_seticon(OscarData
*od
, const guint8
*iconsum
, guint8 iconsumlen
)
1075 struct aim_ssi_item
*tmp
;
1078 if (!od
|| !iconsum
|| !iconsumlen
|| !od
->ssi
.received_data
)
1081 /* Find the ICONINFO item, or add it if it does not exist */
1082 if (!(tmp
= aim_ssi_itemlist_finditem(od
->ssi
.local
, NULL
, "1", AIM_SSI_TYPE_ICONINFO
))) {
1083 /* Make sure the master group exists */
1084 if (aim_ssi_itemlist_find(od
->ssi
.local
, 0x0000, 0x0000) == NULL
)
1085 aim_ssi_itemlist_add(&od
->ssi
.local
, NULL
, 0x0000, 0x0000, AIM_SSI_TYPE_GROUP
, NULL
);
1087 tmp
= aim_ssi_itemlist_add(&od
->ssi
.local
, "1", 0x0000, 0xFFFF, AIM_SSI_TYPE_ICONINFO
, NULL
);
1090 /* Need to add the 0x00d5 TLV to the TLV chain */
1091 csumdata
= (guint8
*)g_malloc((iconsumlen
+2)*sizeof(guint8
));
1092 (void)aimutil_put8(&csumdata
[0], 0x00);
1093 (void)aimutil_put8(&csumdata
[1], iconsumlen
);
1094 memcpy(&csumdata
[2], iconsum
, iconsumlen
);
1095 aim_tlvlist_replace_raw(&tmp
->data
, 0x00d5, (iconsumlen
+2) * sizeof(guint8
), csumdata
);
1098 /* Need to add the 0x0131 TLV to the TLV chain, used to cache the icon */
1099 aim_tlvlist_replace_noval(&tmp
->data
, 0x0131);
1101 /* Sync our local list with the server list */
1107 * Remove a reference to a server stored buddy icon. This will make your
1108 * icon stop showing up to other people.
1110 * Really this function just sets the icon to a dummy value. It's weird...
1111 * but I think the dummy value basically means "I don't have an icon!"
1113 * @param od The oscar session.
1114 * @return Return 0 if no errors, otherwise return the error number.
1116 int aim_ssi_delicon(OscarData
*od
)
1118 const guint8 csumdata
[] = {0x02, 0x01, 0xd2, 0x04, 0x72};
1120 return aim_ssi_seticon(od
, csumdata
, 5);
1124 * Stores your setting for various SSI settings. Whether you
1125 * should show up as idle or not, etc.
1127 * @param od The oscar odion.
1128 * @param presence A bitmask of the first 32 entries [0-31] from
1129 * http://dev.aol.com/aim/oscar/#FEEDBAG__BUDDY_PREFS
1130 * 0x00000002 - Hide "eBuddy group" (whatever that is)
1131 * 0x00000400 - Allow others to see your idle time
1132 * 0x00020000 - Don't show Recent Buddies
1133 * @return Return 0 if no errors, otherwise return the error number.
1135 int aim_ssi_setpresence(OscarData
*od
, guint32 presence
) {
1136 struct aim_ssi_item
*tmp
;
1138 if (!od
|| !od
->ssi
.received_data
)
1141 /* Find the PRESENCEPREFS item, or add it if it does not exist */
1142 if (!(tmp
= aim_ssi_itemlist_finditem(od
->ssi
.local
, NULL
, NULL
, AIM_SSI_TYPE_PRESENCEPREFS
))) {
1143 /* Make sure the master group exists */
1144 if (aim_ssi_itemlist_find(od
->ssi
.local
, 0x0000, 0x0000) == NULL
)
1145 aim_ssi_itemlist_add(&od
->ssi
.local
, NULL
, 0x0000, 0x0000, AIM_SSI_TYPE_GROUP
, NULL
);
1147 tmp
= aim_ssi_itemlist_add(&od
->ssi
.local
, NULL
, 0x0000, 0xFFFF, AIM_SSI_TYPE_PRESENCEPREFS
, NULL
);
1150 /* Need to add the x00c9 TLV to the TLV chain */
1151 aim_tlvlist_replace_32(&tmp
->data
, 0x00c9, presence
);
1153 /* Sync our local list with the server list */
1154 return aim_ssi_sync(od
);
1158 * Subtype 0x0002 - Request SSI Rights.
1160 int aim_ssi_reqrights(OscarData
*od
)
1162 FlapConnection
*conn
;
1164 if (!od
|| !(conn
= flap_connection_findbygroup(od
, SNAC_FAMILY_FEEDBAG
)))
1167 aim_genericreq_n_snacid(od
, conn
, SNAC_FAMILY_FEEDBAG
, SNAC_SUBTYPE_FEEDBAG_REQRIGHTS
);
1173 * Subtype 0x0003 - SSI Rights Information.
1175 static int parserights(OscarData
*od
, FlapConnection
*conn
, aim_module_t
*mod
, FlapFrame
*frame
, aim_modsnac_t
*snac
, ByteStream
*bs
)
1178 aim_rxcallback_t userfunc
;
1184 /* This SNAC is made up of a bunch of TLVs */
1185 tlvlist
= aim_tlvlist_read(bs
);
1187 /* TLV 0x0004 contains the maximum number of each item */
1188 if (!(tlv
= aim_tlv_gettlv(tlvlist
, 0x0004, 1))) {
1189 aim_tlvlist_free(tlvlist
);
1193 byte_stream_init(&bstream
, tlv
->value
, tlv
->length
);
1195 maxitems
= (guint16
*)g_malloc((tlv
->length
/2)*sizeof(guint16
));
1197 for (i
=0; i
<(tlv
->length
/2); i
++)
1198 maxitems
[i
] = byte_stream_get16(&bstream
);
1200 if ((userfunc
= aim_callhandler(od
, snac
->family
, snac
->subtype
)))
1201 ret
= userfunc(od
, conn
, frame
, tlv
->length
/2, maxitems
);
1203 aim_tlvlist_free(tlvlist
);
1210 * Subtype 0x0004 - Request SSI Data when you don't have a timestamp and
1214 int aim_ssi_reqdata(OscarData
*od
)
1216 FlapConnection
*conn
;
1218 if (!od
|| !(conn
= flap_connection_findbygroup(od
, SNAC_FAMILY_FEEDBAG
)))
1221 /* Free any current data, just in case */
1222 aim_ssi_freelist(od
);
1224 aim_genericreq_n_snacid(od
, conn
, SNAC_FAMILY_FEEDBAG
, SNAC_SUBTYPE_FEEDBAG_REQDATA
);
1230 * Subtype 0x0006 - SSI Data.
1232 static int parsedata(OscarData
*od
, FlapConnection
*conn
, aim_module_t
*mod
, FlapFrame
*frame
, aim_modsnac_t
*snac
, ByteStream
*bs
)
1235 aim_rxcallback_t userfunc
;
1236 guint8 fmtver
; /* guess */
1237 guint16 namelen
, gid
, bid
, type
;
1240 GString
*debugstr
= g_string_new("");
1242 fmtver
= byte_stream_get8(bs
); /* Version of ssi data. Should be 0x00 */
1243 od
->ssi
.numitems
+= byte_stream_get16(bs
); /* # of items in this SSI SNAC */
1245 /* Read in the list */
1246 while (byte_stream_bytes_left(bs
) > 4) { /* last four bytes are timestamp */
1247 if ((namelen
= byte_stream_get16(bs
)))
1248 name
= byte_stream_getstr(bs
, namelen
);
1251 gid
= byte_stream_get16(bs
);
1252 bid
= byte_stream_get16(bs
);
1253 type
= byte_stream_get16(bs
);
1254 data
= aim_tlvlist_readlen(bs
, byte_stream_get16(bs
));
1255 aim_ssi_item_debug_append(debugstr
, "\t", aim_ssi_itemlist_add(&od
->ssi
.official
, name
, gid
, bid
, type
, data
));
1257 aim_tlvlist_free(data
);
1259 purple_debug_misc("oscar", "Reading items from tlvlist for account %s:\n%s",
1260 purple_connection_get_account(od
->gc
)->username
, debugstr
->str
);
1261 g_string_free(debugstr
, TRUE
);
1263 /* Read in the timestamp */
1264 od
->ssi
.timestamp
= byte_stream_get32(bs
);
1266 if (!(snac
->flags
& 0x0001)) {
1267 /* Make a copy of the list */
1268 struct aim_ssi_item
*cur
;
1269 for (cur
=od
->ssi
.official
; cur
; cur
=cur
->next
)
1270 aim_ssi_itemlist_add(&od
->ssi
.local
, cur
->name
, cur
->gid
, cur
->bid
, cur
->type
, cur
->data
);
1272 od
->ssi
.received_data
= TRUE
;
1274 if ((userfunc
= aim_callhandler(od
, snac
->family
, snac
->subtype
)))
1275 ret
= userfunc(od
, conn
, frame
, fmtver
, od
->ssi
.numitems
, od
->ssi
.timestamp
);
1282 * Subtype 0x0007 - SSI Activate Data.
1284 * Should be sent after receiving 13/6 or 13/f to tell the server you
1285 * are ready to begin using the list. It will promptly give you the
1286 * presence information for everyone in your list and put your permit/deny
1287 * settings into effect.
1290 int aim_ssi_enable(OscarData
*od
)
1292 FlapConnection
*conn
;
1294 if (!od
|| !(conn
= flap_connection_findbygroup(od
, SNAC_FAMILY_FEEDBAG
)))
1297 aim_genericreq_n(od
, conn
, SNAC_FAMILY_FEEDBAG
, 0x0007);
1303 * Subtype 0x0008/0x0009/0x000a - SSI Add/Mod/Del Item(s).
1305 * Sends the SNAC to add, modify, or delete items from the server-stored
1306 * information. These 3 SNACs all have an identical structure. The only
1307 * difference is the subtype that is set for the SNAC.
1310 static int aim_ssi_addmoddel(OscarData
*od
)
1312 FlapConnection
*conn
;
1314 aim_snacid_t snacid
;
1316 struct aim_ssi_tmp
*cur
;
1318 if (!od
|| !(conn
= flap_connection_findbygroup(od
, SNAC_FAMILY_FEEDBAG
)) || !od
->ssi
.pending
|| !od
->ssi
.pending
->item
)
1321 /* Calculate total SNAC size */
1323 for (cur
=od
->ssi
.pending
; cur
; cur
=cur
->next
) {
1324 bslen
+= 10; /* For length, GID, BID, type, and length */
1325 if (cur
->item
->name
)
1326 bslen
+= strlen(cur
->item
->name
);
1327 if (cur
->item
->data
)
1328 bslen
+= aim_tlvlist_size(cur
->item
->data
);
1331 byte_stream_new(&bs
, bslen
);
1333 for (cur
=od
->ssi
.pending
; cur
; cur
=cur
->next
) {
1334 byte_stream_put16(&bs
, cur
->item
->name
? strlen(cur
->item
->name
) : 0);
1335 if (cur
->item
->name
)
1336 byte_stream_putstr(&bs
, cur
->item
->name
);
1337 byte_stream_put16(&bs
, cur
->item
->gid
);
1338 byte_stream_put16(&bs
, cur
->item
->bid
);
1339 byte_stream_put16(&bs
, cur
->item
->type
);
1340 byte_stream_put16(&bs
, cur
->item
->data
? aim_tlvlist_size(cur
->item
->data
) : 0);
1341 if (cur
->item
->data
)
1342 aim_tlvlist_write(&bs
, &cur
->item
->data
);
1345 snacid
= aim_cachesnac(od
, SNAC_FAMILY_FEEDBAG
, od
->ssi
.pending
->action
, 0x0000, NULL
, 0);
1346 flap_connection_send_snac(od
, conn
, SNAC_FAMILY_FEEDBAG
, od
->ssi
.pending
->action
, snacid
, &bs
);
1348 byte_stream_destroy(&bs
);
1354 * Subtype 0x0008 - Incoming SSI add.
1356 * Sent by the server, for example, when someone is added to
1357 * your "Recent Buddies" group.
1359 static int parseadd(OscarData
*od
, FlapConnection
*conn
, aim_module_t
*mod
, FlapFrame
*frame
, aim_modsnac_t
*snac
, ByteStream
*bs
)
1362 aim_rxcallback_t userfunc
;
1364 guint16 len
, gid
, bid
, type
;
1367 while (byte_stream_bytes_left(bs
)) {
1368 if ((len
= byte_stream_get16(bs
)))
1369 name
= byte_stream_getstr(bs
, len
);
1372 gid
= byte_stream_get16(bs
);
1373 bid
= byte_stream_get16(bs
);
1374 type
= byte_stream_get16(bs
);
1375 if ((len
= byte_stream_get16(bs
)))
1376 data
= aim_tlvlist_readlen(bs
, len
);
1380 aim_ssi_itemlist_add(&od
->ssi
.local
, name
, gid
, bid
, type
, data
);
1381 aim_ssi_itemlist_add(&od
->ssi
.official
, name
, gid
, bid
, type
, data
);
1382 aim_tlvlist_free(data
);
1384 if ((userfunc
= aim_callhandler(od
, snac
->family
, snac
->subtype
)))
1385 ret
= userfunc(od
, conn
, frame
, snac
->subtype
, type
, name
);
1394 * Subtype 0x0009 - Incoming SSI mod.
1396 static int parsemod(OscarData
*od
, FlapConnection
*conn
, aim_module_t
*mod
, FlapFrame
*frame
, aim_modsnac_t
*snac
, ByteStream
*bs
)
1399 aim_rxcallback_t userfunc
;
1401 guint16 len
, gid
, bid
, type
;
1403 struct aim_ssi_item
*item
;
1405 while (byte_stream_bytes_left(bs
)) {
1406 if ((len
= byte_stream_get16(bs
)))
1407 name
= byte_stream_getstr(bs
, len
);
1410 gid
= byte_stream_get16(bs
);
1411 bid
= byte_stream_get16(bs
);
1412 type
= byte_stream_get16(bs
);
1413 if ((len
= byte_stream_get16(bs
)))
1414 data
= aim_tlvlist_readlen(bs
, len
);
1418 /* Replace the 2 local items with the given one */
1419 if ((item
= aim_ssi_itemlist_find(od
->ssi
.local
, gid
, bid
))) {
1422 item
->name
= g_strdup(name
);
1423 aim_tlvlist_free(item
->data
);
1424 item
->data
= aim_tlvlist_copy(data
);
1427 if ((item
= aim_ssi_itemlist_find(od
->ssi
.official
, gid
, bid
))) {
1430 item
->name
= g_strdup(name
);
1431 aim_tlvlist_free(item
->data
);
1432 item
->data
= aim_tlvlist_copy(data
);
1435 if ((userfunc
= aim_callhandler(od
, snac
->family
, snac
->subtype
)))
1436 ret
= userfunc(od
, conn
, frame
, snac
->subtype
, type
, name
);
1439 aim_tlvlist_free(data
);
1446 * Subtype 0x000a - Incoming SSI del.
1448 * XXX - It would probably be good for the client to actually do something when it gets this.
1450 static int parsedel(OscarData
*od
, FlapConnection
*conn
, aim_module_t
*mod
, FlapFrame
*frame
, aim_modsnac_t
*snac
, ByteStream
*bs
)
1453 aim_rxcallback_t userfunc
;
1455 struct aim_ssi_item
*del
;
1457 while (byte_stream_bytes_left(bs
)) {
1458 byte_stream_advance(bs
, byte_stream_get16(bs
));
1459 gid
= byte_stream_get16(bs
);
1460 bid
= byte_stream_get16(bs
);
1461 byte_stream_get16(bs
);
1462 byte_stream_advance(bs
, byte_stream_get16(bs
));
1464 if ((del
= aim_ssi_itemlist_find(od
->ssi
.local
, gid
, bid
)))
1465 aim_ssi_itemlist_del(&od
->ssi
.local
, del
);
1466 if ((del
= aim_ssi_itemlist_find(od
->ssi
.official
, gid
, bid
)))
1467 aim_ssi_itemlist_del(&od
->ssi
.official
, del
);
1469 if ((userfunc
= aim_callhandler(od
, snac
->family
, snac
->subtype
)))
1470 ret
= userfunc(od
, conn
, frame
);
1477 * Subtype 0x000e - SSI Add/Mod/Del Ack.
1479 * Response to add, modify, or delete SNAC (sent with aim_ssi_addmoddel).
1482 static int parseack(OscarData
*od
, FlapConnection
*conn
, aim_module_t
*mod
, FlapFrame
*frame
, aim_modsnac_t
*snac
, ByteStream
*bs
)
1485 aim_rxcallback_t userfunc
;
1486 struct aim_ssi_tmp
*cur
, *del
;
1488 /* Read in the success/failure flags from the ack SNAC */
1489 cur
= od
->ssi
.pending
;
1490 while (cur
&& (byte_stream_bytes_left(bs
)>0)) {
1491 cur
->ack
= byte_stream_get16(bs
);
1496 * If outcome is 0, then add the item to the item list, or replace the other item,
1497 * or remove the old item. If outcome is non-zero, then remove the item from the
1498 * local list, or unmodify it, or add it.
1500 for (cur
=od
->ssi
.pending
; (cur
&& (cur
->ack
!= 0xffff)); cur
=cur
->next
) {
1503 /* Our action was unsuccessful, so change the local list back to how it was */
1504 if (cur
->action
== SNAC_SUBTYPE_FEEDBAG_ADD
) {
1505 /* Remove the item from the local list */
1506 /* Make sure cur->item is still valid memory */
1507 if (aim_ssi_itemlist_valid(od
->ssi
.local
, cur
->item
)) {
1508 cur
->name
= g_strdup(cur
->item
->name
);
1509 aim_ssi_itemlist_del(&od
->ssi
.local
, cur
->item
);
1513 } else if (cur
->action
== SNAC_SUBTYPE_FEEDBAG_MOD
) {
1514 /* Replace the local item with the item from the official list */
1515 if (aim_ssi_itemlist_valid(od
->ssi
.local
, cur
->item
)) {
1516 struct aim_ssi_item
*cur1
;
1517 if ((cur1
= aim_ssi_itemlist_find(od
->ssi
.official
, cur
->item
->gid
, cur
->item
->bid
))) {
1518 g_free(cur
->item
->name
);
1519 cur
->item
->name
= g_strdup(cur1
->name
);
1520 aim_tlvlist_free(cur
->item
->data
);
1521 cur
->item
->data
= aim_tlvlist_copy(cur1
->data
);
1526 } else if (cur
->action
== SNAC_SUBTYPE_FEEDBAG_DEL
) {
1527 /* Add the item back into the local list */
1528 if (aim_ssi_itemlist_valid(od
->ssi
.official
, cur
->item
)) {
1529 aim_ssi_itemlist_add(&od
->ssi
.local
, cur
->item
->name
, cur
->item
->gid
, cur
->item
->bid
, cur
->item
->type
, cur
->item
->data
);
1535 /* Do the exact opposite */
1536 if (cur
->action
== SNAC_SUBTYPE_FEEDBAG_ADD
) {
1537 /* Add the local item to the official list */
1538 if (aim_ssi_itemlist_valid(od
->ssi
.local
, cur
->item
)) {
1539 aim_ssi_itemlist_add(&od
->ssi
.official
, cur
->item
->name
, cur
->item
->gid
, cur
->item
->bid
, cur
->item
->type
, cur
->item
->data
);
1543 } else if (cur
->action
== SNAC_SUBTYPE_FEEDBAG_MOD
) {
1544 /* Replace the official item with the item from the local list */
1545 if (aim_ssi_itemlist_valid(od
->ssi
.local
, cur
->item
)) {
1546 struct aim_ssi_item
*cur1
;
1547 if ((cur1
= aim_ssi_itemlist_find(od
->ssi
.official
, cur
->item
->gid
, cur
->item
->bid
))) {
1549 cur1
->name
= g_strdup(cur
->item
->name
);
1550 aim_tlvlist_free(cur1
->data
);
1551 cur1
->data
= aim_tlvlist_copy(cur
->item
->data
);
1556 } else if (cur
->action
== SNAC_SUBTYPE_FEEDBAG_DEL
) {
1557 /* Remove the item from the official list */
1558 if (aim_ssi_itemlist_valid(od
->ssi
.official
, cur
->item
))
1559 aim_ssi_itemlist_del(&od
->ssi
.official
, cur
->item
);
1564 } /* End if (cur->item) */
1565 } /* End for loop */
1567 if ((userfunc
= aim_callhandler(od
, snac
->family
, snac
->subtype
)))
1568 ret
= userfunc(od
, conn
, frame
, od
->ssi
.pending
);
1570 /* Free all aim_ssi_tmp's with an outcome */
1571 cur
= od
->ssi
.pending
;
1572 while (cur
&& (cur
->ack
!= 0xffff)) {
1578 od
->ssi
.pending
= cur
;
1580 /* If we're not waiting for any more acks, then send more SNACs */
1581 if (!od
->ssi
.pending
) {
1582 od
->ssi
.waiting_for_ack
= FALSE
;
1590 * Subtype 0x000f - SSI Data Unchanged.
1592 * Response to aim_ssi_reqifchanged() if the server-side data is not newer than
1593 * posted local stamp/revision.
1596 static int parsedataunchanged(OscarData
*od
, FlapConnection
*conn
, aim_module_t
*mod
, FlapFrame
*frame
, aim_modsnac_t
*snac
, ByteStream
*bs
)
1599 aim_rxcallback_t userfunc
;
1601 od
->ssi
.received_data
= TRUE
;
1603 if ((userfunc
= aim_callhandler(od
, snac
->family
, snac
->subtype
)))
1604 ret
= userfunc(od
, conn
, frame
);
1610 * Subtype 0x0011 - SSI Begin Data Modification.
1612 * Tell the server you're going to start modifying data. This marks
1613 * the beginning of a transaction.
1615 int aim_ssi_modbegin(OscarData
*od
)
1617 FlapConnection
*conn
;
1619 if (!od
|| !(conn
= flap_connection_findbygroup(od
, SNAC_FAMILY_FEEDBAG
)))
1622 aim_genericreq_n(od
, conn
, SNAC_FAMILY_FEEDBAG
, SNAC_SUBTYPE_FEEDBAG_EDITSTART
);
1628 * Subtype 0x0012 - SSI End Data Modification.
1630 * Tell the server you're finished modifying data. The marks the end
1633 int aim_ssi_modend(OscarData
*od
)
1635 FlapConnection
*conn
;
1637 if (!od
|| !(conn
= flap_connection_findbygroup(od
, SNAC_FAMILY_FEEDBAG
)))
1640 aim_genericreq_n(od
, conn
, SNAC_FAMILY_FEEDBAG
, SNAC_SUBTYPE_FEEDBAG_EDITSTOP
);
1646 * Subtype 0x0015 - Receive an authorization grant
1648 static int receiveauthgrant(OscarData
*od
, FlapConnection
*conn
, aim_module_t
*mod
, FlapFrame
*frame
, aim_modsnac_t
*snac
, ByteStream
*bs
)
1651 aim_rxcallback_t userfunc
;
1653 char *bn
, *msg
, *tmpstr
;
1655 /* Read buddy name */
1656 tmp
= byte_stream_get8(bs
);
1658 purple_debug_warning("oscar", "Dropping auth grant SNAC "
1659 "because username was empty\n");
1662 bn
= byte_stream_getstr(bs
, tmp
);
1663 if (!g_utf8_validate(bn
, -1, NULL
)) {
1664 purple_debug_warning("oscar", "Dropping auth grant SNAC "
1665 "because the username was not valid UTF-8\n");
1670 tmp
= byte_stream_get16(bs
);
1672 msg
= byte_stream_getstr(bs
, tmp
);
1673 if (!g_utf8_validate(msg
, -1, NULL
)) {
1674 /* Ugh, msg isn't UTF8. Let's salvage. */
1675 purple_debug_warning("oscar", "Got non-UTF8 message in auth "
1676 "grant from %s\n", bn
);
1677 tmpstr
= purple_utf8_salvage(msg
);
1685 tmp
= byte_stream_get16(bs
);
1687 if ((userfunc
= aim_callhandler(od
, snac
->family
, snac
->subtype
)))
1688 ret
= userfunc(od
, conn
, frame
, bn
, msg
);
1697 * Subtype 0x0018 - Send authorization request
1699 * Sends a request for authorization to the given contact. The request will either be
1700 * granted, denied, or dropped.
1703 int aim_ssi_sendauthrequest(OscarData
*od
, const char *bn
, const char *msg
)
1705 FlapConnection
*conn
;
1707 aim_snacid_t snacid
;
1709 if (!od
|| !(conn
= flap_connection_findbygroup(od
, SNAC_FAMILY_FEEDBAG
)) || !bn
)
1712 byte_stream_new(&bs
, 1+strlen(bn
) + 2+(msg
? strlen(msg
)+1 : 0) + 2);
1715 byte_stream_put8(&bs
, strlen(bn
));
1716 byte_stream_putstr(&bs
, bn
);
1718 /* Message (null terminated) */
1719 byte_stream_put16(&bs
, msg
? strlen(msg
) : 0);
1721 byte_stream_putstr(&bs
, msg
);
1722 byte_stream_put8(&bs
, 0x00);
1726 byte_stream_put16(&bs
, 0x0000);
1728 snacid
= aim_cachesnac(od
, SNAC_FAMILY_FEEDBAG
, SNAC_SUBTYPE_FEEDBAG_SENDAUTHREQ
, 0x0000, NULL
, 0);
1729 flap_connection_send_snac(od
, conn
, SNAC_FAMILY_FEEDBAG
, SNAC_SUBTYPE_FEEDBAG_SENDAUTHREQ
, snacid
, &bs
);
1731 byte_stream_destroy(&bs
);
1737 * Subtype 0x0019 - Receive an authorization request
1739 static int receiveauthrequest(OscarData
*od
, FlapConnection
*conn
, aim_module_t
*mod
, FlapFrame
*frame
, aim_modsnac_t
*snac
, ByteStream
*bs
)
1742 aim_rxcallback_t userfunc
;
1744 char *bn
, *msg
, *tmpstr
;
1746 /* Read buddy name */
1747 tmp
= byte_stream_get8(bs
);
1749 purple_debug_warning("oscar", "Dropping auth request SNAC "
1750 "because username was empty\n");
1753 bn
= byte_stream_getstr(bs
, tmp
);
1754 if (!g_utf8_validate(bn
, -1, NULL
)) {
1755 purple_debug_warning("oscar", "Dropping auth request SNAC "
1756 "because the username was not valid UTF-8\n");
1761 tmp
= byte_stream_get16(bs
);
1763 msg
= byte_stream_getstr(bs
, tmp
);
1764 if (!g_utf8_validate(msg
, -1, NULL
)) {
1765 /* Ugh, msg isn't UTF8. Let's salvage. */
1766 purple_debug_warning("oscar", "Got non-UTF8 message in auth "
1767 "request from %s\n", bn
);
1768 tmpstr
= purple_utf8_salvage(msg
);
1776 tmp
= byte_stream_get16(bs
);
1778 if ((userfunc
= aim_callhandler(od
, snac
->family
, snac
->subtype
)))
1779 ret
= userfunc(od
, conn
, frame
, bn
, msg
);
1788 * Subtype 0x001a - Send authorization reply
1790 * Sends a reply to a request for authorization. The reply can either
1791 * grant authorization or deny authorization.
1793 * if reply=0x00 then deny
1794 * if reply=0x01 then grant
1797 int aim_ssi_sendauthreply(OscarData
*od
, const char *bn
, guint8 reply
, const char *msg
)
1799 FlapConnection
*conn
;
1801 aim_snacid_t snacid
;
1803 if (!od
|| !(conn
= flap_connection_findbygroup(od
, SNAC_FAMILY_FEEDBAG
)) || !bn
)
1806 byte_stream_new(&bs
, 1+strlen(bn
) + 1 + 2+(msg
? (strlen(msg
)+1) : 0) + 2);
1809 byte_stream_put8(&bs
, strlen(bn
));
1810 byte_stream_putstr(&bs
, bn
);
1813 byte_stream_put8(&bs
, reply
);
1815 /* Message (null terminated) */
1816 byte_stream_put16(&bs
, msg
? (strlen(msg
)+1) : 0);
1818 byte_stream_putstr(&bs
, msg
);
1819 byte_stream_put8(&bs
, 0x00);
1823 byte_stream_put16(&bs
, 0x0000);
1825 snacid
= aim_cachesnac(od
, SNAC_FAMILY_FEEDBAG
, SNAC_SUBTYPE_FEEDBAG_SENDAUTHREP
, 0x0000, NULL
, 0);
1826 flap_connection_send_snac(od
, conn
, SNAC_FAMILY_FEEDBAG
, SNAC_SUBTYPE_FEEDBAG_SENDAUTHREP
, snacid
, &bs
);
1828 byte_stream_destroy(&bs
);
1834 * Subtype 0x001b - Receive an authorization reply
1836 * You get this bad boy when other people respond to the authorization
1837 * request that you have previously sent them.
1839 static int receiveauthreply(OscarData
*od
, FlapConnection
*conn
, aim_module_t
*mod
, FlapFrame
*frame
, aim_modsnac_t
*snac
, ByteStream
*bs
)
1842 aim_rxcallback_t userfunc
;
1845 char *bn
, *msg
, *tmpstr
;
1847 /* Read buddy name */
1848 tmp
= byte_stream_get8(bs
);
1850 purple_debug_warning("oscar", "Dropping auth reply SNAC "
1851 "because username was empty\n");
1854 bn
= byte_stream_getstr(bs
, tmp
);
1855 if (!g_utf8_validate(bn
, -1, NULL
)) {
1856 purple_debug_warning("oscar", "Dropping auth reply SNAC "
1857 "because the username was not valid UTF-8\n");
1862 reply
= byte_stream_get8(bs
);
1865 tmp
= byte_stream_get16(bs
);
1867 msg
= byte_stream_getstr(bs
, tmp
);
1868 if (!g_utf8_validate(msg
, -1, NULL
)) {
1869 /* Ugh, msg isn't UTF8. Let's salvage. */
1870 purple_debug_warning("oscar", "Got non-UTF8 message in auth "
1871 "reply from %s\n", bn
);
1872 tmpstr
= purple_utf8_salvage(msg
);
1880 tmp
= byte_stream_get16(bs
);
1882 if ((userfunc
= aim_callhandler(od
, snac
->family
, snac
->subtype
)))
1883 ret
= userfunc(od
, conn
, frame
, bn
, reply
, msg
);
1892 * Subtype 0x001c - Receive a message telling you someone added you to their list.
1894 static int receiveadded(OscarData
*od
, FlapConnection
*conn
, aim_module_t
*mod
, FlapFrame
*frame
, aim_modsnac_t
*snac
, ByteStream
*bs
)
1897 aim_rxcallback_t userfunc
;
1901 /* Read buddy name */
1902 tmp
= byte_stream_get8(bs
);
1904 purple_debug_warning("oscar", "Dropping 'you were added' SNAC "
1905 "because username was empty\n");
1908 bn
= byte_stream_getstr(bs
, tmp
);
1909 if (!g_utf8_validate(bn
, -1, NULL
)) {
1910 purple_debug_warning("oscar", "Dropping 'you were added' SNAC "
1911 "because the username was not valid UTF-8\n");
1915 if ((userfunc
= aim_callhandler(od
, snac
->family
, snac
->subtype
)))
1916 ret
= userfunc(od
, conn
, frame
, bn
);
1924 * If we're on ICQ, then AIM_SSI_TYPE_DENY is used for the "permanently invisible" list.
1925 * AIM_SSI_TYPE_ICQDENY is used for blocking users instead.
1928 aim_ssi_getdenyentrytype(OscarData
* od
)
1930 return od
->icq
? AIM_SSI_TYPE_ICQDENY
: AIM_SSI_TYPE_DENY
;
1934 snachandler(OscarData
*od
, FlapConnection
*conn
, aim_module_t
*mod
, FlapFrame
*frame
, aim_modsnac_t
*snac
, ByteStream
*bs
)
1936 if (snac
->subtype
== SNAC_SUBTYPE_FEEDBAG_RIGHTSINFO
)
1937 return parserights(od
, conn
, mod
, frame
, snac
, bs
);
1938 else if (snac
->subtype
== SNAC_SUBTYPE_FEEDBAG_LIST
)
1939 return parsedata(od
, conn
, mod
, frame
, snac
, bs
);
1940 else if (snac
->subtype
== SNAC_SUBTYPE_FEEDBAG_ADD
)
1941 return parseadd(od
, conn
, mod
, frame
, snac
, bs
);
1942 else if (snac
->subtype
== SNAC_SUBTYPE_FEEDBAG_MOD
)
1943 return parsemod(od
, conn
, mod
, frame
, snac
, bs
);
1944 else if (snac
->subtype
== SNAC_SUBTYPE_FEEDBAG_DEL
)
1945 return parsedel(od
, conn
, mod
, frame
, snac
, bs
);
1946 else if (snac
->subtype
== SNAC_SUBTYPE_FEEDBAG_SRVACK
)
1947 return parseack(od
, conn
, mod
, frame
, snac
, bs
);
1948 else if (snac
->subtype
== SNAC_SUBTYPE_FEEDBAG_NOLIST
)
1949 return parsedataunchanged(od
, conn
, mod
, frame
, snac
, bs
);
1950 else if (snac
->subtype
== SNAC_SUBTYPE_FEEDBAG_RECVAUTH
)
1951 return receiveauthgrant(od
, conn
, mod
, frame
, snac
, bs
);
1952 else if (snac
->subtype
== SNAC_SUBTYPE_FEEDBAG_RECVAUTHREQ
)
1953 return receiveauthrequest(od
, conn
, mod
, frame
, snac
, bs
);
1954 else if (snac
->subtype
== SNAC_SUBTYPE_FEEDBAG_RECVAUTHREP
)
1955 return receiveauthreply(od
, conn
, mod
, frame
, snac
, bs
);
1956 else if (snac
->subtype
== SNAC_SUBTYPE_FEEDBAG_ADDED
)
1957 return receiveadded(od
, conn
, mod
, frame
, snac
, bs
);
1963 ssi_shutdown(OscarData
*od
, aim_module_t
*mod
)
1965 aim_ssi_freelist(od
);
1969 ssi_modfirst(OscarData
*od
, aim_module_t
*mod
)
1971 mod
->family
= SNAC_FAMILY_FEEDBAG
;
1972 mod
->version
= 0x0004;
1973 mod
->toolid
= 0x0110;
1974 mod
->toolversion
= 0x0629;
1976 strncpy(mod
->name
, "feedbag", sizeof(mod
->name
));
1977 mod
->snachandler
= snachandler
;
1978 mod
->shutdown
= ssi_shutdown
;