changeset 5836:09f7f23dc83a

[gaim-migrate @ 6267] I think I've fixed the negative online time for real, now. Or, at least reduced the frequency of it happening. Also added the ability to see iChat's "available" messages. Sean, I ended up changing more than I thought I would have to, but that's partially because I like to change things a lot. If it conflicts or whatever feel free to back it out and I can re-patch it back in when you're done with your stuff. committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Thu, 12 Jun 2003 03:27:58 +0000
parents 9a08899192ee
children a48c338dff6c
files ChangeLog src/protocols/oscar/aim.h src/protocols/oscar/aim_internal.h src/protocols/oscar/buddylist.c src/protocols/oscar/chat.c src/protocols/oscar/im.c src/protocols/oscar/info.c src/protocols/oscar/oscar.c src/protocols/oscar/service.c
diffstat 9 files changed, 171 insertions(+), 70 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed Jun 11 22:46:38 2003 +0000
+++ b/ChangeLog	Thu Jun 12 03:27:58 2003 +0000
@@ -16,6 +16,7 @@
 	* Added Trepia support.
 	* New Send IM buddy icon merged from Ximian Desktop 2
 	* Fixed "Sort by Status" crash
+	* Ability to view iChat "Available" messages
 
 version 0.64 (05/29/2003):
 	* Buddy list sorting in buddy list preferences.
--- a/src/protocols/oscar/aim.h	Wed Jun 11 22:46:38 2003 +0000
+++ b/src/protocols/oscar/aim.h	Thu Jun 12 03:27:58 2003 +0000
@@ -929,8 +929,18 @@
 #define AIM_FLAG_ACTIVEBUDDY    0x0400
 #define AIM_FLAG_UNKNOWN800	0x0800
 #define AIM_FLAG_ABINTERNAL     0x1000
+#define AIM_FLAG_ALLUSERS	0x001f
 
-#define AIM_FLAG_ALLUSERS	0x001f
+#define AIM_USERINFO_PRESENT_FLAGS        0x00000001
+#define AIM_USERINFO_PRESENT_MEMBERSINCE  0x00000002
+#define AIM_USERINFO_PRESENT_ONLINESINCE  0x00000004
+#define AIM_USERINFO_PRESENT_IDLE         0x00000008
+#define AIM_USERINFO_PRESENT_ICQEXTSTATUS 0x00000010
+#define AIM_USERINFO_PRESENT_ICQIPADDR    0x00000020
+#define AIM_USERINFO_PRESENT_ICQDATA      0x00000040
+#define AIM_USERINFO_PRESENT_CAPABILITIES 0x00000080
+#define AIM_USERINFO_PRESENT_SESSIONLEN   0x00000100
+#define AIM_USERINFO_PRESENT_CREATETIME   0x00000200
 
 typedef struct {
 	char sn[MAXSNLEN+1];
@@ -949,20 +959,10 @@
 	} icqinfo;
 	fu32_t present;
 	fu16_t iconcsumlen;
-	fu8_t iconcsum[30];
+	fu8_t *iconcsum;
+	char *availablemsg;
 } aim_userinfo_t;
 
-#define AIM_USERINFO_PRESENT_FLAGS        0x00000001
-#define AIM_USERINFO_PRESENT_MEMBERSINCE  0x00000002
-#define AIM_USERINFO_PRESENT_ONLINESINCE  0x00000004
-#define AIM_USERINFO_PRESENT_IDLE         0x00000008
-#define AIM_USERINFO_PRESENT_ICQEXTSTATUS 0x00000010
-#define AIM_USERINFO_PRESENT_ICQIPADDR    0x00000020
-#define AIM_USERINFO_PRESENT_ICQDATA      0x00000040
-#define AIM_USERINFO_PRESENT_CAPABILITIES 0x00000080
-#define AIM_USERINFO_PRESENT_SESSIONLEN   0x00000100
-#define AIM_USERINFO_PRESENT_CREATETIME   0x00000200
-
 faim_export const char *aim_userinfo_sn(aim_userinfo_t *ui);
 faim_export fu16_t aim_userinfo_flags(aim_userinfo_t *ui);
 faim_export fu16_t aim_userinfo_idle(aim_userinfo_t *ui);
--- a/src/protocols/oscar/aim_internal.h	Wed Jun 11 22:46:38 2003 +0000
+++ b/src/protocols/oscar/aim_internal.h	Thu Jun 12 03:27:58 2003 +0000
@@ -199,7 +199,8 @@
 faim_internal int aim_msgcookie_gettype(int reqclass);
 faim_internal int aim_cookie_free(aim_session_t *sess, aim_msgcookie_t *cookie);
 
-faim_internal int aim_extractuserinfo(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *);
+faim_internal void aim_info_free(aim_userinfo_t *);
+faim_internal int aim_info_extract(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *);
 faim_internal int aim_putuserinfo(aim_bstream_t *bs, aim_userinfo_t *info);
 
 faim_internal int aim_chat_readroominfo(aim_bstream_t *bs, struct aim_chat_roominfo *outinfo);
--- a/src/protocols/oscar/buddylist.c	Wed Jun 11 22:46:38 2003 +0000
+++ b/src/protocols/oscar/buddylist.c	Thu Jun 12 03:27:58 2003 +0000
@@ -232,11 +232,11 @@
  * Subtypes 0x000b and 0x000c - Change in buddy status
  *
  * Oncoming Buddy notifications contain a subset of the
- * user information structure.  Its close enough to run
- * through aim_extractuserinfo() however.
+ * user information structure.  It's close enough to run
+ * through aim_info_extract() however.
  *
  * Although the offgoing notification contains no information,
- * it is still in a format parsable by extractuserinfo.
+ * it is still in a format parsable by aim_info_extract().
  *
  */
 static int buddychange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
@@ -245,11 +245,13 @@
 	aim_userinfo_t userinfo;
 	aim_rxcallback_t userfunc;
 
-	aim_extractuserinfo(sess, bs, &userinfo);
+	aim_info_extract(sess, bs, &userinfo);
 
 	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
 		ret = userfunc(sess, rx, &userinfo);
 
+	aim_info_free(&userinfo);
+
 	return ret;
 }
 
--- a/src/protocols/oscar/chat.c	Wed Jun 11 22:46:38 2003 +0000
+++ b/src/protocols/oscar/chat.c	Thu Jun 12 03:27:58 2003 +0000
@@ -335,7 +335,7 @@
 		aim_bstream_init(&occbs, tmptlv->value, tmptlv->length);
 
 		while (curoccupant < usercount)
-			aim_extractuserinfo(sess, &occbs, &userinfo[curoccupant++]);
+			aim_info_extract(sess, &occbs, &userinfo[curoccupant++]);
 	}
 
 	/* 
@@ -413,11 +413,11 @@
 
 	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) {
 		ret = userfunc(sess,
-				rx, 
+				rx,
 				&roominfo,
 				roomname,
 				usercount,
-				userinfo,	
+				userinfo,
 				roomdesc,
 				flags,
 				creationtime,
@@ -428,6 +428,10 @@
 	}
 
 	free(roominfo.name);
+
+	while (usercount > 0)
+		aim_info_free(&userinfo[--usercount]);
+
 	free(userinfo);
 	free(roomname);
 	free(roomdesc);
@@ -446,12 +450,13 @@
 	while (aim_bstream_empty(bs)) {
 		curcount++;
 		userinfo = realloc(userinfo, curcount * sizeof(aim_userinfo_t));
-		aim_extractuserinfo(sess, bs, &userinfo[curcount-1]);
+		aim_info_extract(sess, bs, &userinfo[curcount-1]);
 	}
 
 	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
 		ret = userfunc(sess, rx, curcount, userinfo);
 
+	aim_info_free(userinfo);
 	free(userinfo);
 
 	return ret;
@@ -625,7 +630,7 @@
 		userinfotlv = aim_gettlv(otl, 0x0003, 1);
 
 		aim_bstream_init(&tbs, userinfotlv->value, userinfotlv->length);
-		aim_extractuserinfo(sess, &tbs, &userinfo);
+		aim_info_extract(sess, &tbs, &userinfo);
 	}
 
 	/*
@@ -659,6 +664,7 @@
 	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
 		ret = userfunc(sess, rx, &userinfo, msg);
 
+	aim_info_free(&userinfo);
 	free(cookie);
 	free(msg);
 	aim_freetlvchain(&otl);
--- a/src/protocols/oscar/im.c	Wed Jun 11 22:46:38 2003 +0000
+++ b/src/protocols/oscar/im.c	Thu Jun 12 03:27:58 2003 +0000
@@ -1919,16 +1919,15 @@
 	 * with the TLVs read below, they are two different pieces.  The
 	 * userinfo block contains the number of TLVs that contain user
 	 * information, the rest are not even though there is no seperation.
-	 * aim_extractuserinfo() returns the number of bytes used by the
-	 * userinfo tlvs, so you can start reading the rest of them right
-	 * afterward.  
+	 * You can start reading the message TLVs after aim_info_extract() 
+	 * parses out the standard userinfo block.
 	 *
 	 * That also means that TLV types can be duplicated between the
 	 * userinfo block and the rest of the message, however there should
 	 * never be two TLVs of the same type in one block.
 	 * 
 	 */
-	aim_extractuserinfo(sess, bs, &userinfo);
+	aim_info_extract(sess, bs, &userinfo);
 
 	/*
 	 * From here on, its depends on what channel we're on.
@@ -1962,11 +1961,10 @@
 		aim_freetlvchain(&tlvlist);
 
 	} else {
-
-		faimdprintf(sess, 0, "icbm: ICBM received on an unsupported channel.  Ignoring.\n (chan = %04x)", channel);
+		faimdprintf(sess, 0, "icbm: ICBM received on an unsupported channel.  Ignoring.  (chan = %04x)\n", channel);
+	}
 
-		return 0;
-	}
+	aim_info_free(&userinfo);
 
 	return ret;
 }
@@ -2014,12 +2012,14 @@
 	while (aim_bstream_empty(bs)) {	
 
 		channel = aimbs_get16(bs);
-		aim_extractuserinfo(sess, bs, &userinfo);
+		aim_info_extract(sess, bs, &userinfo);
 		nummissed = aimbs_get16(bs);
 		reason = aimbs_get16(bs);
 
 		if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
 			 ret = userfunc(sess, rx, channel, &userinfo, nummissed, reason);
+
+		aim_info_free(&userinfo);
 	}
 
 	return ret;
--- a/src/protocols/oscar/info.c	Wed Jun 11 22:46:38 2003 +0000
+++ b/src/protocols/oscar/info.c	Thu Jun 12 03:27:58 2003 +0000
@@ -443,11 +443,17 @@
 	return;
 }
 
+faim_internal void aim_info_free(aim_userinfo_t *info)
+{
+	free(info->iconcsum);
+	free(info->availablemsg);
+}
+
 /*
  * AIM is fairly regular about providing user info.  This is a generic 
  * routine to extract it in its standard form.
  */
-faim_internal int aim_extractuserinfo(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *outinfo)
+faim_internal int aim_info_extract(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *outinfo)
 {
 	int curtlv, tlvcnt;
 	fu8_t snlen;
@@ -636,24 +642,54 @@
 			/*
 			 * Type = 0x001d
 			 *
-			 * Buddy icon information.  This contains the info 
-			 * about the buddy icon that the user has stored on 
-			 * the server.
+			 * Buddy icon information and available messages.
+			 *
+			 * This almost seems like the AIM protocol guys gave 
+			 * the iChat guys a Type, and the iChat guys tried to 
+			 * cram as much cool shit into it as possible.  Then 
+			 * the Windows AIM guys were like, "hey, that's 
+			 * pretty neat, let's copy those prawns."
+			 *
+			 * In that spirit, this can contain a custom message, 
+			 * kind of like an away message, but you're not away 
+			 * (it's called an "available" message).  Or it can 
+			 * contain information about the buddy icon the user 
+			 * has stored on the server.
 			 */
-			int flags, number, len;
-			fu8_t *csum;
+			int type2, number, length2;
 
 			while (aim_bstream_curpos(bs) < endpos) {
-				flags = aimbs_get16(bs);
+				type2 = aimbs_get16(bs);
 				number = aimbs_get8(bs);
-				len = aimbs_get8(bs);
-				if ((flags & 0x0001) && (number == 0x01) && (len < 30)) {
-					csum = aimbs_getraw(bs, len);
-					memcpy(outinfo->iconcsum, csum, len);
-					outinfo->iconcsumlen = len;
-					free(csum);
-				} else
-					aim_bstream_advance(bs, len);
+				length2 = aimbs_get8(bs);
+
+				switch (type2) {
+					case 0x0000: { /* This is an official buddy icon? */
+						/* This is always 5 bytes? */
+						aim_bstream_advance(bs, length2);
+					} break;
+
+					case 0x0001: { /* A buddy icon checksum */
+						if ((length2 > 0) && (number == 0x01)) {
+							free(outinfo->iconcsum);
+							outinfo->iconcsum = aimbs_getraw(bs, length2);
+							outinfo->iconcsumlen = length2;
+						} else
+							aim_bstream_advance(bs, length2);
+					} break;
+
+					case 0x0002: { /* An available message */
+						if (length2 > 4) {
+							free(outinfo->availablemsg);
+							outinfo->availablemsg = aimbs_getstr(bs, aimbs_get16(bs));
+						} else
+							aim_bstream_advance(bs, length2);
+					} break;
+
+					default: {
+						aim_bstream_advance(bs, length2);
+					} break;
+				}
 			}
 
 		} else if (type == 0x001e) {
@@ -686,7 +722,7 @@
 }
 
 /*
- * Inverse of aim_extractuserinfo()
+ * Inverse of aim_info_extract()
  */
 faim_internal int aim_putuserinfo(aim_bstream_t *bs, aim_userinfo_t *info)
 {
@@ -816,7 +852,7 @@
 		return 0;
 	}
 
-	aim_extractuserinfo(sess, bs, &userinfo);
+	aim_info_extract(sess, bs, &userinfo);
 
 	tlvlist = aim_readtlvchain(bs);
 
@@ -854,10 +890,9 @@
 	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
 		ret = userfunc(sess, rx, &userinfo, inforeq->infotype, text_encoding, text, textlen);
 
+	aim_info_free(&userinfo);
 	free(text_encoding);
-
 	aim_freetlvchain(&tlvlist);
-
 	if (origsnac)
 		free(origsnac->data);
 	free(origsnac);
--- a/src/protocols/oscar/oscar.c	Wed Jun 11 22:46:38 2003 +0000
+++ b/src/protocols/oscar/oscar.c	Thu Jun 12 03:27:58 2003 +0000
@@ -159,19 +159,20 @@
 	time_t signon;
 	int caps;
 	gboolean typingnot;
+	gchar *availablemsg;
+
+	unsigned long ico_me_len;
+	unsigned long ico_me_csum;
+	time_t ico_me_time;
+	gboolean ico_informed;
 
 	unsigned long ico_len;
 	unsigned long ico_csum;
 	time_t ico_time;
 	gboolean ico_need;
 
-	unsigned long ico_me_len;
-	unsigned long ico_me_csum;
-	time_t ico_me_time;
-	gboolean ico_informed;
-
 	fu16_t iconcsumlen;
-	fu8_t iconcsum[30];
+	fu8_t *iconcsum;
 };
 
 struct name_data {
@@ -286,12 +287,19 @@
 /* prpl actions - remove this at some point */
 static void oscar_set_info(GaimConnection *gc, char *text);
 
-static void gaim_free_name_data(struct name_data *data) {
+static void oscar_free_name_data(struct name_data *data) {
 	g_free(data->name);
 	g_free(data->nick);
 	g_free(data);
 }
 
+static void oscar_free_buddyinfo(void *data) {
+	struct buddyinfo *bi = data;
+	g_free(bi->availablemsg);
+	g_free(bi->iconcsum);
+	g_free(bi);
+}
+
 static fu32_t oscar_encoding_check(const char *utf8)
 {
 	int i = 0;
@@ -654,7 +662,7 @@
 		gc->flags |= OPT_CONN_HTML;
 		gc->flags |= OPT_CONN_AUTO_RESP;
 	}
-	od->buddyinfo = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+	od->buddyinfo = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, oscar_free_buddyinfo);
 
 	sess = g_new0(aim_session_t, 1);
 
@@ -1787,7 +1795,9 @@
 		time_idle -= info->idletime*60;
 	}
 
-	if (info->present & AIM_USERINFO_PRESENT_SESSIONLEN)
+	if (info->present & AIM_USERINFO_PRESENT_ONLINESINCE)
+		signon = info->onlinesince;
+	else if (info->present & AIM_USERINFO_PRESENT_SESSIONLEN)
 		signon = time(NULL) - info->sessionlen;
 
 	if (!aim_sncmp(gaim_account_get_username(gaim_connection_get_account(gc)), info->sn))
@@ -1803,12 +1813,18 @@
 		bi->caps = caps;
 	bi->typingnot = FALSE;
 	bi->ico_informed = FALSE;
+	if (info->availablemsg) {
+		free(bi->availablemsg);
+		bi->availablemsg = g_strdup(info->availablemsg);
+	}
 
 	/* Server stored icon stuff */
 	if (info->iconcsumlen) {
 		char *b16, *saved_b16;
 		struct buddy *b;
 
+		free(bi->iconcsum);
+		bi->iconcsum = malloc(info->iconcsumlen);
 		memcpy(bi->iconcsum, info->iconcsum, info->iconcsumlen);
 		bi->iconcsumlen = info->iconcsumlen;
 		b16 = tobase16(bi->iconcsum, bi->iconcsumlen);
@@ -2424,7 +2440,7 @@
 	gaim_request_input(data->gc, NULL, _("Authorization Request Message:"),
 					   NULL, _("Please authorize me!"), TRUE,
 					   _("OK"), G_CALLBACK(gaim_auth_request),
-					   _("Cancel"), G_CALLBACK(gaim_free_name_data),
+					   _("Cancel"), G_CALLBACK(oscar_free_name_data),
 					   data);
 }
 
@@ -2436,7 +2452,7 @@
 		/* XXX - Take the buddy out of our buddy list */
 	}
 
-	gaim_free_name_data(data);
+	oscar_free_name_data(data);
 }
 
 static void gaim_auth_sendrequest(GaimConnection *gc, const char *name) {
@@ -2483,7 +2499,7 @@
 #endif
 	}
 
-	gaim_free_name_data(data);
+	oscar_free_name_data(data);
 }
 
 /* When other people ask you for authorization */
@@ -2504,7 +2520,7 @@
 	gaim_request_input(data->gc, NULL, _("Authorization Denied Message:"),
 					   NULL, _("No reason given."), TRUE,
 					   _("OK"), G_CALLBACK(gaim_auth_dontgrant),
-					   _("Cancel"), G_CALLBACK(gaim_free_name_data),
+					   _("Cancel"), G_CALLBACK(oscar_free_name_data),
 					   data);
 }
 
@@ -2516,7 +2532,7 @@
 		show_add_buddy(gc, data->name, NULL, data->nick);
 	}
 
-	gaim_free_name_data(data);
+	oscar_free_name_data(data);
 }
 
 static int incomingim_chan4(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch4_args *args, time_t t) {
@@ -2656,7 +2672,7 @@
 										  "to your Buddy List?"),
 										0, data, 2,
 										_("Add"), G_CALLBACK(gaim_icq_contactadd),
-										_("Decline"), G_CALLBACK(gaim_free_name_data));
+										_("Decline"), G_CALLBACK(oscar_free_name_data));
 					g_free(message);
 				}
 				g_strfreev(text);
@@ -5000,7 +5016,7 @@
 	gaim_request_yes_no(gc, NULL, _("Authorization Given"), dialog_msg,
 						0, data,
 						G_CALLBACK(gaim_icq_contactadd),
-						G_CALLBACK(gaim_free_name_data));
+						G_CALLBACK(oscar_free_name_data));
 
 	g_free(dialog_msg);
 	g_free(nombre);
@@ -5353,6 +5369,12 @@
 				yay = g_strconcat(tmp, _("<b>Capabilities:</b> "), caps, "\n", NULL);
 				free(tmp);
 			}
+
+			if (bi->availablemsg) {
+				tmp = yay;
+				yay = g_strconcat(tmp, _("<b>Available:</b> "), bi->availablemsg, "\n", NULL);
+				free(tmp);
+			}
 		}
 	} else {
 		char *gname = aim_ssi_itemlist_findparentname(od->sess->ssi.local, b->name);
@@ -5383,7 +5405,11 @@
 			ret = gaim_icq_status((b->uc & 0xffff0000) >> 16);
 		else
 			ret = g_strdup(_("Away"));
-	} else if (!GAIM_BUDDY_IS_ONLINE(b)) {
+	} else if (GAIM_BUDDY_IS_ONLINE(b)) {
+		struct buddyinfo *bi = g_hash_table_lookup(od->buddyinfo, normalize(b->name));
+		if (bi->availablemsg)
+			ret = g_strdup(bi->availablemsg);
+	} else {
 		char *gname = aim_ssi_itemlist_findparentname(od->sess->ssi.local, b->name);
 		if (aim_ssi_waitingforauth(od->sess->ssi.local, gname, b->name))
 			ret = g_strdup(_("Not Authorized"));
--- a/src/protocols/oscar/service.c	Wed Jun 11 22:46:38 2003 +0000
+++ b/src/protocols/oscar/service.c	Thu Jun 12 03:27:58 2003 +0000
@@ -501,11 +501,13 @@
 	aim_rxcallback_t userfunc;
 	aim_userinfo_t userinfo;
 
-	aim_extractuserinfo(sess, bs, &userinfo);
+	aim_info_extract(sess, bs, &userinfo);
 
 	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
 		ret = userfunc(sess, rx, &userinfo);
 
+	aim_info_free(&userinfo);
+
 	return ret;
 }
 
@@ -522,11 +524,13 @@
 	newevil = aimbs_get16(bs);
 
 	if (aim_bstream_empty(bs))
-		aim_extractuserinfo(sess, bs, &userinfo);
+		aim_info_extract(sess, bs, &userinfo);
 
 	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
 		ret = userfunc(sess, rx, newevil, &userinfo);
 
+	aim_info_free(&userinfo);
+
 	return ret;
 }
 
@@ -938,6 +942,30 @@
 	return 0;
 }
 
+/*
+ * Subtype 0x0021 - Receive our extended status
+ *
+ * This is used for MAC non-away "away" messages, and maybe ICQ extended status messages?
+ */
+static int aim_parse_extstatus(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	int ret = 0;
+	aim_rxcallback_t userfunc;
+	char *msg = NULL;
+	fu16_t id;
+
+	aimbs_get16(bs); /* 0x0002 */
+	aimbs_get16(bs); /* 0x0404 or 0x0407?  Maybe 0x04 and then a length? */
+	msg = aimbs_getstr(bs, aimbs_get16(bs));
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, msg);
+
+	free(msg);
+
+	return ret;
+}
+
 static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
 {
 
@@ -965,6 +993,8 @@
 		return hostversions(sess, mod, rx, snac, bs);
 	else if (snac->subtype == 0x001f)
 		return memrequest(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x0021)
+		return aim_parse_extstatus(sess, mod, rx, snac, bs);
 
 	return 0;
 }