changeset 7011:4375bf2d9020

[gaim-migrate @ 7574] The user-visible changes? Gaim now shows peep's away messages in their tooltip for AIM over oscar. Things to do: -Make sure this doesn't screw up with huge buddy lists -Replace %n with your screen name, etc. in the tooltip -Leave in b, i, u tags in the tooltip -Fix the 2 8 byte memleaks in locate.c Client authors that aren't me will want to read the following pretty closely... I made some internal libfaim changes. I desire to make libfaim cleaner. I don't know if this really helps or not. Here's what I've done: Changed all the SNAC-sending functions in info.c to NOT take a conn argument. The connection is looked up from the session. I'm trying to make oscar.c less-aware of connections. Added aim_locate_finduserinfo() - Use for getting the aim_userinfo_t for the given screenname. Added aim_locate_getinfoshort() - Use this to request that the servers send you profile info, away message, caps, or a combination of the above. It's like aim_locate_getinfo() but less rate limited. Renamed aim_bos_reqlocaterights() to aim_locate_reqrights() Renamed aim_getinfo() to aim_locate_getinfo() Renamed aim_setdirectoryinfo() to aim_locate_setdirinfo() Renamed aim_0002_000b() to aim_locate_000b() Renamed aim_setuserinterests() to aim_locate_setinterests() Removed aim_userinfo_sn() Removed aim_userinfo_flags() Removed aim_userinfo_idle() Removed aim_userinfo_warnlevel() Removed aim_userinfo_createtime() Removed aim_userinfo_membersince() Removed aim_userinfo_onlinesince() Removed aim_userinfo_sessionlen() Removed aim_userinfo_hascap() Renamed info.c to locate.c Made locate.c keep a static, linked list of nodes of sn, away message, available message, user info. This list is maintained by libfaim automatically. Now clients don't have to keep track of all this stuff themselves, and users won't have to wait for away message/info retrieval if the person is in your buddy list. libfaim uses the iChat way of retrieving stuff, which is not nearly as rate limited as the old way. I actually have a feeling WinAIM uses the same SNAC now (although I didn't check), but I like pluggin iChat because it's innovative. Moved sess->emailinfo to a static variable inside email.c. Removed evilhack from oscar.c I think that's about everything... committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Mon, 29 Sep 2003 12:30:03 +0000
parents eb3ed649252e
children 587e3610e1e9
files ChangeLog VERSION configure.ac src/protocols/oscar/Makefile.am src/protocols/oscar/Makefile.mingw src/protocols/oscar/aim.h src/protocols/oscar/aim_internal.h src/protocols/oscar/buddylist.c src/protocols/oscar/conn.c src/protocols/oscar/email.c src/protocols/oscar/info.c src/protocols/oscar/locate.c src/protocols/oscar/oscar.c src/protocols/oscar/ssi.c src/protocols/oscar/tlv.c
diffstat 15 files changed, 1354 insertions(+), 1326 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Mon Sep 29 07:39:10 2003 +0000
+++ b/ChangeLog	Mon Sep 29 12:30:03 2003 +0000
@@ -1,5 +1,9 @@
 Gaim: The Pimpin' Penguin IM Clone that's good for the soul!
 
+version 0.71
+	* Display AIM away messages in the tooltip for buddies when 
+	  they are away.
+
 version 0.70 (09/28/2003):
 	* Implemented Yahoo's new authentication method (Cerulean Studios)
 	* Protocol plugins that have plugin dependencies now load correctly.
--- a/VERSION	Mon Sep 29 07:39:10 2003 +0000
+++ b/VERSION	Mon Sep 29 12:30:03 2003 +0000
@@ -1,1 +1,1 @@
-0.70cvs
+0.71
--- a/configure.ac	Mon Sep 29 07:39:10 2003 +0000
+++ b/configure.ac	Mon Sep 29 12:30:03 2003 +0000
@@ -1,7 +1,7 @@
 dnl Process this file with autoconf to produce a configure script.
 AC_INIT(src/main.c)
 AM_CONFIG_HEADER(config.h)
-AM_INIT_AUTOMAKE([gaim], [0.70])
+AM_INIT_AUTOMAKE([gaim], [0.71])
 
 AC_PREREQ([2.50])
 
--- a/src/protocols/oscar/Makefile.am	Mon Sep 29 07:39:10 2003 +0000
+++ b/src/protocols/oscar/Makefile.am	Mon Sep 29 12:30:03 2003 +0000
@@ -27,8 +27,8 @@
 	ft.c           \
 	icq.c          \
 	im.c           \
-	info.c         \
 	invite.c       \
+	locate.c       \
 	md5.h          \
 	misc.c         \
 	msgcookie.c    \
--- a/src/protocols/oscar/Makefile.mingw	Mon Sep 29 07:39:10 2003 +0000
+++ b/src/protocols/oscar/Makefile.mingw	Mon Sep 29 12:30:03 2003 +0000
@@ -81,8 +81,8 @@
 			ft.c		\
 			icq.c		\
 			im.c		\
-			info.c		\
 			invite.c	\
+			locate.c	\
 			misc.c		\
 			msgcookie.c	\
 			odir.c		\
--- a/src/protocols/oscar/aim.h	Mon Sep 29 07:39:10 2003 +0000
+++ b/src/protocols/oscar/aim.h	Mon Sep 29 12:30:03 2003 +0000
@@ -443,7 +443,6 @@
 	struct aim_icq_info *icq_info;
 	struct aim_oft_info *oft_info;
 	struct aim_authresp_info *authinfo;
-	struct aim_emailinfo *emailinfo;
 
 	/* Server-stored information (ssi) */
 	struct {
@@ -603,16 +602,12 @@
 faim_export int aim_bos_setidle(aim_session_t *, aim_conn_t *, fu32_t);
 faim_export int aim_bos_changevisibility(aim_session_t *, aim_conn_t *, int, const char *);
 faim_export int aim_bos_setbuddylist(aim_session_t *, aim_conn_t *, const char *);
-faim_export int aim_bos_setprofile(aim_session_t *sess, aim_conn_t *conn, const char *profile_encoding, const char *profile, const int profile_len, const char *awaymsg_encoding, const char *awaymsg, const int awaymsg_len, fu32_t caps);
 faim_export int aim_bos_setgroupperm(aim_session_t *, aim_conn_t *, fu32_t mask);
 faim_export int aim_bos_setprivacyflags(aim_session_t *, aim_conn_t *, fu32_t);
 faim_export int aim_reqpersonalinfo(aim_session_t *, aim_conn_t *);
 faim_export int aim_reqservice(aim_session_t *, aim_conn_t *, fu16_t);
 faim_export int aim_bos_reqrights(aim_session_t *, aim_conn_t *);
 faim_export int aim_bos_reqbuddyrights(aim_session_t *, aim_conn_t *);
-faim_export int aim_bos_reqlocaterights(aim_session_t *, aim_conn_t *);
-faim_export int aim_setdirectoryinfo(aim_session_t *sess, aim_conn_t *conn, const char *first, const char *middle, const char *last, const char *maiden, const char *nickname, const char *street, const char *city, const char *state, const char *zip, int country, fu16_t privacy);
-faim_export int aim_setuserinterests(aim_session_t *sess, aim_conn_t *conn, const char *interest1, const char *interest2, const char *interest3, const char *interest4, const char *interest5, fu16_t privacy);
 faim_export int aim_setextstatus(aim_session_t *sess, fu32_t status);
 
 #define AIM_CLIENTTYPE_UNKNOWN  0x0000
@@ -936,24 +931,24 @@
 
 
 
-/* info.c */
+/* 0x0002 - locate.c */
 /*
  * AIM User Info, Standard Form.
  */
-#define AIM_FLAG_UNCONFIRMED 	0x0001 /* "damned transients" */
+#define AIM_FLAG_UNCONFIRMED	0x0001 /* "damned transients" */
 #define AIM_FLAG_ADMINISTRATOR	0x0002
-#define AIM_FLAG_AOL		0x0004
-#define AIM_FLAG_OSCAR_PAY	0x0008
-#define AIM_FLAG_FREE 		0x0010
-#define AIM_FLAG_AWAY		0x0020
-#define AIM_FLAG_ICQ		0x0040
-#define AIM_FLAG_WIRELESS	0x0080
-#define AIM_FLAG_UNKNOWN100	0x0100
-#define AIM_FLAG_UNKNOWN200	0x0200
-#define AIM_FLAG_ACTIVEBUDDY    0x0400
-#define AIM_FLAG_UNKNOWN800	0x0800
-#define AIM_FLAG_ABINTERNAL     0x1000
-#define AIM_FLAG_ALLUSERS	0x001f
+#define AIM_FLAG_AOL			0x0004
+#define AIM_FLAG_OSCAR_PAY		0x0008
+#define AIM_FLAG_FREE 			0x0010
+#define AIM_FLAG_AWAY			0x0020
+#define AIM_FLAG_ICQ			0x0040
+#define AIM_FLAG_WIRELESS		0x0080
+#define AIM_FLAG_UNKNOWN100		0x0100
+#define AIM_FLAG_UNKNOWN200		0x0200
+#define AIM_FLAG_ACTIVEBUDDY	0x0400
+#define AIM_FLAG_UNKNOWN800		0x0800
+#define AIM_FLAG_ABINTERNAL		0x1000
+#define AIM_FLAG_ALLUSERS		0x001f
 
 #define AIM_USERINFO_PRESENT_FLAGS        0x00000001
 #define AIM_USERINFO_PRESENT_MEMBERSINCE  0x00000002
@@ -966,8 +961,8 @@
 #define AIM_USERINFO_PRESENT_SESSIONLEN   0x00000100
 #define AIM_USERINFO_PRESENT_CREATETIME   0x00000200
 
-typedef struct {
-	char sn[MAXSNLEN+1];
+typedef struct aim_userinfo_s {
+	char *sn;
 	fu16_t warnlevel; /* evil percent * 10 (999 = 99.9%) */
 	fu16_t idletime; /* in seconds */
 	fu16_t flags;
@@ -982,22 +977,24 @@
 		fu8_t crap[0x25]; /* until we figure it out... */
 	} icqinfo;
 	fu32_t present;
+
 	fu16_t iconcsumlen;
 	fu8_t *iconcsum;
-	char *availmsg_encoding;
-	char *availmsg;
-	int availmsg_len;
-} aim_userinfo_t;
+
+	char *info;
+	char *info_encoding;
+	fu16_t info_len;
 
-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);
-faim_export float aim_userinfo_warnlevel(aim_userinfo_t *ui);
-faim_export time_t aim_userinfo_createtime(aim_userinfo_t *ui);
-faim_export time_t aim_userinfo_membersince(aim_userinfo_t *ui);
-faim_export time_t aim_userinfo_onlinesince(aim_userinfo_t *ui);
-faim_export fu32_t aim_userinfo_sessionlen(aim_userinfo_t *ui);
-faim_export int aim_userinfo_hascap(aim_userinfo_t *ui, fu32_t cap);
+	char *avail;
+	char *avail_encoding;
+	fu16_t avail_len;
+
+	char *away;
+	char *away_encoding;
+	fu16_t away_len;
+
+	struct aim_userinfo_s *next;
+} aim_userinfo_t;
 
 #define AIM_CAPS_BUDDYICON	0x00000001
 #define AIM_CAPS_VOICE		0x00000002
@@ -1023,17 +1020,11 @@
 #define AIM_CAPS_SECUREIM	0x00200000
 #define AIM_CAPS_LAST		0x00400000
 
-faim_export int aim_0002_000b(aim_session_t *sess, aim_conn_t *conn, const char *sn);
-
 #define AIM_SENDMEMBLOCK_FLAG_ISREQUEST  0
 #define AIM_SENDMEMBLOCK_FLAG_ISHASH     1
 
 faim_export int aim_sendmemblock(aim_session_t *sess, aim_conn_t *conn, fu32_t offset, fu32_t len, const fu8_t *buf, fu8_t flag);
 
-#define AIM_GETINFO_GENERALINFO 0x00001
-#define AIM_GETINFO_AWAYMESSAGE 0x00003
-#define AIM_GETINFO_CAPABILITIES 0x0004
-
 struct aim_invite_priv {
 	char *sn;
 	char *roomname;
@@ -1058,7 +1049,15 @@
 #define AIM_COOKIETYPE_OFTIMAGE 0x14
 #define AIM_COOKIETYPE_OFTICON  0x15
 
-/* 0x0005 */ faim_export int aim_getinfo(aim_session_t *, aim_conn_t *, const char *, fu16_t);
+faim_export aim_userinfo_t *aim_locate_finduserinfo(const char *sn);
+
+/* 0x0002 */ faim_export int aim_locate_reqrights(aim_session_t *sess);
+/* 0x0004 */ faim_export int aim_locate_setprofile(aim_session_t *sess, const char *profile_encoding, const char *profile, const int profile_len, const char *awaymsg_encoding, const char *awaymsg, const int awaymsg_len, fu32_t caps);
+/* 0x0005 */ faim_export int aim_locate_getinfo(aim_session_t *sess, const char *, fu16_t);
+/* 0x0009 */ faim_export int aim_locate_setdirinfo(aim_session_t *sess, const char *first, const char *middle, const char *last, const char *maiden, const char *nickname, const char *street, const char *city, const char *state, const char *zip, int country, fu16_t privacy);
+/* 0x000b */ faim_export int aim_locate_000b(aim_session_t *sess, const char *sn);
+/* 0x000f */ faim_export int aim_locate_setinterests(aim_session_t *sess, const char *interest1, const char *interest2, const char *interest3, const char *interest4, const char *interest5, fu16_t privacy);
+/* 0x0015 */ faim_export int aim_locate_getinfoshort(aim_session_t *sess, const char *sn, fu32_t flags);
 
 
 
--- a/src/protocols/oscar/aim_internal.h	Mon Sep 29 07:39:10 2003 +0000
+++ b/src/protocols/oscar/aim_internal.h	Mon Sep 29 12:30:03 2003 +0000
@@ -188,9 +188,6 @@
 
 faim_internal void aim_conn_addgroup(aim_conn_t *conn, fu16_t group);
 
-faim_internal fu32_t aim_getcap(aim_session_t *sess, aim_bstream_t *bs, int len);
-faim_internal int aim_putcap(aim_bstream_t *bs, fu32_t caps);
-
 faim_internal int aim_cachecookie(aim_session_t *sess, aim_msgcookie_t *cookie);
 faim_internal aim_msgcookie_t *aim_uncachecookie(aim_session_t *sess, fu8_t *cookie, int type);
 faim_internal aim_msgcookie_t *aim_mkcookie(fu8_t *, int, void *);
@@ -199,14 +196,16 @@
 faim_internal int aim_msgcookie_gettype(int reqclass);
 faim_internal int aim_cookie_free(aim_session_t *sess, aim_msgcookie_t *cookie);
 
+/* 0x0002 - locate.c */
+faim_internal void aim_locate_requestuserinfo(aim_session_t *sess, const char *sn);
+faim_internal fu32_t aim_getcap(aim_session_t *sess, aim_bstream_t *bs, int len);
+faim_internal int aim_putcap(aim_bstream_t *bs, fu32_t caps);
 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);
 
-faim_internal void faimdprintf(aim_session_t *sess, int dlevel, const char *format, ...);
-
 faim_internal void aim_conn_kill_chat(aim_session_t *sess, aim_conn_t *conn);
 
 /* These are all handled internally now. */
@@ -215,6 +214,8 @@
 faim_internal int aim_rates_addparam(aim_session_t *, aim_conn_t *);
 faim_internal int aim_rates_delparam(aim_session_t *, aim_conn_t *);
 
+faim_internal void faimdprintf(aim_session_t *sess, int dlevel, const char *format, ...);
+
 #ifndef FAIM_INTERNAL_INSANE
 #define printf() printf called inside libfaim
 #define sprintf() unbounded sprintf used inside libfaim
--- a/src/protocols/oscar/buddylist.c	Mon Sep 29 07:39:10 2003 +0000
+++ b/src/protocols/oscar/buddylist.c	Mon Sep 29 12:30:03 2003 +0000
@@ -252,6 +252,8 @@
 	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
 		ret = userfunc(sess, rx, &userinfo);
 
+	if (snac->subtype == 0x000b)
+		aim_locate_requestuserinfo(sess, userinfo.sn);
 	aim_info_free(&userinfo);
 
 	return ret;
--- a/src/protocols/oscar/conn.c	Mon Sep 29 07:39:10 2003 +0000
+++ b/src/protocols/oscar/conn.c	Mon Sep 29 12:30:03 2003 +0000
@@ -891,7 +891,6 @@
 	sess->ssi.waiting_for_ack = 0;
 
 	sess->authinfo = NULL;
-	sess->emailinfo = NULL;
 
 	/*
 	 * Default to SNAC login unless XORLOGIN is explicitly set.
--- a/src/protocols/oscar/email.c	Mon Sep 29 07:39:10 2003 +0000
+++ b/src/protocols/oscar/email.c	Mon Sep 29 12:30:03 2003 +0000
@@ -12,6 +12,11 @@
 #include <aim.h>
 
 /**
+ * All info maintained by this family.
+ */
+static struct aim_emailinfo *emailinfo = NULL;
+
+/**
  * Subtype 0x0006 - Request information about your email account
  *
  * @param sess The oscar session.
@@ -85,7 +90,7 @@
 	cookie16 = aimbs_getraw(bs, 16); /* Mail cookie sent above */
 
 	/* See if we already have some info associated with this cookie */
-	for (new=sess->emailinfo; (new && strncmp(cookie16, new->cookie16, 16)); new=new->next);
+	for (new=emailinfo; (new && strncmp(cookie16, new->cookie16, 16)); new=new->next);
 	if (new) {
 		/* Free some of the old info, if existant */
 		free(new->cookie8);
@@ -97,8 +102,8 @@
 		if (!(new = malloc(sizeof(struct aim_emailinfo))))
 			return -ENOMEM;
 		memset(new, 0, sizeof(struct aim_emailinfo));
-		new->next = sess->emailinfo;
-		sess->emailinfo = new;
+		new->next = emailinfo;
+		emailinfo = new;
 	}
 
 	new->cookie8 = cookie8;
@@ -177,9 +182,9 @@
 
 static void email_shutdown(aim_session_t *sess, aim_module_t *mod)
 {
-	while (sess->emailinfo) {
-		struct aim_emailinfo *tmp = sess->emailinfo;
-		sess->emailinfo = sess->emailinfo->next;
+	while (emailinfo) {
+		struct aim_emailinfo *tmp = emailinfo;
+		emailinfo = emailinfo->next;
 		free(tmp->cookie16);
 		free(tmp->cookie8);
 		free(tmp->url);
--- a/src/protocols/oscar/info.c	Mon Sep 29 07:39:10 2003 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1044 +0,0 @@
-/*
- * Family 0x0002 - Information.
- *
- * The functions here are responsible for requesting and parsing information-
- * gathering SNACs.  Or something like that. 
- *
- */
-
-#define FAIM_INTERNAL
-#include <aim.h>
-#ifdef _WIN32
-#include "win32dep.h"
-#endif
-
-struct aim_priv_inforeq {
-	char sn[MAXSNLEN+1];
-	fu16_t infotype;
-};
-
-/*
- * Subtype 0x0002
- *
- * Request Location services rights.
- *
- */
-faim_export int aim_bos_reqlocaterights(aim_session_t *sess, aim_conn_t *conn)
-{
-        return aim_genericreq_n(sess, conn, 0x0002, 0x0002);
-}
-
-/*
- * Subtype 0x0004
- *
- * Gives BOS your profile.
- *
- * profile_encoding and awaymsg_encoding MUST be set if profile or
- * away are set, respectively, and their value may or may not be
- * restricted to a few choices.  I am currently aware of:
- * 
- * us-ascii		Just that
- * unicode-2-0		UCS2-BE
- * 
- * profile_len and awaymsg_len MUST be set similarly, and they MUST
- * be the length of their respective strings in bytes.
- *
- * To get the previous behavior of awaymsg == "" un-setting the away
- * message, set awaymsg non-NULL and awaymsg_len to 0 (this is the
- * obvious equivalent).
- * 
- */
-faim_export int aim_bos_setprofile(aim_session_t *sess, aim_conn_t *conn, 
-				  const char *profile_encoding, const char *profile, const int profile_len,
-				  const char *awaymsg_encoding, const char *awaymsg, const int awaymsg_len,
-				  fu32_t caps)
-{
-	static const char defencoding[] = {"text/aolrtf; charset=\"%s\""};
-	aim_frame_t *fr;
-	aim_tlvlist_t *tl = NULL;
-	aim_snacid_t snacid;
-	char *encoding;
-
-	if ((profile && profile_encoding == NULL) || (awaymsg && awaymsg_len && awaymsg_encoding == NULL)) {
-		return -EINVAL;
-	}
-
-	/* Build to packet first to get real length */
-	if (profile) {
-		/* no + 1 here because of %s */
-		encoding = malloc(strlen(defencoding) + strlen(profile_encoding));
-		if (encoding == NULL) {
-			return -ENOMEM;
-		}
-		snprintf(encoding, strlen(defencoding) + strlen(profile_encoding), defencoding, profile_encoding);
-		aim_addtlvtochain_raw(&tl, 0x0001, strlen(encoding), encoding);
-		aim_addtlvtochain_raw(&tl, 0x0002, profile_len, profile);
-		free(encoding);
-	}
-
-	/*
-	 * So here's how this works:
-	 *   - You are away when you have a non-zero-length type 4 TLV stored.
-	 *   - You become unaway when you clear the TLV with a zero-length
-	 *       type 4 TLV.
-	 *   - If you do not send the type 4 TLV, your status does not change
-	 *       (that is, if you were away, you'll remain away).
-	 */
-	if (awaymsg) {
-		if (awaymsg_len) {
-			encoding = malloc(strlen(defencoding) + strlen(awaymsg_encoding));
-			if (encoding == NULL) {
-				return -ENOMEM;
-			}
-			snprintf(encoding, strlen(defencoding) + strlen(awaymsg_encoding), defencoding, awaymsg_encoding);
-			aim_addtlvtochain_raw(&tl, 0x0003, strlen(encoding), encoding);
-			aim_addtlvtochain_raw(&tl, 0x0004, awaymsg_len, awaymsg);
-			free(encoding);
-		} else
-			aim_addtlvtochain_noval(&tl, 0x0004);
-	}
-
-	aim_addtlvtochain_caps(&tl, 0x0005, caps);
-
-	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + aim_sizetlvchain(&tl))))
-		return -ENOMEM;
-
-	snacid = aim_cachesnac(sess, 0x0002, 0x0004, 0x0000, NULL, 0);
-
-	aim_putsnac(&fr->data, 0x0002, 0x004, 0x0000, snacid);
-	aim_writetlvchain(&fr->data, &tl);
-	aim_freetlvchain(&tl);
-
-	aim_tx_enqueue(sess, fr);
-
-	return 0;
-}
-
-/*
- * Subtype 0x0005 - Request info of another AIM user.
- *
- */
-faim_export int aim_getinfo(aim_session_t *sess, aim_conn_t *conn, const char *sn, fu16_t infotype)
-{
-	struct aim_priv_inforeq privdata;
-	aim_frame_t *fr;
-	aim_snacid_t snacid;
-
-	if (!sess || !conn || !sn)
-		return -EINVAL;
-
-	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 12+1+strlen(sn))))
-		return -ENOMEM;
-
-	strncpy(privdata.sn, sn, sizeof(privdata.sn));
-	privdata.infotype = infotype;
-	snacid = aim_cachesnac(sess, 0x0002, 0x0005, 0x0000, &privdata, sizeof(struct aim_priv_inforeq));
-	
-	aim_putsnac(&fr->data, 0x0002, 0x0005, 0x0000, snacid);
-	aimbs_put16(&fr->data, infotype);
-	aimbs_put8(&fr->data, strlen(sn));
-	aimbs_putraw(&fr->data, sn, strlen(sn));
-
-	aim_tx_enqueue(sess, fr);
-
-	return 0;
-}
-
-faim_export const char *aim_userinfo_sn(aim_userinfo_t *ui)
-{
-
-	if (!ui)
-		return NULL;
-
-	return ui->sn;
-}
-
-faim_export fu16_t aim_userinfo_flags(aim_userinfo_t *ui)
-{
-
-	if (!ui)
-		return 0;
-
-	return ui->flags;
-}
-
-faim_export fu16_t aim_userinfo_idle(aim_userinfo_t *ui)
-{
-
-	if (!ui)
-		return 0;
-
-	return ui->idletime;
-}
-
-faim_export float aim_userinfo_warnlevel(aim_userinfo_t *ui)
-{
-
-	if (!ui)
-		return 0.00;
-
-	return (ui->warnlevel / 10);
-}
-
-faim_export time_t aim_userinfo_createtime(aim_userinfo_t *ui)
-{
-
-	if (!ui)
-		return 0;
-
-	return (time_t)ui->createtime;
-}
-
-faim_export time_t aim_userinfo_membersince(aim_userinfo_t *ui)
-{
-
-	if (!ui)
-		return 0;
-
-	return (time_t)ui->membersince;
-}
-
-faim_export time_t aim_userinfo_onlinesince(aim_userinfo_t *ui)
-{
-
-	if (!ui)
-		return 0;
-
-	return (time_t)ui->onlinesince;
-}
-
-faim_export fu32_t aim_userinfo_sessionlen(aim_userinfo_t *ui)
-{
-
-	if (!ui)
-		return 0;
-
-	return ui->sessionlen;
-}
-
-faim_export int aim_userinfo_hascap(aim_userinfo_t *ui, fu32_t cap)
-{
-
-	if (!ui || !(ui->present & AIM_USERINFO_PRESENT_CAPABILITIES))
-		return -1;
-
-	return !!(ui->capabilities & cap);
-}
-
-
-/*
- * Capability blocks. 
- *
- * These are CLSIDs. They should actually be of the form:
- *
- * {0x0946134b, 0x4c7f, 0x11d1,
- *  {0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}},
- *
- * But, eh.
- */
-static const struct {
-	fu32_t flag;
-	fu8_t data[16];
-} aim_caps[] = {
-
-	/*
-	 * These are in ascending numerical order.
-	 */
-	{AIM_CAPS_ICHAT,
-	 {0x09, 0x46, 0x00, 0x00, 0x4c, 0x7f, 0x11, 0xd1, 
-	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-
-	{AIM_CAPS_SECUREIM,
-	 {0x09, 0x46, 0x00, 0x01, 0x4c, 0x7f, 0x11, 0xd1, 
-	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-
-	{AIM_CAPS_HIPTOP,
-	 {0x09, 0x46, 0x13, 0x23, 0x4c, 0x7f, 0x11, 0xd1, 
-	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-
-	{AIM_CAPS_VOICE,
-	 {0x09, 0x46, 0x13, 0x41, 0x4c, 0x7f, 0x11, 0xd1, 
-	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-
-	{AIM_CAPS_SENDFILE,
-	 {0x09, 0x46, 0x13, 0x43, 0x4c, 0x7f, 0x11, 0xd1, 
-	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-
-	/*
-	 * Advertised by the EveryBuddy client.
-	 */
-	{AIM_CAPS_ICQ,
-	 {0x09, 0x46, 0x13, 0x44, 0x4c, 0x7f, 0x11, 0xd1, 
-	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-
-	{AIM_CAPS_DIRECTIM,
-	 {0x09, 0x46, 0x13, 0x45, 0x4c, 0x7f, 0x11, 0xd1, 
-	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-
-	{AIM_CAPS_BUDDYICON,
-	 {0x09, 0x46, 0x13, 0x46, 0x4c, 0x7f, 0x11, 0xd1, 
-	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-
-	/*
-	 * Windows AIM calls this "Add-ins," which is probably more accurate
-	 */
-	{AIM_CAPS_SAVESTOCKS,
-	 {0x09, 0x46, 0x13, 0x47, 0x4c, 0x7f, 0x11, 0xd1,
-	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-
-	{AIM_CAPS_GETFILE,
-	 {0x09, 0x46, 0x13, 0x48, 0x4c, 0x7f, 0x11, 0xd1,
-	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-
-	{AIM_CAPS_ICQSERVERRELAY,
-	 {0x09, 0x46, 0x13, 0x49, 0x4c, 0x7f, 0x11, 0xd1,
-	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-
-	/*
-	 * Indeed, there are two of these.  The former appears to be correct, 
-	 * but in some versions of winaim, the second one is set.  Either they 
-	 * forgot to fix endianness, or they made a typo. It really doesn't 
-	 * matter which.
-	 */
-	{AIM_CAPS_GAMES,
-	 {0x09, 0x46, 0x13, 0x4a, 0x4c, 0x7f, 0x11, 0xd1,
-	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-	{AIM_CAPS_GAMES2,
-	 {0x09, 0x46, 0x13, 0x4a, 0x4c, 0x7f, 0x11, 0xd1,
-	  0x22, 0x82, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-
-	{AIM_CAPS_SENDBUDDYLIST,
-	 {0x09, 0x46, 0x13, 0x4b, 0x4c, 0x7f, 0x11, 0xd1,
-	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-
-	/*
-	 * Setting this lets AIM users receive messages from ICQ users, and ICQ 
-	 * users receive messages from AIM users.  It also lets ICQ users show 
-	 * up in buddy lists for AIM users, and AIM users show up in buddy lists 
-	 * for ICQ users.  And ICQ privacy/invisibility acts like AIM privacy, 
-	 * in that if you add a user to your deny list, you will not be able to 
-	 * see them as online (previous you could still see them, but they 
-	 * couldn't see you.
-	 */
-	{AIM_CAPS_INTEROPERATE,
-	 {0x09, 0x46, 0x13, 0x4d, 0x4c, 0x7f, 0x11, 0xd1,
-	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-
-	{AIM_CAPS_ICQUTF8,
-	 {0x09, 0x46, 0x13, 0x4e, 0x4c, 0x7f, 0x11, 0xd1,
-	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-
-	{AIM_CAPS_ICQUTF8OLD,
-	 {0x2e, 0x7a, 0x64, 0x75, 0xfa, 0xdf, 0x4d, 0xc8,
-	  0x88, 0x6f, 0xea, 0x35, 0x95, 0xfd, 0xb6, 0xdf}},
-
-	/*
-	 * Chat is oddball.
-	 */
-	{AIM_CAPS_CHAT,
-	 {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 
-	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
-
-	/*
-	{AIM_CAPS_ICQ2GO,
-	 {0x56, 0x3f, 0xc8, 0x09, 0x0b, 0x6f, 0x41, 0xbd,
-	  0x9f, 0x79, 0x42, 0x26, 0x09, 0xdf, 0xa2, 0xf3}},
-	*/
-
-	{AIM_CAPS_ICQRTF,
-	 {0x97, 0xb1, 0x27, 0x51, 0x24, 0x3c, 0x43, 0x34,
-	  0xad, 0x22, 0xd6, 0xab, 0xf7, 0x3f, 0x14, 0x92}},
-
-	/* supposed to be ICQRTF?
-	{AIM_CAPS_TRILLUNKNOWN,
-	 {0x97, 0xb1, 0x27, 0x51, 0x24, 0x3c, 0x43, 0x34, 
-	  0xad, 0x22, 0xd6, 0xab, 0xf7, 0x3f, 0x14, 0x09}}, */
-
-	{AIM_CAPS_APINFO, 
-	 {0xaa, 0x4a, 0x32, 0xb5, 0xf8, 0x84, 0x48, 0xc6,
-	  0xa3, 0xd7, 0x8c, 0x50, 0x97, 0x19, 0xfd, 0x5b}},
-
-	{AIM_CAPS_TRILLIANCRYPT,
-	 {0xf2, 0xe7, 0xc7, 0xf4, 0xfe, 0xad, 0x4d, 0xfb,
-	  0xb2, 0x35, 0x36, 0x79, 0x8b, 0xdf, 0x00, 0x00}},
-
-	{AIM_CAPS_EMPTY,
-	 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
-
-	{AIM_CAPS_LAST}
-};
-
-/*
- * This still takes a length parameter even with a bstream because capabilities
- * are not naturally bounded.
- * 
- */
-faim_internal fu32_t aim_getcap(aim_session_t *sess, aim_bstream_t *bs, int len)
-{
-	fu32_t flags = 0;
-	int offset;
-
-	for (offset = 0; aim_bstream_empty(bs) && (offset < len); offset += 0x10) {
-		fu8_t *cap;
-		int i, identified;
-
-		cap = aimbs_getraw(bs, 0x10);
-
-		for (i = 0, identified = 0; !(aim_caps[i].flag & AIM_CAPS_LAST); i++) {
-
-			if (memcmp(&aim_caps[i].data, cap, 0x10) == 0) {
-				flags |= aim_caps[i].flag;
-				identified++;
-				break; /* should only match once... */
-
-			}
-		}
-
-		if (!identified) {
-			faimdprintf(sess, 0, "unknown capability: {%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x}\n",
-					cap[0], cap[1], cap[2], cap[3],
-					cap[4], cap[5],
-					cap[6], cap[7],
-					cap[8], cap[9],
-					cap[10], cap[11], cap[12], cap[13],
-					cap[14], cap[15]);
-		}
-
-		free(cap);
-	}
-
-	return flags;
-}
-
-faim_internal int aim_putcap(aim_bstream_t *bs, fu32_t caps)
-{
-	int i;
-
-	if (!bs)
-		return -EINVAL;
-
-	for (i = 0; aim_bstream_empty(bs); i++) {
-
-		if (aim_caps[i].flag == AIM_CAPS_LAST)
-			break;
-
-		if (caps & aim_caps[i].flag)
-			aimbs_putraw(bs, aim_caps[i].data, 0x10);
-
-	}
-
-	return 0;
-}
-
-static void dumptlv(aim_session_t *sess, fu16_t type, aim_bstream_t *bs, fu8_t len)
-{
-	int i;
-
-	if (!sess || !bs || !len)
-		return;
-
-	faimdprintf(sess, 0, "userinfo:   type  =0x%04x\n", type);
-	faimdprintf(sess, 0, "userinfo:   length=0x%04x\n", len);
-
-	faimdprintf(sess, 0, "userinfo:   value:\n");
-
-	for (i = 0; i < len; i++) {
-		if ((i % 8) == 0)
-			faimdprintf(sess, 0, "\nuserinfo:        ");
-
-		faimdprintf(sess, 0, "0x%2x ", aimbs_get8(bs));
-	}
-
-	faimdprintf(sess, 0, "\n");
-
-	return;
-}
-
-faim_internal void aim_info_free(aim_userinfo_t *info)
-{
-	free(info->iconcsum);
-	free(info->availmsg_encoding);
-	free(info->availmsg);
-}
-
-/*
- * AIM is fairly regular about providing user info.  This is a generic 
- * routine to extract it in its standard form.
- */
-faim_internal int aim_info_extract(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *outinfo)
-{
-	int curtlv, tlvcnt;
-	fu8_t snlen;
-
-	if (!bs || !outinfo)
-		return -EINVAL;
-
-	/* Clear out old data first */
-	memset(outinfo, 0x00, sizeof(aim_userinfo_t));
-
-	/*
-	 * Screen name.  Stored as an unterminated string prepended with a 
-	 * byte containing its length.
-	 */
-	snlen = aimbs_get8(bs);
-	aimbs_getrawbuf(bs, outinfo->sn, snlen);
-
-	/*
-	 * Warning Level.  Stored as an unsigned short.
-	 */
-	outinfo->warnlevel = aimbs_get16(bs);
-
-	/*
-	 * TLV Count. Unsigned short representing the number of 
-	 * Type-Length-Value triples that follow.
-	 */
-	tlvcnt = aimbs_get16(bs);
-
-	/* 
-	 * Parse out the Type-Length-Value triples as they're found.
-	 */
-	for (curtlv = 0; curtlv < tlvcnt; curtlv++) {
-		int endpos;
-		fu16_t type, length;
-
-		type = aimbs_get16(bs);
-		length = aimbs_get16(bs);
-
-		endpos = aim_bstream_curpos(bs) + length;
-
-		if (type == 0x0001) {
-			/*
-			 * Type = 0x0001: User flags
-			 * 
-			 * Specified as any of the following ORed together:
-			 *      0x0001  Trial (user less than 60days)
-			 *      0x0002  Unknown bit 2
-			 *      0x0004  AOL Main Service user
-			 *      0x0008  Unknown bit 4
-			 *      0x0010  Free (AIM) user 
-			 *      0x0020  Away
-			 *      0x0400  ActiveBuddy
-			 *
-			 */
-			outinfo->flags = aimbs_get16(bs);
-			outinfo->present |= AIM_USERINFO_PRESENT_FLAGS;
-
-		} else if (type == 0x0002) {
-			/*
-			 * Type = 0x0002: Account creation time. 
-			 *
-			 * The time/date that the user originally registered for
-			 * the service, stored in time_t format.
-			 *
-			 * I'm not sure how this differs from type 5 ("member
-			 * since").
-			 *
-			 * Note: This is the field formerly known as "member
-			 * since".  All these years and I finally found out
-			 * that I got the name wrong.
-			 */
-			outinfo->createtime = aimbs_get32(bs);
-			outinfo->present |= AIM_USERINFO_PRESENT_CREATETIME;
-
-		} else if (type == 0x0003) {
-			/*
-			 * Type = 0x0003: On-Since date.
-			 *
-			 * The time/date that the user started their current 
-			 * session, stored in time_t format.
-			 */
-			outinfo->onlinesince = aimbs_get32(bs);
-			outinfo->present |= AIM_USERINFO_PRESENT_ONLINESINCE;
-
-		} else if (type == 0x0004) {
-			/*
-			 * Type = 0x0004: Idle time.
-			 *
-			 * Number of minutes since the user actively used the 
-			 * service.
-			 *
-			 * Note that the client tells the server when to start
-			 * counting idle times, so this may or may not be 
-			 * related to reality.
-			 */
-			outinfo->idletime = aimbs_get16(bs);
-			outinfo->present |= AIM_USERINFO_PRESENT_IDLE;
-
-		} else if (type == 0x0005) {
-			/*
-			 * Type = 0x0005: Member since date. 
-			 *
-			 * The time/date that the user originally registered for
-			 * the service, stored in time_t format.
-			 *
-			 * This is sometimes sent instead of type 2 ("account
-			 * creation time"), particularly in the self-info.
-			 * And particularly for ICQ?
-			 */
-			outinfo->membersince = aimbs_get32(bs);
-			outinfo->present |= AIM_USERINFO_PRESENT_MEMBERSINCE;
-
-		} else if (type == 0x0006) {
-			/*
-			 * Type = 0x0006: ICQ Online Status
-			 *
-			 * ICQ's Away/DND/etc "enriched" status. Some decoding 
-			 * of values done by Scott <darkagl@pcnet.com>
-			 */
-			aimbs_get16(bs);
-			outinfo->icqinfo.status = aimbs_get16(bs);
-			outinfo->present |= AIM_USERINFO_PRESENT_ICQEXTSTATUS;
-
-		} else if (type == 0x000a) {
-			/*
-			 * Type = 0x000a
-			 *
-			 * ICQ User IP Address.
-			 * Ahh, the joy of ICQ security.
-			 */
-			outinfo->icqinfo.ipaddr = aimbs_get32(bs);
-			outinfo->present |= AIM_USERINFO_PRESENT_ICQIPADDR;
-
-		} else if (type == 0x000c) {
-			/* 
-			 * Type = 0x000c
-			 *
-			 * random crap containing the IP address,
-			 * apparently a port number, and some Other Stuff.
-			 *
-			 * Format is:
-			 * 4 bytes - Our IP address, 0xc0 a8 01 2b for 192.168.1.43
-			 * 
-			 *
-			 */
-			aimbs_getrawbuf(bs, outinfo->icqinfo.crap, 0x25);
-			outinfo->present |= AIM_USERINFO_PRESENT_ICQDATA;
-
-		} else if (type == 0x000d) {
-			/*
-			 * Type = 0x000d
-			 *
-			 * Capability information.
-			 *
-			 */
-			outinfo->capabilities = aim_getcap(sess, bs, length);
-			outinfo->present |= AIM_USERINFO_PRESENT_CAPABILITIES;
-
-		} else if (type == 0x000e) {
-			/*
-			 * Type = 0x000e
-			 *
-			 * Unknown.  Always of zero length, and always only
-			 * on AOL users.
-			 *
-			 * Ignore.
-			 *
-			 */
-
-		} else if ((type == 0x000f) || (type == 0x0010)) {
-			/*
-			 * Type = 0x000f: Session Length. (AIM)
-			 * Type = 0x0010: Session Length. (AOL)
-			 *
-			 * The duration, in seconds, of the user's current 
-			 * session.
-			 *
-			 * Which TLV type this comes in depends on the
-			 * service the user is using (AIM or AOL).
-			 *
-			 */
-			outinfo->sessionlen = aimbs_get32(bs);
-			outinfo->present |= AIM_USERINFO_PRESENT_SESSIONLEN;
-
-		} else if (type == 0x0019) {
-/*			faimdprintf(sess, 0, "userinfo: **warning: unexpected TLV type 0x0019: from %s\n", outinfo->sn); */
-
-		} else if (type == 0x001b) {
-/*			faimdprintf(sess, 0, "userinfo: **warning: unexpected TLV type 0x001b: from %s\n", outinfo->sn); */
-
-		} else if (type == 0x001d) {
-			/*
-			 * Type = 0x001d
-			 *
-			 * 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 type2, number, length2;
-
-			while (aim_bstream_curpos(bs) < endpos) {
-				type2 = aimbs_get16(bs);
-				number = aimbs_get8(bs);
-				length2 = aimbs_get8(bs);
-
-				switch (type2) {
-					case 0x0000: { /* This is an official buddy icon? */
-						/* This is always 5 bytes of "0x02 01 d2 04 72"? */
-						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->availmsg);
-							outinfo->availmsg_len = aimbs_get16(bs);
-							outinfo->availmsg = aimbs_getstr(bs, outinfo->availmsg_len);
-							if (aimbs_get16(bs) == 0x0001) { /* We have an encoding */
-								aimbs_get16(bs);
-								outinfo->availmsg_encoding = aimbs_getstr(bs, aimbs_get16(bs));
-							} else {
-								/* No explicit encoding, client should use UTF-8 */
-								outinfo->availmsg_encoding = NULL;
-							}
-						} else
-							aim_bstream_advance(bs, length2);
-					} break;
-
-					default: {
-						aim_bstream_advance(bs, length2);
-					} break;
-				}
-			}
-
-		} else if (type == 0x001e) {
-			/*
-			 * Type 30: Unknown.
-			 *
-			 * Always four bytes, but it doesn't look like an int.
-			 */
-		} else {
-
-			/*
-			 * Reaching here indicates that either AOL has
-			 * added yet another TLV for us to deal with, 
-			 * or the parsing has gone Terribly Wrong.
-			 *
-			 * Either way, inform the owner and attempt
-			 * recovery.
-			 *
-			 */
-			faimdprintf(sess, 0, "userinfo: **warning: unexpected TLV:\n");
-			faimdprintf(sess, 0, "userinfo:   sn    =%s\n", outinfo->sn);
-			dumptlv(sess, type, bs, length);
-		}
-
-		/* Save ourselves. */
-		aim_bstream_setpos(bs, endpos);
-	}
-
-	return 0;
-}
-
-/*
- * Inverse of aim_info_extract()
- */
-faim_internal int aim_putuserinfo(aim_bstream_t *bs, aim_userinfo_t *info)
-{
-	aim_tlvlist_t *tlvlist = NULL;
-
-	if (!bs || !info)
-		return -EINVAL;
-
-	aimbs_put8(bs, strlen(info->sn));
-	aimbs_putraw(bs, info->sn, strlen(info->sn));
-
-	aimbs_put16(bs, info->warnlevel);
-
-
-	if (info->present & AIM_USERINFO_PRESENT_FLAGS)
-		aim_addtlvtochain16(&tlvlist, 0x0001, info->flags);
-	if (info->present & AIM_USERINFO_PRESENT_MEMBERSINCE)
-		aim_addtlvtochain32(&tlvlist, 0x0002, info->membersince);
-	if (info->present & AIM_USERINFO_PRESENT_ONLINESINCE)
-		aim_addtlvtochain32(&tlvlist, 0x0003, info->onlinesince);
-	if (info->present & AIM_USERINFO_PRESENT_IDLE)
-		aim_addtlvtochain16(&tlvlist, 0x0004, info->idletime);
-
-/* XXX - So, ICQ_OSCAR_SUPPORT is never defined anywhere... */
-#if ICQ_OSCAR_SUPPORT
-	if (atoi(info->sn) != 0) {
-		if (info->present & AIM_USERINFO_PRESENT_ICQEXTSTATUS)
-			aim_addtlvtochain16(&tlvlist, 0x0006, info->icqinfo.status);
-		if (info->present & AIM_USERINFO_PRESENT_ICQIPADDR)
-			aim_addtlvtochain32(&tlvlist, 0x000a, info->icqinfo.ipaddr);
-	}
-#endif
-
-	if (info->present & AIM_USERINFO_PRESENT_CAPABILITIES)
-		aim_addtlvtochain_caps(&tlvlist, 0x000d, info->capabilities);
- 
-	if (info->present & AIM_USERINFO_PRESENT_SESSIONLEN)
-		aim_addtlvtochain32(&tlvlist, (fu16_t)((info->flags & AIM_FLAG_AOL) ? 0x0010 : 0x000f), info->sessionlen);
-
-	aimbs_put16(bs, aim_counttlvchain(&tlvlist));
-	aim_writetlvchain(bs, &tlvlist);
-	aim_freetlvchain(&tlvlist);
-
-	return 0;
-}
-
-/*
- * Subtype 0x000b - Huh? What is this?
- */
-faim_export int aim_0002_000b(aim_session_t *sess, aim_conn_t *conn, const char *sn)
-{
-	aim_frame_t *fr;
-	aim_snacid_t snacid;
-
-	if (!sess || !conn || !sn)
-		return -EINVAL;
-
-	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+1+strlen(sn))))
-		return -ENOMEM;
-
-	snacid = aim_cachesnac(sess, 0x0002, 0x000b, 0x0000, NULL, 0);
-	
-	aim_putsnac(&fr->data, 0x0002, 0x000b, 0x0000, snacid);
-	aimbs_put8(&fr->data, strlen(sn));
-	aimbs_putraw(&fr->data, sn, strlen(sn));
-
-	aim_tx_enqueue(sess, fr);
-
-	return 0;
-}
-
-/*
- * Subtype 0x0003
- *
- * Normally contains:
- *   t(0001)  - short containing max profile length (value = 1024)
- *   t(0002)  - short - unknown (value = 16) [max MIME type length?]
- *   t(0003)  - short - unknown (value = 10)
- *   t(0004)  - short - unknown (value = 2048) [ICQ only?]
- */
-static int rights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
-{
-	aim_tlvlist_t *tlvlist;
-	aim_rxcallback_t userfunc;
-	int ret = 0;
-	fu16_t maxsiglen = 0;
-
-	tlvlist = aim_readtlvchain(bs);
-
-	if (aim_gettlv(tlvlist, 0x0001, 1))
-		maxsiglen = aim_gettlv16(tlvlist, 0x0001, 1);
-
-	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
-		ret = userfunc(sess, rx, maxsiglen);
-
-	aim_freetlvchain(&tlvlist);
-
-	return ret;
-}
-
-/* Subtype 0x0006 */
-static int userinfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
-{
-	aim_userinfo_t userinfo;
-	char *text_encoding = NULL, *text = NULL;
-	int textlen = 0;
-	aim_rxcallback_t userfunc;
-	aim_tlvlist_t *tlvlist;
-	aim_tlv_t *text_tlv = NULL;
-	aim_snac_t *origsnac = NULL;
-	struct aim_priv_inforeq *inforeq;
-	int ret = 0;
-
-	origsnac = aim_remsnac(sess, snac->id);
-
-	if (!origsnac || !origsnac->data) {
-		faimdprintf(sess, 0, "parse_userinfo_middle: major problem: no snac stored!\n");
-		return 0;
-	}
-
-	inforeq = (struct aim_priv_inforeq *)origsnac->data;
-
-	if ((inforeq->infotype != AIM_GETINFO_GENERALINFO) &&
-			(inforeq->infotype != AIM_GETINFO_AWAYMESSAGE) &&
-			(inforeq->infotype != AIM_GETINFO_CAPABILITIES)) {
-		faimdprintf(sess, 0, "parse_userinfo_middle: unknown infotype in request! (0x%04x)\n", inforeq->infotype);
-		return 0;
-	}
-
-	aim_info_extract(sess, bs, &userinfo);
-
-	tlvlist = aim_readtlvchain(bs);
-
-	/* 
-	 * Depending on what informational text was requested, different
-	 * TLVs will appear here.
-	 *
-	 * Profile will be 1 and 2, away message will be 3 and 4, caps
-	 * will be 5.
-	 */
-	if (inforeq->infotype == AIM_GETINFO_GENERALINFO) {
-		text_encoding = aim_gettlv_str(tlvlist, 0x0001, 1);
-		text_tlv = aim_gettlv(tlvlist, 0x0002, 1);
-	} else if (inforeq->infotype == AIM_GETINFO_AWAYMESSAGE) {
-		text_encoding = aim_gettlv_str(tlvlist, 0x0003, 1);
-		text_tlv = aim_gettlv(tlvlist, 0x0004, 1);
-	} else if (inforeq->infotype == AIM_GETINFO_CAPABILITIES) {
-		aim_tlv_t *ct;
-
-		if ((ct = aim_gettlv(tlvlist, 0x0005, 1))) {
-			aim_bstream_t cbs;
-
-			aim_bstream_init(&cbs, ct->value, ct->length);
-
-			userinfo.capabilities = aim_getcap(sess, &cbs, ct->length);
-			userinfo.present = AIM_USERINFO_PRESENT_CAPABILITIES;
-		}
-	}
-
-	if (text_tlv) {
-		text = text_tlv->value;
-		textlen = text_tlv->length;
-	}
-
-	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);
-
-	return ret;
-}
-
-/* 
- * Subtype 0x0009 - Set directory profile data.
- *
- * This is not the same as aim_bos_setprofile!
- * privacy: 1 to allow searching, 0 to disallow.
- *
- */
-faim_export int aim_setdirectoryinfo(aim_session_t *sess, aim_conn_t *conn, const char *first, const char *middle, const char *last, const char *maiden, const char *nickname, const char *street, const char *city, const char *state, const char *zip, int country, fu16_t privacy) 
-{
-	aim_frame_t *fr;
-	aim_snacid_t snacid;
-	aim_tlvlist_t *tl = NULL;
-
-	aim_addtlvtochain16(&tl, 0x000a, privacy);
-
-	if (first)
-		aim_addtlvtochain_raw(&tl, 0x0001, strlen(first), first);
-	if (last)
-		aim_addtlvtochain_raw(&tl, 0x0002, strlen(last), last);
-	if (middle)
-		aim_addtlvtochain_raw(&tl, 0x0003, strlen(middle), middle);
-	if (maiden)
-		aim_addtlvtochain_raw(&tl, 0x0004, strlen(maiden), maiden);
-
-	if (state)
-		aim_addtlvtochain_raw(&tl, 0x0007, strlen(state), state);
-	if (city)
-		aim_addtlvtochain_raw(&tl, 0x0008, strlen(city), city);
-
-	if (nickname)
-		aim_addtlvtochain_raw(&tl, 0x000c, strlen(nickname), nickname);
-	if (zip)
-		aim_addtlvtochain_raw(&tl, 0x000d, strlen(zip), zip);
-
-	if (street)
-		aim_addtlvtochain_raw(&tl, 0x0021, strlen(street), street);
-
-	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+aim_sizetlvchain(&tl))))
-		return -ENOMEM;
-
-	snacid = aim_cachesnac(sess, 0x0002, 0x0009, 0x0000, NULL, 0);
-
-	aim_putsnac(&fr->data, 0x0002, 0x0009, 0x0000, snacid);
-	aim_writetlvchain(&fr->data, &tl);
-	aim_freetlvchain(&tl);
-
-	aim_tx_enqueue(sess, fr);
-
-	return 0;
-}
-
-/*
- * Subtype 0x000f
- * 
- * XXX pass these in better
- *
- */
-faim_export int aim_setuserinterests(aim_session_t *sess, aim_conn_t *conn, const char *interest1, const char *interest2, const char *interest3, const char *interest4, const char *interest5, fu16_t privacy)
-{
-	aim_frame_t *fr;
-	aim_snacid_t snacid;
-	aim_tlvlist_t *tl = NULL;
-
-	/* ?? privacy ?? */
-	aim_addtlvtochain16(&tl, 0x000a, privacy);
-
-	if (interest1)
-		aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest1), interest1);
-	if (interest2)
-		aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest2), interest2);
-	if (interest3)
-		aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest3), interest3);
-	if (interest4)
-		aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest4), interest4);
-	if (interest5)
-		aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest5), interest5);
-
-	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+aim_sizetlvchain(&tl))))
-		return -ENOMEM;
-
-	snacid = aim_cachesnac(sess, 0x0002, 0x000f, 0x0000, NULL, 0);
-
-	aim_putsnac(&fr->data, 0x0002, 0x000f, 0x0000, 0);
-	aim_writetlvchain(&fr->data, &tl);
-	aim_freetlvchain(&tl);
-
-	aim_tx_enqueue(sess, fr);
-
-	return 0;
-}
-
-static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
-{
-
-	if (snac->subtype == 0x0003)
-		return rights(sess, mod, rx, snac, bs);
-	else if (snac->subtype == 0x0006)
-		return userinfo(sess, mod, rx, snac, bs);
-
-	return 0;
-}
-
-faim_internal int locate_modfirst(aim_session_t *sess, aim_module_t *mod)
-{
-
-	mod->family = 0x0002;
-	mod->version = 0x0001;
-	mod->toolid = 0x0110;
-	mod->toolversion = 0x0629;
-	mod->flags = 0;
-	strncpy(mod->name, "locate", sizeof(mod->name));
-	mod->snachandler = snachandler;
-
-	return 0;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/oscar/locate.c	Mon Sep 29 12:30:03 2003 +0000
@@ -0,0 +1,1139 @@
+/*
+ * Family 0x0002 - Locate.
+ *
+ * The functions here are responsible for requesting and parsing information-
+ * gathering SNACs.  Or something like that.  This family contains the SNACs 
+ * for getting and setting info, away messages, directory profile thingy, etc.
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+#ifdef _WIN32
+#include "win32dep.h"
+#endif
+
+struct node {
+	char *sn;
+	struct node *next;
+};
+
+/**
+ * Keep an aim_userinfo_t for each user we are aware of.
+ */
+static aim_userinfo_t *infos = NULL;
+static struct node *request_queue = NULL;
+static int waiting_for_response = FALSE;
+
+/*
+ * Capability blocks. 
+ *
+ * These are CLSIDs. They should actually be of the form:
+ *
+ * {0x0946134b, 0x4c7f, 0x11d1,
+ *  {0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}},
+ *
+ * But, eh.
+ */
+static const struct {
+	fu32_t flag;
+	fu8_t data[16];
+} aim_caps[] = {
+
+	/*
+	 * These are in ascending numerical order.
+	 */
+	{AIM_CAPS_ICHAT,
+	 {0x09, 0x46, 0x00, 0x00, 0x4c, 0x7f, 0x11, 0xd1, 
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	{AIM_CAPS_SECUREIM,
+	 {0x09, 0x46, 0x00, 0x01, 0x4c, 0x7f, 0x11, 0xd1, 
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	{AIM_CAPS_HIPTOP,
+	 {0x09, 0x46, 0x13, 0x23, 0x4c, 0x7f, 0x11, 0xd1, 
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	{AIM_CAPS_VOICE,
+	 {0x09, 0x46, 0x13, 0x41, 0x4c, 0x7f, 0x11, 0xd1, 
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	{AIM_CAPS_SENDFILE,
+	 {0x09, 0x46, 0x13, 0x43, 0x4c, 0x7f, 0x11, 0xd1, 
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	/*
+	 * Advertised by the EveryBuddy client.
+	 */
+	{AIM_CAPS_ICQ,
+	 {0x09, 0x46, 0x13, 0x44, 0x4c, 0x7f, 0x11, 0xd1, 
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	{AIM_CAPS_DIRECTIM,
+	 {0x09, 0x46, 0x13, 0x45, 0x4c, 0x7f, 0x11, 0xd1, 
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	{AIM_CAPS_BUDDYICON,
+	 {0x09, 0x46, 0x13, 0x46, 0x4c, 0x7f, 0x11, 0xd1, 
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	/*
+	 * Windows AIM calls this "Add-ins," which is probably more accurate
+	 */
+	{AIM_CAPS_SAVESTOCKS,
+	 {0x09, 0x46, 0x13, 0x47, 0x4c, 0x7f, 0x11, 0xd1,
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	{AIM_CAPS_GETFILE,
+	 {0x09, 0x46, 0x13, 0x48, 0x4c, 0x7f, 0x11, 0xd1,
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	{AIM_CAPS_ICQSERVERRELAY,
+	 {0x09, 0x46, 0x13, 0x49, 0x4c, 0x7f, 0x11, 0xd1,
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	/*
+	 * Indeed, there are two of these.  The former appears to be correct, 
+	 * but in some versions of winaim, the second one is set.  Either they 
+	 * forgot to fix endianness, or they made a typo. It really doesn't 
+	 * matter which.
+	 */
+	{AIM_CAPS_GAMES,
+	 {0x09, 0x46, 0x13, 0x4a, 0x4c, 0x7f, 0x11, 0xd1,
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+	{AIM_CAPS_GAMES2,
+	 {0x09, 0x46, 0x13, 0x4a, 0x4c, 0x7f, 0x11, 0xd1,
+	  0x22, 0x82, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	{AIM_CAPS_SENDBUDDYLIST,
+	 {0x09, 0x46, 0x13, 0x4b, 0x4c, 0x7f, 0x11, 0xd1,
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	/*
+	 * Setting this lets AIM users receive messages from ICQ users, and ICQ 
+	 * users receive messages from AIM users.  It also lets ICQ users show 
+	 * up in buddy lists for AIM users, and AIM users show up in buddy lists 
+	 * for ICQ users.  And ICQ privacy/invisibility acts like AIM privacy, 
+	 * in that if you add a user to your deny list, you will not be able to 
+	 * see them as online (previous you could still see them, but they 
+	 * couldn't see you.
+	 */
+	{AIM_CAPS_INTEROPERATE,
+	 {0x09, 0x46, 0x13, 0x4d, 0x4c, 0x7f, 0x11, 0xd1,
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	{AIM_CAPS_ICQUTF8,
+	 {0x09, 0x46, 0x13, 0x4e, 0x4c, 0x7f, 0x11, 0xd1,
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	{AIM_CAPS_ICQUTF8OLD,
+	 {0x2e, 0x7a, 0x64, 0x75, 0xfa, 0xdf, 0x4d, 0xc8,
+	  0x88, 0x6f, 0xea, 0x35, 0x95, 0xfd, 0xb6, 0xdf}},
+
+	/*
+	 * Chat is oddball.
+	 */
+	{AIM_CAPS_CHAT,
+	 {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	/*
+	{AIM_CAPS_ICQ2GO,
+	 {0x56, 0x3f, 0xc8, 0x09, 0x0b, 0x6f, 0x41, 0xbd,
+	  0x9f, 0x79, 0x42, 0x26, 0x09, 0xdf, 0xa2, 0xf3}},
+	*/
+
+	{AIM_CAPS_ICQRTF,
+	 {0x97, 0xb1, 0x27, 0x51, 0x24, 0x3c, 0x43, 0x34,
+	  0xad, 0x22, 0xd6, 0xab, 0xf7, 0x3f, 0x14, 0x92}},
+
+	/* supposed to be ICQRTF?
+	{AIM_CAPS_TRILLUNKNOWN,
+	 {0x97, 0xb1, 0x27, 0x51, 0x24, 0x3c, 0x43, 0x34, 
+	  0xad, 0x22, 0xd6, 0xab, 0xf7, 0x3f, 0x14, 0x09}}, */
+
+	{AIM_CAPS_APINFO, 
+	 {0xaa, 0x4a, 0x32, 0xb5, 0xf8, 0x84, 0x48, 0xc6,
+	  0xa3, 0xd7, 0x8c, 0x50, 0x97, 0x19, 0xfd, 0x5b}},
+
+	{AIM_CAPS_TRILLIANCRYPT,
+	 {0xf2, 0xe7, 0xc7, 0xf4, 0xfe, 0xad, 0x4d, 0xfb,
+	  0xb2, 0x35, 0x36, 0x79, 0x8b, 0xdf, 0x00, 0x00}},
+
+	{AIM_CAPS_EMPTY,
+	 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
+
+	{AIM_CAPS_LAST}
+};
+
+/*
+ * Add the userinfo to our linked list.  If we already have userinfo 
+ * for this buddy, then just overwrite parts of the old data.
+ * @param userinfo Contains the new information for the buddy.
+ */
+static void aim_locate_adduserinfo(aim_userinfo_t *userinfo) {
+	aim_userinfo_t *cur;
+
+	cur = aim_locate_finduserinfo(userinfo->sn);
+
+	if (cur == NULL) {
+		cur = (aim_userinfo_t *)calloc(1, sizeof(aim_userinfo_t));
+		cur->sn = strdup(userinfo->sn);
+		cur->next = infos;
+		infos = cur;
+	}
+
+	cur->warnlevel = userinfo->warnlevel;
+	cur->idletime = userinfo->idletime;
+	if (userinfo->flags != 0)
+		cur->flags = userinfo->flags;
+	if (userinfo->createtime != 0)
+		cur->createtime = userinfo->createtime;
+	if (userinfo->membersince != 0)
+		cur->membersince = userinfo->membersince;
+	if (userinfo->onlinesince != 0)
+		cur->onlinesince = userinfo->onlinesince;
+	if (userinfo->sessionlen != 0)
+		cur->sessionlen = userinfo->sessionlen;
+	if (userinfo->capabilities != 0)
+		cur->capabilities = userinfo->capabilities;
+	cur->present |= userinfo->present;
+
+	if ((userinfo->away != NULL) && (userinfo->away_len > 0)) {
+		free(cur->away);
+		free(cur->away_encoding);
+		cur->away = (char *)malloc(userinfo->away_len);
+		memcpy(cur->away, userinfo->away, userinfo->away_len);
+		cur->away_encoding = strdup(userinfo->away_encoding); /* XXX - This seems to leak occasionally */
+		cur->away_len = userinfo->away_len;
+	}
+
+	if ((userinfo->info != NULL) && (userinfo->info_len > 0)) {
+		free(cur->info);
+		free(cur->info_encoding);
+		cur->info = (char *)malloc(userinfo->info_len);
+		memcpy(cur->info, userinfo->info, userinfo->info_len);
+		cur->info_encoding = strdup(userinfo->info_encoding); /* XXX - This seems to leak occasionally */
+		cur->info_len = userinfo->info_len;
+	}
+}
+
+static void aim_locate_dorequest(aim_session_t *sess) {
+	struct node *cur = request_queue;
+
+	if (cur == NULL)
+		return;
+
+	if (waiting_for_response == TRUE)
+		return;
+
+	waiting_for_response = TRUE;
+	aim_locate_getinfoshort(sess, cur->sn, 0x00000007);
+}
+
+faim_internal void aim_locate_requestuserinfo(aim_session_t *sess, const char *sn) {
+	struct node *cur;
+
+	cur = (struct node *)malloc(sizeof(struct node));
+	cur->sn = strdup(sn);
+	cur->next = request_queue;
+	request_queue = cur;
+
+	aim_locate_dorequest(sess);
+}
+
+faim_export aim_userinfo_t *aim_locate_finduserinfo(const char *sn) {
+	aim_userinfo_t *cur = infos;
+
+	while (cur != NULL) {
+		if (aim_sncmp(cur->sn, sn) == 0)
+			return cur;
+		cur = cur->next;
+	}
+
+	return NULL;
+}
+
+/*
+ * This still takes a length parameter even with a bstream because capabilities
+ * are not naturally bounded.
+ */
+faim_internal fu32_t aim_getcap(aim_session_t *sess, aim_bstream_t *bs, int len)
+{
+	fu32_t flags = 0;
+	int offset;
+
+	for (offset = 0; aim_bstream_empty(bs) && (offset < len); offset += 0x10) {
+		fu8_t *cap;
+		int i, identified;
+
+		cap = aimbs_getraw(bs, 0x10);
+
+		for (i = 0, identified = 0; !(aim_caps[i].flag & AIM_CAPS_LAST); i++) {
+
+			if (memcmp(&aim_caps[i].data, cap, 0x10) == 0) {
+				flags |= aim_caps[i].flag;
+				identified++;
+				break; /* should only match once... */
+
+			}
+		}
+
+		if (!identified) {
+			faimdprintf(sess, 0, "unknown capability: {%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x}\n",
+					cap[0], cap[1], cap[2], cap[3],
+					cap[4], cap[5],
+					cap[6], cap[7],
+					cap[8], cap[9],
+					cap[10], cap[11], cap[12], cap[13],
+					cap[14], cap[15]);
+		}
+
+		free(cap);
+	}
+
+	return flags;
+}
+
+faim_internal int aim_putcap(aim_bstream_t *bs, fu32_t caps)
+{
+	int i;
+
+	if (!bs)
+		return -EINVAL;
+
+	for (i = 0; aim_bstream_empty(bs); i++) {
+
+		if (aim_caps[i].flag == AIM_CAPS_LAST)
+			break;
+
+		if (caps & aim_caps[i].flag)
+			aimbs_putraw(bs, aim_caps[i].data, 0x10);
+
+	}
+
+	return 0;
+}
+
+static void dumptlv(aim_session_t *sess, fu16_t type, aim_bstream_t *bs, fu8_t len)
+{
+	int i;
+
+	if (!sess || !bs || !len)
+		return;
+
+	faimdprintf(sess, 0, "userinfo:   type  =0x%04x\n", type);
+	faimdprintf(sess, 0, "userinfo:   length=0x%04x\n", len);
+	faimdprintf(sess, 0, "userinfo:   value:\n");
+
+	for (i = 0; i < len; i++) {
+		if ((i % 8) == 0)
+			faimdprintf(sess, 0, "\nuserinfo:        ");
+		faimdprintf(sess, 0, "0x%2x ", aimbs_get8(bs));
+	}
+
+	faimdprintf(sess, 0, "\n");
+
+	return;
+}
+
+faim_internal void aim_info_free(aim_userinfo_t *info)
+{
+	free(info->sn);
+	free(info->iconcsum);
+	free(info->info);
+	free(info->info_encoding);
+	free(info->avail);
+	free(info->avail_encoding);
+	free(info->away);
+	free(info->away_encoding);
+}
+
+/*
+ * AIM is fairly regular about providing user info.  This is a generic 
+ * routine to extract it in its standard form.
+ */
+faim_internal int aim_info_extract(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *outinfo)
+{
+	int curtlv, tlvcnt;
+	fu8_t snlen;
+
+	if (!bs || !outinfo)
+		return -EINVAL;
+
+	/* Clear out old data first */
+	memset(outinfo, 0x00, sizeof(aim_userinfo_t));
+
+	/*
+	 * Screen name.  Stored as an unterminated string prepended with a 
+	 * byte containing its length.
+	 */
+	snlen = aimbs_get8(bs);
+	outinfo->sn = aimbs_getstr(bs, snlen);
+
+	/*
+	 * Warning Level.  Stored as an unsigned short.
+	 */
+	outinfo->warnlevel = aimbs_get16(bs);
+
+	/*
+	 * TLV Count. Unsigned short representing the number of 
+	 * Type-Length-Value triples that follow.
+	 */
+	tlvcnt = aimbs_get16(bs);
+
+	/* 
+	 * Parse out the Type-Length-Value triples as they're found.
+	 */
+	for (curtlv = 0; curtlv < tlvcnt; curtlv++) {
+		int endpos;
+		fu16_t type, length;
+
+		type = aimbs_get16(bs);
+		length = aimbs_get16(bs);
+
+		endpos = aim_bstream_curpos(bs) + length;
+
+		if (type == 0x0001) {
+			/*
+			 * Type = 0x0001: User flags
+			 * 
+			 * Specified as any of the following ORed together:
+			 *      0x0001  Trial (user less than 60days)
+			 *      0x0002  Unknown bit 2
+			 *      0x0004  AOL Main Service user
+			 *      0x0008  Unknown bit 4
+			 *      0x0010  Free (AIM) user 
+			 *      0x0020  Away
+			 *      0x0400  ActiveBuddy
+			 *
+			 */
+			outinfo->flags = aimbs_get16(bs);
+			outinfo->present |= AIM_USERINFO_PRESENT_FLAGS;
+
+		} else if (type == 0x0002) {
+			/*
+			 * Type = 0x0002: Account creation time. 
+			 *
+			 * The time/date that the user originally registered for
+			 * the service, stored in time_t format.
+			 *
+			 * I'm not sure how this differs from type 5 ("member
+			 * since").
+			 *
+			 * Note: This is the field formerly known as "member
+			 * since".  All these years and I finally found out
+			 * that I got the name wrong.
+			 */
+			outinfo->createtime = aimbs_get32(bs);
+			outinfo->present |= AIM_USERINFO_PRESENT_CREATETIME;
+
+		} else if (type == 0x0003) {
+			/*
+			 * Type = 0x0003: On-Since date.
+			 *
+			 * The time/date that the user started their current 
+			 * session, stored in time_t format.
+			 */
+			outinfo->onlinesince = aimbs_get32(bs);
+			outinfo->present |= AIM_USERINFO_PRESENT_ONLINESINCE;
+
+		} else if (type == 0x0004) {
+			/*
+			 * Type = 0x0004: Idle time.
+			 *
+			 * Number of minutes since the user actively used the 
+			 * service.
+			 *
+			 * Note that the client tells the server when to start
+			 * counting idle times, so this may or may not be 
+			 * related to reality.
+			 */
+			outinfo->idletime = aimbs_get16(bs);
+			outinfo->present |= AIM_USERINFO_PRESENT_IDLE;
+
+		} else if (type == 0x0005) {
+			/*
+			 * Type = 0x0005: Member since date. 
+			 *
+			 * The time/date that the user originally registered for
+			 * the service, stored in time_t format.
+			 *
+			 * This is sometimes sent instead of type 2 ("account
+			 * creation time"), particularly in the self-info.
+			 * And particularly for ICQ?
+			 */
+			outinfo->membersince = aimbs_get32(bs);
+			outinfo->present |= AIM_USERINFO_PRESENT_MEMBERSINCE;
+
+		} else if (type == 0x0006) {
+			/*
+			 * Type = 0x0006: ICQ Online Status
+			 *
+			 * ICQ's Away/DND/etc "enriched" status. Some decoding 
+			 * of values done by Scott <darkagl@pcnet.com>
+			 */
+			aimbs_get16(bs);
+			outinfo->icqinfo.status = aimbs_get16(bs);
+			outinfo->present |= AIM_USERINFO_PRESENT_ICQEXTSTATUS;
+
+		} else if (type == 0x000a) {
+			/*
+			 * Type = 0x000a
+			 *
+			 * ICQ User IP Address.
+			 * Ahh, the joy of ICQ security.
+			 */
+			outinfo->icqinfo.ipaddr = aimbs_get32(bs);
+			outinfo->present |= AIM_USERINFO_PRESENT_ICQIPADDR;
+
+		} else if (type == 0x000c) {
+			/* 
+			 * Type = 0x000c
+			 *
+			 * random crap containing the IP address,
+			 * apparently a port number, and some Other Stuff.
+			 *
+			 * Format is:
+			 * 4 bytes - Our IP address, 0xc0 a8 01 2b for 192.168.1.43
+			 * 
+			 *
+			 */
+			aimbs_getrawbuf(bs, outinfo->icqinfo.crap, 0x25);
+			outinfo->present |= AIM_USERINFO_PRESENT_ICQDATA;
+
+		} else if (type == 0x000d) {
+			/*
+			 * Type = 0x000d
+			 *
+			 * Capability information.
+			 *
+			 */
+			outinfo->capabilities = aim_getcap(sess, bs, length);
+			outinfo->present |= AIM_USERINFO_PRESENT_CAPABILITIES;
+
+		} else if (type == 0x000e) {
+			/*
+			 * Type = 0x000e
+			 *
+			 * Unknown.  Always of zero length, and always only
+			 * on AOL users.
+			 *
+			 * Ignore.
+			 *
+			 */
+
+		} else if ((type == 0x000f) || (type == 0x0010)) {
+			/*
+			 * Type = 0x000f: Session Length. (AIM)
+			 * Type = 0x0010: Session Length. (AOL)
+			 *
+			 * The duration, in seconds, of the user's current 
+			 * session.
+			 *
+			 * Which TLV type this comes in depends on the
+			 * service the user is using (AIM or AOL).
+			 *
+			 */
+			outinfo->sessionlen = aimbs_get32(bs);
+			outinfo->present |= AIM_USERINFO_PRESENT_SESSIONLEN;
+
+		} else if (type == 0x0019) {
+/*			faimdprintf(sess, 0, "userinfo: **warning: unexpected TLV type 0x0019: from %s\n", outinfo->sn); */
+
+		} else if (type == 0x001b) {
+/*			faimdprintf(sess, 0, "userinfo: **warning: unexpected TLV type 0x001b: from %s\n", outinfo->sn); */
+
+		} else if (type == 0x001d) {
+			/*
+			 * Type = 0x001d
+			 *
+			 * 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 type2, number, length2;
+
+			while (aim_bstream_curpos(bs) < endpos) {
+				type2 = aimbs_get16(bs);
+				number = aimbs_get8(bs);
+				length2 = aimbs_get8(bs);
+
+				switch (type2) {
+					case 0x0000: { /* This is an official buddy icon? */
+						/* This is always 5 bytes of "0x02 01 d2 04 72"? */
+						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->avail);
+							outinfo->avail_len = aimbs_get16(bs);
+							outinfo->avail = aimbs_getstr(bs, outinfo->avail_len);
+							if (aimbs_get16(bs) == 0x0001) { /* We have an encoding */
+								aimbs_get16(bs);
+								outinfo->avail_encoding = aimbs_getstr(bs, aimbs_get16(bs));
+							} else {
+								/* No explicit encoding, client should use UTF-8 */
+								outinfo->avail_encoding = NULL;
+							}
+						} else
+							aim_bstream_advance(bs, length2);
+					} break;
+
+					default: {
+						aim_bstream_advance(bs, length2);
+					} break;
+				}
+			}
+
+		} else if (type == 0x001e) {
+			/*
+			 * Type 30: Unknown.
+			 *
+			 * Always four bytes, but it doesn't look like an int.
+			 */
+		} else {
+
+			/*
+			 * Reaching here indicates that either AOL has
+			 * added yet another TLV for us to deal with, 
+			 * or the parsing has gone Terribly Wrong.
+			 *
+			 * Either way, inform the owner and attempt
+			 * recovery.
+			 *
+			 */
+			faimdprintf(sess, 0, "userinfo: **warning: unexpected TLV:\n");
+			faimdprintf(sess, 0, "userinfo:   sn    =%s\n", outinfo->sn);
+			dumptlv(sess, type, bs, length);
+		}
+
+		/* Save ourselves. */
+		aim_bstream_setpos(bs, endpos);
+	}
+
+	aim_locate_adduserinfo(outinfo);
+
+	return 0;
+}
+
+/*
+ * Inverse of aim_info_extract()
+ */
+faim_internal int aim_putuserinfo(aim_bstream_t *bs, aim_userinfo_t *info)
+{
+	aim_tlvlist_t *tlvlist = NULL;
+
+	if (!bs || !info)
+		return -EINVAL;
+
+	aimbs_put8(bs, strlen(info->sn));
+	aimbs_putraw(bs, info->sn, strlen(info->sn));
+
+	aimbs_put16(bs, info->warnlevel);
+
+	if (info->present & AIM_USERINFO_PRESENT_FLAGS)
+		aim_addtlvtochain16(&tlvlist, 0x0001, info->flags);
+	if (info->present & AIM_USERINFO_PRESENT_MEMBERSINCE)
+		aim_addtlvtochain32(&tlvlist, 0x0002, info->membersince);
+	if (info->present & AIM_USERINFO_PRESENT_ONLINESINCE)
+		aim_addtlvtochain32(&tlvlist, 0x0003, info->onlinesince);
+	if (info->present & AIM_USERINFO_PRESENT_IDLE)
+		aim_addtlvtochain16(&tlvlist, 0x0004, info->idletime);
+
+/* XXX - So, ICQ_OSCAR_SUPPORT is never defined anywhere... */
+#if ICQ_OSCAR_SUPPORT
+	if (atoi(info->sn) != 0) {
+		if (info->present & AIM_USERINFO_PRESENT_ICQEXTSTATUS)
+			aim_addtlvtochain16(&tlvlist, 0x0006, info->icqinfo.status);
+		if (info->present & AIM_USERINFO_PRESENT_ICQIPADDR)
+			aim_addtlvtochain32(&tlvlist, 0x000a, info->icqinfo.ipaddr);
+	}
+#endif
+
+	if (info->present & AIM_USERINFO_PRESENT_CAPABILITIES)
+		aim_addtlvtochain_caps(&tlvlist, 0x000d, info->capabilities);
+ 
+	if (info->present & AIM_USERINFO_PRESENT_SESSIONLEN)
+		aim_addtlvtochain32(&tlvlist, (fu16_t)((info->flags & AIM_FLAG_AOL) ? 0x0010 : 0x000f), info->sessionlen);
+
+	aimbs_put16(bs, aim_counttlvchain(&tlvlist));
+	aim_writetlvchain(bs, &tlvlist);
+	aim_freetlvchain(&tlvlist);
+
+	return 0;
+}
+
+/*
+ * Subtype 0x0002
+ *
+ * Request Location services rights.
+ *
+ */
+faim_export int aim_locate_reqrights(aim_session_t *sess)
+{
+	aim_conn_t *conn;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, AIM_CB_FAM_LOC)))
+		return -EINVAL;
+
+	return aim_genericreq_n(sess, conn, AIM_CB_FAM_LOC, AIM_CB_LOC_REQRIGHTS);
+}
+
+/*
+ * Subtype 0x0003
+ *
+ * Normally contains:
+ *   t(0001)  - short containing max profile length (value = 1024)
+ *   t(0002)  - short - unknown (value = 16) [max MIME type length?]
+ *   t(0003)  - short - unknown (value = 10)
+ *   t(0004)  - short - unknown (value = 2048) [ICQ only?]
+ */
+static int rights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_tlvlist_t *tlvlist;
+	aim_rxcallback_t userfunc;
+	int ret = 0;
+	fu16_t maxsiglen = 0;
+
+	tlvlist = aim_readtlvchain(bs);
+
+	if (aim_gettlv(tlvlist, 0x0001, 1))
+		maxsiglen = aim_gettlv16(tlvlist, 0x0001, 1);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, maxsiglen);
+
+	aim_freetlvchain(&tlvlist);
+
+	return ret;
+}
+
+/*
+ * Subtype 0x0004
+ *
+ * Gives BOS your profile.
+ *
+ * profile_encoding and awaymsg_encoding MUST be set if profile or
+ * away are set, respectively, and their value may or may not be
+ * restricted to a few choices.  I am currently aware of:
+ * 
+ * us-ascii		Just that
+ * unicode-2-0		UCS2-BE
+ * 
+ * profile_len and awaymsg_len MUST be set similarly, and they MUST
+ * be the length of their respective strings in bytes.
+ *
+ * To get the previous behavior of awaymsg == "" un-setting the away
+ * message, set awaymsg non-NULL and awaymsg_len to 0 (this is the
+ * obvious equivalent).
+ * 
+ */
+faim_export int aim_locate_setprofile(aim_session_t *sess,
+				  const char *profile_encoding, const char *profile, const int profile_len,
+				  const char *awaymsg_encoding, const char *awaymsg, const int awaymsg_len,
+				  fu32_t caps)
+{
+	aim_conn_t *conn;
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	aim_tlvlist_t *tl = NULL;
+	char *encoding;
+	static const char defencoding[] = {"text/aolrtf; charset=\"%s\""};
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, AIM_CB_FAM_LOC)))
+		return -EINVAL;
+
+	if ((profile && profile_encoding == NULL) || (awaymsg && awaymsg_len && awaymsg_encoding == NULL)) {
+		return -EINVAL;
+	}
+
+	/* Build the packet first to get real length */
+	if (profile) {
+		/* no + 1 here because of %s */
+		encoding = malloc(strlen(defencoding) + strlen(profile_encoding));
+		if (encoding == NULL) {
+			return -ENOMEM;
+		}
+		snprintf(encoding, strlen(defencoding) + strlen(profile_encoding), defencoding, profile_encoding);
+		aim_addtlvtochain_raw(&tl, 0x0001, strlen(encoding), encoding);
+		aim_addtlvtochain_raw(&tl, 0x0002, profile_len, profile);
+		free(encoding);
+	}
+
+	/*
+	 * So here's how this works:
+	 *   - You are away when you have a non-zero-length type 4 TLV stored.
+	 *   - You become unaway when you clear the TLV with a zero-length
+	 *       type 4 TLV.
+	 *   - If you do not send the type 4 TLV, your status does not change
+	 *       (that is, if you were away, you'll remain away).
+	 */
+	if (awaymsg) {
+		if (awaymsg_len) {
+			encoding = malloc(strlen(defencoding) + strlen(awaymsg_encoding));
+			if (encoding == NULL) {
+				return -ENOMEM;
+			}
+			snprintf(encoding, strlen(defencoding) + strlen(awaymsg_encoding), defencoding, awaymsg_encoding);
+			aim_addtlvtochain_raw(&tl, 0x0003, strlen(encoding), encoding);
+			aim_addtlvtochain_raw(&tl, 0x0004, awaymsg_len, awaymsg);
+			free(encoding);
+		} else
+			aim_addtlvtochain_noval(&tl, 0x0004);
+	}
+
+	aim_addtlvtochain_caps(&tl, 0x0005, caps);
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + aim_sizetlvchain(&tl))))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0002, 0x0004, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0002, 0x004, 0x0000, snacid);
+
+	aim_writetlvchain(&fr->data, &tl);
+	aim_freetlvchain(&tl);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * Subtype 0x0005 - Request info of another AIM user.
+ *
+ * @param sn The screenname whose info you wish to request.
+ * @param infotype The type of info you wish to request.
+ *        0x0001 - Info/profile
+ *        0x0003 - Away message
+ *        0x0004 - Capabilities
+ */
+faim_export int aim_locate_getinfo(aim_session_t *sess, const char *sn, fu16_t infotype)
+{
+	aim_conn_t *conn;
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, AIM_CB_FAM_LOC)) || !sn)
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 12+1+strlen(sn))))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0002, 0x0005, 0x0000, NULL, 0);
+	
+	aim_putsnac(&fr->data, 0x0002, 0x0005, 0x0000, snacid);
+	aimbs_put16(&fr->data, infotype);
+	aimbs_put8(&fr->data, strlen(sn));
+	aimbs_putraw(&fr->data, sn, strlen(sn));
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/* Subtype 0x0006 */
+static int userinfo(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;
+	aim_userinfo_t *userinfo, *userinfo2;
+	aim_tlvlist_t *tlvlist;
+	aim_tlv_t *tlv = NULL;
+	int was_explicit;
+	struct node *cur, *del;
+
+	userinfo = (aim_userinfo_t *)malloc(sizeof(aim_userinfo_t));
+	aim_info_extract(sess, bs, userinfo);
+	tlvlist = aim_readtlvchain(bs);
+
+	/* Profile will be 1 and 2 */
+	userinfo->info_encoding = aim_gettlv_str(tlvlist, 0x0001, 1);
+	if ((tlv = aim_gettlv(tlvlist, 0x0002, 1))) {
+		userinfo->info = (char *)malloc(tlv->length);
+		memcpy(userinfo->info, tlv->value, tlv->length);
+		userinfo->info_len = tlv->length;
+	}
+
+	/* Away message will be 3 and 4 */
+	userinfo->away_encoding = aim_gettlv_str(tlvlist, 0x0003, 1);
+	if ((tlv = aim_gettlv(tlvlist, 0x0004, 1))) {
+		userinfo->away = (char *)malloc(tlv->length);
+		memcpy(userinfo->away, tlv->value, tlv->length);
+		userinfo->away_len = tlv->length;
+	}
+
+	/* Caps will be 5 */
+	if ((tlv = aim_gettlv(tlvlist, 0x0005, 1))) {
+		aim_bstream_t cbs;
+		aim_bstream_init(&cbs, tlv->value, tlv->length);
+		userinfo->capabilities = aim_getcap(sess, &cbs, tlv->length);
+		userinfo->present = AIM_USERINFO_PRESENT_CAPABILITIES;
+	}
+	aim_freetlvchain(&tlvlist);
+
+	aim_locate_adduserinfo(userinfo);
+	userinfo2 = aim_locate_finduserinfo(userinfo->sn);
+	aim_info_free(userinfo);
+	free(userinfo);
+
+	/*
+	 * Remove this screen name from our queue.  If the client requested 
+	 * this buddy's info explicitly, then notify them that we have info 
+	 * for this buddy.
+	 */
+	was_explicit = TRUE;
+	while ((request_queue != NULL) && (aim_sncmp(userinfo2->sn, request_queue->sn) == 0)) {
+		del = request_queue;
+		request_queue = del->next;
+		was_explicit = FALSE;
+		free(del->sn);
+		free(del);
+	}
+	cur = request_queue;
+	while ((cur != NULL) && (cur->next != NULL)) {
+		if (aim_sncmp(userinfo2->sn, cur->next->sn) == 0) {
+			del = cur->next;
+			cur->next = del->next;
+			was_explicit = FALSE;
+			free(del->sn);
+			free(del);
+		} else
+			cur = cur->next;
+	}
+
+	if (was_explicit == TRUE) {
+		if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+			ret = userfunc(sess, rx, userinfo2);
+	} else {
+		waiting_for_response = FALSE;
+		aim_locate_dorequest(sess);
+	}
+
+	return ret;
+}
+
+/* 
+ * Subtype 0x0009 - Set directory profile data.
+ *
+ * This is not the same as aim_location_setprofile!
+ * privacy: 1 to allow searching, 0 to disallow.
+ *
+ */
+faim_export int aim_locate_setdirinfo(aim_session_t *sess, const char *first, const char *middle, const char *last, const char *maiden, const char *nickname, const char *street, const char *city, const char *state, const char *zip, int country, fu16_t privacy) 
+{
+	aim_conn_t *conn;
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	aim_tlvlist_t *tl = NULL;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, AIM_CB_FAM_LOC)))
+		return -EINVAL;
+
+	aim_addtlvtochain16(&tl, 0x000a, privacy);
+
+	if (first)
+		aim_addtlvtochain_raw(&tl, 0x0001, strlen(first), first);
+	if (last)
+		aim_addtlvtochain_raw(&tl, 0x0002, strlen(last), last);
+	if (middle)
+		aim_addtlvtochain_raw(&tl, 0x0003, strlen(middle), middle);
+	if (maiden)
+		aim_addtlvtochain_raw(&tl, 0x0004, strlen(maiden), maiden);
+
+	if (state)
+		aim_addtlvtochain_raw(&tl, 0x0007, strlen(state), state);
+	if (city)
+		aim_addtlvtochain_raw(&tl, 0x0008, strlen(city), city);
+
+	if (nickname)
+		aim_addtlvtochain_raw(&tl, 0x000c, strlen(nickname), nickname);
+	if (zip)
+		aim_addtlvtochain_raw(&tl, 0x000d, strlen(zip), zip);
+
+	if (street)
+		aim_addtlvtochain_raw(&tl, 0x0021, strlen(street), street);
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+aim_sizetlvchain(&tl))))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0002, 0x0009, 0x0000, NULL, 0);
+
+	aim_putsnac(&fr->data, 0x0002, 0x0009, 0x0000, snacid);
+	aim_writetlvchain(&fr->data, &tl);
+	aim_freetlvchain(&tl);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * Subtype 0x000b - Huh? What is this?
+ */
+faim_export int aim_locate_000b(aim_session_t *sess, const char *sn)
+{
+	aim_conn_t *conn;
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+		return -EINVAL;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, AIM_CB_FAM_LOC)) || !sn)
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+1+strlen(sn))))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0002, 0x000b, 0x0000, NULL, 0);
+	
+	aim_putsnac(&fr->data, 0x0002, 0x000b, 0x0000, snacid);
+	aimbs_put8(&fr->data, strlen(sn));
+	aimbs_putraw(&fr->data, sn, strlen(sn));
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * Subtype 0x000f
+ * 
+ * XXX pass these in better
+ *
+ */
+faim_export int aim_locate_setinterests(aim_session_t *sess, const char *interest1, const char *interest2, const char *interest3, const char *interest4, const char *interest5, fu16_t privacy)
+{
+	aim_conn_t *conn;
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	aim_tlvlist_t *tl = NULL;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, AIM_CB_FAM_LOC)))
+		return -EINVAL;
+
+	/* ?? privacy ?? */
+	aim_addtlvtochain16(&tl, 0x000a, privacy);
+
+	if (interest1)
+		aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest1), interest1);
+	if (interest2)
+		aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest2), interest2);
+	if (interest3)
+		aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest3), interest3);
+	if (interest4)
+		aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest4), interest4);
+	if (interest5)
+		aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest5), interest5);
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+aim_sizetlvchain(&tl))))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0002, 0x000f, 0x0000, NULL, 0);
+
+	aim_putsnac(&fr->data, 0x0002, 0x000f, 0x0000, 0);
+	aim_writetlvchain(&fr->data, &tl);
+	aim_freetlvchain(&tl);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * Subtype 0x0015 - Request the info a user using the short method.  This is 
+ * what iChat uses.  It normally is VERY leniently rate limited.
+ *
+ * @param sn The screen name whose info you wish to request.
+ * @param flags The bitmask which specifies the type of info you wish to request.
+ *        0x00000001 - Info/profile.
+ *        0x00000002 - Away message.
+ *        0x00000004 - Capabilities.
+ *        0x00000008 - Certification.
+ * @return Return 0 if no errors, otherwise return the error number.
+ */
+faim_export int aim_locate_getinfoshort(aim_session_t *sess, const char *sn, fu32_t flags)
+{
+	aim_conn_t *conn;
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, AIM_CB_FAM_LOC)) || !sn)
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+4+1+strlen(sn))))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0002, 0x0015, 0x0000, NULL, 0);
+
+	aim_putsnac(&fr->data, 0x0002, 0x0015, 0x0000, 0);
+	aimbs_put32(&fr->data, flags);
+	aimbs_put8(&fr->data, strlen(sn));
+	aimbs_putraw(&fr->data, sn, strlen(sn));
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+	if (snac->subtype == 0x0003)
+		return rights(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x0006)
+		return userinfo(sess, mod, rx, snac, bs);
+
+	return 0;
+}
+
+static void locate_shutdown(aim_session_t *sess, aim_module_t *mod)
+{
+	aim_userinfo_t *del;
+
+	while (infos) {
+		del = infos;
+		infos = infos->next;
+		free(del->sn);
+		free(del->info);
+		free(del->avail);
+		free(del->away);
+		free(del);
+	}
+}
+
+faim_internal int locate_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = AIM_CB_FAM_LOC;
+	mod->version = 0x0001;
+	mod->toolid = 0x0110;
+	mod->toolversion = 0x0629;
+	mod->flags = 0;
+	strncpy(mod->name, "locate", sizeof(mod->name));
+	mod->snachandler = snachandler;
+	mod->shutdown = locate_shutdown;
+
+	return 0;
+}
--- a/src/protocols/oscar/oscar.c	Mon Sep 29 07:39:10 2003 +0000
+++ b/src/protocols/oscar/oscar.c	Mon Sep 29 12:30:03 2003 +0000
@@ -100,7 +100,6 @@
 
 	gboolean killme;
 	gboolean icq;
-	GSList *evilhack;
 	guint icontimer;
 	guint getblisttimer;
 
@@ -150,10 +149,12 @@
 	fu8_t cookie[8];
 };
 
-/* Various PRPL-specific buddy info that we want to keep track of */
+/*
+ * Various PRPL-specific buddy info that we want to keep track of
+ * Some other info is maintained by locate.c, and I'd like to move 
+ * the rest of this to libfaim, mostly im.c
+ */
 struct buddyinfo {
-	time_t signon;
-	int caps;
 	gboolean typingnot;
 	gchar *availmsg;
 	fu32_t ipaddr;
@@ -167,9 +168,6 @@
 	unsigned long ico_csum;
 	time_t ico_time;
 	gboolean ico_need;
-
-	fu16_t iconcsumlen;
-	fu8_t *iconcsum;
 };
 
 struct name_data {
@@ -218,7 +216,7 @@
 static int gaim_parse_incoming_im(aim_session_t *, aim_frame_t *, ...);
 static int gaim_parse_misses     (aim_session_t *, aim_frame_t *, ...);
 static int gaim_parse_clientauto (aim_session_t *, aim_frame_t *, ...);
-static int gaim_parse_user_info  (aim_session_t *, aim_frame_t *, ...);
+static int gaim_parse_userinfo   (aim_session_t *, aim_frame_t *, ...);
 static int gaim_parse_motd       (aim_session_t *, aim_frame_t *, ...);
 static int gaim_chatnav_info     (aim_session_t *, aim_frame_t *, ...);
 static int gaim_chat_join        (aim_session_t *, aim_frame_t *, ...);
@@ -294,7 +292,6 @@
 static void oscar_free_buddyinfo(void *data) {
 	struct buddyinfo *bi = data;
 	g_free(bi->availmsg);
-	g_free(bi->iconcsum);
 	g_free(bi);
 }
 
@@ -732,10 +729,6 @@
 		free(sn);
 	}
 	g_hash_table_destroy(od->buddyinfo);
-	while (od->evilhack) {
-		g_free(od->evilhack->data);
-		od->evilhack = g_slist_remove(od->evilhack, od->evilhack->data);
-	}
 	while (od->create_rooms) {
 		struct create_room *cr = od->create_rooms->data;
 		g_free(cr->name);
@@ -1135,7 +1128,7 @@
 	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOK, 0x0003, gaim_parse_searchreply, 0);
 	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_ERROR, gaim_parse_msgerr, 0);
 	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_MTN, gaim_parse_mtn, 0);
-	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_USERINFO, gaim_parse_user_info, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_USERINFO, gaim_parse_userinfo, 0);
 	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_ACK, gaim_parse_msgack, 0);
 	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_MOTD, gaim_parse_motd, 0);
 	aim_conn_addhandler(sess, bosconn, 0x0004, 0x0005, gaim_icbm_param_info, 0);
@@ -1342,12 +1335,12 @@
 }
 
 static int gaim_parse_login(aim_session_t *sess, aim_frame_t *fr, ...) {
-	char *key;
-	va_list ap;
 	GaimConnection *gc = sess->aux_data;
 	GaimAccount *account = gaim_connection_get_account(gc);
 	GaimAccount *ac = gaim_connection_get_account(gc);
 	struct oscar_data *od = gc->proto_data;
+	va_list ap;
+	char *key;
 
 	va_start(ap, fr);
 	key = va_arg(ap, char *);
@@ -1809,23 +1802,20 @@
 		bi = g_new0(struct buddyinfo, 1);
 		g_hash_table_insert(od->buddyinfo, g_strdup(normalize(info->sn)), bi);
 	}
-	bi->signon = info->onlinesince ? info->onlinesince : (info->sessionlen + time(NULL));
-	if (caps)
-		bi->caps = caps;
 	bi->typingnot = FALSE;
 	bi->ico_informed = FALSE;
 	bi->ipaddr = info->icqinfo.ipaddr;
 
 	/* Available message stuff */
 	free(bi->availmsg);
-	if (info->availmsg)
-		if (info->availmsg_encoding) {
-			gchar *enc = g_strdup_printf("charset=\"%s\"", info->availmsg_encoding);
-			bi->availmsg = oscar_encoding_to_utf8(enc, info->availmsg, info->availmsg_len);
+	if (info->avail != NULL)
+		if (info->avail_encoding) {
+			gchar *enc = g_strdup_printf("charset=\"%s\"", info->avail_encoding);
+			bi->availmsg = oscar_encoding_to_utf8(enc, info->avail, info->avail_len);
 			g_free(enc);
 		} else {
 			/* No explicit encoding means utf8.  Yay. */
-			bi->availmsg = g_strdup(info->availmsg);
+			bi->availmsg = g_strdup(info->avail);
 		}
 	else
 		bi->availmsg = NULL;
@@ -1835,11 +1825,7 @@
 		char *b16, *saved_b16;
 		GaimBuddy *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);
+		b16 = tobase16(info->iconcsum, info->iconcsumlen);
 		b = gaim_find_buddy(gc->account, info->sn);
 		saved_b16 = gaim_buddy_get_setting(b, "icon_checksum");
 		if (!b16 || !saved_b16 || strcmp(b16, saved_b16)) {
@@ -2880,30 +2866,11 @@
 		case 0x0003: { /* Reply from an ICQ status message request */
 			char *status_msg = gaim_icq_status(state);
 			char *dialog_msg, **splitmsg;
-			struct oscar_data *od = gc->proto_data;
-			GSList *l = od->evilhack;
-			gboolean evilhack = FALSE;
 
 			/* Split at (carriage return/newline)'s, then rejoin later with BRs between. */
 			splitmsg = g_strsplit(msg, "\r\n", 0);
 
-			/* If who is in od->evilhack, then we're just getting the away message, otherwise this 
-			 * will just get appended to the info box (which is already showing). */
-			while (l) {
-				char *x = l->data;
-				if (!strcmp(x, normalize(who))) {
-					evilhack = TRUE;
-					g_free(x);
-					od->evilhack = g_slist_remove(od->evilhack, x);
-					break;
-				}
-				l = l->next;
-			}
-
-			if (evilhack)
-				dialog_msg = g_strdup_printf(_("<B>UIN:</B> %s<BR><B>Status:</B> %s<HR>%s"), who, status_msg, g_strjoinv("<BR>", splitmsg));
-			else
-				dialog_msg = g_strdup_printf(_("<B>Status:</B> %s<HR>%s"), status_msg, g_strjoinv("<BR>", splitmsg));
+			dialog_msg = g_strdup_printf(_("<B>UIN:</B> %s<BR><B>Status:</B> %s<HR>%s"), who, status_msg, g_strjoinv("<BR>", splitmsg));
 			g_show_info_text(gc, who, 2, dialog_msg, NULL);
 
 			g_free(status_msg);
@@ -3156,110 +3123,54 @@
 	return buf; 
 }
 
-static int gaim_parse_user_info(aim_session_t *sess, aim_frame_t *fr, ...) {
+static int gaim_parse_userinfo(aim_session_t *sess, aim_frame_t *fr, ...) {
 	GaimConnection *gc = sess->aux_data;
-	struct oscar_data *od = gc->proto_data;
-	gchar *header;
-	GSList *l = od->evilhack;
-	gboolean evilhack = FALSE;
-	gchar *membersince = NULL, *onlinesince = NULL, *idle = NULL;
+	GString *text;
+	char *info_utf8 = NULL, *away_utf8 = NULL;
 	va_list ap;
-	aim_userinfo_t *info;
-	fu16_t infotype;
-	char *text_enc = NULL, *text = NULL, *utf8 = NULL;
-	int text_len;
-	const char *username = gaim_account_get_username(gaim_connection_get_account(gc));
+	aim_userinfo_t *userinfo;
 
 	va_start(ap, fr);
-	info = va_arg(ap, aim_userinfo_t *);
-	infotype = (fu16_t) va_arg(ap, unsigned int);
-	text_enc = va_arg(ap, char *);
-	text = va_arg(ap, char *);
-	text_len = va_arg(ap, int);
+	userinfo = va_arg(ap, aim_userinfo_t *);
 	va_end(ap);
 
-	if (text_len > 0) {
-		if (!(utf8 = oscar_encoding_to_utf8(text_enc, text, text_len))) {
-			utf8 = g_strdup(_("<i>Unable to display information because it was sent in an unknown encoding.</i>"));
-			gaim_debug(GAIM_DEBUG_ERROR, "oscar",
-					   "Encountered an unknown encoding while parsing userinfo\n");
-		}
-	}
-
-	if (info->present & AIM_USERINFO_PRESENT_ONLINESINCE) {
-		onlinesince = g_strdup_printf(_("Online Since : <b>%s</b><br>\n"),
-					asctime(localtime((time_t *)&info->onlinesince)));
-	}
-
-	if (info->present & AIM_USERINFO_PRESENT_MEMBERSINCE) {
-		membersince = g_strdup_printf(_("Member Since : <b>%s</b><br>\n"),
-					asctime(localtime((time_t *)&info->membersince)));
-	}
-
-	if (info->present & AIM_USERINFO_PRESENT_IDLE) {
-		gchar *itime = sec_to_text(info->idletime*60);
-		idle = g_strdup_printf(_("Idle : <b>%s</b>"), itime);
+	text = g_string_new("");
+	g_string_append_printf(text, _("Username: <b>%s</b><br>\n"), userinfo->sn);
+	g_string_append_printf(text, _("Warning Level: <b>%d%%</b><br>\n"), (int)((userinfo->warnlevel/10.0) + 0.5));
+
+	if (userinfo->present & AIM_USERINFO_PRESENT_ONLINESINCE)
+		g_string_append_printf(text, _("Online Since: <b>%s</b><br>\n"),
+					asctime(localtime((time_t *)&userinfo->onlinesince)));
+
+	if (userinfo->present & AIM_USERINFO_PRESENT_MEMBERSINCE)
+		g_string_append_printf(text, _("Member Since: <b>%s</b><br>\n"),
+					asctime(localtime((time_t *)&userinfo->membersince)));
+
+	if (userinfo->present & AIM_USERINFO_PRESENT_IDLE) {
+		gchar *itime = sec_to_text(userinfo->idletime*60);
+		g_string_append_printf(text, _("Idle: <b>%s</b>"), itime);
 		g_free(itime);
 	} else
-		idle = g_strdup(_("Idle: <b>Active</b>"));
-
-	header = g_strdup_printf(_("Username : <b>%s</b>  %s <br>\n"
-			"Warning Level : <b>%d %%</b><br>\n"
-			"%s"
-			"%s"
-			"%s\n"
-			"<hr>\n"),
-			info->sn,
-			/* images(info->flags), */
-			"",
-			(int)((info->warnlevel/10.0) + 0.5),
-			onlinesince ? onlinesince : "",
-			membersince ? membersince : "",
-			idle ? idle : "");
-
-	g_free(onlinesince);
-	g_free(membersince);
-	g_free(idle);
-
-	while (l) {
-		char *x = l->data;
-		if (!strcmp(x, normalize(info->sn))) {
-			evilhack = TRUE;
-			g_free(x);
-			od->evilhack = g_slist_remove(od->evilhack, x);
-			break;
+		g_string_append_printf(text, _("Idle: <b>Active</b>"));
+
+	if ((userinfo->flags & AIM_FLAG_AWAY) && (userinfo->away_len > 0) && (userinfo->away != NULL) && (userinfo->away_encoding != NULL)) {
+		away_utf8 = oscar_encoding_to_utf8(userinfo->away_encoding, userinfo->away, userinfo->away_len);
+		if (away_utf8 != NULL) {
+			g_string_append_printf(text, _("<hr>%s"), away_utf8);
+			g_free(away_utf8);
 		}
-		l = l->next;
-	}
-
-	if (infotype == AIM_GETINFO_AWAYMESSAGE) {
-		if (evilhack) {
-			g_show_info_text(gc, info->sn, 2,
-					 header,
-					 (utf8 && *utf8) ? away_subs(utf8, username) :
-					 _("<i>User has no away message</i>"), NULL);
-		} else {
-			g_show_info_text(gc, info->sn, 0,
-					 header,
-					 (utf8 && *utf8) ? away_subs(utf8, username) : NULL,
-					 (utf8 && *utf8) ? "<hr>" : NULL,
-					 NULL);
+	}
+
+	if ((userinfo->info_len > 0) && (userinfo->info != NULL) && (userinfo->info_encoding != NULL)) {
+		info_utf8 = oscar_encoding_to_utf8(userinfo->info_encoding, userinfo->info, userinfo->info_len);
+		if (info_utf8 != NULL) {
+			g_string_append_printf(text, _("<hr>%s"), info_utf8);
+			g_free(info_utf8);
 		}
-	} else if (infotype == AIM_GETINFO_CAPABILITIES) {
-		g_show_info_text(gc, info->sn, 2,
-				header,
-				"<i>", _("Client Capabilities: "),
-				caps_string(info->capabilities),
-				"</i>",
-				NULL);
-	} else {
-		g_show_info_text(gc, info->sn, 1,
-				 (utf8 && *utf8) ? away_subs(utf8, username) : _("<i>No Information Provided</i>"),
-				 NULL);
-	}
-
-	g_free(header);
-	g_free(utf8);
+	}
+
+	gaim_notify_formatted(gc, NULL, _("Buddy Information"), NULL, text->str, NULL, NULL);
+	g_string_free(text, TRUE);
 
 	return 1;
 }
@@ -3551,7 +3462,7 @@
 static gboolean gaim_icon_timerfunc(gpointer data) {
 	GaimConnection *gc = data;
 	struct oscar_data *od = gc->proto_data;
-	struct buddyinfo *bi;
+	aim_userinfo_t *userinfo;
 	aim_conn_t *conn;
 
 	conn = aim_getconn_type(od->sess, AIM_CONN_TYPE_ICON);
@@ -3594,9 +3505,9 @@
 		return FALSE;
 	}
 
-	bi = g_hash_table_lookup(od->buddyinfo, (char *)od->requesticon->data);
-	if (bi && (bi->iconcsumlen > 0)) {
-		aim_bart_request(od->sess, od->requesticon->data, bi->iconcsum, bi->iconcsumlen);
+	userinfo = aim_locate_finduserinfo((char *)od->requesticon->data);
+	if ((userinfo != NULL) && (userinfo->iconcsumlen > 0)) {
+		aim_bart_request(od->sess, od->requesticon->data, userinfo->iconcsum, userinfo->iconcsumlen);
 		return FALSE;
 	} else {
 		char *sn = od->requesticon->data;
@@ -3748,7 +3659,7 @@
 	aim_ssi_reqdata(sess);
 #endif
 
-	aim_bos_reqlocaterights(sess, fr->conn);
+	aim_locate_reqrights(sess);
 	aim_bos_reqbuddyrights(sess, fr->conn);
 	aim_im_reqparams(sess);
 	aim_bos_reqrights(sess, fr->conn); /* XXX - Don't call this with ssi? */
@@ -3852,7 +3763,7 @@
 	od->rights.maxsiglen = od->rights.maxawaymsglen = (guint)maxsiglen;
 
 	if (od->icq)
-		aim_bos_setprofile(sess, fr->conn, NULL, NULL, 0, NULL, NULL, 0, caps_icq);
+		aim_locate_setprofile(sess, NULL, NULL, 0, NULL, NULL, 0, caps_icq);
 	else
 		oscar_set_info(gc, gc->account->user_info);
 
@@ -4420,21 +4331,19 @@
 	return ret;
 }
 
-static void oscar_get_info(GaimConnection *g, const char *name) {
-	struct oscar_data *od = (struct oscar_data *)g->proto_data;
+static void oscar_get_info(GaimConnection *gc, const char *name) {
+	struct oscar_data *od = (struct oscar_data *)gc->proto_data;
+
 	if (od->icq)
 		aim_icq_getallinfo(od->sess, name);
 	else
-		/* people want the away message on the top, so we get the away message
-		 * first and then get the regular info, since it's too difficult to
-		 * insert in the middle. i hate people. */
-		aim_getinfo(od->sess, od->conn, name, AIM_GETINFO_AWAYMESSAGE);
-}
-
-static void oscar_get_away(GaimConnection *g, const char *who) {
-	struct oscar_data *od = (struct oscar_data *)g->proto_data;
+		aim_locate_getinfoshort(od->sess, name, 0x00000007);
+}
+
+static void oscar_get_away(GaimConnection *gc, const char *who) {
+	struct oscar_data *od = (struct oscar_data *)gc->proto_data;
 	if (od->icq) {
-		GaimBuddy *budlight = gaim_find_buddy(g->account, who);
+		GaimBuddy *budlight = gaim_find_buddy(gc->account, who);
 		if (budlight)
 			if ((budlight->uc & 0xffff0000) >> 16)
 				aim_im_sendch2_geticqaway(od->sess, who, (budlight->uc & 0xffff0000) >> 16);
@@ -4445,14 +4354,14 @@
 			gaim_debug(GAIM_DEBUG_ERROR, "oscar",
 					   "Error: Could not find %s in local contact list, therefore unable to request status message.\n", who);
 	} else
-		aim_getinfo(od->sess, od->conn, who, AIM_GETINFO_GENERALINFO);
-}
-
-static void oscar_set_dir(GaimConnection *g, const char *first, const char *middle, const char *last,
+		aim_locate_getinfoshort(od->sess, who, 0x00000002);
+}
+
+static void oscar_set_dir(GaimConnection *gc, const char *first, const char *middle, const char *last,
 			  const char *maiden, const char *city, const char *state, const char *country, int web) {
 	/* XXX - some of these things are wrong, but i'm lazy */
-	struct oscar_data *od = (struct oscar_data *)g->proto_data;
-	aim_setdirectoryinfo(od->sess, od->conn, first, middle, last,
+	struct oscar_data *od = (struct oscar_data *)gc->proto_data;
+	aim_locate_setdirinfo(od->sess, first, middle, last,
 				maiden, NULL, NULL, city, state, NULL, 0, web);
 }
 
@@ -4476,10 +4385,10 @@
 							  "again when you are fully connected."));
 
 	if (od->icq)
-		aim_bos_setprofile(od->sess, od->conn, NULL, NULL, 0, NULL, NULL, 0, caps_icq);
+		aim_locate_setprofile(od->sess, NULL, NULL, 0, NULL, NULL, 0, caps_icq);
 	else {
 		if (!text) {
-			aim_bos_setprofile(od->sess, od->conn, NULL, NULL, 0, NULL, NULL, 0, caps_aim);
+			aim_locate_setprofile(od->sess, NULL, NULL, 0, NULL, NULL, 0, caps_aim);
 			return;
 		}
 		
@@ -4487,15 +4396,15 @@
 		flags = oscar_encoding_check(text_html);
 		if (flags & AIM_IMFLAGS_UNICODE) {
 			msg = g_convert(text_html, strlen(text_html), "UCS-2BE", "UTF-8", NULL, &msglen, NULL);
-			aim_bos_setprofile(od->sess, od->conn, "unicode-2-0", msg, (msglen > od->rights.maxsiglen ? od->rights.maxsiglen : msglen), NULL, NULL, 0, caps_aim);
+			aim_locate_setprofile(od->sess, "unicode-2-0", msg, (msglen > od->rights.maxsiglen ? od->rights.maxsiglen : msglen), NULL, NULL, 0, caps_aim);
 			g_free(msg);
 		} else if (flags & AIM_IMFLAGS_ISO_8859_1) {
 			msg = g_convert(text_html, strlen(text_html), "ISO-8859-1", "UTF-8", NULL, &msglen, NULL);
-			aim_bos_setprofile(od->sess, od->conn, "iso-8859-1", msg, (msglen > od->rights.maxsiglen ? od->rights.maxsiglen : msglen), NULL, NULL, 0, caps_aim);
+			aim_locate_setprofile(od->sess, "iso-8859-1", msg, (msglen > od->rights.maxsiglen ? od->rights.maxsiglen : msglen), NULL, NULL, 0, caps_aim);
 			g_free(msg);
 		} else {
 			msglen = strlen(text_html);
-			aim_bos_setprofile(od->sess, od->conn, "us-ascii", text_html, (msglen > od->rights.maxsiglen ? od->rights.maxsiglen : msglen), NULL, NULL, 0, caps_aim);
+			aim_locate_setprofile(od->sess, "us-ascii", text_html, (msglen > od->rights.maxsiglen ? od->rights.maxsiglen : msglen), NULL, NULL, 0, caps_aim);
 		}
 
 		if (msglen > od->rights.maxsiglen) {
@@ -4537,7 +4446,7 @@
 	}
 
 	if (!text) {
-		aim_bos_setprofile(od->sess, od->conn, NULL, NULL, 0, NULL, "", 0, caps_aim);
+		aim_locate_setprofile(od->sess, NULL, NULL, 0, NULL, "", 0, caps_aim);
 		return;
 	}
 
@@ -4545,19 +4454,19 @@
 	flags = oscar_encoding_check(text_html);
 	if (flags & AIM_IMFLAGS_UNICODE) {
 		msg = g_convert(text_html, strlen(text_html), "UCS-2BE", "UTF-8", NULL, &msglen, NULL);
-		aim_bos_setprofile(od->sess, od->conn, NULL, NULL, 0, "unicode-2-0", msg, 
+		aim_locate_setprofile(od->sess, NULL, NULL, 0, "unicode-2-0", msg, 
 			(msglen > od->rights.maxawaymsglen ? od->rights.maxawaymsglen : msglen), caps_aim);
 		g_free(msg);
 		gc->away = g_strndup(text, od->rights.maxawaymsglen/2);
 	} else if (flags & AIM_IMFLAGS_ISO_8859_1) {
 		msg = g_convert(text_html, strlen(text_html), "ISO-8859-1", "UTF-8", NULL, &msglen, NULL);
-		aim_bos_setprofile(od->sess, od->conn, NULL, NULL, 0, "iso-8859-1", msg, 
+		aim_locate_setprofile(od->sess, NULL, NULL, 0, "iso-8859-1", msg, 
 			(msglen > od->rights.maxawaymsglen ? od->rights.maxawaymsglen : msglen), caps_aim);
 		g_free(msg);
 		gc->away = g_strndup(text_html, od->rights.maxawaymsglen);
 	} else {
 		msglen = strlen(text_html);
-		aim_bos_setprofile(od->sess, od->conn, NULL, NULL, 0, "us-ascii", text_html, 
+		aim_locate_setprofile(od->sess, NULL, NULL, 0, "us-ascii", text_html, 
 			(msglen > od->rights.maxawaymsglen ? od->rights.maxawaymsglen : msglen), caps_aim);
 		gc->away = g_strndup(text_html, od->rights.maxawaymsglen);
 	}
@@ -5441,70 +5350,86 @@
 	GaimConnection *gc = b->account->gc;
 	struct oscar_data *od = gc->proto_data;
 	struct buddyinfo *bi = g_hash_table_lookup(od->buddyinfo, normalize(b->name));
-	gchar *tmp, *yay = g_strdup("");
+	aim_userinfo_t *userinfo = aim_locate_finduserinfo(b->name);
+	gchar *tmp = NULL, *ret = g_strdup("");
 
 	if (GAIM_BUDDY_IS_ONLINE(b)) {
 		if (isdigit(b->name[0])) {
-			char *tmp, *status;
+			char *status;
 			status = gaim_icq_status((b->uc & 0xffff0000) >> 16);
-			tmp = yay;
-			yay = g_strconcat(tmp, _("<b>Status:</b> "), status, "\n", NULL);
+			tmp = ret;
+			ret = g_strconcat(tmp, _("<b>Status:</b> "), status, "\n", NULL);
 			g_free(tmp);
 			g_free(status);
 		}
 
-		if (bi) {
-			char *tstr = sec_to_text(time(NULL) - bi->signon + 
+		if (userinfo != NULL) {
+			char *tstr = sec_to_text(time(NULL) - userinfo->onlinesince + 
 				(gc->login_time_official ? gc->login_time_official - gc->login_time : 0));
-			tmp = yay;
-			yay = g_strconcat(tmp, _("<b>Logged In:</b> "), tstr, "\n", NULL);
+			tmp = ret;
+			ret = g_strconcat(tmp, _("<b>Logged In:</b> "), tstr, "\n", NULL);
+			free(tmp);
+			free(tstr);
+		}
+
+		if ((bi != NULL) && (bi->ipaddr)) {
+			char *tstr =  g_strdup_printf("%hhd.%hhd.%hhd.%hhd",
+							(bi->ipaddr & 0xff000000) >> 24,
+							(bi->ipaddr & 0x00ff0000) >> 16,
+							(bi->ipaddr & 0x0000ff00) >> 8,
+							(bi->ipaddr & 0x000000ff));
+			tmp = ret;
+			ret = g_strconcat(tmp, _("<b>IP Address:</b> "), tstr, "\n", NULL);
 			free(tmp);
 			free(tstr);
-
-			if (bi->ipaddr) {
-				char *tstr =  g_strdup_printf("%hhd.%hhd.%hhd.%hhd",
-								(bi->ipaddr & 0xff000000) >> 24,
-								(bi->ipaddr & 0x00ff0000) >> 16,
-								(bi->ipaddr & 0x0000ff00) >> 8,
-								(bi->ipaddr & 0x000000ff));
-				tmp = yay;
-				yay = g_strconcat(tmp, _("<b>IP Address:</b> "), tstr, "\n", NULL);
+		}
+
+		if ((userinfo != NULL) && (userinfo->capabilities)) {
+			char *caps = caps_string(userinfo->capabilities);
+			tmp = ret;
+			ret = g_strconcat(tmp, _("<b>Capabilities:</b> "), caps, "\n", NULL);
+			free(tmp);
+		}
+
+		if ((bi != NULL) && (bi->availmsg != NULL) && !(b->uc & UC_UNAVAILABLE)) {
+			gchar *escaped = g_markup_escape_text(bi->availmsg, strlen(bi->availmsg));
+			tmp = ret;
+			ret = g_strconcat(tmp, _("<b>Available:</b> "), escaped, "\n", NULL);
+			free(tmp);
+			g_free(escaped);
+		}
+
+		if ((userinfo != NULL) && (userinfo->flags & AIM_FLAG_AWAY) && (userinfo->away_len > 0) && (userinfo->away != NULL) && (userinfo->away_encoding != NULL)) {
+			gchar *away_utf8 = oscar_encoding_to_utf8(userinfo->away_encoding, userinfo->away, userinfo->away_len);
+			if (away_utf8 != NULL) {
+				gchar *withcr, *nohtml;
+				withcr = gaim_strreplace(away_utf8, "<BR>", "\n");
+				nohtml = strip_html(withcr);
+				g_free(withcr);
+				tmp = ret;
+				ret = g_strconcat(tmp, _("<b>Away Message:</b> "), nohtml, "\n", NULL);
 				free(tmp);
-				free(tstr);
-			}
-
-			if (bi->caps) {
-				char *caps = caps_string(bi->caps);
-				tmp = yay;
-				yay = g_strconcat(tmp, _("<b>Capabilities:</b> "), caps, "\n", NULL);
-				free(tmp);
-			}
-
-			if (bi->availmsg && !(b->uc & UC_UNAVAILABLE)) {
-				gchar *escaped = g_markup_escape_text(bi->availmsg, strlen(bi->availmsg));
-				tmp = yay;
-				yay = g_strconcat(tmp, _("<b>Available:</b> "), escaped, "\n", NULL);
-				free(tmp);
-				g_free(escaped);
+				g_free(nohtml);
+				g_free(away_utf8);
 			}
 		}
 	} else {
 		char *gname = aim_ssi_itemlist_findparentname(od->sess->ssi.local, b->name);
 		if (aim_ssi_waitingforauth(od->sess->ssi.local, gname, b->name)) {
-			tmp = yay;
-			yay = g_strconcat(tmp, _("<b>Status:</b> Not Authorized"), "\n", NULL);
+			tmp = ret;
+			ret = g_strconcat(tmp, _("<b>Status:</b> Not Authorized"), "\n", NULL);
 			g_free(tmp);
 		} else {
-			tmp = yay;
-			yay = g_strconcat(tmp, _("<b>Status:</b> Offline"), "\n", NULL);
+			tmp = ret;
+			ret = g_strconcat(tmp, _("<b>Status:</b> Offline"), "\n", NULL);
 			g_free(tmp);
 		}
 	}
 
 	/* remove the trailing newline character */
-	if (yay)
-		yay[strlen(yay)-1] = '\0';
-	return yay;
+	if (ret)
+		ret[strlen(ret)-1] = '\0';
+	return ret;
 }
 
 static char *oscar_status_text(GaimBuddy *b) {
@@ -6142,13 +6067,13 @@
 #endif
 	} else {
 		GaimBuddy *b = gaim_find_buddy(gc->account, who);
-		struct buddyinfo *bi;
+		aim_userinfo_t *userinfo;
 
 		if (b)
-			bi = g_hash_table_lookup(od->buddyinfo, normalize(b->name));
-
-		if (b && bi && aim_sncmp(gaim_account_get_username(gaim_connection_get_account(gc)), who) && GAIM_BUDDY_IS_ONLINE(b)) {
-			if (bi->caps & AIM_CAPS_DIRECTIM) {
+			userinfo = aim_locate_finduserinfo(b->name);
+
+		if (b && userinfo && aim_sncmp(gaim_account_get_username(gaim_connection_get_account(gc)), who) && GAIM_BUDDY_IS_ONLINE(b)) {
+			if (userinfo->capabilities & AIM_CAPS_DIRECTIM) {
 				pbm = g_new0(struct proto_buddy_menu, 1);
 				pbm->label = _("Direct IM");
 				pbm->callback = oscar_ask_direct_im;
@@ -6156,7 +6081,7 @@
 				m = g_list_append(m, pbm);
 			}
 
-			if (bi->caps & AIM_CAPS_SENDFILE) {
+			if (userinfo->capabilities & AIM_CAPS_SENDFILE) {
 				pbm = g_new0(struct proto_buddy_menu, 1);
 				pbm->label = _("Send File");
 				pbm->callback = oscar_ask_sendfile;
@@ -6164,7 +6089,7 @@
 				m = g_list_append(m, pbm);
 			}
 #if 0
-			if (bi->caps & AIM_CAPS_GETFILE) {
+			if (userinfo->capabilities & AIM_CAPS_GETFILE) {
 				pbm = g_new0(struct proto_buddy_menu, 1);
 				pbm->label = _("Get File");
 				pbm->callback = oscar_ask_getfile;
--- a/src/protocols/oscar/ssi.c	Mon Sep 29 07:39:10 2003 +0000
+++ b/src/protocols/oscar/ssi.c	Mon Sep 29 12:30:03 2003 +0000
@@ -1908,8 +1908,6 @@
 static void ssi_shutdown(aim_session_t *sess, aim_module_t *mod)
 {
 	aim_ssi_freelist(sess);
-
-	return;
 }
 
 faim_internal int ssi_modfirst(aim_session_t *sess, aim_module_t *mod)
--- a/src/protocols/oscar/tlv.c	Mon Sep 29 07:39:10 2003 +0000
+++ b/src/protocols/oscar/tlv.c	Mon Sep 29 12:30:03 2003 +0000
@@ -649,7 +649,7 @@
 
 	newstr = (char *) malloc(tlv->length + 1);
 	memcpy(newstr, tlv->value, tlv->length);
-	*(newstr + tlv->length) = '\0';
+	newstr[tlv->length] = '\0';
 
 	return newstr;
 }