Index: libfaim/Makefile
===================================================================
--- libfaim/Makefile	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/Makefile	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,22 @@
+# Generated automatically from Makefile.in by configure.
+CC=gcc
+LIBS=
+LDFLAGS=-L/var/users/kretch/source/ncurses/ncurses-5.2/lib/
+RANLIB=ranlib
+OBJS=admin.o adverts.o auth.o bos.o buddylist.o \
+     chat.o chatnav.o conn.o ft.o icq.o im.o \
+     info.o invite.o md5.o meta.o misc.o msgcookie.o \
+     popups.o rxhandlers.o rxqueue.o search.o service.o \
+     snac.o ssi.o stats.o tlv.o translate.o txqueue.o \
+     util.o
+CFLAGS=-I/var/users/kretch/source/ncurses/ncurses-5.2/include/ -Wall -I. -DAIM_BUILDDATE=\"x\" -DAIM_BUILDTIME=\"x\"
+
+libfaim: $(OBJS)
+	$(AR) -r libfaim.a $(OBJS)
+	$(RANLIB) libfaim.a
+
+clean:
+	$(RM) *.o libfaim.a
+
+distclean: clean
+	$(RM) config.cache config.log config.status Makefile config.h TAGS *~ core
Index: libfaim/Makefile.in
===================================================================
--- libfaim/Makefile.in	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/Makefile.in	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,21 @@
+CC=@CC@
+LIBS=@LIBS@
+LDFLAGS=@LDFLAGS@
+RANLIB=@RANLIB@
+OBJS=admin.o adverts.o auth.o bos.o buddylist.o \
+     chat.o chatnav.o conn.o ft.o icq.o im.o \
+     info.o invite.o md5.o meta.o misc.o msgcookie.o \
+     popups.o rxhandlers.o rxqueue.o search.o service.o \
+     snac.o ssi.o stats.o tlv.o translate.o txqueue.o \
+     util.o
+CFLAGS=@CFLAGS@ -I. -DAIM_BUILDDATE=\"x\" -DAIM_BUILDTIME=\"x\"
+
+libfaim: $(OBJS)
+	$(AR) -r libfaim.a $(OBJS)
+	$(RANLIB) libfaim.a
+
+clean:
+	$(RM) *.o libfaim.a
+
+distclean: clean
+	$(RM) config.cache config.log config.status Makefile config.h TAGS *~ core
Index: libfaim/admin.c
===================================================================
--- libfaim/admin.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/admin.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,197 @@
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+/* called for both reply and change-reply */
+static int infochange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+	/*
+	 * struct {
+	 *    unsigned short perms;
+	 *    unsigned short tlvcount;
+	 *    aim_tlv_t tlvs[tlvcount];
+	 *  } admin_info[n];
+	 */
+	while (aim_bstream_empty(bs)) {
+		fu16_t perms, tlvcount;
+
+		perms = aimbs_get16(bs);
+		tlvcount = aimbs_get16(bs);
+
+		while (tlvcount && aim_bstream_empty(bs)) {
+			aim_rxcallback_t userfunc;
+			fu16_t type, len;
+			fu8_t *val;
+			int str = 0;
+
+			type = aimbs_get16(bs);
+			len = aimbs_get16(bs);
+
+			if ((type == 0x0011) || (type == 0x0004))
+				str = 1;
+
+			if (str)
+				val = aimbs_getstr(bs, len);
+			else
+				val = aimbs_getraw(bs, len);
+
+			/* XXX fix so its only called once for the entire packet */
+			if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+				userfunc(sess, rx, (snac->subtype == 0x0005) ? 1 : 0, perms, type, len, val, str);
+
+			free(val);
+
+			tlvcount--;
+		}
+	}
+
+	return 1;
+}
+
+static int accountconfirm(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_rxcallback_t userfunc;
+	fu16_t status;
+
+	status = aimbs_get16(bs);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		return userfunc(sess, rx, status);
+
+	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) || (snac->subtype == 0x0005))
+		return infochange(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x0007)
+		return accountconfirm(sess, mod, rx, snac, bs);
+
+	return 0;
+}
+
+faim_internal int admin_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0x0007;
+	mod->version = 0x0001;
+	mod->toolid = AIM_TOOL_NEWWIN;
+	mod->toolversion = 0x0361; /* XXX this and above aren't right */
+	mod->flags = 0;
+	strncpy(mod->name, "admin", sizeof(mod->name));
+	mod->snachandler = snachandler;
+
+	return 0;
+}
+
+faim_export int aim_admin_changepasswd(aim_session_t *sess, aim_conn_t *conn, const char *newpw, const char *curpw)
+{
+	aim_frame_t *tx;
+	aim_tlvlist_t *tl = NULL;
+	aim_snacid_t snacid;
+
+	if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+4+strlen(curpw)+4+strlen(newpw))))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0007, 0x0004, 0x0000, NULL, 0);
+	aim_putsnac(&tx->data, 0x0007, 0x0004, 0x0000, snacid);
+
+	/* new password TLV t(0002) */
+	aim_addtlvtochain_raw(&tl, 0x0002, strlen(newpw), newpw);
+
+	/* current password TLV t(0012) */
+	aim_addtlvtochain_raw(&tl, 0x0012, strlen(curpw), curpw);
+
+	aim_writetlvchain(&tx->data, &tl);
+	aim_freetlvchain(&tl);
+
+	aim_tx_enqueue(sess, tx);
+
+	return 0;
+}
+
+/*
+ * Request account confirmation. 
+ *
+ * This will cause an email to be sent to the address associated with
+ * the account.  By following the instructions in the mail, you can
+ * get the TRIAL flag removed from your account.
+ *
+ */
+faim_export int aim_admin_reqconfirm(aim_session_t *sess, aim_conn_t *conn)
+{
+	return aim_genericreq_n(sess, conn, 0x0007, 0x0006);
+}
+
+/*
+ * Request a bit of account info.
+ *
+ * The only known valid tag is 0x0011 (email address).
+ *
+ */ 
+faim_export int aim_admin_getinfo(aim_session_t *sess, aim_conn_t *conn, fu16_t info)
+{
+	aim_frame_t *tx;
+	aim_snacid_t snacid;
+
+	if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 14)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0002, 0x0002, 0x0000, NULL, 0);
+	aim_putsnac(&tx->data, 0x0007, 0x0002, 0x0000, snacid);
+
+	aimbs_put16(&tx->data, info);
+	aimbs_put16(&tx->data, 0x0000);
+
+	aim_tx_enqueue(sess, tx);
+
+	return 0;
+}
+
+faim_export int aim_admin_setemail(aim_session_t *sess, aim_conn_t *conn, const char *newemail)
+{
+	aim_frame_t *tx;
+	aim_snacid_t snacid;
+	aim_tlvlist_t *tl = NULL;
+
+	if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+2+strlen(newemail))))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0007, 0x0004, 0x0000, NULL, 0);
+	aim_putsnac(&tx->data, 0x0007, 0x0004, 0x0000, snacid);
+
+	aim_addtlvtochain_raw(&tl, 0x0011, strlen(newemail), newemail);
+	
+	aim_writetlvchain(&tx->data, &tl);
+	aim_freetlvchain(&tl);
+	
+	aim_tx_enqueue(sess, tx);
+
+	return 0;
+}
+
+faim_export int aim_admin_setnick(aim_session_t *sess, aim_conn_t *conn, const char *newnick)
+{
+	aim_frame_t *tx;
+	aim_snacid_t snacid;
+	aim_tlvlist_t *tl = NULL;
+
+	if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+2+strlen(newnick))))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0007, 0x0004, 0x0000, NULL, 0);
+	aim_putsnac(&tx->data, 0x0007, 0x0004, 0x0000, snacid);
+
+	aim_addtlvtochain_raw(&tl, 0x0001, strlen(newnick), newnick);
+	
+	aim_writetlvchain(&tx->data, &tl);
+	aim_freetlvchain(&tl);
+	
+	aim_tx_enqueue(sess, tx);
+
+	return 0;
+}
+
Index: libfaim/adverts.c
===================================================================
--- libfaim/adverts.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/adverts.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,33 @@
+/*
+ *
+ *
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+faim_export int aim_ads_requestads(aim_session_t *sess, aim_conn_t *conn)
+{
+	return aim_genericreq_n(sess, conn, 0x0005, 0x0002);
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	return 0;
+}
+
+faim_internal int adverts_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0x0005;
+	mod->version = 0x0001;
+	mod->toolid = 0x0001;
+	mod->toolversion = 0x0001;
+	mod->flags = 0;
+	strncpy(mod->name, "adverts", sizeof(mod->name));
+	mod->snachandler = snachandler;
+
+	return 0;
+}
+
+
Index: libfaim/aim.h
===================================================================
--- libfaim/aim.h	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/aim.h	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,1110 @@
+/* 
+ * Main libfaim header.  Must be included in client for prototypes/macros.
+ *
+ * "come on, i turned a chick lesbian; i think this is the hackish equivalent"
+ *                                                -- Josh Meyer
+ *
+ */
+
+#ifndef __AIM_H__
+#define __AIM_H__
+
+#define FAIM_VERSION_MAJOR 0
+#define FAIM_VERSION_MINOR 99
+#define FAIM_VERSION_MINORMINOR 4
+
+#include <faimconfig.h>
+#include <aim_cbtypes.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <time.h>
+
+#ifdef _WIN32
+#include <windows.h>
+#include <io.h>
+#else
+#include <sys/time.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#endif
+
+/* XXX adjust these based on autoconf-detected platform */
+typedef unsigned char fu8_t;
+typedef unsigned short fu16_t;
+typedef unsigned long fu32_t;
+typedef fu32_t aim_snacid_t;
+typedef fu16_t flap_seqnum_t;
+
+/* Portability stuff (DMP) */
+
+#ifdef _WIN32
+#define sleep(x) Sleep((x)*1000)
+#define snprintf _snprintf /* I'm not sure whats wrong with Microsoft here */
+#define close(x) closesocket(x) /* no comment */
+#endif
+
+#if defined(mach) && defined(__APPLE__)
+#define gethostbyname(x) gethostbyname2(x, AF_INET) 
+#endif
+
+#if defined(_WIN32) || defined(STRICT_ANSI)
+#define faim_shortfunc
+#else
+#define faim_shortfunc inline
+#endif
+
+#if defined(_WIN32) && !defined(WIN32_STATIC)
+/*
+ * For a win32 DLL, we define WIN32_INDLL if this file
+ * is included while compiling the DLL. If its not 
+ * defined (its included in a client app), the symbols
+ * will be imported instead of exported.
+ */
+#ifdef WIN32_INDLL
+#define faim_export __declspec(dllexport)
+#else 
+#define faim_export __declspec(dllimport)
+#endif /* WIN32_INDLL */
+#define faim_internal
+#else
+/*
+ * Nothing normally needed for unix...
+ */
+#define faim_export
+#define faim_internal
+#endif
+
+/* 
+ * Current Maximum Length for Screen Names (not including NULL) 
+ *
+ * Currently only names up to 16 characters can be registered
+ * however it is aparently legal for them to be larger.
+ */
+#define MAXSNLEN 32
+
+/*
+ * Current Maximum Length for Instant Messages
+ *
+ * This was found basically by experiment, but not wholly
+ * accurate experiment.  It should not be regarded
+ * as completely correct.  But its a decent approximation.
+ *
+ * Note that although we can send this much, its impossible
+ * for WinAIM clients (up through the latest (4.0.1957)) to
+ * send any more than 1kb.  Amaze all your windows friends
+ * with utterly oversized instant messages!
+ *
+ * XXX: the real limit is the total SNAC size at 8192. Fix this.
+ * 
+ */
+#define MAXMSGLEN 7987
+
+/*
+ * Maximum size of a Buddy Icon.
+ */
+#define MAXICONLEN 7168
+#define AIM_ICONIDENT "AVT1picture.id"
+
+/*
+ * Current Maximum Length for Chat Room Messages
+ *
+ * This is actually defined by the protocol to be
+ * dynamic, but I have yet to see due cause to 
+ * define it dynamically here.  Maybe later.
+ *
+ */
+#define MAXCHATMSGLEN 512
+
+/*
+ * Standard size of an AIM authorization cookie
+ */
+#define AIM_COOKIELEN            0x100
+
+#define AIM_MD5_STRING "AOL Instant Messenger (SM)"
+
+/*
+ * Client info.  Filled in by the client and passed in to 
+ * aim_send_login().  The information ends up getting passed to OSCAR
+ * through the initial login command.
+ *
+ */
+struct client_info_s {
+	const char *clientstring;
+	fu16_t clientid;
+	int major;
+	int minor;
+	int point;
+	int build;
+	const char *country; /* two-letter abbrev */
+	const char *lang; /* two-letter abbrev */
+};
+
+#define AIM_CLIENTINFO_KNOWNGOOD_3_5_1670 { \
+	"AOL Instant Messenger (SM), version 3.5.1670/WIN32", \
+	0x0004, \
+	0x0003, \
+	0x0005, \
+	0x0000, \
+	0x0686, \
+	"us", \
+	"en", \
+}
+
+#define AIM_CLIENTINFO_KNOWNGOOD_4_1_2010 { \
+	  "AOL Instant Messenger (SM), version 4.1.2010/WIN32", \
+	  0x0004, \
+	  0x0004, \
+	  0x0001, \
+	  0x0000, \
+	  0x07da, \
+	  "us", \
+	  "en", \
+}
+
+/*
+ * I would make 4.1.2010 the default, but they seem to have found
+ * an alternate way of breaking that one. 
+ *
+ * 3.5.1670 should work fine, however, you will be subjected to the
+ * memory test, which may require you to have a WinAIM binary laying 
+ * around. (see login.c::memrequest())
+ */
+#define AIM_CLIENTINFO_KNOWNGOOD AIM_CLIENTINFO_KNOWNGOOD_3_5_1670
+
+#ifndef TRUE
+#define TRUE 1
+#define FALSE 0
+#endif
+
+/* 
+ * These could be arbitrary, but its easier to use the actual AIM values 
+ */
+#define AIM_CONN_TYPE_AUTH          0x0007
+#define AIM_CONN_TYPE_ADS           0x0005
+#define AIM_CONN_TYPE_BOS           0x0002
+#define AIM_CONN_TYPE_CHAT          0x000e
+#define AIM_CONN_TYPE_CHATNAV       0x000d
+
+/* they start getting arbitrary in rendezvous stuff =) */
+#define AIM_CONN_TYPE_RENDEZVOUS    0x0101 /* these do not speak FLAP! */
+#define AIM_CONN_TYPE_RENDEZVOUS_OUT 0x0102 /* socket waiting for accept() */
+
+/*
+ * Subtypes, we need these for OFT stuff.
+ */
+#define AIM_CONN_SUBTYPE_OFT_DIRECTIM  0x0001
+#define AIM_CONN_SUBTYPE_OFT_GETFILE   0x0002
+#define AIM_CONN_SUBTYPE_OFT_SENDFILE  0x0003
+#define AIM_CONN_SUBTYPE_OFT_BUDDYICON 0x0004
+#define AIM_CONN_SUBTYPE_OFT_VOICE     0x0005
+
+/*
+ * Status values returned from aim_conn_new().  ORed together.
+ */
+#define AIM_CONN_STATUS_READY       0x0001
+#define AIM_CONN_STATUS_INTERNALERR 0x0002
+#define AIM_CONN_STATUS_RESOLVERR   0x0040
+#define AIM_CONN_STATUS_CONNERR     0x0080
+#define AIM_CONN_STATUS_INPROGRESS  0x0100
+
+#define AIM_FRAMETYPE_FLAP 0x0000
+#define AIM_FRAMETYPE_OFT  0x0001
+
+typedef struct aim_conn_s {
+	int fd;
+	fu16_t type;
+	fu16_t subtype;
+	flap_seqnum_t seqnum;
+	fu32_t status;
+	void *priv; /* misc data the client may want to store */
+	void *internal; /* internal conn-specific libfaim data */
+	time_t lastactivity; /* time of last transmit */
+	int forcedlatency; 
+	void *handlerlist;
+	void *sessv; /* pointer to parent session */
+	void *inside; /* only accessible from inside libfaim */
+	struct aim_conn_s *next;
+} aim_conn_t;
+
+/*
+ * Byte Stream type. Sort of.
+ *
+ * Use of this type serves a couple purposes:
+ *   - Buffer/buflen pairs are passed all around everywhere. This turns
+ *     that into one value, as well as abstracting it slightly.
+ *   - Through the abstraction, it is possible to enable bounds checking
+ *     for robustness at the cost of performance.  But a clean failure on
+ *     weird packets is much better than a segfault.
+ *   - I like having variables named "bs".
+ *
+ * Don't touch the insides of this struct.  Or I'll have to kill you.
+ *
+ */
+typedef struct aim_bstream_s {
+	fu8_t *data;
+	fu16_t len;
+	fu16_t offset;
+} aim_bstream_t;
+
+typedef struct aim_frame_s {
+	fu8_t hdrtype; /* defines which piece of the union to use */
+	union {
+		struct { 
+			fu8_t type;
+			flap_seqnum_t seqnum;     
+		} flap;
+		struct {
+			fu16_t type;
+			fu8_t magic[4]; /* ODC2 OFT2 */
+			fu16_t hdr2len;
+			fu8_t *hdr2; /* rest of bloated header */
+		} oft;
+	} hdr;
+	aim_bstream_t data;	/* payload stream */
+	fu8_t handled;		/* 0 = new, !0 = been handled */
+	fu8_t nofree;		/* 0 = free data on purge, 1 = only unlink */
+	aim_conn_t *conn;  /* the connection it came in on... */
+	struct aim_frame_s *next;
+} aim_frame_t;
+
+typedef struct aim_msgcookie_s {
+	unsigned char cookie[8];
+	int type;
+	void *data;
+	time_t addtime;
+	struct aim_msgcookie_s *next;
+} aim_msgcookie_t;
+
+/*
+ * AIM Session: The main client-data interface.  
+ *
+ */
+typedef struct aim_session_s {
+
+	/* ---- Client Accessible ------------------------ */
+
+	/* Our screen name. */
+	char sn[MAXSNLEN+1];
+
+	/*
+	 * Pointer to anything the client wants to 
+	 * explicitly associate with this session.
+	 *
+	 * This is for use in the callbacks mainly. In any
+	 * callback, you can access this with sess->aux_data.
+	 *
+	 */
+	void *aux_data;
+
+	/* ---- Internal Use Only ------------------------ */
+
+	/* Connection information */
+	aim_conn_t *connlist;
+
+	/*
+	 * Transmit/receive queues.
+	 *
+	 * These are only used when you don't use your own lowlevel
+	 * I/O.  I don't suggest that you use libfaim's internal I/O.
+	 * Its really bad and the API/event model is quirky at best.
+	 *  
+	 */
+	aim_frame_t *queue_outgoing;   
+	aim_frame_t *queue_incoming; 
+
+	/*
+	 * Tx Enqueuing function.
+	 *
+	 * This is how you override the transmit direction of libfaim's
+	 * internal I/O.  This function will be called whenever it needs
+	 * to send something.
+	 *
+	 */
+	int (*tx_enqueue)(struct aim_session_s *, aim_frame_t *);
+
+	/*
+	 * Outstanding snac handling 
+	 *
+	 * XXX: Should these be per-connection? -mid
+	 */
+	void *snac_hash[FAIM_SNAC_HASH_SIZE];
+	aim_snacid_t snacid_next;
+
+	struct {
+		char server[128];
+		char username[128];
+		char password[128];
+	} socksproxy;
+
+	fu32_t flags; /* AIM_SESS_FLAGS_ */
+
+	int debug;
+	void (*debugcb)(struct aim_session_s *sess, int level, const char *format, va_list va); /* same as faim_debugging_callback_t */
+
+	aim_msgcookie_t *msgcookies;
+
+	void *modlistv;
+} aim_session_t;
+
+/* Values for sess->flags */
+#define AIM_SESS_FLAGS_SNACLOGIN         0x00000001
+#define AIM_SESS_FLAGS_XORLOGIN          0x00000002
+#define AIM_SESS_FLAGS_NONBLOCKCONNECT   0x00000004
+#define AIM_SESS_FLAGS_DONTTIMEOUTONICBM 0x00000008
+
+/* Valid for calling aim_icq_setstatus() and for aim_userinfo_t->icqinfo.status */
+#define AIM_ICQ_STATE_NORMAL    0x00000000
+#define AIM_ICQ_STATE_AWAY      0x00000001
+#define AIM_ICQ_STATE_DND       0x00000002
+#define AIM_ICQ_STATE_OUT       0x00000004
+#define AIM_ICQ_STATE_BUSY      0x00000010
+#define AIM_ICQ_STATE_CHAT      0x00000020
+#define AIM_ICQ_STATE_INVISIBLE 0x00000100
+#define AIM_ICQ_STATE_WEBAWARE  0x00010000
+
+/*
+ * AIM User Info, Standard Form.
+ */
+typedef struct {
+	char sn[MAXSNLEN+1];
+	fu16_t warnlevel; /* evil percent * 10 (999 = 99.9%) */
+	fu16_t idletime; /* in seconds */
+	fu16_t flags;
+	fu32_t createtime; /* time_t */
+	fu32_t membersince; /* time_t */
+	fu32_t onlinesince; /* time_t */
+	fu32_t sessionlen; /* in seconds */
+	fu32_t capabilities;
+	struct {
+		fu32_t status;
+		fu32_t ipaddr;
+		fu8_t crap[0x25]; /* until we figure it out... */
+	} icqinfo;
+	fu32_t present;
+} aim_userinfo_t;
+
+#define AIM_USERINFO_PRESENT_FLAGS        0x00000001
+#define AIM_USERINFO_PRESENT_MEMBERSINCE  0x00000002
+#define AIM_USERINFO_PRESENT_ONLINESINCE  0x00000004
+#define AIM_USERINFO_PRESENT_IDLE         0x00000008
+#define AIM_USERINFO_PRESENT_ICQEXTSTATUS 0x00000010
+#define AIM_USERINFO_PRESENT_ICQIPADDR    0x00000020
+#define AIM_USERINFO_PRESENT_ICQDATA      0x00000040
+#define AIM_USERINFO_PRESENT_CAPABILITIES 0x00000080
+#define AIM_USERINFO_PRESENT_SESSIONLEN   0x00000100
+#define AIM_USERINFO_PRESENT_CREATETIME   0x00000200
+
+faim_export const char *aim_userinfo_sn(aim_userinfo_t *ui);
+faim_export fu16_t aim_userinfo_flags(aim_userinfo_t *ui);
+faim_export fu16_t aim_userinfo_idle(aim_userinfo_t *ui);
+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);
+
+#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
+
+
+#if defined(FAIM_INTERNAL) || defined(FAIM_NEED_TLV)
+/*
+ * TLV handling
+ */
+
+/* Generic TLV structure. */
+typedef struct aim_tlv_s {
+	fu16_t type;
+	fu16_t length;
+	fu8_t *value;
+} aim_tlv_t;
+
+/* List of above. */
+typedef struct aim_tlvlist_s {
+	aim_tlv_t *tlv;
+	struct aim_tlvlist_s *next;
+} aim_tlvlist_t;
+
+/* TLV-handling functions */
+
+#if 0
+/* Very, very raw TLV handling. */
+faim_internal int aim_puttlv_8(fu8_t *buf, const fu16_t t, const fu8_t v);
+faim_internal int aim_puttlv_16(fu8_t *buf, const fu16_t t, const fu16_t v);
+faim_internal int aim_puttlv_32(fu8_t *buf, const fu16_t t, const fu32_t v);
+faim_internal int aim_puttlv_raw(fu8_t *buf, const fu16_t t, const fu16_t l, const fu8_t *v);
+#endif
+
+/* TLV list handling. */
+faim_internal aim_tlvlist_t *aim_readtlvchain(aim_bstream_t *bs);
+faim_internal void aim_freetlvchain(aim_tlvlist_t **list);
+faim_internal aim_tlv_t *aim_gettlv(aim_tlvlist_t *, fu16_t t, const int n);
+faim_internal char *aim_gettlv_str(aim_tlvlist_t *, const fu16_t t, const int n);
+faim_internal fu8_t aim_gettlv8(aim_tlvlist_t *list, const fu16_t type, const int num);
+faim_internal fu16_t aim_gettlv16(aim_tlvlist_t *list, const fu16_t t, const int n);
+faim_internal fu32_t aim_gettlv32(aim_tlvlist_t *list, const fu16_t t, const int n);
+faim_internal int aim_writetlvchain(aim_bstream_t *bs, aim_tlvlist_t **list);
+faim_internal int aim_addtlvtochain8(aim_tlvlist_t **list, const fu16_t t, const fu8_t v);
+faim_internal int aim_addtlvtochain16(aim_tlvlist_t **list, const fu16_t t, const fu16_t v);
+faim_internal int aim_addtlvtochain32(aim_tlvlist_t **list, const fu16_t type, const fu32_t v);
+faim_internal int aim_addtlvtochain_raw(aim_tlvlist_t **list, const fu16_t t, const fu16_t l, const fu8_t *v);
+faim_internal int aim_addtlvtochain_caps(aim_tlvlist_t **list, const fu16_t t, const fu32_t caps);
+faim_internal int aim_addtlvtochain_noval(aim_tlvlist_t **list, const fu16_t type);
+faim_internal int aim_addtlvtochain_userinfo(aim_tlvlist_t **list, fu16_t type, aim_userinfo_t *ui);
+faim_internal int aim_addtlvtochain_frozentlvlist(aim_tlvlist_t **list, fu16_t type, aim_tlvlist_t **tl);
+faim_internal int aim_counttlvchain(aim_tlvlist_t **list);
+faim_internal int aim_sizetlvchain(aim_tlvlist_t **list);
+#endif /* FAIM_INTERNAL */
+
+/*
+ * Get command from connections
+ *
+ * aim_get_commmand() is the libfaim lowlevel I/O in the receive direction.
+ * XXX Make this easily overridable.
+ *
+ */
+faim_export int aim_get_command(aim_session_t *, aim_conn_t *);
+
+/*
+ * Dispatch commands that are in the rx queue.
+ */
+faim_export void aim_rxdispatch(aim_session_t *);
+
+faim_export int aim_debugconn_sendconnect(aim_session_t *sess, aim_conn_t *conn);
+
+faim_export int aim_logoff(aim_session_t *);
+
+#if !defined(FAIM_INTERNAL) || defined(FAIM_INTERNAL_INSANE)
+/* the library should never call aim_conn_kill */
+faim_export void aim_conn_kill(aim_session_t *sess, aim_conn_t **deadconn);
+#endif
+
+typedef int (*aim_rxcallback_t)(aim_session_t *, aim_frame_t *, ...);
+
+struct aim_clientrelease {
+	char *name;
+	fu32_t build;
+	char *url;
+	char *info;
+};
+
+struct aim_authresp_info {
+	char *sn;
+	fu16_t errorcode;
+	char *errorurl;
+	fu16_t regstatus;
+	char *email;
+	char *bosip;
+	fu8_t *cookie;
+	struct aim_clientrelease latestrelease;
+	struct aim_clientrelease latestbeta;
+};
+
+/* Callback data for redirect. */
+struct aim_redirect_data {
+	fu16_t group;
+	const char *ip;
+	const fu8_t *cookie;
+	struct { /* group == AIM_CONN_TYPE_CHAT */
+		fu16_t exchange;
+		const char *room;
+		fu16_t instance;
+	} chat;
+};
+
+faim_export int aim_clientready(aim_session_t *sess, aim_conn_t *conn);
+faim_export int aim_sendflapver(aim_session_t *sess, aim_conn_t *conn);
+faim_export int aim_request_login(aim_session_t *sess, aim_conn_t *conn, const char *sn);
+faim_export int aim_send_login(aim_session_t *, aim_conn_t *, const char *, const char *, struct client_info_s *, const char *key);
+faim_export int aim_encode_password_md5(const char *password, const char *key, unsigned char *digest);
+faim_export void aim_purge_rxqueue(aim_session_t *);
+
+#define AIM_TX_QUEUED    0 /* default */
+#define AIM_TX_IMMEDIATE 1
+#define AIM_TX_USER      2
+faim_export int aim_tx_setenqueue(aim_session_t *sess, int what, int (*func)(aim_session_t *, aim_frame_t *));
+
+faim_export int aim_tx_flushqueue(aim_session_t *);
+faim_export void aim_tx_purgequeue(aim_session_t *);
+
+faim_export int aim_conn_setlatency(aim_conn_t *conn, int newval);
+
+faim_export int aim_conn_addhandler(aim_session_t *, aim_conn_t *conn, u_short family, u_short type, aim_rxcallback_t newhandler, u_short flags);
+faim_export int aim_clearhandlers(aim_conn_t *conn);
+
+faim_export aim_conn_t *aim_conn_findbygroup(aim_session_t *sess, fu16_t group);
+faim_export aim_session_t *aim_conn_getsess(aim_conn_t *conn);
+faim_export void aim_conn_close(aim_conn_t *deadconn);
+faim_export aim_conn_t *aim_newconn(aim_session_t *, int type, const char *dest);
+faim_export int aim_conngetmaxfd(aim_session_t *);
+faim_export aim_conn_t *aim_select(aim_session_t *, struct timeval *, int *);
+faim_export int aim_conn_isready(aim_conn_t *);
+faim_export int aim_conn_setstatus(aim_conn_t *, int);
+faim_export int aim_conn_completeconnect(aim_session_t *sess, aim_conn_t *conn);
+faim_export int aim_conn_isconnecting(aim_conn_t *conn);
+
+typedef void (*faim_debugging_callback_t)(aim_session_t *sess, int level, const char *format, va_list va);
+faim_export int aim_setdebuggingcb(aim_session_t *sess, faim_debugging_callback_t);
+faim_export void aim_session_init(aim_session_t *, unsigned long flags, int debuglevel);
+faim_export void aim_session_kill(aim_session_t *);
+faim_export void aim_setupproxy(aim_session_t *sess, const char *server, const char *username, const char *password);
+faim_export aim_conn_t *aim_getconn_type(aim_session_t *, int type);
+faim_export aim_conn_t *aim_getconn_type_all(aim_session_t *, int type);
+faim_export aim_conn_t *aim_getconn_fd(aim_session_t *, int fd);
+
+/* aim_misc.c */
+
+#define AIM_VISIBILITYCHANGE_PERMITADD    0x05
+#define AIM_VISIBILITYCHANGE_PERMITREMOVE 0x06
+#define AIM_VISIBILITYCHANGE_DENYADD      0x07
+#define AIM_VISIBILITYCHANGE_DENYREMOVE   0x08
+
+#define AIM_PRIVFLAGS_ALLOWIDLE           0x01
+#define AIM_PRIVFLAGS_ALLOWMEMBERSINCE    0x02
+
+#define AIM_WARN_ANON                     0x01
+
+faim_export int aim_sendpauseack(aim_session_t *sess, aim_conn_t *conn);
+faim_export int aim_send_warning(aim_session_t *sess, aim_conn_t *conn, const char *destsn, fu32_t flags);
+faim_export int aim_nop(aim_session_t *, aim_conn_t *);
+faim_export int aim_flap_nop(aim_session_t *sess, aim_conn_t *conn);
+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, const char *awaymsg, 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, aim_conn_t *conn, fu32_t status);
+
+faim_export struct aim_fileheader_t *aim_getlisting(aim_session_t *sess, FILE *);
+
+#define AIM_CLIENTTYPE_UNKNOWN  0x0000
+#define AIM_CLIENTTYPE_MC       0x0001
+#define AIM_CLIENTTYPE_WINAIM   0x0002
+#define AIM_CLIENTTYPE_WINAIM41 0x0003
+#define AIM_CLIENTTYPE_AOL_TOC  0x0004
+faim_export unsigned short aim_fingerprintclient(unsigned char *msghdr, int len);
+
+#define AIM_RATE_CODE_CHANGE     0x0001
+#define AIM_RATE_CODE_WARNING    0x0002
+#define AIM_RATE_CODE_LIMIT      0x0003
+#define AIM_RATE_CODE_CLEARLIMIT 0x0004
+faim_export int aim_ads_requestads(aim_session_t *sess, aim_conn_t *conn);
+
+/* aim_im.c */
+
+struct aim_fileheader_t {
+#if 0
+	char  magic[4];		/* 0 */
+	short hdrlen; 		/* 4 */
+	short hdrtype;		/* 6 */
+#endif
+	char  bcookie[8];       /* 8 */
+	short encrypt;          /* 16 */
+	short compress;         /* 18 */
+	short totfiles;         /* 20 */
+	short filesleft;        /* 22 */
+	short totparts;         /* 24 */
+	short partsleft;        /* 26 */
+	long  totsize;          /* 28 */
+	long  size;             /* 32 */
+	long  modtime;          /* 36 */
+	long  checksum;         /* 40 */
+	long  rfrcsum;          /* 44 */
+	long  rfsize;           /* 48 */
+	long  cretime;          /* 52 */
+	long  rfcsum;           /* 56 */
+	long  nrecvd;           /* 60 */
+	long  recvcsum;         /* 64 */
+	char  idstring[32];     /* 68 */
+	char  flags;            /* 100 */
+	char  lnameoffset;      /* 101 */
+	char  lsizeoffset;      /* 102 */
+	char  dummy[69];        /* 103 */
+	char  macfileinfo[16];  /* 172 */
+	short nencode;          /* 188 */
+	short nlanguage;        /* 190 */
+	char  name[64];         /* 192 */
+				/* 256 */
+};
+
+struct aim_filetransfer_priv {
+	char sn[MAXSNLEN];
+	char cookie[8];
+	char ip[30];
+	int state;
+	struct aim_fileheader_t fh;
+};
+
+struct aim_chat_roominfo {
+	unsigned short exchange;
+	char *name;
+	unsigned short instance;
+};
+
+#define AIM_IMFLAGS_AWAY		0x0001 /* mark as an autoreply */
+#define AIM_IMFLAGS_ACK			0x0002 /* request a receipt notice */
+#define AIM_IMFLAGS_UNICODE		0x0004
+#define AIM_IMFLAGS_ISO_8859_1		0x0008
+#define AIM_IMFLAGS_BUDDYREQ		0x0010 /* buddy icon requested */
+#define AIM_IMFLAGS_HASICON		0x0020 /* already has icon */
+#define AIM_IMFLAGS_SUBENC_MACINTOSH	0x0040 /* damn that Steve Jobs! */
+#define AIM_IMFLAGS_CUSTOMFEATURES 	0x0080 /* features field present */
+#define AIM_IMFLAGS_EXTDATA		0x0100
+#define AIM_IMFLAGS_CUSTOMCHARSET	0x0200 /* charset fields set */
+#define AIM_IMFLAGS_MULTIPART		0x0400 /* ->mpmsg section valid */
+#define AIM_IMFLAGS_OFFLINE		0x0800 /* send to offline user */
+
+/*
+ * Multipart message structures.
+ */
+typedef struct aim_mpmsg_section_s {
+	fu16_t charset;
+	fu16_t charsubset;
+	fu8_t *data;
+	fu16_t datalen;
+	struct aim_mpmsg_section_s *next;
+} aim_mpmsg_section_t;
+
+typedef struct aim_mpmsg_s {
+	int numparts;
+	aim_mpmsg_section_t *parts;
+} aim_mpmsg_t;
+
+faim_export int aim_mpmsg_init(aim_session_t *sess, aim_mpmsg_t *mpm);
+faim_export int aim_mpmsg_addraw(aim_session_t *sess, aim_mpmsg_t *mpm, fu16_t charset, fu16_t charsubset, const fu8_t *data, fu16_t datalen);
+faim_export int aim_mpmsg_addascii(aim_session_t *sess, aim_mpmsg_t *mpm, const char *ascii);
+faim_export int aim_mpmsg_addunicode(aim_session_t *sess, aim_mpmsg_t *mpm, const fu16_t *unicode, fu16_t unicodelen);
+faim_export void aim_mpmsg_free(aim_session_t *sess, aim_mpmsg_t *mpm);
+
+/*
+ * Arguments to aim_send_im_ext().
+ *
+ * This is really complicated.  But immensely versatile.
+ *
+ */
+struct aim_sendimext_args {
+
+	/* These are _required_ */
+	const char *destsn;
+	fu32_t flags; /* often 0 */
+
+	/* Only required if not using multipart messages */
+	const char *msg;
+	int msglen;
+
+	/* Required if ->msg is not provided */
+	aim_mpmsg_t *mpmsg;
+
+	/* Only used if AIM_IMFLAGS_HASICON is set */
+	fu32_t iconlen;
+	time_t iconstamp;
+	fu32_t iconsum;
+
+	/* Only used if AIM_IMFLAGS_CUSTOMFEATURES is set */
+	fu8_t *features;
+	fu8_t featureslen;
+
+	/* Only used if AIM_IMFLAGS_CUSTOMCHARSET is set and mpmsg not used */
+	fu16_t charset;
+	fu16_t charsubset;
+};
+
+/*
+ * Arguments to aim_send_rtfmsg().
+ */
+struct aim_sendrtfmsg_args {
+	const char *destsn;
+	fu32_t fgcolor;
+	fu32_t bgcolor;
+	const char *rtfmsg; /* must be in RTF */
+};
+
+/*
+ * This information is provided in the Incoming ICBM callback for
+ * Channel 1 ICBM's.  
+ *
+ * Note that although CUSTOMFEATURES and CUSTOMCHARSET say they
+ * are optional, both are always set by the current libfaim code.
+ * That may or may not change in the future.  It is mainly for
+ * consistency with aim_sendimext_args.
+ *
+ * Multipart messages require some explanation. If you want to use them,
+ * I suggest you read all the comments in im.c.
+ *
+ */
+struct aim_incomingim_ch1_args {
+
+	/* Always provided */
+	aim_mpmsg_t mpmsg;
+	fu32_t icbmflags; /* some flags apply only to ->msg, not all mpmsg */
+	
+	/* Only provided if message has a human-readable section */
+	char *msg;
+	int msglen;
+
+	/* Only provided if AIM_IMFLAGS_HASICON is set */
+	time_t iconstamp;
+	fu32_t iconlen;
+	fu16_t iconsum;
+
+	/* Only provided if AIM_IMFLAGS_CUSTOMFEATURES is set */
+	fu8_t *features;
+	fu8_t featureslen;
+
+	/* Only provided if AIM_IMFLAGS_EXTDATA is set */
+	fu8_t extdatalen;
+	fu8_t *extdata;
+
+	/* Only used if AIM_IMFLAGS_CUSTOMCHARSET is set */
+	fu16_t charset;
+	fu16_t charsubset;
+};
+
+/* Valid values for channel 2 args->status */
+#define AIM_RENDEZVOUS_PROPOSE 0x0000
+#define AIM_RENDEZVOUS_CANCEL  0x0001
+#define AIM_RENDEZVOUS_ACCEPT  0x0002
+
+struct aim_incomingim_ch2_args {
+	fu8_t cookie[8];
+	fu16_t reqclass;
+	fu16_t status;
+	fu16_t errorcode;
+	const char *clientip;
+	const char *clientip2;
+	const char *verifiedip;
+	fu16_t port;
+	const char *msg; /* invite message or file description */
+	const char *encoding;
+	const char *language;
+	union {
+		struct {
+			fu32_t checksum;
+			fu32_t length;
+			time_t timestamp;
+			fu8_t *icon;
+		} icon;
+		struct {
+			struct aim_chat_roominfo roominfo;
+		} chat;
+		struct {
+			fu32_t fgcolor;
+			fu32_t bgcolor;
+			const char *rtfmsg;
+		} rtfmsg;
+	} info;
+	void *destructor; /* used internally only */
+};
+
+faim_export int aim_send_rtfmsg(aim_session_t *sess, struct aim_sendrtfmsg_args *args);
+faim_export int aim_send_im_ext(aim_session_t *sess, struct aim_sendimext_args *args);
+faim_export int aim_send_im(aim_session_t *, const char *destsn, unsigned short flags, const char *msg);
+faim_export int aim_send_icon(aim_session_t *sess, const char *sn, const fu8_t *icon, int iconlen, time_t stamp, fu16_t iconsum);
+faim_export fu16_t aim_iconsum(const fu8_t *buf, int buflen);
+faim_export int aim_send_im_direct(aim_session_t *, aim_conn_t *, const char *msg);
+faim_export const char *aim_directim_getsn(aim_conn_t *conn);
+faim_export aim_conn_t *aim_directim_initiate(aim_session_t *, const char *destsn);
+faim_export aim_conn_t *aim_directim_connect(aim_session_t *, const char *sn, const char *addr, const fu8_t *cookie);
+
+faim_export aim_conn_t *aim_sendfile_initiate(aim_session_t *, const char *destsn, const char *filename, fu16_t numfiles, fu32_t totsize);
+
+faim_export aim_conn_t *aim_getfile_initiate(aim_session_t *sess, aim_conn_t *conn, const char *destsn);
+faim_export int aim_oft_getfile_request(aim_session_t *sess, aim_conn_t *conn, const char *name, int size);
+faim_export int aim_oft_getfile_ack(aim_session_t *sess, aim_conn_t *conn);
+faim_export int aim_oft_getfile_end(aim_session_t *sess, aim_conn_t *conn);
+
+/* aim_info.c */
+#define AIM_CAPS_BUDDYICON      0x00000001
+#define AIM_CAPS_VOICE          0x00000002
+#define AIM_CAPS_IMIMAGE        0x00000004
+#define AIM_CAPS_CHAT           0x00000008
+#define AIM_CAPS_GETFILE        0x00000010
+#define AIM_CAPS_SENDFILE       0x00000020
+#define AIM_CAPS_GAMES          0x00000040
+#define AIM_CAPS_SAVESTOCKS     0x00000080
+#define AIM_CAPS_SENDBUDDYLIST  0x00000100
+#define AIM_CAPS_GAMES2         0x00000200
+#define AIM_CAPS_ICQ            0x00000400
+#define AIM_CAPS_APINFO         0x00000800
+#define AIM_CAPS_ICQRTF		0x00001000
+#define AIM_CAPS_EMPTY		0x00002000
+#define AIM_CAPS_ICQSERVERRELAY 0x00004000
+#define AIM_CAPS_ICQUNKNOWN     0x00008000
+#define AIM_CAPS_TRILLIANCRYPT  0x00010000
+#define AIM_CAPS_LAST           0x00020000
+
+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, unsigned long offset, unsigned long len, const unsigned char *buf, unsigned char flag);
+
+#define AIM_GETINFO_GENERALINFO 0x00001
+#define AIM_GETINFO_AWAYMESSAGE 0x00003
+#define AIM_GETINFO_CAPABILITIES 0x0004
+
+struct aim_invite_priv {
+	char *sn;
+	char *roomname;
+	fu16_t exchange;
+	fu16_t instance;
+};
+
+#define AIM_COOKIETYPE_UNKNOWN  0x00
+#define AIM_COOKIETYPE_ICBM     0x01
+#define AIM_COOKIETYPE_ADS      0x02
+#define AIM_COOKIETYPE_BOS      0x03
+#define AIM_COOKIETYPE_IM       0x04
+#define AIM_COOKIETYPE_CHAT     0x05
+#define AIM_COOKIETYPE_CHATNAV  0x06
+#define AIM_COOKIETYPE_INVITE   0x07
+/* we'll move OFT up a bit to give breathing room.  not like it really
+ * matters. */
+#define AIM_COOKIETYPE_OFTIM    0x10
+#define AIM_COOKIETYPE_OFTGET   0x11
+#define AIM_COOKIETYPE_OFTSEND  0x12
+#define AIM_COOKIETYPE_OFTVOICE 0x13
+#define AIM_COOKIETYPE_OFTIMAGE 0x14
+#define AIM_COOKIETYPE_OFTICON  0x15
+
+faim_export int aim_handlerendconnect(aim_session_t *sess, aim_conn_t *cur);
+
+#define AIM_TRANSFER_DENY_NOTSUPPORTED 0x0000
+#define AIM_TRANSFER_DENY_DECLINE 0x0001
+#define AIM_TRANSFER_DENY_NOTACCEPTING 0x0002
+faim_export int aim_denytransfer(aim_session_t *sess, const char *sender, const char *cookie, unsigned short code);
+faim_export aim_conn_t *aim_accepttransfer(aim_session_t *sess, aim_conn_t *conn, const char *sn, const fu8_t *cookie, const fu8_t *ip, fu16_t listingfiles, fu16_t listingtotsize, fu16_t listingsize, fu32_t listingchecksum, fu16_t rendid);
+
+faim_export int aim_getinfo(aim_session_t *, aim_conn_t *, const char *, unsigned short);
+faim_export int aim_sendbuddyoncoming(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *info);
+faim_export int aim_sendbuddyoffgoing(aim_session_t *sess, aim_conn_t *conn, const char *sn);
+
+#define AIM_IMPARAM_FLAG_CHANMSGS_ALLOWED	0x00000001
+#define AIM_IMPARAM_FLAG_MISSEDCALLS_ENABLED	0x00000002
+
+/* This is what the server will give you if you don't set them yourself. */
+#define AIM_IMPARAM_DEFAULTS { \
+	0, \
+	AIM_IMPARAM_FLAG_CHANMSGS_ALLOWED | AIM_IMPARAM_FLAG_MISSEDCALLS_ENABLED, \
+	512, /* !! Note how small this is. */ \
+	(99.9)*10, (99.9)*10, \
+	1000 /* !! And how large this is. */ \
+}
+
+/* This is what most AIM versions use. */
+#define AIM_IMPARAM_REASONABLE { \
+	0, \
+	AIM_IMPARAM_FLAG_CHANMSGS_ALLOWED | AIM_IMPARAM_FLAG_MISSEDCALLS_ENABLED, \
+	8000, \
+	(99.9)*10, (99.9)*10, \
+	0 \
+}
+
+
+struct aim_icbmparameters {
+	unsigned short maxchan;
+	unsigned long flags; /* AIM_IMPARAM_FLAG_ */
+	unsigned short maxmsglen; /* message size that you will accept */
+	unsigned short maxsenderwarn; /* this and below are *10 (999=99.9%) */
+	unsigned short maxrecverwarn;
+	unsigned long minmsginterval; /* in milliseconds? */
+};
+
+faim_export int aim_reqicbmparams(aim_session_t *sess);
+faim_export int aim_seticbmparam(aim_session_t *sess, struct aim_icbmparameters *params);
+
+
+/* auth.c */
+faim_export int aim_sendcookie(aim_session_t *, aim_conn_t *, const fu8_t *);
+
+faim_export int aim_admin_changepasswd(aim_session_t *, aim_conn_t *, const char *newpw, const char *curpw);
+faim_export int aim_admin_reqconfirm(aim_session_t *sess, aim_conn_t *conn);
+faim_export int aim_admin_getinfo(aim_session_t *sess, aim_conn_t *conn, fu16_t info);
+faim_export int aim_admin_setemail(aim_session_t *sess, aim_conn_t *conn, const char *newemail);
+faim_export int aim_admin_setnick(aim_session_t *sess, aim_conn_t *conn, const char *newnick);
+
+/* aim_buddylist.c */
+faim_export int aim_add_buddy(aim_session_t *, aim_conn_t *, const char *);
+faim_export int aim_remove_buddy(aim_session_t *, aim_conn_t *, const char *);
+
+/* aim_search.c */
+faim_export int aim_usersearch_address(aim_session_t *, aim_conn_t *, const char *);
+
+/* These apply to exchanges as well. */
+#define AIM_CHATROOM_FLAG_EVILABLE 0x0001
+#define AIM_CHATROOM_FLAG_NAV_ONLY 0x0002
+#define AIM_CHATROOM_FLAG_INSTANCING_ALLOWED 0x0004
+#define AIM_CHATROOM_FLAG_OCCUPANT_PEEK_ALLOWED 0x0008
+
+struct aim_chat_exchangeinfo {
+	fu16_t number;
+	fu16_t flags;
+	char *name;
+	char *charset1;
+	char *lang1;
+	char *charset2;
+	char *lang2;
+};
+
+#define AIM_CHATFLAGS_NOREFLECT 0x0001
+#define AIM_CHATFLAGS_AWAY      0x0002
+faim_export int aim_chat_send_im(aim_session_t *sess, aim_conn_t *conn, fu16_t flags, const char *msg, int msglen);
+faim_export int aim_chat_join(aim_session_t *sess, aim_conn_t *conn, fu16_t exchange, const char *roomname, fu16_t instance);
+faim_export int aim_chat_attachname(aim_conn_t *conn, fu16_t exchange, const char *roomname, fu16_t instance);
+faim_export char *aim_chat_getname(aim_conn_t *conn);
+faim_export aim_conn_t *aim_chat_getconn(aim_session_t *, const char *name);
+
+faim_export int aim_chatnav_reqrights(aim_session_t *sess, aim_conn_t *conn);
+
+faim_export int aim_chat_invite(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *msg, fu16_t exchange, const char *roomname, fu16_t instance);
+
+faim_export int aim_chatnav_createroom(aim_session_t *sess, aim_conn_t *conn, const char *name, fu16_t exchange);
+faim_export int aim_chat_leaveroom(aim_session_t *sess, const char *name);
+
+
+#define AIM_SSI_TYPE_BUDDY         0x0000
+#define AIM_SSI_TYPE_GROUP         0x0001
+#define AIM_SSI_TYPE_PERMITLIST    0x0002
+#define AIM_SSI_TYPE_DENYLIST      0x0003
+#define AIM_SSI_TYPE_PDINFO        0x0004
+#define AIM_SSI_TYPE_PRESENCEPREFS 0x0005
+
+struct aim_ssi_item {
+	char *name;
+	fu16_t gid;
+	fu16_t bid;
+	fu16_t type;
+	void *data;
+	struct aim_ssi_item *next;
+};
+
+faim_export int aim_ssi_reqrights(aim_session_t *sess, aim_conn_t *conn);
+faim_export int aim_ssi_reqdata(aim_session_t *sess, aim_conn_t *conn, time_t localstamp, fu16_t localrev);
+faim_export int aim_ssi_enable(aim_session_t *sess, aim_conn_t *conn);
+faim_export int aim_ssi_modbegin(aim_session_t *sess, aim_conn_t *conn);
+faim_export int aim_ssi_modend(aim_session_t *sess, aim_conn_t *conn);
+
+struct aim_icq_offlinemsg {
+	fu32_t sender;
+	fu16_t year;
+	fu8_t month, day, hour, minute;
+	fu16_t type;
+	char *msg;
+};
+
+struct aim_icq_simpleinfo {
+	fu32_t uin;
+	char *nick;
+	char *first;
+	char *last;
+	char *email;
+};
+
+faim_export int aim_icq_reqofflinemsgs(aim_session_t *sess);
+faim_export int aim_icq_ackofflinemsgs(aim_session_t *sess);
+faim_export int aim_icq_getsimpleinfo(aim_session_t *sess, const char *uin);
+
+/* aim_util.c */
+/*
+ * These are really ugly.  You'd think this was LISP.  I wish it was.
+ *
+ * XXX With the advent of bstream's, these should be removed to enforce
+ * their use.
+ *
+ */
+#define aimutil_put8(buf, data) ((*(buf) = (u_char)(data)&0xff),1)
+#define aimutil_get8(buf) ((*(buf))&0xff)
+#define aimutil_put16(buf, data) ( \
+		(*(buf) = (u_char)((data)>>8)&0xff), \
+		(*((buf)+1) = (u_char)(data)&0xff),  \
+		2)
+#define aimutil_get16(buf) ((((*(buf))<<8)&0xff00) + ((*((buf)+1)) & 0xff))
+#define aimutil_put32(buf, data) ( \
+		(*((buf)) = (u_char)((data)>>24)&0xff), \
+		(*((buf)+1) = (u_char)((data)>>16)&0xff), \
+		(*((buf)+2) = (u_char)((data)>>8)&0xff), \
+		(*((buf)+3) = (u_char)(data)&0xff), \
+		4)
+#define aimutil_get32(buf) ((((*(buf))<<24)&0xff000000) + \
+		(((*((buf)+1))<<16)&0x00ff0000) + \
+		(((*((buf)+2))<< 8)&0x0000ff00) + \
+		(((*((buf)+3)    )&0x000000ff)))
+
+/* Little-endian versions (damn ICQ) */
+#define aimutil_putle8(buf, data) ( \
+		(*(buf) = (unsigned char)(data) & 0xff), \
+		1)
+#define aimutil_getle8(buf) ( \
+		(*(buf)) & 0xff \
+		)
+#define aimutil_putle16(buf, data) ( \
+		(*((buf)+0) = (unsigned char)((data) >> 0) & 0xff),  \
+		(*((buf)+1) = (unsigned char)((data) >> 8) & 0xff), \
+		2)
+#define aimutil_getle16(buf) ( \
+		(((*((buf)+0)) << 0) & 0x00ff) + \
+		(((*((buf)+1)) << 8) & 0xff00) \
+		)
+#define aimutil_putle32(buf, data) ( \
+		(*((buf)+0) = (unsigned char)((data) >>  0) & 0xff), \
+		(*((buf)+1) = (unsigned char)((data) >>  8) & 0xff), \
+		(*((buf)+2) = (unsigned char)((data) >> 16) & 0xff), \
+		(*((buf)+3) = (unsigned char)((data) >> 24) & 0xff), \
+		4)
+#define aimutil_getle32(buf) ( \
+		(((*((buf)+0)) <<  0) & 0x000000ff) + \
+		(((*((buf)+1)) <<  8) & 0x0000ff00) + \
+		(((*((buf)+2)) << 16) & 0x00ff0000) + \
+		(((*((buf)+3)) << 24) & 0xff000000))
+
+
+faim_export int aimutil_putstr(u_char *, const char *, int);
+faim_export int aimutil_tokslen(char *toSearch, int index, char dl);
+faim_export int aimutil_itemcnt(char *toSearch, char dl);
+faim_export char *aimutil_itemidx(char *toSearch, int index, char dl);
+
+faim_export int aim_snlen(const char *sn);
+faim_export int aim_sncmp(const char *sn1, const char *sn2);
+
+/* for libc's that dont have it */
+faim_export char *aim_strsep(char **pp, const char *delim);
+
+/* aim_meta.c */
+faim_export char *aim_getbuilddate(void);
+faim_export char *aim_getbuildtime(void);
+faim_export int aim_getbuildstring(char *buf, int buflen);
+
+#include <aim_internal.h>
+
+#endif /* __AIM_H__ */
+
Index: libfaim/aim_cbtypes.h
===================================================================
--- libfaim/aim_cbtypes.h	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/aim_cbtypes.h	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,243 @@
+/*
+ * AIM Callback Types
+ *
+ */
+#ifndef __AIM_CBTYPES_H__
+#define __AIM_CBTYPES_H__
+
+/*
+ * SNAC Families.
+ */
+#define AIM_CB_FAM_ACK 0x0000
+#define AIM_CB_FAM_GEN 0x0001
+#define AIM_CB_FAM_LOC 0x0002
+#define AIM_CB_FAM_BUD 0x0003
+#define AIM_CB_FAM_MSG 0x0004
+#define AIM_CB_FAM_ADS 0x0005
+#define AIM_CB_FAM_INV 0x0006
+#define AIM_CB_FAM_ADM 0x0007
+#define AIM_CB_FAM_POP 0x0008
+#define AIM_CB_FAM_BOS 0x0009
+#define AIM_CB_FAM_LOK 0x000a
+#define AIM_CB_FAM_STS 0x000b
+#define AIM_CB_FAM_TRN 0x000c
+#define AIM_CB_FAM_CTN 0x000d /* ChatNav */
+#define AIM_CB_FAM_CHT 0x000e /* Chat */
+#define AIM_CB_FAM_ICQ 0x0015
+#define AIM_CB_FAM_ATH 0x0017
+#define AIM_CB_FAM_OFT 0xfffe /* OFT/Rvous */
+#define AIM_CB_FAM_SPECIAL 0xffff /* Internal libfaim use */
+
+/*
+ * SNAC Family: Ack.
+ * 
+ * Not really a family, but treating it as one really
+ * helps it fit into the libfaim callback structure better.
+ *
+ */
+#define AIM_CB_ACK_ACK 0x0001
+
+/*
+ * SNAC Family: General.
+ */ 
+#define AIM_CB_GEN_ERROR 0x0001
+#define AIM_CB_GEN_CLIENTREADY 0x0002
+#define AIM_CB_GEN_SERVERREADY 0x0003
+#define AIM_CB_GEN_SERVICEREQ 0x0004
+#define AIM_CB_GEN_REDIRECT 0x0005
+#define AIM_CB_GEN_RATEINFOREQ 0x0006
+#define AIM_CB_GEN_RATEINFO 0x0007
+#define AIM_CB_GEN_RATEINFOACK 0x0008
+#define AIM_CB_GEN_RATECHANGE 0x000a
+#define AIM_CB_GEN_SERVERPAUSE 0x000b
+#define AIM_CB_GEN_SERVERRESUME 0x000d
+#define AIM_CB_GEN_REQSELFINFO 0x000e
+#define AIM_CB_GEN_SELFINFO 0x000f
+#define AIM_CB_GEN_EVIL 0x0010
+#define AIM_CB_GEN_SETIDLE 0x0011
+#define AIM_CB_GEN_MIGRATIONREQ 0x0012
+#define AIM_CB_GEN_MOTD 0x0013
+#define AIM_CB_GEN_SETPRIVFLAGS 0x0014
+#define AIM_CB_GEN_WELLKNOWNURL 0x0015
+#define AIM_CB_GEN_NOP 0x0016
+#define AIM_CB_GEN_DEFAULT 0xffff
+
+/*
+ * SNAC Family: Location Services.
+ */ 
+#define AIM_CB_LOC_ERROR 0x0001
+#define AIM_CB_LOC_REQRIGHTS 0x0002
+#define AIM_CB_LOC_RIGHTSINFO 0x0003
+#define AIM_CB_LOC_SETUSERINFO 0x0004
+#define AIM_CB_LOC_REQUSERINFO 0x0005
+#define AIM_CB_LOC_USERINFO 0x0006
+#define AIM_CB_LOC_WATCHERSUBREQ 0x0007
+#define AIM_CB_LOC_WATCHERNOT 0x0008
+#define AIM_CB_LOC_DEFAULT 0xffff
+
+/*
+ * SNAC Family: Buddy List Management Services.
+ */ 
+#define AIM_CB_BUD_ERROR 0x0001
+#define AIM_CB_BUD_REQRIGHTS 0x0002
+#define AIM_CB_BUD_RIGHTSINFO 0x0003
+#define AIM_CB_BUD_ADDBUDDY 0x0004
+#define AIM_CB_BUD_REMBUDDY 0x0005
+#define AIM_CB_BUD_REJECT 0x000a
+#define AIM_CB_BUD_ONCOMING 0x000b
+#define AIM_CB_BUD_OFFGOING 0x000c
+#define AIM_CB_BUD_DEFAULT 0xffff
+
+/*
+ * SNAC Family: Messeging Services.
+ */ 
+#define AIM_CB_MSG_ERROR 0x0001
+#define AIM_CB_MSG_PARAMINFO 0x0005
+#define AIM_CB_MSG_INCOMING 0x0007
+#define AIM_CB_MSG_EVIL 0x0009
+#define AIM_CB_MSG_MISSEDCALL 0x000a
+#define AIM_CB_MSG_CLIENTERROR 0x000b
+#define AIM_CB_MSG_ACK 0x000c
+#define AIM_CB_MSG_DEFAULT 0xffff
+
+/*
+ * SNAC Family: Advertisement Services
+ */ 
+#define AIM_CB_ADS_ERROR 0x0001
+#define AIM_CB_ADS_DEFAULT 0xffff
+
+/*
+ * SNAC Family: Invitation Services.
+ */ 
+#define AIM_CB_INV_ERROR 0x0001
+#define AIM_CB_INV_DEFAULT 0xffff
+
+/*
+ * SNAC Family: Administrative Services.
+ */ 
+#define AIM_CB_ADM_ERROR 0x0001
+#define AIM_CB_ADM_INFOCHANGE_REPLY 0x0005
+#define AIM_CB_ADM_DEFAULT 0xffff
+
+/*
+ * SNAC Family: Popup Messages
+ */ 
+#define AIM_CB_POP_ERROR 0x0001
+#define AIM_CB_POP_DEFAULT 0xffff
+
+/*
+ * SNAC Family: Misc BOS Services.
+ */ 
+#define AIM_CB_BOS_ERROR 0x0001
+#define AIM_CB_BOS_RIGHTSQUERY 0x0002
+#define AIM_CB_BOS_RIGHTS 0x0003
+#define AIM_CB_BOS_DEFAULT 0xffff
+
+/*
+ * SNAC Family: User Lookup Services
+ */ 
+#define AIM_CB_LOK_ERROR 0x0001
+#define AIM_CB_LOK_DEFAULT 0xffff
+
+/*
+ * SNAC Family: User Status Services
+ */ 
+#define AIM_CB_STS_ERROR 0x0001
+#define AIM_CB_STS_SETREPORTINTERVAL 0x0002
+#define AIM_CB_STS_REPORTACK 0x0004
+#define AIM_CB_STS_DEFAULT 0xffff
+
+/*
+ * SNAC Family: Translation Services
+ */ 
+#define AIM_CB_TRN_ERROR 0x0001
+#define AIM_CB_TRN_DEFAULT 0xffff
+
+/*
+ * SNAC Family: Chat Navigation Services
+ */ 
+#define AIM_CB_CTN_ERROR 0x0001
+#define AIM_CB_CTN_CREATE 0x0008
+#define AIM_CB_CTN_INFO 0x0009
+#define AIM_CB_CTN_DEFAULT 0xffff
+
+/*
+ * SNAC Family: Chat Services
+ */ 
+#define AIM_CB_CHT_ERROR 0x0001
+#define AIM_CB_CHT_ROOMINFOUPDATE 0x0002
+#define AIM_CB_CHT_USERJOIN 0x0003
+#define AIM_CB_CHT_USERLEAVE 0x0004
+#define AIM_CB_CHT_OUTGOINGMSG 0x0005
+#define AIM_CB_CHT_INCOMINGMSG 0x0006
+#define AIM_CB_CHT_DEFAULT 0xffff
+
+/*
+ * SNAC Family: ICQ
+ *
+ * Most of these are actually special.
+ */ 
+#define AIM_CB_ICQ_ERROR 0x0001
+#define AIM_CB_ICQ_OFFLINEMSG 0x00f0
+#define AIM_CB_ICQ_OFFLINEMSGCOMPLETE 0x00f1
+#define AIM_CB_ICQ_SIMPLEINFO 0x00f2
+#define AIM_CB_ICQ_DEFAULT 0xffff
+
+/*
+ * SNAC Family: Authorizer
+ *
+ * Used only in protocol versions three and above.
+ *
+ */
+#define AIM_CB_ATH_ERROR 0x0001
+#define AIM_CB_ATH_LOGINREQEST 0x0002
+#define AIM_CB_ATH_LOGINRESPONSE 0x0003
+#define AIM_CB_ATH_AUTHREQ 0x0006
+#define AIM_CB_ATH_AUTHRESPONSE 0x0007
+
+/*
+ * OFT Services
+ *
+ * See non-SNAC note below.
+ */
+#define AIM_CB_OFT_DIRECTIMCONNECTREQ 0x0001/* connect request -- actually an OSCAR CAP*/
+#define AIM_CB_OFT_DIRECTIMINCOMING 0x0002
+#define AIM_CB_OFT_DIRECTIMDISCONNECT 0x0003
+#define AIM_CB_OFT_DIRECTIMTYPING 0x0004
+#define AIM_CB_OFT_DIRECTIMINITIATE 0x0005
+
+#define AIM_CB_OFT_GETFILECONNECTREQ 0x0006 /* connect request -- actually an OSCAR CAP*/
+#define AIM_CB_OFT_GETFILELISTINGREQ 0x0007 /* OFT listing.txt request */
+#define AIM_CB_OFT_GETFILEFILEREQ 0x0008    /* received file request */
+#define AIM_CB_OFT_GETFILEFILESEND 0x0009   /* received file request confirm -- send data */
+#define AIM_CB_OFT_GETFILECOMPLETE 0x000a   /* received file send complete*/
+#define AIM_CB_OFT_GETFILEINITIATE 0x000b   /* request for file get acknowledge */
+#define AIM_CB_OFT_GETFILEDISCONNECT 0x000c   /* OFT connection disconnected.*/
+#define AIM_CB_OFT_GETFILELISTING 0x000d   /* OFT listing.txt received.*/
+#define AIM_CB_OFT_GETFILERECEIVE 0x000e   /* OFT file incoming.*/
+#define AIM_CB_OFT_GETFILELISTINGRXCONFIRM 0x000f
+#define AIM_CB_OFT_GETFILESTATE4 0x0010
+
+#define AIM_CB_OFT_SENDFILEDISCONNECT 0x0020   /* OFT connection disconnected.*/
+
+
+
+/*
+ * SNAC Family: Internal Messages
+ *
+ * This isn't truely a SNAC family either, but using
+ * these, we can integrated non-SNAC services into
+ * the SNAC-centered libfaim callback structure.
+ *
+ */ 
+#define AIM_CB_SPECIAL_AUTHSUCCESS 0x0001
+#define AIM_CB_SPECIAL_AUTHOTHER 0x0002
+#define AIM_CB_SPECIAL_CONNERR 0x0003
+#define AIM_CB_SPECIAL_CONNCOMPLETE 0x0004
+#define AIM_CB_SPECIAL_FLAPVER 0x0005
+#define AIM_CB_SPECIAL_CONNINITDONE 0x0006
+#define AIM_CB_SPECIAL_UNKNOWN 0xffff
+#define AIM_CB_SPECIAL_DEFAULT AIM_CB_SPECIAL_UNKNOWN
+
+
+#endif/*__AIM_CBTYPES_H__ */
Index: libfaim/aim_internal.h
===================================================================
--- libfaim/aim_internal.h	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/aim_internal.h	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,234 @@
+/*
+ * aim_internal.h -- prototypes/structs for the guts of libfaim
+ *
+ */
+
+#ifdef FAIM_INTERNAL
+#ifndef __AIM_INTERNAL_H__
+#define __AIM_INTERNAL_H__ 1
+
+typedef struct {
+	fu16_t family;
+	fu16_t subtype;
+	fu16_t flags;
+	fu32_t id;
+} aim_modsnac_t;
+
+#define AIM_MODULENAME_MAXLEN 16
+#define AIM_MODFLAG_MULTIFAMILY 0x0001
+typedef struct aim_module_s {
+	fu16_t family;
+	fu16_t version;
+	fu16_t toolid;
+	fu16_t toolversion;
+	fu16_t flags;
+	char name[AIM_MODULENAME_MAXLEN+1];
+	int (*snachandler)(aim_session_t *sess, struct aim_module_s *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs);
+	void (*shutdown)(aim_session_t *sess, struct aim_module_s *mod);
+	void *priv;
+	struct aim_module_s *next;
+} aim_module_t;
+
+faim_internal int aim__registermodule(aim_session_t *sess, int (*modfirst)(aim_session_t *, aim_module_t *));
+faim_internal void aim__shutdownmodules(aim_session_t *sess);
+faim_internal aim_module_t *aim__findmodulebygroup(aim_session_t *sess, fu16_t group);
+faim_internal aim_module_t *aim__findmodule(aim_session_t *sess, const char *name);
+
+faim_internal int buddylist_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int admin_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int bos_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int search_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int stats_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int auth_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int msg_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int misc_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int chatnav_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int chat_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int locate_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int general_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int ssi_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int invite_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int translate_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int popups_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int adverts_modfirst(aim_session_t *sess, aim_module_t *mod);
+faim_internal int icq_modfirst(aim_session_t *sess, aim_module_t *mod);
+
+faim_internal int aim_genericreq_n(aim_session_t *, aim_conn_t *conn, fu16_t family, fu16_t subtype);
+faim_internal int aim_genericreq_n_snacid(aim_session_t *, aim_conn_t *conn, fu16_t family, fu16_t subtype);
+faim_internal int aim_genericreq_l(aim_session_t *, aim_conn_t *conn, fu16_t family, fu16_t subtype, fu32_t *);
+faim_internal int aim_genericreq_s(aim_session_t *, aim_conn_t *conn, fu16_t family, fu16_t subtype, fu16_t *);
+
+#define AIMBS_CURPOSPAIR(x) ((x)->data + (x)->offset), ((x)->len - (x)->offset)
+
+faim_internal void aim_rxqueue_cleanbyconn(aim_session_t *sess, aim_conn_t *conn);
+faim_internal int aim_recv(int fd, void *buf, size_t count);
+faim_internal int aim_bstream_recv(aim_bstream_t *bs, int fd, size_t count);
+faim_internal int aim_bstream_init(aim_bstream_t *bs, fu8_t *data, int len);
+faim_internal int aim_bstream_empty(aim_bstream_t *bs);
+faim_internal int aim_bstream_curpos(aim_bstream_t *bs);
+faim_internal int aim_bstream_setpos(aim_bstream_t *bs, int off);
+faim_internal void aim_bstream_rewind(aim_bstream_t *bs);
+faim_internal int aim_bstream_advance(aim_bstream_t *bs, int n);
+faim_internal fu8_t aimbs_get8(aim_bstream_t *bs);
+faim_internal fu16_t aimbs_get16(aim_bstream_t *bs);
+faim_internal fu32_t aimbs_get32(aim_bstream_t *bs);
+faim_internal fu8_t aimbs_getle8(aim_bstream_t *bs);
+faim_internal fu16_t aimbs_getle16(aim_bstream_t *bs);
+faim_internal fu32_t aimbs_getle32(aim_bstream_t *bs);
+faim_internal int aimbs_put8(aim_bstream_t *bs, fu8_t v);
+faim_internal int aimbs_put16(aim_bstream_t *bs, fu16_t v);
+faim_internal int aimbs_put32(aim_bstream_t *bs, fu32_t v);
+faim_internal int aimbs_putle8(aim_bstream_t *bs, fu8_t v);
+faim_internal int aimbs_putle16(aim_bstream_t *bs, fu16_t v);
+faim_internal int aimbs_putle32(aim_bstream_t *bs, fu32_t v);
+faim_internal int aimbs_getrawbuf(aim_bstream_t *bs, fu8_t *buf, int len);
+faim_internal fu8_t *aimbs_getraw(aim_bstream_t *bs, int len);
+faim_internal char *aimbs_getstr(aim_bstream_t *bs, int len);
+faim_internal int aimbs_putraw(aim_bstream_t *bs, const fu8_t *v, int len);
+faim_internal int aimbs_putbs(aim_bstream_t *bs, aim_bstream_t *srcbs, int len);
+
+faim_internal int aim_get_command_rendezvous(aim_session_t *sess, aim_conn_t *conn);
+
+faim_internal int aim_tx_sendframe(aim_session_t *sess, aim_frame_t *cur);
+faim_internal flap_seqnum_t aim_get_next_txseqnum(aim_conn_t *);
+faim_internal aim_frame_t *aim_tx_new(aim_session_t *sess, aim_conn_t *conn, fu8_t framing, fu8_t chan, int datalen);
+faim_internal void aim_frame_destroy(aim_frame_t *);
+faim_internal int aim_tx_enqueue(aim_session_t *, aim_frame_t *);
+faim_internal int aim_tx_printqueue(aim_session_t *);
+faim_internal void aim_tx_cleanqueue(aim_session_t *, aim_conn_t *);
+
+faim_internal aim_rxcallback_t aim_callhandler(aim_session_t *sess, aim_conn_t *conn, u_short family, u_short type);
+faim_internal int aim_callhandler_noparam(aim_session_t *sess, aim_conn_t *conn, fu16_t family, fu16_t type, aim_frame_t *ptr);
+
+/*
+ * Generic SNAC structure.  Rarely if ever used.
+ */
+typedef struct aim_snac_s {
+	aim_snacid_t id;
+	fu16_t family;
+	fu16_t type;
+	fu16_t flags;
+	void *data;
+	time_t issuetime;
+	struct aim_snac_s *next;
+} aim_snac_t;
+
+faim_internal void aim_initsnachash(aim_session_t *sess);
+faim_internal aim_snacid_t aim_newsnac(aim_session_t *, aim_snac_t *newsnac);
+faim_internal aim_snacid_t aim_cachesnac(aim_session_t *sess, const fu16_t family, const fu16_t type, const fu16_t flags, const void *data, const int datalen);
+faim_internal aim_snac_t *aim_remsnac(aim_session_t *, aim_snacid_t id);
+faim_internal void aim_cleansnacs(aim_session_t *, int maxage);
+faim_internal int aim_putsnac(aim_bstream_t *, fu16_t family, fu16_t type, fu16_t flags, aim_snacid_t id);
+
+faim_internal aim_conn_t *aim_cloneconn(aim_session_t *sess, aim_conn_t *src);
+faim_internal void aim_clonehandlers(aim_session_t *sess, aim_conn_t *dest, aim_conn_t *src);
+
+faim_internal int aim_oft_buildheader(unsigned char *,struct aim_fileheader_t *);
+
+faim_internal int aim_parse_unknown(aim_session_t *, aim_frame_t *, ...);
+
+/* Stored in ->priv of the service request SNAC for chats. */
+struct chatsnacinfo {
+	fu16_t exchange;
+	char name[128];
+	fu16_t instance;
+};
+
+/* these are used by aim_*_clientready */
+#define AIM_TOOL_JAVA   0x0001
+#define AIM_TOOL_MAC    0x0002
+#define AIM_TOOL_WIN16  0x0003
+#define AIM_TOOL_WIN32  0x0004
+#define AIM_TOOL_MAC68K 0x0005
+#define AIM_TOOL_MACPPC 0x0006
+#define AIM_TOOL_NEWWIN 0x0010
+struct aim_tool_version {
+	fu16_t group;
+	fu16_t version;
+	fu16_t tool;
+	fu16_t toolversion;
+};
+
+/* 
+ * In SNACland, the terms 'family' and 'group' are synonymous -- the former
+ * is my term, the latter is AOL's.
+ */
+struct snacgroup {
+	fu16_t group;
+	struct snacgroup *next;
+};
+
+#ifdef FAIM_NEED_CONN_INTERNAL
+struct snacpair {
+	fu16_t group;
+	fu16_t subtype;
+	struct snacpair *next;
+};
+
+struct rateclass {
+	fu16_t classid;
+	fu32_t windowsize;
+	fu32_t clear;
+	fu32_t alert;
+	fu32_t limit;
+	fu32_t disconnect;
+	fu32_t current;
+	fu32_t max;
+	fu8_t unknown[5]; /* only present in versions >= 3 */
+	struct snacpair *members;
+	struct rateclass *next;
+};
+#endif /* FAIM_NEED_CONN_INTERNAL */
+
+/*
+ * This is inside every connection.  But it is a void * to anything
+ * outside of libfaim.  It should remain that way.  It's called data
+ * abstraction.  Maybe you've heard of it.  (Probably not if you're a 
+ * libfaim user.)
+ * 
+ */
+typedef struct aim_conn_inside_s {
+	struct snacgroup *groups;
+	struct rateclass *rates;
+} aim_conn_inside_t;
+
+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 *);
+faim_internal aim_msgcookie_t *aim_checkcookie(aim_session_t *, const unsigned char *, const int);
+faim_internal int aim_freecookie(aim_session_t *sess, aim_msgcookie_t *cookie);
+faim_internal int aim_msgcookie_gettype(int reqclass);
+faim_internal int aim_cookie_free(aim_session_t *sess, aim_msgcookie_t *cookie);
+
+faim_internal int aim_extractuserinfo(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *);
+faim_internal 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 int aim_request_directim(aim_session_t *sess, const char *destsn, fu8_t *ip, fu16_t port, fu8_t *ckret);
+faim_internal int aim_request_sendfile(aim_session_t *sess, const char *sn, const char *filename, fu16_t numfiles, fu32_t totsize, fu8_t *ip, fu16_t port, fu8_t *ckret);
+faim_internal void aim_conn_close_rend(aim_session_t *sess, aim_conn_t *conn);
+faim_internal void aim_conn_kill_rend(aim_session_t *sess, aim_conn_t *conn);
+
+faim_internal void aim_conn_kill_chat(aim_session_t *sess, aim_conn_t *conn);
+
+/* These are all handled internally now. */
+faim_internal int aim_setversions(aim_session_t *sess, aim_conn_t *conn);
+faim_internal int aim_reqrates(aim_session_t *, aim_conn_t *);
+faim_internal int aim_rates_addparam(aim_session_t *, aim_conn_t *);
+faim_internal int aim_rates_delparam(aim_session_t *, aim_conn_t *);
+
+#ifndef FAIM_INTERNAL_INSANE
+#define printf() printf called inside libfaim
+#define sprintf() unbounded sprintf used inside libfaim
+#endif
+
+#endif /* __AIM_INTERNAL_H__ */
+#endif /* FAIM_INTERNAL */
Index: libfaim/auth.c
===================================================================
--- libfaim/auth.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/auth.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,541 @@
+/*
+ * Deals with the authorizer (group 0x0017=23, and old-style non-SNAC login).
+ *
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h> 
+
+#include "md5.h"
+
+static int aim_encode_password(const char *password, unsigned char *encoded);
+
+/* 
+ * This just pushes the passed cookie onto the passed connection, without
+ * the SNAC header or any of that.
+ *
+ * Very commonly used, as every connection except auth will require this to
+ * be the first thing you send.
+ *
+ */
+faim_export int aim_sendcookie(aim_session_t *sess, aim_conn_t *conn, const fu8_t *chipsahoy)
+{
+	aim_frame_t *fr;
+	aim_tlvlist_t *tl = NULL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x0001, 4+2+2+AIM_COOKIELEN)))
+		return -ENOMEM;
+
+	aimbs_put32(&fr->data, 0x00000001);
+	aim_addtlvtochain_raw(&tl, 0x0006, AIM_COOKIELEN, chipsahoy);	
+	aim_writetlvchain(&fr->data, &tl);
+	aim_freetlvchain(&tl);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * Normally the FLAP version is sent as the first few bytes of the cookie,
+ * meaning you generally never call this.
+ *
+ * But there are times when something might want it seperate. Specifically,
+ * libfaim sends this internally when doing SNAC login.
+ *
+ */
+faim_export int aim_sendflapver(aim_session_t *sess, aim_conn_t *conn)
+{
+	aim_frame_t *fr;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x01, 4)))
+		return -ENOMEM;
+
+	aimbs_put32(&fr->data, 0x00000001);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * This is a bit confusing.
+ *
+ * Normal SNAC login goes like this:
+ *   - connect
+ *   - server sends flap version
+ *   - client sends flap version
+ *   - client sends screen name (17/6)
+ *   - server sends hash key (17/7)
+ *   - client sends auth request (17/2 -- aim_send_login)
+ *   - server yells
+ *
+ * XOR login (for ICQ) goes like this:
+ *   - connect
+ *   - server sends flap version
+ *   - client sends auth request which contains flap version (aim_send_login)
+ *   - server yells
+ *
+ * For the client API, we make them implement the most complicated version,
+ * and for the simpler version, we fake it and make it look like the more
+ * complicated process.
+ *
+ * This is done by giving the client a faked key, just so we can convince
+ * them to call aim_send_login right away, which will detect the session
+ * flag that says this is XOR login and ignore the key, sending an ICQ
+ * login request instead of the normal SNAC one.
+ *
+ * As soon as AOL makes ICQ log in the same way as AIM, this is /gone/.
+ *
+ * XXX This may cause problems if the client relies on callbacks only
+ * being called from the context of aim_rxdispatch()...
+ *
+ */
+static int goddamnicq(aim_session_t *sess, aim_conn_t *conn, const char *sn)
+{
+	aim_frame_t fr;
+	aim_rxcallback_t userfunc;
+	
+	sess->flags &= ~AIM_SESS_FLAGS_SNACLOGIN;
+	sess->flags |= AIM_SESS_FLAGS_XORLOGIN;
+
+	fr.conn = conn;
+	
+	if ((userfunc = aim_callhandler(sess, conn, 0x0017, 0x0007)))
+		userfunc(sess, &fr, "");
+
+	return 0;
+}
+
+/*
+ * In AIM 3.5 protocol, the first stage of login is to request login from the 
+ * Authorizer, passing it the screen name for verification.  If the name is 
+ * invalid, a 0017/0003 is spit back, with the standard error contents.  If 
+ * valid, a 0017/0007 comes back, which is the signal to send it the main 
+ * login command (0017/0002). 
+ *
+ */
+faim_export int aim_request_login(aim_session_t *sess, aim_conn_t *conn, const char *sn)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	aim_tlvlist_t *tl = NULL;
+	
+	if (!sess || !conn || !sn)
+		return -EINVAL;
+
+	if ((sn[0] >= '0') && (sn[0] <= '9'))
+		return goddamnicq(sess, conn, sn);
+
+	sess->flags |= AIM_SESS_FLAGS_SNACLOGIN;
+
+	aim_sendflapver(sess, conn);
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+2+strlen(sn))))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0017, 0x0006, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0017, 0x0006, 0x0000, snacid);
+
+	aim_addtlvtochain_raw(&tl, 0x0001, strlen(sn), sn);
+	aim_writetlvchain(&fr->data, &tl);
+	aim_freetlvchain(&tl);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * Part two of the ICQ hack.  Note the ignoring of the key and clientinfo.
+ */
+static int goddamnicq2(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *password)
+{
+	static const char clientstr[] = {"ICQ Inc. - Product of ICQ (TM) 2000b.4.65.1.3281.85"};
+	static const char lang[] = {"en"};
+	static const char country[] = {"us"};
+	aim_frame_t *fr;
+	aim_tlvlist_t *tl = NULL;
+	char *password_encoded;
+
+	if (!(password_encoded = (char *) malloc(strlen(password))))
+		return -ENOMEM;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x01, 1152))) {
+		free(password_encoded);
+		return -ENOMEM;
+	}
+
+	aim_encode_password(password, password_encoded);
+
+	aimbs_put32(&fr->data, 0x00000001);
+	aim_addtlvtochain_raw(&tl, 0x0001, strlen(sn), sn);
+	aim_addtlvtochain_raw(&tl, 0x0002, strlen(password), password_encoded);
+	aim_addtlvtochain_raw(&tl, 0x0003, strlen(clientstr), clientstr);
+	aim_addtlvtochain16(&tl, 0x0016, 0x010a); /* cliend ID */
+	aim_addtlvtochain16(&tl, 0x0017, 0x0004); /* major version */
+	aim_addtlvtochain16(&tl, 0x0018, 0x0041); /* minor version */
+	aim_addtlvtochain16(&tl, 0x0019, 0x0001); /* point version */
+	aim_addtlvtochain16(&tl, 0x001a, 0x0cd1); /* build */
+	aim_addtlvtochain32(&tl, 0x0014, 0x00000055); /* distribution chan */
+	aim_addtlvtochain_raw(&tl, 0x000f, strlen(lang), lang);
+	aim_addtlvtochain_raw(&tl, 0x000e, strlen(country), country);
+
+	aim_writetlvchain(&fr->data, &tl);
+
+	free(password_encoded);
+	aim_freetlvchain(&tl);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * send_login(int socket, char *sn, char *password)
+ *  
+ * This is the initial login request packet.
+ *
+ * NOTE!! If you want/need to make use of the aim_sendmemblock() function,
+ * then the client information you send here must exactly match the
+ * executable that you're pulling the data from.
+ *
+ * WinAIM 4.8.2540
+ *   clientstring = "AOL Instant Messenger (SM), version 4.8.2540/WIN32"
+ *   clientid = 0x0109
+ *   major = 0x0004
+ *   minor = 0x0008
+ *   point = 0x0000
+ *   build = 0x09ec
+ *   t(0x0014) = 0x000000af
+ *   t(0x004a) = 0x01
+ *
+ * WinAIM 4.3.2188:
+ *   clientstring = "AOL Instant Messenger (SM), version 4.3.2188/WIN32"
+ *   clientid = 0x0109
+ *   major = 0x0400
+ *   minor = 0x0003
+ *   point = 0x0000
+ *   build = 0x088c
+ *   unknown = 0x00000086
+ *   lang = "en"
+ *   country = "us"
+ *   unknown4a = 0x01
+ *
+ * Latest WinAIM that libfaim can emulate without server-side buddylists:
+ *   clientstring = "AOL Instant Messenger (SM), version 4.1.2010/WIN32"
+ *   clientid = 0x0004
+ *   major  = 0x0004
+ *   minor  = 0x0001
+ *   point = 0x0000
+ *   build  = 0x07da
+ *   unknown= 0x0000004b
+ *
+ * WinAIM 3.5.1670:
+ *   clientstring = "AOL Instant Messenger (SM), version 3.5.1670/WIN32"
+ *   clientid = 0x0004
+ *   major =  0x0003
+ *   minor =  0x0005
+ *   point = 0x0000
+ *   build =  0x0686
+ *   unknown =0x0000002a
+ *
+ * Java AIM 1.1.19:
+ *   clientstring = "AOL Instant Messenger (TM) version 1.1.19 for Java built 03/24/98, freeMem 215871 totalMem 1048567, i686, Linus, #2 SMP Sun Feb 11 03:41:17 UTC 2001 2.4.1-ac9, IBM Corporation, 1.1.8, 45.3, Tue Mar 27 12:09:17 PST 2001"
+ *   clientid = 0x0001
+ *   major  = 0x0001
+ *   minor  = 0x0001
+ *   point = (not sent)
+ *   build  = 0x0013
+ *   unknown= (not sent)
+ *   
+ * AIM for Linux 1.1.112:
+ *   clientstring = "AOL Instant Messenger (SM)"
+ *   clientid = 0x1d09
+ *   major  = 0x0001
+ *   minor  = 0x0001
+ *   point = 0x0001
+ *   build  = 0x0070
+ *   unknown= 0x0000008b
+ *   serverstore = 0x01
+ *
+ */
+faim_export int aim_send_login(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *password, struct client_info_s *ci, const char *key)
+{
+	aim_frame_t *fr;
+	aim_tlvlist_t *tl = NULL;
+	fu8_t digest[16];
+	aim_snacid_t snacid;
+
+	if (!ci || !sn || !password)
+		return -EINVAL;
+
+	/*
+	 * What the XORLOGIN flag _really_ means is that its an ICQ login,
+	 * which is really stupid and painful, so its not done here.
+	 *
+	 */
+	if (sess->flags & AIM_SESS_FLAGS_XORLOGIN)
+		return goddamnicq2(sess, conn, sn, password);
+
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0017, 0x0002, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0017, 0x0002, 0x0000, snacid);
+
+	aim_addtlvtochain_raw(&tl, 0x0001, strlen(sn), sn);
+
+	aim_encode_password_md5(password, key, digest);
+	aim_addtlvtochain_raw(&tl, 0x0025, 16, digest);
+
+	if (ci->clientstring)
+		aim_addtlvtochain_raw(&tl, 0x0003, strlen(ci->clientstring), ci->clientstring);
+	aim_addtlvtochain16(&tl, 0x0016, (fu16_t)ci->clientid);
+	aim_addtlvtochain16(&tl, 0x0017, (fu16_t)ci->major);
+	aim_addtlvtochain16(&tl, 0x0018, (fu16_t)ci->minor);
+	aim_addtlvtochain16(&tl, 0x0019, (fu16_t)ci->point);
+	aim_addtlvtochain16(&tl, 0x001a, (fu16_t)ci->build);
+	aim_addtlvtochain_raw(&tl, 0x000e, strlen(ci->country), ci->country);
+	aim_addtlvtochain_raw(&tl, 0x000f, strlen(ci->lang), ci->lang);
+
+	/*
+	 * If set, old-fashioned buddy lists will not work. You will need
+	 * to use SSI.
+	 */
+	if (0)
+		aim_addtlvtochain8(&tl, 0x004a, 0x01);
+
+	aim_writetlvchain(&fr->data, &tl);
+
+	aim_freetlvchain(&tl);
+	
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+faim_export int aim_encode_password_md5(const char *password, const char *key, fu8_t *digest)
+{
+	md5_state_t state;
+
+	md5_init(&state);	
+	md5_append(&state, (const md5_byte_t *)key, strlen(key));
+	md5_append(&state, (const md5_byte_t *)password, strlen(password));
+	md5_append(&state, (const md5_byte_t *)AIM_MD5_STRING, strlen(AIM_MD5_STRING));
+	md5_finish(&state, (md5_byte_t *)digest);
+
+	return 0;
+}
+
+/**
+ * aim_encode_password - Encode a password using old XOR method
+ * @password: incoming password
+ * @encoded: buffer to put encoded password
+ *
+ * This takes a const pointer to a (null terminated) string
+ * containing the unencoded password.  It also gets passed
+ * an already allocated buffer to store the encoded password.
+ * This buffer should be the exact length of the password without
+ * the null.  The encoded password buffer /is not %NULL terminated/.
+ *
+ * The encoding_table seems to be a fixed set of values.  We'll
+ * hope it doesn't change over time!  
+ *
+ * This is only used for the XOR method, not the better MD5 method.
+ *
+ */
+static int aim_encode_password(const char *password, fu8_t *encoded)
+{
+	fu8_t encoding_table[] = {
+#if 0 /* old v1 table */
+		0xf3, 0xb3, 0x6c, 0x99,
+		0x95, 0x3f, 0xac, 0xb6,
+		0xc5, 0xfa, 0x6b, 0x63,
+		0x69, 0x6c, 0xc3, 0x9f
+#else /* v2.1 table, also works for ICQ */
+		0xf3, 0x26, 0x81, 0xc4,
+		0x39, 0x86, 0xdb, 0x92,
+		0x71, 0xa3, 0xb9, 0xe6,
+		0x53, 0x7a, 0x95, 0x7c
+#endif
+	};
+	int i;
+
+	for (i = 0; i < strlen(password); i++)
+		encoded[i] = (password[i] ^ encoding_table[i]);
+
+	return 0;
+}
+
+/*
+ * This is sent back as a general response to the login command.
+ * It can be either an error or a success, depending on the
+ * precense of certain TLVs.  
+ *
+ * The client should check the value passed as errorcode. If
+ * its nonzero, there was an error.
+ *
+ */
+static int parse(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;
+	struct aim_authresp_info info;
+	int ret = 0;
+
+	memset(&info, 0, sizeof(info));
+
+	/*
+	 * Read block of TLVs.  All further data is derived
+	 * from what is parsed here.
+	 */
+	tlvlist = aim_readtlvchain(bs);
+
+	/*
+	 * No matter what, we should have a screen name.
+	 */
+	memset(sess->sn, 0, sizeof(sess->sn));
+	if (aim_gettlv(tlvlist, 0x0001, 1)) {
+		info.sn = aim_gettlv_str(tlvlist, 0x0001, 1);
+		strncpy(sess->sn, info.sn, sizeof(sess->sn));
+	}
+
+	/*
+	 * Check for an error code.  If so, we should also
+	 * have an error url.
+	 */
+	if (aim_gettlv(tlvlist, 0x0008, 1)) 
+		info.errorcode = aim_gettlv16(tlvlist, 0x0008, 1);
+	if (aim_gettlv(tlvlist, 0x0004, 1))
+		info.errorurl = aim_gettlv_str(tlvlist, 0x0004, 1);
+
+	/*
+	 * BOS server address.
+	 */
+	if (aim_gettlv(tlvlist, 0x0005, 1))
+		info.bosip = aim_gettlv_str(tlvlist, 0x0005, 1);
+
+	/*
+	 * Authorization cookie.
+	 */
+	if (aim_gettlv(tlvlist, 0x0006, 1)) {
+		aim_tlv_t *tmptlv;
+
+		tmptlv = aim_gettlv(tlvlist, 0x0006, 1);
+
+		info.cookie = tmptlv->value;
+	}
+
+	/*
+	 * The email address attached to this account
+	 *   Not available for ICQ logins.
+	 */
+	if (aim_gettlv(tlvlist, 0x0011, 1))
+		info.email = aim_gettlv_str(tlvlist, 0x0011, 1);
+
+	/*
+	 * The registration status.  (Not real sure what it means.)
+	 *   Not available for ICQ logins.
+	 *
+	 *   1 = No disclosure
+	 *   2 = Limited disclosure
+	 *   3 = Full disclosure
+	 *
+	 * This has to do with whether your email address is available
+	 * to other users or not.  AFAIK, this feature is no longer used.
+	 *
+	 */
+	if (aim_gettlv(tlvlist, 0x0013, 1))
+		info.regstatus = aim_gettlv16(tlvlist, 0x0013, 1);
+
+	if (aim_gettlv(tlvlist, 0x0040, 1))
+		info.latestbeta.build = aim_gettlv32(tlvlist, 0x0040, 1);
+	if (aim_gettlv(tlvlist, 0x0041, 1))
+		info.latestbeta.url = aim_gettlv_str(tlvlist, 0x0041, 1);
+	if (aim_gettlv(tlvlist, 0x0042, 1))
+		info.latestbeta.info = aim_gettlv_str(tlvlist, 0x0042, 1);
+	if (aim_gettlv(tlvlist, 0x0043, 1))
+		info.latestbeta.name = aim_gettlv_str(tlvlist, 0x0043, 1);
+	if (aim_gettlv(tlvlist, 0x0048, 1))
+		; /* no idea what this is */
+
+	if (aim_gettlv(tlvlist, 0x0044, 1))
+		info.latestrelease.build = aim_gettlv32(tlvlist, 0x0044, 1);
+	if (aim_gettlv(tlvlist, 0x0045, 1))
+		info.latestrelease.url = aim_gettlv_str(tlvlist, 0x0045, 1);
+	if (aim_gettlv(tlvlist, 0x0046, 1))
+		info.latestrelease.info = aim_gettlv_str(tlvlist, 0x0046, 1);
+	if (aim_gettlv(tlvlist, 0x0047, 1))
+		info.latestrelease.name = aim_gettlv_str(tlvlist, 0x0047, 1);
+	if (aim_gettlv(tlvlist, 0x0049, 1))
+		; /* no idea what this is */
+
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac ? snac->family : 0x0017, snac ? snac->subtype : 0x0003)))
+		ret = userfunc(sess, rx, &info);
+
+	free(info.sn);
+	free(info.bosip);
+	free(info.errorurl);
+	free(info.email);
+	free(info.latestrelease.name);
+	free(info.latestrelease.url);
+	free(info.latestrelease.info);
+	free(info.latestbeta.name);
+	free(info.latestbeta.url);
+	free(info.latestbeta.info);
+
+	aim_freetlvchain(&tlvlist);
+
+	return ret;
+}
+
+/*
+ * Middle handler for 0017/0007 SNACs.  Contains the auth key prefixed
+ * by only its length in a two byte word.
+ *
+ * Calls the client, which should then use the value to call aim_send_login.
+ *
+ */
+static int keyparse(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	int keylen, ret = 1;
+	aim_rxcallback_t userfunc;
+	char *keystr;
+
+	keylen = aimbs_get16(bs);
+	keystr = aimbs_getstr(bs, keylen);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, keystr);
+
+	free(keystr); 
+
+	return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+	if (snac->subtype == 0x0003)
+		return parse(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x0007)
+		return keyparse(sess, mod, rx, snac, bs);
+
+	return 0;
+}
+
+faim_internal int auth_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0x0017;
+	mod->version = 0x0000;
+	mod->flags = 0;
+	strncpy(mod->name, "auth", sizeof(mod->name));
+	mod->snachandler = snachandler;
+
+	return 0;
+}
+
Index: libfaim/bos.c
===================================================================
--- libfaim/bos.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/bos.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,162 @@
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+/* Request BOS rights (group 9, type 2) */
+faim_export int aim_bos_reqrights(aim_session_t *sess, aim_conn_t *conn)
+{
+	return aim_genericreq_n(sess, conn, 0x0009, 0x0002);
+}
+
+/* BOS Rights (group 9, type 3) */
+static int rights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_rxcallback_t userfunc;
+	aim_tlvlist_t *tlvlist;
+	fu16_t maxpermits = 0, maxdenies = 0;
+	int ret = 0;
+
+	/* 
+	 * TLVs follow 
+	 */
+	tlvlist = aim_readtlvchain(bs);
+
+	/*
+	 * TLV type 0x0001: Maximum number of buddies on permit list.
+	 */
+	if (aim_gettlv(tlvlist, 0x0001, 1))
+		maxpermits = aim_gettlv16(tlvlist, 0x0001, 1);
+
+	/*
+	 * TLV type 0x0002: Maximum number of buddies on deny list.
+	 */
+	if (aim_gettlv(tlvlist, 0x0002, 1)) 
+		maxdenies = aim_gettlv16(tlvlist, 0x0002, 1);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, maxpermits, maxdenies);
+
+	aim_freetlvchain(&tlvlist);
+
+	return ret;  
+}
+
+/* 
+ * Set group permisson mask (group 9, type 4)
+ *
+ * Normally 0x1f (all classes).
+ *
+ * The group permission mask allows you to keep users of a certain
+ * class or classes from talking to you.  The mask should be
+ * a bitwise OR of all the user classes you want to see you.
+ *
+ */
+faim_export int aim_bos_setgroupperm(aim_session_t *sess, aim_conn_t *conn, fu32_t mask)
+{
+	return aim_genericreq_l(sess, conn, 0x0009, 0x0004, &mask);
+}
+
+/*
+ * Modify permit/deny lists (group 9, types 5, 6, 7, and 8)
+ *
+ * Changes your visibility depending on changetype:
+ *
+ *  AIM_VISIBILITYCHANGE_PERMITADD: Lets provided list of names see you
+ *  AIM_VISIBILITYCHANGE_PERMIDREMOVE: Removes listed names from permit list
+ *  AIM_VISIBILITYCHANGE_DENYADD: Hides you from provided list of names
+ *  AIM_VISIBILITYCHANGE_DENYREMOVE: Lets list see you again
+ *
+ * list should be a list of 
+ * screen names in the form "Screen Name One&ScreenNameTwo&" etc.
+ *
+ * Equivelents to options in WinAIM:
+ *   - Allow all users to contact me: Send an AIM_VISIBILITYCHANGE_DENYADD
+ *      with only your name on it.
+ *   - Allow only users on my Buddy List: Send an 
+ *      AIM_VISIBILITYCHANGE_PERMITADD with the list the same as your
+ *      buddy list
+ *   - Allow only the uesrs below: Send an AIM_VISIBILITYCHANGE_PERMITADD 
+ *      with everyone listed that you want to see you.
+ *   - Block all users: Send an AIM_VISIBILITYCHANGE_PERMITADD with only 
+ *      yourself in the list
+ *   - Block the users below: Send an AIM_VISIBILITYCHANGE_DENYADD with
+ *      the list of users to be blocked
+ *
+ * XXX ye gods.
+ */
+faim_export int aim_bos_changevisibility(aim_session_t *sess, aim_conn_t *conn, int changetype, const char *denylist)
+{
+	aim_frame_t *fr;
+	int packlen = 0;
+	fu16_t subtype;
+	char *localcpy = NULL, *tmpptr = NULL;
+	int i;
+	int listcount;
+	aim_snacid_t snacid;
+
+	if (!denylist)
+		return -EINVAL;
+
+	if (changetype == AIM_VISIBILITYCHANGE_PERMITADD)
+		subtype = 0x05;
+	else if (changetype == AIM_VISIBILITYCHANGE_PERMITREMOVE)
+		subtype = 0x06;
+	else if (changetype == AIM_VISIBILITYCHANGE_DENYADD)
+		subtype = 0x07;
+	else if (changetype == AIM_VISIBILITYCHANGE_DENYREMOVE)
+		subtype = 0x08;
+	else
+		return -EINVAL;
+
+	localcpy = strdup(denylist);
+
+	listcount = aimutil_itemcnt(localcpy, '&');
+	packlen = aimutil_tokslen(localcpy, 99, '&') + listcount + 9;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, packlen))) {
+		free(localcpy);
+		return -ENOMEM;
+	}
+
+	snacid = aim_cachesnac(sess, 0x0009, subtype, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0009, subtype, 0x00, snacid);
+
+	for (i = 0; (i < (listcount - 1)) && (i < 99); i++) {
+		tmpptr = aimutil_itemidx(localcpy, i, '&');
+
+		aimbs_put8(&fr->data, strlen(tmpptr));
+		aimbs_putraw(&fr->data, tmpptr, strlen(tmpptr));
+
+		free(tmpptr);
+	}
+	free(localcpy);
+
+	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);
+
+	return 0;
+}
+
+faim_internal int bos_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0x0009;
+	mod->version = 0x0001;
+	mod->toolid = 0x0110;
+	mod->toolversion = 0x047b;
+	mod->flags = 0;
+	strncpy(mod->name, "bos", sizeof(mod->name));
+	mod->snachandler = snachandler;
+
+	return 0;
+}
+
+
Index: libfaim/buddylist.c
===================================================================
--- libfaim/buddylist.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/buddylist.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,145 @@
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+/*
+ * Oncoming Buddy notifications contain a subset of the
+ * user information structure.  Its close enough to run
+ * through aim_extractuserinfo() however.
+ *
+ * Although the offgoing notification contains no information,
+ * it is still in a format parsable by extractuserinfo.
+ *
+ */
+static int buddychange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_userinfo_t userinfo;
+	aim_rxcallback_t userfunc;
+
+	aim_extractuserinfo(sess, bs, &userinfo);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		return userfunc(sess, rx, &userinfo);
+
+	return 0;
+}
+
+static int rights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_rxcallback_t userfunc;
+	aim_tlvlist_t *tlvlist;
+	fu16_t maxbuddies = 0, maxwatchers = 0;
+	int ret = 0;
+
+	/* 
+	 * TLVs follow 
+	 */
+	tlvlist = aim_readtlvchain(bs);
+
+	/*
+	 * TLV type 0x0001: Maximum number of buddies.
+	 */
+	if (aim_gettlv(tlvlist, 0x0001, 1))
+		maxbuddies = aim_gettlv16(tlvlist, 0x0001, 1);
+
+	/*
+	 * TLV type 0x0002: Maximum number of watchers.
+	 *
+	 * Watchers are other users who have you on their buddy
+	 * list.  (This is called the "reverse list" by a certain
+	 * other IM protocol.)
+	 * 
+	 */
+	if (aim_gettlv(tlvlist, 0x0002, 1))
+		maxwatchers = aim_gettlv16(tlvlist, 0x0002, 1);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, maxbuddies, maxwatchers);
+
+	aim_freetlvchain(&tlvlist);
+
+	return ret;  
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+	if (snac->subtype == 0x0003)
+		return rights(sess, mod, rx, snac, bs);
+	else if ((snac->subtype == 0x000b) || (snac->subtype == 0x000c))
+		return buddychange(sess, mod, rx, snac, bs);
+
+	return 0;
+}
+
+faim_internal int buddylist_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0x0003;
+	mod->version = 0x0001;
+	mod->toolid = 0x0110;
+	mod->toolversion = 0x047b;
+	mod->flags = 0;
+	strncpy(mod->name, "buddylist", sizeof(mod->name));
+	mod->snachandler = snachandler;
+
+	return 0;
+}
+
+/*
+ * aim_add_buddy()
+ *
+ * Adds a single buddy to your buddy list after login.
+ *
+ * XXX this should just be an extension of setbuddylist()
+ *
+ */
+faim_export int aim_add_buddy(aim_session_t *sess, aim_conn_t *conn, const char *sn)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!sn || !strlen(sn))
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+1+strlen(sn))))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0003, 0x0004, 0x0000, sn, strlen(sn)+1);
+	aim_putsnac(&fr->data, 0x0003, 0x0004, 0x0000, snacid);
+
+	aimbs_put8(&fr->data, strlen(sn));
+	aimbs_putraw(&fr->data, sn, strlen(sn));
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * XXX generalise to support removing multiple buddies (basically, its
+ * the same as setbuddylist() but with a different snac subtype).
+ *
+ */
+faim_export int aim_remove_buddy(aim_session_t *sess, aim_conn_t *conn, const char *sn)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!sn || !strlen(sn))
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+1+strlen(sn))))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0003, 0x0005, 0x0000, sn, strlen(sn)+1);
+	aim_putsnac(&fr->data, 0x0003, 0x0005, 0x0000, snacid);
+
+	aimbs_put8(&fr->data, strlen(sn));
+	aimbs_putraw(&fr->data, sn, strlen(sn));
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
Index: libfaim/chat.c
===================================================================
--- libfaim/chat.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/chat.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,737 @@
+/*
+ * aim_chat.c
+ *
+ * Routines for the Chat service.
+ *
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h> 
+
+/* Stored in the ->priv of chat connections */
+struct chatconnpriv {
+	fu16_t exchange;
+	char *name;
+	fu16_t instance;
+};
+
+static void dumpbox(aim_session_t *sess, unsigned char *buf, int len)
+{
+	int i;
+
+	if (!sess || !buf || !len)
+		return;
+
+	faimdprintf(sess, 1, "\nDump of %d bytes at %p:", len, buf);
+
+	for (i = 0; i < len; i++) {
+		if ((i % 8) == 0)
+			faimdprintf(sess, 1, "\n\t");
+
+		faimdprintf(sess, 1, "0x%2x ", buf[i]);
+	}
+
+	faimdprintf(sess, 1, "\n\n");
+
+	return;
+}
+
+faim_internal void aim_conn_kill_chat(aim_session_t *sess, aim_conn_t *conn)
+{
+	struct chatconnpriv *ccp = (struct chatconnpriv *)conn->priv;
+
+	if (ccp)
+		free(ccp->name);
+	free(ccp);
+
+	return;
+}
+
+faim_export char *aim_chat_getname(aim_conn_t *conn)
+{
+	struct chatconnpriv *ccp;
+
+	if (!conn)
+		return NULL;
+
+	if (conn->type != AIM_CONN_TYPE_CHAT)
+		return NULL;
+
+	ccp = (struct chatconnpriv *)conn->priv;
+
+	return ccp->name;
+}
+
+/* XXX get this into conn.c -- evil!! */
+faim_export aim_conn_t *aim_chat_getconn(aim_session_t *sess, const char *name)
+{
+	aim_conn_t *cur;
+
+	for (cur = sess->connlist; cur; cur = cur->next) {
+		struct chatconnpriv *ccp = (struct chatconnpriv *)cur->priv;
+
+		if (cur->type != AIM_CONN_TYPE_CHAT)
+			continue;
+		if (!cur->priv) {
+			faimdprintf(sess, 0, "faim: chat: chat connection with no name! (fd = %d)\n", cur->fd);
+			continue;
+		}
+
+		if (strcmp(ccp->name, name) == 0)
+			break;
+	}
+
+	return cur;
+}
+
+faim_export int aim_chat_attachname(aim_conn_t *conn, fu16_t exchange, const char *roomname, fu16_t instance)
+{
+	struct chatconnpriv *ccp;
+
+	if (!conn || !roomname)
+		return -EINVAL;
+
+	if (conn->priv)
+		free(conn->priv);
+
+	if (!(ccp = malloc(sizeof(struct chatconnpriv))))
+		return -ENOMEM;
+
+	ccp->exchange = exchange;
+	ccp->name = strdup(roomname);
+	ccp->instance = instance;
+
+	conn->priv = (void *)ccp;
+
+	return 0;
+}
+
+/*
+ * Send a Chat Message.
+ *
+ * Possible flags:
+ *   AIM_CHATFLAGS_NOREFLECT   --  Unset the flag that requests messages
+ *                                 should be sent to their sender.
+ *   AIM_CHATFLAGS_AWAY        --  Mark the message as an autoresponse
+ *                                 (Note that WinAIM does not honor this,
+ *                                 and displays the message as normal.)
+ *
+ * XXX convert this to use tlvchains 
+ */
+faim_export int aim_chat_send_im(aim_session_t *sess, aim_conn_t *conn, fu16_t flags, const char *msg, int msglen)
+{   
+	int i;
+	aim_frame_t *fr;
+	aim_msgcookie_t *cookie;
+	aim_snacid_t snacid;
+	fu8_t ckstr[8];
+	aim_tlvlist_t *otl = NULL, *itl = NULL;
+
+	if (!sess || !conn || !msg || (msglen <= 0))
+		return 0;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x000e, 0x0005, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x000e, 0x0005, 0x0000, snacid);
+
+
+	/* 
+	 * Generate a random message cookie.
+	 *
+	 * XXX mkcookie should generate the cookie and cache it in one
+	 * operation to preserve uniqueness.
+	 *
+	 */
+	for (i = 0; i < sizeof(ckstr); i++)
+		aimutil_put8(ckstr+i, (fu8_t) rand());
+
+	cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_CHAT, NULL);
+	cookie->data = NULL; /* XXX store something useful here */
+
+	aim_cachecookie(sess, cookie);
+
+	for (i = 0; i < sizeof(ckstr); i++)
+		aimbs_put8(&fr->data, ckstr[i]);
+
+
+	/*
+	 * Channel ID. 
+	 */
+	aimbs_put16(&fr->data, 0x0003);
+
+
+	/*
+	 * Type 1: Flag meaning this message is destined to the room.
+	 */
+	aim_addtlvtochain_noval(&otl, 0x0001);
+
+	/*
+	 * Type 6: Reflect
+	 */
+	if (!(flags & AIM_CHATFLAGS_NOREFLECT))
+		aim_addtlvtochain_noval(&otl, 0x0006);
+
+	/*
+	 * Type 7: Autoresponse
+	 */
+	if (flags & AIM_CHATFLAGS_AWAY)
+		aim_addtlvtochain_noval(&otl, 0x0007);
+
+	/*
+	 * SubTLV: Type 1: Message
+	 */
+	aim_addtlvtochain_raw(&itl, 0x0001, strlen(msg), msg);
+
+	/*
+	 * Type 5: Message block.  Contains more TLVs.
+	 *
+	 * This could include other information... We just
+	 * put in a message TLV however.  
+	 * 
+	 */
+	aim_addtlvtochain_frozentlvlist(&otl, 0x0005, &itl);
+
+	aim_writetlvchain(&fr->data, &otl);
+	
+	aim_freetlvchain(&itl);
+	aim_freetlvchain(&otl);
+	
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+static int aim_addtlvtochain_chatroom(aim_tlvlist_t **list, fu16_t type, fu16_t exchange, const char *roomname, fu16_t instance)
+{
+	fu8_t *buf;
+	int buflen;
+	aim_bstream_t bs;
+
+	buflen = 2 + 1 + strlen(roomname) + 2;
+	
+	if (!(buf = malloc(buflen)))
+		return 0;
+
+	aim_bstream_init(&bs, buf, buflen);
+
+	aimbs_put16(&bs, exchange);
+	aimbs_put8(&bs, strlen(roomname));
+	aimbs_putraw(&bs, roomname, strlen(roomname));
+	aimbs_put16(&bs, instance);
+
+	aim_addtlvtochain_raw(list, type, aim_bstream_curpos(&bs), buf);
+
+	free(buf);
+
+	return 0;
+}
+
+/*
+ * Join a room of name roomname.  This is the first step to joining an 
+ * already created room.  It's basically a Service Request for 
+ * family 0x000e, with a little added on to specify the exchange and room 
+ * name.
+ */
+faim_export int aim_chat_join(aim_session_t *sess, aim_conn_t *conn, fu16_t exchange, const char *roomname, fu16_t instance)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	aim_tlvlist_t *tl = NULL;
+	struct chatsnacinfo csi;
+	
+	if (!sess || !conn || !roomname || !strlen(roomname))
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512)))
+		return -ENOMEM;
+
+	memset(&csi, 0, sizeof(csi));
+	csi.exchange = exchange;
+	strncpy(csi.name, roomname, sizeof(csi.name));
+	csi.instance = instance;
+
+	snacid = aim_cachesnac(sess, 0x0001, 0x0004, 0x0000, &csi, sizeof(csi));
+	aim_putsnac(&fr->data, 0x0001, 0x0004, 0x0000, snacid);
+
+	/*
+	 * Requesting service chat (0x000e)
+	 */
+	aimbs_put16(&fr->data, 0x000e);
+
+	aim_addtlvtochain_chatroom(&tl, 0x0001, exchange, roomname, instance);
+	aim_writetlvchain(&fr->data, &tl);
+	aim_freetlvchain(&tl);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0; 
+}
+
+faim_internal int aim_chat_readroominfo(aim_bstream_t *bs, struct aim_chat_roominfo *outinfo)
+{
+	int namelen;
+
+	if (!bs || !outinfo)
+		return 0;
+
+	outinfo->exchange = aimbs_get16(bs);
+	namelen = aimbs_get8(bs);
+	outinfo->name = aimbs_getstr(bs, namelen);
+	outinfo->instance = aimbs_get16(bs);
+
+	return 0;
+}
+
+faim_export int aim_chat_leaveroom(aim_session_t *sess, const char *name)
+{
+	aim_conn_t *conn;
+
+	if (!(conn = aim_chat_getconn(sess, name)))
+		return -ENOENT;
+
+	aim_conn_close(conn);
+
+	return 0;
+}
+
+/*
+ * conn must be a BOS connection!
+ */
+faim_export int aim_chat_invite(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *msg, fu16_t exchange, const char *roomname, fu16_t instance)
+{
+	int i;
+	aim_frame_t *fr;
+	aim_msgcookie_t *cookie;
+	struct aim_invite_priv *priv;
+	fu8_t ckstr[8];
+	aim_snacid_t snacid;
+	aim_tlvlist_t *otl = NULL, *itl = NULL;
+	fu8_t *hdr;
+	int hdrlen;
+	aim_bstream_t hdrbs;
+	
+	if (!sess || !conn || !sn || !msg || !roomname)
+		return -EINVAL;
+
+	if (conn->type != AIM_CONN_TYPE_BOS)
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152+strlen(sn)+strlen(roomname)+strlen(msg))))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, sn, strlen(sn)+1);
+	aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
+
+
+	/*
+	 * Cookie
+	 */
+	for (i = 0; i < sizeof(ckstr); i++)
+		aimutil_put8(ckstr, (fu8_t) rand());
+
+	/* XXX should be uncached by an unwritten 'invite accept' handler */
+	if ((priv = malloc(sizeof(struct aim_invite_priv)))) {
+		priv->sn = strdup(sn);
+		priv->roomname = strdup(roomname);
+		priv->exchange = exchange;
+		priv->instance = instance;
+	}
+
+	if ((cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_INVITE, priv)))
+		aim_cachecookie(sess, cookie);
+	else
+		free(priv);
+
+	for (i = 0; i < sizeof(ckstr); i++)
+		aimbs_put8(&fr->data, ckstr[i]);
+
+
+	/*
+	 * Channel (2)
+	 */
+	aimbs_put16(&fr->data, 0x0002);
+
+	/*
+	 * Dest sn
+	 */
+	aimbs_put8(&fr->data, strlen(sn));
+	aimbs_putraw(&fr->data, sn, strlen(sn));
+
+	/*
+	 * TLV t(0005)
+	 *
+	 * Everything else is inside this TLV.
+	 *
+	 * Sigh.  AOL was rather inconsistent right here.  So we have
+	 * to play some minor tricks.  Right inside the type 5 is some
+	 * raw data, followed by a series of TLVs.  
+	 *
+	 */
+	hdrlen = 2+8+16+6+4+4+strlen(msg)+4+2+1+strlen(roomname)+2;
+	hdr = malloc(hdrlen);
+	aim_bstream_init(&hdrbs, hdr, hdrlen);
+	
+	aimbs_put16(&hdrbs, 0x0000); /* Unknown! */
+	aimbs_putraw(&hdrbs, ckstr, sizeof(ckstr)); /* I think... */
+	aim_putcap(&hdrbs, AIM_CAPS_CHAT);
+
+	aim_addtlvtochain16(&itl, 0x000a, 0x0001);
+	aim_addtlvtochain_noval(&itl, 0x000f);
+	aim_addtlvtochain_raw(&itl, 0x000c, strlen(msg), msg);
+	aim_addtlvtochain_chatroom(&itl, 0x2711, exchange, roomname, instance);
+	aim_writetlvchain(&hdrbs, &itl);
+	
+	aim_addtlvtochain_raw(&otl, 0x0005, aim_bstream_curpos(&hdrbs), hdr);
+
+	aim_writetlvchain(&fr->data, &otl);
+
+	free(hdr);
+	aim_freetlvchain(&itl);
+	aim_freetlvchain(&otl);
+	
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * General room information.  Lots of stuff.
+ *
+ * Values I know are in here but I havent attached
+ * them to any of the 'Unknown's:
+ *	- Language (English)
+ *
+ * SNAC 000e/0002
+ */
+static int infoupdate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_userinfo_t *userinfo = NULL;
+	aim_rxcallback_t userfunc;
+	int ret = 0;
+	int usercount = 0;
+	fu8_t detaillevel = 0;
+	char *roomname = NULL;
+	struct aim_chat_roominfo roominfo;
+	fu16_t tlvcount = 0;
+	aim_tlvlist_t *tlvlist;
+	char *roomdesc = NULL;
+	fu16_t flags = 0;
+	fu32_t creationtime = 0;
+	fu16_t maxmsglen = 0, maxvisiblemsglen = 0;
+	fu16_t unknown_d2 = 0, unknown_d5 = 0;
+
+	dumpbox(sess, bs->data + bs->offset, aim_bstream_empty(bs));
+
+	aim_chat_readroominfo(bs, &roominfo);
+
+	detaillevel = aimbs_get8(bs);
+
+	if (detaillevel != 0x02) {
+		faimdprintf(sess, 0, "faim: chat_roomupdateinfo: detail level %d not supported\n", detaillevel);
+		return 1;
+	}
+
+	tlvcount = aimbs_get16(bs);
+
+	/*
+	 * Everything else are TLVs.
+	 */ 
+	tlvlist = aim_readtlvchain(bs);
+
+	/*
+	 * TLV type 0x006a is the room name in Human Readable Form.
+	 */
+	if (aim_gettlv(tlvlist, 0x006a, 1))
+		roomname = aim_gettlv_str(tlvlist, 0x006a, 1);
+
+	/*
+	 * Type 0x006f: Number of occupants.
+	 */
+	if (aim_gettlv(tlvlist, 0x006f, 1))
+		usercount = aim_gettlv16(tlvlist, 0x006f, 1);
+
+	/*
+	 * Type 0x0073:  Occupant list.
+	 */
+	if (aim_gettlv(tlvlist, 0x0073, 1)) {	
+		int curoccupant = 0;
+		aim_tlv_t *tmptlv;
+		aim_bstream_t occbs;
+
+		tmptlv = aim_gettlv(tlvlist, 0x0073, 1);
+
+		/* Allocate enough userinfo structs for all occupants */
+		userinfo = calloc(usercount, sizeof(aim_userinfo_t));
+
+		aim_bstream_init(&occbs, tmptlv->value, tmptlv->length);
+
+		while (curoccupant < usercount)
+			aim_extractuserinfo(sess, &occbs, &userinfo[curoccupant++]);
+	}
+
+	/* 
+	 * Type 0x00c9: Flags. (AIM_CHATROOM_FLAG)
+	 */
+	if (aim_gettlv(tlvlist, 0x00c9, 1))
+		flags = aim_gettlv16(tlvlist, 0x00c9, 1);
+
+	/* 
+	 * Type 0x00ca: Creation time (4 bytes)
+	 */
+	if (aim_gettlv(tlvlist, 0x00ca, 1))
+		creationtime = aim_gettlv32(tlvlist, 0x00ca, 1);
+
+	/* 
+	 * Type 0x00d1: Maximum Message Length
+	 */
+	if (aim_gettlv(tlvlist, 0x00d1, 1))
+		maxmsglen = aim_gettlv16(tlvlist, 0x00d1, 1);
+
+	/* 
+	 * Type 0x00d2: Unknown. (2 bytes)
+	 */
+	if (aim_gettlv(tlvlist, 0x00d2, 1))
+		unknown_d2 = aim_gettlv16(tlvlist, 0x00d2, 1);
+
+	/* 
+	 * Type 0x00d3: Room Description
+	 */
+	if (aim_gettlv(tlvlist, 0x00d3, 1))
+		roomdesc = aim_gettlv_str(tlvlist, 0x00d3, 1);
+
+	/*
+	 * Type 0x000d4: Unknown (flag only)
+	 */
+	if (aim_gettlv(tlvlist, 0x000d4, 1))
+		;
+
+	/* 
+	 * Type 0x00d5: Unknown. (1 byte)
+	 */
+	if (aim_gettlv(tlvlist, 0x00d5, 1))
+		unknown_d5 = aim_gettlv8(tlvlist, 0x00d5, 1);
+
+
+	/*
+	 * Type 0x00d6: Encoding 1 ("us-ascii")
+	 */
+	if (aim_gettlv(tlvlist, 0x000d6, 1))
+		;
+	
+	/*
+	 * Type 0x00d7: Language 1 ("en")
+	 */
+	if (aim_gettlv(tlvlist, 0x000d7, 1))
+		;
+
+	/*
+	 * Type 0x00d8: Encoding 2 ("us-ascii")
+	 */
+	if (aim_gettlv(tlvlist, 0x000d8, 1))
+		;
+	
+	/*
+	 * Type 0x00d9: Language 2 ("en")
+	 */
+	if (aim_gettlv(tlvlist, 0x000d9, 1))
+		;
+
+	/*
+	 * Type 0x00da: Maximum visible message length
+	 */
+	if (aim_gettlv(tlvlist, 0x000da, 1))
+		maxvisiblemsglen = aim_gettlv16(tlvlist, 0x00da, 1);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) {
+		ret = userfunc(sess,
+				rx, 
+				&roominfo,
+				roomname,
+				usercount,
+				userinfo,	
+				roomdesc,
+				flags,
+				creationtime,
+				maxmsglen,
+				unknown_d2,
+				unknown_d5,
+				maxvisiblemsglen);
+	}
+
+	free(roominfo.name);
+	free(userinfo);
+	free(roomname);
+	free(roomdesc);
+	aim_freetlvchain(&tlvlist);
+
+	return ret;
+}
+
+static int userlistchange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_userinfo_t *userinfo = NULL;
+	aim_rxcallback_t userfunc;
+	int curcount = 0, ret = 0;
+
+	while (aim_bstream_empty(bs)) {
+		curcount++;
+		userinfo = realloc(userinfo, curcount * sizeof(aim_userinfo_t));
+		aim_extractuserinfo(sess, bs, &userinfo[curcount-1]);
+	}
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, curcount, userinfo);
+
+	free(userinfo);
+
+	return ret;
+}
+
+/*
+ * We could probably include this in the normal ICBM parsing 
+ * code as channel 0x0003, however, since only the start
+ * would be the same, we might as well do it here.
+ *
+ * General outline of this SNAC:
+ *   snac
+ *   cookie
+ *   channel id
+ *   tlvlist
+ *     unknown
+ *     source user info
+ *       name
+ *       evility
+ *       userinfo tlvs
+ *         online time
+ *         etc
+ *     message metatlv
+ *       message tlv
+ *         message string
+ *       possibly others
+ *  
+ */
+static int incomingmsg(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_userinfo_t userinfo;
+	aim_rxcallback_t userfunc;	
+	int ret = 0;
+	fu8_t *cookie;
+	fu16_t channel;
+	aim_tlvlist_t *otl;
+	char *msg = NULL;
+	aim_msgcookie_t *ck;
+
+	memset(&userinfo, 0, sizeof(aim_userinfo_t));
+
+	/*
+	 * ICBM Cookie.  Uncache it.
+	 */
+	cookie = aimbs_getraw(bs, 8);
+
+	if ((ck = aim_uncachecookie(sess, cookie, AIM_COOKIETYPE_CHAT))) {
+		free(ck->data);
+		free(ck);
+	}
+
+	/*
+	 * Channel ID
+	 *
+	 * Channels 1 and 2 are implemented in the normal ICBM
+	 * parser.
+	 *
+	 * We only do channel 3 here.
+	 *
+	 */
+	channel = aimbs_get16(bs);
+
+	if (channel != 0x0003) {
+		faimdprintf(sess, 0, "faim: chat_incoming: unknown channel! (0x%04x)\n", channel);
+		return 0;
+	}
+
+	/*
+	 * Start parsing TLVs right away. 
+	 */
+	otl = aim_readtlvchain(bs);
+
+	/*
+	 * Type 0x0003: Source User Information
+	 */
+	if (aim_gettlv(otl, 0x0003, 1)) {
+		aim_tlv_t *userinfotlv;
+		aim_bstream_t tbs;
+
+		userinfotlv = aim_gettlv(otl, 0x0003, 1);
+
+		aim_bstream_init(&tbs, userinfotlv->value, userinfotlv->length);
+		aim_extractuserinfo(sess, &tbs, &userinfo);
+	}
+
+	/*
+	 * Type 0x0001: If present, it means it was a message to the 
+	 * room (as opposed to a whisper).
+	 */
+	if (aim_gettlv(otl, 0x0001, 1))
+		;
+
+	/*
+	 * Type 0x0005: Message Block.  Conains more TLVs.
+	 */
+	if (aim_gettlv(otl, 0x0005, 1)) {
+		aim_tlvlist_t *itl;
+		aim_tlv_t *msgblock;
+		aim_bstream_t tbs;
+
+		msgblock = aim_gettlv(otl, 0x0005, 1);
+		aim_bstream_init(&tbs, msgblock->value, msgblock->length);
+		itl = aim_readtlvchain(&tbs);
+
+		/* 
+		 * Type 0x0001: Message.
+		 */	
+		if (aim_gettlv(itl, 0x0001, 1))
+			msg = aim_gettlv_str(itl, 0x0001, 1);
+
+		aim_freetlvchain(&itl); 
+	}
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, &userinfo, msg);
+
+	free(cookie);
+	free(msg);
+	aim_freetlvchain(&otl);
+
+	return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+	if (snac->subtype == 0x0002)
+		return infoupdate(sess, mod, rx, snac, bs);
+	else if ((snac->subtype == 0x0003) || (snac->subtype == 0x0004))
+		return userlistchange(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x0006)
+		return incomingmsg(sess, mod, rx, snac, bs);
+
+	return 0;
+}
+
+faim_internal int chat_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0x000e;
+	mod->version = 0x0001;
+	mod->toolid = 0x0004; /* XXX this doesn't look right */
+	mod->toolversion = 0x0001; /* nor does this */
+	mod->flags = 0;
+	strncpy(mod->name, "chat", sizeof(mod->name));
+	mod->snachandler = snachandler;
+
+	return 0;
+}
+
+
Index: libfaim/chatnav.c
===================================================================
--- libfaim/chatnav.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/chatnav.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,426 @@
+/*
+ * Handle ChatNav.
+ *
+ * [The ChatNav(igation) service does various things to keep chat
+ *  alive.  It provides room information, room searching and creating, 
+ *  as well as giving users the right ("permission") to use chat.]
+ *
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+/*
+ * conn must be a chatnav connection!
+ */
+faim_export int aim_chatnav_reqrights(aim_session_t *sess, aim_conn_t *conn)
+{
+	return aim_genericreq_n_snacid(sess, conn, 0x000d, 0x0002);
+}
+
+faim_export int aim_chatnav_createroom(aim_session_t *sess, aim_conn_t *conn, const char *name, fu16_t exchange)
+{
+	static const char ck[] = {"create"};
+	static const char lang[] = {"en"};
+	static const char charset[] = {"us-ascii"};
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	aim_tlvlist_t *tl = NULL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x000d, 0x0008, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x000d, 0x0008, 0x0000, snacid);
+
+	/* exchange */
+	aimbs_put16(&fr->data, exchange);
+
+	/*
+	 * This looks to be a big hack.  You'll note that this entire
+	 * SNAC is just a room info structure, but the hard room name,
+	 * here, is set to "create".  
+	 *
+	 * Either this goes on the "list of questions concerning
+	 * why-the-hell-did-you-do-that", or this value is completly
+	 * ignored.  Without experimental evidence, but a good knowledge of
+	 * AOL style, I'm going to guess that it is the latter, and that
+	 * the value of the room name in create requests is ignored.
+	 */
+	aimbs_put8(&fr->data, strlen(ck));
+	aimbs_putraw(&fr->data, ck, strlen(ck));
+
+	/* 
+	 * instance
+	 * 
+	 * Setting this to 0xffff apparently assigns the last instance.
+	 *
+	 */
+	aimbs_put16(&fr->data, 0xffff);
+
+	/* detail level */
+	aimbs_put8(&fr->data, 0x01);
+
+	aim_addtlvtochain_raw(&tl, 0x00d3, strlen(name), name);
+	aim_addtlvtochain_raw(&tl, 0x00d6, strlen(charset), charset);
+	aim_addtlvtochain_raw(&tl, 0x00d7, strlen(lang), lang);
+
+	/* tlvcount */
+	aimbs_put16(&fr->data, aim_counttlvchain(&tl));
+	aim_writetlvchain(&fr->data, &tl);
+
+	aim_freetlvchain(&tl);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+static int parseinfo_perms(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs, aim_snac_t *snac2)
+{
+	aim_rxcallback_t userfunc;
+	int ret = 0;
+	struct aim_chat_exchangeinfo *exchanges = NULL;
+	int curexchange;
+	aim_tlv_t *exchangetlv;
+	fu8_t maxrooms = 0;
+	aim_tlvlist_t *tlvlist, *innerlist;
+
+	tlvlist = aim_readtlvchain(bs);
+
+	/* 
+	 * Type 0x0002: Maximum concurrent rooms.
+	 */ 
+	if (aim_gettlv(tlvlist, 0x0002, 1))
+		maxrooms = aim_gettlv8(tlvlist, 0x0002, 1);
+
+	/* 
+	 * Type 0x0003: Exchange information
+	 *
+	 * There can be any number of these, each one
+	 * representing another exchange.  
+	 * 
+	 */
+	for (curexchange = 0; ((exchangetlv = aim_gettlv(tlvlist, 0x0003, curexchange+1))); ) {
+		aim_bstream_t tbs;
+
+		aim_bstream_init(&tbs, exchangetlv->value, exchangetlv->length);
+
+		curexchange++;
+
+		exchanges = realloc(exchanges, curexchange * sizeof(struct aim_chat_exchangeinfo));
+
+		/* exchange number */
+		exchanges[curexchange-1].number = aimbs_get16(&tbs);
+		innerlist = aim_readtlvchain(&tbs);
+
+		/* 
+		 * Type 0x000a: Unknown.
+		 *
+		 * Usually three bytes: 0x0114 (exchange 1) or 0x010f (others).
+		 *
+		 */
+		if (aim_gettlv(innerlist, 0x000a, 1))
+			;
+
+		/* 
+		 * Type 0x000d: Unknown.
+		 */
+		if (aim_gettlv(innerlist, 0x000d, 1))
+			;
+
+		/* 
+		 * Type 0x0004: Unknown
+		 */
+		if (aim_gettlv(innerlist, 0x0004, 1))
+			;
+
+		/* 
+		 * Type 0x0002: Unknown
+		 */
+		if (aim_gettlv(innerlist, 0x0002, 1)) {
+			fu16_t classperms;
+
+			classperms = aim_gettlv16(innerlist, 0x0002, 1);
+			
+			faimdprintf(sess, 1, "faim: class permissions %x\n", classperms);
+		}
+
+		/*
+		 * Type 0x00c9: Flags
+		 *
+		 * 1 Evilable
+		 * 2 Nav Only
+		 * 4 Instancing Allowed
+		 * 8 Occupant Peek Allowed
+		 *
+		 */ 
+		if (aim_gettlv(innerlist, 0x00c9, 1))
+			exchanges[curexchange-1].flags = aim_gettlv16(innerlist, 0x00c9, 1);
+		      
+		/*
+		 * Type 0x00ca: Creation Date 
+		 */
+		if (aim_gettlv(innerlist, 0x00ca, 1))
+			;
+		      
+		/*
+		 * Type 0x00d0: Mandatory Channels?
+		 */
+		if (aim_gettlv(innerlist, 0x00d0, 1))
+			;
+
+		/*
+		 * Type 0x00d1: Maximum Message length
+		 */
+		if (aim_gettlv(innerlist, 0x00d1, 1))
+			;
+
+		/*
+		 * Type 0x00d2: Maximum Occupancy?
+		 */
+		if (aim_gettlv(innerlist, 0x00d2, 1))	
+			;
+
+		/*
+		 * Type 0x00d3: Exchange Description
+		 */
+		if (aim_gettlv(innerlist, 0x00d3, 1))	
+			exchanges[curexchange-1].name = aim_gettlv_str(innerlist, 0x00d3, 1);
+		else
+			exchanges[curexchange-1].name = NULL;
+
+		/*
+		 * Type 0x00d4: Exchange Description URL
+		 */
+		if (aim_gettlv(innerlist, 0x00d4, 1))	
+			;
+
+		/*
+		 * Type 0x00d5: Creation Permissions
+		 *
+		 * 0  Creation not allowed
+		 * 1  Room creation allowed
+		 * 2  Exchange creation allowed
+		 * 
+		 */
+		if (aim_gettlv(innerlist, 0x00d5, 1)) {
+			fu8_t createperms;
+
+			createperms = aim_gettlv8(innerlist, 0x00d5, 1);
+		}
+
+		/*
+		 * Type 0x00d6: Character Set (First Time)
+		 */	      
+		if (aim_gettlv(innerlist, 0x00d6, 1))	
+			exchanges[curexchange-1].charset1 = aim_gettlv_str(innerlist, 0x00d6, 1);
+		else
+			exchanges[curexchange-1].charset1 = NULL;
+		      
+		/*
+		 * Type 0x00d7: Language (First Time)
+		 */	      
+		if (aim_gettlv(innerlist, 0x00d7, 1))	
+			exchanges[curexchange-1].lang1 = aim_gettlv_str(innerlist, 0x00d7, 1);
+		else
+			exchanges[curexchange-1].lang1 = NULL;
+
+		/*
+		 * Type 0x00d8: Character Set (Second Time)
+		 */	      
+		if (aim_gettlv(innerlist, 0x00d8, 1))	
+			exchanges[curexchange-1].charset2 = aim_gettlv_str(innerlist, 0x00d8, 1);
+		else
+			exchanges[curexchange-1].charset2 = NULL;
+
+		/*
+		 * Type 0x00d9: Language (Second Time)
+		 */	      
+		if (aim_gettlv(innerlist, 0x00d9, 1))	
+			exchanges[curexchange-1].lang2 = aim_gettlv_str(innerlist, 0x00d9, 1);
+		else
+			exchanges[curexchange-1].lang2 = NULL;
+		      
+		/*
+		 * Type 0x00da: Unknown
+		 */
+		if (aim_gettlv(innerlist, 0x00da, 1))	
+			;
+
+		aim_freetlvchain(&innerlist);
+	}
+
+	/*
+	 * Call client.
+	 */
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, snac2->type, maxrooms, curexchange, exchanges);
+
+	for (curexchange--; curexchange >= 0; curexchange--) {
+		free(exchanges[curexchange].name);
+		free(exchanges[curexchange].charset1);
+		free(exchanges[curexchange].lang1);
+		free(exchanges[curexchange].charset2);
+		free(exchanges[curexchange].lang2);
+	}
+	free(exchanges);
+	aim_freetlvchain(&tlvlist);
+
+	return ret;
+}
+
+static int parseinfo_create(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs, aim_snac_t *snac2)
+{
+	aim_rxcallback_t userfunc;
+	aim_tlvlist_t *tlvlist, *innerlist;
+	char *ck = NULL, *fqcn = NULL, *name = NULL;
+	fu16_t exchange = 0, instance = 0, unknown = 0, flags = 0, maxmsglen = 0, maxoccupancy = 0;
+	fu32_t createtime = 0;
+	fu8_t createperms = 0, detaillevel;
+	int cklen;
+	aim_tlv_t *bigblock;
+	int ret = 0;
+	aim_bstream_t bbbs;
+
+	tlvlist = aim_readtlvchain(bs);
+
+	if (!(bigblock = aim_gettlv(tlvlist, 0x0004, 1))) {
+		faimdprintf(sess, 0, "no bigblock in top tlv in create room response\n");
+		aim_freetlvchain(&tlvlist);
+		return 0;
+	}
+
+	aim_bstream_init(&bbbs, bigblock->value, bigblock->length);
+
+	exchange = aimbs_get16(&bbbs);
+	cklen = aimbs_get8(&bbbs);
+	ck = aimbs_getstr(&bbbs, cklen);
+	instance = aimbs_get16(&bbbs);
+	detaillevel = aimbs_get8(&bbbs);
+
+	if (detaillevel != 0x02) {
+		faimdprintf(sess, 0, "unknown detaillevel in create room response (0x%02x)\n", detaillevel);
+		aim_freetlvchain(&tlvlist);
+		free(ck);
+		return 0;
+	}
+
+	unknown = aimbs_get16(&bbbs);
+
+	innerlist = aim_readtlvchain(&bbbs);
+
+	if (aim_gettlv(innerlist, 0x006a, 1))
+		fqcn = aim_gettlv_str(innerlist, 0x006a, 1);
+
+	if (aim_gettlv(innerlist, 0x00c9, 1))
+		flags = aim_gettlv16(innerlist, 0x00c9, 1);
+
+	if (aim_gettlv(innerlist, 0x00ca, 1))
+		createtime = aim_gettlv32(innerlist, 0x00ca, 1);
+
+	if (aim_gettlv(innerlist, 0x00d1, 1))
+		maxmsglen = aim_gettlv16(innerlist, 0x00d1, 1);
+
+	if (aim_gettlv(innerlist, 0x00d2, 1))
+		maxoccupancy = aim_gettlv16(innerlist, 0x00d2, 1);
+
+	if (aim_gettlv(innerlist, 0x00d3, 1))
+		name = aim_gettlv_str(innerlist, 0x00d3, 1);
+
+	if (aim_gettlv(innerlist, 0x00d5, 1))
+		createperms = aim_gettlv8(innerlist, 0x00d5, 1);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) {
+		ret = userfunc(sess, rx, snac2->type, fqcn, instance, exchange, flags, createtime, maxmsglen, maxoccupancy, createperms, unknown, name, ck);
+	}
+
+	free(ck);
+	free(name);
+	free(fqcn);
+	aim_freetlvchain(&innerlist);
+	aim_freetlvchain(&tlvlist);
+
+	return ret;
+}
+
+/*
+ * Since multiple things can trigger this callback, we must lookup the 
+ * snacid to determine the original snac subtype that was called.
+ *
+ * XXX This isn't really how this works.  But this is:  Every d/9 response
+ * has a 16bit value at the beginning. That matches to:
+ *    Short Desc = 1
+ *    Full Desc = 2
+ *    Instance Info = 4
+ *    Nav Short Desc = 8
+ *    Nav Instance Info = 16
+ * And then everything is really asynchronous.  There is no specific 
+ * attachment of a response to a create room request, for example.  Creating
+ * the room yields no different a response than requesting the room's info.
+ *
+ */
+static int parseinfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_snac_t *snac2;
+	int ret = 0;
+
+	if (!(snac2 = aim_remsnac(sess, snac->id))) {
+		faimdprintf(sess, 0, "faim: chatnav_parse_info: received response to unknown request! (%08lx)\n", snac->id);
+		return 0;
+	}
+
+	if (snac2->family != 0x000d) {
+		faimdprintf(sess, 0, "faim: chatnav_parse_info: recieved response that maps to corrupt request! (fam=%04x)\n", snac2->family);
+		return 0;
+	}
+
+	/*
+	 * We now know what the original SNAC subtype was.
+	 */
+	if (snac2->type == 0x0002) /* request chat rights */
+		ret = parseinfo_perms(sess, mod, rx, snac, bs, snac2);
+	else if (snac2->type == 0x0003) /* request exchange info */
+		faimdprintf(sess, 0, "chatnav_parse_info: resposne to exchange info\n");
+	else if (snac2->type == 0x0004) /* request room info */
+		faimdprintf(sess, 0, "chatnav_parse_info: response to room info\n");
+	else if (snac2->type == 0x0005) /* request more room info */
+		faimdprintf(sess, 0, "chatnav_parse_info: response to more room info\n");
+	else if (snac2->type == 0x0006) /* request occupant list */
+		faimdprintf(sess, 0, "chatnav_parse_info: response to occupant info\n");
+	else if (snac2->type == 0x0007) /* search for a room */
+		faimdprintf(sess, 0, "chatnav_parse_info: search results\n");
+	else if (snac2->type == 0x0008) /* create room */
+		ret = parseinfo_create(sess, mod, rx, snac, bs, snac2);
+	else
+		faimdprintf(sess, 0, "chatnav_parse_info: unknown request subtype (%04x)\n", snac2->type);
+
+	if (snac2)
+		free(snac2->data);
+	free(snac2);
+
+	return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+	if (snac->subtype == 0x0009)
+		return parseinfo(sess, mod, rx, snac, bs);
+
+	return 0;
+}
+
+faim_internal int chatnav_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0x000d;
+	mod->version = 0x0001;
+	mod->toolid = 0x0010;
+	mod->toolversion = 0x047c;
+	mod->flags = 0;
+	strncpy(mod->name, "chatnav", sizeof(mod->name));
+	mod->snachandler = snachandler;
+
+	return 0;
+}
Index: libfaim/config.h.in
===================================================================
--- libfaim/config.h.in	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/config.h.in	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,83 @@
+/* include/config.h.in.  Generated automatically from configure.in by autoheader.  */
+
+/* Define to empty if the keyword does not work.  */
+#undef const
+
+/* Define if you don't have vprintf but do have _doprnt.  */
+#undef HAVE_DOPRNT
+
+/* Define if you have the strftime function.  */
+#undef HAVE_STRFTIME
+
+/* Define if you have <sys/wait.h> that is POSIX.1 compatible.  */
+#undef HAVE_SYS_WAIT_H
+
+/* Define if you have the vprintf function.  */
+#undef HAVE_VPRINTF
+
+/* Define as __inline if that's what the C compiler calls it.  */
+#undef inline
+
+/* Define to `int' if <sys/types.h> doesn't define.  */
+#undef pid_t
+
+/* Define as the return type of signal handlers (int or void).  */
+#undef RETSIGTYPE
+
+/* Define to `unsigned' if <sys/types.h> doesn't define.  */
+#undef size_t
+
+/* Define if you have the ANSI C header files.  */
+#undef STDC_HEADERS
+
+/* Define if you can safely include both <sys/time.h> and <time.h>.  */
+#undef TIME_WITH_SYS_TIME
+
+/* Define if your <sys/time.h> declares struct tm.  */
+#undef TM_IN_SYS_TIME
+
+/* Define if you have the getaddrinfo function.  */
+#undef HAVE_GETADDRINFO
+
+/* Define if you have the gethostname function.  */
+#undef HAVE_GETHOSTNAME
+
+/* Define if you have the gettimeofday function.  */
+#undef HAVE_GETTIMEOFDAY
+
+/* Define if you have the select function.  */
+#undef HAVE_SELECT
+
+/* Define if you have the socket function.  */
+#undef HAVE_SOCKET
+
+/* Define if you have the strdup function.  */
+#undef HAVE_STRDUP
+
+/* Define if you have the strstr function.  */
+#undef HAVE_STRSTR
+
+/* Define if you have the strtol function.  */
+#undef HAVE_STRTOL
+
+/* Define if you have the uname function.  */
+#undef HAVE_UNAME
+
+/* Define if you have the <fcntl.h> header file.  */
+#undef HAVE_FCNTL_H
+
+/* Define if you have the <sys/time.h> header file.  */
+#undef HAVE_SYS_TIME_H
+
+/* Define if you have the <unistd.h> header file.  */
+#undef HAVE_UNISTD_H
+
+/* Define if you have the faim library (-lfaim).  */
+#undef HAVE_LIBFAIM
+
+/* Name of package */
+#undef PACKAGE
+
+/* Version number of package */
+#undef VERSION
+
Index: libfaim/config.log
===================================================================
--- libfaim/config.log	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/config.log	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,10 @@
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+configure:530: checking for gcc
+configure:643: checking whether the C compiler (gcc -I/var/users/kretch/source/ncurses/ncurses-5.2/include/ -L/var/users/kretch/source/ncurses/ncurses-5.2/lib/) works
+configure:659: gcc -o conftest -I/var/users/kretch/source/ncurses/ncurses-5.2/include/  -L/var/users/kretch/source/ncurses/ncurses-5.2/lib/ conftest.c  1>&5
+configure:685: checking whether the C compiler (gcc -I/var/users/kretch/source/ncurses/ncurses-5.2/include/ -L/var/users/kretch/source/ncurses/ncurses-5.2/lib/) is a cross-compiler
+configure:690: checking whether we are using GNU C
+configure:718: checking whether gcc accepts -g
+configure:755: checking for ranlib
Index: libfaim/config.status
===================================================================
--- libfaim/config.status	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/config.status	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,151 @@
+#! /bin/sh
+# Generated automatically by configure.
+# Run this file to recreate the current configuration.
+# This directory was configured as follows,
+# on host mission-control.mit.edu:
+#
+# ./configure  --cache-file=.././config.cache --srcdir=.
+#
+# Compiler output produced by configure, useful for debugging
+# configure, is in ./config.log if it exists.
+
+ac_cs_usage="Usage: ./config.status [--recheck] [--version] [--help]"
+for ac_option
+do
+  case "$ac_option" in
+  -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+    echo "running ${CONFIG_SHELL-/bin/sh} ./configure  --cache-file=.././config.cache --srcdir=. --no-create --no-recursion"
+    exec ${CONFIG_SHELL-/bin/sh} ./configure  --cache-file=.././config.cache --srcdir=. --no-create --no-recursion ;;
+  -version | --version | --versio | --versi | --vers | --ver | --ve | --v)
+    echo "./config.status generated by autoconf version 2.13"
+    exit 0 ;;
+  -help | --help | --hel | --he | --h)
+    echo "$ac_cs_usage"; exit 0 ;;
+  *) echo "$ac_cs_usage"; exit 1 ;;
+  esac
+done
+
+ac_given_srcdir=.
+
+trap 'rm -fr Makefile conftest*; exit 1' 1 2 15
+
+# Protect against being on the right side of a sed subst in config.status.
+sed 's/%@/@@/; s/@%/@@/; s/%g$/@g/; /@g$/s/[\\&%]/\\&/g;
+ s/@@/%@/; s/@@/@%/; s/@g$/%g/' > conftest.subs <<\CEOF
+/^[ 	]*VPATH[ 	]*=[^:]*$/d
+
+s%@SHELL@%/bin/sh%g
+s%@CFLAGS@%-I/var/users/kretch/source/ncurses/ncurses-5.2/include/ -Wall%g
+s%@CPPFLAGS@%%g
+s%@CXXFLAGS@%%g
+s%@FFLAGS@%%g
+s%@DEFS@% %g
+s%@LDFLAGS@%-L/var/users/kretch/source/ncurses/ncurses-5.2/lib/%g
+s%@LIBS@%%g
+s%@exec_prefix@%${prefix}%g
+s%@prefix@%/usr/local%g
+s%@program_transform_name@%s,x,x,%g
+s%@bindir@%${exec_prefix}/bin%g
+s%@sbindir@%${exec_prefix}/sbin%g
+s%@libexecdir@%${exec_prefix}/libexec%g
+s%@datadir@%${prefix}/share%g
+s%@sysconfdir@%${prefix}/etc%g
+s%@sharedstatedir@%${prefix}/com%g
+s%@localstatedir@%${prefix}/var%g
+s%@libdir@%${exec_prefix}/lib%g
+s%@includedir@%${prefix}/include%g
+s%@oldincludedir@%/usr/include%g
+s%@infodir@%${prefix}/info%g
+s%@mandir@%${prefix}/man%g
+s%@CC@%gcc%g
+s%@RANLIB@%ranlib%g
+
+CEOF
+
+# Split the substitutions into bite-sized pieces for seds with
+# small command number limits, like on Digital OSF/1 and HP-UX.
+ac_max_sed_cmds=90 # Maximum number of lines to put in a sed script.
+ac_file=1 # Number of current file.
+ac_beg=1 # First line for current file.
+ac_end=$ac_max_sed_cmds # Line after last line for current file.
+ac_more_lines=:
+ac_sed_cmds=""
+while $ac_more_lines; do
+  if test $ac_beg -gt 1; then
+    sed "1,${ac_beg}d; ${ac_end}q" conftest.subs > conftest.s$ac_file
+  else
+    sed "${ac_end}q" conftest.subs > conftest.s$ac_file
+  fi
+  if test ! -s conftest.s$ac_file; then
+    ac_more_lines=false
+    rm -f conftest.s$ac_file
+  else
+    if test -z "$ac_sed_cmds"; then
+      ac_sed_cmds="sed -f conftest.s$ac_file"
+    else
+      ac_sed_cmds="$ac_sed_cmds | sed -f conftest.s$ac_file"
+    fi
+    ac_file=`expr $ac_file + 1`
+    ac_beg=$ac_end
+    ac_end=`expr $ac_end + $ac_max_sed_cmds`
+  fi
+done
+if test -z "$ac_sed_cmds"; then
+  ac_sed_cmds=cat
+fi
+
+CONFIG_FILES=${CONFIG_FILES-"Makefile"}
+for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then
+  # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+  case "$ac_file" in
+  *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'`
+       ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
+  *) ac_file_in="${ac_file}.in" ;;
+  esac
+
+  # Adjust a relative srcdir, top_srcdir, and INSTALL for subdirectories.
+
+  # Remove last slash and all that follows it.  Not all systems have dirname.
+  ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'`
+  if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then
+    # The file is in a subdirectory.
+    test ! -d "$ac_dir" && mkdir "$ac_dir"
+    ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`"
+    # A "../" for each directory in $ac_dir_suffix.
+    ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'`
+  else
+    ac_dir_suffix= ac_dots=
+  fi
+
+  case "$ac_given_srcdir" in
+  .)  srcdir=.
+      if test -z "$ac_dots"; then top_srcdir=.
+      else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;;
+  /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;;
+  *) # Relative path.
+    srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix"
+    top_srcdir="$ac_dots$ac_given_srcdir" ;;
+  esac
+
+
+  echo creating "$ac_file"
+  rm -f "$ac_file"
+  configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure."
+  case "$ac_file" in
+  *Makefile*) ac_comsub="1i\\
+# $configure_input" ;;
+  *) ac_comsub= ;;
+  esac
+
+  ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"`
+  sed -e "$ac_comsub
+s%@configure_input@%$configure_input%g
+s%@srcdir@%$srcdir%g
+s%@top_srcdir@%$top_srcdir%g
+" $ac_file_inputs | (eval "$ac_sed_cmds") > $ac_file
+fi; done
+rm -f conftest.s*
+
+
+
+exit 0
Index: libfaim/configure
===================================================================
--- libfaim/configure	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/configure	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,1035 @@
+#! /bin/sh
+
+# Guess values for system-dependent variables and create Makefiles.
+# Generated automatically using autoconf version 2.13 
+# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc.
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+
+# Defaults:
+ac_help=
+ac_default_prefix=/usr/local
+# Any additions from configure.in:
+
+# Initialize some variables set by options.
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+build=NONE
+cache_file=./config.cache
+exec_prefix=NONE
+host=NONE
+no_create=
+nonopt=NONE
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+target=NONE
+verbose=
+x_includes=NONE
+x_libraries=NONE
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datadir='${prefix}/share'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+libdir='${exec_prefix}/lib'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+infodir='${prefix}/info'
+mandir='${prefix}/man'
+
+# Initialize some other variables.
+subdirs=
+MFLAGS= MAKEFLAGS=
+SHELL=${CONFIG_SHELL-/bin/sh}
+# Maximum number of lines to put in a shell here document.
+ac_max_here_lines=12
+
+ac_prev=
+for ac_option
+do
+
+  # If the previous option needs an argument, assign it.
+  if test -n "$ac_prev"; then
+    eval "$ac_prev=\$ac_option"
+    ac_prev=
+    continue
+  fi
+
+  case "$ac_option" in
+  -*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;;
+  *) ac_optarg= ;;
+  esac
+
+  # Accept the important Cygnus configure options, so we can diagnose typos.
+
+  case "$ac_option" in
+
+  -bindir | --bindir | --bindi | --bind | --bin | --bi)
+    ac_prev=bindir ;;
+  -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+    bindir="$ac_optarg" ;;
+
+  -build | --build | --buil | --bui | --bu)
+    ac_prev=build ;;
+  -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+    build="$ac_optarg" ;;
+
+  -cache-file | --cache-file | --cache-fil | --cache-fi \
+  | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+    ac_prev=cache_file ;;
+  -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+  | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+    cache_file="$ac_optarg" ;;
+
+  -datadir | --datadir | --datadi | --datad | --data | --dat | --da)
+    ac_prev=datadir ;;
+  -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \
+  | --da=*)
+    datadir="$ac_optarg" ;;
+
+  -disable-* | --disable-*)
+    ac_feature=`echo $ac_option|sed -e 's/-*disable-//'`
+    # Reject names that are not valid shell variable names.
+    if test -n "`echo $ac_feature| sed 's/[-a-zA-Z0-9_]//g'`"; then
+      { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; }
+    fi
+    ac_feature=`echo $ac_feature| sed 's/-/_/g'`
+    eval "enable_${ac_feature}=no" ;;
+
+  -enable-* | --enable-*)
+    ac_feature=`echo $ac_option|sed -e 's/-*enable-//' -e 's/=.*//'`
+    # Reject names that are not valid shell variable names.
+    if test -n "`echo $ac_feature| sed 's/[-_a-zA-Z0-9]//g'`"; then
+      { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; }
+    fi
+    ac_feature=`echo $ac_feature| sed 's/-/_/g'`
+    case "$ac_option" in
+      *=*) ;;
+      *) ac_optarg=yes ;;
+    esac
+    eval "enable_${ac_feature}='$ac_optarg'" ;;
+
+  -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+  | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+  | --exec | --exe | --ex)
+    ac_prev=exec_prefix ;;
+  -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+  | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+  | --exec=* | --exe=* | --ex=*)
+    exec_prefix="$ac_optarg" ;;
+
+  -gas | --gas | --ga | --g)
+    # Obsolete; use --with-gas.
+    with_gas=yes ;;
+
+  -help | --help | --hel | --he)
+    # Omit some internal or obsolete options to make the list less imposing.
+    # This message is too long to be a string in the A/UX 3.1 sh.
+    cat << EOF
+Usage: configure [options] [host]
+Options: [defaults in brackets after descriptions]
+Configuration:
+  --cache-file=FILE       cache test results in FILE
+  --help                  print this message
+  --no-create             do not create output files
+  --quiet, --silent       do not print \`checking...' messages
+  --version               print the version of autoconf that created configure
+Directory and file names:
+  --prefix=PREFIX         install architecture-independent files in PREFIX
+                          [$ac_default_prefix]
+  --exec-prefix=EPREFIX   install architecture-dependent files in EPREFIX
+                          [same as prefix]
+  --bindir=DIR            user executables in DIR [EPREFIX/bin]
+  --sbindir=DIR           system admin executables in DIR [EPREFIX/sbin]
+  --libexecdir=DIR        program executables in DIR [EPREFIX/libexec]
+  --datadir=DIR           read-only architecture-independent data in DIR
+                          [PREFIX/share]
+  --sysconfdir=DIR        read-only single-machine data in DIR [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data in DIR
+                          [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data in DIR [PREFIX/var]
+  --libdir=DIR            object code libraries in DIR [EPREFIX/lib]
+  --includedir=DIR        C header files in DIR [PREFIX/include]
+  --oldincludedir=DIR     C header files for non-gcc in DIR [/usr/include]
+  --infodir=DIR           info documentation in DIR [PREFIX/info]
+  --mandir=DIR            man documentation in DIR [PREFIX/man]
+  --srcdir=DIR            find the sources in DIR [configure dir or ..]
+  --program-prefix=PREFIX prepend PREFIX to installed program names
+  --program-suffix=SUFFIX append SUFFIX to installed program names
+  --program-transform-name=PROGRAM
+                          run sed PROGRAM on installed program names
+EOF
+    cat << EOF
+Host type:
+  --build=BUILD           configure for building on BUILD [BUILD=HOST]
+  --host=HOST             configure for HOST [guessed]
+  --target=TARGET         configure for TARGET [TARGET=HOST]
+Features and packages:
+  --disable-FEATURE       do not include FEATURE (same as --enable-FEATURE=no)
+  --enable-FEATURE[=ARG]  include FEATURE [ARG=yes]
+  --with-PACKAGE[=ARG]    use PACKAGE [ARG=yes]
+  --without-PACKAGE       do not use PACKAGE (same as --with-PACKAGE=no)
+  --x-includes=DIR        X include files are in DIR
+  --x-libraries=DIR       X library files are in DIR
+EOF
+    if test -n "$ac_help"; then
+      echo "--enable and --with options recognized:$ac_help"
+    fi
+    exit 0 ;;
+
+  -host | --host | --hos | --ho)
+    ac_prev=host ;;
+  -host=* | --host=* | --hos=* | --ho=*)
+    host="$ac_optarg" ;;
+
+  -includedir | --includedir | --includedi | --included | --include \
+  | --includ | --inclu | --incl | --inc)
+    ac_prev=includedir ;;
+  -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+  | --includ=* | --inclu=* | --incl=* | --inc=*)
+    includedir="$ac_optarg" ;;
+
+  -infodir | --infodir | --infodi | --infod | --info | --inf)
+    ac_prev=infodir ;;
+  -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+    infodir="$ac_optarg" ;;
+
+  -libdir | --libdir | --libdi | --libd)
+    ac_prev=libdir ;;
+  -libdir=* | --libdir=* | --libdi=* | --libd=*)
+    libdir="$ac_optarg" ;;
+
+  -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+  | --libexe | --libex | --libe)
+    ac_prev=libexecdir ;;
+  -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+  | --libexe=* | --libex=* | --libe=*)
+    libexecdir="$ac_optarg" ;;
+
+  -localstatedir | --localstatedir | --localstatedi | --localstated \
+  | --localstate | --localstat | --localsta | --localst \
+  | --locals | --local | --loca | --loc | --lo)
+    ac_prev=localstatedir ;;
+  -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+  | --localstate=* | --localstat=* | --localsta=* | --localst=* \
+  | --locals=* | --local=* | --loca=* | --loc=* | --lo=*)
+    localstatedir="$ac_optarg" ;;
+
+  -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+    ac_prev=mandir ;;
+  -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+    mandir="$ac_optarg" ;;
+
+  -nfp | --nfp | --nf)
+    # Obsolete; use --without-fp.
+    with_fp=no ;;
+
+  -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+  | --no-cr | --no-c)
+    no_create=yes ;;
+
+  -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+  | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+    no_recursion=yes ;;
+
+  -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+  | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+  | --oldin | --oldi | --old | --ol | --o)
+    ac_prev=oldincludedir ;;
+  -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+  | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+  | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+    oldincludedir="$ac_optarg" ;;
+
+  -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+    ac_prev=prefix ;;
+  -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+    prefix="$ac_optarg" ;;
+
+  -program-prefix | --program-prefix | --program-prefi | --program-pref \
+  | --program-pre | --program-pr | --program-p)
+    ac_prev=program_prefix ;;
+  -program-prefix=* | --program-prefix=* | --program-prefi=* \
+  | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+    program_prefix="$ac_optarg" ;;
+
+  -program-suffix | --program-suffix | --program-suffi | --program-suff \
+  | --program-suf | --program-su | --program-s)
+    ac_prev=program_suffix ;;
+  -program-suffix=* | --program-suffix=* | --program-suffi=* \
+  | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+    program_suffix="$ac_optarg" ;;
+
+  -program-transform-name | --program-transform-name \
+  | --program-transform-nam | --program-transform-na \
+  | --program-transform-n | --program-transform- \
+  | --program-transform | --program-transfor \
+  | --program-transfo | --program-transf \
+  | --program-trans | --program-tran \
+  | --progr-tra | --program-tr | --program-t)
+    ac_prev=program_transform_name ;;
+  -program-transform-name=* | --program-transform-name=* \
+  | --program-transform-nam=* | --program-transform-na=* \
+  | --program-transform-n=* | --program-transform-=* \
+  | --program-transform=* | --program-transfor=* \
+  | --program-transfo=* | --program-transf=* \
+  | --program-trans=* | --program-tran=* \
+  | --progr-tra=* | --program-tr=* | --program-t=*)
+    program_transform_name="$ac_optarg" ;;
+
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil)
+    silent=yes ;;
+
+  -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+    ac_prev=sbindir ;;
+  -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+  | --sbi=* | --sb=*)
+    sbindir="$ac_optarg" ;;
+
+  -sharedstatedir | --sharedstatedir | --sharedstatedi \
+  | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+  | --sharedst | --shareds | --shared | --share | --shar \
+  | --sha | --sh)
+    ac_prev=sharedstatedir ;;
+  -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+  | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+  | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+  | --sha=* | --sh=*)
+    sharedstatedir="$ac_optarg" ;;
+
+  -site | --site | --sit)
+    ac_prev=site ;;
+  -site=* | --site=* | --sit=*)
+    site="$ac_optarg" ;;
+
+  -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+    ac_prev=srcdir ;;
+  -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+    srcdir="$ac_optarg" ;;
+
+  -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+  | --syscon | --sysco | --sysc | --sys | --sy)
+    ac_prev=sysconfdir ;;
+  -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+  | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+    sysconfdir="$ac_optarg" ;;
+
+  -target | --target | --targe | --targ | --tar | --ta | --t)
+    ac_prev=target ;;
+  -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+    target="$ac_optarg" ;;
+
+  -v | -verbose | --verbose | --verbos | --verbo | --verb)
+    verbose=yes ;;
+
+  -version | --version | --versio | --versi | --vers)
+    echo "configure generated by autoconf version 2.13"
+    exit 0 ;;
+
+  -with-* | --with-*)
+    ac_package=`echo $ac_option|sed -e 's/-*with-//' -e 's/=.*//'`
+    # Reject names that are not valid shell variable names.
+    if test -n "`echo $ac_package| sed 's/[-_a-zA-Z0-9]//g'`"; then
+      { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; }
+    fi
+    ac_package=`echo $ac_package| sed 's/-/_/g'`
+    case "$ac_option" in
+      *=*) ;;
+      *) ac_optarg=yes ;;
+    esac
+    eval "with_${ac_package}='$ac_optarg'" ;;
+
+  -without-* | --without-*)
+    ac_package=`echo $ac_option|sed -e 's/-*without-//'`
+    # Reject names that are not valid shell variable names.
+    if test -n "`echo $ac_package| sed 's/[-a-zA-Z0-9_]//g'`"; then
+      { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; }
+    fi
+    ac_package=`echo $ac_package| sed 's/-/_/g'`
+    eval "with_${ac_package}=no" ;;
+
+  --x)
+    # Obsolete; use --with-x.
+    with_x=yes ;;
+
+  -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+  | --x-incl | --x-inc | --x-in | --x-i)
+    ac_prev=x_includes ;;
+  -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+  | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+    x_includes="$ac_optarg" ;;
+
+  -x-libraries | --x-libraries | --x-librarie | --x-librari \
+  | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+    ac_prev=x_libraries ;;
+  -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+  | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+    x_libraries="$ac_optarg" ;;
+
+  -*) { echo "configure: error: $ac_option: invalid option; use --help to show usage" 1>&2; exit 1; }
+    ;;
+
+  *)
+    if test -n "`echo $ac_option| sed 's/[-a-z0-9.]//g'`"; then
+      echo "configure: warning: $ac_option: invalid host type" 1>&2
+    fi
+    if test "x$nonopt" != xNONE; then
+      { echo "configure: error: can only configure for one host and one target at a time" 1>&2; exit 1; }
+    fi
+    nonopt="$ac_option"
+    ;;
+
+  esac
+done
+
+if test -n "$ac_prev"; then
+  { echo "configure: error: missing argument to --`echo $ac_prev | sed 's/_/-/g'`" 1>&2; exit 1; }
+fi
+
+trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15
+
+# File descriptor usage:
+# 0 standard input
+# 1 file creation
+# 2 errors and warnings
+# 3 some systems may open it to /dev/tty
+# 4 used on the Kubota Titan
+# 6 checking for... messages and results
+# 5 compiler messages saved in config.log
+if test "$silent" = yes; then
+  exec 6>/dev/null
+else
+  exec 6>&1
+fi
+exec 5>./config.log
+
+echo "\
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+" 1>&5
+
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Also quote any args containing shell metacharacters.
+ac_configure_args=
+for ac_arg
+do
+  case "$ac_arg" in
+  -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+  | --no-cr | --no-c) ;;
+  -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+  | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) ;;
+  *" "*|*"	"*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?]*)
+  ac_configure_args="$ac_configure_args '$ac_arg'" ;;
+  *) ac_configure_args="$ac_configure_args $ac_arg" ;;
+  esac
+done
+
+# NLS nuisances.
+# Only set these to C if already set.  These must not be set unconditionally
+# because not all systems understand e.g. LANG=C (notably SCO).
+# Fixing LC_MESSAGES prevents Solaris sh from translating var values in `set'!
+# Non-C LC_CTYPE values break the ctype check.
+if test "${LANG+set}"   = set; then LANG=C;   export LANG;   fi
+if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi
+if test "${LC_MESSAGES+set}" = set; then LC_MESSAGES=C; export LC_MESSAGES; fi
+if test "${LC_CTYPE+set}"    = set; then LC_CTYPE=C;    export LC_CTYPE;    fi
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -rf conftest* confdefs.h
+# AIX cpp loses on an empty file, so make sure it contains at least a newline.
+echo > confdefs.h
+
+# A filename unique to this package, relative to the directory that
+# configure is in, which we can look for to find out if srcdir is correct.
+ac_unique_file=bos.c
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+  ac_srcdir_defaulted=yes
+  # Try the directory containing this script, then its parent.
+  ac_prog=$0
+  ac_confdir=`echo $ac_prog|sed 's%/[^/][^/]*$%%'`
+  test "x$ac_confdir" = "x$ac_prog" && ac_confdir=.
+  srcdir=$ac_confdir
+  if test ! -r $srcdir/$ac_unique_file; then
+    srcdir=..
+  fi
+else
+  ac_srcdir_defaulted=no
+fi
+if test ! -r $srcdir/$ac_unique_file; then
+  if test "$ac_srcdir_defaulted" = yes; then
+    { echo "configure: error: can not find sources in $ac_confdir or .." 1>&2; exit 1; }
+  else
+    { echo "configure: error: can not find sources in $srcdir" 1>&2; exit 1; }
+  fi
+fi
+srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'`
+
+# Prefer explicitly selected file to automatically selected ones.
+if test -z "$CONFIG_SITE"; then
+  if test "x$prefix" != xNONE; then
+    CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site"
+  else
+    CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
+  fi
+fi
+for ac_site_file in $CONFIG_SITE; do
+  if test -r "$ac_site_file"; then
+    echo "loading site script $ac_site_file"
+    . "$ac_site_file"
+  fi
+done
+
+if test -r "$cache_file"; then
+  echo "loading cache $cache_file"
+  . $cache_file
+else
+  echo "creating cache $cache_file"
+  > $cache_file
+fi
+
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+ac_exeext=
+ac_objext=o
+if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then
+  # Stardent Vistra SVR4 grep lacks -e, says ghazi@caip.rutgers.edu.
+  if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then
+    ac_n= ac_c='
+' ac_t='	'
+  else
+    ac_n=-n ac_c= ac_t=
+  fi
+else
+  ac_n= ac_c='\c' ac_t=
+fi
+
+
+
+
+# Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:530: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  IFS="${IFS= 	}"; ac_save_ifs="$IFS"; IFS=":"
+  ac_dummy="$PATH"
+  for ac_dir in $ac_dummy; do
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/$ac_word; then
+      ac_cv_prog_CC="gcc"
+      break
+    fi
+  done
+  IFS="$ac_save_ifs"
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+  echo "$ac_t""$CC" 1>&6
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+if test -z "$CC"; then
+  # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:560: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  IFS="${IFS= 	}"; ac_save_ifs="$IFS"; IFS=":"
+  ac_prog_rejected=no
+  ac_dummy="$PATH"
+  for ac_dir in $ac_dummy; do
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/$ac_word; then
+      if test "$ac_dir/$ac_word" = "/usr/ucb/cc"; then
+        ac_prog_rejected=yes
+	continue
+      fi
+      ac_cv_prog_CC="cc"
+      break
+    fi
+  done
+  IFS="$ac_save_ifs"
+if test $ac_prog_rejected = yes; then
+  # We found a bogon in the path, so make sure we never use it.
+  set dummy $ac_cv_prog_CC
+  shift
+  if test $# -gt 0; then
+    # We chose a different compiler from the bogus one.
+    # However, it has the same basename, so the bogon will be chosen
+    # first if we set CC to just the basename; use the full file name.
+    shift
+    set dummy "$ac_dir/$ac_word" "$@"
+    shift
+    ac_cv_prog_CC="$@"
+  fi
+fi
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+  echo "$ac_t""$CC" 1>&6
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+  if test -z "$CC"; then
+    case "`uname -s`" in
+    *win32* | *WIN32*)
+      # Extract the first word of "cl", so it can be a program name with args.
+set dummy cl; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:611: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  IFS="${IFS= 	}"; ac_save_ifs="$IFS"; IFS=":"
+  ac_dummy="$PATH"
+  for ac_dir in $ac_dummy; do
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/$ac_word; then
+      ac_cv_prog_CC="cl"
+      break
+    fi
+  done
+  IFS="$ac_save_ifs"
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+  echo "$ac_t""$CC" 1>&6
+else
+  echo "$ac_t""no" 1>&6
+fi
+ ;;
+    esac
+  fi
+  test -z "$CC" && { echo "configure: error: no acceptable cc found in \$PATH" 1>&2; exit 1; }
+fi
+
+echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works""... $ac_c" 1>&6
+echo "configure:643: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5
+
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+cat > conftest.$ac_ext << EOF
+
+#line 654 "configure"
+#include "confdefs.h"
+
+main(){return(0);}
+EOF
+if { (eval echo configure:659: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  ac_cv_prog_cc_works=yes
+  # If we can't run a trivial program, we are probably using a cross compiler.
+  if (./conftest; exit) 2>/dev/null; then
+    ac_cv_prog_cc_cross=no
+  else
+    ac_cv_prog_cc_cross=yes
+  fi
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  ac_cv_prog_cc_works=no
+fi
+rm -fr conftest*
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+echo "$ac_t""$ac_cv_prog_cc_works" 1>&6
+if test $ac_cv_prog_cc_works = no; then
+  { echo "configure: error: installation or configuration problem: C compiler cannot create executables." 1>&2; exit 1; }
+fi
+echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler""... $ac_c" 1>&6
+echo "configure:685: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5
+echo "$ac_t""$ac_cv_prog_cc_cross" 1>&6
+cross_compiling=$ac_cv_prog_cc_cross
+
+echo $ac_n "checking whether we are using GNU C""... $ac_c" 1>&6
+echo "configure:690: checking whether we are using GNU C" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_gcc'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.c <<EOF
+#ifdef __GNUC__
+  yes;
+#endif
+EOF
+if { ac_try='${CC-cc} -E conftest.c'; { (eval echo configure:699: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then
+  ac_cv_prog_gcc=yes
+else
+  ac_cv_prog_gcc=no
+fi
+fi
+
+echo "$ac_t""$ac_cv_prog_gcc" 1>&6
+
+if test $ac_cv_prog_gcc = yes; then
+  GCC=yes
+else
+  GCC=
+fi
+
+ac_test_CFLAGS="${CFLAGS+set}"
+ac_save_CFLAGS="$CFLAGS"
+CFLAGS=
+echo $ac_n "checking whether ${CC-cc} accepts -g""... $ac_c" 1>&6
+echo "configure:718: checking whether ${CC-cc} accepts -g" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_cc_g'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  echo 'void f(){}' > conftest.c
+if test -z "`${CC-cc} -g -c conftest.c 2>&1`"; then
+  ac_cv_prog_cc_g=yes
+else
+  ac_cv_prog_cc_g=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_prog_cc_g" 1>&6
+if test "$ac_test_CFLAGS" = set; then
+  CFLAGS="$ac_save_CFLAGS"
+elif test $ac_cv_prog_cc_g = yes; then
+  if test "$GCC" = yes; then
+    CFLAGS="-g -O2"
+  else
+    CFLAGS="-g"
+  fi
+else
+  if test "$GCC" = yes; then
+    CFLAGS="-O2"
+  else
+    CFLAGS=
+  fi
+fi
+
+if test "$GCC" = yes; then
+   CFLAGS="$CFLAGS -Wall";
+fi
+# Extract the first word of "ranlib", so it can be a program name with args.
+set dummy ranlib; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:755: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_RANLIB'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  if test -n "$RANLIB"; then
+  ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test.
+else
+  IFS="${IFS= 	}"; ac_save_ifs="$IFS"; IFS=":"
+  ac_dummy="$PATH"
+  for ac_dir in $ac_dummy; do
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/$ac_word; then
+      ac_cv_prog_RANLIB="ranlib"
+      break
+    fi
+  done
+  IFS="$ac_save_ifs"
+  test -z "$ac_cv_prog_RANLIB" && ac_cv_prog_RANLIB=":"
+fi
+fi
+RANLIB="$ac_cv_prog_RANLIB"
+if test -n "$RANLIB"; then
+  echo "$ac_t""$RANLIB" 1>&6
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+
+
+trap '' 1 2 15
+cat > confcache <<\EOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs.  It is not useful on other systems.
+# If it contains results you don't want to keep, you may remove or edit it.
+#
+# By default, configure uses ./config.cache as the cache file,
+# creating it if it does not exist already.  You can give configure
+# the --cache-file=FILE option to use a different cache file; that is
+# what configure does when it calls configure scripts in
+# subdirectories, so they share the cache.
+# Giving --cache-file=/dev/null disables caching, for debugging configure.
+# config.status only pays attention to the cache file if you give it the
+# --recheck option to rerun configure.
+#
+EOF
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, don't put newlines in cache variables' values.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(set) 2>&1 |
+  case `(ac_space=' '; set | grep ac_space) 2>&1` in
+  *ac_space=\ *)
+    # `set' does not quote correctly, so add quotes (double-quote substitution
+    # turns \\\\ into \\, and sed turns \\ into \).
+    sed -n \
+      -e "s/'/'\\\\''/g" \
+      -e "s/^\\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\\)=\\(.*\\)/\\1=\${\\1='\\2'}/p"
+    ;;
+  *)
+    # `set' quotes correctly as required by POSIX, so do not add quotes.
+    sed -n -e 's/^\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\)=\(.*\)/\1=${\1=\2}/p'
+    ;;
+  esac >> confcache
+if cmp -s $cache_file confcache; then
+  :
+else
+  if test -w $cache_file; then
+    echo "updating cache $cache_file"
+    cat confcache > $cache_file
+  else
+    echo "not updating unwritable cache $cache_file"
+  fi
+fi
+rm -f confcache
+
+trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+# Any assignment to VPATH causes Sun make to only execute
+# the first set of double-colon rules, so remove it if not needed.
+# If there is a colon in the path, we need to keep it.
+if test "x$srcdir" = x.; then
+  ac_vpsub='/^[ 	]*VPATH[ 	]*=[^:]*$/d'
+fi
+
+trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15
+
+# Transform confdefs.h into DEFS.
+# Protect against shell expansion while executing Makefile rules.
+# Protect against Makefile macro expansion.
+cat > conftest.defs <<\EOF
+s%#define \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%-D\1=\2%g
+s%[ 	`~#$^&*(){}\\|;'"<>?]%\\&%g
+s%\[%\\&%g
+s%\]%\\&%g
+s%\$%$$%g
+EOF
+DEFS=`sed -f conftest.defs confdefs.h | tr '\012' ' '`
+rm -f conftest.defs
+
+
+# Without the "./", some shells look in PATH for config.status.
+: ${CONFIG_STATUS=./config.status}
+
+echo creating $CONFIG_STATUS
+rm -f $CONFIG_STATUS
+cat > $CONFIG_STATUS <<EOF
+#! /bin/sh
+# Generated automatically by configure.
+# Run this file to recreate the current configuration.
+# This directory was configured as follows,
+# on host `(hostname || uname -n) 2>/dev/null | sed 1q`:
+#
+# $0 $ac_configure_args
+#
+# Compiler output produced by configure, useful for debugging
+# configure, is in ./config.log if it exists.
+
+ac_cs_usage="Usage: $CONFIG_STATUS [--recheck] [--version] [--help]"
+for ac_option
+do
+  case "\$ac_option" in
+  -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+    echo "running \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion"
+    exec \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion ;;
+  -version | --version | --versio | --versi | --vers | --ver | --ve | --v)
+    echo "$CONFIG_STATUS generated by autoconf version 2.13"
+    exit 0 ;;
+  -help | --help | --hel | --he | --h)
+    echo "\$ac_cs_usage"; exit 0 ;;
+  *) echo "\$ac_cs_usage"; exit 1 ;;
+  esac
+done
+
+ac_given_srcdir=$srcdir
+
+trap 'rm -fr `echo "Makefile" | sed "s/:[^ ]*//g"` conftest*; exit 1' 1 2 15
+EOF
+cat >> $CONFIG_STATUS <<EOF
+
+# Protect against being on the right side of a sed subst in config.status.
+sed 's/%@/@@/; s/@%/@@/; s/%g\$/@g/; /@g\$/s/[\\\\&%]/\\\\&/g;
+ s/@@/%@/; s/@@/@%/; s/@g\$/%g/' > conftest.subs <<\\CEOF
+$ac_vpsub
+$extrasub
+s%@SHELL@%$SHELL%g
+s%@CFLAGS@%$CFLAGS%g
+s%@CPPFLAGS@%$CPPFLAGS%g
+s%@CXXFLAGS@%$CXXFLAGS%g
+s%@FFLAGS@%$FFLAGS%g
+s%@DEFS@%$DEFS%g
+s%@LDFLAGS@%$LDFLAGS%g
+s%@LIBS@%$LIBS%g
+s%@exec_prefix@%$exec_prefix%g
+s%@prefix@%$prefix%g
+s%@program_transform_name@%$program_transform_name%g
+s%@bindir@%$bindir%g
+s%@sbindir@%$sbindir%g
+s%@libexecdir@%$libexecdir%g
+s%@datadir@%$datadir%g
+s%@sysconfdir@%$sysconfdir%g
+s%@sharedstatedir@%$sharedstatedir%g
+s%@localstatedir@%$localstatedir%g
+s%@libdir@%$libdir%g
+s%@includedir@%$includedir%g
+s%@oldincludedir@%$oldincludedir%g
+s%@infodir@%$infodir%g
+s%@mandir@%$mandir%g
+s%@CC@%$CC%g
+s%@RANLIB@%$RANLIB%g
+
+CEOF
+EOF
+
+cat >> $CONFIG_STATUS <<\EOF
+
+# Split the substitutions into bite-sized pieces for seds with
+# small command number limits, like on Digital OSF/1 and HP-UX.
+ac_max_sed_cmds=90 # Maximum number of lines to put in a sed script.
+ac_file=1 # Number of current file.
+ac_beg=1 # First line for current file.
+ac_end=$ac_max_sed_cmds # Line after last line for current file.
+ac_more_lines=:
+ac_sed_cmds=""
+while $ac_more_lines; do
+  if test $ac_beg -gt 1; then
+    sed "1,${ac_beg}d; ${ac_end}q" conftest.subs > conftest.s$ac_file
+  else
+    sed "${ac_end}q" conftest.subs > conftest.s$ac_file
+  fi
+  if test ! -s conftest.s$ac_file; then
+    ac_more_lines=false
+    rm -f conftest.s$ac_file
+  else
+    if test -z "$ac_sed_cmds"; then
+      ac_sed_cmds="sed -f conftest.s$ac_file"
+    else
+      ac_sed_cmds="$ac_sed_cmds | sed -f conftest.s$ac_file"
+    fi
+    ac_file=`expr $ac_file + 1`
+    ac_beg=$ac_end
+    ac_end=`expr $ac_end + $ac_max_sed_cmds`
+  fi
+done
+if test -z "$ac_sed_cmds"; then
+  ac_sed_cmds=cat
+fi
+EOF
+
+cat >> $CONFIG_STATUS <<EOF
+
+CONFIG_FILES=\${CONFIG_FILES-"Makefile"}
+EOF
+cat >> $CONFIG_STATUS <<\EOF
+for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then
+  # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+  case "$ac_file" in
+  *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'`
+       ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
+  *) ac_file_in="${ac_file}.in" ;;
+  esac
+
+  # Adjust a relative srcdir, top_srcdir, and INSTALL for subdirectories.
+
+  # Remove last slash and all that follows it.  Not all systems have dirname.
+  ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'`
+  if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then
+    # The file is in a subdirectory.
+    test ! -d "$ac_dir" && mkdir "$ac_dir"
+    ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`"
+    # A "../" for each directory in $ac_dir_suffix.
+    ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'`
+  else
+    ac_dir_suffix= ac_dots=
+  fi
+
+  case "$ac_given_srcdir" in
+  .)  srcdir=.
+      if test -z "$ac_dots"; then top_srcdir=.
+      else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;;
+  /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;;
+  *) # Relative path.
+    srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix"
+    top_srcdir="$ac_dots$ac_given_srcdir" ;;
+  esac
+
+
+  echo creating "$ac_file"
+  rm -f "$ac_file"
+  configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure."
+  case "$ac_file" in
+  *Makefile*) ac_comsub="1i\\
+# $configure_input" ;;
+  *) ac_comsub= ;;
+  esac
+
+  ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"`
+  sed -e "$ac_comsub
+s%@configure_input@%$configure_input%g
+s%@srcdir@%$srcdir%g
+s%@top_srcdir@%$top_srcdir%g
+" $ac_file_inputs | (eval "$ac_sed_cmds") > $ac_file
+fi; done
+rm -f conftest.s*
+
+EOF
+cat >> $CONFIG_STATUS <<EOF
+
+EOF
+cat >> $CONFIG_STATUS <<\EOF
+
+exit 0
+EOF
+chmod +x $CONFIG_STATUS
+rm -fr confdefs* $ac_clean_files
+test "$no_create" = yes || ${CONFIG_SHELL-/bin/sh} $CONFIG_STATUS || exit 1
+
Index: libfaim/configure.in
===================================================================
--- libfaim/configure.in	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/configure.in	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,13 @@
+AC_INIT(bos.c)
+
+dnl AC_CONFIG_HEADER(config.h)
+
+AC_PROG_CC
+dnl we're using GCC, enable all warnings
+if test "$GCC" = yes; then
+   CFLAGS="$CFLAGS -Wall";
+fi
+AC_PROG_RANLIB
+
+
+AC_OUTPUT(Makefile)
Index: libfaim/conn.c
===================================================================
--- libfaim/conn.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/conn.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,1077 @@
+
+/*
+ * conn.c
+ *
+ * Does all this gloriously nifty connection handling stuff...
+ *
+ */
+
+#define FAIM_INTERNAL
+#define FAIM_NEED_CONN_INTERNAL
+#include <aim.h> 
+
+#ifndef _WIN32
+#include <netdb.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+
+/*
+ * In OSCAR, every connection has a set of SNAC groups associated
+ * with it.  These are the groups that you can send over this connection
+ * without being guarenteed a "Not supported" SNAC error.  
+ *
+ * The grand theory of things says that these associations transcend 
+ * what libfaim calls "connection types" (conn->type).  You can probably
+ * see the elegance here, but since I want to revel in it for a bit, you 
+ * get to hear it all spelled out.
+ *
+ * So let us say that you have your core BOS connection running.  One
+ * of your modules has just given you a SNAC of the group 0x0004 to send
+ * you.  Maybe an IM destined for some twit in Greenland.  So you start
+ * at the top of your connection list, looking for a connection that 
+ * claims to support group 0x0004.  You find one.  Why, that neat BOS
+ * connection of yours can do that.  So you send it on its way.
+ *
+ * Now, say, that fellow from Greenland has friends and they all want to
+ * meet up with you in a lame chat room.  This has landed you a SNAC
+ * in the family 0x000e and you have to admit you're a bit lost.  You've
+ * searched your connection list for someone who wants to make your life
+ * easy and deliver this SNAC for you, but there isn't one there.
+ *
+ * Here comes the good bit.  Without even letting anyone know, particularly
+ * the module that decided to send this SNAC, and definitly not that twit
+ * in Greenland, you send out a service request.  In this request, you have
+ * marked the need for a connection supporting group 0x000e.  A few seconds
+ * later, you receive a service redirect with an IP address and a cookie in
+ * it.  Great, you say.  Now I have something to do.  Off you go, making
+ * that connection.  One of the first things you get from this new server
+ * is a message saying that indeed it does support the group you were looking
+ * for.  So you continue and send rate confirmation and all that.  
+ * 
+ * Then you remember you had that SNAC to send, and now you have a means to
+ * do it, and you do, and everyone is happy.  Except the Greenlander, who is
+ * still stuck in the bitter cold.
+ *
+ * Oh, and this is useful for building the Migration SNACs, too.  In the
+ * future, this may help convince me to implement rate limit mitigation
+ * for real.  We'll see.
+ *
+ * Just to make me look better, I'll say that I've known about this great
+ * scheme for quite some time now.  But I still haven't convinced myself
+ * to make libfaim work that way.  It would take a fair amount of effort,
+ * and probably some client API changes as well.  (Whenever I don't want
+ * to do something, I just say it would change the client API.  Then I 
+ * instantly have a couple of supporters of not doing it.)
+ *
+ * Generally, addgroup is only called by the internal handling of the
+ * server ready SNAC.  So if you want to do something before that, you'll
+ * have to be more creative.  That is done rather early, though, so I don't
+ * think you have to worry about it.  Unless you're me.  I care deeply
+ * about such inane things.
+ *
+ */
+faim_internal void aim_conn_addgroup(aim_conn_t *conn, fu16_t group)
+{
+	aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside;
+	struct snacgroup *sg;
+
+	if (!(sg = malloc(sizeof(struct snacgroup))))
+		return;
+
+	faimdprintf(aim_conn_getsess(conn), 1, "adding group 0x%04x\n", group);
+	sg->group = group;
+
+	sg->next = ins->groups;
+	ins->groups = sg;
+
+	return;
+}
+
+faim_export aim_conn_t *aim_conn_findbygroup(aim_session_t *sess, fu16_t group)
+{
+	aim_conn_t *cur;
+
+	for (cur = sess->connlist; cur; cur = cur->next) {
+		aim_conn_inside_t *ins = (aim_conn_inside_t *)cur->inside;
+		struct snacgroup *sg;
+
+		for (sg = ins->groups; sg; sg = sg->next) {
+			if (sg->group == group)
+				return cur;
+		}
+	}
+
+	return NULL;
+}
+
+static void connkill_snacgroups(struct snacgroup **head)
+{
+	struct snacgroup *sg;
+
+	for (sg = *head; sg; ) {
+		struct snacgroup *tmp;
+
+		tmp = sg->next;
+		free(sg);
+		sg = tmp;
+	}
+
+	*head = NULL;
+
+	return;
+}
+
+static void connkill_rates(struct rateclass **head)
+{
+	struct rateclass *rc;
+
+	for (rc = *head; rc; ) {
+		struct rateclass *tmp;
+		struct snacpair *sp;
+
+		tmp = rc->next;
+
+		for (sp = rc->members; sp; ) {
+			struct snacpair *tmpsp;
+
+			tmpsp = sp->next;
+			free(sp);
+			sp = tmpsp;
+		}
+		free(rc);
+
+		rc = tmp;
+	}
+
+	*head = NULL;
+
+	return;
+}
+
+static void connkill_real(aim_session_t *sess, aim_conn_t **deadconn)
+{
+
+	aim_rxqueue_cleanbyconn(sess, *deadconn);
+	aim_tx_cleanqueue(sess, *deadconn);
+
+	if ((*deadconn)->fd != -1) 
+		aim_conn_close(*deadconn);
+
+	/*
+	 * XXX ->priv should never be touched by the library. I know
+	 * it used to be, but I'm getting rid of all that.  Use
+	 * ->internal instead.
+	 */
+	if ((*deadconn)->priv)
+		free((*deadconn)->priv);
+
+	/*
+	 * This will free ->internal if it necessary...
+	 */
+	if ((*deadconn)->type == AIM_CONN_TYPE_RENDEZVOUS)
+		aim_conn_kill_rend(sess, *deadconn);
+	else if ((*deadconn)->type == AIM_CONN_TYPE_CHAT)
+		aim_conn_kill_chat(sess, *deadconn);
+
+	if ((*deadconn)->inside) {
+		aim_conn_inside_t *inside = (aim_conn_inside_t *)(*deadconn)->inside;
+
+		connkill_snacgroups(&inside->groups);
+		connkill_rates(&inside->rates);
+
+		free(inside);
+	}
+
+	free(*deadconn);
+	*deadconn = NULL;
+
+	return;
+}
+
+/**
+ * aim_connrst - Clears out connection list, killing remaining connections.
+ * @sess: Session to be cleared
+ *
+ * Clears out the connection list and kills any connections left.
+ *
+ */
+static void aim_connrst(aim_session_t *sess)
+{
+
+	if (sess->connlist) {
+		aim_conn_t *cur = sess->connlist, *tmp;
+
+		while (cur) {
+			tmp = cur->next;
+			aim_conn_close(cur);
+			connkill_real(sess, &cur);
+			cur = tmp;
+		}
+	}
+
+	sess->connlist = NULL;
+
+	return;
+}
+
+/**
+ * aim_conn_init - Reset a connection to default values.
+ * @deadconn: Connection to be reset
+ *
+ * Initializes and/or resets a connection structure.
+ *
+ */
+static void aim_conn_init(aim_conn_t *deadconn)
+{
+
+	if (!deadconn)
+		return;
+
+	deadconn->fd = -1;
+	deadconn->subtype = -1;
+	deadconn->type = -1;
+	deadconn->seqnum = 0;
+	deadconn->lastactivity = 0;
+	deadconn->forcedlatency = 0;
+	deadconn->handlerlist = NULL;
+	deadconn->priv = NULL;
+	memset(deadconn->inside, 0, sizeof(aim_conn_inside_t));
+
+	return;
+}
+
+/**
+ * aim_conn_getnext - Gets a new connection structure.
+ * @sess: Session
+ *
+ * Allocate a new empty connection structure.
+ *
+ */
+static aim_conn_t *aim_conn_getnext(aim_session_t *sess)
+{
+	aim_conn_t *newconn;
+
+	if (!(newconn = malloc(sizeof(aim_conn_t)))) 	
+		return NULL;
+	memset(newconn, 0, sizeof(aim_conn_t));
+
+	if (!(newconn->inside = malloc(sizeof(aim_conn_inside_t)))) {
+		free(newconn);
+		return NULL;
+	}
+	memset(newconn->inside, 0, sizeof(aim_conn_inside_t));
+
+	aim_conn_init(newconn);
+
+	newconn->next = sess->connlist;
+	sess->connlist = newconn;
+
+	return newconn;
+}
+
+/**
+ * aim_conn_kill - Close and free a connection.
+ * @sess: Session for the connection
+ * @deadconn: Connection to be freed
+ *
+ * Close, clear, and free a connection structure. Should never be
+ * called from within libfaim.
+ *
+ */
+faim_export void aim_conn_kill(aim_session_t *sess, aim_conn_t **deadconn)
+{
+	aim_conn_t *cur, **prev;
+
+	if (!deadconn || !*deadconn)	
+		return;
+
+	for (prev = &sess->connlist; (cur = *prev); ) {
+		if (cur == *deadconn) {
+			*prev = cur->next;
+			break;
+		}
+		prev = &cur->next;
+	}
+
+	if (!cur)
+		return; /* oops */
+
+	connkill_real(sess, &cur);
+
+	return;
+}
+
+/**
+ * aim_conn_close - Close a connection
+ * @deadconn: Connection to close
+ *
+ * Close (but not free) a connection.
+ *
+ * This leaves everything untouched except for clearing the 
+ * handler list and setting the fd to -1 (used to recognize
+ * dead connections).  It will also remove cookies if necessary.
+ *
+ */
+faim_export void aim_conn_close(aim_conn_t *deadconn)
+{
+
+	if (deadconn->fd >= 3)
+		close(deadconn->fd);
+	deadconn->fd = -1;
+	if (deadconn->handlerlist)
+		aim_clearhandlers(deadconn);
+	if (deadconn->type == AIM_CONN_TYPE_RENDEZVOUS)
+		aim_conn_close_rend((aim_session_t *)deadconn->sessv, deadconn);
+
+	return;
+}
+
+/**
+ * aim_getconn_type - Find a connection of a specific type
+ * @sess: Session to search
+ * @type: Type of connection to look for
+ *
+ * Searches for a connection of the specified type in the 
+ * specified session.  Returns the first connection of that
+ * type found.
+ *
+ * XXX except for RENDEZVOUS, all uses of this should be removed and
+ * use aim_conn_findbygroup() instead.
+ */
+faim_export aim_conn_t *aim_getconn_type(aim_session_t *sess, int type)
+{
+	aim_conn_t *cur;
+
+	for (cur = sess->connlist; cur; cur = cur->next) {
+		if ((cur->type == type) && 
+				!(cur->status & AIM_CONN_STATUS_INPROGRESS))
+			break;
+	}
+
+	return cur;
+}
+
+faim_export aim_conn_t *aim_getconn_type_all(aim_session_t *sess, int type)
+{
+	aim_conn_t *cur;
+
+	for (cur = sess->connlist; cur; cur = cur->next) {
+		if (cur->type == type)
+			break;
+	}
+
+	return cur;
+}
+
+/* If you pass -1 for the fd, you'll get what you ask for.  Gibberish. */
+faim_export aim_conn_t *aim_getconn_fd(aim_session_t *sess, int fd)
+{
+	aim_conn_t *cur;
+
+	for (cur = sess->connlist; cur; cur = cur->next) {
+		if (cur->fd == fd)
+			break;
+	}
+
+	return cur;
+}
+
+/**
+ * aim_proxyconnect - An extrememly quick and dirty SOCKS5 interface. 
+ * @sess: Session to connect
+ * @host: Host to connect to
+ * @port: Port to connect to
+ * @statusret: Return value of the connection
+ *
+ * Attempts to connect to the specified host via the configured
+ * proxy settings, if present.  If no proxy is configured for
+ * this session, the connection is done directly.
+ *
+ * XXX this is really awful.
+ *
+ */
+static int aim_proxyconnect(aim_session_t *sess, const char *host, fu16_t port, fu32_t *statusret)
+{
+	int fd = -1;
+
+	if (strlen(sess->socksproxy.server)) { /* connecting via proxy */
+		int i;
+		unsigned char buf[512];
+		struct sockaddr_in sa;
+		struct hostent *hp;
+		char *proxy;
+		unsigned short proxyport = 1080;
+
+		for(i=0;i<(int)strlen(sess->socksproxy.server);i++) {
+			if (sess->socksproxy.server[i] == ':') {
+				proxyport = atoi(&(sess->socksproxy.server[i+1]));
+				break;
+			}
+		}
+
+		proxy = (char *)malloc(i+1);
+		strncpy(proxy, sess->socksproxy.server, i);
+		proxy[i] = '\0';
+
+		if (!(hp = gethostbyname(proxy))) {
+			faimdprintf(sess, 0, "proxyconnect: unable to resolve proxy name\n");
+			*statusret = (h_errno | AIM_CONN_STATUS_RESOLVERR);
+			return -1;
+		}
+		free(proxy);
+
+		memset(&sa.sin_zero, 0, 8);
+		sa.sin_port = htons(proxyport);
+		memcpy(&sa.sin_addr, hp->h_addr, hp->h_length);
+		sa.sin_family = hp->h_addrtype;
+
+		fd = socket(hp->h_addrtype, SOCK_STREAM, 0);
+		if (connect(fd, (struct sockaddr *)&sa, sizeof(struct sockaddr_in)) < 0) {
+			faimdprintf(sess, 0, "proxyconnect: unable to connect to proxy\n");
+			close(fd);
+			return -1;
+		}
+
+		i = 0;
+		buf[0] = 0x05; /* SOCKS version 5 */
+		if (strlen(sess->socksproxy.username)) {
+			buf[1] = 0x02; /* two methods */
+			buf[2] = 0x00; /* no authentication */
+			buf[3] = 0x02; /* username/password authentication */
+			i = 4;
+		} else {
+			buf[1] = 0x01;
+			buf[2] = 0x00;
+			i = 3;
+		}
+
+		if (write(fd, buf, i) < i) {
+			*statusret = errno;
+			close(fd);
+			return -1;
+		}
+
+		if (read(fd, buf, 2) < 2) {
+			*statusret = errno;
+			close(fd);
+			return -1;
+		}
+
+		if ((buf[0] != 0x05) || (buf[1] == 0xff)) {
+			*statusret = EINVAL;
+			close(fd);
+			return -1;
+		}
+
+		/* check if we're doing username authentication */
+		if (buf[1] == 0x02) {
+			i  = aimutil_put8(buf, 0x01); /* version 1 */
+			i += aimutil_put8(buf+i, strlen(sess->socksproxy.username));
+			i += aimutil_putstr(buf+i, sess->socksproxy.username, strlen(sess->socksproxy.username));
+			i += aimutil_put8(buf+i, strlen(sess->socksproxy.password));
+			i += aimutil_putstr(buf+i, sess->socksproxy.password, strlen(sess->socksproxy.password));
+			if (write(fd, buf, i) < i) {
+				*statusret = errno;
+				close(fd);
+				return -1;
+			}
+			if (read(fd, buf, 2) < 2) {
+				*statusret = errno;
+				close(fd);
+				return -1;
+			}
+			if ((buf[0] != 0x01) || (buf[1] != 0x00)) {
+				*statusret = EINVAL;
+				close(fd);
+				return -1;
+			}
+		}
+
+		i  = aimutil_put8(buf, 0x05);
+		i += aimutil_put8(buf+i, 0x01); /* CONNECT */
+		i += aimutil_put8(buf+i, 0x00); /* reserved */
+		i += aimutil_put8(buf+i, 0x03); /* address type: host name */
+		i += aimutil_put8(buf+i, strlen(host));
+		i += aimutil_putstr(buf+i, host, strlen(host));
+		i += aimutil_put16(buf+i, port);
+
+		if (write(fd, buf, i) < i) {
+			*statusret = errno;
+			close(fd);
+			return -1;
+		}
+		if (read(fd, buf, 10) < 10) {
+			*statusret = errno;
+			close(fd);
+			return -1;
+		}
+		if ((buf[0] != 0x05) || (buf[1] != 0x00)) {
+			*statusret = EINVAL;
+			close(fd);
+			return -1;
+		}
+
+	} else { /* connecting directly */
+		struct sockaddr_in sa;
+		struct hostent *hp;
+
+		if (!(hp = gethostbyname(host))) {
+			*statusret = (h_errno | AIM_CONN_STATUS_RESOLVERR);
+			return -1;
+		}
+
+		memset(&sa, 0, sizeof(struct sockaddr_in));
+		sa.sin_port = htons(port);
+		memcpy(&sa.sin_addr, hp->h_addr, hp->h_length);
+		sa.sin_family = hp->h_addrtype;
+
+		fd = socket(hp->h_addrtype, SOCK_STREAM, 0);
+
+		if (sess->flags & AIM_SESS_FLAGS_NONBLOCKCONNECT)
+			fcntl(fd, F_SETFL, O_NONBLOCK); /* XXX save flags */
+
+		if (connect(fd, (struct sockaddr *)&sa, sizeof(struct sockaddr_in)) < 0) {
+			if (sess->flags & AIM_SESS_FLAGS_NONBLOCKCONNECT) {
+				if ((errno == EINPROGRESS) || (errno == EINTR)) {
+					if (statusret)
+						*statusret |= AIM_CONN_STATUS_INPROGRESS;
+					return fd;
+				}
+			}
+			close(fd);
+			fd = -1;
+		}
+	}
+	return fd;
+}
+
+/**
+ * aim_cloneconn - clone an aim_conn_t
+ * @sess: session containing parent
+ * @src: connection to clone
+ *
+ * A new connection is allocated, and the values are filled in
+ * appropriately. Note that this function sets the new connnection's
+ * ->priv pointer to be equal to that of its parent: only the pointer
+ * is copied, not the data it points to.
+ *
+ * This function returns a pointer to the new aim_conn_t, or %NULL on
+ * error
+ */
+faim_internal aim_conn_t *aim_cloneconn(aim_session_t *sess, aim_conn_t *src)
+{
+	aim_conn_t *conn;
+
+	if (!(conn = aim_conn_getnext(sess)))
+		return NULL;
+
+	conn->fd = src->fd;
+	conn->type = src->type;
+	conn->subtype = src->subtype;
+	conn->seqnum = src->seqnum;
+	conn->priv = src->priv;
+	conn->internal = src->internal;
+	conn->lastactivity = src->lastactivity;
+	conn->forcedlatency = src->forcedlatency;
+	conn->sessv = src->sessv;
+	aim_clonehandlers(sess, conn, src);
+
+	if (src->inside) {
+		/*
+		 * XXX should clone this section as well, but since currently
+		 * this function only gets called for some of that rendezvous
+		 * crap, and not on SNAC connections, its probably okay for
+		 * now. 
+		 *
+		 */
+	}
+
+	return conn;
+}
+
+/**
+ * aim_newconn - Open a new connection
+ * @sess: Session to create connection in
+ * @type: Type of connection to create
+ * @dest: Host to connect to (in "host:port" syntax)
+ *
+ * Opens a new connection to the specified dest host of specified
+ * type, using the proxy settings if available.  If @host is %NULL,
+ * the connection is allocated and returned, but no connection 
+ * is made.
+ *
+ * FIXME: Return errors in a more sane way.
+ *
+ */
+faim_export aim_conn_t *aim_newconn(aim_session_t *sess, int type, const char *dest)
+{
+	aim_conn_t *connstruct;
+	fu16_t port = FAIM_LOGIN_PORT;
+	char *host;
+	int i, ret;
+
+	if (!(connstruct = aim_conn_getnext(sess)))
+		return NULL;
+
+	connstruct->sessv = (void *)sess;
+	connstruct->type = type;
+
+	if (!dest) { /* just allocate a struct */
+		connstruct->fd = -1;
+		connstruct->status = 0;
+		return connstruct;
+	}
+
+	/* 
+	 * As of 23 Jul 1999, AOL now sends the port number, preceded by a 
+	 * colon, in the BOS redirect.  This fatally breaks all previous 
+	 * libfaims.  Bad, bad AOL.
+	 *
+	 * We put this here to catch every case. 
+	 *
+	 */
+
+	for(i = 0; i < (int)strlen(dest); i++) {
+		if (dest[i] == ':') {
+			port = atoi(&(dest[i+1]));
+			break;
+		}
+	}
+
+	host = (char *)malloc(i+1);
+	strncpy(host, dest, i);
+	host[i] = '\0';
+
+	if ((ret = aim_proxyconnect(sess, host, port, &connstruct->status)) < 0) {
+		connstruct->fd = -1;
+		connstruct->status = (errno | AIM_CONN_STATUS_CONNERR);
+		free(host);
+		return connstruct;
+	} else
+		connstruct->fd = ret;
+
+	free(host);
+
+	return connstruct;
+}
+
+/**
+ * aim_conngetmaxfd - Return the highest valued file discriptor in session
+ * @sess: Session to search
+ *
+ * Returns the highest valued filed descriptor of all open 
+ * connections in @sess.
+ *
+ */
+faim_export int aim_conngetmaxfd(aim_session_t *sess)
+{
+	int j;
+	aim_conn_t *cur;
+
+	for (cur = sess->connlist, j = 0; cur; cur = cur->next) {
+		if (cur->fd > j)
+			j = cur->fd;
+	}
+
+	return j;
+}
+
+/**
+ * aim_conn_in_sess - Predicate to test the precense of a connection in a sess
+ * @sess: Session to look in
+ * @conn: Connection to look for
+ *
+ * Searches @sess for the passed connection.  Returns 1 if its present,
+ * zero otherwise.
+ *
+ */
+faim_export int aim_conn_in_sess(aim_session_t *sess, aim_conn_t *conn)
+{
+	aim_conn_t *cur;
+
+	for (cur = sess->connlist; cur; cur = cur->next) {
+		if (cur == conn)
+			return 1;
+	}
+
+	return 0;
+}
+
+/**
+ * aim_select - Wait for a socket with data or timeout
+ * @sess: Session to wait on
+ * @timeout: How long to wait
+ * @status: Return status
+ *
+ * Waits for a socket with data or for timeout, whichever comes first.
+ * See select(2).
+ * 
+ * Return codes in *status:
+ *   -1  error in select() (%NULL returned)
+ *    0  no events pending (%NULL returned)
+ *    1  outgoing data pending (%NULL returned)
+ *    2  incoming data pending (connection with pending data returned)
+ *
+ */ 
+faim_export aim_conn_t *aim_select(aim_session_t *sess, struct timeval *timeout, int *status)
+{
+	aim_conn_t *cur;
+	fd_set fds, wfds;
+	int maxfd, i, haveconnecting = 0;
+
+	if (!sess->connlist) {
+		*status = -1;
+		return NULL;
+	}
+
+	FD_ZERO(&fds);
+	FD_ZERO(&wfds);
+
+	for (cur = sess->connlist, maxfd = 0; cur; cur = cur->next) {
+		if (cur->fd == -1) {
+			/* don't let invalid/dead connections sit around */
+			*status = 2;
+			return cur;
+		} else if (cur->status & AIM_CONN_STATUS_INPROGRESS) {
+			FD_SET(cur->fd, &wfds);
+
+			haveconnecting++;
+		}
+		FD_SET(cur->fd, &fds);
+		if (cur->fd > maxfd)
+			maxfd = cur->fd;
+	}
+
+	/* 
+	 * If we have data waiting to be sent, return
+	 *
+	 * We have to not do this if theres at least one
+	 * connection thats still connecting, since that connection
+	 * may have queued data and this return would prevent
+	 * the connection from ever completing!  This is a major
+	 * inadequacy of the libfaim way of doing things.  It means
+	 * that nothing can transmit as long as there's connecting
+	 * sockets. Evil.
+	 *
+	 * But its still better than having blocking connects.
+	 *
+	 */
+	if (!haveconnecting && sess->queue_outgoing) {
+		*status = 1;
+		return NULL;
+	} 
+
+	if ((i = select(maxfd+1, &fds, &wfds, NULL, timeout))>=1) {
+		for (cur = sess->connlist; cur; cur = cur->next) {
+			if ((FD_ISSET(cur->fd, &fds)) || 
+					((cur->status & AIM_CONN_STATUS_INPROGRESS) && 
+					FD_ISSET(cur->fd, &wfds))) {
+				*status = 2;
+				return cur;
+			}
+		}
+		*status = 0; /* shouldn't happen */
+	} else if ((i == -1) && (errno == EINTR)) /* treat interrupts as a timeout */
+		*status = 0;
+	else
+		*status = i; /* can be 0 or -1 */
+
+	return NULL;  /* no waiting or error, return */
+}
+
+/**
+ * aim_conn_setlatency - Set a forced latency value for connection
+ * @conn: Conn to set latency for
+ * @newval: Number of seconds to force between transmits
+ *
+ * Causes @newval seconds to be spent between transmits on a connection.
+ *
+ * This is my lame attempt at overcoming not understanding the rate
+ * limiting. 
+ *
+ * XXX: This should really be replaced with something that scales and
+ * backs off like the real rate limiting does.
+ *
+ */
+faim_export int aim_conn_setlatency(aim_conn_t *conn, int newval)
+{
+
+	if (!conn)
+		return -1;
+
+	conn->forcedlatency = newval;
+	conn->lastactivity = 0; /* reset this just to make sure */
+
+	return 0;
+}
+
+/**
+ * aim_setupproxy - Configure a proxy for this session
+ * @sess: Session to set proxy for
+ * @server: SOCKS server
+ * @username: SOCKS username
+ * @password: SOCKS password
+ *
+ * Call this with your SOCKS5 proxy server parameters before
+ * the first call to aim_newconn().  If called with all %NULL
+ * args, it will clear out a previously set proxy.  
+ *
+ * Set username and password to %NULL if not applicable.
+ *
+ */
+faim_export void aim_setupproxy(aim_session_t *sess, const char *server, const char *username, const char *password)
+{
+	/* clear out the proxy info */
+	if (!server || !strlen(server)) {
+		memset(sess->socksproxy.server, 0, sizeof(sess->socksproxy.server));
+		memset(sess->socksproxy.username, 0, sizeof(sess->socksproxy.username));
+		memset(sess->socksproxy.password, 0, sizeof(sess->socksproxy.password));
+		return;
+	}
+
+	strncpy(sess->socksproxy.server, server, sizeof(sess->socksproxy.server));
+	if (username && strlen(username)) 
+		strncpy(sess->socksproxy.username, username, sizeof(sess->socksproxy.username));
+	if (password && strlen(password))
+		strncpy(sess->socksproxy.password, password, sizeof(sess->socksproxy.password));
+
+	return;
+}
+
+static void defaultdebugcb(aim_session_t *sess, int level, const char *format, va_list va)
+{
+
+	vfprintf(stderr, format, va);
+
+	return;
+}
+
+/**
+ * aim_session_init - Initializes a session structure
+ * @sess: Session to initialize
+ * @flags: Flags to use. Any of %AIM_SESS_FLAGS %OR'd together.
+ * @debuglevel: Level of debugging output (zero is least)
+ *
+ * Sets up the initial values for a session.
+ *
+ */
+faim_export void aim_session_init(aim_session_t *sess, fu32_t flags, int debuglevel)
+{
+
+	if (!sess)
+		return;
+
+	memset(sess, 0, sizeof(aim_session_t));
+	aim_connrst(sess);
+	sess->queue_outgoing = NULL;
+	sess->queue_incoming = NULL;
+	aim_initsnachash(sess);
+	sess->msgcookies = NULL;
+	sess->snacid_next = 0x00000001;
+
+	sess->flags = 0;
+	sess->debug = debuglevel;
+	sess->debugcb = defaultdebugcb;
+
+	sess->modlistv = NULL;
+
+	/*
+	 * Default to SNAC login unless XORLOGIN is explicitly set.
+	 */
+	if (!(flags & AIM_SESS_FLAGS_XORLOGIN))
+		sess->flags |= AIM_SESS_FLAGS_SNACLOGIN;
+	sess->flags |= flags;
+
+	/*
+	 * This must always be set.  Default to the queue-based
+	 * version for back-compatibility.  
+	 */
+	aim_tx_setenqueue(sess, AIM_TX_QUEUED, NULL);
+
+
+	/*
+	 * Register all the modules for this session...
+	 */
+	aim__registermodule(sess, misc_modfirst); /* load the catch-all first */
+	aim__registermodule(sess, general_modfirst);
+	aim__registermodule(sess, locate_modfirst);
+	aim__registermodule(sess, buddylist_modfirst);
+	aim__registermodule(sess, msg_modfirst);
+	aim__registermodule(sess, adverts_modfirst);
+	aim__registermodule(sess, invite_modfirst);
+	aim__registermodule(sess, admin_modfirst);
+	aim__registermodule(sess, popups_modfirst);
+	aim__registermodule(sess, bos_modfirst);
+	aim__registermodule(sess, search_modfirst);
+	aim__registermodule(sess, stats_modfirst);
+	aim__registermodule(sess, translate_modfirst);
+	aim__registermodule(sess, chatnav_modfirst);
+	aim__registermodule(sess, chat_modfirst);
+	/* missing 0x0f - 0x12 */
+	aim__registermodule(sess, ssi_modfirst);
+	/* missing 0x14 */
+	aim__registermodule(sess, icq_modfirst);
+	/* missing 0x16 */
+	aim__registermodule(sess, auth_modfirst);
+
+	return;
+}
+
+/**
+ * aim_session_kill - Deallocate a session
+ * @sess: Session to kill
+ *
+ */
+faim_export void aim_session_kill(aim_session_t *sess)
+{
+	aim_cleansnacs(sess, -1);
+
+	aim_logoff(sess);
+
+	aim__shutdownmodules(sess);
+
+	return;
+}
+
+/**
+ * aim_setdebuggingcb - Set the function to call when outputting debugging info
+ * @sess: Session to change
+ * @cb: Function to call
+ *
+ * The function specified is called whenever faimdprintf() is used within
+ * libfaim, and the session's debugging level is greater tha nor equal to
+ * the value faimdprintf was called with.
+ *
+ */
+faim_export int aim_setdebuggingcb(aim_session_t *sess, faim_debugging_callback_t cb)
+{
+
+	if (!sess)
+		return -1;
+
+	sess->debugcb = cb;
+
+	return 0;
+}
+
+/**
+ * aim_conn_isconnecting - Determine if a connection is connecting
+ * @conn: Connection to examine
+ *
+ * Returns nonzero if the connection is in the process of
+ * connecting (or if it just completed and aim_conn_completeconnect()
+ * has yet to be called on it).
+ *
+ */
+faim_export int aim_conn_isconnecting(aim_conn_t *conn)
+{
+
+	if (!conn)
+		return 0;
+
+	return !!(conn->status & AIM_CONN_STATUS_INPROGRESS);
+}
+
+/*
+ * XXX this is nearly as ugly as proxyconnect().
+ */
+faim_export int aim_conn_completeconnect(aim_session_t *sess, aim_conn_t *conn)
+{
+	fd_set fds, wfds;
+	struct timeval tv;
+	int res, error = ETIMEDOUT;
+	aim_rxcallback_t userfunc;
+
+	if (!conn || (conn->fd == -1))
+		return -1;
+
+	if (!(conn->status & AIM_CONN_STATUS_INPROGRESS))
+		return -1;
+
+	FD_ZERO(&fds);
+	FD_SET(conn->fd, &fds);
+	FD_ZERO(&wfds);
+	FD_SET(conn->fd, &wfds);
+	tv.tv_sec = 0;
+	tv.tv_usec = 0;
+
+	if ((res = select(conn->fd+1, &fds, &wfds, NULL, &tv)) == -1) {
+		error = errno;
+		aim_conn_close(conn);
+		errno = error;
+		return -1;
+	} else if (res == 0) {
+		faimdprintf(sess, 0, "aim_conn_completeconnect: false alarm on %d\n", conn->fd);
+		return 0; /* hasn't really completed yet... */
+	} 
+
+	if (FD_ISSET(conn->fd, &fds) || FD_ISSET(conn->fd, &wfds)) {
+		int len = sizeof(error);
+
+		if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
+			error = errno;
+	}
+
+	if (error) {
+		aim_conn_close(conn);
+		errno = error;
+		return -1;
+	}
+
+	fcntl(conn->fd, F_SETFL, 0); /* XXX should restore original flags */
+
+	conn->status &= ~AIM_CONN_STATUS_INPROGRESS;
+
+	if ((userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNCOMPLETE)))
+		userfunc(sess, NULL, conn);
+
+	/* Flush out the queues if there was something waiting for this conn  */
+	aim_tx_flushqueue(sess);
+
+	return 0;
+}
+
+faim_export aim_session_t *aim_conn_getsess(aim_conn_t *conn)
+{
+
+	if (!conn)
+		return NULL;
+
+	return (aim_session_t *)conn->sessv;
+}
+
+/*
+ * aim_logoff()
+ *
+ * Closes -ALL- open connections.
+ *
+ */
+faim_export int aim_logoff(aim_session_t *sess)
+{
+
+	aim_connrst(sess);  /* in case we want to connect again */
+
+	return 0;
+
+}
+
+/*
+ * aim_flap_nop()
+ *
+ * No-op.  WinAIM 4.x sends these _every minute_ to keep
+ * the connection alive.  
+ */
+faim_export int aim_flap_nop(aim_session_t *sess, aim_conn_t *conn)
+{
+	aim_frame_t *fr;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x05, 0)))
+		return -ENOMEM;
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+
Index: libfaim/faimconfig.h
===================================================================
--- libfaim/faimconfig.h	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/faimconfig.h	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,59 @@
+/*
+ *  faimconfig.h
+ *
+ * Contains various compile-time options that apply _only_ to libfaim.
+ *
+ */
+
+#ifndef __FAIMCONFIG_H__
+#define __FAIMCONFIG_H__
+
+/*
+ * USE_SNAC_FOR_IMS is an old feature that allowed better
+ * tracking of error messages by caching SNAC IDs of outgoing
+ * ICBMs and comparing them to incoming errors.  However,
+ * its a helluvalot of overhead for something that should
+ * rarely happen.  
+ *
+ * Default: defined.  This is now defined by default
+ * because it should be stable and its not too bad.  
+ * And Josh wanted it.
+ *
+ */
+#define USE_SNAC_FOR_IMS
+
+/*
+ * Default Authorizer server name and TCP port for the OSCAR farm.  
+ *
+ * You shouldn't need to change this unless you're writing
+ * your own server. 
+ *
+ * Note that only one server is needed to start the whole
+ * AIM process.  The later server addresses come from
+ * the authorizer service.
+ *
+ * This is only here for convenience.  Its still up to
+ * the client to connect to it.
+ *
+ */
+#define FAIM_LOGIN_SERVER "login.oscar.aol.com"
+#define FAIM_LOGIN_PORT 5190
+
+/*
+ * Size of the SNAC caching hash.
+ *
+ * Default: 16
+ *
+ */
+#define FAIM_SNAC_HASH_SIZE 16
+
+/*
+ * If building on Win32, define WIN32_STATIC if you don't want
+ * to compile libfaim as a DLL (and instead link it right into
+ * your app).
+ */
+#define WIN32_STATIC
+
+#endif /* __FAIMCONFIG_H__ */
+
+
Index: libfaim/ft.c
===================================================================
--- libfaim/ft.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/ft.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,1927 @@
+/*
+ * File transfer (OFT) and DirectIM (ODC).
+ * (OSCAR File Transfer, Oscar Direct Connect(ion?)
+ */
+
+#define FAIM_INTERNAL
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <aim.h>
+
+
+#ifndef _WIN32
+#include <netdb.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/utsname.h> /* for aim_directim_initiate */
+
+#include <arpa/inet.h> /* for inet_ntoa */
+
+#endif
+
+/* TODO: 
+   o look for memory leaks.. there's going to be shitloads, i'm sure. 
+*/
+
+struct aim_directim_intdata {
+	fu8_t cookie[8];
+	char sn[MAXSNLEN+1];
+	char ip[22];
+};
+
+static int listenestablish(fu16_t portnum);
+ 
+/**
+ * aim_handlerendconnect - call this to accept OFT connections and set up the required structures
+ * @sess: the session
+ * @cur: the conn the incoming connection is on
+ *
+ * call this when you get an outstanding read on a conn with subtype
+ * AIM_CONN_SUBTYPE_RENDEZVOUS_OUT, it will clone the current
+ * &aim_conn_t and tweak things as appropriate. the new conn and the
+ * listener conn are both returned to the client in the
+ * %AIM_CB_FAM_OFT, %AIM_CB_OFT_<CLASS>INITIATE callback.
+ */
+faim_export int aim_handlerendconnect(aim_session_t *sess, aim_conn_t *cur)
+{ 
+	int acceptfd = 0;
+	struct sockaddr cliaddr;
+	int clilen = sizeof(cliaddr);
+	int ret = 0;
+	aim_conn_t *newconn;
+
+	if ((acceptfd = accept(cur->fd, &cliaddr, &clilen)) == -1)
+		return 0; /* not an error */
+
+	if (cliaddr.sa_family != AF_INET) { /* just in case IPv6 really is happening */
+		close(acceptfd);
+		aim_conn_close(cur);
+		return -1;
+	} 
+
+	if (!(newconn = aim_cloneconn(sess, cur))) {
+		close(acceptfd);
+		aim_conn_close(cur);
+		return -1;
+	}
+
+	newconn->type = AIM_CONN_TYPE_RENDEZVOUS;
+	newconn->fd = acceptfd;
+
+	if (newconn->subtype == AIM_CONN_SUBTYPE_OFT_DIRECTIM) { 
+		struct aim_directim_intdata *priv;
+		aim_rxcallback_t userfunc;
+
+		priv = (struct aim_directim_intdata *)(newconn->internal = cur->internal);
+		cur->internal = NULL;
+
+		snprintf(priv->ip, sizeof(priv->ip), "%s:%u", 
+				inet_ntoa(((struct sockaddr_in *)&cliaddr)->sin_addr), 
+				ntohs(((struct sockaddr_in *)&cliaddr)->sin_port));
+
+		if ((userfunc = aim_callhandler(sess, newconn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMINITIATE)))
+			ret = userfunc(sess, NULL, newconn, cur);
+
+	} else if (newconn->subtype == AIM_CONN_SUBTYPE_OFT_GETFILE) {
+#if 0
+		struct aim_filetransfer_priv *priv;
+		aim_rxcallback_t userfunc;
+
+
+		newconn->priv = cur->priv;
+		cur->priv = NULL;
+		priv = (struct aim_filetransfer_priv *)newconn->priv;
+
+		snprintf(priv->ip, sizeof(priv->ip), "%s:%u", inet_ntoa(((struct sockaddr_in *)&cliaddr)->sin_addr), ntohs(((struct sockaddr_in *)&cliaddr)->sin_port));
+
+		if ((userfunc = aim_callhandler(sess, newconn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILEINITIATE)))
+			ret = userfunc(sess, NULL, newconn, cur);
+#endif
+	} else { 
+		faimdprintf(sess, 1,"Got a Connection on a listener that's not Rendezvous Closing conn.\n");
+		aim_conn_close(newconn);
+		ret = -1;
+	}
+
+	return ret;
+}
+
+/**
+ * aim_send_im_direct - send IM client-to-client over established connection
+ * @sess: session to conn
+ * @conn: directim connection
+ * @msg: null-terminated string to send; if this is NULL, it will send a "typing" notice. 
+ *
+ * Call this just like you would aim_send_im, to send a directim. You
+ * _must_ have previously established the directim connection.
+ */
+faim_export int aim_send_im_direct(aim_session_t *sess, aim_conn_t *conn, const char *msg)
+{
+	struct aim_directim_intdata *intdata = (struct aim_directim_intdata *)conn->internal;
+	aim_frame_t *fr;
+	aim_bstream_t hdrbs; /* XXX this should be within aim_frame_t */
+
+	if (!sess || !conn || (conn->type != AIM_CONN_TYPE_RENDEZVOUS)) 
+		return -EINVAL; 
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x01, strlen(msg))))
+	       return -ENOMEM;	
+
+	memcpy(fr->hdr.oft.magic, "ODC2", 4);
+	
+	fr->hdr.oft.hdr2len = 0x44;
+	
+	if (!(fr->hdr.oft.hdr2 = calloc(1, fr->hdr.oft.hdr2len))) { 
+		aim_frame_destroy(fr);
+		return -ENOMEM;
+	}
+	
+	aim_bstream_init(&hdrbs, fr->hdr.oft.hdr2, fr->hdr.oft.hdr2len);
+
+	aimbs_put16(&hdrbs, 0x0006);
+	aimbs_put16(&hdrbs, 0x0000);
+	aimbs_putraw(&hdrbs, intdata->cookie, 8);
+	aimbs_put16(&hdrbs, 0x0000);
+	aimbs_put16(&hdrbs, 0x0000);
+	aimbs_put16(&hdrbs, 0x0000);
+	aimbs_put16(&hdrbs, 0x0000);
+	aimbs_put32(&hdrbs, strlen(msg));
+	aimbs_put16(&hdrbs, 0x0000);
+	aimbs_put16(&hdrbs, 0x0000);
+	aimbs_put16(&hdrbs, 0x0000);
+
+	/* flags -- 0x000e for "typing", 0x0000 for message */
+	aimbs_put16(&hdrbs, msg ? 0x0000 : 0x000e);
+
+	aimbs_put16(&hdrbs, 0x0000);
+	aimbs_put16(&hdrbs, 0x0000);
+	aimbs_putraw(&hdrbs, sess->sn, strlen(sess->sn));
+
+	aim_bstream_setpos(&hdrbs, 52); /* bleeehh */
+
+	aimbs_put8(&hdrbs, 0x00);
+	aimbs_put16(&hdrbs, 0x0000);
+	aimbs_put16(&hdrbs, 0x0000);
+	aimbs_put16(&hdrbs, 0x0000);
+	aimbs_put16(&hdrbs, 0x0000);
+	aimbs_put16(&hdrbs, 0x0000);
+	aimbs_put16(&hdrbs, 0x0000);
+	aimbs_put16(&hdrbs, 0x0000);
+
+	/* end of hdr2 */
+
+	if (msg) {
+#if 0 /* XXX this is how you send buddy icon info... */	
+		i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x0008);
+		i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x000c);
+		i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x0000);
+		i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x1466);
+		i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x0001);
+		i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x2e0f);
+		i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x393e);
+		i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0xcac8);
+#endif
+		aimbs_putraw(&fr->data, msg, strlen(msg));
+	} 
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+} 
+
+static int getlocalip(fu8_t *ip)
+{
+	struct hostent *hptr;
+	char localhost[129];
+
+	/* XXX if available, use getaddrinfo() */
+	/* XXX allow client to specify which IP to use for multihomed boxes */
+
+	if (gethostname(localhost, 128) < 0)
+		return -1;
+
+	if (!(hptr = gethostbyname(localhost)))
+		return -1;
+
+	memcpy(ip, hptr->h_addr_list[0], 4);
+
+	return 0;
+}
+
+/**
+ * aim_directim_intitiate - For those times when we want to open up the directim channel ourselves.
+ * @sess: your session,
+ * @conn: the BOS conn,
+ * @priv: a dummy priv value (we'll let it get filled in later) (if you pass a %NULL, we alloc one)
+ * @destsn: the SN to connect to.
+ *
+ */
+faim_export aim_conn_t *aim_directim_initiate(aim_session_t *sess, const char *destsn)
+{ 
+	aim_conn_t *newconn;
+	aim_msgcookie_t *cookie;
+	struct aim_directim_intdata *priv;
+	int listenfd;
+	fu16_t port = 4443;
+	fu8_t localip[4];
+	fu8_t ck[8];
+
+	if (getlocalip(localip) == -1)
+		return NULL;
+
+	if ((listenfd = listenestablish(port)) == -1)
+		return NULL;
+
+	aim_request_directim(sess, destsn, localip, port, ck);
+
+	cookie = (aim_msgcookie_t *)calloc(1, sizeof(aim_msgcookie_t));
+	memcpy(cookie->cookie, ck, 8);
+	cookie->type = AIM_COOKIETYPE_OFTIM;
+
+	/* this one is for the cookie */
+	priv = (struct aim_directim_intdata *)calloc(1, sizeof(struct aim_directim_intdata));
+
+	memcpy(priv->cookie, ck, 8);
+	strncpy(priv->sn, destsn, sizeof(priv->sn));
+	cookie->data = priv;
+	aim_cachecookie(sess, cookie);
+
+	/* XXX switch to aim_cloneconn()? */
+	if (!(newconn = aim_newconn(sess, AIM_CONN_TYPE_RENDEZVOUS_OUT, NULL))) {
+		close(listenfd);
+		return NULL;
+	}
+
+	/* this one is for the conn */
+	priv = (struct aim_directim_intdata *)calloc(1, sizeof(struct aim_directim_intdata));
+
+	memcpy(priv->cookie, ck, 8);
+	strncpy(priv->sn, destsn, sizeof(priv->sn));
+
+	newconn->fd = listenfd;
+	newconn->subtype = AIM_CONN_SUBTYPE_OFT_DIRECTIM;
+	newconn->internal = priv;
+	newconn->lastactivity = time(NULL);
+
+	faimdprintf(sess, 2,"faim: listening (fd = %d, unconnected)\n", newconn->fd);
+
+	return newconn;
+}
+
+/**
+ * aim_sendfile_intitiate - For those times when we want to send the file ourselves.
+ * @sess: your session,
+ * @conn: the BOS conn,
+ * @destsn: the SN to connect to.
+ * @filename: the name of the files you want to send
+ *
+ */
+faim_export aim_conn_t *aim_sendfile_initiate(aim_session_t *sess, const char *destsn, const char *filename, fu16_t numfiles, fu32_t totsize)
+{ 
+	aim_conn_t *newconn;
+	aim_msgcookie_t *cookie;
+	struct aim_directim_intdata *priv;
+	int listenfd;
+	fu16_t port = 4443;
+	fu8_t localip[4];
+	fu8_t ck[8];
+
+	if (getlocalip(localip) == -1)
+		return NULL;
+
+	if ((listenfd = listenestablish(port)) == -1)
+		return NULL;
+
+	aim_request_sendfile(sess, destsn, filename, numfiles, totsize, localip, port, ck);
+
+	cookie = (aim_msgcookie_t *)calloc(1, sizeof(aim_msgcookie_t));
+	memcpy(cookie->cookie, ck, 8);
+	cookie->type = AIM_COOKIETYPE_OFTIM;
+
+	/* this one is for the cookie */
+	priv = (struct aim_directim_intdata *)calloc(1, sizeof(struct aim_directim_intdata));
+
+	memcpy(priv->cookie, ck, 8);
+	strncpy(priv->sn, destsn, sizeof(priv->sn));
+	cookie->data = priv;
+	aim_cachecookie(sess, cookie);
+
+	/* XXX switch to aim_cloneconn()? */
+	if (!(newconn = aim_newconn(sess, AIM_CONN_TYPE_RENDEZVOUS_OUT, NULL))) {
+		close(listenfd);
+		return NULL;
+	}
+
+	/* this one is for the conn */
+	priv = (struct aim_directim_intdata *)calloc(1, sizeof(struct aim_directim_intdata));
+
+	memcpy(priv->cookie, ck, 8);
+	strncpy(priv->sn, destsn, sizeof(priv->sn));
+
+	newconn->fd = listenfd;
+	newconn->subtype = AIM_CONN_SUBTYPE_OFT_SENDFILE;
+	newconn->internal = priv;
+	newconn->lastactivity = time(NULL);
+
+	faimdprintf(sess, 2,"faim: listening (fd = %d, unconnected)\n", newconn->fd);
+
+	return newconn;
+}
+
+#if 0
+/**
+ * unsigned int aim_oft_listener_clean - close up old listeners
+ * @sess: session to clean up in
+ * @age: maximum age in seconds 
+ *
+ * returns number closed, -1 on error.
+ */
+faim_export unsigned int aim_oft_listener_clean(struct aim_session_t *sess, time_t age) 
+{ 
+  struct aim_conn_t *cur;
+  time_t now;
+  unsigned int hit = 0;
+  
+  if (!sess)
+    return -1;
+  now = time(NULL);
+  faim_mutex_lock(&sess->connlistlock);
+  for(cur = sess->connlist;cur; cur = cur->next)
+    if (cur->type == AIM_CONN_TYPE_RENDEZVOUS_OUT) { 
+      faim_mutex_lock(&cur->active);
+      if (cur->lastactivity < (now - age) ) { 
+	faim_mutex_unlock(&cur->active);
+	aim_conn_close(cur);
+	hit++;
+      } else 
+	faim_mutex_unlock(&cur->active);
+    } 
+  faim_mutex_unlock(&sess->connlistlock);
+  return hit;
+} 
+#endif 
+
+faim_export const char *aim_directim_getsn(aim_conn_t *conn)
+{
+	struct aim_directim_intdata *intdata;
+
+	if (!conn)
+	       return NULL;
+
+	if ((conn->type != AIM_CONN_TYPE_RENDEZVOUS) || 
+			(conn->subtype != AIM_CONN_SUBTYPE_OFT_DIRECTIM))
+	       return NULL;
+
+	if (!conn->internal)
+		return NULL;
+
+	intdata = (struct aim_directim_intdata *)conn->internal;
+
+	return intdata->sn;
+}
+
+/**
+ * aim_directim_connect - connect to buddy for directim
+ * @sess: the session to append the conn to,
+ * @sn: the SN we're connecting to
+ * @addr: address to connect to
+ *
+ * This is a wrapper for aim_newconn.
+ *
+ * If addr is NULL, the socket is not created, but the connection is 
+ * allocated and setup to connect.
+ *
+ */
+faim_export aim_conn_t *aim_directim_connect(aim_session_t *sess, const char *sn, const char *addr, const fu8_t *cookie)
+{ 
+	aim_conn_t *newconn;
+	struct aim_directim_intdata *intdata;
+
+	if (!sess || !sn)
+		return NULL;
+
+	if (!(intdata = malloc(sizeof(struct aim_directim_intdata))))
+		return NULL;
+	memset(intdata, 0, sizeof(struct aim_directim_intdata));
+
+	memcpy(intdata->cookie, cookie, 8);
+	strncpy(intdata->sn, sn, sizeof(intdata->sn));
+	if (addr)
+		strncpy(intdata->ip, addr, sizeof(intdata->ip));
+
+	/* XXX verify that non-blocking connects actually work */
+	if (!(newconn = aim_newconn(sess, AIM_CONN_TYPE_RENDEZVOUS, addr))) {
+		free(intdata);
+		return NULL;
+	}
+
+	if (!newconn) {
+		free(intdata);
+		return newconn;
+	}
+
+	newconn->subtype = AIM_CONN_SUBTYPE_OFT_DIRECTIM;
+	newconn->internal = intdata;
+
+	return newconn;
+} 
+
+/**
+ * aim_directim_getconn - find a directim conn for buddy name
+ * @sess: your session,
+ * @name: the name to get,  
+ *
+ * returns conn for directim with name, %NULL if none found. 
+ *
+ */
+faim_export aim_conn_t *aim_directim_getconn(aim_session_t *sess, const char *name)
+{
+	aim_conn_t *cur;
+
+	if (!sess || !name || !strlen(name))
+		return NULL;
+
+	for (cur = sess->connlist; cur; cur = cur->next) {
+		struct aim_directim_intdata *intdata;
+		
+		if ((cur->type != AIM_CONN_TYPE_RENDEZVOUS) || (cur->subtype != AIM_CONN_SUBTYPE_OFT_DIRECTIM))
+			continue;
+
+		intdata = cur->internal;
+
+		if (aim_sncmp(intdata->sn, name) == 0)
+			break;
+	}
+
+	return cur;
+} 
+
+/**
+ * aim_accepttransfer - accept a file transfer request
+ * @sess: the session,
+ * @conn: the BOS conn for the CAP reply
+ * @sn: the screenname to send it to,
+ * @cookie: the cookie used
+ * @ip: the ip to connect to
+ * @listingfiles: number of files to share
+ * @listingtotsize: total size of shared files
+ * @listingsize: length of the listing file(buffer)
+ * @listingchecksum: checksum of the listing
+ * @rendid: capability type (%AIM_CAPS_GETFILE or %AIM_CAPS_SENDFILE)  
+ *
+ * Returns new connection or %NULL on error.
+ *
+ * XXX this should take a struct.
+ */
+faim_export aim_conn_t *aim_accepttransfer(aim_session_t *sess, 
+						  aim_conn_t *conn, 
+						  const char *sn, const fu8_t *cookie, 
+						  const fu8_t *ip, 
+						  fu16_t listingfiles, 
+						  fu16_t listingtotsize, 
+						  fu16_t listingsize, 
+						  fu32_t listingchecksum, 
+						  fu16_t rendid)
+{
+       return NULL;	
+#if 0
+  struct command_tx_struct *newpacket, *newoft;
+  struct aim_conn_t *newconn;
+  struct aim_fileheader_t *fh;
+  struct aim_filetransfer_priv *priv;
+  struct aim_msgcookie_t *cachedcook;
+  int curbyte, i;
+
+  if (!sess || !conn || !sn || !cookie || !ip) {
+    return NULL;
+  }
+
+  newconn = aim_newconn(sess, AIM_CONN_TYPE_RENDEZVOUS, ip);
+
+  if (!newconn || (newconn->fd == -1)) {
+    perror("aim_newconn");
+    faimdprintf(sess, 2, "could not connect to %s (fd: %i)\n", ip, newconn?newconn->fd:0);
+    return newconn;
+  } else {
+    priv = (struct aim_filetransfer_priv *)calloc(1, sizeof(struct aim_filetransfer_priv));
+
+    memcpy(priv->cookie, cookie, 8);
+    priv->state = 0;
+    strncpy(priv->sn, sn, MAXSNLEN);
+    strncpy(priv->ip, ip, sizeof(priv->ip));
+    newconn->priv = (void *)priv;
+
+    faimdprintf(sess, 2, "faim: connected to peer (fd = %d)\n", newconn->fd);
+  }
+
+  if (rendid == AIM_CAPS_GETFILE)  {
+    newconn->subtype = AIM_CONN_SUBTYPE_OFT_GETFILE;
+
+      faimdprintf(sess, 2, "faim: getfile request accept\n");
+
+      if (!(newoft = aim_tx_new(sess, newconn, AIM_FRAMETYPE_OFT, 0x1108, 0))) { 
+	faimdprintf(sess, 2, "faim: aim_accepttransfer: tx_new OFT failed\n");
+	/* XXX: conn leak here */
+	return NULL;
+      } 
+
+      newoft->lock = 1;
+      memcpy(newoft->hdr.oft.magic, "OFT2", 4);
+      newoft->hdr.oft.hdr2len = 0x100 - 8;
+
+      if (!(fh = (struct aim_fileheader_t*)calloc(1, sizeof(struct aim_fileheader_t)))) {
+	/* XXX: conn leak here */
+	perror("calloc");
+	return NULL;
+      }
+
+      fh->encrypt = 0x0000;
+      fh->compress = 0x0000;
+      fh->totfiles = listingfiles;
+      fh->filesleft = listingfiles;      /* is this right -- total parts and parts left?*/
+      fh->totparts = 0x0001;
+      fh->partsleft = 0x0001;
+      fh->totsize = listingtotsize;
+      fh->size = listingsize;      /* ls -l listing.txt */
+      fh->modtime = (int)time(NULL); /* we'll go with current time for now */
+      fh->checksum = listingchecksum;
+      fh->rfcsum = 0x00000000;
+      fh->rfsize = 0x00000000;
+      fh->cretime = 0x00000000;
+      fh->rfcsum = 0x00000000;
+      fh->nrecvd = 0x00000000;
+      fh->recvcsum = 0x00000000;
+      memset(fh->idstring, 0, sizeof(fh->idstring));
+      memcpy(fh->idstring, "OFT_Windows ICBMFT V1.1 32", sizeof(fh->idstring));
+      fh->flags = 0x02;
+      fh->lnameoffset = 0x1a;
+      fh->lsizeoffset = 0x10;
+      memset(fh->dummy, 0, sizeof(fh->dummy));
+      memset(fh->macfileinfo, 0, sizeof(fh->macfileinfo));
+
+      /* we need to figure out these encodings for filenames */
+      fh->nencode = 0x0000;
+      fh->nlanguage = 0x0000;
+      memset(fh->name, 0, sizeof(fh->name));
+      memcpy(fh->name, "listing.txt", sizeof(fh->name));
+
+      if (!(newoft->hdr.oft.hdr2 = (char *)calloc(1,newoft->hdr.oft.hdr2len))) { 
+	newoft->lock = 0;
+	aim_frame_destroy(newoft);
+	/* XXX: conn leak */
+	perror("calloc (1)");
+	return NULL;
+      } 
+
+      memcpy(fh->bcookie, cookie, 8);
+
+      if (!(aim_oft_buildheader((unsigned char *)newoft->hdr.oft.hdr2, fh)))
+	faimdprintf(sess, 1, "eek, bh fail!\n");
+
+      newoft->lock = 0;
+      aim_tx_enqueue(sess, newoft);
+   
+      if (!(cachedcook = (struct aim_msgcookie_t *)calloc(1, sizeof(struct aim_msgcookie_t)))) { 
+	faimdprintf(sess, 1, "faim: accepttransfer: couldn't calloc cachedcook. yeep!\n");
+	/* XXX: more cleanup, conn leak */
+	perror("calloc (2)");
+	return NULL;
+      }
+
+      memcpy(&(priv->fh), fh, sizeof(struct aim_fileheader_t));
+      memcpy(cachedcook->cookie, cookie, 8);
+
+      cachedcook->type = AIM_COOKIETYPE_OFTGET;
+      cachedcook->data = (void *)priv;
+
+      if (aim_cachecookie(sess, cachedcook) == -1)
+	faimdprintf(sess, 1, "faim: ERROR caching message cookie\n");
+
+      free(fh);     
+ 
+      /* OSCAR CAP accept packet */
+   
+      if (!(newpacket = aim_tx_new(sess, conn, AIM_FRAMETYPE_OSCAR, 0x0002, 10+8+2+1+strlen(sn)+4+2+8+16))) {
+	return NULL;
+      }
+  } else {
+    return NULL;
+  }
+  
+  newpacket->lock = 1;
+  curbyte = aim_putsnac(newpacket->data, 0x0004, 0x0006, 0x0000, sess->snac_nextid);
+
+  for (i = 0; i < 8; i++)
+    curbyte += aimutil_put8(newpacket->data+curbyte, cookie[i]);
+
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x0002);
+  curbyte += aimutil_put8(newpacket->data+curbyte, strlen(sn));
+  curbyte += aimutil_putstr(newpacket->data+curbyte, sn, strlen(sn));
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x0005);
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x001a);
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x0002 /* accept*/);
+
+  for (i = 0;i < 8; i++) 
+    curbyte += aimutil_put8(newpacket->data+curbyte, cookie[i]);
+
+  curbyte += aim_putcap(newpacket->data+curbyte, 0x10, rendid);
+  newpacket->lock = 0;
+  aim_tx_enqueue(sess, newpacket);
+
+  return newconn;
+#endif
+}
+
+/**
+ * aim_getlisting(FILE *file) -- get an aim_fileheader_t for a given FILE*
+ *  @file is an opened listing file
+ * 
+ * returns a pointer to the filled-in fileheader_t
+ *
+ * Currently omits checksum. we'll fix this when AOL breaks us, i
+ * guess.
+ *
+ */
+faim_export struct aim_fileheader_t *aim_getlisting(aim_session_t *sess, FILE *file) 
+{
+	return NULL;
+#if 0
+  struct aim_fileheader_t *fh;
+  u_long totsize = 0, size = 0, checksum = 0xffff0000;
+  short totfiles = 0;
+  char *linebuf, sizebuf[9];
+  
+  int linelength = 1024;
+
+  /* XXX: if we have a line longer than 1024chars, God help us. */
+  if ( (linebuf = (char *)calloc(1, linelength)) == NULL ) {
+    faimdprintf(sess, 2, "linebuf calloc failed\n");
+    return NULL;
+  }  
+
+  if (fseek(file, 0, SEEK_END) == -1) { /* use this for sanity check */
+    perror("getlisting END1 fseek:");
+    faimdprintf(sess, 2, "getlising fseek END1 error\n");
+  }
+
+  if ((size = ftell(file)) == -1) {
+    perror("getlisting END1 getpos:");
+    faimdprintf(sess, 2, "getlising getpos END1 error\n");
+  }
+
+  if (fseek(file, 0, SEEK_SET) != 0) {
+    perror("getlesting fseek(SET):");
+    faimdprintf(sess, 2, "faim: getlisting: couldn't seek to beginning of listing file\n");
+  }
+
+  memset(linebuf, 0, linelength);
+
+  size = 0;
+
+  while(fgets(linebuf,  linelength, file)) {
+    totfiles++;
+    memset(sizebuf, 0, 9);
+
+    size += strlen(linebuf);
+    
+    if (strlen(linebuf) < 23) {
+      faimdprintf(sess, 2, "line \"%s\" too short. skipping\n", linebuf);
+      continue;
+    }
+    if (linebuf[strlen(linebuf)-1] != '\n') {
+      faimdprintf(sess, 2, "faim: OFT: getlisting -- hit EOF or line too long!\n");
+    }
+
+    memcpy(sizebuf, linebuf+17, 8);
+
+    totsize += strtol(sizebuf, NULL, 10);
+    memset(linebuf, 0, linelength);
+  }   
+
+  if (fseek(file, 0, SEEK_SET) == -1) {
+    perror("getlisting END2 fseek:");
+    faimdprintf(sess, 2, "getlising fseek END2 error\n");
+  }  
+
+  free(linebuf);
+
+  /* we're going to ignore checksumming the data for now -- that
+   * requires walking the whole listing.txt. it should probably be
+   * done at register time and cached, but, eh. */  
+
+  if (!(fh = (struct aim_fileheader_t*)calloc(1, sizeof(struct aim_fileheader_t))))
+    return NULL;
+
+  fh->encrypt     = 0x0000;
+  fh->compress    = 0x0000; 
+  fh->totfiles    = totfiles;
+  fh->filesleft   = totfiles; /* is this right ?*/
+  fh->totparts    = 0x0001;
+  fh->partsleft   = 0x0001;
+  fh->totsize     = totsize;
+  fh->size        = size; /* ls -l listing.txt */
+  fh->modtime     = (int)time(NULL); /* we'll go with current time for now */
+  fh->checksum    = checksum; /* XXX: checksum ! */
+  fh->rfcsum      = 0x00000000;
+  fh->rfsize      = 0x00000000;
+  fh->cretime     = 0x00000000;
+  fh->rfcsum      = 0x00000000;
+  fh->nrecvd      = 0x00000000;
+  fh->recvcsum    = 0x00000000;
+
+  /*  memset(fh->idstring, 0, sizeof(fh->idstring)); */
+  memcpy(fh->idstring, "OFT_Windows ICBMFT V1.1 32", sizeof(fh->idstring));
+  memset(fh->idstring+strlen(fh->idstring), 0, sizeof(fh->idstring)-strlen(fh->idstring));
+
+  fh->flags       = 0x02;
+  fh->lnameoffset = 0x1a;
+  fh->lsizeoffset = 0x10;
+
+  /*  memset(fh->dummy, 0, sizeof(fh->dummy)); */
+  memset(fh->macfileinfo, 0, sizeof(fh->macfileinfo));
+
+  fh->nencode     = 0x0000; /* we need to figure out these encodings for filenames */
+  fh->nlanguage   = 0x0000;
+
+  /*  memset(fh->name, 0, sizeof(fh->name)); */
+  memcpy(fh->name, "listing.txt", sizeof(fh->name));
+  memset(fh->name+strlen(fh->name), 0, 64-strlen(fh->name));
+
+  faimdprintf(sess, 2, "faim: OFT: listing fh name %s / %s\n", fh->name, (fh->name+(strlen(fh->name))));
+  return fh;
+#endif
+}
+
+/**
+ * aim_listenestablish - create a listening socket on a port.
+ * @portnum: the port number to bind to.  
+ *
+ * you need to call accept() when it's connected. returns your fd 
+ *
+ * XXX: give the client author the responsibility of setting up a
+ * listener, then we no longer have a libfaim problem with broken
+ * solaris *innocent smile* -jbm
+ */
+static int listenestablish(fu16_t portnum)
+{
+#if HAVE_GETADDRINFO
+	int listenfd;
+	const int on = 1;
+	struct addrinfo hints, *res, *ressave;
+	char serv[5];
+
+	snprintf(serv, sizeof(serv), "%d", portnum);
+	memset(&hints, 0, sizeof(struct addrinfo));
+	hints.ai_flags = AI_PASSIVE;
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+	if (getaddrinfo(NULL /*any IP*/, serv, &hints, &res) != 0) {
+		perror("getaddrinfo");
+		return -1;
+	} 
+	ressave = res;
+	do { 
+		listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);    
+		if (listenfd < 0)
+			continue;
+		setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+		if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0)
+			break;
+		/* success */
+		close(listenfd);
+	} while ( (res = res->ai_next) );
+
+	if (!res)
+		return -1;
+
+	if (listen(listenfd, 1024)!=0) { 
+		perror("listen");
+		return -1;
+	} 
+
+	freeaddrinfo(ressave);
+	return listenfd;
+#else
+	int listenfd;
+	const int on = 1;
+	struct sockaddr_in sockin;
+
+	if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+		perror("socket(listenfd)");
+		return -1;
+	}
+
+	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) != 0) {
+		perror("setsockopt(listenfd)");
+		close(listenfd);
+		return -1;
+	} 
+
+	memset(&sockin, 0, sizeof(struct sockaddr_in));
+	sockin.sin_family = AF_INET;
+	sockin.sin_port = htons(portnum);
+
+	if (bind(listenfd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0) {
+		perror("bind(listenfd)");
+		close(listenfd);
+		return -1;
+	}
+	if (listen(listenfd, 4) != 0) {
+		perror("listen(listenfd)");
+		close(listenfd);
+		return -1;
+	}
+	return listenfd;
+#endif
+} 
+
+static int getcommand_getfile(aim_session_t *sess, aim_conn_t *conn)
+{
+#if 0
+	struct aim_filetransfer_priv *ft;
+	aim_rxcallback_t userfunc;
+
+	ft = conn->priv;
+	if (ft->state == 2) {
+		/* waiting on listing data */
+		int ret = 0;
+		char *listing;
+		struct command_tx_struct *newoft;
+
+		if (!(listing = malloc(ft->fh.size)))
+			return -1;
+
+		ft->state = 0;
+		if (aim_recv(conn->fd, listing, ft->fh.size) != ft->fh.size)	
+			faimdprintf(sess, 2, "OFT get: file %s was short. (0x%lx)\n", ft->fh.name, ft->fh.size);
+
+		if (!(newoft = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x120b, 0))) {
+			faimdprintf(sess, 2, "faim: aim_get_command_rendezvous: getfile listing: tx_new OFT failed\n");
+			faim_mutex_unlock(&conn->active);
+			free(listing);
+			aim_conn_close(conn);
+			return -1;
+		}
+
+		memcpy(newoft->hdr.oft.magic, "OFT2", 4);
+		newoft->hdr.oft.hdr2len = 0x100 - 8;
+
+		/* Protocol BS - set nrecvd to size of listing, recvcsum to listing checksum, flags to 0 */
+
+		ft->fh.nrecvd = ft->fh.size;
+		ft->fh.recvcsum = ft->fh.checksum;
+		ft->fh.flags = 0;
+
+		if (!(newoft->hdr.oft.hdr2 = (char *)calloc(1,newoft->hdr.oft.hdr2len))) {
+			aim_frame_destroy(newoft);
+			free(listing);
+			return -1;
+		}
+
+		if (!(aim_oft_buildheader((unsigned char *)newoft->hdr.oft.hdr2, &(ft->fh))))
+			faimdprintf(sess, 2, "eek! bh fail listing\n");
+
+		/* send the 120b */
+		aim_tx_enqueue(sess, newoft);
+		if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILELISTING)) )
+			ret = userfunc(sess, NULL, conn, ft, listing);
+
+		free(listing);
+		return ret;
+	}
+
+	if (ft->state == 3) { 
+		/* waiting on file data */
+		if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILERECEIVE)) ) 
+			return userfunc(sess, NULL, conn, ft);
+		return 0;
+	}
+
+	if (ft->state == 4) {
+		if( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILESTATE4)) )
+			return userfunc(sess, NULL, conn);
+		aim_conn_close(conn);
+		return 0;
+	}	
+
+	return 0;
+#else
+	return -1;
+#endif
+}
+
+static void connclose_sendfile(aim_session_t *sess, aim_conn_t *conn)
+{
+	aim_msgcookie_t *cook;
+	struct aim_filetransfer_priv *priv = (struct aim_filetransfer_priv *)conn->priv;
+
+	cook = aim_uncachecookie(sess, priv->cookie, AIM_COOKIETYPE_OFTSEND);
+	aim_cookie_free(sess, cook);
+
+	return;
+}
+
+static void connkill_sendfile(aim_session_t *sess, aim_conn_t *conn)
+{
+	
+	free(conn->internal);
+
+	return;
+}
+
+static void connclose_getfile(aim_session_t *sess, aim_conn_t *conn)
+{
+	aim_msgcookie_t *cook;
+	struct aim_filetransfer_priv *priv = (struct aim_filetransfer_priv *)conn->priv;
+
+	cook = aim_uncachecookie(sess, priv->cookie, AIM_COOKIETYPE_OFTGET);
+	aim_cookie_free(sess, cook);
+
+	return;
+}
+
+static void connkill_getfile(aim_session_t *sess, aim_conn_t *conn)
+{
+	
+	free(conn->internal);
+
+	return;
+}
+
+static void connclose_directim(aim_session_t *sess, aim_conn_t *conn)
+{
+	struct aim_directim_intdata *intdata = (struct aim_directim_intdata *)conn->internal;
+	aim_msgcookie_t *cook;
+
+	cook = aim_uncachecookie(sess, intdata->cookie, AIM_COOKIETYPE_OFTIM);
+	aim_cookie_free(sess, cook);
+
+	return;
+}
+
+static void connkill_directim(aim_session_t *sess, aim_conn_t *conn)
+{
+	
+	free(conn->internal);
+
+	return;
+}
+
+faim_internal void aim_conn_close_rend(aim_session_t *sess, aim_conn_t *conn)
+{
+
+	if (conn->type != AIM_CONN_TYPE_RENDEZVOUS)
+		return;
+
+	if (conn->subtype == AIM_CONN_SUBTYPE_OFT_SENDFILE)
+		connclose_sendfile(sess, conn);
+	else if (conn->subtype == AIM_CONN_SUBTYPE_OFT_GETFILE)
+		connclose_getfile(sess, conn);
+	else if (conn->subtype == AIM_CONN_SUBTYPE_OFT_DIRECTIM)
+		connclose_directim(sess, conn);
+
+	return;
+}
+
+faim_internal void aim_conn_kill_rend(aim_session_t *sess, aim_conn_t *conn)
+{
+
+	if (conn->type != AIM_CONN_TYPE_RENDEZVOUS)
+		return;
+
+	if (conn->subtype == AIM_CONN_SUBTYPE_OFT_SENDFILE)
+		connkill_sendfile(sess, conn);
+	else if (conn->subtype == AIM_CONN_SUBTYPE_OFT_GETFILE)
+		connkill_getfile(sess, conn);
+	else if (conn->subtype == AIM_CONN_SUBTYPE_OFT_DIRECTIM)
+		connkill_directim(sess, conn);
+
+	return;
+}
+
+static int handlehdr_directim(aim_session_t *sess, aim_conn_t *conn, fu8_t *hdr)
+{
+	aim_frame_t fr;
+	aim_rxcallback_t userfunc;
+	fu32_t payloadlength;
+	fu16_t flags;
+	char *snptr = NULL;
+
+	fr.conn = conn;
+
+	payloadlength = aimutil_get32(hdr+22);
+	flags = aimutil_get16(hdr+32);
+	snptr = (char *)hdr+38;
+
+	faimdprintf(sess, 2, "faim: OFT frame: handlehdr_directim: %04x / %04x / %s\n", payloadlength, flags, snptr);
+
+	if (flags == 0x000e) { 
+		int ret = 0;
+
+		if ((userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMTYPING)))
+			ret = userfunc(sess, &fr, snptr);
+
+		return ret;
+
+	} else if ((flags == 0x0000) && payloadlength) { 
+		char *msg;
+		int ret = 0;
+
+		if (!(msg = calloc(1, payloadlength+1)))
+			return -1;
+
+		if (aim_recv(conn->fd, msg, payloadlength) < payloadlength) {
+			free(msg);
+			return -1;
+		}
+
+		msg[payloadlength] = '\0';
+
+		if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMINCOMING)) )
+			ret = userfunc(sess, &fr, snptr, msg);
+
+		free(msg);
+
+		return ret;
+	}
+
+	return 0;
+}
+
+static int handlehdr_getfile_listing(aim_session_t *sess, aim_conn_t *conn, fu8_t *hdr)
+{
+#if 0
+	struct aim_filetransfer_priv *ft;
+	struct aim_fileheader_t *fh;
+	struct aim_msgcookie_t *cook;
+	struct command_tx_struct *newoft;
+	aim_rxcallback_t userfunc;
+
+	faimdprintf(sess, 2,"faim: rend: fileget 0x1108\n");
+	fh = aim_oft_getfh(hdr);
+
+	faim_mutex_unlock(&conn->active);
+
+	if (!(cook = aim_checkcookie(sess, fh->bcookie, AIM_COOKIETYPE_OFTGET))) {
+		free(fh);
+		return -1;
+	}
+
+	ft = cook->data;
+
+	/* we're waaaaiiiting.. for listing.txt */
+	ft->state = 2;
+
+	memcpy(&(ft->fh), fh, sizeof(struct aim_fileheader_t));
+	free(fh);
+
+	if(aim_cachecookie(sess, cook) == -1) {
+		faimdprintf(sess, 1, "error caching cookie\n");
+		return -1;
+	}     
+
+	if (!(newoft = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x1209, 0))) {
+		aim_conn_close(conn);
+		return -1;
+	}
+
+	memcpy(newoft->hdr.oft.magic, "OFT2", 4);
+	newoft->hdr.oft.hdr2len = 0x100 - 8;
+
+	if (!(newoft->hdr.oft.hdr2 = (char *)calloc(1,newoft->hdr.oft.hdr2len))) {
+		newoft->lock = 0;
+		aim_frame_destroy(newoft);
+		return -1;
+	}
+
+	if (!(aim_oft_buildheader((unsigned char *)newoft->hdr.oft.hdr2, &(ft->fh)))) {
+		newoft->lock = 0;
+		aim_frame_destroy(newoft);
+		return -1;
+	}
+
+	newoft->lock = 0;
+	aim_tx_enqueue(sess, newoft);
+#endif
+	return -1;
+}
+
+static int handlehdr_getfile_listing2(aim_session_t *sess, aim_conn_t *conn, fu8_t *hdr)
+{
+#if 0
+	struct aim_filetransfer_priv *ft;
+	struct aim_fileheader_t *fh;
+	struct aim_msgcookie_t *cook;
+	int ret = 0;
+	aim_rxcallback_t userfunc;
+	
+	fh = aim_oft_getfh(hdr);
+
+	if (!(cook = aim_checkcookie(sess, fh->bcookie, AIM_COOKIETYPE_OFTGET)))
+		faimdprintf(sess, 2, "shit, no cookie in 0x1209. (%i/%s)going to crash..\n",  AIM_COOKIETYPE_OFTGET, fh->bcookie);
+
+	ft = cook->data;
+
+	if (ft->fh.size != fh->size)
+		faimdprintf(sess, 2, "hrm. ft->fh.size (%ld) != fh->size (%ld). um. using ft->fh.size\n", ft->fh.size, fh->size);
+
+	if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILELISTINGREQ)))
+		ret = userfunc(sess, NULL, conn, fh);
+
+	faimdprintf(sess, 2, "faim: get_command_rendezvous: hit end of 1209\n");
+
+	free(fh);
+
+	return ret;
+#else
+	return -1;
+#endif
+}
+
+static int handlehdr_getfile_listing3(aim_session_t *sess, aim_conn_t *conn, fu8_t *hdr)
+{
+#if 0
+	struct aim_filetransfer_priv *ft;
+	struct aim_msgcookie_t *cook;
+	struct aim_fileheader_t *fh;
+	aim_rxcallback_t userfunc;
+
+	fh = aim_oft_getfh(hdr);
+
+	if (!(cook = aim_checkcookie(sess, fh->bcookie, AIM_COOKIETYPE_OFTGET))) {
+		free(fh);
+		return -1;
+	}
+
+	free(fh);
+
+	ft = cook->data;
+
+	if (aim_cachecookie(sess, cook) == -1)
+		return -1;
+
+	if ((userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILELISTINGRXCONFIRM)))
+		return userfunc(sess, NULL, conn);
+#endif
+	return -1;
+}
+
+static int handlehdr_getfile_request(aim_session_t *sess, aim_conn_t *conn, fu8_t *hdr)
+{
+#if 0
+	struct aim_filetransfer_priv *ft;
+	struct aim_msgcookie_t *cook;
+	struct aim_fileheader_t *fh;
+	struct command_tx_struct *newoft;
+	int i = 0;
+	aim_rxcallback_t userfunc;
+
+	fh = aim_oft_getfh(hdr);
+
+	if (!(cook = aim_checkcookie(sess, fh->bcookie, AIM_COOKIETYPE_OFTGET))) {
+		free(fh);
+		return -1;
+	}
+
+	ft = cook->data;
+	memcpy(&(ft->fh), fh, sizeof(struct aim_fileheader_t));
+	free(fh);
+
+	aim_cachecookie(sess, cook);
+
+	faimdprintf(sess, 2, "faim: fileget: %s seems to want %s\n", ft->sn, ft->fh.name);
+
+	if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILEFILEREQ)) )
+		i = userfunc(sess, NULL, conn, &(ft->fh), cook->cookie);
+
+	if (i < 0)
+		return i;
+
+	if (!(newoft = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x0101, 0))) {
+		faimdprintf(sess, 2, "faim: send_final_transfer: tx_new OFT failed\n");
+		return -1;
+	}
+
+	newoft->lock = 1;
+	memcpy(newoft->hdr.oft.magic, "OFT2", 4);
+	newoft->hdr.oft.hdr2len = 0x100 - 8;
+
+	if (!(newoft->hdr.oft.hdr2 = calloc(1,newoft->hdr.oft.hdr2len))) {
+		aim_frame_destroy(newoft);
+		return -1;
+	} 
+
+	/* protocol BS: nrecvd, recvcsum to 0, flags to 0x20. */
+	ft->fh.nrecvd = 0;
+	ft->fh.recvcsum = 0;
+	ft->fh.flags = 0x20;
+
+	aim_oft_buildheader((unsigned char *)newoft->hdr.oft.hdr2, &(ft->fh));
+
+	newoft->lock = 0;
+	aim_tx_enqueue(sess, newoft);
+
+	faimdprintf(sess, 2, "faim: OFT: OFT file header enqueued.\n");
+
+	return i;
+#else
+	return -1;
+#endif
+}
+
+static int handlehdr_getfile_sending(aim_session_t *sess, aim_conn_t *conn, fu8_t *hdr)
+{
+#if 0
+	struct aim_fileheader_t *fh;
+	struct aim_filetransfer_priv *ft;
+	struct aim_msgcookie_t *cook;
+	struct command_tx_struct *newoft;
+	aim_rxcallback_t userfunc;
+
+	fh = aim_oft_getfh(hdr);
+
+	if (!(cook = aim_checkcookie(sess, fh->bcookie, AIM_COOKIETYPE_OFTGET))) {
+		free(fh);
+		return -1;
+	}
+
+	free(fh);
+
+	ft = cook->data;
+
+	ft->state = 3;
+
+	if (aim_cachecookie(sess, cook) == -1)
+		return -1;
+
+	faimdprintf(sess, 2, "faim: fileget: %s seems to want to send %s\n", ft->sn, ft->fh.name);
+
+	if (!(newoft = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x0202, 0))) {
+		faimdprintf(sess, 2, "faim: send_final_transfer: tx_new OFT failed\n");
+		return -1;
+	}
+
+	newoft->lock = 1;
+	memcpy(newoft->hdr.oft.magic, "OFT2", 4);
+
+	newoft->hdr.oft.hdr2len = 0x100 - 8;
+
+	if (!(newoft->hdr.oft.hdr2 = calloc(1,newoft->hdr.oft.hdr2len))) {
+		aim_frame_destroy(newoft);
+		return -1;
+	}
+
+	aim_oft_buildheader((unsigned char *)newoft->hdr.oft.hdr2, &(ft->fh));
+
+	newoft->lock = 0;
+	aim_tx_enqueue(sess, newoft);
+
+	faimdprintf(sess, 2, "faim: OFT: OFT 0x0202 enqueued.\n");
+
+	if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILEFILEREQ)) == NULL)
+		return 1;
+#else
+	return -1;
+#endif
+}
+
+static int handlehdr_getfile_recv(aim_session_t *sess, aim_conn_t *conn, fu8_t *hdr)
+{
+#if 0
+	struct aim_fileheader_t *fh;
+	struct aim_filetransfer_priv *ft;
+	struct aim_msgcookie_t *cook;
+	int ret = 1;
+	aim_rxcallback_t userfunc;
+
+	fh = aim_oft_getfh(hdr);
+
+	if (!(cook = aim_checkcookie(sess, fh->bcookie, AIM_COOKIETYPE_OFTGET))) {
+		free(fh);
+		return -1;
+	}
+
+	ft = cook->data;
+
+	faimdprintf(sess, 2, "faim: get_rend: looks like we're ready to send data.(oft 0x0202)\n");
+
+	if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILEFILESEND)) )
+		ret = userfunc(sess, NULL, conn, fh);
+
+	free(fh);
+
+	return ret;
+#else
+	return -1;
+#endif
+}
+
+static int handlehdr_getfile_finish(aim_session_t *sess, aim_conn_t *conn, fu8_t *hdr)
+{
+#if 0
+	struct aim_fileheader_t *fh;
+	aim_rxcallback_t userfunc;
+
+	fh = aim_oft_getfh(hdr);
+
+	faimdprintf(sess, 2, "faim: get_rend: looks like we're done with a transfer (oft 0x0204)\n");
+
+	if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILECOMPLETE)) )
+		userfunc(sess, NULL, conn, fh);
+
+	free(fh);
+#endif
+
+	return -1;
+}
+
+/**
+ * aim_get_command_rendezvous - OFT equivalent of aim_get_command
+ * @sess: session to work on
+ * @conn: conn to pull data from 
+ *
+ * this reads and handles data from conn->fd. currently a little rough
+ * around the edges
+ */
+faim_internal int aim_get_command_rendezvous(aim_session_t *sess, aim_conn_t *conn)
+{
+	fu8_t hdrbuf1[6];
+	fu8_t *hdr = NULL;
+	int hdrlen, hdrtype;
+	int ret = -1;
+
+	if (!sess || !conn)
+		return -1;
+
+	memset(hdrbuf1, 0, sizeof(hdrbuf1));
+
+	/* I guess? I didn't understand any of that mess... */
+	if (conn->subtype == AIM_CONN_SUBTYPE_OFT_GETFILE)
+		return getcommand_getfile(sess, conn);
+
+	/* XXX fix all the error cases here */
+	if (aim_recv(conn->fd, hdrbuf1, 6) < 6) {
+
+		faimdprintf(sess, 2, "faim: rend: read error (fd: %i)\n", conn->fd);
+
+		aim_conn_close(conn);
+
+		return -1;
+	}
+
+	hdrlen = aimutil_get16(hdrbuf1+4);
+	hdrlen -= 6;
+
+	hdr = malloc(hdrlen);
+
+	if (aim_recv(conn->fd, hdr, hdrlen) < hdrlen) {
+		faimdprintf(sess, 2, "faim: rend: read2 error on %d (%d)\n", conn->fd, hdrlen);
+		free(hdr);
+		aim_conn_close(conn);
+		return -1;
+	}
+
+	hdrtype = aimutil_get16(hdr);
+
+	if (hdrtype == 0x0001)
+		ret = handlehdr_directim(sess, conn, hdr);
+	else if (hdrtype == 0x1108) /* getfile listing.txt incoming tx->rx */
+		ret = handlehdr_getfile_listing(sess, conn, hdr);
+	else if (hdrtype == 0x1209) /* get file listing ack rx->tx */
+		ret = handlehdr_getfile_listing2(sess, conn, hdr);
+	else if (hdrtype == 0x120b) /* get file listing rx confirm */
+		ret = handlehdr_getfile_listing3(sess, conn, hdr);
+	else if (hdrtype == 0x120c) /* getfile request */
+		ret = handlehdr_getfile_request(sess, conn, hdr);
+	else if (hdrtype == 0x0101) /* getfile sending data */
+		ret = handlehdr_getfile_sending(sess, conn, hdr);
+	else if (hdrtype == 0x0202) /* getfile recv data */
+		ret = handlehdr_getfile_recv(sess, conn, hdr);
+	else if (hdrtype == 0x0204) /* getfile finished */
+		ret = handlehdr_getfile_finish(sess, conn, hdr);
+	else {
+		faimdprintf(sess, 2,"faim: OFT frame: uknown type %04x\n", hdrtype);
+		ret = -1;
+	}
+	
+	free(hdr);
+
+	if (ret == -1)
+		aim_conn_close(conn);
+
+	return ret;
+}
+
+#if 0
+/**
+ * aim_oft_getfh - extracts an &aim_fileheader_t from buffer hdr.
+ * @hdr: buffer to extract header from  
+ *
+ * returns pointer to new struct on success; %NULL on error.  
+ *
+ */
+static struct aim_fileheader_t *aim_oft_getfh(unsigned char *hdr) 
+{
+  struct aim_fileheader_t *fh;
+  int i, j;
+  if (!(fh = calloc(1, sizeof(struct aim_fileheader_t))))
+    return NULL;
+  
+  /* [0] and [1] are the type. we can ignore those here. */
+  i = 2;
+  for(j = 0; j < 8; j++, i++)
+    fh->bcookie[j] = hdr[i];
+  fh->encrypt = aimutil_get16(hdr+i);
+  i += 2;
+  fh->compress = aimutil_get16(hdr+i);
+  i += 2;
+  fh->totfiles = aimutil_get16(hdr+i);
+  i += 2;
+  fh->filesleft = aimutil_get16(hdr+i);
+  i += 2;
+  fh->totparts = aimutil_get16(hdr+i);
+  i += 2;
+  fh->partsleft = aimutil_get16(hdr+i);
+  i += 2;
+  fh->totsize = aimutil_get32(hdr+i);
+  i += 4;
+  fh->size = aimutil_get32(hdr+i);
+  i += 4;
+  fh->modtime = aimutil_get32(hdr+i);
+  i += 4;
+  fh->checksum = aimutil_get32(hdr+i);
+  i += 4;
+  fh->rfrcsum = aimutil_get32(hdr+i);
+  i += 4;
+  fh->rfsize = aimutil_get32(hdr+i);
+  i += 4;
+  fh->cretime = aimutil_get32(hdr+i);
+  i += 4;
+  fh->rfcsum = aimutil_get32(hdr+i);
+  i += 4;
+  fh->nrecvd = aimutil_get32(hdr+i);
+  i += 4;
+  fh->recvcsum = aimutil_get32(hdr+i);
+  i += 4;
+  memcpy(fh->idstring, hdr+i, 32);
+  i += 32;
+  fh->flags = aimutil_get8(hdr+i);
+  i += 1;
+  fh->lnameoffset = aimutil_get8(hdr+i);
+  i += 1;
+  fh->lsizeoffset = aimutil_get8(hdr+i);
+  i += 1;
+  memcpy(fh->dummy, hdr+i, 69);
+  i += 69;
+  memcpy(fh->macfileinfo, hdr+i, 16);
+  i += 16;
+  fh->nencode = aimutil_get16(hdr+i);
+  i += 2;
+  fh->nlanguage = aimutil_get16(hdr+i);
+  i += 2;
+  memcpy(fh->name, hdr+i, 64);
+  i += 64;
+  return fh;
+} 
+#endif
+
+/**
+ * aim_oft_checksum - calculate oft checksum of buffer
+ * @buffer: buffer of data to checksum
+ * @bufsize: size of buffer
+ * @checksum: pointer to integer to place result in (pointer!) 
+ *
+ *
+ * Note that checksum is a pointer. Checksum should be filled with
+ * 0xFFFF0000 for each new file; you can have this checksum chunks of
+ * files in series if you just call it repeatedly in a for(; ; ) loop
+ * and don't reset the checksum between each call. And you thought we
+ * didn't care about you and your pathetic client's meomry footprint
+ * ;^) 
+ *
+ *
+ * Also, it's been said that this is incorrect as currently
+ * written. You were warned.
+ */
+faim_export fu32_t aim_oft_checksum(aim_session_t *sess, const char *buffer, int bufsize, fu32_t *checksum)
+{
+	return 0xdeadbeef;
+#if 0
+  fu16_t check0, check1;
+  int i;
+
+  check0 = ((*checksum & 0xFF000000) >> 16);
+  check1 = ((*checksum & 0x00ff0000) >> 16);
+  for(i = 0; i < bufsize; i++) {
+    if (i % 2) { /* use check1 -- second byte */
+      if ( (short)buffer[i] > check1 ) { /* wrapping */
+	check1 += 0x100;  /* this is a cheap way to wrap */
+
+	/* if we're wrapping, decrement the other one */
+	/* XXX: check this corner case */
+	if (check0 == 0) 
+	  check0 = 0x00ff;
+	else 
+	  check0--;
+      } 
+      check1 -= buffer[i];
+    } else { /* use check0 -- first byte  */
+      if ( (short)buffer[i] > check0 ) { /* wrapping */
+	check0 += 0x100;       /* this is a cheap way to wrap */
+  
+	/* if we're wrapping, decrement the other one */
+	/* XXX: check this corner case */
+	if (check1 == 0) 
+	  check1 = 0x00ff;
+	else 
+	  check1--;
+      } 
+      check0 -= buffer[i];
+    } 
+  }
+
+  if (check0 > 0xff || check1 > 0xff)  { 
+    /* they shouldn't be able to do this. error! */
+    faimdprintf(sess, 2, "check0 or check1 is too high: 0x%04x, 0x%04x\n", check0, check1);
+    return -1;
+  } 
+
+  /* grab just the lowest byte; this should be clean, but just in
+     case */
+  check0 &= 0xff;
+  check1 &= 0xff;
+
+  *checksum = ((check0 * 0x1000000) + (check1 * 0x10000));
+  return *checksum;
+#endif
+} 
+
+#if 0
+/**
+ * aim_oft_buildheader - fills a buffer with network-order fh data
+ * @dest: buffer to fill -- pre-alloced
+ * @fh: fh to get data from  
+ *
+ * returns length written; -1 on error.
+ * DOES NOT DO BOUNDS CHECKING!
+ *
+ */
+static int oft_buildheader(unsigned char *dest, struct aim_fileheader_t *fh) 
+{ 
+  int i, curbyte;
+  if (!dest || !fh)
+    return -1;
+  curbyte = 0;
+  for(i = 0; i < 8; i++) 
+    curbyte += aimutil_put8(dest+curbyte, fh->bcookie[i]);
+  curbyte += aimutil_put16(dest+curbyte, fh->encrypt);
+  curbyte += aimutil_put16(dest+curbyte, fh->compress);
+  curbyte += aimutil_put16(dest+curbyte, fh->totfiles);
+  curbyte += aimutil_put16(dest+curbyte, fh->filesleft);
+  curbyte += aimutil_put16(dest+curbyte, fh->totparts);
+  curbyte += aimutil_put16(dest+curbyte, fh->partsleft);
+  curbyte += aimutil_put32(dest+curbyte, fh->totsize);
+  curbyte += aimutil_put32(dest+curbyte, fh->size);
+  curbyte += aimutil_put32(dest+curbyte, fh->modtime);
+  curbyte += aimutil_put32(dest+curbyte, fh->checksum);
+  curbyte += aimutil_put32(dest+curbyte, fh->rfrcsum);
+  curbyte += aimutil_put32(dest+curbyte, fh->rfsize);
+  curbyte += aimutil_put32(dest+curbyte, fh->cretime);
+  curbyte += aimutil_put32(dest+curbyte, fh->rfcsum);
+  curbyte += aimutil_put32(dest+curbyte, fh->nrecvd);
+  curbyte += aimutil_put32(dest+curbyte, fh->recvcsum);
+  memcpy(dest+curbyte, fh->idstring, 32);
+  curbyte += 32;
+  curbyte += aimutil_put8(dest+curbyte, fh->flags);
+  curbyte += aimutil_put8(dest+curbyte, fh->lnameoffset);
+  curbyte += aimutil_put8(dest+curbyte, fh->lsizeoffset);
+  memcpy(dest+curbyte, fh->dummy, 69);
+  curbyte += 69;
+  memcpy(dest+curbyte, fh->macfileinfo, 16);
+  curbyte += 16;
+  curbyte += aimutil_put16(dest+curbyte, fh->nencode);
+  curbyte += aimutil_put16(dest+curbyte, fh->nlanguage);
+  memset(dest+curbyte, 0x00, 64);
+  memcpy(dest+curbyte, fh->name, 64);
+
+  /* XXX: Filenames longer than 64B  */
+  curbyte += 64;
+  return curbyte;
+}
+#endif
+
+/**
+ * aim_getfile_intitiate - Request an OFT getfile session
+ * @sess: your session,
+ * @conn: the BOS conn,
+ * @destsn is the SN to connect to.
+ * 
+ * returns a new &aim_conn_t on success, %NULL on error
+ */
+faim_export aim_conn_t *aim_getfile_initiate(aim_session_t *sess, aim_conn_t *conn, const char *destsn)
+{ 
+	return NULL;
+#if 0
+  struct command_tx_struct *newpacket;
+  struct aim_conn_t *newconn;
+  struct aim_filetransfer_priv *priv;
+  struct aim_msgcookie_t *cookie;
+  int curbyte, i, listenfd;
+  short port = 4443;
+  struct hostent *hptr;
+  struct utsname myname;
+  char cap[16];
+  char d[4];
+ 
+  /* Open our socket */
+
+  if ( (listenfd = aim_listenestablish(port)) == -1)
+    return NULL;
+
+  /* get our local IP */
+
+  if (uname(&myname) < 0)
+    return NULL;
+  if ( (hptr = gethostbyname(myname.nodename)) == NULL)
+    return NULL;
+  memcpy(&d, hptr->h_addr_list[0], 4);
+
+  aim_putcap(cap, 16, AIM_CAPS_GETFILE);
+
+  /* create the OSCAR packet */
+
+  if (!(newpacket = aim_tx_new(sess, conn, AIM_FRAMETYPE_OSCAR, 0x0002, 10+8+2+1+strlen(destsn)+4+4+0x42)))
+    return NULL;
+  newpacket->lock = 1;
+
+  /* lock struct */
+  curbyte = 0;
+  curbyte += aim_putsnac(newpacket->data+curbyte, 0x0004, 0x0006, 0x0000, sess->snac_nextid);
+
+  /* XXX: check the cookie before commiting to using it */
+
+  /* Generate a random message cookie
+   * This cookie needs to be alphanumeric and NULL-terminated to be TOC-compatible. */
+  for (i=0; i<7; i++) 
+    curbyte += aimutil_put8(newpacket->data+curbyte, 0x30 + ((u_char) random() % 10));
+
+  curbyte += aimutil_put8(newpacket->data+curbyte, 0x00);
+
+  /* grab all the data for cookie caching. */
+ 
+  if (!(cookie = (struct aim_msgcookie_t *)calloc(1, sizeof(struct aim_msgcookie_t))))
+    return NULL;
+  memcpy(cookie->cookie, newpacket->data+curbyte-8, 8);
+  cookie->type = AIM_COOKIETYPE_OFTGET;
+
+  if (!(priv = (struct aim_filetransfer_priv *)calloc(1, sizeof(struct aim_filetransfer_priv))))
+    return NULL;
+  memcpy(priv->cookie, cookie, 8);
+  memcpy(priv->sn, destsn, sizeof(priv->sn));
+  memcpy(priv->fh.name, "listing.txt", strlen("listing.txt"));
+  priv->state = 1;
+
+  cookie->data = priv;
+
+  aim_cachecookie(sess, cookie);
+
+  /* Channel ID */
+  curbyte += aimutil_put16(newpacket->data+curbyte,0x0002);
+
+  /* Destination SN (prepended with byte length) */
+  curbyte += aimutil_put8(newpacket->data+curbyte,strlen(destsn));
+  curbyte += aimutil_putstr(newpacket->data+curbyte, destsn, strlen(destsn));
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x0003);
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x0000);
+
+  /* enTLV start */
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x0005);
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x0042);
+
+  /* Flag data / ICBM Parameters? */
+  curbyte += aimutil_put8(newpacket->data+curbyte, 0x00);
+  curbyte += aimutil_put8(newpacket->data+curbyte, 0x00);
+
+  /* Cookie */
+  curbyte += aimutil_putstr(newpacket->data+curbyte, (char *)cookie, 8);
+
+  /* Capability String */
+  curbyte += aimutil_putstr(newpacket->data+curbyte, (char *)cap, 0x10);
+
+  /* 000a/0002 : 0001 */
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x000a);
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x0002);
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x0001);
+
+  /* 0003/0004: IP address */
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x0003);
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x0004);
+  for(i = 0; i < 4; i++)
+    curbyte += aimutil_put8(newpacket->data+curbyte, d[i]);
+
+  /* already in network byte order  */
+ 
+  /* 0005/0002: Port */
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x0005);
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x0002);
+  curbyte += aimutil_put16(newpacket->data+curbyte, port);
+
+  /* 000f/0000: ?? */
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x000f);
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x0000);
+
+  /* 2711/000c: ?? */
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x2711);
+  curbyte += aimutil_put16(newpacket->data+curbyte, 0x000c);
+  curbyte += aimutil_put32(newpacket->data+curbyte, 0x00120001);
+
+  for(i = 0; i < 0x000c - 4; i++)
+    curbyte += aimutil_put8(newpacket->data+curbyte, 0x00);
+
+  newpacket->commandlen = curbyte;
+  newpacket->lock = 0;
+  aim_tx_enqueue(sess, newpacket);
+
+  /* allocate and set up our connection */
+
+  i = fcntl(listenfd, F_GETFL, 0);
+  fcntl(listenfd, F_SETFL, i | O_NONBLOCK);
+  newconn = aim_newconn(sess, AIM_CONN_TYPE_RENDEZVOUS_OUT, NULL);
+
+  if (!newconn){ 
+    perror("aim_newconn");
+    return NULL;
+  }
+
+  newconn->fd = listenfd;
+  newconn->subtype = AIM_CONN_SUBTYPE_OFT_GETFILE;
+  newconn->priv = priv;
+  faimdprintf(sess, 2,"faim: listening (fd = %d, unconnected)\n", newconn->fd);
+
+  return newconn;
+#endif
+}
+ 
+/**
+ * aim_oft_getfile_request - request a particular file over an established getfile connection
+ * @sess: your session
+ * @conn: the established OFT getfile connection
+ * @name: filename to request
+ * @size: size of the file 
+ *
+ *
+ * returns -1 on error, 0 on successful enqueuing
+ */
+faim_export int aim_oft_getfile_request(aim_session_t *sess, aim_conn_t *conn, const char *name, int size)
+{
+	return -EINVAL;
+#if 0
+  struct command_tx_struct *newoft;
+  struct aim_filetransfer_priv *ft;
+  if (!sess || !conn || !conn->priv || !name)
+    return -1;
+
+  if (!(newoft = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x120c, 0))) {
+    faimdprintf(sess, 2, "faim: aim_accepttransfer: tx_new OFT failed\n");
+    return -1;
+  }
+
+  newoft->lock = 1;
+
+  memcpy(newoft->hdr.oft.magic, "OFT2", 4);
+  newoft->hdr.oft.hdr2len = 0x100 - 8;
+
+  ft = (struct aim_filetransfer_priv *)conn->priv;
+  ft->fh.filesleft = 1;
+  ft->fh.totfiles = 1;
+  ft->fh.totparts = 1;
+  ft->fh.partsleft = 1;
+  ft->fh.totsize = size;
+  ft->fh.size = size;
+  ft->fh.checksum = 0;
+  memcpy(ft->fh.name, name, strlen(name));
+  memset(ft->fh.name+strlen(name), 0, 1);
+
+  if (!(newoft->hdr.oft.hdr2 = (unsigned char *)calloc(1,newoft->hdr.oft.hdr2len))) {
+    newoft->lock = 0;
+    aim_frame_destroy(newoft);
+    return -1;
+  }
+
+  if (!(aim_oft_buildheader(newoft->hdr.oft.hdr2, &(ft->fh)))) {
+    newoft->lock = 0;
+    aim_frame_destroy(newoft);
+    return -1;
+  }
+
+  newoft->lock = 0;
+
+  aim_tx_enqueue(sess, newoft);
+  return 0;
+#endif
+}
+ 
+/**
+ * aim_oft_getfile_ack - acknowledge a getfile download as complete
+ * @sess: your session
+ * @conn: the getfile conn to send the ack over 
+ *
+ * Call this function after you have read all the data in a particular
+ * filetransfer. Returns -1 on error, 0 on apparent success
+ *
+ */
+faim_export int aim_oft_getfile_ack(aim_session_t *sess, aim_conn_t *conn) 
+{
+	return -EINVAL;
+#if 0
+  struct command_tx_struct *newoft;
+  struct aim_filetransfer_priv *ft;
+
+  if (!sess || !conn || !conn->priv)
+    return -1;
+
+  if (!(newoft = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x0202, 0))) {
+    faimdprintf(sess, 2, "faim: aim_accepttransfer: tx_new OFT failed\n");
+    return -1;
+  } 
+
+  newoft->lock = 1;
+
+  memcpy(newoft->hdr.oft.magic, "OFT2", 4);
+  newoft->hdr.oft.hdr2len = 0x100-8;
+
+ if (!(newoft->hdr.oft.hdr2 = (char *)calloc(1,newoft->hdr.oft.hdr2len))) { 
+   newoft->lock = 0;
+   aim_frame_destroy(newoft);
+   return -1;
+ }
+
+ ft = (struct aim_filetransfer_priv *)conn->priv;
+
+ if (!(aim_oft_buildheader((unsigned char *)newoft->hdr.oft.hdr2, &(ft->fh)))) {
+   newoft->lock = 0;
+   aim_frame_destroy(newoft);
+   return -1;
+ }
+
+ newoft->lock = 0;
+ aim_tx_enqueue(sess, newoft);
+ return 0;
+#endif
+}
+ 
+/**
+ * aim_oft_getfile_end - end a getfile.
+ * @sess: your session
+ * @conn: the getfile connection 
+ *
+ * call this before you close the getfile connection if you're on the
+ * receiving/requesting end.
+ */
+faim_export int aim_oft_getfile_end(aim_session_t *sess, aim_conn_t *conn)
+{
+	return -EINVAL;
+#if 0
+  struct command_tx_struct *newoft;
+  struct aim_filetransfer_priv *ft;
+  
+  if (!sess || !conn || !conn->priv)
+    return -1;
+
+  if (!(newoft = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x0204, 0))) {
+    faimdprintf(sess, 2, "faim: aim_accepttransfer: tx_new OFT failed\n");
+    return -1;
+  }
+  
+  newoft->lock = 1;
+  
+  memcpy(newoft->hdr.oft.magic, "OFT2", 4);
+  newoft->hdr.oft.hdr2len = 0x100 - 8;
+  
+  if (!(newoft->hdr.oft.hdr2 = (char *)calloc(1,newoft->hdr.oft.hdr2len))) {
+    newoft->lock = 0;
+    aim_frame_destroy(newoft);
+    return -1;
+  }
+  
+  ft = (struct aim_filetransfer_priv *)conn->priv;
+  ft->state = 4; /* no longer wanting data */
+  ft->fh.nrecvd = ft->fh.size;
+  ft->fh.recvcsum = ft->fh.checksum;
+  ft->fh.flags = 0x21;
+  
+  if (!(aim_oft_buildheader((unsigned char *)newoft->hdr.oft.hdr2, &(ft->fh)))) {
+    newoft->lock = 0;
+    aim_frame_destroy(newoft);
+    return -1;
+  }
+  
+  newoft->lock = 0;
+  aim_tx_enqueue(sess, newoft);
+  
+  return 0;
+#endif /* 0 */
+}
+
Index: libfaim/icq.c
===================================================================
--- libfaim/icq.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/icq.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,266 @@
+/*
+ * Encapsulated ICQ.
+ *
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+faim_export int aim_icq_reqofflinemsgs(aim_session_t *sess)
+{
+	aim_conn_t *conn;
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	int bslen;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015)))
+		return -EINVAL;
+
+	bslen = 2 + 4 + 2 + 2;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid);
+
+	/* For simplicity, don't bother using a tlvlist */
+	aimbs_put16(&fr->data, 0x0001);
+	aimbs_put16(&fr->data, bslen);
+
+	aimbs_putle16(&fr->data, bslen - 2);
+	aimbs_putle32(&fr->data, atoi(sess->sn));
+	aimbs_putle16(&fr->data, 0x003c); /* I command thee. */
+	aimbs_putle16(&fr->data, snacid); /* eh. */
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+faim_export int aim_icq_ackofflinemsgs(aim_session_t *sess)
+{
+	aim_conn_t *conn;
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	int bslen;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015)))
+		return -EINVAL;
+
+	bslen = 2 + 4 + 2 + 2;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid);
+
+	/* For simplicity, don't bother using a tlvlist */
+	aimbs_put16(&fr->data, 0x0001);
+	aimbs_put16(&fr->data, bslen);
+
+	aimbs_putle16(&fr->data, bslen - 2);
+	aimbs_putle32(&fr->data, atoi(sess->sn));
+	aimbs_putle16(&fr->data, 0x003e); /* I command thee. */
+	aimbs_putle16(&fr->data, snacid); /* eh. */
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+faim_export int aim_icq_sendxmlreq(aim_session_t *sess, const char *xml)
+{
+	aim_conn_t *conn;
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	int bslen;
+
+	if (!xml || !strlen(xml))
+		return -EINVAL;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015)))
+		return -EINVAL;
+
+	bslen = 2 + 10 + 2 + strlen(xml) + 1;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid);
+
+	/* For simplicity, don't bother using a tlvlist */
+	aimbs_put16(&fr->data, 0x0001);
+	aimbs_put16(&fr->data, bslen);
+
+	aimbs_putle16(&fr->data, bslen - 2);
+	aimbs_putle32(&fr->data, atoi(sess->sn));
+	aimbs_putle16(&fr->data, 0x07d0); /* I command thee. */
+	aimbs_putle16(&fr->data, snacid); /* eh. */
+	aimbs_putle16(&fr->data, 0x0998); /* shrug. */
+	aimbs_putle16(&fr->data, strlen(xml) + 1);
+	aimbs_putraw(&fr->data, xml, strlen(xml) + 1);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+faim_export int aim_icq_getsimpleinfo(aim_session_t *sess, const char *uin)
+{
+	aim_conn_t *conn;
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	int bslen;
+
+	if (!uin || uin[0] < '0' || uin[0] > '9')
+		return -EINVAL;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015)))
+		return -EINVAL;
+
+	bslen = 2 + 4 + 2 + 2 + 2 + 4;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid);
+
+	/* For simplicity, don't bother using a tlvlist */
+	aimbs_put16(&fr->data, 0x0001);
+	aimbs_put16(&fr->data, bslen);
+
+	aimbs_putle16(&fr->data, bslen - 2);
+	aimbs_putle32(&fr->data, atoi(sess->sn));
+	aimbs_putle16(&fr->data, 0x07d0); /* I command thee. */
+	aimbs_putle16(&fr->data, snacid); /* eh. */
+	aimbs_putle16(&fr->data, 0x051f); /* shrug. */
+	aimbs_putle32(&fr->data, atoi(uin));
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * Response to 15/2, contains an ICQ packet.
+ */
+static int icqresponse(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	int ret = 0;
+	aim_tlvlist_t *tl;
+	aim_tlv_t *datatlv;
+	aim_bstream_t qbs;
+	fu32_t ouruin;
+	fu16_t cmdlen, cmd, reqid;
+
+	if (!(tl = aim_readtlvchain(bs)) || !(datatlv = aim_gettlv(tl, 0x0001, 1))) {
+		aim_freetlvchain(&tl);
+		faimdprintf(sess, 0, "corrupt ICQ response\n");
+		return 0;
+	}
+
+	aim_bstream_init(&qbs, datatlv->value, datatlv->length);
+
+	cmdlen = aimbs_getle16(&qbs);
+	ouruin = aimbs_getle32(&qbs);
+	cmd = aimbs_getle16(&qbs);
+	reqid = aimbs_getle16(&qbs);
+
+	faimdprintf(sess, 1, "icq response: %d bytes, %ld, 0x%04x, 0x%04x\n", cmdlen, ouruin, cmd, reqid);
+
+	if (cmd == 0x0041) {
+		fu16_t msglen;
+		struct aim_icq_offlinemsg msg;
+		aim_rxcallback_t userfunc;
+
+		memset(&msg, 0, sizeof(msg));
+
+		msg.sender = aimbs_getle32(&qbs);
+		msg.year = aimbs_getle16(&qbs);
+		msg.month = aimbs_getle8(&qbs);
+		msg.day = aimbs_getle8(&qbs);
+		msg.hour = aimbs_getle8(&qbs);
+		msg.minute = aimbs_getle8(&qbs);
+		msg.type = aimbs_getle16(&qbs);
+		msglen = aimbs_getle16(&qbs);
+		msg.msg = aimbs_getstr(&qbs, msglen);
+
+		if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSG)))
+			ret = userfunc(sess, rx, &msg);
+
+		free(msg.msg);
+
+	} else if (cmd == 0x0042) {
+		aim_rxcallback_t userfunc;
+
+		if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSGCOMPLETE)))
+			ret = userfunc(sess, rx);
+	} else if (cmd == 0x07da) {
+		fu16_t subtype;
+
+		subtype = aimbs_getle16(&qbs);
+
+		if (subtype == 0x019a) {
+			fu16_t tlen;
+			struct aim_icq_simpleinfo info;
+			aim_rxcallback_t userfunc;
+
+			memset(&info, 0, sizeof(info));
+
+			aimbs_getle8(&qbs); /* no clue */
+			aimbs_getle16(&qbs); /* no clue */
+			info.uin = aimbs_getle32(&qbs);
+			tlen = aimbs_getle16(&qbs);
+			info.nick = aimbs_getstr(&qbs, tlen);
+			tlen = aimbs_getle16(&qbs);
+			info.first = aimbs_getstr(&qbs, tlen);
+			tlen = aimbs_getle16(&qbs);
+			info.last = aimbs_getstr(&qbs, tlen);
+			tlen = aimbs_getle16(&qbs);
+			info.email = aimbs_getstr(&qbs, tlen);
+			/* no clue what the rest of it is */
+
+			if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_SIMPLEINFO)))
+				ret = userfunc(sess, rx, &info);
+
+			free(info.nick);
+			free(info.first);
+			free(info.last);
+			free(info.email);
+		}
+
+	}
+
+	aim_freetlvchain(&tl);
+
+	return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+	if (snac->subtype == 0x0003)
+		return icqresponse(sess, mod, rx, snac, bs);
+
+	return 0;
+}
+
+faim_internal int icq_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0x0015;
+	mod->version = 0x0001;
+	mod->toolid = 0x0110;
+	mod->toolversion = 0x047b;
+	mod->flags = 0;
+	strncpy(mod->name, "icq", sizeof(mod->name));
+	mod->snachandler = snachandler;
+
+	return 0;
+}
+
+
Index: libfaim/im.c
===================================================================
--- libfaim/im.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/im.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,1823 @@
+/*
+ *  aim_im.c
+ *
+ *  The routines for sending/receiving Instant Messages.
+ *
+ *  Note the term ICBM (Inter-Client Basic Message) which blankets
+ *  all types of genericly routed through-server messages.  Within
+ *  the ICBM types (family 4), a channel is defined.  Each channel
+ *  represents a different type of message.  Channel 1 is used for
+ *  what would commonly be called an "instant message".  Channel 2
+ *  is used for negotiating "rendezvous".  These transactions end in
+ *  something more complex happening, such as a chat invitation, or
+ *  a file transfer.
+ *
+ *  In addition to the channel, every ICBM contains a cookie.  For
+ *  standard IMs, these are only used for error messages.  However,
+ *  the more complex rendezvous messages make suitably more complex
+ *  use of this field.
+ *
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+/*
+ * Takes a msghdr (and a length) and returns a client type
+ * code.  Note that this is *only a guess* and has a low likelihood
+ * of actually being accurate.
+ *
+ * Its based on experimental data, with the help of Eric Warmenhoven
+ * who seems to have collected a wide variety of different AIM clients.
+ *
+ *
+ * Heres the current collection:
+ *  0501 0003 0101 0101 01       AOL Mobile Communicator, WinAIM 1.0.414
+ *  0501 0003 0101 0201 01       WinAIM 2.0.847, 2.1.1187, 3.0.1464, 
+ *                                      4.3.2229, 4.4.2286
+ *  0501 0004 0101 0102 0101     WinAIM 4.1.2010, libfaim (right here)
+ *  0501 0001 0101 01            AOL v6.0, CompuServe 2000 v6.0, any
+ *                                      TOC client
+ *
+ * Note that in this function, only the feature bytes are tested, since
+ * the rest will always be the same.
+ *
+ */
+faim_export fu16_t aim_fingerprintclient(fu8_t *msghdr, int len)
+{
+	static const struct {
+		fu16_t clientid;
+		int len;
+		fu8_t data[10];
+	} fingerprints[] = {
+		/* AOL Mobile Communicator, WinAIM 1.0.414 */
+		{ AIM_CLIENTTYPE_MC, 
+		  3, {0x01, 0x01, 0x01}},
+
+		/* WinAIM 2.0.847, 2.1.1187, 3.0.1464, 4.3.2229, 4.4.2286 */
+		{ AIM_CLIENTTYPE_WINAIM, 
+		  3, {0x01, 0x01, 0x02}},
+
+		/* WinAIM 4.1.2010, libfaim */
+		{ AIM_CLIENTTYPE_WINAIM41,
+		  4, {0x01, 0x01, 0x01, 0x02}},
+
+		/* AOL v6.0, CompuServe 2000 v6.0, any TOC client */
+		{ AIM_CLIENTTYPE_AOL_TOC,
+		  1, {0x01}},
+
+		{ 0, 0}
+	};
+	int i;
+
+	if (!msghdr || (len <= 0))
+		return AIM_CLIENTTYPE_UNKNOWN;
+
+	for (i = 0; fingerprints[i].len; i++) {
+		if (fingerprints[i].len != len)
+			continue;
+		if (memcmp(fingerprints[i].data, msghdr, fingerprints[i].len) == 0)
+			return fingerprints[i].clientid;
+	}
+
+	return AIM_CLIENTTYPE_UNKNOWN;
+}
+
+/* This should be endian-safe now... but who knows... */
+faim_export fu16_t aim_iconsum(const fu8_t *buf, int buflen)
+{
+	fu32_t sum;
+	int i;
+
+	for (i = 0, sum = 0; i + 1 < buflen; i += 2)
+		sum += (buf[i+1] << 8) + buf[i];
+	if (i < buflen)
+		sum += buf[i];
+
+	sum = ((sum & 0xffff0000) >> 16) + (sum & 0x0000ffff);
+
+	return (fu16_t)sum;
+}
+
+/*
+ * Send an ICBM (instant message).  
+ *
+ *
+ * Possible flags:
+ *   AIM_IMFLAGS_AWAY  -- Marks the message as an autoresponse
+ *   AIM_IMFLAGS_ACK   -- Requests that the server send an ack
+ *                        when the message is received (of type 0x0004/0x000c)
+ *   AIM_IMFLAGS_OFFLINE--If destination is offline, store it until they are
+ *                        online (probably ICQ only).
+ *   AIM_IMFLAGS_UNICODE--Instead of ASCII7, the passed message is
+ *                        made up of UNICODE duples.  If you set
+ *                        this, you'd better be damn sure you know
+ *                        what you're doing.
+ *   AIM_IMFLAGS_ISO_8859_1 -- The message contains the ASCII8 subset
+ *                        known as ISO-8859-1.  
+ *
+ * Generally, you should use the lowest encoding possible to send
+ * your message.  If you only use basic punctuation and the generic
+ * Latin alphabet, use ASCII7 (no flags).  If you happen to use non-ASCII7
+ * characters, but they are all clearly defined in ISO-8859-1, then 
+ * use that.  Keep in mind that not all characters in the PC ASCII8
+ * character set are defined in the ISO standard. For those cases (most
+ * notably when the (r) symbol is used), you must use the full UNICODE
+ * encoding for your message.  In UNICODE mode, _all_ characters must
+ * occupy 16bits, including ones that are not special.  (Remember that
+ * the first 128 UNICODE symbols are equivelent to ASCII7, however they
+ * must be prefixed with a zero high order byte.)
+ *
+ * I strongly discourage the use of UNICODE mode, mainly because none
+ * of the clients I use can parse those messages (and besides that,
+ * wchars are difficult and non-portable to handle in most UNIX environments).
+ * If you really need to include special characters, use the HTML UNICODE 
+ * entities.  These are of the form &#2026; where 2026 is the hex 
+ * representation of the UNICODE index (in this case, UNICODE 
+ * "Horizontal Ellipsis", or 133 in in ASCII8).
+ *
+ * Implementation note:  Since this is one of the most-used functions
+ * in all of libfaim, it is written with performance in mind.  As such,
+ * it is not as clear as it could be in respect to how this message is
+ * supposed to be layed out. Most obviously, tlvlists should be used 
+ * instead of writing out the bytes manually. 
+ *
+ * XXX more precise verification that we never send SNACs larger than 8192
+ * XXX check SNAC size for multipart
+ *
+ */
+faim_export int aim_send_im_ext(aim_session_t *sess, struct aim_sendimext_args *args)
+{
+	static const fu8_t deffeatures[] = {
+		0x01, 0x01, 0x01, 0x02
+	};
+	aim_conn_t *conn;
+	int i, msgtlvlen;
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+		return -EINVAL;
+
+	if (!args)
+		return -EINVAL;
+
+	if (args->flags & AIM_IMFLAGS_MULTIPART) {
+		if (args->mpmsg->numparts <= 0)
+			return -EINVAL;
+	} else {
+		if (!args->msg || (args->msglen <= 0))
+			return -EINVAL;
+
+		if (args->msglen >= MAXMSGLEN)
+			return -E2BIG;
+	}
+
+	/* Painfully calculate the size of the message TLV */
+	msgtlvlen = 1 + 1; /* 0501 */
+
+	if (args->flags & AIM_IMFLAGS_CUSTOMFEATURES)
+		msgtlvlen += 2 + args->featureslen;
+	else
+		msgtlvlen += 2 + sizeof(deffeatures);
+
+	if (args->flags & AIM_IMFLAGS_MULTIPART) {
+		aim_mpmsg_section_t *sec;
+
+		for (sec = args->mpmsg->parts; sec; sec = sec->next) {
+			msgtlvlen += 2 /* 0101 */ + 2 /* block len */;
+			msgtlvlen += 4 /* charset */ + sec->datalen;
+		}
+
+	} else {
+		msgtlvlen += 2 /* 0101 */ + 2 /* block len */;
+		msgtlvlen += 4 /* charset */ + args->msglen;
+	}
+
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, msgtlvlen+128)))
+		return -ENOMEM;
+
+	/* XXX should be optional */	
+	snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, args->destsn, strlen(args->destsn)+1);
+	aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
+
+	/* 
+	 * Generate a random message cookie 
+	 *
+	 * We could cache these like we do SNAC IDs.  (In fact, it 
+	 * might be a good idea.)  In the message error functions, 
+	 * the 8byte message cookie is returned as well as the 
+	 * SNAC ID.
+	 *
+	 */
+	for (i = 0; i < 8; i++)
+		aimbs_put8(&fr->data, (fu8_t) rand());
+
+	/*
+	 * Channel ID
+	 */
+	aimbs_put16(&fr->data, 0x0001);
+
+	/*
+	 * Destination SN (prepended with byte length)
+	 */
+	aimbs_put8(&fr->data, strlen(args->destsn));
+	aimbs_putraw(&fr->data, args->destsn, strlen(args->destsn));
+
+	/*
+	 * Message TLV (type 2).
+	 */
+	aimbs_put16(&fr->data, 0x0002);
+	aimbs_put16(&fr->data, msgtlvlen);
+
+	/*
+	 * Features 
+	 *
+	 */
+	aimbs_put8(&fr->data, 0x05);
+	aimbs_put8(&fr->data, 0x01);
+
+	if (args->flags & AIM_IMFLAGS_CUSTOMFEATURES) {
+		aimbs_put16(&fr->data, args->featureslen);
+		aimbs_putraw(&fr->data, args->features, args->featureslen);
+	} else {
+		aimbs_put16(&fr->data, sizeof(deffeatures));
+		aimbs_putraw(&fr->data, deffeatures, sizeof(deffeatures));
+	}
+
+	if (args->flags & AIM_IMFLAGS_MULTIPART) {
+		aim_mpmsg_section_t *sec;
+
+		for (sec = args->mpmsg->parts; sec; sec = sec->next) {
+			aimbs_put16(&fr->data, 0x0101);
+			aimbs_put16(&fr->data, sec->datalen + 4);
+			aimbs_put16(&fr->data, sec->charset);
+			aimbs_put16(&fr->data, sec->charsubset);
+			aimbs_putraw(&fr->data, sec->data, sec->datalen);
+		}
+
+	} else {
+
+		aimbs_put16(&fr->data, 0x0101);
+
+		/* 
+		 * Message block length.
+		 */
+		aimbs_put16(&fr->data, args->msglen + 0x04);
+
+		/*
+		 * Character set.
+		 */
+		if (args->flags & AIM_IMFLAGS_CUSTOMCHARSET) {
+
+			aimbs_put16(&fr->data, args->charset);
+			aimbs_put16(&fr->data, args->charsubset);
+
+		} else {
+			if (args->flags & AIM_IMFLAGS_UNICODE)
+				aimbs_put16(&fr->data, 0x0002);
+			else if (args->flags & AIM_IMFLAGS_ISO_8859_1)
+				aimbs_put16(&fr->data, 0x0003);
+			else
+				aimbs_put16(&fr->data, 0x0000);
+
+			aimbs_put16(&fr->data, 0x0000);
+		}
+
+		/*
+		 * Message.  Not terminated.
+		 */
+		aimbs_putraw(&fr->data, args->msg, args->msglen);
+	}
+
+	/*
+	 * Set the Request Acknowledge flag.  
+	 */
+	if (args->flags & AIM_IMFLAGS_ACK) {
+		aimbs_put16(&fr->data, 0x0003);
+		aimbs_put16(&fr->data, 0x0000);
+	}
+
+	/*
+	 * Set the Autoresponse flag.
+	 */
+	if (args->flags & AIM_IMFLAGS_AWAY) {
+		aimbs_put16(&fr->data, 0x0004);
+		aimbs_put16(&fr->data, 0x0000);
+	}
+
+	if (args->flags & AIM_IMFLAGS_OFFLINE) {
+		aimbs_put16(&fr->data, 0x0006);
+		aimbs_put16(&fr->data, 0x0000);
+	}
+
+	/*
+	 * Set the I HAVE A REALLY PURTY ICON flag.
+	 */
+	if (args->flags & AIM_IMFLAGS_HASICON) {
+		aimbs_put16(&fr->data, 0x0008);
+		aimbs_put16(&fr->data, 0x000c);
+		aimbs_put32(&fr->data, args->iconlen);
+		aimbs_put16(&fr->data, 0x0001);
+		aimbs_put16(&fr->data, args->iconsum);
+		aimbs_put32(&fr->data, args->iconstamp);
+	}
+
+	/*
+	 * Set the Buddy Icon Requested flag.
+	 */
+	if (args->flags & AIM_IMFLAGS_BUDDYREQ) {
+		aimbs_put16(&fr->data, 0x0009);
+		aimbs_put16(&fr->data, 0x0000);
+	}
+
+	aim_tx_enqueue(sess, fr);
+
+	if (!(sess->flags & AIM_SESS_FLAGS_DONTTIMEOUTONICBM))
+		aim_cleansnacs(sess, 60); /* clean out SNACs over 60sec old */
+
+	return 0;
+}
+
+/*
+ * Simple wrapper for aim_send_im_ext() 
+ *
+ * You cannot use aim_send_im if you need the HASICON flag.  You must
+ * use aim_send_im_ext directly for that.
+ *
+ * aim_send_im also cannot be used if you require UNICODE messages, because
+ * that requires an explicit message length.  Use aim_send_im_ext().
+ *
+ */
+faim_export int aim_send_im(aim_session_t *sess, const char *destsn, fu16_t flags, const char *msg)
+{
+	struct aim_sendimext_args args;
+
+	args.destsn = destsn;
+	args.flags = flags;
+	args.msg = msg;
+	args.msglen = strlen(msg);
+
+	/* Make these don't get set by accident -- they need aim_send_im_ext */
+	args.flags &= ~(AIM_IMFLAGS_CUSTOMFEATURES | AIM_IMFLAGS_HASICON | AIM_IMFLAGS_MULTIPART);
+
+	return aim_send_im_ext(sess, &args);
+}
+
+/*
+ * This is also performance sensitive. (If you can believe it...)
+ *
+ */
+faim_export int aim_send_icon(aim_session_t *sess, const char *sn, const fu8_t *icon, int iconlen, time_t stamp, fu16_t iconsum)
+{
+	aim_conn_t *conn;
+	int i;
+	fu8_t ck[8];
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+		return -EINVAL;
+
+	if (!sn || !icon || (iconlen <= 0) || (iconlen >= MAXICONLEN))
+		return -EINVAL;
+
+	for (i = 0; i < 8; i++)
+		aimutil_put8(ck+i, (fu8_t) rand());
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+8+2+1+strlen(sn)+2+2+2+8+16+2+2+2+2+2+2+2+4+4+4+iconlen+strlen(AIM_ICONIDENT)+2+2)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
+
+	/*
+	 * Cookie
+	 */
+	aimbs_putraw(&fr->data, ck, 8);
+
+	/*
+	 * Channel (2)
+	 */
+	aimbs_put16(&fr->data, 0x0002);
+
+	/*
+	 * Dest sn
+	 */
+	aimbs_put8(&fr->data, strlen(sn));
+	aimbs_putraw(&fr->data, sn, strlen(sn));
+
+	/*
+	 * TLV t(0005)
+	 *
+	 * Encompasses everything below.
+	 */
+	aimbs_put16(&fr->data, 0x0005);
+	aimbs_put16(&fr->data, 2+8+16+6+4+4+iconlen+4+4+4+strlen(AIM_ICONIDENT));
+
+	aimbs_put16(&fr->data, 0x0000);
+	aimbs_putraw(&fr->data, ck, 8);
+	aim_putcap(&fr->data, AIM_CAPS_BUDDYICON);
+
+	/* TLV t(000a) */
+	aimbs_put16(&fr->data, 0x000a);
+	aimbs_put16(&fr->data, 0x0002);
+	aimbs_put16(&fr->data, 0x0001);
+
+	/* TLV t(000f) */
+	aimbs_put16(&fr->data, 0x000f);
+	aimbs_put16(&fr->data, 0x0000);
+
+	/* TLV t(2711) */
+	aimbs_put16(&fr->data, 0x2711);
+	aimbs_put16(&fr->data, 4+4+4+iconlen+strlen(AIM_ICONIDENT));
+	aimbs_put16(&fr->data, 0x0000);
+	aimbs_put16(&fr->data, iconsum);
+	aimbs_put32(&fr->data, iconlen);
+	aimbs_put32(&fr->data, stamp);
+	aimbs_putraw(&fr->data, icon, iconlen);
+	aimbs_putraw(&fr->data, AIM_ICONIDENT, strlen(AIM_ICONIDENT));
+
+	/* TLV t(0003) */
+	aimbs_put16(&fr->data, 0x0003);
+	aimbs_put16(&fr->data, 0x0000);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * This only works for ICQ 2001b (thats 2001 not 2000).  Better, only
+ * send it to clients advertising the RTF capability.  In fact, if you send
+ * it to a client that doesn't support that capability, the server will gladly
+ * bounce it back to you.
+ *
+ * You'd think this would be in icq.c, but, well, I'm trying to stick with
+ * the one-group-per-file scheme as much as possible.  This could easily
+ * be an exception, since Rendezvous IMs are external of the Oscar core, 
+ * and therefore are undefined.  Really I just need to think of a good way to
+ * make an interface similar to what AOL actually uses.  But I'm not using COM.
+ *
+ */
+faim_export int aim_send_rtfmsg(aim_session_t *sess, struct aim_sendrtfmsg_args *args)
+{
+	const char rtfcap[] = {"{97B12751-243C-4334-AD22-D6ABF73F1492}"}; /* AIM_CAPS_ICQRTF capability in string form */
+	aim_conn_t *conn;
+	int i;
+	fu8_t ck[8];
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	int servdatalen;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+		return -EINVAL;
+
+	if (!args || !args->destsn || !args->rtfmsg)
+		return -EINVAL;
+
+	servdatalen = 2+2+16+2+4+1+2  +  2+2+4+4+4  +  2+4+2+strlen(args->rtfmsg)+1  +  4+4+4+strlen(rtfcap)+1;
+
+	for (i = 0; i < 8; i++)
+		aimutil_put8(ck+i, (fu8_t) rand());
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+128+servdatalen)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
+
+	/*
+	 * Cookie
+	 */
+	aimbs_putraw(&fr->data, ck, 8);
+
+	/*
+	 * Channel (2)
+	 */
+	aimbs_put16(&fr->data, 0x0002);
+
+	/*
+	 * Dest sn
+	 */
+	aimbs_put8(&fr->data, strlen(args->destsn));
+	aimbs_putraw(&fr->data, args->destsn, strlen(args->destsn));
+
+	/*
+	 * TLV t(0005)
+	 *
+	 * Encompasses everything below.
+	 */
+	aimbs_put16(&fr->data, 0x0005);
+	aimbs_put16(&fr->data, 2+8+16  +  2+2+2  +  2+2  +  2+2+servdatalen);
+
+	aimbs_put16(&fr->data, 0x0000);
+	aimbs_putraw(&fr->data, ck, 8);
+	aim_putcap(&fr->data, AIM_CAPS_ICQSERVERRELAY);
+
+	/*
+	 * t(000a) l(0002) v(0001)
+	 */
+	aimbs_put16(&fr->data, 0x000a);
+	aimbs_put16(&fr->data, 0x0002);
+	aimbs_put16(&fr->data, 0x0001);
+
+	/*
+	 * t(000f) l(0000) v()
+	 */
+	aimbs_put16(&fr->data, 0x000f);
+	aimbs_put16(&fr->data, 0x0000);
+
+	/*
+	 * Service Data TLV
+	 */
+	aimbs_put16(&fr->data, 0x2711);
+	aimbs_put16(&fr->data, servdatalen);
+
+	aimbs_putle16(&fr->data, 11 + 16 /* 11 + (sizeof CLSID) */);
+	aimbs_putle16(&fr->data, 9);
+	aim_putcap(&fr->data, AIM_CAPS_EMPTY);
+	aimbs_putle16(&fr->data, 0);
+	aimbs_putle32(&fr->data, 0);
+	aimbs_putle8(&fr->data, 0);
+	aimbs_putle16(&fr->data, 0x03ea); /* trid1 */
+
+	aimbs_putle16(&fr->data, 14);
+	aimbs_putle16(&fr->data, 0x03eb); /* trid2 */
+	aimbs_putle32(&fr->data, 0);
+	aimbs_putle32(&fr->data, 0);
+	aimbs_putle32(&fr->data, 0);
+
+	aimbs_putle16(&fr->data, 0x0001);
+	aimbs_putle32(&fr->data, 0);
+	aimbs_putle16(&fr->data, strlen(args->rtfmsg)+1);
+	aimbs_putraw(&fr->data, args->rtfmsg, strlen(args->rtfmsg)+1);
+
+	aimbs_putle32(&fr->data, args->fgcolor);
+	aimbs_putle32(&fr->data, args->bgcolor);
+	aimbs_putle32(&fr->data, strlen(rtfcap)+1);
+	aimbs_putraw(&fr->data, rtfcap, strlen(rtfcap)+1);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+faim_internal int aim_request_directim(aim_session_t *sess, const char *destsn, fu8_t *ip, fu16_t port, fu8_t *ckret)
+{
+	aim_conn_t *conn;
+	fu8_t ck[8];
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	aim_tlvlist_t *tl = NULL, *itl = NULL;
+	int hdrlen, i;
+	fu8_t *hdr;
+	aim_bstream_t hdrbs;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 256+strlen(destsn))))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
+
+	/* 
+	 * Generate a random message cookie 
+	 *
+	 * This cookie needs to be alphanumeric and NULL-terminated to be 
+	 * TOC-compatible.
+	 *
+	 * XXX have I mentioned these should be generated in msgcookie.c?
+	 *
+	 */
+	for (i = 0; i < 7; i++)
+	       	ck[i] = 0x30 + ((fu8_t) rand() % 10);
+	ck[7] = '\0';
+
+	if (ckret)
+		memcpy(ckret, ck, 8);
+
+	/* Cookie */
+	aimbs_putraw(&fr->data, ck, 8);
+
+	/* Channel */
+	aimbs_put16(&fr->data, 0x0002);
+
+	/* Destination SN */
+	aimbs_put8(&fr->data, strlen(destsn));
+	aimbs_putraw(&fr->data, destsn, strlen(destsn));
+
+	aim_addtlvtochain_noval(&tl, 0x0003);
+
+	hdrlen = 2+8+16+6+8+6+4;
+	hdr = malloc(hdrlen);
+	aim_bstream_init(&hdrbs, hdr, hdrlen);
+
+	aimbs_put16(&hdrbs, 0x0000);
+	aimbs_putraw(&hdrbs, ck, 8);
+	aim_putcap(&hdrbs, AIM_CAPS_IMIMAGE);
+
+	aim_addtlvtochain16(&itl, 0x000a, 0x0001);
+	aim_addtlvtochain_raw(&itl, 0x0003, 4, ip);
+	aim_addtlvtochain16(&itl, 0x0005, port);
+	aim_addtlvtochain_noval(&itl, 0x000f);
+	
+	aim_writetlvchain(&hdrbs, &itl);
+
+	aim_addtlvtochain_raw(&tl, 0x0005, aim_bstream_curpos(&hdrbs), hdr);
+
+	aim_writetlvchain(&fr->data, &tl);
+
+	free(hdr);
+	aim_freetlvchain(&itl);
+	aim_freetlvchain(&tl);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+faim_internal int aim_request_sendfile(aim_session_t *sess, const char *sn, const char *filename, fu16_t numfiles, fu32_t totsize, fu8_t *ip, fu16_t port, fu8_t *ckret)
+{
+	aim_conn_t *conn;
+	int i;
+	fu8_t ck[8];
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+		return -EINVAL;
+
+	if (!sn || !filename)
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+8+2+1+strlen(sn)+2+2+2+8+16+6+8+6+4+2+2+2+2+4+strlen(filename)+4)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
+
+	for (i = 0; i < 7; i++)
+		aimutil_put8(ck+i, 0x30 + ((fu8_t) rand() % 10));
+	ck[7] = '\0';
+
+	if (ckret)
+		memcpy(ckret, ck, 8);
+
+	/*
+	 * Cookie
+	 */
+	aimbs_putraw(&fr->data, ck, 8);
+
+	/*
+	 * Channel (2)
+	 */
+	aimbs_put16(&fr->data, 0x0002);
+
+	/*
+	 * Dest sn
+	 */
+	aimbs_put8(&fr->data, strlen(sn));
+	aimbs_putraw(&fr->data, sn, strlen(sn));
+
+	/*
+	 * TLV t(0005)
+	 *
+	 * Encompasses everything below. Gee.
+	 */
+	aimbs_put16(&fr->data, 0x0005);
+	aimbs_put16(&fr->data, 2+8+16+6+8+6+4+2+2+2+2+4+strlen(filename)+4);
+
+	aimbs_put16(&fr->data, 0x0000);
+	aimbs_putraw(&fr->data, ck, 8);
+	aim_putcap(&fr->data, AIM_CAPS_SENDFILE);
+
+	/* TLV t(000a) */
+	aimbs_put16(&fr->data, 0x000a);
+	aimbs_put16(&fr->data, 0x0002);
+	aimbs_put16(&fr->data, 0x0001);
+
+	/* TLV t(0003) (IP) */
+	aimbs_put16(&fr->data, 0x0003);
+	aimbs_put16(&fr->data, 0x0004);
+	aimbs_putraw(&fr->data, ip, 4);
+
+	/* TLV t(0005) (port) */
+	aimbs_put16(&fr->data, 0x0005);
+	aimbs_put16(&fr->data, 0x0002);
+	aimbs_put16(&fr->data, port);
+
+	/* TLV t(000f) */
+	aimbs_put16(&fr->data, 0x000f);
+	aimbs_put16(&fr->data, 0x0000);
+
+	/* TLV t(2711) */
+	aimbs_put16(&fr->data, 0x2711);
+	aimbs_put16(&fr->data, 2+2+4+strlen(filename)+4);
+
+	/* ? */
+	aimbs_put16(&fr->data, 0x0001);
+	aimbs_put16(&fr->data, numfiles);
+	aimbs_put32(&fr->data, totsize);
+	aimbs_putraw(&fr->data, filename, strlen(filename));
+
+	/* ? */
+	aimbs_put32(&fr->data, 0x00000000);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+static int outgoingim(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	int i, ret = 0;
+	aim_rxcallback_t userfunc;
+	fu8_t cookie[8];
+	fu16_t channel;
+	aim_tlvlist_t *tlvlist;
+	char *sn;
+	int snlen;
+	fu16_t icbmflags = 0;
+	fu8_t flag1 = 0, flag2 = 0;
+	fu8_t *msg = NULL;
+	aim_tlv_t *msgblock;
+
+	/* ICBM Cookie. */
+	for (i = 0; i < 8; i++)
+		cookie[i] = aimbs_get8(bs);
+
+	/* Channel ID */
+	channel = aimbs_get16(bs);
+
+	if (channel != 0x01) {
+		faimdprintf(sess, 0, "icbm: ICBM recieved on unsupported channel.  Ignoring. (chan = %04x)\n", channel);
+		return 0;
+	}
+
+	snlen = aimbs_get8(bs);
+	sn = aimbs_getstr(bs, snlen);
+
+	tlvlist = aim_readtlvchain(bs);
+
+	if (aim_gettlv(tlvlist, 0x0003, 1))
+		icbmflags |= AIM_IMFLAGS_ACK;
+	if (aim_gettlv(tlvlist, 0x0004, 1))
+		icbmflags |= AIM_IMFLAGS_AWAY;
+
+	if ((msgblock = aim_gettlv(tlvlist, 0x0002, 1))) {
+		aim_bstream_t mbs;
+		int featurelen, msglen;
+
+		aim_bstream_init(&mbs, msgblock->value, msgblock->length);
+
+		aimbs_get8(&mbs);
+		aimbs_get8(&mbs);
+		for (featurelen = aimbs_get16(&mbs); featurelen; featurelen--)
+			aimbs_get8(&mbs);
+		aimbs_get8(&mbs);
+		aimbs_get8(&mbs);
+
+		msglen = aimbs_get16(&mbs) - 4; /* final block length */
+
+		flag1 = aimbs_get16(&mbs);
+		flag2 = aimbs_get16(&mbs);
+
+		msg = aimbs_getstr(&mbs, msglen);
+	}
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, channel, sn, msg, icbmflags, flag1, flag2);
+
+	free(sn);
+	aim_freetlvchain(&tlvlist);
+
+	return ret;
+}
+
+/*
+ * Ahh, the joys of nearly ridiculous over-engineering.
+ *
+ * Not only do AIM ICBM's support multiple channels.  Not only do they
+ * support multiple character sets.  But they support multiple character 
+ * sets / encodings within the same ICBM.
+ *
+ * These multipart messages allow for complex space savings techniques, which
+ * seem utterly unnecessary by today's standards.  In fact, there is only
+ * one client still in popular use that still uses this method: AOL for the
+ * Macintosh, Version 5.0.  Obscure, yes, I know.  
+ *
+ * In modern (non-"legacy") clients, if the user tries to send a character
+ * that is not ISO-8859-1 or ASCII, the client will send the entire message
+ * as UNICODE, meaning that every character in the message will occupy the
+ * full 16 bit UNICODE field, even if the high order byte would be zero.
+ * Multipart messages prevent this wasted space by allowing the client to
+ * only send the characters in UNICODE that need to be sent that way, and
+ * the rest of the message can be sent in whatever the native character 
+ * set is (probably ASCII).
+ *
+ * An important note is that sections will be displayed in the order that
+ * they appear in the ICBM.  There is no facility for merging or rearranging
+ * sections at run time.  So if you have, say, ASCII then UNICODE then ASCII,
+ * you must supply two ASCII sections with a UNICODE in the middle, and incur
+ * the associated overhead.
+ *
+ * Normally I would have laughed and given a firm 'no' to supporting this
+ * seldom-used feature, but something is attracting me to it.  In the future,
+ * it may be possible to abuse this to send mixed-media messages to other
+ * open source clients (like encryption or something) -- see faimtest for
+ * examples of how to do this.
+ *
+ * I would definitly recommend avoiding this feature unless you really
+ * know what you are doing, and/or you have something neat to do with it.
+ *
+ */
+faim_export int aim_mpmsg_init(aim_session_t *sess, aim_mpmsg_t *mpm)
+{
+
+	memset(mpm, 0, sizeof(aim_mpmsg_t));
+
+	return 0;
+}
+
+static int mpmsg_addsection(aim_session_t *sess, aim_mpmsg_t *mpm, fu16_t charset, fu16_t charsubset, fu8_t *data, fu16_t datalen)
+{
+	aim_mpmsg_section_t *sec; 
+	
+	if (!(sec = malloc(sizeof(aim_mpmsg_section_t))))
+		return -1;
+
+	sec->charset = charset;
+	sec->charsubset = charsubset;
+	sec->data = data;
+	sec->datalen = datalen;
+	sec->next = NULL;
+
+	if (!mpm->parts)
+		mpm->parts = sec;
+	else {
+		aim_mpmsg_section_t *cur;
+
+		for (cur = mpm->parts; cur->next; cur = cur->next)
+			;
+		cur->next = sec;
+	}
+
+	mpm->numparts++;
+
+	return 0;
+}
+
+faim_export int aim_mpmsg_addraw(aim_session_t *sess, aim_mpmsg_t *mpm, fu16_t charset, fu16_t charsubset, const fu8_t *data, fu16_t datalen)
+{
+	fu8_t *dup;
+
+	if (!(dup = malloc(datalen)))
+		return -1;
+	memcpy(dup, data, datalen);
+
+	if (mpmsg_addsection(sess, mpm, charset, charsubset, dup, datalen) == -1) {
+		free(dup);
+		return -1;
+	}
+
+	return 0;
+}
+
+/* XXX should provide a way of saying ISO-8859-1 specifically */
+faim_export int aim_mpmsg_addascii(aim_session_t *sess, aim_mpmsg_t *mpm, const char *ascii)
+{
+	fu8_t *dup;
+
+	if (!(dup = strdup(ascii))) 
+		return -1;
+
+	if (mpmsg_addsection(sess, mpm, 0x0000, 0x0000, dup, strlen(ascii)) == -1) {
+		free(dup);
+		return -1;
+	}
+
+	return 0;
+}
+
+faim_export int aim_mpmsg_addunicode(aim_session_t *sess, aim_mpmsg_t *mpm, const fu16_t *unicode, fu16_t unicodelen)
+{
+	fu8_t *buf;
+	aim_bstream_t bs;
+	int i;
+
+	if (!(buf = malloc(unicodelen * 2)))
+		return -1;
+
+	aim_bstream_init(&bs, buf, unicodelen * 2);
+
+	/* We assume unicode is in /host/ byte order -- convert to network */
+	for (i = 0; i < unicodelen; i++)
+		aimbs_put16(&bs, unicode[i]);
+
+	if (mpmsg_addsection(sess, mpm, 0x0002, 0x0000, buf, aim_bstream_curpos(&bs)) == -1) {
+		free(buf);
+		return -1;
+	}
+	
+	return 0;
+}
+
+faim_export void aim_mpmsg_free(aim_session_t *sess, aim_mpmsg_t *mpm)
+{
+	aim_mpmsg_section_t *cur;
+
+	for (cur = mpm->parts; cur; ) {
+		aim_mpmsg_section_t *tmp;
+		
+		tmp = cur->next;
+		free(cur->data);
+		free(cur);
+		cur = tmp;
+	}
+	
+	mpm->numparts = 0;
+	mpm->parts = NULL;
+
+	return;
+}
+
+/*
+ * Start by building the multipart structures, then pick the first 
+ * human-readable section and stuff it into args->msg so no one gets
+ * suspicious.
+ *
+ */
+static int incomingim_ch1_parsemsgs(aim_session_t *sess, fu8_t *data, int len, struct aim_incomingim_ch1_args *args)
+{
+	static const fu16_t charsetpri[] = {
+		0x0000, /* ASCII first */
+		0x0003, /* then ISO-8859-1 */
+		0x0002, /* UNICODE as last resort */
+	};
+	static const int charsetpricount = 3;
+	int i;
+	aim_bstream_t mbs;
+	aim_mpmsg_section_t *sec;
+
+	aim_bstream_init(&mbs, data, len);
+
+	while (aim_bstream_empty(&mbs)) {
+		fu16_t msglen, flag1, flag2;
+		fu8_t *msgbuf;
+
+		aimbs_get8(&mbs); /* 01 */
+		aimbs_get8(&mbs); /* 01 */
+
+		/* Message string length, including character set info. */
+		msglen = aimbs_get16(&mbs);
+
+		/* Character set info */
+		flag1 = aimbs_get16(&mbs);
+		flag2 = aimbs_get16(&mbs);
+
+		/* Message. */
+		msglen -= 4;
+
+		/*
+		 * For now, we don't care what the encoding is.  Just copy
+		 * it into a multipart struct and deal with it later. However,
+		 * always pad the ending with a NULL.  This makes it easier
+		 * to treat ASCII sections as strings.  It won't matter for
+		 * UNICODE or binary data, as you should never read past
+		 * the specified data length, which will not include the pad.
+		 *
+		 * XXX There's an API bug here.  For sending, the UNICODE is
+		 * given in host byte order (aim_mpmsg_addunicode), but here
+		 * the received messages are given in network byte order.
+		 *
+		 */
+		msgbuf = aimbs_getstr(&mbs, msglen);
+		mpmsg_addsection(sess, &args->mpmsg, flag1, flag2, msgbuf, msglen);
+
+	} /* while */
+
+	args->icbmflags |= AIM_IMFLAGS_MULTIPART; /* always set */
+
+	/*
+	 * Clients that support multiparts should never use args->msg, as it
+	 * will point to an arbitrary section.
+	 *
+	 * Here, we attempt to provide clients that do not support multipart
+	 * messages with something to look at -- hopefully a human-readable
+	 * string.  But, failing that, a UNICODE message, or nothing at all.
+	 *
+	 * Which means that even if args->msg is NULL, it does not mean the
+	 * message was blank.
+	 *
+	 */
+	for (i = 0; i < charsetpricount; i++) {
+		for (sec = args->mpmsg.parts; sec; sec = sec->next) {
+
+			if (sec->charset != charsetpri[i])
+				continue;
+
+			/* Great. We found one.  Fill it in. */
+			args->charset = sec->charset;
+			args->charsubset = sec->charsubset;
+			args->icbmflags |= AIM_IMFLAGS_CUSTOMCHARSET;
+
+			/* Set up the simple flags */
+			if (args->charset == 0x0000)
+				; /* ASCII */
+			else if (args->charset == 0x0002)
+				args->icbmflags |= AIM_IMFLAGS_UNICODE;
+			else if (args->charset == 0x0003)
+				args->icbmflags |= AIM_IMFLAGS_ISO_8859_1;
+			else if (args->charset == 0xffff)
+				; /* no encoding (yeep!) */
+
+			if (args->charsubset == 0x0000)
+				; /* standard subencoding? */
+			else if (args->charsubset == 0x000b)
+				args->icbmflags |= AIM_IMFLAGS_SUBENC_MACINTOSH;
+			else if (args->charsubset == 0xffff)
+				; /* no subencoding */
+#if 0
+			/* XXX this isn't really necesary... */	
+			if (	((args.flag1 != 0x0000) &&
+				 (args.flag1 != 0x0002) &&
+				 (args.flag1 != 0x0003) &&
+				 (args.flag1 != 0xffff)) ||
+				((args.flag2 != 0x0000) &&
+				 (args.flag2 != 0x000b) &&
+				 (args.flag2 != 0xffff))) {
+				faimdprintf(sess, 0, "icbm: **warning: encoding flags are being used! {%04x, %04x}\n", args.flag1, args.flag2);
+			}
+#endif
+
+			args->msg = sec->data;
+			args->msglen = sec->datalen;
+
+			return 0;
+		}
+	}
+
+	/* No human-readable sections found.  Oh well. */
+	args->charset = args->charsubset = 0xffff;
+	args->msg = NULL;
+	args->msglen = 0;
+
+	return 0;
+}
+
+static int incomingim_ch1(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, fu16_t channel, aim_userinfo_t *userinfo, aim_bstream_t *bs, fu8_t *cookie)
+{
+	fu16_t type, length;
+	aim_rxcallback_t userfunc;
+	int ret = 0;
+	struct aim_incomingim_ch1_args args;
+	int endpos;
+
+	memset(&args, 0, sizeof(args));
+
+	aim_mpmsg_init(sess, &args.mpmsg);
+
+	/*
+	 * This used to be done using tlvchains.  For performance reasons,
+	 * I've changed it to process the TLVs in-place.  This avoids lots
+	 * of per-IM memory allocations.
+	 */
+	while (aim_bstream_empty(bs)) {
+
+		type = aimbs_get16(bs);
+		length = aimbs_get16(bs);
+
+		endpos = aim_bstream_curpos(bs) + length;
+
+		if (type == 0x0002) { /* Message Block */
+
+			/*
+			 * This TLV consists of the following:
+			 *   - 0501 -- Unknown
+			 *   - Features: Don't know how to interpret these
+			 *   - 0101 -- Unknown
+			 *   - Message
+			 *
+			 */
+
+			aimbs_get8(bs); /* 05 */
+			aimbs_get8(bs); /* 01 */
+
+			args.featureslen = aimbs_get16(bs);
+			/* XXX XXX this is all evil! */
+			args.features = bs->data + bs->offset;
+			aim_bstream_advance(bs, args.featureslen);
+			args.icbmflags |= AIM_IMFLAGS_CUSTOMFEATURES;
+
+			/*
+			 * The rest of the TLV contains one or more message
+			 * blocks...
+			 */
+			incomingim_ch1_parsemsgs(sess, bs->data + bs->offset /* XXX evil!!! */, length - 2 - 2 - args.featureslen, &args);
+
+		} else if (type == 0x0003) { /* Server Ack Requested */
+
+			args.icbmflags |= AIM_IMFLAGS_ACK;
+
+		} else if (type == 0x0004) { /* Message is Auto Response */
+
+			args.icbmflags |= AIM_IMFLAGS_AWAY;
+
+		} else if (type == 0x0006) { /* Message was received offline. */
+
+			/* XXX not sure if this actually gets sent. */
+			args.icbmflags |= AIM_IMFLAGS_OFFLINE;
+
+		} else if (type == 0x0008) { /* I-HAVE-A-REALLY-PURTY-ICON Flag */
+
+			args.iconlen = aimbs_get32(bs);
+			aimbs_get16(bs); /* 0x0001 */
+			args.iconsum = aimbs_get16(bs);
+			args.iconstamp = aimbs_get32(bs);
+
+			/*
+			 * This looks to be a client bug.  MacAIM 4.3 will
+			 * send this tag, but with all zero values, in the
+			 * first message of a conversation. This makes no
+			 * sense whatsoever, so I'm going to say its a bug.
+			 *
+			 * You really shouldn't advertise a zero-length icon
+			 * anyway.
+			 * 
+			 */
+			if (args.iconlen)
+				args.icbmflags |= AIM_IMFLAGS_HASICON;
+
+		} else if (type == 0x0009) {
+
+			args.icbmflags |= AIM_IMFLAGS_BUDDYREQ;
+
+		} else if (type == 0x0017) {
+
+			args.extdatalen = length;
+			args.extdata = aimbs_getraw(bs, args.extdatalen);
+
+		} else {
+			faimdprintf(sess, 0, "incomingim_ch1: unknown TLV 0x%04x (len %d)\n", type, length);
+		}
+
+		/*
+		 * This is here to protect ourselves from ourselves.  That
+		 * is, if something above doesn't completly parse its value
+		 * section, or, worse, overparses it, this will set the
+		 * stream where it needs to be in order to land on the next
+		 * TLV when the loop continues.
+		 *
+		 */
+		aim_bstream_setpos(bs, endpos);
+	}
+
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, channel, userinfo, &args);
+
+	aim_mpmsg_free(sess, &args.mpmsg);
+	free(args.extdata);
+
+	return ret;
+}
+
+static void incomingim_ch2_buddylist(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args, aim_bstream_t *servdata)
+{
+
+	/*
+	 * This goes like this...
+	 *
+	 *   group name length
+	 *   group name
+	 *     num of buddies in group
+	 *     buddy name length
+	 *     buddy name
+	 *     buddy name length
+	 *     buddy name
+	 *     ...
+	 *   group name length
+	 *   group name
+	 *     num of buddies in group
+	 *     buddy name length
+	 *     buddy name
+	 *     ...
+	 *   ...
+	 */
+	while (servdata && aim_bstream_empty(servdata)) {
+		fu16_t gnlen, numb;
+		int i;
+		char *gn;
+
+		gnlen = aimbs_get16(servdata);
+		gn = aimbs_getstr(servdata, gnlen);
+		numb = aimbs_get16(servdata);
+
+		for (i = 0; i < numb; i++) {
+			fu16_t bnlen;
+			char *bn;
+
+			bnlen = aimbs_get16(servdata);
+			bn = aimbs_getstr(servdata, bnlen);
+
+			faimdprintf(sess, 0, "got a buddy list from %s: group %s, buddy %s\n", userinfo->sn, gn, bn);
+
+			free(bn);
+		}
+
+		free(gn);
+	}
+
+	return;
+}
+
+static void incomingim_ch2_buddyicon_free(aim_session_t *sess, struct aim_incomingim_ch2_args *args)
+{
+
+	free(args->info.icon.icon);
+
+	return;
+}
+
+static void incomingim_ch2_buddyicon(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args, aim_bstream_t *servdata)
+{
+
+	if (servdata) {
+		args->info.icon.checksum = aimbs_get32(servdata);
+		args->info.icon.length = aimbs_get32(servdata);
+		args->info.icon.timestamp = aimbs_get32(servdata);
+		args->info.icon.icon = aimbs_getraw(servdata, args->info.icon.length);
+	}
+
+	args->destructor = (void *)incomingim_ch2_buddyicon_free;
+
+	return;
+}
+
+static void incomingim_ch2_chat_free(aim_session_t *sess, struct aim_incomingim_ch2_args *args)
+{
+
+	/* XXX aim_chat_roominfo_free() */
+	free(args->info.chat.roominfo.name);
+
+	return;
+}
+
+static void incomingim_ch2_chat(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args, aim_bstream_t *servdata)
+{
+
+	/*
+	 * Chat room info.
+	 */
+	if (servdata)
+		aim_chat_readroominfo(servdata, &args->info.chat.roominfo);
+
+	args->destructor = (void *)incomingim_ch2_chat_free;
+
+	return;
+}
+
+static void incomingim_ch2_icqserverrelay_free(aim_session_t *sess, struct aim_incomingim_ch2_args *args)
+{
+
+	free((char *)args->info.rtfmsg.rtfmsg);
+
+	return;
+}
+
+/*
+ * The relationship between AIM_CAPS_ICQSERVERRELAY and AIM_CAPS_ICQRTF is 
+ * kind of odd. This sends the client ICQRTF since that is all that I've seen
+ * SERVERRELAY used for.
+ *
+ * Note that this is all little-endian.  Cringe.
+ *
+ */
+static void incomingim_ch2_icqserverrelay(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args, aim_bstream_t *servdata)
+{
+	fu16_t hdrlen, anslen, msglen;
+	fu16_t msgtype;
+
+	hdrlen = aimbs_getle16(servdata);
+	aim_bstream_advance(servdata, hdrlen);
+
+	hdrlen = aimbs_getle16(servdata);
+	aim_bstream_advance(servdata, hdrlen);
+
+	msgtype = aimbs_getle16(servdata);
+	
+	anslen = aimbs_getle32(servdata);
+	aim_bstream_advance(servdata, anslen);
+
+	msglen = aimbs_getle16(servdata);
+	args->info.rtfmsg.rtfmsg = aimbs_getstr(servdata, msglen);
+
+	args->info.rtfmsg.fgcolor = aimbs_getle32(servdata);
+	args->info.rtfmsg.bgcolor = aimbs_getle32(servdata);
+
+	hdrlen = aimbs_getle32(servdata);
+	aim_bstream_advance(servdata, hdrlen);
+
+	/* XXX This is such a hack. */
+	args->reqclass = AIM_CAPS_ICQRTF;
+
+	args->destructor = (void *)incomingim_ch2_icqserverrelay_free;
+
+	return;
+}
+
+typedef void (*ch2_args_destructor_t)(aim_session_t *sess, struct aim_incomingim_ch2_args *args);
+
+static int incomingim_ch2(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, fu16_t channel, aim_userinfo_t *userinfo, aim_tlvlist_t *tlvlist, fu8_t *cookie)
+{
+	aim_rxcallback_t userfunc;
+	aim_tlv_t *block1, *servdatatlv;
+	aim_tlvlist_t *list2;
+	struct aim_incomingim_ch2_args args;
+	aim_bstream_t bbs, sdbs, *sdbsptr = NULL;
+	fu8_t *cookie2;
+	int ret = 0;
+
+	char clientip1[30] = {""};
+	char clientip2[30] = {""};
+	char verifiedip[30] = {""};
+
+	memset(&args, 0, sizeof(args));
+
+	/*
+	 * There's another block of TLVs embedded in the type 5 here. 
+	 */
+	block1 = aim_gettlv(tlvlist, 0x0005, 1);
+	aim_bstream_init(&bbs, block1->value, block1->length);
+
+	/*
+	 * First two bytes represent the status of the connection.
+	 *
+	 * 0 is a request, 1 is a deny (?), 2 is an accept
+	 */ 
+	args.status = aimbs_get16(&bbs);
+
+	/*
+	 * Next comes the cookie.  Should match the ICBM cookie.
+	 */
+	cookie2 = aimbs_getraw(&bbs, 8);
+	if (memcmp(cookie, cookie2, 8) != 0) 
+		faimdprintf(sess, 0, "rend: warning cookies don't match!\n");
+	memcpy(args.cookie, cookie2, 8);
+	free(cookie2);
+
+	/*
+	 * The next 16bytes are a capability block so we can
+	 * identify what type of rendezvous this is.
+	 */
+	args.reqclass = aim_getcap(sess, &bbs, 0x10);
+
+	/* 
+	 * What follows may be TLVs or nothing, depending on the
+	 * purpose of the message.
+	 *
+	 * Ack packets for instance have nothing more to them.
+	 */
+	list2 = aim_readtlvchain(&bbs);
+
+	/*
+	 * IP address from the perspective of the client.
+	 */
+	if (aim_gettlv(list2, 0x0002, 1)) {
+		aim_tlv_t *iptlv;
+
+		iptlv = aim_gettlv(list2, 0x0002, 1);
+
+		snprintf(clientip1, sizeof(clientip1), "%d.%d.%d.%d",
+				aimutil_get8(iptlv->value+0),
+				aimutil_get8(iptlv->value+1),
+				aimutil_get8(iptlv->value+2),
+				aimutil_get8(iptlv->value+3));
+	}
+
+	/*
+	 * Secondary IP address from the perspective of the client.
+	 */
+	if (aim_gettlv(list2, 0x0003, 1)) {
+		aim_tlv_t *iptlv;
+
+		iptlv = aim_gettlv(list2, 0x0003, 1);
+
+		snprintf(clientip2, sizeof(clientip2), "%d.%d.%d.%d",
+				aimutil_get8(iptlv->value+0),
+				aimutil_get8(iptlv->value+1),
+				aimutil_get8(iptlv->value+2),
+				aimutil_get8(iptlv->value+3));
+	}
+
+	/*
+	 * Verified IP address (from the perspective of Oscar).
+	 *
+	 * This is added by the server.
+	 */
+	if (aim_gettlv(list2, 0x0004, 1)) {
+		aim_tlv_t *iptlv;
+
+		iptlv = aim_gettlv(list2, 0x0004, 1);
+
+		snprintf(verifiedip, sizeof(verifiedip), "%d.%d.%d.%d",
+				aimutil_get8(iptlv->value+0),
+				aimutil_get8(iptlv->value+1),
+				aimutil_get8(iptlv->value+2),
+				aimutil_get8(iptlv->value+3));
+	}
+
+	/*
+	 * Port number for something.
+	 */
+	if (aim_gettlv(list2, 0x0005, 1))
+		args.port = aim_gettlv16(list2, 0x0005, 1);
+
+	/*
+	 * Error code.
+	 */
+	if (aim_gettlv(list2, 0x000b, 1))
+		args.errorcode = aim_gettlv16(list2, 0x000b, 1);
+
+	/*
+	 * Invitation message / chat description.
+	 */
+	if (aim_gettlv(list2, 0x000c, 1))
+		args.msg = aim_gettlv_str(list2, 0x000c, 1);
+
+	/*
+	 * Character set.
+	 */
+	if (aim_gettlv(list2, 0x000d, 1))
+		args.encoding = aim_gettlv_str(list2, 0x000d, 1);
+	
+	/*
+	 * Language.
+	 */
+	if (aim_gettlv(list2, 0x000e, 1))
+		args.language = aim_gettlv_str(list2, 0x000e, 1);
+
+	/* Unknown -- two bytes = 0x0001 */
+	if (aim_gettlv(list2, 0x000a, 1))
+		;
+
+	/* Unknown -- no value */
+	if (aim_gettlv(list2, 0x000f, 1))
+		;
+
+	if (strlen(clientip1))
+		args.clientip = (char *)clientip1;
+	if (strlen(clientip2))
+		args.clientip2 = (char *)clientip2;
+	if (strlen(verifiedip))
+		args.verifiedip = (char *)verifiedip;
+
+	/*
+	 * This is must be present in PROPOSALs, but will probably not
+	 * exist in CANCELs and ACCEPTs.
+	 *
+	 * Service Data blocks are module-specific in format.
+	 */
+	if ((servdatatlv = aim_gettlv(list2, 0x2711 /* 10001 */, 1))) {
+
+		aim_bstream_init(&sdbs, servdatatlv->value, servdatatlv->length);
+		sdbsptr = &sdbs;
+	}
+
+	/*
+	 * The rest of the handling depends on what type it is.
+	 *
+	 * Not all of them have special handling (yet).
+	 */
+	if (args.reqclass & AIM_CAPS_BUDDYICON)
+		incomingim_ch2_buddyicon(sess, mod, rx, snac, userinfo, &args, sdbsptr);
+	else if (args.reqclass & AIM_CAPS_SENDBUDDYLIST)
+		incomingim_ch2_buddylist(sess, mod, rx, snac, userinfo, &args, sdbsptr);
+	else if (args.reqclass & AIM_CAPS_CHAT)
+		incomingim_ch2_chat(sess, mod, rx, snac, userinfo, &args, sdbsptr);
+	else if (args.reqclass & AIM_CAPS_ICQSERVERRELAY)
+		incomingim_ch2_icqserverrelay(sess, mod, rx, snac, userinfo, &args, sdbsptr);
+
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, channel, userinfo, &args);
+
+
+	if (args.destructor)
+		((ch2_args_destructor_t)args.destructor)(sess, &args);
+
+	free((char *)args.msg);
+	free((char *)args.encoding);
+	free((char *)args.language);
+
+	aim_freetlvchain(&list2);
+
+	return ret;
+}
+
+/*
+ * It can easily be said that parsing ICBMs is THE single
+ * most difficult thing to do in the in AIM protocol.  In
+ * fact, I think I just did say that.
+ *
+ * Below is the best damned solution I've come up with
+ * over the past sixteen months of battling with it. This
+ * can parse both away and normal messages from every client
+ * I have access to.  Its not fast, its not clean.  But it works.
+ *
+ */
+static int incomingim(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	int i, ret = 0;
+	fu8_t cookie[8];
+	fu16_t channel;
+	aim_userinfo_t userinfo;
+
+	memset(&userinfo, 0x00, sizeof(aim_userinfo_t));
+
+	/*
+	 * Read ICBM Cookie.  And throw away.
+	 */
+	for (i = 0; i < 8; i++)
+		cookie[i] = aimbs_get8(bs);
+
+	/*
+	 * Channel ID.
+	 *
+	 * Channel 0x0001 is the message channel.  There are 
+	 * other channels for things called "rendevous"
+	 * which represent chat and some of the other new
+	 * features of AIM2/3/3.5. 
+	 *
+	 * Channel 0x0002 is the Rendevous channel, which
+	 * is where Chat Invitiations and various client-client
+	 * connection negotiations come from.
+	 * 
+	 */
+	channel = aimbs_get16(bs);
+
+	/*
+	 * Extract the standard user info block.
+	 *
+	 * Note that although this contains TLVs that appear contiguous
+	 * with the TLVs read below, they are two different pieces.  The
+	 * userinfo block contains the number of TLVs that contain user
+	 * information, the rest are not even though there is no seperation.
+	 * aim_extractuserinfo() returns the number of bytes used by the
+	 * userinfo tlvs, so you can start reading the rest of them right
+	 * afterward.  
+	 *
+	 * That also means that TLV types can be duplicated between the
+	 * userinfo block and the rest of the message, however there should
+	 * never be two TLVs of the same type in one block.
+	 * 
+	 */
+	aim_extractuserinfo(sess, bs, &userinfo);
+
+	/*
+	 * From here on, its depends on what channel we're on.
+	 *
+	 * Technically all channels have a TLV list have this, however,
+	 * for the common channel 1 case, in-place parsing is used for
+	 * performance reasons (less memory allocation).
+	 */
+	if (channel == 1) {
+
+		ret = incomingim_ch1(sess, mod, rx, snac, channel, &userinfo, bs, cookie);
+
+	} else if (channel == 2) {
+		aim_tlvlist_t *tlvlist;
+
+		/*
+		 * Read block of TLVs (not including the userinfo data).  All 
+		 * further data is derived from what is parsed here.
+		 */
+		tlvlist = aim_readtlvchain(bs);
+
+		ret = incomingim_ch2(sess, mod, rx, snac, channel, &userinfo, tlvlist, cookie);
+
+		aim_freetlvchain(&tlvlist);
+
+	} else {
+
+		faimdprintf(sess, 0, "icbm: ICBM received on an unsupported channel.  Ignoring.\n (chan = %04x)", channel);
+
+		return 0;
+	}
+
+	return ret;
+}
+
+/*
+ * Possible codes:
+ *    AIM_TRANSFER_DENY_NOTSUPPORTED -- "client does not support"
+ *    AIM_TRANSFER_DENY_DECLINE -- "client has declined transfer"
+ *    AIM_TRANSFER_DENY_NOTACCEPTING -- "client is not accepting transfers"
+ * 
+ */
+faim_export int aim_denytransfer(aim_session_t *sess, const char *sender, const char *cookie, fu16_t code)
+{
+	aim_conn_t *conn;
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	aim_tlvlist_t *tl = NULL;
+	
+	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+8+2+1+strlen(sender)+6)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0004, 0x000b, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0004, 0x000b, 0x0000, snacid);
+	
+	aimbs_putraw(&fr->data, cookie, 8);
+
+	aimbs_put16(&fr->data, 0x0002); /* channel */
+	aimbs_put8(&fr->data, strlen(sender));
+	aimbs_putraw(&fr->data, sender, strlen(sender));
+
+	aim_addtlvtochain16(&tl, 0x0003, code);
+	aim_writetlvchain(&fr->data, &tl);
+	aim_freetlvchain(&tl);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * aim_reqicbmparaminfo()
+ *
+ * Request ICBM parameter information.
+ *
+ */
+faim_export int aim_reqicbmparams(aim_session_t *sess)
+{
+	aim_conn_t *conn;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+		return -EINVAL;
+
+	return aim_genericreq_n(sess, conn, 0x0004, 0x0004);
+}
+
+/*
+ *
+ * I definitly recommend sending this.  If you don't, you'll be stuck
+ * with the rather unreasonable defaults.  You don't want those.  Send this.
+ * 
+ */
+faim_export int aim_seticbmparam(aim_session_t *sess, struct aim_icbmparameters *params)
+{
+	aim_conn_t *conn;
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+		return -EINVAL;
+
+	if (!params)
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+16)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0004, 0x0002, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0004, 0x0002, 0x0000, snacid);
+
+	/* This is read-only (see Parameter Reply). Must be set to zero here. */
+	aimbs_put16(&fr->data, 0x0000);
+
+	/* These are all read-write */
+	aimbs_put32(&fr->data, params->flags); 
+	aimbs_put16(&fr->data, params->maxmsglen);
+	aimbs_put16(&fr->data, params->maxsenderwarn); 
+	aimbs_put16(&fr->data, params->maxrecverwarn); 
+	aimbs_put32(&fr->data, params->minmsginterval);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+static int paraminfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	struct aim_icbmparameters params;
+	aim_rxcallback_t userfunc;
+
+	params.maxchan = aimbs_get16(bs);
+	params.flags = aimbs_get32(bs);
+	params.maxmsglen = aimbs_get16(bs);
+	params.maxsenderwarn = aimbs_get16(bs);
+	params.maxrecverwarn = aimbs_get16(bs);
+	params.minmsginterval = aimbs_get32(bs);
+	
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		return userfunc(sess, rx, &params);
+
+	return 0;
+}
+
+static int missedcall(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;
+	fu16_t channel, nummissed, reason;
+	aim_userinfo_t userinfo;
+
+	while (aim_bstream_empty(bs)) {	
+
+		channel = aimbs_get16(bs);
+		aim_extractuserinfo(sess, bs, &userinfo);
+		nummissed = aimbs_get16(bs);
+		reason = aimbs_get16(bs);
+
+		if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+			 ret = userfunc(sess, rx, channel, &userinfo, nummissed, reason);
+	}
+
+	return ret;
+}
+
+static int clienterr(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;
+	fu16_t channel, reason;
+	char *sn;
+	fu8_t *ck, snlen;
+
+	ck = aimbs_getraw(bs, 8);
+	channel = aimbs_get16(bs);
+	snlen = aimbs_get8(bs);
+	sn = aimbs_getstr(bs, snlen);
+	reason = aimbs_get16(bs);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		 ret = userfunc(sess, rx, channel, sn, reason);
+
+	free(ck);
+	free(sn);
+
+	return ret;
+}
+
+static int msgack(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_rxcallback_t userfunc;
+	fu16_t type;
+	fu8_t snlen, *ck;
+	char *sn;
+	int ret = 0;
+
+	ck = aimbs_getraw(bs, 8);
+	type = aimbs_get16(bs);
+	snlen = aimbs_get8(bs);
+	sn = aimbs_getstr(bs, snlen);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, type, sn);
+
+	free(sn);
+	free(ck);
+
+	return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+	if (snac->subtype == 0x0005)
+		return paraminfo(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x0006)
+		return outgoingim(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x0007)
+		return incomingim(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x000a)
+		return missedcall(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x000b)
+		return clienterr(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x000c)
+		return msgack(sess, mod, rx, snac, bs);
+
+	return 0;
+}
+
+faim_internal int msg_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0x0004;
+	mod->version = 0x0001;
+	mod->toolid = 0x0110;
+	mod->toolversion = 0x047b;
+	mod->flags = 0;
+	strncpy(mod->name, "messaging", sizeof(mod->name));
+	mod->snachandler = snachandler;
+
+	return 0;
+}
Index: libfaim/info.c
===================================================================
--- libfaim/info.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/info.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,769 @@
+/*
+ * aim_info.c
+ *
+ * The functions here are responsible for requesting and parsing information-
+ * gathering SNACs.  Or something like that. 
+ *
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+struct aim_priv_inforeq {
+	char sn[MAXSNLEN+1];
+	fu16_t infotype;
+};
+
+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[] = {
+
+	/*
+	 * Chat is oddball.
+	 */
+	{AIM_CAPS_CHAT,
+	 {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	/*
+	 * These are mostly in order.
+	 */
+	{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_IMIMAGE,
+	 {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}},
+
+	{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}},
+
+	{AIM_CAPS_ICQRTF,
+	 {0x97, 0xb1, 0x27, 0x51, 0x24, 0x3c, 0x43, 0x34, 
+	  0xad, 0x22, 0xd6, 0xab, 0xf7, 0x3f, 0x14, 0x92}},
+
+	{AIM_CAPS_ICQUNKNOWN,
+	 {0x2e, 0x7a, 0x64, 0x75, 0xfa, 0xdf, 0x4d, 0xc8,
+	  0x88, 0x6f, 0xea, 0x35, 0x95, 0xfd, 0xb6, 0xdf}},
+
+	{AIM_CAPS_EMPTY,
+	 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
+
+	{AIM_CAPS_TRILLIANCRYPT,
+	 {0xf2, 0xe7, 0xc7, 0xf4, 0xfe, 0xad, 0x4d, 0xfb,
+	  0xb2, 0x35, 0x36, 0x79, 0x8b, 0xdf, 0x00, 0x00}},
+
+	{AIM_CAPS_APINFO, 
+         {0xAA, 0x4A, 0x32, 0xB5,
+	         0xF8, 0x84,
+                0x48, 0xc6,
+	         0xA3, 0xD7,
+	         0x8C, 0x50, 0x97, 0x19, 0xFD, 0x5B}},
+
+	{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;
+}
+
+/*
+ * AIM is fairly regular about providing user info.  This is a generic 
+ * routine to extract it in its standard form.
+ */
+faim_internal int aim_extractuserinfo(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *outinfo)
+{
+	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 seconds 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.
+			 */
+			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.
+			 *
+			 */
+			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 == 0x001d) {
+			/*
+			 * Type 29: Unknown.
+			 *
+			 * Currently very rare. Always 18 bytes of mostly zero.
+			 */
+
+		} 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_extractuserinfo()
+ */
+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);
+
+#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;
+}
+
+faim_export int aim_sendbuddyoncoming(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *info)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!sess || !conn || !info)
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0003, 0x000b, 0x0000, NULL, 0);
+	
+	aim_putsnac(&fr->data, 0x0003, 0x000b, 0x0000, snacid);
+	aim_putuserinfo(&fr->data, info);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+faim_export int aim_sendbuddyoffgoing(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, 0x0003, 0x000c, 0x0000, NULL, 0);
+	
+	aim_putsnac(&fr->data, 0x0003, 0x000c, 0x0000, snacid);
+	aimbs_put8(&fr->data, strlen(sn));
+	aimbs_putraw(&fr->data, sn, strlen(sn));
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * 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)
+ */
+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;
+}
+
+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;
+	aim_rxcallback_t userfunc;
+	aim_tlvlist_t *tlvlist;
+	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_extractuserinfo(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 = aim_gettlv_str(tlvlist, 0x0002, 1);
+	} else if (inforeq->infotype == AIM_GETINFO_AWAYMESSAGE) {
+		text_encoding = aim_gettlv_str(tlvlist, 0x0003, 1);
+		text = aim_gettlv_str(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 ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, &userinfo, inforeq->infotype, text_encoding, text);
+
+	free(text_encoding);
+	free(text);
+
+	aim_freetlvchain(&tlvlist);
+
+	if (origsnac)
+		free(origsnac->data);
+	free(origsnac);
+
+	return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+	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 = 0x0101;
+	mod->toolversion = 0x047b;
+	mod->flags = 0;
+	strncpy(mod->name, "locate", sizeof(mod->name));
+	mod->snachandler = snachandler;
+
+	return 0;
+}
Index: libfaim/invite.c
===================================================================
--- libfaim/invite.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/invite.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,36 @@
+/*
+ * This isn't really ever used by anyone anymore.
+ *
+ * Once upon a time, there used to be a menu item in AIM clients that
+ * said something like "Invite a friend to use AIM..." and then it would
+ * ask for an email address and it would sent a mail to them saying
+ * how perfectly wonderful the AIM service is and why you should use it
+ * and click here if you hate the person who sent this to you and want to
+ * complain and yell at them in a small box with pretty fonts.
+ *
+ * I could've sworn libfaim had this implemented once, a long long time ago,
+ * but I can't find it.
+ *
+ * I'm mainly adding this so that I can keep advertising that we support
+ * group 6, even though we don't.
+ *
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+faim_internal int invite_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0x0006;
+	mod->version = 0x0001;
+	mod->toolid = 0x0110;
+	mod->toolversion = 0x047b;
+	mod->flags = 0;
+	strncpy(mod->name, "invite", sizeof(mod->name));
+	mod->snachandler = NULL;
+
+	return 0;
+}
+
+
Index: libfaim/md5.c
===================================================================
--- libfaim/md5.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/md5.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,392 @@
+/*
+  Copyright (C) 1999 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321.
+  It is derived directly from the text of the RFC and not from the
+  reference implementation.
+
+  The original and principal author of md5.c is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
+  1999-05-03 lpd Original version.
+ */
+
+#include "md5.h"
+#include <string.h>
+
+#ifdef TEST
+/*
+ * Compile with -DTEST to create a self-contained executable test program.
+ * The test program should print out the same values as given in section
+ * A.5 of RFC 1321, reproduced below.
+ */
+#include <string.h>
+main()
+{
+    static const char *const test[7] = {
+	"", /*d41d8cd98f00b204e9800998ecf8427e*/
+	"945399884.61923487334tuvga", /*0cc175b9c0f1b6a831c399e269772661*/
+	"abc", /*900150983cd24fb0d6963f7d28e17f72*/
+	"message digest", /*f96b697d7cb7938d525a2f31aaf161d0*/
+	"abcdefghijklmnopqrstuvwxyz", /*c3fcd3d76192e4007dfb496cca67e13b*/
+	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
+				/*d174ab98d277d9f5a5611c2c9f419d9f*/
+	"12345678901234567890123456789012345678901234567890123456789012345678901234567890" /*57edf4a22be3c955ac49da2e2107b67a*/
+    };
+    int i;
+
+    for (i = 0; i < 7; ++i) {
+	md5_state_t state;
+	md5_byte_t digest[16];
+	int di;
+
+	md5_init(&state);
+	md5_append(&state, (const md5_byte_t *)test[i], strlen(test[i]));
+	md5_finish(&state, digest);
+	printf("MD5 (\"%s\") = ", test[i]);
+	for (di = 0; di < 16; ++di)
+	    printf("%02x", digest[di]);
+	printf("\n");
+    }
+    return 0;
+}
+#endif /* TEST */
+
+
+/*
+ * For reference, here is the program that computed the T values.
+ */
+#if 0
+#include <math.h>
+main()
+{
+    int i;
+    for (i = 1; i <= 64; ++i) {
+	unsigned long v = (unsigned long)(4294967296.0 * fabs(sin((double)i)));
+	printf("#define T%d 0x%08lx\n", i, v);
+    }
+    return 0;
+}
+#endif
+/*
+ * End of T computation program.
+ */
+#define T1 0xd76aa478
+#define T2 0xe8c7b756
+#define T3 0x242070db
+#define T4 0xc1bdceee
+#define T5 0xf57c0faf
+#define T6 0x4787c62a
+#define T7 0xa8304613
+#define T8 0xfd469501
+#define T9 0x698098d8
+#define T10 0x8b44f7af
+#define T11 0xffff5bb1
+#define T12 0x895cd7be
+#define T13 0x6b901122
+#define T14 0xfd987193
+#define T15 0xa679438e
+#define T16 0x49b40821
+#define T17 0xf61e2562
+#define T18 0xc040b340
+#define T19 0x265e5a51
+#define T20 0xe9b6c7aa
+#define T21 0xd62f105d
+#define T22 0x02441453
+#define T23 0xd8a1e681
+#define T24 0xe7d3fbc8
+#define T25 0x21e1cde6
+#define T26 0xc33707d6
+#define T27 0xf4d50d87
+#define T28 0x455a14ed
+#define T29 0xa9e3e905
+#define T30 0xfcefa3f8
+#define T31 0x676f02d9
+#define T32 0x8d2a4c8a
+#define T33 0xfffa3942
+#define T34 0x8771f681
+#define T35 0x6d9d6122
+#define T36 0xfde5380c
+#define T37 0xa4beea44
+#define T38 0x4bdecfa9
+#define T39 0xf6bb4b60
+#define T40 0xbebfbc70
+#define T41 0x289b7ec6
+#define T42 0xeaa127fa
+#define T43 0xd4ef3085
+#define T44 0x04881d05
+#define T45 0xd9d4d039
+#define T46 0xe6db99e5
+#define T47 0x1fa27cf8
+#define T48 0xc4ac5665
+#define T49 0xf4292244
+#define T50 0x432aff97
+#define T51 0xab9423a7
+#define T52 0xfc93a039
+#define T53 0x655b59c3
+#define T54 0x8f0ccc92
+#define T55 0xffeff47d
+#define T56 0x85845dd1
+#define T57 0x6fa87e4f
+#define T58 0xfe2ce6e0
+#define T59 0xa3014314
+#define T60 0x4e0811a1
+#define T61 0xf7537e82
+#define T62 0xbd3af235
+#define T63 0x2ad7d2bb
+#define T64 0xeb86d391
+
+static void
+md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
+{
+    md5_word_t
+	a = pms->abcd[0], b = pms->abcd[1],
+	c = pms->abcd[2], d = pms->abcd[3];
+    md5_word_t t;
+
+#ifndef ARCH_IS_BIG_ENDIAN
+# define ARCH_IS_BIG_ENDIAN 1	/* slower, default implementation */
+#endif
+#if ARCH_IS_BIG_ENDIAN
+
+    /*
+     * On big-endian machines, we must arrange the bytes in the right
+     * order.  (This also works on machines of unknown byte order.)
+     */
+    md5_word_t X[16];
+    const md5_byte_t *xp = data;
+    int i;
+
+    for (i = 0; i < 16; ++i, xp += 4)
+	X[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
+
+#else  /* !ARCH_IS_BIG_ENDIAN */
+
+    /*
+     * On little-endian machines, we can process properly aligned data
+     * without copying it.
+     */
+    md5_word_t xbuf[16];
+    const md5_word_t *X;
+
+    if (!((data - (const md5_byte_t *)0) & 3)) {
+	/* data are properly aligned */
+	X = (const md5_word_t *)data;
+    } else {
+	/* not aligned */
+	memcpy(xbuf, data, 64);
+	X = xbuf;
+    }
+#endif
+
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+    /* Round 1. */
+    /* Let [abcd k s i] denote the operation
+       a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
+#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + F(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+    /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  7,  T1);
+    SET(d, a, b, c,  1, 12,  T2);
+    SET(c, d, a, b,  2, 17,  T3);
+    SET(b, c, d, a,  3, 22,  T4);
+    SET(a, b, c, d,  4,  7,  T5);
+    SET(d, a, b, c,  5, 12,  T6);
+    SET(c, d, a, b,  6, 17,  T7);
+    SET(b, c, d, a,  7, 22,  T8);
+    SET(a, b, c, d,  8,  7,  T9);
+    SET(d, a, b, c,  9, 12, T10);
+    SET(c, d, a, b, 10, 17, T11);
+    SET(b, c, d, a, 11, 22, T12);
+    SET(a, b, c, d, 12,  7, T13);
+    SET(d, a, b, c, 13, 12, T14);
+    SET(c, d, a, b, 14, 17, T15);
+    SET(b, c, d, a, 15, 22, T16);
+#undef SET
+
+     /* Round 2. */
+     /* Let [abcd k s i] denote the operation
+          a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
+#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + G(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  1,  5, T17);
+    SET(d, a, b, c,  6,  9, T18);
+    SET(c, d, a, b, 11, 14, T19);
+    SET(b, c, d, a,  0, 20, T20);
+    SET(a, b, c, d,  5,  5, T21);
+    SET(d, a, b, c, 10,  9, T22);
+    SET(c, d, a, b, 15, 14, T23);
+    SET(b, c, d, a,  4, 20, T24);
+    SET(a, b, c, d,  9,  5, T25);
+    SET(d, a, b, c, 14,  9, T26);
+    SET(c, d, a, b,  3, 14, T27);
+    SET(b, c, d, a,  8, 20, T28);
+    SET(a, b, c, d, 13,  5, T29);
+    SET(d, a, b, c,  2,  9, T30);
+    SET(c, d, a, b,  7, 14, T31);
+    SET(b, c, d, a, 12, 20, T32);
+#undef SET
+
+     /* Round 3. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + H(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  5,  4, T33);
+    SET(d, a, b, c,  8, 11, T34);
+    SET(c, d, a, b, 11, 16, T35);
+    SET(b, c, d, a, 14, 23, T36);
+    SET(a, b, c, d,  1,  4, T37);
+    SET(d, a, b, c,  4, 11, T38);
+    SET(c, d, a, b,  7, 16, T39);
+    SET(b, c, d, a, 10, 23, T40);
+    SET(a, b, c, d, 13,  4, T41);
+    SET(d, a, b, c,  0, 11, T42);
+    SET(c, d, a, b,  3, 16, T43);
+    SET(b, c, d, a,  6, 23, T44);
+    SET(a, b, c, d,  9,  4, T45);
+    SET(d, a, b, c, 12, 11, T46);
+    SET(c, d, a, b, 15, 16, T47);
+    SET(b, c, d, a,  2, 23, T48);
+#undef SET
+
+     /* Round 4. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + I(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  6, T49);
+    SET(d, a, b, c,  7, 10, T50);
+    SET(c, d, a, b, 14, 15, T51);
+    SET(b, c, d, a,  5, 21, T52);
+    SET(a, b, c, d, 12,  6, T53);
+    SET(d, a, b, c,  3, 10, T54);
+    SET(c, d, a, b, 10, 15, T55);
+    SET(b, c, d, a,  1, 21, T56);
+    SET(a, b, c, d,  8,  6, T57);
+    SET(d, a, b, c, 15, 10, T58);
+    SET(c, d, a, b,  6, 15, T59);
+    SET(b, c, d, a, 13, 21, T60);
+    SET(a, b, c, d,  4,  6, T61);
+    SET(d, a, b, c, 11, 10, T62);
+    SET(c, d, a, b,  2, 15, T63);
+    SET(b, c, d, a,  9, 21, T64);
+#undef SET
+
+     /* Then perform the following additions. (That is increment each
+        of the four registers by the value it had before this block
+        was started.) */
+    pms->abcd[0] += a;
+    pms->abcd[1] += b;
+    pms->abcd[2] += c;
+    pms->abcd[3] += d;
+}
+
+void
+md5_init(md5_state_t *pms)
+{
+    pms->count[0] = pms->count[1] = 0;
+    pms->abcd[0] = 0x67452301;
+    pms->abcd[1] = 0xefcdab89;
+    pms->abcd[2] = 0x98badcfe;
+    pms->abcd[3] = 0x10325476;
+}
+
+void
+md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
+{
+    const md5_byte_t *p = data;
+    int left = nbytes;
+    int offset = (pms->count[0] >> 3) & 63;
+    md5_word_t nbits = (md5_word_t)(nbytes << 3);
+
+    if (nbytes <= 0)
+	return;
+
+    /* Update the message length. */
+    pms->count[1] += nbytes >> 29;
+    pms->count[0] += nbits;
+    if (pms->count[0] < nbits)
+	pms->count[1]++;
+
+    /* Process an initial partial block. */
+    if (offset) {
+	int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
+
+	memcpy(pms->buf + offset, p, copy);
+	if (offset + copy < 64)
+	    return;
+	p += copy;
+	left -= copy;
+	md5_process(pms, pms->buf);
+    }
+
+    /* Process full blocks. */
+    for (; left >= 64; p += 64, left -= 64)
+	md5_process(pms, p);
+
+    /* Process a final partial block. */
+    if (left)
+	memcpy(pms->buf, p, left);
+}
+
+void
+md5_finish(md5_state_t *pms, md5_byte_t digest[16])
+{
+    static const md5_byte_t pad[64] = {
+	0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+    };
+    md5_byte_t data[8];
+    int i;
+
+    /* Save the length before padding. */
+    for (i = 0; i < 8; ++i)
+	data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
+    /* Pad to 56 bytes mod 64. */
+    md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
+    /* Append the length. */
+    md5_append(pms, data, 8);
+    for (i = 0; i < 16; ++i)
+	digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
+}
Index: libfaim/md5.h
===================================================================
--- libfaim/md5.h	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/md5.h	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,93 @@
+/*
+  Copyright (C) 1999 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321.
+  It is derived directly from the text of the RFC and not from the
+  reference implementation.
+
+  The original and principal author of md5.h is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
+	added conditionalization for C++ compilation from Martin
+	Purschke <purschke@bnl.gov>.
+  1999-05-03 lpd Original version.
+ */
+
+#ifndef md5_INCLUDED
+#  define md5_INCLUDED
+
+/*
+ * This code has some adaptations for the Ghostscript environment, but it
+ * will compile and run correctly in any environment with 8-bit chars and
+ * 32-bit ints.  Specifically, it assumes that if the following are
+ * defined, they have the same meaning as in Ghostscript: P1, P2, P3,
+ * ARCH_IS_BIG_ENDIAN.
+ */
+
+typedef unsigned char md5_byte_t; /* 8-bit byte */
+typedef unsigned int md5_word_t; /* 32-bit word */
+
+/* Define the state of the MD5 Algorithm. */
+typedef struct md5_state_s {
+    md5_word_t count[2];	/* message length in bits, lsw first */
+    md5_word_t abcd[4];		/* digest buffer */
+    md5_byte_t buf[64];		/* accumulate block */
+} md5_state_t;
+
+#ifdef __cplusplus
+extern "C" 
+{
+#endif
+
+/* Initialize the algorithm. */
+#ifdef P1
+void md5_init(P1(md5_state_t *pms));
+#else
+void md5_init(md5_state_t *pms);
+#endif
+
+/* Append a string to the message. */
+#ifdef P3
+void md5_append(P3(md5_state_t *pms, const md5_byte_t *data, int nbytes));
+#else
+void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
+#endif
+
+/* Finish the message and return the digest. */
+#ifdef P2
+void md5_finish(P2(md5_state_t *pms, md5_byte_t digest[16]));
+#else
+void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
+#endif
+
+#ifdef __cplusplus
+}  /* end extern "C" */
+#endif
+
+#endif /* md5_INCLUDED */
Index: libfaim/meta.c
===================================================================
--- libfaim/meta.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/meta.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,50 @@
+/*
+ * Administrative things for libfaim.
+ *
+ *  
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+faim_export char *aim_getbuilddate(void)
+{
+	return AIM_BUILDDATE;
+}
+
+faim_export char *aim_getbuildtime(void)
+{
+	return AIM_BUILDTIME;
+}
+
+faim_export int aim_getbuildstring(char *buf, int buflen)
+{
+
+	snprintf(buf, buflen, "%d.%d.%d-%s%s", 
+			FAIM_VERSION_MAJOR,
+			FAIM_VERSION_MINOR,
+			FAIM_VERSION_MINORMINOR,
+			aim_getbuilddate(),
+			aim_getbuildtime());
+
+	return 0;
+}
+
+faim_internal void faimdprintf(aim_session_t *sess, int dlevel, const char *format, ...)
+{
+	if (!sess) {
+		fprintf(stderr, "faimdprintf: no session! boo! (%d, %s)\n", dlevel, format);
+		return;
+	}
+
+	if ((dlevel <= sess->debug) && sess->debugcb) {
+		va_list ap;
+
+		va_start(ap, format);
+		sess->debugcb(sess, dlevel, format, ap);
+		va_end(ap);
+	}
+
+	return;
+}
+
Index: libfaim/misc.c
===================================================================
--- libfaim/misc.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/misc.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,400 @@
+
+/*
+ * aim_misc.c
+ *
+ * TODO: Seperate a lot of this into an aim_bos.c.
+ *
+ * Other things...
+ *
+ *   - Idle setting 
+ * 
+ *
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h> 
+
+/*
+ * aim_bos_setbuddylist(buddylist)
+ *
+ * This just builds the "set buddy list" command then queues it.
+ *
+ * buddy_list = "Screen Name One&ScreenNameTwo&";
+ *
+ * TODO: Clean this up.  
+ *
+ * XXX: I can't stress the TODO enough.
+ *
+ */
+faim_export int aim_bos_setbuddylist(aim_session_t *sess, aim_conn_t *conn, const char *buddy_list)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	int len = 0;
+	char *localcpy = NULL;
+	char *tmpptr = NULL;
+
+	if (!buddy_list || !(localcpy = strdup(buddy_list))) 
+		return -EINVAL;
+
+	for (tmpptr = strtok(localcpy, "&"); tmpptr; ) {
+		faimdprintf(sess, 2, "---adding: %s (%d)\n", tmpptr, strlen(tmpptr));
+		len += 1 + strlen(tmpptr);
+		tmpptr = strtok(NULL, "&");
+	}
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+len)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0003, 0x0004, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0003, 0x0004, 0x0000, snacid);
+
+	strncpy(localcpy, buddy_list, strlen(buddy_list) + 1);
+
+	for (tmpptr = strtok(localcpy, "&"); tmpptr; ) {
+
+		faimdprintf(sess, 2, "---adding: %s (%d)\n", tmpptr, strlen(tmpptr));
+
+		aimbs_put8(&fr->data, strlen(tmpptr));
+		aimbs_putraw(&fr->data, tmpptr, strlen(tmpptr));
+		tmpptr = strtok(NULL, "&");
+	}
+
+	aim_tx_enqueue(sess, fr);
+
+	free(localcpy);
+
+	return 0;
+}
+
+/* 
+ * aim_bos_setprofile(profile)
+ *
+ * Gives BOS your profile.
+ * 
+ */
+faim_export int aim_bos_setprofile(aim_session_t *sess, aim_conn_t *conn, const char *profile, const char *awaymsg, fu32_t caps)
+{
+	static const char defencoding[] = {"text/aolrtf; charset=\"us-ascii\""};
+	aim_frame_t *fr;
+	aim_tlvlist_t *tl = NULL;
+	aim_snacid_t snacid;
+
+	/* Build to packet first to get real length */
+	if (profile) {
+		aim_addtlvtochain_raw(&tl, 0x0001, strlen(defencoding), defencoding);
+		aim_addtlvtochain_raw(&tl, 0x0002, strlen(profile), profile);
+	}
+
+	/*
+	 * 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 (strlen(awaymsg)) {
+			aim_addtlvtochain_raw(&tl, 0x0003, strlen(defencoding), defencoding);
+			aim_addtlvtochain_raw(&tl, 0x0004, strlen(awaymsg), awaymsg);
+		} 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;
+}
+
+/*
+ * aim_bos_reqbuddyrights()
+ *
+ * Request Buddy List rights.
+ *
+ */
+faim_export int aim_bos_reqbuddyrights(aim_session_t *sess, aim_conn_t *conn)
+{
+	return aim_genericreq_n(sess, conn, 0x0003, 0x0002);
+}
+
+/*
+ * Send a warning to destsn.
+ * 
+ * Flags:
+ *  AIM_WARN_ANON  Send as an anonymous (doesn't count as much)
+ *
+ * returns -1 on error (couldn't alloc packet), 0 on success. 
+ *
+ */
+faim_export int aim_send_warning(aim_session_t *sess, aim_conn_t *conn, const char *destsn, fu32_t flags)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	fu16_t outflags = 0x0000;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, strlen(destsn)+13)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0004, 0x0008, 0x0000, destsn, strlen(destsn)+1);
+
+	aim_putsnac(&fr->data, 0x0004, 0x0008, 0x0000, snacid);
+
+	if (flags & AIM_WARN_ANON)
+		outflags |= 0x0001;
+
+	aimbs_put16(&fr->data, outflags); 
+	aimbs_put8(&fr->data, strlen(destsn));
+	aimbs_putraw(&fr->data, destsn, strlen(destsn));
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * Generic routine for sending commands.
+ *
+ *
+ * I know I can do this in a smarter way...but I'm not thinking straight
+ * right now...
+ *
+ * I had one big function that handled all three cases, but then it broke
+ * and I split it up into three.  But then I fixed it.  I just never went
+ * back to the single.  I don't see any advantage to doing it either way.
+ *
+ */
+faim_internal int aim_genericreq_n(aim_session_t *sess, aim_conn_t *conn, fu16_t family, fu16_t subtype)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid = 0x00000000;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10)))
+		return -ENOMEM;
+
+	aim_putsnac(&fr->data, family, subtype, 0x0000, snacid);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+faim_internal int aim_genericreq_n_snacid(aim_session_t *sess, aim_conn_t *conn, fu16_t family, fu16_t subtype)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, family, subtype, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, family, subtype, 0x0000, snacid);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+faim_internal int aim_genericreq_l(aim_session_t *sess, aim_conn_t *conn, fu16_t family, fu16_t subtype, fu32_t *longdata)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!longdata)
+		return aim_genericreq_n(sess, conn, family, subtype);
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+4)))
+		return -ENOMEM; 
+
+	snacid = aim_cachesnac(sess, family, subtype, 0x0000, NULL, 0);
+
+	aim_putsnac(&fr->data, family, subtype, 0x0000, snacid);
+	aimbs_put32(&fr->data, *longdata);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+faim_internal int aim_genericreq_s(aim_session_t *sess, aim_conn_t *conn, fu16_t family, fu16_t subtype, fu16_t *shortdata)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!shortdata)
+		return aim_genericreq_n(sess, conn, family, subtype);
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2)))
+		return -ENOMEM; 
+
+	snacid = aim_cachesnac(sess, family, subtype, 0x0000, NULL, 0);
+
+	aim_putsnac(&fr->data, family, subtype, 0x0000, snacid);
+	aimbs_put16(&fr->data, *shortdata);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * aim_bos_reqlocaterights()
+ *
+ * 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);
+}
+
+/* 
+ * Set directory profile data (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;
+}
+
+/* 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;
+}
+
+/*
+ * Should be generic enough to handle the errors for all groups.
+ *
+ */
+static int generror(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	int ret = 0;
+	int error = 0;
+	aim_rxcallback_t userfunc;
+	aim_snac_t *snac2;
+
+	snac2 = aim_remsnac(sess, snac->id);
+
+	if (aim_bstream_empty(bs))
+		error = aimbs_get16(bs);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, error, snac2 ? snac2->data : NULL);
+
+	if (snac2)
+		free(snac2->data);
+	free(snac2);
+
+	return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+	if (snac->subtype == 0x0001)
+		return generror(sess, mod, rx, snac, bs);
+	else if ((snac->family == 0xffff) && (snac->subtype == 0xffff)) {
+		aim_rxcallback_t userfunc;
+
+		if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+			return userfunc(sess, rx);
+	}
+
+	return 0;
+}
+
+faim_internal int misc_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0xffff;
+	mod->version = 0x0000;
+	mod->flags = AIM_MODFLAG_MULTIFAMILY;
+	strncpy(mod->name, "misc", sizeof(mod->name));
+	mod->snachandler = snachandler;
+
+	return 0;
+}
+
+
Index: libfaim/msgcookie.c
===================================================================
--- libfaim/msgcookie.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/msgcookie.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,196 @@
+/*
+ * Cookie Caching stuff. Adam wrote this, apparently just some
+ * derivatives of n's SNAC work. I cleaned it up, added comments.
+ * 
+ */
+
+/*
+ * I'm assuming that cookies are type-specific. that is, we can have
+ * "1234578" for type 1 and type 2 concurrently. if i'm wrong, then we
+ * lose some error checking. if we assume cookies are not type-specific and are
+ * wrong, we get quirky behavior when cookies step on each others' toes.
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+/**
+ * aim_cachecookie - appends a cookie to the cookie list
+ * @sess: session to add to
+ * @cookie: pointer to struct to append
+ *
+ * if cookie->cookie for type cookie->type is found, updates the
+ * ->addtime of the found structure; otherwise adds the given cookie
+ * to the cache
+ *
+ * returns -1 on error, 0 on append, 1 on update.  the cookie you pass
+ * in may be free'd, so don't count on its value after calling this!
+ * 
+ */
+faim_internal int aim_cachecookie(aim_session_t *sess, aim_msgcookie_t *cookie)
+{
+	aim_msgcookie_t *newcook;
+
+	if (!sess || !cookie)
+		return -EINVAL;
+
+	newcook = aim_checkcookie(sess, cookie->cookie, cookie->type);
+	
+	if (newcook == cookie) {
+		newcook->addtime = time(NULL);
+		return 1;
+	} else if (newcook)
+		aim_cookie_free(sess, newcook);
+
+	cookie->addtime = time(NULL);  
+
+	cookie->next = sess->msgcookies;
+	sess->msgcookies = cookie;
+
+	return 0;
+}
+
+/**
+ * aim_uncachecookie - grabs a cookie from the cookie cache (removes it from the list)
+ * @sess: session to grab cookie from
+ * @cookie: cookie string to look for
+ * @type: cookie type to look for
+ *
+ * takes a cookie string and a cookie type and finds the cookie struct associated with that duple, removing it from the cookie list ikn the process.
+ *
+ * if found, returns the struct; if none found (or on error), returns NULL:
+ */
+faim_internal aim_msgcookie_t *aim_uncachecookie(aim_session_t *sess, fu8_t *cookie, int type)
+{
+	aim_msgcookie_t *cur, **prev;
+
+	if (!cookie || !sess->msgcookies)
+		return NULL;
+
+	for (prev = &sess->msgcookies; (cur = *prev); ) {
+		if ((cur->type == type) && 
+				(memcmp(cur->cookie, cookie, 8) == 0)) {
+			*prev = cur->next;
+			return cur;
+		}
+		prev = &cur->next;
+	}
+
+	return NULL;
+}
+
+/**
+ * aim_mkcookie - generate an aim_msgcookie_t *struct from a cookie string, a type, and a data pointer.
+ * @c: pointer to the cookie string array
+ * @type: cookie type to use
+ * @data: data to be cached with the cookie
+ *
+ * returns NULL on error, a pointer to the newly-allocated cookie on
+ * success.
+ *
+ */
+faim_internal aim_msgcookie_t *aim_mkcookie(fu8_t *c, int type, void *data) 
+{
+	aim_msgcookie_t *cookie;
+
+	if (!c)
+		return NULL;
+
+	if (!(cookie = calloc(1, sizeof(aim_msgcookie_t))))
+		return NULL;
+
+	cookie->data = data;
+	cookie->type = type;
+	memcpy(cookie->cookie, c, 8);
+
+	return cookie;
+}
+
+/**
+ * aim_checkcookie - check to see if a cookietuple has been cached
+ * @sess: session to check for the cookie in
+ * @cookie: pointer to the cookie string array
+ * @type: type of the cookie to look for
+ *
+ * this returns a pointer to the cookie struct (still in the list) on
+ * success; returns NULL on error/not found
+ *
+ */
+
+faim_internal aim_msgcookie_t *aim_checkcookie(aim_session_t *sess, const fu8_t *cookie, int type)
+{
+	aim_msgcookie_t *cur;
+
+	for (cur = sess->msgcookies; cur; cur = cur->next) {
+		if ((cur->type == type) && 
+				(memcmp(cur->cookie, cookie, 8) == 0))
+			return cur;   
+	}
+
+	return NULL;
+}
+
+#if 0 /* debugging feature */
+faim_internal int aim_dumpcookie(aim_msgcookie_t *cookie) 
+{
+
+	if (!cookie)
+		return -EINVAL;
+
+	printf("\tCookie at %p: %d/%s with %p, next %p\n", 
+			cookie, cookie->type, cookie->cookie, 
+			cookie->data, cookie->next);
+
+	return 0;
+}
+#endif
+
+/**
+ * aim_cookie_free - free an aim_msgcookie_t struct
+ * @sess: session to remove the cookie from
+ * @cookiep: the address of a pointer to the cookie struct to remove
+ *
+ * this function removes the cookie *cookie from teh list of cookies
+ * in sess, and then frees all memory associated with it. including
+ * its data! if you want to use the private data after calling this,
+ * make sure you copy it first.
+ *
+ * returns -1 on error, 0 on success.
+ *
+ */
+faim_internal int aim_cookie_free(aim_session_t *sess, aim_msgcookie_t *cookie) 
+{
+	aim_msgcookie_t *cur, **prev;
+
+	if (!sess || !cookie)
+		return -EINVAL;
+
+	for (prev = &sess->msgcookies; (cur = *prev); ) {
+		if (cur == cookie)
+			*prev = cur->next;
+		else
+			prev = &cur->next;
+	}
+
+	free(cookie->data);
+	free(cookie);
+
+	return 0;
+} 
+
+/* XXX I hate switch */
+faim_internal int aim_msgcookie_gettype(int reqclass) 
+{
+	/* XXX: hokey-assed. needs fixed. */
+	switch(reqclass) {
+	case AIM_CAPS_BUDDYICON: return AIM_COOKIETYPE_OFTICON;
+	case AIM_CAPS_VOICE: return AIM_COOKIETYPE_OFTVOICE;
+	case AIM_CAPS_IMIMAGE: return AIM_COOKIETYPE_OFTIMAGE;
+	case AIM_CAPS_CHAT: return AIM_COOKIETYPE_CHAT;
+	case AIM_CAPS_GETFILE: return AIM_COOKIETYPE_OFTGET;
+	case AIM_CAPS_SENDFILE: return AIM_COOKIETYPE_OFTSEND;
+	default: return AIM_COOKIETYPE_UNKNOWN;
+	}           
+}
+
+
Index: libfaim/popups.c
===================================================================
--- libfaim/popups.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/popups.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,65 @@
+
+/*
+ * Popups are just what it sounds like.  They're a way for the server to
+ * open up an informative box on the client's screen.
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+/*
+ * This is all there is to it.
+ *
+ * The message is probably HTML.
+ * 
+ */
+static int parsepopup(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_rxcallback_t userfunc;
+	aim_tlvlist_t *tl;
+	int ret = 0;
+	char *msg, *url;
+	fu16_t width, height, delay;
+
+	tl = aim_readtlvchain(bs);
+
+	msg = aim_gettlv_str(tl, 0x0001, 1);
+	url = aim_gettlv_str(tl, 0x0002, 1);
+	width = aim_gettlv16(tl, 0x0003, 1);
+	height = aim_gettlv16(tl, 0x0004, 1);
+	delay = aim_gettlv16(tl, 0x0005, 1);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, msg, url, width, height, delay);
+
+	aim_freetlvchain(&tl);
+	free(msg);
+	free(url);
+
+	return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+	if (snac->subtype == 0x0002)
+		return parsepopup(sess, mod, rx, snac, bs);
+
+	return 0;
+}
+
+faim_internal int popups_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0x0008;
+	mod->version = 0x0001;
+	mod->toolid = 0x0104;
+	mod->toolversion = 0x0001;
+	mod->flags = 0;
+	strncpy(mod->name, "popups", sizeof(mod->name));
+	mod->snachandler = snachandler;
+
+	return 0;
+}
+
+
Index: libfaim/rxhandlers.c
===================================================================
--- libfaim/rxhandlers.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/rxhandlers.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,618 @@
+/*
+ * aim_rxhandlers.c
+ *
+ * This file contains most all of the incoming packet handlers, along
+ * with aim_rxdispatch(), the Rx dispatcher.  Queue/list management is
+ * actually done in aim_rxqueue.c.
+ *
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+struct aim_rxcblist_s {
+	fu16_t family;
+	fu16_t type;
+	aim_rxcallback_t handler;
+	u_short flags;
+	struct aim_rxcblist_s *next;
+};
+
+faim_internal aim_module_t *aim__findmodulebygroup(aim_session_t *sess, fu16_t group)
+{
+	aim_module_t *cur;
+
+	for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) {
+		if (cur->family == group)
+			return cur;
+	}
+
+	return NULL;
+}
+
+faim_internal aim_module_t *aim__findmodule(aim_session_t *sess, const char *name)
+{
+	aim_module_t *cur;
+
+	for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) {
+		if (strcmp(name, cur->name) == 0)
+			return cur;
+	}
+
+	return NULL;
+}
+
+faim_internal int aim__registermodule(aim_session_t *sess, int (*modfirst)(aim_session_t *, aim_module_t *))
+{
+	aim_module_t *mod;
+
+	if (!sess || !modfirst)
+		return -1;
+
+	if (!(mod = malloc(sizeof(aim_module_t))))
+		return -1;
+	memset(mod, 0, sizeof(aim_module_t));
+
+	if (modfirst(sess, mod) == -1) {
+		free(mod);
+		return -1;
+	}
+
+	if (aim__findmodule(sess, mod->name)) {
+		if (mod->shutdown)
+			mod->shutdown(sess, mod);
+		free(mod);
+		return -1;
+	}
+
+	mod->next = (aim_module_t *)sess->modlistv;
+	(aim_module_t *)sess->modlistv = mod;
+
+	/* faimdprintf(sess, 1, "registered module %s (family 0x%04x, version = 0x%04x, tool 0x%04x, tool version 0x%04x)\n", mod->name, mod->family, mod->version, mod->toolid, mod->toolversion); */
+
+	return 0;
+}
+
+faim_internal void aim__shutdownmodules(aim_session_t *sess)
+{
+	aim_module_t *cur;
+
+	for (cur = (aim_module_t *)sess->modlistv; cur; ) {
+		aim_module_t *tmp;
+
+		tmp = cur->next;
+
+		if (cur->shutdown)
+			cur->shutdown(sess, cur);
+
+		free(cur);
+
+		cur = tmp;
+	}
+
+	sess->modlistv = NULL;
+
+	return;
+}
+
+static int consumesnac(aim_session_t *sess, aim_frame_t *rx)
+{
+	aim_module_t *cur;
+	aim_modsnac_t snac;
+
+	if (aim_bstream_empty(&rx->data) < 10)
+		return 0;
+
+	snac.family = aimbs_get16(&rx->data);
+	snac.subtype = aimbs_get16(&rx->data);
+	snac.flags = aimbs_get16(&rx->data);
+	snac.id = aimbs_get32(&rx->data);
+
+	for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) {
+
+		if (!(cur->flags & AIM_MODFLAG_MULTIFAMILY) && 
+				(cur->family != snac.family))
+			continue;
+
+		if (cur->snachandler(sess, cur, rx, &snac, &rx->data))
+			return 1;
+
+	}
+
+	return 0;
+}
+
+static int consumenonsnac(aim_session_t *sess, aim_frame_t *rx, fu16_t family, fu16_t subtype)
+{
+	aim_module_t *cur;
+	aim_modsnac_t snac;
+
+	snac.family = family;
+	snac.subtype = subtype;
+	snac.flags = snac.id = 0;
+
+	for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) {
+
+		if (!(cur->flags & AIM_MODFLAG_MULTIFAMILY) && 
+				(cur->family != snac.family))
+			continue;
+
+		if (cur->snachandler(sess, cur, rx, &snac, &rx->data))
+			return 1;
+
+	}
+
+	return 0;
+}
+
+static int negchan_middle(aim_session_t *sess, aim_frame_t *fr)
+{
+	aim_tlvlist_t *tlvlist;
+	char *msg = NULL;
+	fu16_t code = 0;
+	aim_rxcallback_t userfunc;
+	int ret = 1;
+
+	if (aim_bstream_empty(&fr->data) == 0) {
+		/* XXX should do something with this */
+		return 1;
+	}
+
+	/* Used only by the older login protocol */
+	/* XXX remove this special case? */
+	if (fr->conn->type == AIM_CONN_TYPE_AUTH)
+		return consumenonsnac(sess, fr, 0x0017, 0x0003);
+
+	tlvlist = aim_readtlvchain(&fr->data);
+
+	if (aim_gettlv(tlvlist, 0x0009, 1))
+		code = aim_gettlv16(tlvlist, 0x0009, 1);
+
+	if (aim_gettlv(tlvlist, 0x000b, 1))
+		msg = aim_gettlv_str(tlvlist, 0x000b, 1);
+
+	if ((userfunc = aim_callhandler(sess, fr->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNERR))) 
+		ret = userfunc(sess, fr, code, msg);
+
+	aim_freetlvchain(&tlvlist);
+
+	free(msg);
+
+	return ret;
+}
+
+/*
+ * Bleck functions get called when there's no non-bleck functions
+ * around to cleanup the mess...
+ */
+faim_internal int bleck(aim_session_t *sess, aim_frame_t *frame, ...)
+{
+	fu16_t family, subtype;
+	fu16_t maxf, maxs;
+
+	static const char *channels[6] = {
+		"Invalid (0)",
+		"FLAP Version",
+		"SNAC",
+		"Invalid (3)",
+		"Negotiation",
+		"FLAP NOP"
+	};
+	static const int maxchannels = 5;
+	
+	/* XXX: this is ugly. and big just for debugging. */
+	static const char *literals[14][25] = {
+		{"Invalid", 
+		 NULL
+		},
+		{"General", 
+		 "Invalid",
+		 "Error",
+		 "Client Ready",
+		 "Server Ready",
+		 "Service Request",
+		 "Redirect",
+		 "Rate Information Request",
+		 "Rate Information",
+		 "Rate Information Ack",
+		 NULL,
+		 "Rate Information Change",
+		 "Server Pause",
+		 NULL,
+		 "Server Resume",
+		 "Request Personal User Information",
+		 "Personal User Information",
+		 "Evil Notification",
+		 NULL,
+		 "Migration notice",
+		 "Message of the Day",
+		 "Set Privacy Flags",
+		 "Well Known URL",
+		 "NOP"
+		},
+		{"Location", 
+		 "Invalid",
+		 "Error",
+		 "Request Rights",
+		 "Rights Information", 
+		 "Set user information", 
+		 "Request User Information", 
+		 "User Information", 
+		 "Watcher Sub Request",
+		 "Watcher Notification"
+		},
+		{"Buddy List Management", 
+		 "Invalid", 
+		 "Error", 
+		 "Request Rights",
+		 "Rights Information",
+		 "Add Buddy", 
+		 "Remove Buddy", 
+		 "Watcher List Query", 
+		 "Watcher List Response", 
+		 "Watcher SubRequest", 
+		 "Watcher Notification", 
+		 "Reject Notification", 
+		 "Oncoming Buddy", 
+		 "Offgoing Buddy"
+		},
+		{"Messeging", 
+		 "Invalid",
+		 "Error", 
+		 "Add ICBM Parameter",
+		 "Remove ICBM Parameter", 
+		 "Request Parameter Information",
+		 "Parameter Information",
+		 "Outgoing Message", 
+		 "Incoming Message",
+		 "Evil Request",
+		 "Evil Reply", 
+		 "Missed Calls",
+		 "Message Error", 
+		 "Host Ack"
+		},
+		{"Advertisements", 
+		 "Invalid", 
+		 "Error", 
+		 "Request Ad",
+		 "Ad Data (GIFs)"
+		},
+		{"Invitation / Client-to-Client", 
+		 "Invalid",
+		 "Error",
+		 "Invite a Friend",
+		 "Invitation Ack"
+		},
+		{"Administrative", 
+		 "Invalid",
+		 "Error",
+		 "Information Request",
+		 "Information Reply",
+		 "Information Change Request",
+		 "Information Chat Reply",
+		 "Account Confirm Request",
+		 "Account Confirm Reply",
+		 "Account Delete Request",
+		 "Account Delete Reply"
+		},
+		{"Popups", 
+		 "Invalid",
+		 "Error",
+		 "Display Popup"
+		},
+		{"BOS", 
+		 "Invalid",
+		 "Error",
+		 "Request Rights",
+		 "Rights Response",
+		 "Set group permission mask",
+		 "Add permission list entries",
+		 "Delete permission list entries",
+		 "Add deny list entries",
+		 "Delete deny list entries",
+		 "Server Error"
+		},
+		{"User Lookup", 
+		 "Invalid",
+		 "Error",
+		 "Search Request",
+		 "Search Response"
+		},
+		{"Stats", 
+		 "Invalid",
+		 "Error",
+		 "Set minimum report interval",
+		 "Report Events"
+		},
+		{"Translate", 
+		 "Invalid",
+		 "Error",
+		 "Translate Request",
+		 "Translate Reply",
+		},
+		{"Chat Navigation", 
+		 "Invalid",
+		 "Error",
+		 "Request rights",
+		 "Request Exchange Information",
+		 "Request Room Information",
+		 "Request Occupant List",
+		 "Search for Room",
+		 "Outgoing Message", 
+		 "Incoming Message",
+		 "Evil Request", 
+		 "Evil Reply", 
+		 "Chat Error",
+		}
+	};
+
+	maxf = sizeof(literals) / sizeof(literals[0]);
+	maxs = sizeof(literals[0]) / sizeof(literals[0][0]);
+
+	if (frame->hdr.flap.type == 0x02) {
+
+		family = aimbs_get16(&frame->data);
+		subtype = aimbs_get16(&frame->data);
+		
+		if ((family < maxf) && (subtype+1 < maxs) && (literals[family][subtype] != NULL))
+			faimdprintf(sess, 0, "bleck: channel %s: null handler for %04x/%04x (%s)\n", channels[frame->hdr.flap.type], family, subtype, literals[family][subtype+1]);
+		else
+			faimdprintf(sess, 0, "bleck: channel %s: null handler for %04x/%04x (no literal)\n", channels[frame->hdr.flap.type], family, subtype);
+	} else {
+
+		if (frame->hdr.flap.type <= maxchannels)
+			faimdprintf(sess, 0, "bleck: channel %s (0x%02x)\n", channels[frame->hdr.flap.type], frame->hdr.flap.type);
+		else
+			faimdprintf(sess, 0, "bleck: unknown channel 0x%02x\n", frame->hdr.flap.type);
+
+	}
+		
+	return 1;
+}
+
+/*
+ * Some SNACs we do not allow to be hooked, for good reason.
+ */
+static int checkdisallowed(fu16_t group, fu16_t type)
+{
+	static const struct {
+		fu16_t group;
+		fu16_t type;
+	} dontuse[] = {
+		{0x0001, 0x0002},
+		{0x0001, 0x0003},
+		{0x0001, 0x0006},
+		{0x0001, 0x0007},
+		{0x0001, 0x0008},
+		{0x0001, 0x0017},
+		{0x0001, 0x0018},
+		{0x0000, 0x0000}
+	};
+	int i;
+
+	for (i = 0; dontuse[i].group != 0x0000; i++) {
+		if ((dontuse[i].group == group) && (dontuse[i].type == type))
+			return 1;
+	}
+
+	return 0;
+}
+
+faim_export int aim_conn_addhandler(aim_session_t *sess, aim_conn_t *conn, fu16_t family, fu16_t type, aim_rxcallback_t newhandler, fu16_t flags)
+{
+	struct aim_rxcblist_s *newcb;
+
+	if (!conn)
+		return -1;
+
+	faimdprintf(sess, 1, "aim_conn_addhandler: adding for %04x/%04x\n", family, type);
+
+	if (checkdisallowed(family, type)) {
+		faimdprintf(sess, 0, "aim_conn_addhandler: client tried to hook %x/%x -- BUG!!!\n", family, type);
+		return -1;
+	}
+
+	if (!(newcb = (struct aim_rxcblist_s *)calloc(1, sizeof(struct aim_rxcblist_s))))
+		return -1;
+
+	newcb->family = family;
+	newcb->type = type;
+	newcb->flags = flags;
+	newcb->handler = newhandler ? newhandler : bleck;
+	newcb->next = NULL;
+
+	if (!conn->handlerlist)
+		conn->handlerlist = (void *)newcb;
+	else {
+		struct aim_rxcblist_s *cur;
+
+		for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur->next; cur = cur->next)
+			;
+		cur->next = newcb;
+	}
+
+	return 0;
+}
+
+faim_export int aim_clearhandlers(aim_conn_t *conn)
+{
+	struct aim_rxcblist_s *cur;
+
+	if (!conn)
+		return -1;
+
+	for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur; ) {
+		struct aim_rxcblist_s *tmp;
+
+		tmp = cur->next;
+		free(cur);
+		cur = tmp;
+	}
+	conn->handlerlist = NULL;
+
+	return 0;
+}
+
+faim_internal aim_rxcallback_t aim_callhandler(aim_session_t *sess, aim_conn_t *conn, fu16_t family, fu16_t type)
+{
+	struct aim_rxcblist_s *cur;
+
+	if (!conn)
+		return NULL;
+
+	faimdprintf(sess, 1, "aim_callhandler: calling for %04x/%04x\n", family, type);
+
+	for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur; cur = cur->next) {
+		if ((cur->family == family) && (cur->type == type))
+			return cur->handler;
+	}
+
+	if (type == AIM_CB_SPECIAL_DEFAULT) {
+		faimdprintf(sess, 1, "aim_callhandler: no default handler for family 0x%04x\n", family);
+		return NULL; /* prevent infinite recursion */
+	}
+
+	faimdprintf(sess, 1, "aim_callhandler: no handler for  0x%04x/0x%04x\n", family, type);
+
+	return aim_callhandler(sess, conn, family, AIM_CB_SPECIAL_DEFAULT);
+}
+
+faim_internal void aim_clonehandlers(aim_session_t *sess, aim_conn_t *dest, aim_conn_t *src)
+{
+	struct aim_rxcblist_s *cur;
+
+	for (cur = (struct aim_rxcblist_s *)src->handlerlist; cur; cur = cur->next) {
+		aim_conn_addhandler(sess, dest, cur->family, cur->type, 
+						cur->handler, cur->flags);
+	}
+
+	return;
+}
+
+faim_internal int aim_callhandler_noparam(aim_session_t *sess, aim_conn_t *conn,fu16_t family, fu16_t type, aim_frame_t *ptr)
+{
+	aim_rxcallback_t userfunc;
+
+	if ((userfunc = aim_callhandler(sess, conn, family, type)))
+		return userfunc(sess, ptr);
+
+	return 1; /* XXX */
+}
+
+/*
+ * aim_rxdispatch()
+ *
+ * Basically, heres what this should do:
+ *   1) Determine correct packet handler for this packet
+ *   2) Mark the packet handled (so it can be dequeued in purge_queue())
+ *   3) Send the packet to the packet handler
+ *   4) Go to next packet in the queue and start over
+ *   5) When done, run purge_queue() to purge handled commands
+ *
+ * TODO: Clean up.
+ * TODO: More support for mid-level handlers.
+ * TODO: Allow for NULL handlers.
+ *
+ */
+faim_export void aim_rxdispatch(aim_session_t *sess)
+{
+	int i;
+	aim_frame_t *cur;
+
+	for (cur = sess->queue_incoming, i = 0; cur; cur = cur->next, i++) {
+
+		/*
+		 * XXX: This is still fairly ugly.
+		 */
+
+		if (cur->handled)
+			continue;
+
+		/*
+		 * This is a debugging/sanity check only and probably 
+		 * could/should be removed for stable code.
+		 */
+		if (((cur->hdrtype == AIM_FRAMETYPE_OFT) && 
+		   (cur->conn->type != AIM_CONN_TYPE_RENDEZVOUS)) || 
+		  ((cur->hdrtype == AIM_FRAMETYPE_FLAP) && 
+		   (cur->conn->type == AIM_CONN_TYPE_RENDEZVOUS))) {
+			faimdprintf(sess, 0, "rxhandlers: incompatible frame type %d on connection type 0x%04x\n", cur->hdrtype, cur->conn->type);
+			cur->handled = 1;
+			continue;
+		}
+
+		if (cur->conn->type == AIM_CONN_TYPE_RENDEZVOUS) {
+			if (cur->hdrtype != AIM_FRAMETYPE_OFT) {
+				faimdprintf(sess, 0, "internal error: non-OFT frames on OFT connection\n");
+				cur->handled = 1; /* get rid of it */
+			} else {
+				/* XXX: implement this */
+				faimdprintf(sess, 0, "faim: OFT frame!\n");
+				cur->handled = 1; /* get rid of it */
+			}
+			continue;
+		}
+
+		if (cur->conn->type == AIM_CONN_TYPE_RENDEZVOUS_OUT) {
+			/* not possible */
+			faimdprintf(sess, 0, "rxdispatch called on RENDEZVOUS_OUT connection!\n");
+			cur->handled = 1;
+			continue;
+		}
+
+		if (cur->hdr.flap.type == 0x01) {
+			
+			cur->handled = aim_callhandler_noparam(sess, cur->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_FLAPVER, cur); /* XXX use consumenonsnac */
+			
+			continue;
+			
+		} else if (cur->hdr.flap.type == 0x02) {
+
+			if ((cur->handled = consumesnac(sess, cur)))
+				continue;
+
+		} else if (cur->hdr.flap.type == 0x04) {
+
+			cur->handled = negchan_middle(sess, cur);
+			continue;
+
+		} else if (cur->hdr.flap.type == 0x05)
+			;
+		
+		if (!cur->handled) {
+			consumenonsnac(sess, cur, 0xffff, 0xffff); /* last chance! */
+			cur->handled = 1;
+		}
+	}
+
+	/* 
+	 * This doesn't have to be called here.  It could easily be done
+	 * by a seperate thread or something. It's an administrative operation,
+	 * and can take a while. Though the less you call it the less memory
+	 * you'll have :)
+	 */
+	aim_purge_rxqueue(sess);
+
+	return;
+}
+
+faim_internal int aim_parse_unknown(aim_session_t *sess, aim_frame_t *frame, ...)
+{
+	int i;
+
+	faimdprintf(sess, 1, "\nRecieved unknown packet:");
+
+	for (i = 0; aim_bstream_empty(&frame->data); i++) {
+		if ((i % 8) == 0)
+			faimdprintf(sess, 1, "\n\t");
+
+		faimdprintf(sess, 1, "0x%2x ", aimbs_get8(&frame->data));
+	}
+
+	faimdprintf(sess, 1, "\n\n");
+
+	return 1;
+}
+
+
+
Index: libfaim/rxhandlers.c.orig
===================================================================
--- libfaim/rxhandlers.c.orig	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/rxhandlers.c.orig	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,618 @@
+/*
+ * aim_rxhandlers.c
+ *
+ * This file contains most all of the incoming packet handlers, along
+ * with aim_rxdispatch(), the Rx dispatcher.  Queue/list management is
+ * actually done in aim_rxqueue.c.
+ *
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+struct aim_rxcblist_s {
+	fu16_t family;
+	fu16_t type;
+	aim_rxcallback_t handler;
+	u_short flags;
+	struct aim_rxcblist_s *next;
+};
+
+faim_internal aim_module_t *aim__findmodulebygroup(aim_session_t *sess, fu16_t group)
+{
+	aim_module_t *cur;
+
+	for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) {
+		if (cur->family == group)
+			return cur;
+	}
+
+	return NULL;
+}
+
+faim_internal aim_module_t *aim__findmodule(aim_session_t *sess, const char *name)
+{
+	aim_module_t *cur;
+
+	for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) {
+		if (strcmp(name, cur->name) == 0)
+			return cur;
+	}
+
+	return NULL;
+}
+
+faim_internal int aim__registermodule(aim_session_t *sess, int (*modfirst)(aim_session_t *, aim_module_t *))
+{
+	aim_module_t *mod;
+
+	if (!sess || !modfirst)
+		return -1;
+
+	if (!(mod = malloc(sizeof(aim_module_t))))
+		return -1;
+	memset(mod, 0, sizeof(aim_module_t));
+
+	if (modfirst(sess, mod) == -1) {
+		free(mod);
+		return -1;
+	}
+
+	if (aim__findmodule(sess, mod->name)) {
+		if (mod->shutdown)
+			mod->shutdown(sess, mod);
+		free(mod);
+		return -1;
+	}
+
+	mod->next = (aim_module_t *)sess->modlistv;
+	(aim_module_t *)sess->modlistv = mod;
+
+	faimdprintf(sess, 1, "registered module %s (family 0x%04x, version = 0x%04x, tool 0x%04x, tool version 0x%04x)\n", mod->name, mod->family, mod->version, mod->toolid, mod->toolversion);
+
+	return 0;
+}
+
+faim_internal void aim__shutdownmodules(aim_session_t *sess)
+{
+	aim_module_t *cur;
+
+	for (cur = (aim_module_t *)sess->modlistv; cur; ) {
+		aim_module_t *tmp;
+
+		tmp = cur->next;
+
+		if (cur->shutdown)
+			cur->shutdown(sess, cur);
+
+		free(cur);
+
+		cur = tmp;
+	}
+
+	sess->modlistv = NULL;
+
+	return;
+}
+
+static int consumesnac(aim_session_t *sess, aim_frame_t *rx)
+{
+	aim_module_t *cur;
+	aim_modsnac_t snac;
+
+	if (aim_bstream_empty(&rx->data) < 10)
+		return 0;
+
+	snac.family = aimbs_get16(&rx->data);
+	snac.subtype = aimbs_get16(&rx->data);
+	snac.flags = aimbs_get16(&rx->data);
+	snac.id = aimbs_get32(&rx->data);
+
+	for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) {
+
+		if (!(cur->flags & AIM_MODFLAG_MULTIFAMILY) && 
+				(cur->family != snac.family))
+			continue;
+
+		if (cur->snachandler(sess, cur, rx, &snac, &rx->data))
+			return 1;
+
+	}
+
+	return 0;
+}
+
+static int consumenonsnac(aim_session_t *sess, aim_frame_t *rx, fu16_t family, fu16_t subtype)
+{
+	aim_module_t *cur;
+	aim_modsnac_t snac;
+
+	snac.family = family;
+	snac.subtype = subtype;
+	snac.flags = snac.id = 0;
+
+	for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) {
+
+		if (!(cur->flags & AIM_MODFLAG_MULTIFAMILY) && 
+				(cur->family != snac.family))
+			continue;
+
+		if (cur->snachandler(sess, cur, rx, &snac, &rx->data))
+			return 1;
+
+	}
+
+	return 0;
+}
+
+static int negchan_middle(aim_session_t *sess, aim_frame_t *fr)
+{
+	aim_tlvlist_t *tlvlist;
+	char *msg = NULL;
+	fu16_t code = 0;
+	aim_rxcallback_t userfunc;
+	int ret = 1;
+
+	if (aim_bstream_empty(&fr->data) == 0) {
+		/* XXX should do something with this */
+		return 1;
+	}
+
+	/* Used only by the older login protocol */
+	/* XXX remove this special case? */
+	if (fr->conn->type == AIM_CONN_TYPE_AUTH)
+		return consumenonsnac(sess, fr, 0x0017, 0x0003);
+
+	tlvlist = aim_readtlvchain(&fr->data);
+
+	if (aim_gettlv(tlvlist, 0x0009, 1))
+		code = aim_gettlv16(tlvlist, 0x0009, 1);
+
+	if (aim_gettlv(tlvlist, 0x000b, 1))
+		msg = aim_gettlv_str(tlvlist, 0x000b, 1);
+
+	if ((userfunc = aim_callhandler(sess, fr->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNERR))) 
+		ret = userfunc(sess, fr, code, msg);
+
+	aim_freetlvchain(&tlvlist);
+
+	free(msg);
+
+	return ret;
+}
+
+/*
+ * Bleck functions get called when there's no non-bleck functions
+ * around to cleanup the mess...
+ */
+faim_internal int bleck(aim_session_t *sess, aim_frame_t *frame, ...)
+{
+	fu16_t family, subtype;
+	fu16_t maxf, maxs;
+
+	static const char *channels[6] = {
+		"Invalid (0)",
+		"FLAP Version",
+		"SNAC",
+		"Invalid (3)",
+		"Negotiation",
+		"FLAP NOP"
+	};
+	static const int maxchannels = 5;
+	
+	/* XXX: this is ugly. and big just for debugging. */
+	static const char *literals[14][25] = {
+		{"Invalid", 
+		 NULL
+		},
+		{"General", 
+		 "Invalid",
+		 "Error",
+		 "Client Ready",
+		 "Server Ready",
+		 "Service Request",
+		 "Redirect",
+		 "Rate Information Request",
+		 "Rate Information",
+		 "Rate Information Ack",
+		 NULL,
+		 "Rate Information Change",
+		 "Server Pause",
+		 NULL,
+		 "Server Resume",
+		 "Request Personal User Information",
+		 "Personal User Information",
+		 "Evil Notification",
+		 NULL,
+		 "Migration notice",
+		 "Message of the Day",
+		 "Set Privacy Flags",
+		 "Well Known URL",
+		 "NOP"
+		},
+		{"Location", 
+		 "Invalid",
+		 "Error",
+		 "Request Rights",
+		 "Rights Information", 
+		 "Set user information", 
+		 "Request User Information", 
+		 "User Information", 
+		 "Watcher Sub Request",
+		 "Watcher Notification"
+		},
+		{"Buddy List Management", 
+		 "Invalid", 
+		 "Error", 
+		 "Request Rights",
+		 "Rights Information",
+		 "Add Buddy", 
+		 "Remove Buddy", 
+		 "Watcher List Query", 
+		 "Watcher List Response", 
+		 "Watcher SubRequest", 
+		 "Watcher Notification", 
+		 "Reject Notification", 
+		 "Oncoming Buddy", 
+		 "Offgoing Buddy"
+		},
+		{"Messeging", 
+		 "Invalid",
+		 "Error", 
+		 "Add ICBM Parameter",
+		 "Remove ICBM Parameter", 
+		 "Request Parameter Information",
+		 "Parameter Information",
+		 "Outgoing Message", 
+		 "Incoming Message",
+		 "Evil Request",
+		 "Evil Reply", 
+		 "Missed Calls",
+		 "Message Error", 
+		 "Host Ack"
+		},
+		{"Advertisements", 
+		 "Invalid", 
+		 "Error", 
+		 "Request Ad",
+		 "Ad Data (GIFs)"
+		},
+		{"Invitation / Client-to-Client", 
+		 "Invalid",
+		 "Error",
+		 "Invite a Friend",
+		 "Invitation Ack"
+		},
+		{"Administrative", 
+		 "Invalid",
+		 "Error",
+		 "Information Request",
+		 "Information Reply",
+		 "Information Change Request",
+		 "Information Chat Reply",
+		 "Account Confirm Request",
+		 "Account Confirm Reply",
+		 "Account Delete Request",
+		 "Account Delete Reply"
+		},
+		{"Popups", 
+		 "Invalid",
+		 "Error",
+		 "Display Popup"
+		},
+		{"BOS", 
+		 "Invalid",
+		 "Error",
+		 "Request Rights",
+		 "Rights Response",
+		 "Set group permission mask",
+		 "Add permission list entries",
+		 "Delete permission list entries",
+		 "Add deny list entries",
+		 "Delete deny list entries",
+		 "Server Error"
+		},
+		{"User Lookup", 
+		 "Invalid",
+		 "Error",
+		 "Search Request",
+		 "Search Response"
+		},
+		{"Stats", 
+		 "Invalid",
+		 "Error",
+		 "Set minimum report interval",
+		 "Report Events"
+		},
+		{"Translate", 
+		 "Invalid",
+		 "Error",
+		 "Translate Request",
+		 "Translate Reply",
+		},
+		{"Chat Navigation", 
+		 "Invalid",
+		 "Error",
+		 "Request rights",
+		 "Request Exchange Information",
+		 "Request Room Information",
+		 "Request Occupant List",
+		 "Search for Room",
+		 "Outgoing Message", 
+		 "Incoming Message",
+		 "Evil Request", 
+		 "Evil Reply", 
+		 "Chat Error",
+		}
+	};
+
+	maxf = sizeof(literals) / sizeof(literals[0]);
+	maxs = sizeof(literals[0]) / sizeof(literals[0][0]);
+
+	if (frame->hdr.flap.type == 0x02) {
+
+		family = aimbs_get16(&frame->data);
+		subtype = aimbs_get16(&frame->data);
+		
+		if ((family < maxf) && (subtype+1 < maxs) && (literals[family][subtype] != NULL))
+			faimdprintf(sess, 0, "bleck: channel %s: null handler for %04x/%04x (%s)\n", channels[frame->hdr.flap.type], family, subtype, literals[family][subtype+1]);
+		else
+			faimdprintf(sess, 0, "bleck: channel %s: null handler for %04x/%04x (no literal)\n", channels[frame->hdr.flap.type], family, subtype);
+	} else {
+
+		if (frame->hdr.flap.type <= maxchannels)
+			faimdprintf(sess, 0, "bleck: channel %s (0x%02x)\n", channels[frame->hdr.flap.type], frame->hdr.flap.type);
+		else
+			faimdprintf(sess, 0, "bleck: unknown channel 0x%02x\n", frame->hdr.flap.type);
+
+	}
+		
+	return 1;
+}
+
+/*
+ * Some SNACs we do not allow to be hooked, for good reason.
+ */
+static int checkdisallowed(fu16_t group, fu16_t type)
+{
+	static const struct {
+		fu16_t group;
+		fu16_t type;
+	} dontuse[] = {
+		{0x0001, 0x0002},
+		{0x0001, 0x0003},
+		{0x0001, 0x0006},
+		{0x0001, 0x0007},
+		{0x0001, 0x0008},
+		{0x0001, 0x0017},
+		{0x0001, 0x0018},
+		{0x0000, 0x0000}
+	};
+	int i;
+
+	for (i = 0; dontuse[i].group != 0x0000; i++) {
+		if ((dontuse[i].group == group) && (dontuse[i].type == type))
+			return 1;
+	}
+
+	return 0;
+}
+
+faim_export int aim_conn_addhandler(aim_session_t *sess, aim_conn_t *conn, fu16_t family, fu16_t type, aim_rxcallback_t newhandler, fu16_t flags)
+{
+	struct aim_rxcblist_s *newcb;
+
+	if (!conn)
+		return -1;
+
+	faimdprintf(sess, 1, "aim_conn_addhandler: adding for %04x/%04x\n", family, type);
+
+	if (checkdisallowed(family, type)) {
+		faimdprintf(sess, 0, "aim_conn_addhandler: client tried to hook %x/%x -- BUG!!!\n", family, type);
+		return -1;
+	}
+
+	if (!(newcb = (struct aim_rxcblist_s *)calloc(1, sizeof(struct aim_rxcblist_s))))
+		return -1;
+
+	newcb->family = family;
+	newcb->type = type;
+	newcb->flags = flags;
+	newcb->handler = newhandler ? newhandler : bleck;
+	newcb->next = NULL;
+
+	if (!conn->handlerlist)
+		conn->handlerlist = (void *)newcb;
+	else {
+		struct aim_rxcblist_s *cur;
+
+		for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur->next; cur = cur->next)
+			;
+		cur->next = newcb;
+	}
+
+	return 0;
+}
+
+faim_export int aim_clearhandlers(aim_conn_t *conn)
+{
+	struct aim_rxcblist_s *cur;
+
+	if (!conn)
+		return -1;
+
+	for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur; ) {
+		struct aim_rxcblist_s *tmp;
+
+		tmp = cur->next;
+		free(cur);
+		cur = tmp;
+	}
+	conn->handlerlist = NULL;
+
+	return 0;
+}
+
+faim_internal aim_rxcallback_t aim_callhandler(aim_session_t *sess, aim_conn_t *conn, fu16_t family, fu16_t type)
+{
+	struct aim_rxcblist_s *cur;
+
+	if (!conn)
+		return NULL;
+
+	faimdprintf(sess, 1, "aim_callhandler: calling for %04x/%04x\n", family, type);
+
+	for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur; cur = cur->next) {
+		if ((cur->family == family) && (cur->type == type))
+			return cur->handler;
+	}
+
+	if (type == AIM_CB_SPECIAL_DEFAULT) {
+		faimdprintf(sess, 1, "aim_callhandler: no default handler for family 0x%04x\n", family);
+		return NULL; /* prevent infinite recursion */
+	}
+
+	faimdprintf(sess, 1, "aim_callhandler: no handler for  0x%04x/0x%04x\n", family, type);
+
+	return aim_callhandler(sess, conn, family, AIM_CB_SPECIAL_DEFAULT);
+}
+
+faim_internal void aim_clonehandlers(aim_session_t *sess, aim_conn_t *dest, aim_conn_t *src)
+{
+	struct aim_rxcblist_s *cur;
+
+	for (cur = (struct aim_rxcblist_s *)src->handlerlist; cur; cur = cur->next) {
+		aim_conn_addhandler(sess, dest, cur->family, cur->type, 
+						cur->handler, cur->flags);
+	}
+
+	return;
+}
+
+faim_internal int aim_callhandler_noparam(aim_session_t *sess, aim_conn_t *conn,fu16_t family, fu16_t type, aim_frame_t *ptr)
+{
+	aim_rxcallback_t userfunc;
+
+	if ((userfunc = aim_callhandler(sess, conn, family, type)))
+		return userfunc(sess, ptr);
+
+	return 1; /* XXX */
+}
+
+/*
+ * aim_rxdispatch()
+ *
+ * Basically, heres what this should do:
+ *   1) Determine correct packet handler for this packet
+ *   2) Mark the packet handled (so it can be dequeued in purge_queue())
+ *   3) Send the packet to the packet handler
+ *   4) Go to next packet in the queue and start over
+ *   5) When done, run purge_queue() to purge handled commands
+ *
+ * TODO: Clean up.
+ * TODO: More support for mid-level handlers.
+ * TODO: Allow for NULL handlers.
+ *
+ */
+faim_export void aim_rxdispatch(aim_session_t *sess)
+{
+	int i;
+	aim_frame_t *cur;
+
+	for (cur = sess->queue_incoming, i = 0; cur; cur = cur->next, i++) {
+
+		/*
+		 * XXX: This is still fairly ugly.
+		 */
+
+		if (cur->handled)
+			continue;
+
+		/*
+		 * This is a debugging/sanity check only and probably 
+		 * could/should be removed for stable code.
+		 */
+		if (((cur->hdrtype == AIM_FRAMETYPE_OFT) && 
+		   (cur->conn->type != AIM_CONN_TYPE_RENDEZVOUS)) || 
+		  ((cur->hdrtype == AIM_FRAMETYPE_FLAP) && 
+		   (cur->conn->type == AIM_CONN_TYPE_RENDEZVOUS))) {
+			faimdprintf(sess, 0, "rxhandlers: incompatible frame type %d on connection type 0x%04x\n", cur->hdrtype, cur->conn->type);
+			cur->handled = 1;
+			continue;
+		}
+
+		if (cur->conn->type == AIM_CONN_TYPE_RENDEZVOUS) {
+			if (cur->hdrtype != AIM_FRAMETYPE_OFT) {
+				faimdprintf(sess, 0, "internal error: non-OFT frames on OFT connection\n");
+				cur->handled = 1; /* get rid of it */
+			} else {
+				/* XXX: implement this */
+				faimdprintf(sess, 0, "faim: OFT frame!\n");
+				cur->handled = 1; /* get rid of it */
+			}
+			continue;
+		}
+
+		if (cur->conn->type == AIM_CONN_TYPE_RENDEZVOUS_OUT) {
+			/* not possible */
+			faimdprintf(sess, 0, "rxdispatch called on RENDEZVOUS_OUT connection!\n");
+			cur->handled = 1;
+			continue;
+		}
+
+		if (cur->hdr.flap.type == 0x01) {
+			
+			cur->handled = aim_callhandler_noparam(sess, cur->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_FLAPVER, cur); /* XXX use consumenonsnac */
+			
+			continue;
+			
+		} else if (cur->hdr.flap.type == 0x02) {
+
+			if ((cur->handled = consumesnac(sess, cur)))
+				continue;
+
+		} else if (cur->hdr.flap.type == 0x04) {
+
+			cur->handled = negchan_middle(sess, cur);
+			continue;
+
+		} else if (cur->hdr.flap.type == 0x05)
+			;
+		
+		if (!cur->handled) {
+			consumenonsnac(sess, cur, 0xffff, 0xffff); /* last chance! */
+			cur->handled = 1;
+		}
+	}
+
+	/* 
+	 * This doesn't have to be called here.  It could easily be done
+	 * by a seperate thread or something. It's an administrative operation,
+	 * and can take a while. Though the less you call it the less memory
+	 * you'll have :)
+	 */
+	aim_purge_rxqueue(sess);
+
+	return;
+}
+
+faim_internal int aim_parse_unknown(aim_session_t *sess, aim_frame_t *frame, ...)
+{
+	int i;
+
+	faimdprintf(sess, 1, "\nRecieved unknown packet:");
+
+	for (i = 0; aim_bstream_empty(&frame->data); i++) {
+		if ((i % 8) == 0)
+			faimdprintf(sess, 1, "\n\t");
+
+		faimdprintf(sess, 1, "0x%2x ", aimbs_get8(&frame->data));
+	}
+
+	faimdprintf(sess, 1, "\n\n");
+
+	return 1;
+}
+
+
+
Index: libfaim/rxqueue.c
===================================================================
--- libfaim/rxqueue.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/rxqueue.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,510 @@
+/*
+ *  aim_rxqueue.c
+ *
+ * This file contains the management routines for the receive
+ * (incoming packet) queue.  The actual packet handlers are in
+ * aim_rxhandlers.c.
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h> 
+
+#ifndef _WIN32
+#include <sys/socket.h>
+#endif
+
+/*
+ *
+ */
+faim_internal int aim_recv(int fd, void *buf, size_t count)
+{
+	int left, cur; 
+
+	for (cur = 0, left = count; left; ) {
+		int ret;
+		
+		ret = recv(fd, ((unsigned char *)buf)+cur, left, 0);
+
+		/* Of course EOF is an error, only morons disagree with that. */
+		if (ret <= 0)
+			return -1;
+
+		cur += ret;
+		left -= ret;
+	}
+
+	return cur;
+}
+
+/*
+ * Read into a byte stream.  Will not read more than count, but may read
+ * less if there is not enough room in the stream buffer.
+ */
+faim_internal int aim_bstream_recv(aim_bstream_t *bs, int fd, size_t count)
+{
+	int red = 0;
+
+	if (!bs || (fd < 0) || (count < 0))
+		return -1;
+	
+	if (count > (bs->len - bs->offset))
+		count = bs->len - bs->offset; /* truncate to remaining space */
+
+	if (count) {
+
+		red = aim_recv(fd, bs->data + bs->offset, count);
+
+		if (red <= 0)
+			return -1;
+	}
+
+	bs->offset += red;
+
+	return red;
+}
+
+faim_internal int aim_bstream_init(aim_bstream_t *bs, fu8_t *data, int len)
+{
+	
+	if (!bs)
+		return -1;
+
+	bs->data = data;
+	bs->len = len;
+	bs->offset = 0;
+
+	return 0;
+}
+
+faim_internal int aim_bstream_empty(aim_bstream_t *bs)
+{
+	return bs->len - bs->offset;
+}
+
+faim_internal int aim_bstream_curpos(aim_bstream_t *bs)
+{
+	return bs->offset;
+}
+
+faim_internal int aim_bstream_setpos(aim_bstream_t *bs, int off)
+{
+
+	if (off > bs->len)
+		return -1;
+
+	bs->offset = off;
+
+	return off;
+}
+
+faim_internal void aim_bstream_rewind(aim_bstream_t *bs)
+{
+
+	aim_bstream_setpos(bs, 0);
+
+	return;
+}
+
+faim_internal int aim_bstream_advance(aim_bstream_t *bs, int n)
+{
+
+	if (aim_bstream_empty(bs) < n)
+		return 0; /* XXX throw an exception */
+
+	bs->offset += n;
+
+	return n;
+}
+
+faim_internal fu8_t aimbs_get8(aim_bstream_t *bs)
+{
+	
+	if (aim_bstream_empty(bs) < 1)
+		return 0; /* XXX throw an exception */
+	
+	bs->offset++;
+	
+	return aimutil_get8(bs->data + bs->offset - 1);
+}
+
+faim_internal fu16_t aimbs_get16(aim_bstream_t *bs)
+{
+	
+	if (aim_bstream_empty(bs) < 2)
+		return 0; /* XXX throw an exception */
+	
+	bs->offset += 2;
+	
+	return aimutil_get16(bs->data + bs->offset - 2);
+}
+
+faim_internal fu32_t aimbs_get32(aim_bstream_t *bs)
+{
+	
+	if (aim_bstream_empty(bs) < 4)
+		return 0; /* XXX throw an exception */
+	
+	bs->offset += 4;
+	
+	return aimutil_get32(bs->data + bs->offset - 4);
+}
+
+faim_internal fu8_t aimbs_getle8(aim_bstream_t *bs)
+{
+	
+	if (aim_bstream_empty(bs) < 1)
+		return 0; /* XXX throw an exception */
+	
+	bs->offset++;
+	
+	return aimutil_getle8(bs->data + bs->offset - 1);
+}
+
+faim_internal fu16_t aimbs_getle16(aim_bstream_t *bs)
+{
+	
+	if (aim_bstream_empty(bs) < 2)
+		return 0; /* XXX throw an exception */
+	
+	bs->offset += 2;
+	
+	return aimutil_getle16(bs->data + bs->offset - 2);
+}
+
+faim_internal fu32_t aimbs_getle32(aim_bstream_t *bs)
+{
+	
+	if (aim_bstream_empty(bs) < 4)
+		return 0; /* XXX throw an exception */
+	
+	bs->offset += 4;
+	
+	return aimutil_getle32(bs->data + bs->offset - 4);
+}
+
+faim_internal int aimbs_put8(aim_bstream_t *bs, fu8_t v)
+{
+
+	if (aim_bstream_empty(bs) < 1)
+		return 0; /* XXX throw an exception */
+
+	bs->offset += aimutil_put8(bs->data + bs->offset, v);
+
+	return 1;
+}
+
+faim_internal int aimbs_put16(aim_bstream_t *bs, fu16_t v)
+{
+
+	if (aim_bstream_empty(bs) < 2)
+		return 0; /* XXX throw an exception */
+
+	bs->offset += aimutil_put16(bs->data + bs->offset, v);
+
+	return 2;
+}
+
+faim_internal int aimbs_put32(aim_bstream_t *bs, fu32_t v)
+{
+
+	if (aim_bstream_empty(bs) < 4)
+		return 0; /* XXX throw an exception */
+
+	bs->offset += aimutil_put32(bs->data + bs->offset, v);
+
+	return 1;
+}
+
+faim_internal int aimbs_putle8(aim_bstream_t *bs, fu8_t v)
+{
+
+	if (aim_bstream_empty(bs) < 1)
+		return 0; /* XXX throw an exception */
+
+	bs->offset += aimutil_putle8(bs->data + bs->offset, v);
+
+	return 1;
+}
+
+faim_internal int aimbs_putle16(aim_bstream_t *bs, fu16_t v)
+{
+
+	if (aim_bstream_empty(bs) < 2)
+		return 0; /* XXX throw an exception */
+
+	bs->offset += aimutil_putle16(bs->data + bs->offset, v);
+
+	return 2;
+}
+
+faim_internal int aimbs_putle32(aim_bstream_t *bs, fu32_t v)
+{
+
+	if (aim_bstream_empty(bs) < 4)
+		return 0; /* XXX throw an exception */
+
+	bs->offset += aimutil_putle32(bs->data + bs->offset, v);
+
+	return 1;
+}
+
+faim_internal int aimbs_getrawbuf(aim_bstream_t *bs, fu8_t *buf, int len)
+{
+
+	if (aim_bstream_empty(bs) < len)
+		return 0;
+
+	memcpy(buf, bs->data + bs->offset, len);
+	bs->offset += len;
+
+	return len;
+}
+
+faim_internal fu8_t *aimbs_getraw(aim_bstream_t *bs, int len)
+{
+	fu8_t *ob;
+
+	if (!(ob = malloc(len)))
+		return NULL;
+
+	if (aimbs_getrawbuf(bs, ob, len) < len) {
+		free(ob);
+		return NULL;
+	}
+
+	return ob;
+}
+
+faim_internal char *aimbs_getstr(aim_bstream_t *bs, int len)
+{
+	char *ob;
+
+	if (!(ob = malloc(len+1)))
+		return NULL;
+
+	if (aimbs_getrawbuf(bs, ob, len) < len) {
+		free(ob);
+		return NULL;
+	}
+	
+	ob[len] = '\0';
+
+	return ob;
+}
+
+faim_internal int aimbs_putraw(aim_bstream_t *bs, const fu8_t *v, int len)
+{
+
+	if (aim_bstream_empty(bs) < len)
+		return 0; /* XXX throw an exception */
+
+	memcpy(bs->data + bs->offset, v, len);
+	bs->offset += len;
+
+	return len;
+}
+
+faim_internal int aimbs_putbs(aim_bstream_t *bs, aim_bstream_t *srcbs, int len)
+{
+
+	if (aim_bstream_empty(srcbs) < len)
+		return 0; /* XXX throw exception (underrun) */
+
+	if (aim_bstream_empty(bs) < len)
+		return 0; /* XXX throw exception (overflow) */
+
+	memcpy(bs->data + bs->offset, srcbs->data + srcbs->offset, len);
+	bs->offset += len;
+	srcbs->offset += len;
+
+	return len;
+}
+
+/**
+ * aim_frame_destroy - free aim_frame_t 
+ * @frame: the frame to free  
+ *
+ * returns -1 on error; 0 on success.  
+ *
+ */
+faim_internal void aim_frame_destroy(aim_frame_t *frame)
+{
+
+	free(frame->data.data); /* XXX aim_bstream_free */
+
+	if (frame->hdrtype == AIM_FRAMETYPE_OFT)
+		free(frame->hdr.oft.hdr2);
+	free(frame);
+	
+	return;
+} 
+
+
+/*
+ * Grab a single command sequence off the socket, and enqueue
+ * it in the incoming event queue in a seperate struct.
+ */
+faim_export int aim_get_command(aim_session_t *sess, aim_conn_t *conn)
+{
+	fu8_t flaphdr_raw[6];
+	aim_bstream_t flaphdr;
+	aim_frame_t *newrx;
+	fu16_t payloadlen;
+	
+	if (!sess || !conn)
+		return 0;
+
+	if (conn->fd == -1)
+		return -1; /* its a aim_conn_close()'d connection */
+
+	if (conn->fd < 3)  /* can happen when people abuse the interface */
+		return 0;
+
+	if (conn->status & AIM_CONN_STATUS_INPROGRESS)
+		return aim_conn_completeconnect(sess, conn);
+
+	/*
+	 * Rendezvous (client-client) connections do not speak
+	 * FLAP, so this function will break on them.
+	 */
+	if (conn->type == AIM_CONN_TYPE_RENDEZVOUS) 
+		return aim_get_command_rendezvous(sess, conn);
+	else if (conn->type == AIM_CONN_TYPE_RENDEZVOUS_OUT) {
+		faimdprintf(sess, 0, "AIM_CONN_TYPE_RENDEZVOUS_OUT on fd %d\n", conn->fd);
+		return 0; 
+	}
+
+	aim_bstream_init(&flaphdr, flaphdr_raw, sizeof(flaphdr_raw));
+
+	/*
+	 * Read FLAP header.  Six bytes:
+	 *    
+	 *   0 char  -- Always 0x2a
+	 *   1 char  -- Channel ID.  Usually 2 -- 1 and 4 are used during login.
+	 *   2 short -- Sequence number 
+	 *   4 short -- Number of data bytes that follow.
+	 */
+	if (aim_bstream_recv(&flaphdr, conn->fd, 6) < 6) {
+		aim_conn_close(conn);
+		return -1;
+	}
+
+	aim_bstream_rewind(&flaphdr);
+
+	/*
+	 * This shouldn't happen unless the socket breaks, the server breaks,
+	 * or we break.  We must handle it just in case.
+	 */
+	if (aimbs_get8(&flaphdr) != 0x2a) {
+		fu8_t start;
+
+		aim_bstream_rewind(&flaphdr);
+		start = aimbs_get8(&flaphdr);
+		faimdprintf(sess, 0, "FLAP framing disrupted (0x%02x)", start);
+		aim_conn_close(conn);
+		return -1;
+	}	
+
+	/* allocate a new struct */
+	if (!(newrx = (aim_frame_t *)malloc(sizeof(aim_frame_t))))
+		return -1;
+	memset(newrx, 0, sizeof(aim_frame_t));
+
+	/* we're doing FLAP if we're here */
+	newrx->hdrtype = AIM_FRAMETYPE_FLAP;
+	
+	newrx->hdr.flap.type = aimbs_get8(&flaphdr);
+	newrx->hdr.flap.seqnum = aimbs_get16(&flaphdr);
+	payloadlen = aimbs_get16(&flaphdr);
+
+	newrx->nofree = 0; /* free by default */
+
+	if (payloadlen) {
+		fu8_t *payload = NULL;
+
+		if (!(payload = (fu8_t *) malloc(payloadlen))) {
+			aim_frame_destroy(newrx);
+			return -1;
+		}
+
+		aim_bstream_init(&newrx->data, payload, payloadlen);
+
+		/* read the payload */
+		if (aim_bstream_recv(&newrx->data, conn->fd, payloadlen) < payloadlen) {
+			aim_frame_destroy(newrx); /* free's payload */
+			aim_conn_close(conn);
+			return -1;
+		}
+	} else
+		aim_bstream_init(&newrx->data, NULL, 0);
+
+
+	aim_bstream_rewind(&newrx->data);
+
+	newrx->conn = conn;
+
+	newrx->next = NULL;  /* this will always be at the bottom */
+
+	if (!sess->queue_incoming)
+		sess->queue_incoming = newrx;
+	else {
+		aim_frame_t *cur;
+
+		for (cur = sess->queue_incoming; cur->next; cur = cur->next)
+			;
+		cur->next = newrx;
+	}
+
+	newrx->conn->lastactivity = time(NULL);
+
+	return 0;  
+}
+
+/*
+ * Purge recieve queue of all handled commands (->handled==1).  Also
+ * allows for selective freeing using ->nofree so that the client can
+ * keep the data for various purposes.  
+ *
+ * If ->nofree is nonzero, the frame will be delinked from the global list, 
+ * but will not be free'ed.  The client _must_ keep a pointer to the
+ * data -- libfaim will not!  If the client marks ->nofree but
+ * does not keep a pointer, it's lost forever.
+ *
+ */
+faim_export void aim_purge_rxqueue(aim_session_t *sess)
+{
+	aim_frame_t *cur, **prev;
+
+	for (prev = &sess->queue_incoming; (cur = *prev); ) {
+		if (cur->handled) {
+
+			*prev = cur->next;
+			
+			if (!cur->nofree)
+				aim_frame_destroy(cur);
+
+		} else
+			prev = &cur->next;
+	}
+
+	return;
+}
+
+/*
+ * Since aim_get_command will aim_conn_kill dead connections, we need
+ * to clean up the rxqueue of unprocessed connections on that socket.
+ *
+ * XXX: this is something that was handled better in the old connection
+ * handling method, but eh.
+ */
+faim_internal void aim_rxqueue_cleanbyconn(aim_session_t *sess, aim_conn_t *conn)
+{
+	aim_frame_t *currx;
+
+	for (currx = sess->queue_incoming; currx; currx = currx->next) {
+		if ((!currx->handled) && (currx->conn == conn))
+			currx->handled = 1;
+	}	
+	return;
+}
+
Index: libfaim/search.c
===================================================================
--- libfaim/search.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/search.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,122 @@
+
+/*
+ * aim_search.c
+ *
+ * TODO: Add aim_usersearch_name()
+ *
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+faim_export int aim_usersearch_address(aim_session_t *sess, aim_conn_t *conn, const char *address)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!sess || !conn || !address)
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+strlen(address))))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x000a, 0x0002, 0x0000, strdup(address), strlen(address)+1);
+	aim_putsnac(&fr->data, 0x000a, 0x0002, 0x0000, snacid);
+	
+	aimbs_putraw(&fr->data, address, strlen(address)); 
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/* XXX can this be integrated with the rest of the error handling? */
+static int error(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_snac_t *snac2;
+
+	/* XXX the modules interface should have already retrieved this for us */
+	if (!(snac2 = aim_remsnac(sess, snac->id))) {
+		faimdprintf(sess, 2, "search error: couldn't get a snac for 0x%08lx\n", snac->id);
+		return 0;
+	}
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, snac2->data /* address */);
+
+	/* XXX freesnac()? */
+	if (snac2)
+		free(snac2->data);
+	free(snac2);
+
+	return ret;
+}
+
+static int reply(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	int j = 0, m, ret = 0;
+	aim_tlvlist_t *tlvlist;
+	char *cur = NULL, *buf = NULL;
+	aim_rxcallback_t userfunc;
+	aim_snac_t *snac2;
+	char *searchaddr = NULL;
+
+	if ((snac2 = aim_remsnac(sess, snac->id)))
+		searchaddr = (char *)snac2->data;
+
+	tlvlist = aim_readtlvchain(bs);
+	m = aim_counttlvchain(&tlvlist);
+
+	/* XXX uhm. */
+	while ((cur = aim_gettlv_str(tlvlist, 0x0001, j+1)) && j < m) {
+		buf = realloc(buf, (j+1) * (MAXSNLEN+1));
+
+		strncpy(&buf[j * (MAXSNLEN+1)], cur, MAXSNLEN);
+		free(cur);
+
+		j++; 
+	}
+
+	aim_freetlvchain(&tlvlist);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, searchaddr, j, buf);
+
+	/* XXX freesnac()? */
+	if (snac2)
+		free(snac2->data);
+	free(snac2);
+
+	free(buf);
+
+	return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+	if (snac->subtype == 0x0001)
+		return error(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x0003)
+		return reply(sess, mod, rx, snac, bs);
+
+	return 0;
+}
+
+faim_internal int search_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0x000a;
+	mod->version = 0x0001;
+	mod->toolid = 0x0110;
+	mod->toolversion = 0x047b;
+	mod->flags = 0;
+	strncpy(mod->name, "search", sizeof(mod->name));
+	mod->snachandler = snachandler;
+
+	return 0;
+}
+
+
Index: libfaim/service.c
===================================================================
--- libfaim/service.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/service.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,972 @@
+/*
+ * Group 1.  This is a very special group.  All connections support
+ * this group, as it does some particularly good things (like rate limiting).
+ */
+
+#define FAIM_INTERNAL
+#define FAIM_NEED_CONN_INTERNAL
+#include <aim.h>
+
+#include "md5.h"
+
+/* Client Online (group 1, subtype 2) */
+faim_export int aim_clientready(aim_session_t *sess, aim_conn_t *conn)
+{
+	aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside;
+	struct snacgroup *sg;
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!ins)
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0001, 0x0002, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0001, 0x0002, 0x0000, snacid);
+
+	/*
+	 * Send only the tool versions that the server cares about (that it
+	 * marked as supporting in the server ready SNAC).  
+	 */
+	for (sg = ins->groups; sg; sg = sg->next) {
+		aim_module_t *mod;
+
+		if ((mod = aim__findmodulebygroup(sess, sg->group))) {
+			aimbs_put16(&fr->data, mod->family);
+			aimbs_put16(&fr->data, mod->version);
+			aimbs_put16(&fr->data, mod->toolid);
+			aimbs_put16(&fr->data, mod->toolversion);
+		} else
+			faimdprintf(sess, 1, "aim_clientready: server supports group 0x%04x but we don't!\n", sg->group);
+	}
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * Host Online (group 1, type 3)
+ * 
+ * See comments in conn.c about how the group associations are supposed
+ * to work, and how they really work.
+ *
+ * This info probably doesn't even need to make it to the client.
+ *
+ * We don't actually call the client here.  This starts off the connection
+ * initialization routine required by all AIM connections.  The next time
+ * the client is called is the CONNINITDONE callback, which should be 
+ * shortly after the rate information is acknowledged.
+ * 
+ */
+static int hostonline(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	fu16_t *families;
+	int famcount;
+
+
+	if (!(families = malloc(aim_bstream_empty(bs))))
+		return 0;
+
+	for (famcount = 0; aim_bstream_empty(bs); famcount++) {
+		families[famcount] = aimbs_get16(bs);
+		aim_conn_addgroup(rx->conn, families[famcount]);
+	}
+
+	free(families);
+
+
+	/*
+	 * Next step is in the Host Versions handler.
+	 *
+	 * Note that we must send this before we request rates, since
+	 * the format of the rate information depends on the versions we
+	 * give it.
+	 *
+	 */
+	aim_setversions(sess, rx->conn);
+
+	return 1; 
+}
+
+/* Service request (group 1, type 4) */
+faim_export int aim_reqservice(aim_session_t *sess, aim_conn_t *conn, fu16_t serviceid)
+{
+	return aim_genericreq_s(sess, conn, 0x0001, 0x0004, &serviceid);
+}
+
+/* Redirect (group 1, type 5) */
+static int redirect(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	struct aim_redirect_data redir;
+	aim_rxcallback_t userfunc;
+	aim_tlvlist_t *tlvlist;
+	aim_snac_t *origsnac = NULL;
+	int ret = 0;
+
+	memset(&redir, 0, sizeof(redir));
+
+	tlvlist = aim_readtlvchain(bs);
+
+	if (!aim_gettlv(tlvlist, 0x000d, 1) ||
+			!aim_gettlv(tlvlist, 0x0005, 1) ||
+			!aim_gettlv(tlvlist, 0x0006, 1)) {
+		aim_freetlvchain(&tlvlist);
+		return 0;
+	}
+
+	redir.group = aim_gettlv16(tlvlist, 0x000d, 1);
+	redir.ip = aim_gettlv_str(tlvlist, 0x0005, 1);
+	redir.cookie = aim_gettlv_str(tlvlist, 0x0006, 1);
+
+	/* Fetch original SNAC so we can get csi if needed */
+	origsnac = aim_remsnac(sess, snac->id);
+
+	if ((redir.group == AIM_CONN_TYPE_CHAT) && origsnac) {
+		struct chatsnacinfo *csi = (struct chatsnacinfo *)origsnac->data;
+
+		redir.chat.exchange = csi->exchange;
+		redir.chat.room = csi->name;
+		redir.chat.instance = csi->instance;
+	}
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, &redir);
+
+	free((void *)redir.ip);
+	free((void *)redir.cookie);
+
+	if (origsnac)
+		free(origsnac->data);
+	free(origsnac);
+
+	aim_freetlvchain(&tlvlist);
+
+	return ret;
+}
+
+/* Request Rate Information. (group 1, type 6) */
+faim_internal int aim_reqrates(aim_session_t *sess, aim_conn_t *conn)
+{
+	return aim_genericreq_n(sess, conn, 0x0001, 0x0006);
+}
+
+/*
+ * OSCAR defines several 'rate classes'.  Each class has seperate
+ * rate limiting properties (limit level, alert level, disconnect
+ * level, etc), and a set of SNAC family/type pairs associated with
+ * it.  The rate classes, their limiting properties, and the definitions
+ * of which SNACs are belong to which class, are defined in the
+ * Rate Response packet at login to each host.  
+ *
+ * Logically, all rate offenses within one class count against further
+ * offenses for other SNACs in the same class (ie, sending messages
+ * too fast will limit the number of user info requests you can send,
+ * since those two SNACs are in the same rate class).
+ *
+ * Since the rate classes are defined dynamically at login, the values
+ * below may change. But they seem to be fairly constant.
+ *
+ * Currently, BOS defines five rate classes, with the commonly used
+ * members as follows...
+ *
+ *  Rate class 0x0001:
+ *  	- Everything thats not in any of the other classes
+ *
+ *  Rate class 0x0002:
+ * 	- Buddy list add/remove
+ *	- Permit list add/remove
+ *	- Deny list add/remove
+ *
+ *  Rate class 0x0003:
+ *	- User information requests
+ *	- Outgoing ICBMs
+ *
+ *  Rate class 0x0004:
+ *	- A few unknowns: 2/9, 2/b, and f/2
+ *
+ *  Rate class 0x0005:
+ *	- Chat room create
+ *	- Outgoing chat ICBMs
+ *
+ * The only other thing of note is that class 5 (chat) has slightly looser
+ * limiting properties than class 3 (normal messages).  But thats just a 
+ * small bit of trivia for you.
+ *
+ * The last thing that needs to be learned about the rate limiting
+ * system is how the actual numbers relate to the passing of time.  This
+ * seems to be a big mystery.
+ * 
+ */
+
+static void rc_addclass(struct rateclass **head, struct rateclass *inrc)
+{
+	struct rateclass *rc, *rc2;
+
+	if (!(rc = malloc(sizeof(struct rateclass))))
+		return;
+
+	memcpy(rc, inrc, sizeof(struct rateclass));
+	rc->next = NULL;
+
+	for (rc2 = *head; rc2 && rc2->next; rc2 = rc2->next)
+		;
+
+	if (!rc2)
+		*head = rc;
+	else
+		rc2->next = rc;
+
+	return;
+}
+
+static struct rateclass *rc_findclass(struct rateclass **head, fu16_t id)
+{
+	struct rateclass *rc;
+
+	for (rc = *head; rc; rc = rc->next) {
+		if (rc->classid == id)
+			return rc;
+	}
+
+	return NULL;
+}
+
+static void rc_addpair(struct rateclass *rc, fu16_t group, fu16_t type)
+{
+	struct snacpair *sp, *sp2;
+
+	if (!(sp = malloc(sizeof(struct snacpair))))
+		return;
+	memset(sp, 0, sizeof(struct snacpair));
+
+	sp->group = group;
+	sp->subtype = type;
+	sp->next = NULL;
+
+	for (sp2 = rc->members; sp2 && sp2->next; sp2 = sp2->next)
+		;
+
+	if (!sp2)
+		rc->members = sp;
+	else
+		sp2->next = sp;
+
+	return;
+}
+
+/* Rate Parameters (group 1, type 7) */
+static int rateresp(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_conn_inside_t *ins = (aim_conn_inside_t *)rx->conn->inside;
+	fu16_t numclasses, i;
+	aim_rxcallback_t userfunc;
+
+
+	/*
+	 * First are the parameters for each rate class.
+	 */
+	numclasses = aimbs_get16(bs);
+	for (i = 0; i < numclasses; i++) {
+		struct rateclass rc;
+
+		memset(&rc, 0, sizeof(struct rateclass));
+
+		rc.classid = aimbs_get16(bs);
+		rc.windowsize = aimbs_get32(bs);
+		rc.clear = aimbs_get32(bs);
+		rc.alert = aimbs_get32(bs);
+		rc.limit = aimbs_get32(bs);
+		rc.disconnect = aimbs_get32(bs);
+		rc.current = aimbs_get32(bs);
+		rc.max = aimbs_get32(bs);
+
+		/*
+		 * The server will send an extra five bytes of parameters
+		 * depending on the version we advertised in 1/17.  If we
+		 * didn't send 1/17 (evil!), then this will crash and you
+		 * die, as it will default to the old version but we have 
+		 * the new version hardcoded here. 
+		 */
+		if (mod->version >= 3)
+			aimbs_getrawbuf(bs, rc.unknown, sizeof(rc.unknown));
+
+		faimdprintf(sess, 1, "--- Adding rate class %d to connection type %d: window size = %ld, clear = %ld, alert = %ld, limit = %ld, disconnect = %ld, current = %ld, max = %ld\n", rx->conn->type, rc.classid, rc.windowsize, rc.clear, rc.alert, rc.limit, rc.disconnect, rc.current, rc.max);
+
+		rc_addclass(&ins->rates, &rc);
+	}
+
+	/*
+	 * Then the members of each class.
+	 */
+	for (i = 0; i < numclasses; i++) {
+		fu16_t classid, count;
+		struct rateclass *rc;
+		int j;
+
+		classid = aimbs_get16(bs);
+		count = aimbs_get16(bs);
+
+		rc = rc_findclass(&ins->rates, classid);
+
+		for (j = 0; j < count; j++) {
+			fu16_t group, subtype;
+
+			group = aimbs_get16(bs);
+			subtype = aimbs_get16(bs);
+
+			if (rc)
+				rc_addpair(rc, group, subtype);
+		}
+	}
+
+	/*
+	 * We don't pass the rate information up to the client, as it really
+	 * doesn't care.  The information is stored in the connection, however
+	 * so that we can do more fun stuff later (not really).
+	 */
+
+	/*
+	 * Last step in the conn init procedure is to acknowledge that we
+	 * agree to these draconian limitations.
+	 */
+	aim_rates_addparam(sess, rx->conn);
+
+	/*
+	 * Finally, tell the client it's ready to go...
+	 */
+	if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE)))
+		userfunc(sess, rx);
+
+
+	return 1;
+}
+
+/* Add Rate Parameter (group 1, type 8) */
+faim_internal int aim_rates_addparam(aim_session_t *sess, aim_conn_t *conn)
+{
+	aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside;
+	aim_frame_t *fr;	
+	aim_snacid_t snacid;
+	struct rateclass *rc;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512)))
+		return -ENOMEM; 
+
+	snacid = aim_cachesnac(sess, 0x0001, 0x0008, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0001, 0x0008, 0x0000, snacid);
+
+	for (rc = ins->rates; rc; rc = rc->next)
+		aimbs_put16(&fr->data, rc->classid);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/* Delete Rate Parameter (group 1, type 9) */
+faim_internal int aim_rates_delparam(aim_session_t *sess, aim_conn_t *conn)
+{
+	aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside;
+	aim_frame_t *fr;	
+	aim_snacid_t snacid;
+	struct rateclass *rc;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512)))
+		return -ENOMEM; 
+
+	snacid = aim_cachesnac(sess, 0x0001, 0x0009, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0001, 0x0009, 0x0000, snacid);
+
+	for (rc = ins->rates; rc; rc = rc->next)
+		aimbs_put16(&fr->data, rc->classid);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/* Rate Change (group 1, type 0x0a) */
+static int ratechange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_rxcallback_t userfunc;
+	fu16_t code, rateclass;
+	fu32_t currentavg, maxavg, windowsize, clear, alert, limit, disconnect;
+
+	code = aimbs_get16(bs);
+	rateclass = aimbs_get16(bs);
+	
+	windowsize = aimbs_get32(bs);
+	clear = aimbs_get32(bs);
+	alert = aimbs_get32(bs);
+	limit = aimbs_get32(bs);
+	disconnect = aimbs_get32(bs);
+	currentavg = aimbs_get32(bs);
+	maxavg = aimbs_get32(bs);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		return userfunc(sess, rx, code, rateclass, windowsize, clear, alert, limit, disconnect, currentavg, maxavg);
+
+	return 0;
+}
+
+/*
+ * How Migrations work.  
+ *
+ * The server sends a Server Pause message, which the client should respond to 
+ * with a Server Pause Ack, which contains the families it needs on this 
+ * connection. The server will send a Migration Notice with an IP address, and 
+ * then disconnect. Next the client should open the connection and send the 
+ * cookie.  Repeat the normal login process and pretend this never happened.
+ *
+ * The Server Pause contains no data.
+ *
+ */
+
+/* Service Pause (group 1, type 0x0b) */
+static int serverpause(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_rxcallback_t userfunc;
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		return userfunc(sess, rx);
+
+	return 0;
+}
+
+/*
+ * Service Pause Acknowledgement (group 1, type 0x0c)
+ *
+ * It is rather important that aim_sendpauseack() gets called for the exact
+ * same connection that the Server Pause callback was called for, since
+ * libfaim extracts the data for the SNAC from the connection structure.
+ *
+ * Of course, if you don't do that, more bad things happen than just what
+ * libfaim can cause.
+ *
+ */
+faim_export int aim_sendpauseack(aim_session_t *sess, aim_conn_t *conn)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside;
+	struct snacgroup *sg;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1024)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0001, 0x000c, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0001, 0x000c, 0x0000, snacid);
+
+	/*
+	 * This list should have all the groups that the original 
+	 * Host Online / Server Ready said this host supports.  And 
+	 * we want them all back after the migration.
+	 */
+	for (sg = ins->groups; sg; sg = sg->next)
+		aimbs_put16(&fr->data, sg->group);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/* Service Resume (group 1, type 0x0d) */
+static int serverresume(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_rxcallback_t userfunc;
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		return userfunc(sess, rx);
+
+	return 0;
+}
+
+/* Request self-info (group 1, type 0x0e) */
+faim_export int aim_reqpersonalinfo(aim_session_t *sess, aim_conn_t *conn)
+{
+	return aim_genericreq_n(sess, conn, 0x0001, 0x000e);
+}
+
+/* Self User Info (group 1, type 0x0f) */
+static int selfinfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_rxcallback_t userfunc;
+	aim_userinfo_t userinfo;
+
+	aim_extractuserinfo(sess, bs, &userinfo);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		return userfunc(sess, rx, &userinfo);
+
+	return 0;
+}
+
+/* Evil Notification (group 1, type 0x10) */
+static int evilnotify(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_rxcallback_t userfunc;
+	fu16_t newevil;
+	aim_userinfo_t userinfo;
+
+	memset(&userinfo, 0, sizeof(aim_userinfo_t));
+	
+	newevil = aimbs_get16(bs);
+
+	if (aim_bstream_empty(bs))
+		aim_extractuserinfo(sess, bs, &userinfo);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		return userfunc(sess, rx, newevil, &userinfo);
+
+	return 0;
+}
+
+/*
+ * Idle Notification (group 1, type 0x11)
+ *
+ * Should set your current idle time in seconds.  Note that this should
+ * never be called consecutively with a non-zero idle time.  That makes
+ * OSCAR do funny things.  Instead, just set it once you go idle, and then
+ * call it again with zero when you're back.
+ *
+ */
+faim_export int aim_bos_setidle(aim_session_t *sess, aim_conn_t *conn, fu32_t idletime)
+{
+	return aim_genericreq_l(sess, conn, 0x0001, 0x0011, &idletime);
+}
+
+/*
+ * Service Migrate (group 1, type 0x12)
+ *
+ * This is the final SNAC sent on the original connection during a migration.
+ * It contains the IP and cookie used to connect to the new server, and 
+ * optionally a list of the SNAC groups being migrated.
+ *
+ */
+static int migrate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_rxcallback_t userfunc;
+	int ret = 0;
+	fu16_t groupcount, i;
+	aim_tlvlist_t *tl;
+	char *ip = NULL;
+	aim_tlv_t *cktlv;
+
+	/*
+	 * Apparently there's some fun stuff that can happen right here. The
+	 * migration can actually be quite selective about what groups it
+	 * moves to the new server.  When not all the groups for a connection
+	 * are migrated, or they are all migrated but some groups are moved
+	 * to a different server than others, it is called a bifurcated 
+	 * migration.
+	 *
+	 * Let's play dumb and not support that.
+	 *
+	 */
+	groupcount = aimbs_get16(bs);
+	for (i = 0; i < groupcount; i++) {
+		fu16_t group;
+
+		group = aimbs_get16(bs);
+
+		faimdprintf(sess, 0, "bifurcated migration unsupported -- group 0x%04x\n", group);
+	}
+
+	tl = aim_readtlvchain(bs);
+
+	if (aim_gettlv(tl, 0x0005, 1))
+		ip = aim_gettlv_str(tl, 0x0005, 1);
+
+	cktlv = aim_gettlv(tl, 0x0006, 1);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, ip, cktlv ? cktlv->value : NULL);
+
+	aim_freetlvchain(&tl);
+	free(ip);
+
+	return ret;
+}
+
+/* Message of the Day (group 1, type 0x13) */
+static int motd(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_rxcallback_t userfunc;
+	char *msg = NULL;
+	int ret = 0;
+	aim_tlvlist_t *tlvlist;
+	fu16_t id;
+
+	/*
+	 * Code.
+	 *
+	 * Valid values:
+	 *   1 Mandatory upgrade
+	 *   2 Advisory upgrade
+	 *   3 System bulletin
+	 *   4 Nothing's wrong ("top o the world" -- normal)
+	 *   5 Lets-break-something. 
+	 *
+	 */
+	id = aimbs_get16(bs);
+
+	/* 
+	 * TLVs follow 
+	 */
+	tlvlist = aim_readtlvchain(bs);
+
+	msg = aim_gettlv_str(tlvlist, 0x000b, 1);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, id, msg);
+
+	free(msg);
+
+	aim_freetlvchain(&tlvlist);
+
+	return ret;
+}
+
+/* 
+ * Set privacy flags (group 1, type 0x14)
+ *
+ * Normally 0x03.
+ *
+ *  Bit 1:  Allows other AIM users to see how long you've been idle.
+ *  Bit 2:  Allows other AIM users to see how long you've been a member.
+ *
+ */
+faim_export int aim_bos_setprivacyflags(aim_session_t *sess, aim_conn_t *conn, fu32_t flags)
+{
+	return aim_genericreq_l(sess, conn, 0x0001, 0x0014, &flags);
+}
+
+/*
+ * No-op (group 1, type 0x16)
+ *
+ * WinAIM sends these every 4min or so to keep the connection alive.  Its not 
+ * real necessary.
+ *
+ */
+faim_export int aim_nop(aim_session_t *sess, aim_conn_t *conn)
+{
+	return aim_genericreq_n(sess, conn, 0x0001, 0x0016);
+}
+
+/* 
+ * Set client versions (group 1, subtype 0x17) 
+ *
+ * If you've seen the clientonline/clientready SNAC you're probably 
+ * wondering what the point of this one is.  And that point seems to be
+ * that the versions in the client online SNAC are sent too late for the
+ * server to be able to use them to change the protocol for the earlier
+ * login packets (client versions are sent right after Host Online is 
+ * received, but client online versions aren't sent until quite a bit later).
+ * We can see them already making use of this by changing the format of
+ * the rate information based on what version of group 1 we advertise here.
+ *
+ */
+faim_internal int aim_setversions(aim_session_t *sess, aim_conn_t *conn)
+{
+	aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside;
+	struct snacgroup *sg;
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!ins)
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0001, 0x0017, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0001, 0x0017, 0x0000, snacid);
+
+	/*
+	 * Send only the versions that the server cares about (that it
+	 * marked as supporting in the server ready SNAC).  
+	 */
+	for (sg = ins->groups; sg; sg = sg->next) {
+		aim_module_t *mod;
+
+		if ((mod = aim__findmodulebygroup(sess, sg->group))) {
+			aimbs_put16(&fr->data, mod->family);
+			aimbs_put16(&fr->data, mod->version);
+		} else
+			faimdprintf(sess, 1, "aim_setversions: server supports group 0x%04x but we don't!\n", sg->group);
+	}
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/* Host versions (group 1, subtype 0x18) */
+static int hostversions(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	int vercount;
+	fu8_t *versions;
+
+	/* This is frivolous. (Thank you SmarterChild.) */
+	vercount = aim_bstream_empty(bs)/4;
+	versions = aimbs_getraw(bs, aim_bstream_empty(bs));
+	free(versions);
+
+	/*
+	 * Now request rates.
+	 */
+	aim_reqrates(sess, rx->conn);
+
+	return 1;
+}
+
+/* 
+ * Set Extended Status (group 1, type 0x1e) 
+ *
+ * Currently only works if using ICQ.
+ *
+ */
+faim_export int aim_setextstatus(aim_session_t *sess, aim_conn_t *conn, fu32_t status)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	aim_tlvlist_t *tl = NULL;
+	fu32_t data;
+
+	data = 0x00030000 | status; /* yay for error checking ;^) */
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 8)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0001, 0x001e, 0x0000, NULL, 0);
+	aim_putsnac(&fr->data, 0x0001, 0x001e, 0x0000, snacid);
+	
+	aim_addtlvtochain32(&tl, 0x0006, data);
+	aim_writetlvchain(&fr->data, &tl);
+	aim_freetlvchain(&tl);
+	
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * Starting this past week (26 Mar 2001, say), AOL has started sending
+ * this nice little extra SNAC.  AFAIK, it has never been used until now.
+ *
+ * The request contains eight bytes.  The first four are an offset, the
+ * second four are a length.
+ *
+ * The offset is an offset into aim.exe when it is mapped during execution
+ * on Win32.  So far, AOL has only been requesting bytes in static regions
+ * of memory.  (I won't put it past them to start requesting data in
+ * less static regions -- regions that are initialized at run time, but still
+ * before the client recieves this request.)
+ *
+ * When the client recieves the request, it adds it to the current ds
+ * (0x00400000) and dereferences it, copying the data into a buffer which
+ * it then runs directly through the MD5 hasher.  The 16 byte output of
+ * the hash is then sent back to the server.
+ *
+ * If the client does not send any data back, or the data does not match
+ * the data that the specific client should have, the client will get the
+ * following message from "AOL Instant Messenger":
+ *    "You have been disconnected from the AOL Instant Message Service (SM) 
+ *     for accessing the AOL network using unauthorized software.  You can
+ *     download a FREE, fully featured, and authorized client, here 
+ *     http://www.aol.com/aim/download2.html"
+ * The connection is then closed, recieving disconnect code 1, URL
+ * http://www.aim.aol.com/errors/USER_LOGGED_OFF_NEW_LOGIN.html.  
+ *
+ * Note, however, that numerous inconsistencies can cause the above error, 
+ * not just sending back a bad hash.  Do not immediatly suspect this code
+ * if you get disconnected.  AOL and the open/free software community have
+ * played this game for a couple years now, generating the above message
+ * on numerous ocassions.
+ *
+ * Anyway, neener.  We win again.
+ *
+ */
+/* Client verification (group 1, subtype 0x1f) */
+static int memrequest(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	aim_rxcallback_t userfunc;
+	fu32_t offset, len;
+	aim_tlvlist_t *list;
+	char *modname;
+
+	offset = aimbs_get32(bs);
+	len = aimbs_get32(bs);
+	list = aim_readtlvchain(bs);
+
+	modname = aim_gettlv_str(list, 0x0001, 1);
+
+	faimdprintf(sess, 1, "data at 0x%08lx (%d bytes) of requested\n", offset, len, modname ? modname : "aim.exe");
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		return userfunc(sess, rx, offset, len, modname);
+
+	free(modname);
+	aim_freetlvchain(&list);
+
+	return 0;
+}
+
+#if 0 
+static void dumpbox(aim_session_t *sess, unsigned char *buf, int len)
+{
+	int i;
+
+	if (!sess || !buf || !len)
+		return;
+
+	faimdprintf(sess, 1, "\nDump of %d bytes at %p:", len, buf);
+
+	for (i = 0; i < len; i++) {
+		if ((i % 8) == 0)
+			faimdprintf(sess, 1, "\n\t");
+
+		faimdprintf(sess, 1, "0x%2x ", buf[i]);
+	}
+
+	faimdprintf(sess, 1, "\n\n");
+
+	return;
+}
+#endif
+
+/* Client verification reply (group 1, subtype 0x20) */
+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)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!sess || !conn)
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+16)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0001, 0x0020, 0x0000, NULL, 0);
+
+	aim_putsnac(&fr->data, 0x0001, 0x0020, 0x0000, snacid);
+	aimbs_put16(&fr->data, 0x0010); /* md5 is always 16 bytes */
+
+	if ((flag == AIM_SENDMEMBLOCK_FLAG_ISHASH) && buf && (len == 0x10)) { /* we're getting a hash */
+
+		aimbs_putraw(&fr->data, buf, 0x10); 
+
+	} else if (buf && (len > 0)) { /* use input buffer */
+		md5_state_t state;
+		md5_byte_t digest[0x10];
+
+		md5_init(&state);	
+		md5_append(&state, (const md5_byte_t *)buf, len);
+		md5_finish(&state, digest);
+
+		aimbs_putraw(&fr->data, (fu8_t *)digest, 0x10);
+
+	} else if (len == 0) { /* no length, just hash NULL (buf is optional) */
+		md5_state_t state;
+		fu8_t nil = '\0';
+		md5_byte_t digest[0x10];
+
+		/*
+		 * These MD5 routines are stupid in that you have to have
+		 * at least one append.  So thats why this doesn't look 
+		 * real logical.
+		 */
+		md5_init(&state);
+		md5_append(&state, (const md5_byte_t *)&nil, 0);
+		md5_finish(&state, digest);
+
+		aimbs_putraw(&fr->data, (fu8_t *)digest, 0x10);
+
+	} else {
+
+		/* 
+		 * This data is correct for AIM 3.5.1670.
+		 *
+		 * Using these blocks is as close to "legal" as you can get
+		 * without using an AIM binary.
+		 *
+		 */
+		if ((offset == 0x03ffffff) && (len == 0x03ffffff)) {
+
+#if 1 /* with "AnrbnrAqhfzcd" */
+			aimbs_put32(&fr->data, 0x44a95d26);
+			aimbs_put32(&fr->data, 0xd2490423);
+			aimbs_put32(&fr->data, 0x93b8821f);
+			aimbs_put32(&fr->data, 0x51c54b01);
+#else /* no filename */
+			aimbs_put32(&fr->data, 0x1df8cbae);
+			aimbs_put32(&fr->data, 0x5523b839);
+			aimbs_put32(&fr->data, 0xa0e10db3);
+			aimbs_put32(&fr->data, 0xa46d3b39);
+#endif
+
+		} else if ((offset == 0x00001000) && (len == 0x00000000)) {
+
+			aimbs_put32(&fr->data, 0xd41d8cd9);
+			aimbs_put32(&fr->data, 0x8f00b204);
+			aimbs_put32(&fr->data, 0xe9800998);
+			aimbs_put32(&fr->data, 0xecf8427e);
+
+		} else
+			faimdprintf(sess, 0, "sendmemblock: WARNING: unknown hash request\n");
+
+	}
+
+	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 hostonline(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x0005)
+		return redirect(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x0007)
+		return rateresp(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x000a)
+		return ratechange(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x000b)
+		return serverpause(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x000d)
+		return serverresume(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x000f)
+		return selfinfo(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x0010)
+		return evilnotify(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x0012)
+		return migrate(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x0013)
+		return motd(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x0018)
+		return hostversions(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x001f)
+		return memrequest(sess, mod, rx, snac, bs);
+
+	return 0;
+}
+
+faim_internal int general_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0x0001;
+	mod->version = 0x0003;
+	mod->toolid = 0x0110;
+	mod->toolversion = 0x047b;
+	mod->flags = 0;
+	strncpy(mod->name, "general", sizeof(mod->name));
+	mod->snachandler = snachandler;
+
+	return 0;
+}
+
Index: libfaim/snac.c
===================================================================
--- libfaim/snac.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/snac.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,146 @@
+/*
+ *
+ * Various SNAC-related dodads... 
+ *
+ * outstanding_snacs is a list of aim_snac_t structs.  A SNAC should be added
+ * whenever a new SNAC is sent and it should remain in the list until the
+ * response for it has been receieved.  
+ *
+ * cleansnacs() should be called periodically by the client in order
+ * to facilitate the aging out of unreplied-to SNACs. This can and does
+ * happen, so it should be handled.
+ *
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+/*
+ * Called from aim_session_init() to initialize the hash.
+ */
+faim_internal void aim_initsnachash(aim_session_t *sess)
+{
+	int i;
+
+	for (i = 0; i < FAIM_SNAC_HASH_SIZE; i++)
+		sess->snac_hash[i] = NULL;
+
+	return;
+}
+
+faim_internal aim_snacid_t aim_cachesnac(aim_session_t *sess, const fu16_t family, const fu16_t type, const fu16_t flags, const void *data, const int datalen)
+{
+	aim_snac_t snac;
+
+	snac.id = sess->snacid_next++;
+	snac.family = family;
+	snac.type = type;
+	snac.flags = flags;
+
+	if (datalen) {
+		if (!(snac.data = malloc(datalen)))
+			return 0; /* er... */
+		memcpy(snac.data, data, datalen);
+	} else
+		snac.data = NULL;
+
+	return aim_newsnac(sess, &snac);
+}
+
+/*
+ * Clones the passed snac structure and caches it in the
+ * list/hash.
+ */
+faim_internal aim_snacid_t aim_newsnac(aim_session_t *sess, aim_snac_t *newsnac)
+{
+	aim_snac_t *snac;
+	int index;
+
+	if (!newsnac)
+		return 0;
+
+	if (!(snac = malloc(sizeof(aim_snac_t))))
+		return 0;
+	memcpy(snac, newsnac, sizeof(aim_snac_t));
+	snac->issuetime = time(NULL);
+
+	index = snac->id % FAIM_SNAC_HASH_SIZE;
+
+	snac->next = (aim_snac_t *)sess->snac_hash[index];
+	sess->snac_hash[index] = (void *)snac;
+
+	return snac->id;
+}
+
+/*
+ * Finds a snac structure with the passed SNAC ID, 
+ * removes it from the list/hash, and returns a pointer to it.
+ *
+ * The returned structure must be freed by the caller.
+ *
+ */
+faim_internal aim_snac_t *aim_remsnac(aim_session_t *sess, aim_snacid_t id) 
+{
+	aim_snac_t *cur, **prev;
+	int index;
+
+	index = id % FAIM_SNAC_HASH_SIZE;
+
+	for (prev = (aim_snac_t **)&sess->snac_hash[index]; (cur = *prev); ) {
+		if (cur->id == id) {
+			*prev = cur->next;
+			return cur;
+		} else
+			prev = &cur->next;
+	}
+
+	return cur;
+}
+
+/*
+ * This is for cleaning up old SNACs that either don't get replies or
+ * a reply was never received for.  Garabage collection. Plain and simple.
+ *
+ * maxage is the _minimum_ age in seconds to keep SNACs.
+ *
+ */
+faim_internal void aim_cleansnacs(aim_session_t *sess, int maxage)
+{
+	int i;
+
+	for (i = 0; i < FAIM_SNAC_HASH_SIZE; i++) {
+		aim_snac_t *cur, **prev;
+		time_t curtime;
+
+		if (!sess->snac_hash[i])
+			continue;
+
+		curtime = time(NULL); /* done here in case we waited for the lock */
+
+		for (prev = (aim_snac_t **)&sess->snac_hash[i]; (cur = *prev); ) {
+			if ((curtime - cur->issuetime) > maxage) {
+
+				*prev = cur->next;
+
+				/* XXX should we have destructors here? */
+				free(cur->data);
+				free(cur);
+
+			} else
+				prev = &cur->next;
+		}
+	}
+
+	return;
+}
+
+faim_internal int aim_putsnac(aim_bstream_t *bs, fu16_t family, fu16_t subtype, fu16_t flags, aim_snacid_t snacid)
+{
+
+	aimbs_put16(bs, family);
+	aimbs_put16(bs, subtype);
+	aimbs_put16(bs, flags);
+	aimbs_put32(bs, snacid);
+
+	return 10;
+}
Index: libfaim/ssi.c
===================================================================
--- libfaim/ssi.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/ssi.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,211 @@
+/*
+ * Server-Side/Stored Information.
+ *
+ * Relatively new facility that allows storing of certain types of information,
+ * such as a users buddy list, permit/deny list, and permit/deny preferences, 
+ * to be stored on the server, so that they can be accessed from any client.
+ *
+ * This is entirely too complicated.
+ *
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+/*
+ * Request SSI Rights.
+ */
+faim_export int aim_ssi_reqrights(aim_session_t *sess, aim_conn_t *conn)
+{
+	return aim_genericreq_n(sess, conn, 0x0013, 0x0002);
+}
+
+/*
+ * SSI Rights Information.
+ */
+static int parserights(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;
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx);
+
+	return ret;
+}
+
+/*
+ * Request SSI Data.
+ *
+ * The data will only be sent if it is newer than the posted local
+ * timestamp and revision.
+ * 
+ * Note that the client should never increment the revision, only the server.
+ * 
+ */
+faim_export int aim_ssi_reqdata(aim_session_t *sess, aim_conn_t *conn, time_t localstamp, fu16_t localrev)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+
+	if (!sess || !conn)
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+4+2)))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0013, 0x0005, 0x0000, NULL, 0);
+
+	aim_putsnac(&fr->data, 0x0013, 0x0005, 0x0000, snacid);
+	aimbs_put32(&fr->data, localstamp);
+	aimbs_put16(&fr->data, localrev);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
+/*
+ * SSI Data.
+ */
+static int parsedata(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;
+	struct aim_ssi_item *list = NULL;
+	fu8_t fmtver; /* guess */
+	fu16_t itemcount;
+	fu32_t stamp;
+
+	fmtver = aimbs_get8(bs);
+	itemcount = aimbs_get16(bs);
+
+	while (aim_bstream_empty(bs) > 4) { /* last four bytes are stamp */
+		fu16_t namelen, tbslen;
+		struct aim_ssi_item *nl, *el;
+
+		if (!(nl = malloc(sizeof(struct aim_ssi_item))))
+			break;
+		memset(nl, 0, sizeof(struct aim_ssi_item));
+
+		if ((namelen = aimbs_get16(bs)))
+			nl->name = aimbs_getstr(bs, namelen);
+		nl->gid = aimbs_get16(bs);
+		nl->bid = aimbs_get16(bs);
+		nl->type = aimbs_get16(bs);
+
+		if ((tbslen = aimbs_get16(bs))) {
+			aim_bstream_t tbs;
+
+			aim_bstream_init(&tbs, bs->data + bs->offset /* XXX */, tbslen);
+			nl->data = (void *)aim_readtlvchain(&tbs);
+			aim_bstream_advance(bs, tbslen);
+		}
+
+		for (el = list; el && el->next; el = el->next)
+			;
+		if (el)
+			el->next = nl;
+		else
+			list = nl;
+	}
+
+	stamp = aimbs_get32(bs);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx, fmtver, itemcount, stamp, list);
+
+	while (list) {
+		struct aim_ssi_item *tmp;
+
+		tmp = list->next;
+		aim_freetlvchain((aim_tlvlist_t **)&list->data);
+		free(list);
+		list = tmp;
+	}
+
+	return ret;
+}
+
+/*
+ * SSI Data Enable Presence.
+ *
+ * Should be sent after receiving 13/6 or 13/f to tell the server you
+ * are ready to begin using the list.  It will promptly give you the
+ * presence information for everyone in your list and put your permit/deny
+ * settings into effect.
+ * 
+ */
+faim_export int aim_ssi_enable(aim_session_t *sess, aim_conn_t *conn)
+{
+	return aim_genericreq_n(sess, conn, 0x0013, 0x0007);
+}
+
+/*
+ * SSI Begin Data Modification.
+ *
+ * Tells the server you're going to start modifying data.
+ * 
+ */
+faim_export int aim_ssi_modbegin(aim_session_t *sess, aim_conn_t *conn)
+{
+	return aim_genericreq_n(sess, conn, 0x0013, 0x0011);
+}
+
+/*
+ * SSI End Data Modification.
+ *
+ * Tells the server you're done modifying data.
+ *
+ */
+faim_export int aim_ssi_modend(aim_session_t *sess, aim_conn_t *conn)
+{
+	return aim_genericreq_n(sess, conn, 0x0013, 0x0012);
+}
+
+/*
+ * SSI Data Unchanged.
+ *
+ * Response to aim_ssi_reqdata() if the server-side data is not newer than
+ * posted local stamp/revision.
+ *
+ */
+static int parsedataunchanged(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;
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		ret = userfunc(sess, rx);
+
+	return ret;
+}
+
+static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+
+	if (snac->subtype == 0x0003)
+		return parserights(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x006)
+		return parsedata(sess, mod, rx, snac, bs);
+	else if (snac->subtype == 0x00f)
+		return parsedataunchanged(sess, mod, rx, snac, bs);
+
+	return 0;
+}
+
+faim_internal int ssi_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0x0013;
+	mod->version = 0x0001;
+	mod->toolid = 0x0110;
+	mod->toolversion = 0x047b;
+	mod->flags = 0;
+	strncpy(mod->name, "ssi", sizeof(mod->name));
+	mod->snachandler = snachandler;
+
+	return 0;
+}
+
+
Index: libfaim/stamp-h
===================================================================
--- libfaim/stamp-h	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/stamp-h	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,1 @@
+timestamp
Index: libfaim/stats.c
===================================================================
--- libfaim/stats.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/stats.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,39 @@
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+static int reportinterval(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+{
+	fu16_t interval;
+	aim_rxcallback_t userfunc;
+
+	interval = aimbs_get16(bs);
+
+	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
+		return userfunc(sess, rx, interval);
+
+	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 == 0x0002)
+		return reportinterval(sess, mod, rx, snac, bs);
+
+	return 0;
+}
+
+faim_internal int stats_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0x000b;
+	mod->version = 0x0001;
+	mod->toolid = 0x0104;
+	mod->toolversion = 0x0001;
+	mod->flags = 0;
+	strncpy(mod->name, "stats", sizeof(mod->name));
+	mod->snachandler = snachandler;
+
+	return 0;
+}
Index: libfaim/tlv.c
===================================================================
--- libfaim/tlv.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/tlv.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,609 @@
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+static aim_tlv_t *createtlv(void)
+{
+	aim_tlv_t *newtlv;
+
+	if (!(newtlv = (aim_tlv_t *)malloc(sizeof(aim_tlv_t))))
+		return NULL;
+	memset(newtlv, 0, sizeof(aim_tlv_t));
+
+	return newtlv;
+}
+
+static void freetlv(aim_tlv_t **oldtlv)
+{
+
+	if (!oldtlv || !*oldtlv)
+		return;
+	
+	free((*oldtlv)->value);
+	free(*oldtlv);
+	*oldtlv = NULL;
+
+	return;
+}
+
+/**
+ * aim_readtlvchain - Read a TLV chain from a buffer.
+ * @buf: Input buffer
+ * @maxlen: Length of input buffer
+ *
+ * Reads and parses a series of TLV patterns from a data buffer; the
+ * returned structure is manipulatable with the rest of the TLV
+ * routines.  When done with a TLV chain, aim_freetlvchain() should
+ * be called to free the dynamic substructures.
+ *
+ * XXX There should be a flag setable here to have the tlvlist contain
+ * bstream references, so that at least the ->value portion of each 
+ * element doesn't need to be malloc/memcpy'd.  This could prove to be
+ * just as effecient as the in-place TLV parsing used in a couple places
+ * in libfaim.
+ *
+ */
+faim_internal aim_tlvlist_t *aim_readtlvchain(aim_bstream_t *bs)
+{
+	aim_tlvlist_t *list = NULL, *cur;
+
+	while (aim_bstream_empty(bs) > 0) {
+		fu16_t type, length;
+
+		type = aimbs_get16(bs);
+		length = aimbs_get16(bs);
+
+#if 0 /* temporarily disabled until I know if they're still doing it or not */
+		/*
+		 * Okay, so now AOL has decided that any TLV of
+		 * type 0x0013 can only be two bytes, despite
+		 * what the actual given length is.  So here 
+		 * we dump any invalid TLVs of that sort.  Hopefully
+		 * theres no special cases to this special case.
+		 *   - mid (30jun2000)
+		 */
+		if ((type == 0x0013) && (length != 0x0002))
+			length = 0x0002;
+#else
+		if (0)
+			;
+#endif
+		else {
+
+			if (length > aim_bstream_empty(bs))
+				goto errout;
+
+			cur = (aim_tlvlist_t *)malloc(sizeof(aim_tlvlist_t));
+			if (!cur)
+				goto errout;
+
+			memset(cur, 0, sizeof(aim_tlvlist_t));
+
+			cur->tlv = createtlv();	
+			if (!cur->tlv) {
+				free(cur);
+				goto errout;
+			}
+			cur->tlv->type = type;
+			if ((cur->tlv->length = length)) {
+			       cur->tlv->value = aimbs_getraw(bs, length);	
+			       if (!cur->tlv->value) {
+				       freetlv(&cur->tlv);
+				       free(cur);
+				       goto errout;
+			       }
+			}
+
+			cur->next = list;
+			list = cur;
+		}
+	}
+
+	return list;
+
+errout:
+	aim_freetlvchain(&list);
+	return NULL;
+}
+
+/**
+ * aim_freetlvchain - Free a TLV chain structure
+ * @list: Chain to be freed
+ *
+ * Walks the list of TLVs in the passed TLV chain and
+ * frees each one. Note that any references to this data
+ * should be removed before calling this.
+ *
+ */
+faim_internal void aim_freetlvchain(aim_tlvlist_t **list)
+{
+	aim_tlvlist_t *cur;
+
+	if (!list || !*list)
+		return;
+
+	for (cur = *list; cur; ) {
+		aim_tlvlist_t *tmp;
+		
+		freetlv(&cur->tlv);
+
+		tmp = cur->next;
+		free(cur);
+		cur = tmp;
+	}
+
+	list = NULL;
+
+	return;
+}
+
+/**
+ * aim_counttlvchain - Count the number of TLVs in a chain
+ * @list: Chain to be counted
+ *
+ * Returns the number of TLVs stored in the passed chain.
+ *
+ */
+faim_internal int aim_counttlvchain(aim_tlvlist_t **list)
+{
+	aim_tlvlist_t *cur;
+	int count;
+
+	if (!list || !*list)
+		return 0;
+
+	for (cur = *list, count = 0; cur; cur = cur->next)
+		count++;
+
+	return count;
+}
+
+/**
+ * aim_sizetlvchain - Count the number of bytes in a TLV chain
+ * @list: Chain to be sized
+ *
+ * Returns the number of bytes that would be needed to 
+ * write the passed TLV chain to a data buffer.
+ *
+ */
+faim_internal int aim_sizetlvchain(aim_tlvlist_t **list)
+{
+	aim_tlvlist_t *cur;
+	int size;
+
+	if (!list || !*list)
+		return 0;
+
+	for (cur = *list, size = 0; cur; cur = cur->next)
+		size += (4 + cur->tlv->length);
+
+	return size;
+}
+
+/**
+ * aim_addtlvtochain_str - Add a string to a TLV chain
+ * @list: Desination chain (%NULL pointer if empty)
+ * @type: TLV type
+ * @str: String to add
+ * @len: Length of string to add (not including %NULL)
+ *
+ * Adds the passed string as a TLV element of the passed type
+ * to the TLV chain.
+ *
+ */
+faim_internal int aim_addtlvtochain_raw(aim_tlvlist_t **list, const fu16_t t, const fu16_t l, const fu8_t *v)
+{
+	aim_tlvlist_t *newtlv, *cur;
+
+	if (!list)
+		return 0;
+
+	if (!(newtlv = (aim_tlvlist_t *)malloc(sizeof(aim_tlvlist_t))))
+		return 0;
+	memset(newtlv, 0x00, sizeof(aim_tlvlist_t));
+
+	if (!(newtlv->tlv = createtlv())) {
+		free(newtlv);
+		return 0;
+	}
+	newtlv->tlv->type = t;
+	if ((newtlv->tlv->length = l)) {
+		newtlv->tlv->value = (fu8_t *)malloc(newtlv->tlv->length);
+		memcpy(newtlv->tlv->value, v, newtlv->tlv->length);
+	}
+
+	if (!*list)
+		*list = newtlv;
+	else {
+		for(cur = *list; cur->next; cur = cur->next)
+			;
+		cur->next = newtlv;
+	}
+
+	return newtlv->tlv->length;
+}
+
+/**
+ * aim_addtlvtochain8 - Add a 8bit integer to a TLV chain
+ * @list: Destination chain
+ * @type: TLV type to add
+ * @val: Value to add
+ *
+ * Adds a one-byte unsigned integer to a TLV chain.
+ *
+ */
+faim_internal int aim_addtlvtochain8(aim_tlvlist_t **list, const fu16_t t, const fu8_t v)
+{
+	fu8_t v8[1];
+
+	aimutil_put8(v8, v);
+
+	return aim_addtlvtochain_raw(list, t, 1, v8);
+}
+
+/**
+ * aim_addtlvtochain16 - Add a 16bit integer to a TLV chain
+ * @list: Destination chain
+ * @type: TLV type to add
+ * @val: Value to add
+ *
+ * Adds a two-byte unsigned integer to a TLV chain.
+ *
+ */
+faim_internal int aim_addtlvtochain16(aim_tlvlist_t **list, const fu16_t t, const fu16_t v)
+{
+	fu8_t v16[2];
+
+	aimutil_put16(v16, v);
+
+	return aim_addtlvtochain_raw(list, t, 2, v16);
+}
+
+/**
+ * aim_addtlvtochain32 - Add a 32bit integer to a TLV chain
+ * @list: Destination chain
+ * @type: TLV type to add
+ * @val: Value to add
+ *
+ * Adds a four-byte unsigned integer to a TLV chain.
+ *
+ */
+faim_internal int aim_addtlvtochain32(aim_tlvlist_t **list, const fu16_t t, const fu32_t v)
+{
+	fu8_t v32[4];
+
+	aimutil_put32(v32, v);
+
+	return aim_addtlvtochain_raw(list, t, 4, v32);
+}
+
+/**
+ * aim_addtlvtochain_caps - Add a capability block to a TLV chain
+ * @list: Destination chain
+ * @type: TLV type to add
+ * @caps: Bitfield of capability flags to send
+ *
+ * Adds a block of capability blocks to a TLV chain. The bitfield
+ * passed in should be a bitwise %OR of any of the %AIM_CAPS constants:
+ *
+ *      %AIM_CAPS_BUDDYICON   Supports Buddy Icons
+ *
+ *      %AIM_CAPS_VOICE       Supports Voice Chat
+ *
+ *      %AIM_CAPS_IMIMAGE     Supports DirectIM/IMImage
+ *
+ *      %AIM_CAPS_CHAT        Supports Chat
+ *
+ *      %AIM_CAPS_GETFILE     Supports Get File functions
+ *
+ *      %AIM_CAPS_SENDFILE    Supports Send File functions
+ *
+ */
+faim_internal int aim_addtlvtochain_caps(aim_tlvlist_t **list, const fu16_t t, const fu32_t caps)
+{
+	fu8_t buf[16*16]; /* XXX icky fixed length buffer */
+	aim_bstream_t bs;
+
+	if (!caps)
+		return 0; /* nothing there anyway */
+
+	aim_bstream_init(&bs, buf, sizeof(buf));
+
+	aim_putcap(&bs, caps);
+
+	return aim_addtlvtochain_raw(list, t, aim_bstream_curpos(&bs), buf);
+}
+
+faim_internal int aim_addtlvtochain_userinfo(aim_tlvlist_t **list, fu16_t type, aim_userinfo_t *ui)
+{
+	fu8_t buf[1024]; /* bleh */
+	aim_bstream_t bs;
+
+	aim_bstream_init(&bs, buf, sizeof(buf));
+
+	aim_putuserinfo(&bs, ui);
+
+	return aim_addtlvtochain_raw(list, type, aim_bstream_curpos(&bs), buf);
+}
+
+/**
+ * aim_addtlvtochain_noval - Add a blank TLV to a TLV chain
+ * @list: Destination chain
+ * @type: TLV type to add
+ *
+ * Adds a TLV with a zero length to a TLV chain.
+ *
+ */
+faim_internal int aim_addtlvtochain_noval(aim_tlvlist_t **list, const fu16_t t)
+{
+	return aim_addtlvtochain_raw(list, t, 0, NULL);
+}
+
+/*
+ * Note that the inner TLV chain will not be modifiable as a tlvchain once
+ * it is written using this.  Or rather, it can be, but updates won't be
+ * made to this.
+ *
+ * XXX should probably support sublists for real.
+ * 
+ * This is so neat.
+ *
+ */
+faim_internal int aim_addtlvtochain_frozentlvlist(aim_tlvlist_t **list, fu16_t type, aim_tlvlist_t **tl)
+{
+	fu8_t *buf;
+	int buflen;
+	aim_bstream_t bs;
+
+	buflen = aim_sizetlvchain(tl);
+
+	if (buflen <= 0)
+		return 0;
+
+	if (!(buf = malloc(buflen)))
+		return 0;
+
+	aim_bstream_init(&bs, buf, buflen);
+
+	aim_writetlvchain(&bs, tl);
+
+	aim_addtlvtochain_raw(list, type, aim_bstream_curpos(&bs), buf);
+
+	free(buf);
+
+	return buflen;
+}
+
+/**
+ * aim_writetlvchain - Write a TLV chain into a data buffer.
+ * @buf: Destination buffer
+ * @buflen: Maximum number of bytes that will be written to buffer
+ * @list: Source TLV chain
+ *
+ * Copies a TLV chain into a raw data buffer, writing only the number
+ * of bytes specified. This operation does not free the chain; 
+ * aim_freetlvchain() must still be called to free up the memory used
+ * by the chain structures.
+ *
+ * XXX clean this up, make better use of bstreams 
+ */
+faim_internal int aim_writetlvchain(aim_bstream_t *bs, aim_tlvlist_t **list)
+{
+	int goodbuflen;
+	aim_tlvlist_t *cur;
+
+	/* do an initial run to test total length */
+	for (cur = *list, goodbuflen = 0; cur; cur = cur->next) {
+		goodbuflen += 2 + 2; /* type + len */
+		goodbuflen += cur->tlv->length;
+	}
+
+	if (goodbuflen > aim_bstream_empty(bs))
+		return 0; /* not enough buffer */
+
+	/* do the real write-out */
+	for (cur = *list; cur; cur = cur->next) {
+		aimbs_put16(bs, cur->tlv->type);
+		aimbs_put16(bs, cur->tlv->length);
+		if (cur->tlv->length)
+			aimbs_putraw(bs, cur->tlv->value, cur->tlv->length);
+	}
+
+	return 1; /* XXX this is a nonsensical return */
+}
+
+
+/**
+ * aim_gettlv - Grab the Nth TLV of type type in the TLV list list.
+ * @list: Source chain
+ * @type: Requested TLV type
+ * @nth: Index of TLV of type to get
+ *
+ * Returns a pointer to an aim_tlv_t of the specified type; 
+ * %NULL on error.  The @nth parameter is specified starting at %1.
+ * In most cases, there will be no more than one TLV of any type
+ * in a chain.
+ *
+ */
+faim_internal aim_tlv_t *aim_gettlv(aim_tlvlist_t *list, const fu16_t t, const int n)
+{
+	aim_tlvlist_t *cur;
+	int i;
+
+	for (cur = list, i = 0; cur; cur = cur->next) {
+		if (cur && cur->tlv) {
+			if (cur->tlv->type == t)
+				i++;
+			if (i >= n)
+				return cur->tlv;
+		}
+	}
+
+	return NULL;
+}
+
+/**
+ * aim_gettlv_str - Retrieve the Nth TLV in chain as a string.
+ * @list: Source TLV chain
+ * @type: TLV type to search for
+ * @nth: Index of TLV to return
+ *
+ * Same as aim_gettlv(), except that the return value is a %NULL-
+ * terminated string instead of an aim_tlv_t.  This is a 
+ * dynamic buffer and must be freed by the caller.
+ *
+ */
+faim_internal char *aim_gettlv_str(aim_tlvlist_t *list, const fu16_t t, const int n)
+{
+	aim_tlv_t *tlv;
+	char *newstr;
+
+	if (!(tlv = aim_gettlv(list, t, n)))
+		return NULL;
+
+	newstr = (char *) malloc(tlv->length + 1);
+	memcpy(newstr, tlv->value, tlv->length);
+	*(newstr + tlv->length) = '\0';
+
+	return newstr;
+}
+
+/**
+ * aim_gettlv8 - Retrieve the Nth TLV in chain as a 8bit integer.
+ * @list: Source TLV chain
+ * @type: TLV type to search for
+ * @nth: Index of TLV to return
+ *
+ * Same as aim_gettlv(), except that the return value is a 
+ * 8bit integer instead of an aim_tlv_t. 
+ *
+ */
+faim_internal fu8_t aim_gettlv8(aim_tlvlist_t *list, const fu16_t t, const int n)
+{
+	aim_tlv_t *tlv;
+
+	if (!(tlv = aim_gettlv(list, t, n)))
+		return 0; /* erm */
+	return aimutil_get8(tlv->value);
+}
+
+/**
+ * aim_gettlv16 - Retrieve the Nth TLV in chain as a 16bit integer.
+ * @list: Source TLV chain
+ * @type: TLV type to search for
+ * @nth: Index of TLV to return
+ *
+ * Same as aim_gettlv(), except that the return value is a 
+ * 16bit integer instead of an aim_tlv_t. 
+ *
+ */
+faim_internal fu16_t aim_gettlv16(aim_tlvlist_t *list, const fu16_t t, const int n)
+{
+	aim_tlv_t *tlv;
+
+	if (!(tlv = aim_gettlv(list, t, n)))
+		return 0; /* erm */
+	return aimutil_get16(tlv->value);
+}
+
+/**
+ * aim_gettlv32 - Retrieve the Nth TLV in chain as a 32bit integer.
+ * @list: Source TLV chain
+ * @type: TLV type to search for
+ * @nth: Index of TLV to return
+ *
+ * Same as aim_gettlv(), except that the return value is a 
+ * 32bit integer instead of an aim_tlv_t. 
+ *
+ */
+faim_internal fu32_t aim_gettlv32(aim_tlvlist_t *list, const fu16_t t, const int n)
+{
+	aim_tlv_t *tlv;
+
+	if (!(tlv = aim_gettlv(list, t, n)))
+		return 0; /* erm */
+	return aimutil_get32(tlv->value);
+}
+
+#if 0
+/**
+ * aim_puttlv_8 - Write a one-byte TLV.
+ * @buf: Destination buffer
+ * @t: TLV type
+ * @v: Value
+ *
+ * Writes a TLV with a one-byte integer value portion.
+ *
+ */
+faim_export int aim_puttlv_8(fu8_t *buf, const fu16_t t, const fu8_t v)
+{
+	fu8_t v8[1];
+
+	aimutil_put8(v8, v);
+
+	return aim_puttlv_raw(buf, t, 1, v8);
+}
+
+/**
+ * aim_puttlv_16 - Write a two-byte TLV.
+ * @buf: Destination buffer
+ * @t: TLV type
+ * @v: Value
+ *
+ * Writes a TLV with a two-byte integer value portion.
+ *
+ */
+faim_export int aim_puttlv_16(fu8_t *buf, const fu16_t t, const fu16_t v)
+{
+	fu8_t v16[2];
+
+	aimutil_put16(v16, v);
+
+	return aim_puttlv_raw(buf, t, 2, v16);
+}
+
+
+/**
+ * aim_puttlv_32 - Write a four-byte TLV.
+ * @buf: Destination buffer
+ * @t: TLV type
+ * @v: Value
+ *
+ * Writes a TLV with a four-byte integer value portion.
+ *
+ */
+faim_export int aim_puttlv_32(fu8_t *buf, const fu16_t t, const fu32_t v)
+{
+	fu8_t v32[4];
+
+	aimutil_put32(v32, v);
+
+	return aim_puttlv_raw(buf, t, 4, v32);
+}
+
+/**
+ * aim_puttlv_raw - Write a raw TLV.
+ * @buf: Destination buffer
+ * @t: TLV type
+ * @l: Length of string
+ * @v: String to write
+ *
+ * Writes a TLV with a raw value portion.  (Only the first @l
+ * bytes of the passed buffer will be written, which should not
+ * include a terminating NULL.)
+ *
+ */
+faim_export int aim_puttlv_raw(fu8_t *buf, const fu16_t t, const fu16_t l, const fu8_t *v)
+{
+	int i;
+
+	i = aimutil_put16(buf, t);
+	i += aimutil_put16(buf+i, l);
+	if (l)
+		memcpy(buf+i, v, l);
+	i += l;
+
+	return i;
+}
+#endif
+
Index: libfaim/translate.c
===================================================================
--- libfaim/translate.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/translate.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,27 @@
+/*
+ * I have no idea why this group was issued.  I have never seen anything
+ * that uses it.  From what I remember, the last time I tried to poke at
+ * the server with this group, it whined about not supporting it.
+ *
+ * But we advertise it anyway, because its fun.
+ * 
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+faim_internal int translate_modfirst(aim_session_t *sess, aim_module_t *mod)
+{
+
+	mod->family = 0x000c;
+	mod->version = 0x0001;
+	mod->toolid = 0x0104;
+	mod->toolversion = 0x0001;
+	mod->flags = 0;
+	strncpy(mod->name, "translate", sizeof(mod->name));
+	mod->snachandler = NULL;
+
+	return 0;
+}
+
+
Index: libfaim/txqueue.c
===================================================================
--- libfaim/txqueue.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/txqueue.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,436 @@
+/*
+ *  aim_txqueue.c
+ *
+ * Herein lies all the mangement routines for the transmit (Tx) queue.
+ *
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+
+#ifndef _WIN32
+#include <sys/socket.h>
+#endif
+
+/*
+ * Allocate a new tx frame.
+ *
+ * This is more for looks than anything else.
+ *
+ * Right now, that is.  If/when we implement a pool of transmit
+ * frames, this will become the request-an-unused-frame part.
+ *
+ * framing = AIM_FRAMETYPE_OFT/FLAP
+ * chan = channel for FLAP, hdrtype for OFT
+ *
+ */
+faim_internal aim_frame_t *aim_tx_new(aim_session_t *sess, aim_conn_t *conn, fu8_t framing, fu8_t chan, int datalen)
+{
+	aim_frame_t *fr;
+
+	if (!conn) {
+		faimdprintf(sess, 0, "aim_tx_new: ERROR: no connection specified\n");
+		return NULL;
+	}
+
+	/* For sanity... */
+	if ((conn->type == AIM_CONN_TYPE_RENDEZVOUS) || 
+			(conn->type == AIM_CONN_TYPE_RENDEZVOUS_OUT)) {
+		if (framing != AIM_FRAMETYPE_OFT) {
+			faimdprintf(sess, 0, "aim_tx_new: attempted to allocate inappropriate frame type for rendezvous connection\n");
+			return NULL;
+		}
+	} else {
+		if (framing != AIM_FRAMETYPE_FLAP) {
+			faimdprintf(sess, 0, "aim_tx_new: attempted to allocate inappropriate frame type for FLAP connection\n");
+			return NULL;
+		}
+	}
+
+	if (!(fr = (aim_frame_t *)malloc(sizeof(aim_frame_t))))
+		return NULL;
+	memset(fr, 0, sizeof(aim_frame_t));
+
+	fr->conn = conn; 
+
+	fr->hdrtype = framing;
+
+	if (fr->hdrtype == AIM_FRAMETYPE_FLAP) {
+
+		fr->hdr.flap.type = chan;
+
+	} else if (fr->hdrtype == AIM_FRAMETYPE_OFT) {
+
+		fr->hdr.oft.type = chan;
+		fr->hdr.oft.hdr2len = 0; /* this will get setup by caller */
+
+	} else 
+		faimdprintf(sess, 0, "tx_new: unknown framing\n");
+
+	if (datalen > 0) {
+		fu8_t *data;
+
+		if (!(data = (unsigned char *)malloc(datalen))) {
+			aim_frame_destroy(fr);
+			return NULL;
+		}
+
+		aim_bstream_init(&fr->data, data, datalen);
+	}
+
+	return fr;
+}
+
+/*
+ * aim_tx_enqeue__queuebased()
+ *
+ * The overall purpose here is to enqueue the passed in command struct
+ * into the outgoing (tx) queue.  Basically...
+ *   1) Make a scope-irrelevent copy of the struct
+ *   3) Mark as not-sent-yet
+ *   4) Enqueue the struct into the list
+ *   6) Return
+ *
+ * Note that this is only used when doing queue-based transmitting;
+ * that is, when sess->tx_enqueue is set to &aim_tx_enqueue__queuebased.
+ *
+ */
+static int aim_tx_enqueue__queuebased(aim_session_t *sess, aim_frame_t *fr)
+{
+
+	if (!fr->conn) {
+		faimdprintf(sess, 1, "aim_tx_enqueue: WARNING: enqueueing packet with no connecetion\n");
+		fr->conn = aim_getconn_type(sess, AIM_CONN_TYPE_BOS);
+	}
+
+	if (fr->hdrtype == AIM_FRAMETYPE_FLAP) {
+		/* assign seqnum -- XXX should really not assign until hardxmit */
+		fr->hdr.flap.seqnum = aim_get_next_txseqnum(fr->conn);
+	}
+
+	fr->handled = 0; /* not sent yet */
+
+	/* see overhead note in aim_rxqueue counterpart */
+	if (!sess->queue_outgoing)
+		sess->queue_outgoing = fr;
+	else {
+		aim_frame_t *cur;
+
+		for (cur = sess->queue_outgoing; cur->next; cur = cur->next)
+			;
+		cur->next = fr;
+	}
+
+	return 0;
+}
+
+/*
+ * aim_tx_enqueue__immediate()
+ *
+ * Parallel to aim_tx_enqueue__queuebased, however, this bypasses
+ * the whole queue mess when you want immediate writes to happen.
+ *
+ * Basically the same as its __queuebased couterpart, however
+ * instead of doing a list append, it just calls aim_tx_sendframe()
+ * right here. 
+ * 
+ */
+static int aim_tx_enqueue__immediate(aim_session_t *sess, aim_frame_t *fr)
+{
+
+	if (!fr->conn) {
+		faimdprintf(sess, 1, "aim_tx_enqueue: ERROR: packet has no connection\n");
+		aim_frame_destroy(fr);
+		return 0;
+	}
+
+	if (fr->hdrtype == AIM_FRAMETYPE_FLAP)
+		fr->hdr.flap.seqnum = aim_get_next_txseqnum(fr->conn);
+
+	fr->handled = 0; /* not sent yet */
+
+	aim_tx_sendframe(sess, fr);
+
+	aim_frame_destroy(fr);
+
+	return 0;
+}
+
+faim_export int aim_tx_setenqueue(aim_session_t *sess, int what, int (*func)(aim_session_t *, aim_frame_t *))
+{
+	
+	if (what == AIM_TX_QUEUED)
+		sess->tx_enqueue = &aim_tx_enqueue__queuebased;
+	else if (what == AIM_TX_IMMEDIATE) 
+		sess->tx_enqueue = &aim_tx_enqueue__immediate;
+	else if (what == AIM_TX_USER) {
+		if (!func)
+			return -EINVAL;
+		sess->tx_enqueue = func;
+	} else
+		return -EINVAL; /* unknown action */
+
+	return 0;
+}
+
+faim_internal int aim_tx_enqueue(aim_session_t *sess, aim_frame_t *fr)
+{
+	
+	/*
+	 * If we want to send a connection thats inprogress, we have to force
+	 * them to use the queue based version. Otherwise, use whatever they
+	 * want.
+	 */
+	if (fr && fr->conn && 
+			(fr->conn->status & AIM_CONN_STATUS_INPROGRESS)) {
+		return aim_tx_enqueue__queuebased(sess, fr);
+	}
+
+	return (*sess->tx_enqueue)(sess, fr);
+}
+
+/* 
+ *  aim_get_next_txseqnum()
+ *
+ *   This increments the tx command count, and returns the seqnum
+ *   that should be stamped on the next FLAP packet sent.  This is
+ *   normally called during the final step of packet preparation
+ *   before enqueuement (in aim_tx_enqueue()).
+ *
+ */
+faim_internal flap_seqnum_t aim_get_next_txseqnum(aim_conn_t *conn)
+{
+	flap_seqnum_t ret;
+	
+	ret = ++conn->seqnum;
+
+	return ret;
+}
+
+static int aim_send(int fd, const void *buf, size_t count)
+{
+	int left, cur;
+
+	for (cur = 0, left = count; left; ) {
+		int ret;
+
+		ret = send(fd, ((unsigned char *)buf)+cur, left, 0);
+		if (ret == -1)
+			return -1;
+		else if (ret == 0)
+			return cur;
+
+		cur += ret;
+		left -= ret;
+	}
+
+	return cur;
+}
+
+static int aim_bstream_send(aim_bstream_t *bs, aim_conn_t *conn, size_t count)
+{
+	int wrote = 0;
+
+	if (!bs || !conn || (count < 0))
+		return -EINVAL;
+
+	if (count > aim_bstream_empty(bs))
+		count = aim_bstream_empty(bs); /* truncate to remaining space */
+
+	if (count)
+		wrote = aim_send(conn->fd, bs->data + bs->offset, count);
+
+	if (((aim_session_t *)conn->sessv)->debug >= 2) {
+		int i;
+		aim_session_t *sess = (aim_session_t *)conn->sessv;
+
+		faimdprintf(sess, 2, "\nOutgoing data: (%d bytes)", wrote);
+		for (i = 0; i < wrote; i++) {
+			if (!(i % 8)) 
+				faimdprintf(sess, 2, "\n\t");
+			faimdprintf(sess, 2, "0x%02x ", *(bs->data + bs->offset + i));
+		}
+		faimdprintf(sess, 2, "\n");
+	}
+
+
+	bs->offset += wrote;
+
+	return wrote;	
+}
+
+static int sendframe_flap(aim_session_t *sess, aim_frame_t *fr)
+{
+	aim_bstream_t obs;
+	fu8_t *obs_raw;
+	int payloadlen, err = 0, obslen;
+
+	payloadlen = aim_bstream_curpos(&fr->data);
+
+	if (!(obs_raw = malloc(6 + payloadlen)))
+		return -ENOMEM;
+
+	aim_bstream_init(&obs, obs_raw, 6 + payloadlen);
+
+	/* FLAP header */
+	aimbs_put8(&obs, 0x2a);
+	aimbs_put8(&obs, fr->hdr.flap.type);
+	aimbs_put16(&obs, fr->hdr.flap.seqnum);
+	aimbs_put16(&obs, payloadlen);
+
+	/* payload */
+	aim_bstream_rewind(&fr->data);
+	aimbs_putbs(&obs, &fr->data, payloadlen);
+
+	obslen = aim_bstream_curpos(&obs);
+	aim_bstream_rewind(&obs);
+
+	if (aim_bstream_send(&obs, fr->conn, obslen) != obslen)
+		err = -errno;
+	
+	free(obs_raw); /* XXX aim_bstream_free */
+
+	fr->handled = 1;
+	fr->conn->lastactivity = time(NULL);
+
+	return err;
+}
+
+static int sendframe_oft(aim_session_t *sess, aim_frame_t *fr)
+{
+	aim_bstream_t hbs;
+	fu8_t *hbs_raw;
+	int hbslen;
+	int err = 0;
+
+	hbslen = 8 + fr->hdr.oft.hdr2len;
+
+	if (!(hbs_raw = malloc(hbslen)))
+		return -1;
+
+	aim_bstream_init(&hbs, hbs_raw, hbslen);
+
+	aimbs_putraw(&hbs, fr->hdr.oft.magic, 4);
+	aimbs_put16(&hbs, fr->hdr.oft.hdr2len + 8);
+	aimbs_put16(&hbs, fr->hdr.oft.type);
+	aimbs_putraw(&hbs, fr->hdr.oft.hdr2, fr->hdr.oft.hdr2len);
+
+	aim_bstream_rewind(&hbs);
+	
+	if (aim_bstream_send(&hbs, fr->conn, hbslen) != hbslen) {
+
+		err = -errno;
+		
+	} else if (aim_bstream_curpos(&fr->data)) {
+		int len;
+
+		len = aim_bstream_curpos(&fr->data);
+		aim_bstream_rewind(&fr->data);
+		
+		if (aim_bstream_send(&fr->data, fr->conn, len) != len)
+			err = -errno;
+	}
+
+	free(hbs_raw); /* XXX aim_bstream_free */
+
+	fr->handled = 1;
+	fr->conn->lastactivity = time(NULL);
+
+
+	return err;
+}
+
+faim_internal int aim_tx_sendframe(aim_session_t *sess, aim_frame_t *fr)
+{
+	if (fr->hdrtype == AIM_FRAMETYPE_FLAP)
+		return sendframe_flap(sess, fr);
+	else if (fr->hdrtype == AIM_FRAMETYPE_OFT)
+		return sendframe_oft(sess, fr);
+	return -1;
+}
+
+faim_export int aim_tx_flushqueue(aim_session_t *sess)
+{
+	aim_frame_t *cur;
+
+	for (cur = sess->queue_outgoing; cur; cur = cur->next) {
+
+		if (cur->handled)
+	       		continue; /* already been sent */
+
+		if (cur->conn && (cur->conn->status & AIM_CONN_STATUS_INPROGRESS))
+			continue;
+
+		/*
+		 * And now for the meager attempt to force transmit
+		 * latency and avoid missed messages.
+		 */
+		if ((cur->conn->lastactivity + cur->conn->forcedlatency) >= time(NULL)) {
+			/* 
+			 * XXX should be a break! we dont want to block the 
+			 * upper layers
+			 *
+			 * XXX or better, just do this right.
+			 *
+			 */
+			sleep((cur->conn->lastactivity + cur->conn->forcedlatency) - time(NULL));
+		}
+
+		/* XXX this should call the custom "queuing" function!! */
+		aim_tx_sendframe(sess, cur);
+	}
+
+	/* purge sent commands from queue */
+	aim_tx_purgequeue(sess);
+
+	return 0;
+}
+
+/*
+ *  aim_tx_purgequeue()
+ *  
+ *  This is responsable for removing sent commands from the transmit 
+ *  queue. This is not a required operation, but it of course helps
+ *  reduce memory footprint at run time!  
+ *
+ */
+faim_export void aim_tx_purgequeue(aim_session_t *sess)
+{
+	aim_frame_t *cur, **prev;
+
+	for (prev = &sess->queue_outgoing; (cur = *prev); ) {
+
+		if (cur->handled) {
+			*prev = cur->next;
+
+			aim_frame_destroy(cur);
+
+		} else
+			prev = &cur->next;
+	}
+
+	return;
+}
+
+/**
+ * aim_tx_cleanqueue - get rid of packets waiting for tx on a dying conn
+ * @sess: session
+ * @conn: connection that's dying
+ *
+ * for now this simply marks all packets as sent and lets them
+ * disappear without warning.
+ *
+ */
+faim_export void aim_tx_cleanqueue(aim_session_t *sess, aim_conn_t *conn)
+{
+	aim_frame_t *cur;
+
+	for (cur = sess->queue_outgoing; cur; cur = cur->next) {
+		if (cur->conn == conn)
+			cur->handled = 1;
+	}
+
+	return;
+}
+
+
Index: libfaim/util.c
===================================================================
--- libfaim/util.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
+++ libfaim/util.c	(revision 5e53c4a15eca8b264df0aedf726d4545bc5b4b46)
@@ -0,0 +1,208 @@
+/*
+ *
+ *
+ *
+ */
+
+#define FAIM_INTERNAL
+#include <aim.h>
+#include <ctype.h>
+
+faim_export faim_shortfunc int aimutil_putstr(u_char *dest, const char *src, int len)
+{
+	memcpy(dest, src, len);
+	return len;
+}
+
+/*
+ * Tokenizing functions.  Used to portably replace strtok/sep.
+ *   -- DMP.
+ *
+ */
+faim_export int aimutil_tokslen(char *toSearch, int index, char dl)
+{
+	int curCount = 1;
+	char *next;
+	char *last;
+	int toReturn;
+
+	last = toSearch;
+	next = strchr(toSearch, dl);
+
+	while(curCount < index && next != NULL) {
+		curCount++;
+		last = next + 1;
+		next = strchr(last, dl);
+	}
+
+	if ((curCount < index) || (next == NULL))
+		toReturn = strlen(toSearch) - (curCount - 1);
+	else
+		toReturn = next - toSearch - (curCount - 1);
+
+	return toReturn;
+}
+
+faim_export int aimutil_itemcnt(char *toSearch, char dl)
+{
+	int curCount;
+	char *next;
+
+	curCount = 1;
+
+	next = strchr(toSearch, dl);
+
+	while(next != NULL) {
+		curCount++;
+		next = strchr(next + 1, dl);
+	}
+
+	return curCount;
+}
+
+faim_export char *aimutil_itemidx(char *toSearch, int index, char dl)
+{
+	int curCount;
+	char *next;
+	char *last;
+	char *toReturn;
+
+	curCount = 0;
+
+	last = toSearch;
+	next = strchr(toSearch, dl);
+
+	while (curCount < index && next != NULL) {
+		curCount++;
+		last = next + 1;
+		next = strchr(last, dl);
+	}
+
+	if (curCount < index) {
+		toReturn = malloc(sizeof(char));
+		*toReturn = '\0';
+	}
+	next = strchr(last, dl);
+
+	if (curCount < index) {
+		toReturn = malloc(sizeof(char));
+		*toReturn = '\0';
+	} else {
+		if (next == NULL) {
+			toReturn = malloc((strlen(last) + 1) * sizeof(char));
+			strcpy(toReturn, last);
+		} else {
+			toReturn = malloc((next - last + 1) * sizeof(char));
+			memcpy(toReturn, last, (next - last));
+			toReturn[next - last] = '\0';
+		}
+	}
+	return toReturn;
+}
+
+/*
+* int snlen(const char *)
+* 
+* This takes a screen name and returns its length without
+* spaces.  If there are no spaces in the SN, then the 
+* return is equal to that of strlen().
+*
+*/
+faim_export int aim_snlen(const char *sn)
+{
+	int i = 0;
+	const char *curPtr = NULL;
+
+	if (!sn)
+		return 0;
+
+	curPtr = sn;
+	while ( (*curPtr) != (char) NULL) {
+		if ((*curPtr) != ' ')
+		i++;
+		curPtr++;
+	}
+
+	return i;
+}
+
+/*
+* int sncmp(const char *, const char *)
+*
+* This takes two screen names and compares them using the rules
+* on screen names for AIM/AOL.  Mainly, this means case and space
+* insensitivity (all case differences and spacing differences are
+* ignored).
+*
+* Return: 0 if equal
+*     non-0 if different
+*
+*/
+
+faim_export int aim_sncmp(const char *sn1, const char *sn2)
+{
+	const char *curPtr1 = NULL, *curPtr2 = NULL;
+
+	if (aim_snlen(sn1) != aim_snlen(sn2))
+		return 1;
+
+	curPtr1 = sn1;
+	curPtr2 = sn2;
+	while ( (*curPtr1 != (char) NULL) && (*curPtr2 != (char) NULL) ) {
+		if ( (*curPtr1 == ' ') || (*curPtr2 == ' ') ) {
+			if (*curPtr1 == ' ')
+				curPtr1++;
+			if (*curPtr2 == ' ')
+				curPtr2++;
+		} else {
+			if ( toupper(*curPtr1) != toupper(*curPtr2))
+				return 1;
+			curPtr1++;
+			curPtr2++;
+		}
+	}
+
+	/* Should both be NULL */
+	if (*curPtr1 != *curPtr2)
+		return 1;
+
+	return 0;
+}
+
+/* strsep Copyright (C) 1992, 1993 Free Software Foundation, Inc.
+strsep is part of the GNU C Library.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+The GNU C Library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with the GNU C Library; see the file COPYING.LIB.  If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA.  */
+
+/* Minor changes by and1000 on 15/1/97 to make it go under Nemesis */
+
+faim_export char *aim_strsep(char **pp, const char *delim)
+{
+	char *p, *q;
+
+	if (!(p = *pp))
+		return 0;
+
+	if ((q = strpbrk (p, delim))) {
+		*pp = q + 1;
+		*q = '\0';
+	} else
+		*pp = 0;
+
+	return p;
+}
+
+
