Remove useless comparison
[pidgin-git.git] / libpurple / protocols / oscar / family_feedbag.c
blobb752af0fed918477378c2cf480758a763dcf6160
1 /*
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.
46 #include "oscar.h"
47 #include "debug.h"
49 static int aim_ssi_addmoddel(OscarData *od);
51 /**
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.
58 static const gchar*
59 aim_ssi_type_to_string(guint16 type)
61 struct TypeStringPair
63 guint16 type;
64 const gchar *string;
66 static const struct TypeStringPair type_strings[] = {
67 { 0x0000, "Buddy" },
68 { 0x0001, "Group" },
69 { 0x0002, "Permit/Visible" },
70 { 0x0003, "Deny/Invisible" },
71 { 0x0004, "PDInfo" },
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" },
83 size_t i;
84 for (i = 0; i < G_N_ELEMENTS(type_strings); i++) {
85 if (type_strings[i].type == type) {
86 return type_strings[i].string;
89 return "unknown";
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.
98 static void
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.
115 static void
116 aim_ssi_itemlist_rebuildgroup(struct aim_ssi_item *list, const char *name)
118 int newlen;
119 struct aim_ssi_item *cur, *group;
121 /* Find the group */
122 if (!(group = aim_ssi_itemlist_finditem(list, name, NULL, AIM_SSI_TYPE_GROUP)))
123 return;
125 /* Find the length for the new additional data */
126 newlen = 0;
127 if (group->gid == 0x0000) {
128 for (cur=list; cur; cur=cur->next)
129 if ((cur->type == AIM_SSI_TYPE_GROUP) && (cur->gid != 0x0000))
130 newlen += 2;
131 } else {
132 for (cur=list; cur; cur=cur->next)
133 if ((cur->gid == group->gid) && (cur->type == AIM_SSI_TYPE_BUDDY))
134 newlen += 2;
137 /* Build the new TLV list */
138 if (newlen > 0) {
139 guint8 *newdata;
141 newdata = (guint8 *)g_malloc((newlen)*sizeof(guint8));
142 newlen = 0;
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);
147 } else {
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);
154 g_free(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)
172 gboolean exists;
173 struct aim_ssi_item *cur, *new;
175 new = g_new(struct aim_ssi_item, 1);
177 /* Set the name */
178 new->name = g_strdup(name);
180 /* Set the group ID# and buddy ID# */
181 new->gid = gid;
182 new->bid = bid;
183 if (type == AIM_SSI_TYPE_GROUP) {
184 if ((new->gid == 0xFFFF) && name) {
185 do {
186 new->gid += 0x0001;
187 exists = FALSE;
188 for (cur = *list; cur != NULL; cur = cur->next)
189 if ((cur->type == AIM_SSI_TYPE_GROUP) && (cur->gid == new->gid)) {
190 exists = TRUE;
191 break;
193 } while (exists);
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) {
202 do {
203 new->bid += 0x0001;
204 exists = FALSE;
205 for (cur = *list; cur != NULL; cur = cur->next)
206 if (cur->bid == new->bid || cur->gid == new->bid) {
207 exists = TRUE;
208 break;
210 } while (exists);
212 } else {
213 if (new->bid == 0xFFFF) {
214 do {
215 new->bid += 0x0001;
216 exists = FALSE;
217 for (cur = *list; cur != NULL; cur = cur->next)
218 if (cur->bid == new->bid && cur->gid == new->gid) {
219 exists = TRUE;
220 break;
222 } while (exists);
226 /* Set the type */
227 new->type = type;
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? */
233 if (*list) {
234 if ((new->gid < (*list)->gid) || ((new->gid == (*list)->gid) && (new->bid < (*list)->bid))) {
235 new->next = *list;
236 *list = new;
237 } else {
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;
241 prev->next = new;
243 } else {
244 new->next = *list;
245 *list = new;
248 return new;
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)
261 return -EINVAL;
263 /* Remove the item from the list */
264 if (*list == del) {
265 *list = (*list)->next;
266 } else {
267 struct aim_ssi_item *cur;
268 for (cur=*list; (cur->next && (cur->next!=del)); cur=cur->next);
269 if (cur->next)
270 cur->next = del->next;
273 /* Free the removed item */
274 g_free(del->name);
275 aim_tlvlist_free(del->data);
276 g_free(del);
278 return 0;
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)
290 if (!cur1 || !cur2)
291 return 1;
293 if (cur1->data && !cur2->data)
294 return 2;
296 if (!cur1->data && cur2->data)
297 return 3;
299 if ((cur1->data && cur2->data) && (aim_tlvlist_cmp(cur1->data, cur2->data)))
300 return 4;
302 if (cur1->name && !cur2->name)
303 return 5;
305 if (!cur1->name && cur2->name)
306 return 6;
308 if (cur1->name && cur2->name && oscar_util_name_compare(cur1->name, cur2->name))
309 return 7;
311 if (cur1->gid != cur2->gid)
312 return 8;
314 if (cur1->bid != cur2->bid)
315 return 9;
317 if (cur1->type != cur2->type)
318 return 10;
320 return 0;
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)
327 if (cur == item)
328 return TRUE;
329 return FALSE;
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))
345 return cur;
346 return NULL;
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;
362 if (!list)
363 return NULL;
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)))
371 return cur;
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))) {
377 return cur;
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))) {
384 return cur;
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))
391 return cur;
394 return NULL;
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)
406 if (!bn)
407 return NULL;
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;
421 if (!list || !bn)
422 return NULL;
423 if (!(cur = aim_ssi_itemlist_exists(list, bn)))
424 return NULL;
425 if (!(curg = aim_ssi_itemlist_find(list, cur->gid, 0x0000)))
426 return NULL;
427 return curg->name;
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);
439 if (cur) {
440 aim_tlv_t *tlv = aim_tlv_gettlv(cur->data, 0x00ca, 1);
441 if (tlv && tlv->value)
442 return aimutil_get8(tlv->value);
444 return 0;
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);
457 if (cur) {
458 aim_tlv_t *tlv = aim_tlv_gettlv(cur->data, 0x00c9, 1);
459 if (tlv && tlv->length)
460 return aimutil_get32(tlv->value);
462 return 0xFFFFFFFF;
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);
478 if (cur) {
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);
483 return NULL;
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);
499 if (cur) {
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);
505 return NULL;
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);
519 if (cur) {
520 if (aim_tlv_gettlv(cur->data, 0x0066, 1))
521 return TRUE;
523 return FALSE;
527 * If there are changes, then create temporary items and
528 * call addmoddel.
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;
537 int n = 0;
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.
546 if (!od)
547 return -EINVAL;
549 /* If we're waiting for an ack, we shouldn't do anything else */
550 if (od->ssi.waiting_for_ack)
551 return 0;
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.
562 /* Deletions */
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)) {
566 n++;
567 new = g_new(struct aim_ssi_tmp, 1);
568 new->action = SNAC_SUBTYPE_FEEDBAG_DEL;
569 new->ack = 0xffff;
570 new->name = NULL;
571 new->item = cur1;
572 new->next = NULL;
573 if (od->ssi.pending) {
574 for (cur=od->ssi.pending; cur->next; cur=cur->next);
575 cur->next = new;
576 } else
577 od->ssi.pending = new;
578 aim_ssi_item_debug_append(debugstr, "Deleting item ", cur1);
583 /* Additions */
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)) {
587 n++;
588 new = g_new(struct aim_ssi_tmp, 1);
589 new->action = SNAC_SUBTYPE_FEEDBAG_ADD;
590 new->ack = 0xffff;
591 new->name = NULL;
592 new->item = cur1;
593 new->next = NULL;
594 if (od->ssi.pending) {
595 for (cur=od->ssi.pending; cur->next; cur=cur->next);
596 cur->next = new;
597 } else
598 od->ssi.pending = new;
599 aim_ssi_item_debug_append(debugstr, "Adding item ", cur1);
604 /* Modifications */
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))) {
609 n++;
610 new = g_new(struct aim_ssi_tmp, 1);
611 new->action = SNAC_SUBTYPE_FEEDBAG_MOD;
612 new->ack = 0xffff;
613 new->name = NULL;
614 new->item = cur1;
615 new->next = NULL;
616 if (od->ssi.pending) {
617 for (cur=od->ssi.pending; cur->next; cur=cur->next);
618 cur->next = new;
619 } else
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) {
640 aim_ssi_modend(od);
641 od->ssi.in_transaction = FALSE;
643 return 0;
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);;
663 * Free all SSI data.
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.
670 static void
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;
677 while (cur) {
678 del = cur;
679 cur = cur->next;
680 g_free(del->name);
681 aim_tlvlist_free(del->data);
682 g_free(del);
685 cur = od->ssi.local;
686 while (cur) {
687 del = cur;
688 cur = cur->next;
689 g_free(del->name);
690 aim_tlvlist_free(del->data);
691 g_free(del);
694 curtmp = od->ssi.pending;
695 while (curtmp) {
696 deltmp = curtmp;
697 curtmp = curtmp->next;
698 g_free(deltmp);
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;
721 if (!od)
722 return -EINVAL;
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 */
729 cur = od->ssi.local;
730 while (cur) {
731 next = cur->next;
732 if (!cur->name) {
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);
741 g_free(alias);
743 cur = next;
746 /* Make sure there aren't any duplicate buddies in a group, or duplicate permits or denies */
747 cur = od->ssi.local;
748 while (cur) {
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;
752 cur2 = cur->next;
753 while (cur2) {
754 next2 = cur2->next;
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);
758 cur2 = next2;
761 cur = cur->next;
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)
785 return -EINVAL;
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);
793 /* Add the parent */
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 */
801 if (needauth)
802 aim_tlvlist_add_noval(&data, 0x0066);
803 if (alias != NULL)
804 aim_tlvlist_add_str(&data, 0x0131, alias);
805 if (smsnum != NULL)
806 aim_tlvlist_add_str(&data, 0x013a, smsnum);
807 if (comment != NULL)
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)
825 return -EINVAL;
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;
839 if (!od)
840 return -EINVAL;
842 if (!(del = aim_ssi_itemlist_finditem(od->ssi.local, NULL, name, list_type)))
843 return -EINVAL;
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;
861 if (!od)
862 return -EINVAL;
864 /* Find the buddy */
865 if (!(del = aim_ssi_itemlist_finditem(od->ssi.local, group, name, AIM_SSI_TYPE_BUDDY)))
866 return -EINVAL;
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;
888 aim_tlv_t *tlv;
890 if (!od)
891 return -EINVAL;
893 /* Find the group */
894 if (!(del = aim_ssi_itemlist_finditem(od->ssi.local, group, NULL, AIM_SSI_TYPE_GROUP)))
895 return -EINVAL;
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)
900 return -EINVAL;
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;
925 GSList *data;
927 /* Find the buddy */
928 buddy = aim_ssi_itemlist_finditem(od->ssi.local, oldgn, bn, AIM_SSI_TYPE_BUDDY);
929 if (buddy == NULL)
930 return -EINVAL;
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);
941 return 0;
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
951 * a buddy's comment.
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)
959 return -EINVAL;
961 if (!(tmp = aim_ssi_itemlist_finditem(od->ssi.local, gn, bn, AIM_SSI_TYPE_BUDDY)))
962 return -EINVAL;
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);
967 else
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
981 * a buddy's comment.
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)
989 return -EINVAL;
991 if (!(tmp = aim_ssi_itemlist_finditem(od->ssi.local, gn, bn, AIM_SSI_TYPE_BUDDY)))
992 return -EINVAL;
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);
997 else
998 aim_tlvlist_remove(&tmp->data, 0x013c);
1000 /* Sync our local list with the server list */
1001 return aim_ssi_sync(od);
1005 * Rename a group.
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)
1017 return -EINVAL;
1019 if (!(group = aim_ssi_itemlist_finditem(od->ssi.local, oldgn, NULL, AIM_SSI_TYPE_GROUP)))
1020 return -EINVAL;
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)
1047 return -EINVAL;
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;
1076 guint8 *csumdata;
1078 if (!od || !iconsum || !iconsumlen || !od->ssi.received_data)
1079 return -EINVAL;
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);
1096 g_free(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 */
1102 aim_ssi_sync(od);
1103 return 0;
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)
1139 return -EINVAL;
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)))
1165 return -EINVAL;
1167 aim_genericreq_n_snacid(od, conn, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_REQRIGHTS);
1169 return 0;
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)
1177 int ret = 0, i;
1178 aim_rxcallback_t userfunc;
1179 GSList *tlvlist;
1180 aim_tlv_t *tlv;
1181 ByteStream bstream;
1182 guint16 *maxitems;
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);
1190 return 0;
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);
1204 g_free(maxitems);
1206 return ret;
1210 * Subtype 0x0004 - Request SSI Data when you don't have a timestamp and
1211 * revision number.
1214 int aim_ssi_reqdata(OscarData *od)
1216 FlapConnection *conn;
1218 if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_FEEDBAG)))
1219 return -EINVAL;
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);
1226 return 0;
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)
1234 int ret = 0;
1235 aim_rxcallback_t userfunc;
1236 guint8 fmtver; /* guess */
1237 guint16 namelen, gid, bid, type;
1238 char *name;
1239 GSList *data;
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);
1249 else
1250 name = NULL;
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));
1256 g_free(name);
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);
1278 return ret;
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)))
1295 return -EINVAL;
1297 aim_genericreq_n(od, conn, SNAC_FAMILY_FEEDBAG, 0x0007);
1299 return 0;
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;
1313 ByteStream bs;
1314 aim_snacid_t snacid;
1315 int bslen;
1316 struct aim_ssi_tmp *cur;
1318 if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_FEEDBAG)) || !od->ssi.pending || !od->ssi.pending->item)
1319 return -EINVAL;
1321 /* Calculate total SNAC size */
1322 bslen = 0;
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);
1350 return 0;
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)
1361 int ret = 0;
1362 aim_rxcallback_t userfunc;
1363 char *name;
1364 guint16 len, gid, bid, type;
1365 GSList *data;
1367 while (byte_stream_bytes_left(bs)) {
1368 if ((len = byte_stream_get16(bs)))
1369 name = byte_stream_getstr(bs, len);
1370 else
1371 name = NULL;
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);
1377 else
1378 data = NULL;
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);
1387 g_free(name);
1390 return ret;
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)
1398 int ret = 0;
1399 aim_rxcallback_t userfunc;
1400 char *name;
1401 guint16 len, gid, bid, type;
1402 GSList *data;
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);
1408 else
1409 name = NULL;
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);
1415 else
1416 data = NULL;
1418 /* Replace the 2 local items with the given one */
1419 if ((item = aim_ssi_itemlist_find(od->ssi.local, gid, bid))) {
1420 item->type = type;
1421 g_free(item->name);
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))) {
1428 item->type = type;
1429 g_free(item->name);
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);
1438 g_free(name);
1439 aim_tlvlist_free(data);
1442 return ret;
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)
1452 int ret = 0;
1453 aim_rxcallback_t userfunc;
1454 guint16 gid, bid;
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);
1473 return ret;
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)
1484 int ret = 0;
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);
1492 cur = cur->next;
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) {
1501 if (cur->item) {
1502 if (cur->ack) {
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);
1511 cur->item = NULL;
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);
1523 } else
1524 cur->item = NULL;
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);
1530 } else
1531 cur->item = NULL;
1534 } else {
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);
1540 } else
1541 cur->item = NULL;
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))) {
1548 g_free(cur1->name);
1549 cur1->name = g_strdup(cur->item->name);
1550 aim_tlvlist_free(cur1->data);
1551 cur1->data = aim_tlvlist_copy(cur->item->data);
1553 } else
1554 cur->item = NULL;
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);
1560 cur->item = NULL;
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)) {
1573 del = cur;
1574 cur = cur->next;
1575 g_free(del->name);
1576 g_free(del);
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;
1583 aim_ssi_sync(od);
1586 return ret;
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)
1598 int ret = 0;
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);
1606 return ret;
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)))
1620 return -EINVAL;
1622 aim_genericreq_n(od, conn, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_EDITSTART);
1624 return 0;
1628 * Subtype 0x0012 - SSI End Data Modification.
1630 * Tell the server you're finished modifying data. The marks the end
1631 * of a transaction.
1633 int aim_ssi_modend(OscarData *od)
1635 FlapConnection *conn;
1637 if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_FEEDBAG)))
1638 return -EINVAL;
1640 aim_genericreq_n(od, conn, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_EDITSTOP);
1642 return 0;
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)
1650 int ret = 0;
1651 aim_rxcallback_t userfunc;
1652 guint16 tmp;
1653 char *bn, *msg, *tmpstr;
1655 /* Read buddy name */
1656 tmp = byte_stream_get8(bs);
1657 if (!tmp) {
1658 purple_debug_warning("oscar", "Dropping auth grant SNAC "
1659 "because username was empty\n");
1660 return 0;
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");
1666 g_free(bn);
1669 /* Read message */
1670 tmp = byte_stream_get16(bs);
1671 if (tmp) {
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);
1678 g_free(msg);
1679 msg = tmpstr;
1681 } else
1682 msg = NULL;
1684 /* Unknown */
1685 tmp = byte_stream_get16(bs);
1687 if ((userfunc = aim_callhandler(od, snac->family, snac->subtype)))
1688 ret = userfunc(od, conn, frame, bn, msg);
1690 g_free(bn);
1691 g_free(msg);
1693 return ret;
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;
1706 ByteStream bs;
1707 aim_snacid_t snacid;
1709 if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_FEEDBAG)) || !bn)
1710 return -EINVAL;
1712 byte_stream_new(&bs, 1+strlen(bn) + 2+(msg ? strlen(msg)+1 : 0) + 2);
1714 /* Username */
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);
1720 if (msg) {
1721 byte_stream_putstr(&bs, msg);
1722 byte_stream_put8(&bs, 0x00);
1725 /* Unknown */
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);
1733 return 0;
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)
1741 int ret = 0;
1742 aim_rxcallback_t userfunc;
1743 guint16 tmp;
1744 char *bn, *msg, *tmpstr;
1746 /* Read buddy name */
1747 tmp = byte_stream_get8(bs);
1748 if (!tmp) {
1749 purple_debug_warning("oscar", "Dropping auth request SNAC "
1750 "because username was empty\n");
1751 return 0;
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");
1757 g_free(bn);
1760 /* Read message */
1761 tmp = byte_stream_get16(bs);
1762 if (tmp) {
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);
1769 g_free(msg);
1770 msg = tmpstr;
1772 } else
1773 msg = NULL;
1775 /* Unknown */
1776 tmp = byte_stream_get16(bs);
1778 if ((userfunc = aim_callhandler(od, snac->family, snac->subtype)))
1779 ret = userfunc(od, conn, frame, bn, msg);
1781 g_free(bn);
1782 g_free(msg);
1784 return ret;
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;
1800 ByteStream bs;
1801 aim_snacid_t snacid;
1803 if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_FEEDBAG)) || !bn)
1804 return -EINVAL;
1806 byte_stream_new(&bs, 1+strlen(bn) + 1 + 2+(msg ? (strlen(msg)+1) : 0) + 2);
1808 /* Username */
1809 byte_stream_put8(&bs, strlen(bn));
1810 byte_stream_putstr(&bs, bn);
1812 /* Grant or deny */
1813 byte_stream_put8(&bs, reply);
1815 /* Message (null terminated) */
1816 byte_stream_put16(&bs, msg ? (strlen(msg)+1) : 0);
1817 if (msg) {
1818 byte_stream_putstr(&bs, msg);
1819 byte_stream_put8(&bs, 0x00);
1822 /* Unknown */
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);
1830 return 0;
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)
1841 int ret = 0;
1842 aim_rxcallback_t userfunc;
1843 guint16 tmp;
1844 guint8 reply;
1845 char *bn, *msg, *tmpstr;
1847 /* Read buddy name */
1848 tmp = byte_stream_get8(bs);
1849 if (!tmp) {
1850 purple_debug_warning("oscar", "Dropping auth reply SNAC "
1851 "because username was empty\n");
1852 return 0;
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");
1858 g_free(bn);
1861 /* Read reply */
1862 reply = byte_stream_get8(bs);
1864 /* Read message */
1865 tmp = byte_stream_get16(bs);
1866 if (tmp) {
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);
1873 g_free(msg);
1874 msg = tmpstr;
1876 } else
1877 msg = NULL;
1879 /* Unknown */
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);
1885 g_free(bn);
1886 g_free(msg);
1888 return ret;
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)
1896 int ret = 0;
1897 aim_rxcallback_t userfunc;
1898 guint16 tmp;
1899 char *bn;
1901 /* Read buddy name */
1902 tmp = byte_stream_get8(bs);
1903 if (!tmp) {
1904 purple_debug_warning("oscar", "Dropping 'you were added' SNAC "
1905 "because username was empty\n");
1906 return 0;
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");
1912 g_free(bn);
1915 if ((userfunc = aim_callhandler(od, snac->family, snac->subtype)))
1916 ret = userfunc(od, conn, frame, bn);
1918 g_free(bn);
1920 return ret;
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.
1927 guint16
1928 aim_ssi_getdenyentrytype(OscarData* od)
1930 return od->icq ? AIM_SSI_TYPE_ICQDENY : AIM_SSI_TYPE_DENY;
1933 static int
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);
1959 return 0;
1962 static void
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;
1975 mod->flags = 0;
1976 strncpy(mod->name, "feedbag", sizeof(mod->name));
1977 mod->snachandler = snachandler;
1978 mod->shutdown = ssi_shutdown;
1980 return 0;