changeset 981:7e231bc0018a

[gaim-migrate @ 991] I think I need a Pepsi. committer: Tailor Script <tailor@pidgin.im>
author Eric Warmenhoven <eric@warmenhoven.org>
date Fri, 13 Oct 2000 07:24:40 +0000
parents 82c5865f7cfe
children 09e5065fe22c
files PRPL configure.in plugins/Makefile.am plugins/oscar.c sounds/Makefile.am src/Makefile.am src/aim.c src/dialogs.c src/multi.c src/multi.h src/oscar.c src/plugins.c src/prpl.c src/prpl.h src/server.c src/toc.c
diffstat 16 files changed, 2254 insertions(+), 341 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PRPL	Fri Oct 13 07:24:40 2000 +0000
@@ -0,0 +1,70 @@
+Protocol Plugins. What EveryBuddy should have been.
+
+Protocol Plugins are easier than GUI plugins. This is because there is necessarily a limited set
+of server functions; protocols are limited. What a programmer can do with a UI is limitless.
+
+In order to design a framework for which Protocol Plugins can work, we must first think about
+which protocols can be pluginized. (Henceforth PRPL will stand for both the Protocol Plugin
+system and an individual Protocol Plugin.)
+
+Oscar. TOC. Yahoo. MSN. ICQ. FTP. IRC.
+
+There is a longer list. Note that FTP is in the list presented. FTP has basic functions similar
+to those of the others. The basic functions that must be performed are:
+
+Sign on. (log in)
+Sign off.
+Send and receive messages.
+
+There really isn't much more to it than that. There are, then, additional functions that some
+protocols implement:
+
+Chat.
+File Transfer.
+Away/Idle. (states)
+Directory Info.
+
+Before PRPL there was a slight abstraction in the code. The UI simply needed to call serv_login,
+and that function would decide between the protocols, which function to call. It did that with
+an if-elseif-else series.
+
+With PRPL, each PRPL is stored in a struct. The structs are then kept in a GSList. The serv_
+functions then simply need to search the GSList for the correct protocol, and call the desired
+function. The struct holds void * references to the desired functions. serv_* simply needs to
+call that function. (Even better, each connection has a reference to the PRPL struct it uses;
+this will reduce search time.)
+
+A new PRPL can be loaded at any time. They can be either static or dynamic code. When a new
+protocol is loaded, gaim will call its protocol_init function, which is expected to return a
+pointer to the aforementioned struct. If it returns NULL, the PRPL will not be loaded. (This is
+not entirely true and needs to be modified in the code to work similarly to how it's decscribed
+here.)
+
+Each PRPL needs to have a unique identifier. In the pre-PRPL system TOC was 0 and Oscar was 1.
+This identifier can be found in prpl.h. They are pre-assigned. PROTO_TOC is still 0, PROTO_OSCAR
+is still 1. The protocol_init function is expected to set the struct's protocol member to the
+appropriate value. If you want to write a new PRPL for gaim, please email one of the maintainers
+with the name of the protocol. We'll then reserve a number for it. Please do not use a number
+that has not been assigned to your protocol, not even for testing purposes.
+
+The addition of PRPL to gaim means that gaim now supports multiple connections and multiple (and
+dynamically loadable) protocols.
+
+======
+
+In order to test that PRPL was working with as little work as possible I made Oscar a plugin. In
+order to use Oscar as a plugin, first recompile gaim with the make variable DEBUG_CFLAGS set to
+-DDYNAMIC_OSCAR, e.g.
+
+/usr/src/gaim $ make DEBUG_CFLAGS=-DDYNAMIC_OSCAR
+
+This will then remove Oscar support from the code (though it will still link with libfaim. However,
+gaim does not need to link to libfaim itself). Making the Oscar plugin is straight-forward; simply
+make oscar.so in the plugins directory, e.g.
+
+/usr/src/gaim/plugins $ make oscar.so
+
+You will then be presented with a gaim binary in src/gaim and the Oscar plugin in plugins/oscar.so.
+Simply load the oscar.so file from the normal plugin window. This will set up everything necessary.
+You may unload the plugin at any time, but the protocol will still be loaded. This may or may not
+be a problem and may or may not be fixed at a later point in time.
--- a/configure.in	Thu Oct 12 18:59:36 2000 +0000
+++ b/configure.in	Fri Oct 13 07:24:40 2000 +0000
@@ -100,8 +100,15 @@
 
 CFLAGS="$CFLAGS $GTK_CFLAGS"
 
+dnl Even more X-Chat code
 if test "$enable_plugins" = yes ; then
-	AC_DEFINE(GAIM_PLUGINS)
+	AC_CHECK_FUNCS(dlopen, have_dl=yes)
+	if test "$have_dl" = yes; then
+		AC_CHECK_FUNCS(dlerror)
+		AC_DEFINE(GAIM_PLUGINS)
+	else
+		enable_plugins=no
+	fi
 fi
 
 dnl This was taken straight from X-Chat.
@@ -145,7 +152,7 @@
 		AC_DEFINE(ESD_SOUND)
                 LDADD="$LDADD $ESD_LIBS"
 	else
-		enable_esd = no
+		enable_esd=no
         fi
 fi
 
@@ -158,7 +165,6 @@
 AC_SUBST(CFLAGS)
 AC_SUBST(LDADD)
 AC_SUBST(LIBS)
-AC_SUBST(LIBFAIM_DO)
 
 AC_OUTPUT([Makefile
 	   src/Makefile
--- a/plugins/Makefile.am	Thu Oct 12 18:59:36 2000 +0000
+++ b/plugins/Makefile.am	Fri Oct 13 07:24:40 2000 +0000
@@ -1,10 +1,8 @@
-LDFLAGS += $(LIBS) -ggdb -shared
+LDFLAGS += $(LDADD) $(LIBS) -ggdb -shared
 SUFFIXES = .c .so
 .c.so:
 	$(CC) $(CFLAGS) -I../src -DVERSION=\"$(VERSION)\" -fPIC -DPIC -o $@ $< $(LDFLAGS) $(PLUGIN_LIBS)
 
-
-
 #if PLUGINS
 #plugin_DATA = autorecon.so iconaway.so notify.so spellchk.so lagmeter.so
 #plugindir = $(libdir)/gaim
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/oscar.c	Fri Oct 13 07:24:40 2000 +0000
@@ -0,0 +1,1530 @@
+/*
+ * gaim
+ *
+ * Some code copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
+ * libfaim code copyright 1998, 1999 Adam Fritzler <afritz@auk.cx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "../config.h"
+#endif
+
+
+#include <netdb.h>
+#include <gtk/gtk.h>
+#include <unistd.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include "multi.h"
+#include "prpl.h"
+#include "gaim.h"
+#include "aim.h"
+#include "gnome_applet_mgr.h"
+
+#include "pixmaps/cancel.xpm"
+#include "pixmaps/ok.xpm"
+
+int gaim_caps = AIM_CAPS_CHAT | AIM_CAPS_SENDFILE | AIM_CAPS_GETFILE |
+		AIM_CAPS_VOICE | AIM_CAPS_IMIMAGE | AIM_CAPS_BUDDYICON;
+int keepalv = -1;
+
+struct chat_connection *find_oscar_chat(struct gaim_connection *gc, char *name) {
+	GSList *g = gc->oscar_chats;
+	struct chat_connection *c = NULL;
+	if (gc->protocol != PROTO_OSCAR) return NULL;
+
+	while (g) {
+		c = (struct chat_connection *)g->data;
+		if (!strcmp(name, c->name))
+			break;
+		g = g->next;
+		c = NULL;
+	}
+
+	return c;
+}
+
+static struct chat_connection *find_oscar_chat_by_conn(struct gaim_connection *gc,
+							struct aim_conn_t *conn) {
+	GSList *g = gc->oscar_chats;
+	struct chat_connection *c = NULL;
+
+	while (g) {
+		c = (struct chat_connection *)g->data;
+		if (c->conn == conn)
+			break;
+		g = g->next;
+		c = NULL;
+	}
+
+	return c;
+}
+
+static struct gaim_connection *find_gaim_conn_by_aim_sess(struct aim_session_t *sess) {
+	GSList *g = connections;
+	struct gaim_connection *gc = NULL;
+
+	while (g) {
+		gc = (struct gaim_connection *)g->data;
+		if (sess == gc->oscar_sess)
+			break;
+		g = g->next;
+		gc = NULL;
+	}
+
+	return gc;
+}
+
+static struct gaim_connection *find_gaim_conn_by_oscar_conn(struct aim_conn_t *conn) {
+	GSList *g = connections;
+	struct gaim_connection *c = NULL;
+	struct aim_conn_t *s;
+	while (g) {
+		c = (struct gaim_connection *)g->data;
+		if (c->protocol != PROTO_OSCAR) {
+			c = NULL;
+			g = g->next;
+			continue;
+		}
+		s = c->oscar_sess->connlist;
+		while (s) {
+			if (conn == s)
+				break;
+			s = s->next;
+		}
+		if (s) break;
+		g = g->next;
+		c = NULL;
+	}
+
+	return c;
+}
+
+static int gaim_parse_auth_resp  (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_parse_login      (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_server_ready     (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_handle_redirect  (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_parse_oncoming   (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_parse_offgoing   (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_parse_incoming_im(struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_parse_misses     (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_parse_user_info  (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_parse_motd       (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_chatnav_info     (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_chat_join        (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_chat_leave       (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_chat_info_update (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_chat_incoming_msg(struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_parse_msgack     (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_parse_ratechange (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_parse_evilnotify (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_bosrights        (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_rateresp         (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_reportinterval   (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_parse_msgerr     (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_parse_buddyrights(struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_parse_locerr     (struct aim_session_t *, struct command_rx_struct *, ...);
+
+static int gaim_directim_incoming(struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_directim_typing  (struct aim_session_t *, struct command_rx_struct *, ...);
+static int gaim_directim_initiate(struct aim_session_t *, struct command_rx_struct *, ...);
+
+static char *msgerrreason[] = {
+	"Invalid error",
+	"Invalid SNAC",
+	"Rate to host",
+	"Rate to client",
+	"Not logged in",
+	"Service unavailable",
+	"Service not defined",
+	"Obsolete SNAC",
+	"Not supported by host",
+	"Not supported by client",
+	"Refused by client",
+	"Reply too big",
+	"Responses lost",
+	"Request denied",
+	"Busted SNAC payload",
+	"Insufficient rights",
+	"In local permit/deny",
+	"Too evil (sender)",
+	"Too evil (receiver)",
+	"User temporarily unavailable",
+	"No match",
+	"List overflow",
+	"Request ambiguous",
+	"Queue full",
+	"Not while on AOL"
+};
+static int msgerrreasonlen = 25;
+
+static void oscar_callback(gpointer data, gint source,
+				GdkInputCondition condition) {
+	struct aim_conn_t *conn = (struct aim_conn_t *)data;
+	struct gaim_connection *gc = find_gaim_conn_by_oscar_conn(conn);
+	if (!gc) {
+		/* oh boy. this is probably bad. i guess the only thing we can really do
+		 * is return? */
+		debug_print("oscar callback for closed connection.\n");
+		return;
+	}
+
+	if (condition & GDK_INPUT_EXCEPTION) {
+		hide_login_progress(gc->username, _("Disconnected."));
+		signoff(gc);
+		return;
+	}
+	if (condition & GDK_INPUT_READ) {
+		if (conn->type == AIM_CONN_TYPE_RENDEZVOUS_OUT) {
+			debug_print("got information on rendezvous\n");
+			if (aim_handlerendconnect(gc->oscar_sess, conn) < 0) {
+				debug_print(_("connection error (rend)\n"));
+			}
+		} else {
+			if (aim_get_command(gc->oscar_sess, conn) >= 0) {
+				aim_rxdispatch(gc->oscar_sess);
+			} else {
+				if (conn->type == AIM_CONN_TYPE_RENDEZVOUS &&
+				    conn->subtype == AIM_CONN_SUBTYPE_OFT_DIRECTIM) {
+					struct conversation *cnv =
+						find_conversation(((struct aim_directim_priv *)conn->priv)->sn);
+					debug_print("connection error for directim\n");
+					if (cnv) {
+						make_direct(cnv, FALSE, NULL, 0);
+					}
+					aim_conn_kill(gc->oscar_sess, &conn);
+				} else if ((conn->type == AIM_CONN_TYPE_BOS) ||
+					   !(aim_getconn_type(gc->oscar_sess, AIM_CONN_TYPE_BOS))) {
+					debug_print(_("major connection error\n"));
+					hide_login_progress(gc->username, _("Disconnected."));
+					signoff(gc);
+				} else if (conn->type == AIM_CONN_TYPE_CHAT) {
+					struct chat_connection *c = find_oscar_chat_by_conn(gc, conn);
+					char buf[BUF_LONG];
+					sprintf(debug_buff, "disconnected from chat room %s\n", c->name);
+					debug_print(debug_buff);
+					c->conn = NULL;
+					if (c->inpa > -1)
+						gdk_input_remove(c->inpa);
+					c->inpa = -1;
+					c->fd = -1;
+					aim_conn_kill(gc->oscar_sess, &conn);
+					sprintf(buf, _("You have been disconnected from chat room %s."), c->name);
+					do_error_dialog(buf, _("Chat Error!"));
+				} else if (conn->type == AIM_CONN_TYPE_CHATNAV) {
+					if (gc->cnpa > -1)
+						gdk_input_remove(gc->cnpa);
+					gc->cnpa = -1;
+					debug_print("removing chatnav input watcher\n");
+					aim_conn_kill(gc->oscar_sess, &conn);
+				} else {
+					sprintf(debug_buff, "holy crap! generic connection error! %d\n",
+							conn->type);
+					debug_print(debug_buff);
+					aim_conn_kill(gc->oscar_sess, &conn);
+				}
+			}
+		}
+	}
+}
+
+void oscar_login(struct aim_user *user) {
+	struct aim_session_t *sess;
+	struct aim_conn_t *conn;
+	char buf[256];
+	struct gaim_connection *gc;
+
+	sprintf(debug_buff, _("Logging in %s\n"), user->username);
+	debug_print(debug_buff);
+
+	gc = new_gaim_conn(PROTO_OSCAR, user->username, user->password);
+	sess = g_new0(struct aim_session_t, 1);
+	aim_session_init(sess);
+	/* we need an immediate queue because we don't use a while-loop to
+	 * see if things need to be sent. */
+	sess->tx_enqueue = &aim_tx_enqueue__immediate;
+	gc->oscar_sess = sess;
+
+	sprintf(buf, _("Looking up %s"), FAIM_LOGIN_SERVER);
+	conn = aim_newconn(sess, AIM_CONN_TYPE_AUTH, FAIM_LOGIN_SERVER);
+
+	if (conn == NULL) {
+		debug_print(_("internal connection error\n"));
+		hide_login_progress(gc->username, _("Unable to login to AIM"));
+		destroy_gaim_conn(gc);
+		return;
+	} else if (conn->fd == -1) {
+		if (conn->status & AIM_CONN_STATUS_RESOLVERR) {
+			sprintf(debug_buff, _("couldn't resolve host"));
+			debug_print(debug_buff); debug_print("\n");
+			hide_login_progress(gc->username, debug_buff);
+		} else if (conn->status & AIM_CONN_STATUS_CONNERR) {
+			sprintf(debug_buff, _("couldn't connect to host"));
+			debug_print(debug_buff); debug_print("\n");
+			hide_login_progress(gc->username, debug_buff);
+		}
+		destroy_gaim_conn(gc);
+		return;
+	}
+	g_snprintf(buf, sizeof(buf), _("Signon: %s"), gc->username);
+
+	aim_conn_addhandler(sess, conn, 0x0017, 0x0007, gaim_parse_login, 0);
+	aim_conn_addhandler(sess, conn, 0x0017, 0x0003, gaim_parse_auth_resp, 0);
+	aim_sendconnack(sess, conn);
+	aim_request_login(sess, conn, gc->username);
+
+	gc->inpa = gdk_input_add(conn->fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
+			oscar_callback, conn);
+
+	sprintf(gc->user_info, "%s", user->user_info);
+	gc->options = user->options;
+	save_prefs(); /* is this necessary anymore? */
+
+	debug_print(_("Password sent, waiting for response\n"));
+}
+
+void oscar_close(struct gaim_connection *gc) {
+	if (gc->protocol != PROTO_OSCAR) return;
+	if (gc->inpa > 0)
+		gdk_input_remove(gc->inpa);
+	gc->inpa = -1;
+	if (gc->cnpa > 0)
+		gdk_input_remove(gc->cnpa);
+	gc->cnpa = -1;
+	if (gc->paspa > 0)
+		gdk_input_remove(gc->paspa);
+	gc->paspa = -1;
+	aim_logoff(gc->oscar_sess);
+	g_free(gc->oscar_sess);
+	debug_print(_("Signed off.\n"));
+}
+
+int gaim_parse_auth_resp(struct aim_session_t *sess,
+			 struct command_rx_struct *command, ...) {
+	struct aim_conn_t *bosconn = NULL;
+	struct gaim_connection *gc = find_gaim_conn_by_aim_sess(sess);
+	sprintf(debug_buff, "inside auth_resp (Screen name: %s)\n",
+			sess->logininfo.screen_name);
+	debug_print(debug_buff);
+
+	if (sess->logininfo.errorcode) {
+		switch (sess->logininfo.errorcode) {
+		case 0x18:
+			/* connecting too frequently */
+			do_error_dialog(_("You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer."), _("Gaim - Error"));
+			plugin_event(event_error, (void *)983, 0, 0);
+			break;
+		case 0x05:
+			/* Incorrect nick/password */
+			do_error_dialog(_("Incorrect nickname or password."), _("Gaim - Error"));
+			plugin_event(event_error, (void *)980, 0, 0);
+			break;
+		case 0x1c:
+			/* client too old */
+			do_error_dialog(_("The client version you are using is too old. Please upgrade at http://www.marko.net/gaim/"), _("Gaim - Error"));
+			plugin_event(event_error, (void *)989, 0, 0);
+			break;
+		}
+		sprintf(debug_buff, "Login Error Code 0x%04x\n",
+				sess->logininfo.errorcode);
+		debug_print(debug_buff);
+		sprintf(debug_buff, "Error URL: %s\n",
+				sess->logininfo.errorurl);
+		debug_print(debug_buff);
+#ifdef USE_APPLET
+		set_user_state(offline);
+#endif
+		gdk_input_remove(gc->inpa);
+		hide_login_progress(gc->username, _("Authentication Failed"));
+		signoff(gc);
+		return 0;
+	}
+
+
+	if (sess->logininfo.email) {
+		sprintf(debug_buff, "Email: %s\n", sess->logininfo.email);
+		debug_print(debug_buff);
+	} else {
+		debug_print("Email is NULL\n");
+	}
+	sprintf(debug_buff, "Closing auth connection...\n");
+	debug_print(debug_buff);
+	gdk_input_remove(gc->inpa);
+	aim_conn_kill(sess, &command->conn);
+
+	bosconn = aim_newconn(sess, AIM_CONN_TYPE_BOS, sess->logininfo.BOSIP);
+	if (bosconn == NULL) {
+#ifdef USE_APPLET
+		set_user_state(offline);
+#endif
+		hide_login_progress(gc->username, _("Internal Error"));
+		destroy_gaim_conn(gc);
+		return -1;
+	} else if (bosconn->status != 0) {
+#ifdef USE_APPLET
+		set_user_state(offline);
+#endif
+		hide_login_progress(gc->username, _("Could Not Connect"));
+		destroy_gaim_conn(gc);
+		return -1;
+	}
+
+	aim_conn_addhandler(sess, bosconn, 0x0009, 0x0003, gaim_bosrights, 0);
+	aim_conn_addhandler(sess, bosconn, 0x0001, 0x0007, gaim_rateresp, 0); /* rate info */
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ACK, AIM_CB_ACK_ACK, NULL, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_SERVERREADY, gaim_server_ready, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_RATEINFO, NULL, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_REDIRECT, gaim_handle_redirect, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_STS, AIM_CB_STS_SETREPORTINTERVAL, gaim_reportinterval, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_RIGHTSINFO, gaim_parse_buddyrights, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_ONCOMING, gaim_parse_oncoming, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_OFFGOING, gaim_parse_offgoing, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_INCOMING, gaim_parse_incoming_im, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_ERROR, gaim_parse_locerr, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_MISSEDCALL, gaim_parse_misses, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_RATECHANGE, gaim_parse_ratechange, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_EVIL, gaim_parse_evilnotify, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_ERROR, gaim_parse_msgerr, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_USERINFO, gaim_parse_user_info, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_ACK, gaim_parse_msgack, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_CTN, AIM_CB_CTN_DEFAULT, aim_parse_unknown, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_DEFAULT, aim_parse_unknown, 0);
+	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_MOTD, gaim_parse_motd, 0);
+
+	aim_auth_sendcookie(sess, bosconn, sess->logininfo.cookie);
+	gc->oscar_conn = bosconn;
+	gc->inpa = gdk_input_add(bosconn->fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
+			oscar_callback, bosconn);
+	return 1;
+}
+
+int gaim_parse_login(struct aim_session_t *sess,
+		     struct command_rx_struct *command, ...) {
+	struct client_info_s info = {"AOL Instant Messenger (SM), version 4.1.2010/WIN32", 4, 30, 3141, "us", "en", 0x0004, 0x0001, 0x055};
+	char *key;
+	va_list ap;
+	struct gaim_connection *gc = find_gaim_conn_by_aim_sess(sess);
+
+	va_start(ap, command);
+	key = va_arg(ap, char *);
+	va_end(ap);
+
+	aim_send_login(sess, command->conn, gc->username, gc->password, &info, key);
+	return 1;
+}
+
+int gaim_server_ready(struct aim_session_t *sess,
+		      struct command_rx_struct *command, ...) {
+	static int id = 1;
+	struct gaim_connection *gc = find_gaim_conn_by_aim_sess(sess);
+	switch (command->conn->type) {
+	case AIM_CONN_TYPE_BOS:
+		aim_setversions(sess, command->conn);
+		aim_bos_reqrate(sess, command->conn); /* request rate info */
+		debug_print("done with BOS ServerReady\n");
+		break;
+	case AIM_CONN_TYPE_CHATNAV:
+		debug_print("chatnav: got server ready\n");
+		aim_conn_addhandler(sess, command->conn, AIM_CB_FAM_CTN, AIM_CB_CTN_INFO, gaim_chatnav_info, 0);
+		aim_bos_reqrate(sess, command->conn);
+		aim_bos_ackrateresp(sess, command->conn);
+		aim_chatnav_clientready(sess, command->conn);
+		aim_chatnav_reqrights(sess, command->conn);
+		break;
+	case AIM_CONN_TYPE_CHAT:
+		debug_print("chat: got server ready\n");
+		aim_conn_addhandler(sess, command->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_USERJOIN, gaim_chat_join, 0);
+		aim_conn_addhandler(sess, command->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_USERLEAVE, gaim_chat_leave, 0);
+		aim_conn_addhandler(sess, command->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_ROOMINFOUPDATE, gaim_chat_info_update, 0);
+		aim_conn_addhandler(sess, command->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_INCOMINGMSG, gaim_chat_incoming_msg, 0);
+		aim_bos_reqrate(sess, command->conn);
+		aim_bos_ackrateresp(sess, command->conn);
+		aim_chat_clientready(sess, command->conn);
+		serv_got_joined_chat(gc, id++, aim_chat_getname(command->conn));
+		break;
+	case AIM_CONN_TYPE_RENDEZVOUS:
+		aim_conn_addhandler(sess, command->conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMINCOMING, gaim_directim_incoming, 0);
+		break;
+	default: /* huh? */
+		sprintf(debug_buff, "server ready: got unexpected connection type %04x\n", command->conn->type);
+		debug_print(debug_buff);
+		break;
+	}
+	return 1;
+}
+
+int gaim_handle_redirect(struct aim_session_t *sess,
+			 struct command_rx_struct *command, ...) {
+	va_list ap;
+	int serviceid;
+	char *ip;
+	unsigned char *cookie;
+	struct gaim_connection *gc = find_gaim_conn_by_aim_sess(sess);
+
+	va_start(ap, command);
+	serviceid = va_arg(ap, int);
+	ip        = va_arg(ap, char *);
+	cookie    = va_arg(ap, unsigned char *);
+
+	switch(serviceid) {
+	case 0x7: /* Authorizer */
+		debug_print("Reconnecting with authorizor...\n");
+		{
+		struct aim_conn_t *tstconn = aim_newconn(sess, AIM_CONN_TYPE_AUTH, ip);
+		if (tstconn == NULL || tstconn->status >= AIM_CONN_STATUS_RESOLVERR)
+			debug_print("unable to reconnect with authorizer\n");
+		else {
+			gc->paspa = gdk_input_add(tstconn->fd,
+					GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
+					oscar_callback, tstconn);
+			aim_auth_sendcookie(sess, tstconn, cookie);
+		}
+		}
+		break;
+	case 0xd: /* ChatNav */
+		{
+		struct aim_conn_t *tstconn = aim_newconn(sess, AIM_CONN_TYPE_CHATNAV, ip);
+		if (tstconn == NULL || tstconn->status >= AIM_CONN_STATUS_RESOLVERR) {
+			debug_print("unable to connect to chatnav server\n");
+			return 1;
+		}
+		aim_conn_addhandler(sess, tstconn, 0x0001, 0x0003, gaim_server_ready, 0);
+		aim_auth_sendcookie(sess, tstconn, cookie);
+		gc->cnpa = gdk_input_add(tstconn->fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
+					oscar_callback, tstconn);
+		}
+		debug_print("chatnav: connected\n");
+		break;
+	case 0xe: /* Chat */
+		{
+		struct aim_conn_t *tstconn = aim_newconn(sess, AIM_CONN_TYPE_CHAT, ip);
+		char *roomname = va_arg(ap, char *);
+		struct chat_connection *ccon;
+		if (tstconn == NULL || tstconn->status >= AIM_CONN_STATUS_RESOLVERR) {
+			debug_print("unable to connect to chat server\n");
+			return 1;
+		}
+		sprintf(debug_buff, "Connected to chat room %s\n", roomname);
+		debug_print(debug_buff);
+
+		ccon = g_new0(struct chat_connection, 1);
+		ccon->conn = tstconn;
+		ccon->fd = tstconn->fd;
+		ccon->name = g_strdup(roomname);
+		
+		ccon->inpa = gdk_input_add(tstconn->fd,
+				GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
+				oscar_callback, tstconn);
+
+		gc->oscar_chats = g_slist_append(gc->oscar_chats, ccon);
+		
+		aim_chat_attachname(tstconn, roomname);
+		aim_conn_addhandler(sess, tstconn, 0x0001, 0x0003, gaim_server_ready, 0);
+		aim_auth_sendcookie(sess, tstconn, cookie);
+		}
+		break;
+	default: /* huh? */
+		sprintf(debug_buff, "got redirect for unknown service 0x%04x\n",
+				serviceid);
+		debug_print(debug_buff);
+		break;
+	}
+
+	va_end(ap);
+
+	return 1;
+}
+
+int gaim_parse_oncoming(struct aim_session_t *sess,
+			struct command_rx_struct *command, ...) {
+	struct aim_userinfo_s *info;
+	time_t time_idle;
+	int type = 0;
+
+	va_list ap;
+	va_start(ap, command);
+	info = va_arg(ap, struct aim_userinfo_s *);
+	va_end(ap);
+
+	if (info->flags & AIM_FLAG_UNCONFIRMED)
+		type |= UC_UNCONFIRMED;
+	else if (info->flags & AIM_FLAG_ADMINISTRATOR)
+		type |= UC_ADMIN;
+	else if (info->flags & AIM_FLAG_AOL)
+		type |= UC_AOL;
+	else if (info->flags & AIM_FLAG_FREE)
+		type |= UC_NORMAL;
+	if (info->flags & AIM_FLAG_AWAY)
+		type |= UC_UNAVAILABLE;
+
+	if (info->idletime) {
+		time(&time_idle);
+		time_idle -= info->idletime*60;
+	} else
+		time_idle = 0;
+
+	serv_got_update(info->sn, 1, info->warnlevel/10, info->onlinesince,
+			time_idle, type, info->capabilities);
+
+	return 1;
+}
+
+int gaim_parse_offgoing(struct aim_session_t *sess,
+			struct command_rx_struct *command, ...) {
+	char *sn;
+	va_list ap;
+
+	va_start(ap, command);
+	sn = va_arg(ap, char *);
+	va_end(ap);
+
+	serv_got_update(sn, 0, 0, 0, 0, 0, 0);
+
+	return 1;
+}
+
+static void accept_directim(GtkWidget *w, GtkWidget *m)
+{
+	struct aim_conn_t *newconn;
+	struct aim_directim_priv *priv;
+	struct gaim_connection *gc;
+	int watcher;
+
+	priv = (struct aim_directim_priv *)gtk_object_get_user_data(GTK_OBJECT(m));
+	gc = (struct gaim_connection *)gtk_object_get_user_data(GTK_OBJECT(w));
+	gtk_widget_destroy(m);
+
+	if (!(newconn = aim_directim_connect(gc->oscar_sess, gc->oscar_conn, priv))) {
+		debug_print("imimage: could not connect\n");
+		return;
+	}
+
+	aim_conn_addhandler(gc->oscar_sess, newconn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMINCOMING, gaim_directim_incoming, 0);
+	aim_conn_addhandler(gc->oscar_sess, newconn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMTYPING, gaim_directim_typing, 0);
+
+	watcher = gdk_input_add(newconn->fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
+				oscar_callback, newconn);
+
+	sprintf(debug_buff, "DirectIM: connected to %s\n", priv->sn);
+	debug_print(debug_buff);
+
+	serv_got_imimage(gc, priv->sn, priv->cookie, priv->ip, newconn, watcher);
+
+	g_free(priv);
+}
+
+static void cancel_directim(GtkWidget *w, GtkWidget *m)
+{
+	gtk_widget_destroy(m);
+}
+
+static void directim_dialog(struct gaim_connection *gc, struct aim_directim_priv *priv)
+{
+	GtkWidget *window;
+	GtkWidget *vbox;
+	GtkWidget *hbox;
+	GtkWidget *label;
+	GtkWidget *yes;
+	GtkWidget *no;
+	char buf[BUF_LONG];
+
+	window = gtk_window_new(GTK_WINDOW_DIALOG);
+	gtk_window_set_title(GTK_WINDOW(window), _("Accept Direct IM?"));
+	gtk_window_set_wmclass(GTK_WINDOW(window), "directim", "Gaim");
+	gtk_widget_realize(window);
+	aol_icon(window->window);
+	gtk_object_set_user_data(GTK_OBJECT(window), (void *)priv);
+
+	vbox = gtk_vbox_new(TRUE, 5);
+	gtk_container_add(GTK_CONTAINER(window), vbox);
+	gtk_widget_show(vbox);
+
+	sprintf(buf,  _("%s has requested to directly connect to your computer. "
+			"Do you accept?"), priv->sn);
+	label = gtk_label_new(buf);
+	gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 5);
+	gtk_widget_show(label);
+
+	hbox = gtk_hbox_new(TRUE, 10);
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
+	gtk_widget_show(hbox);
+
+	yes = picture_button(window, _("Accept"), ok_xpm);
+	gtk_box_pack_start(GTK_BOX(hbox), yes, FALSE, FALSE, 5);
+	gtk_object_set_user_data(GTK_OBJECT(yes), gc);
+
+	no = picture_button(window, _("Cancel"), cancel_xpm);
+	gtk_box_pack_end(GTK_BOX(hbox), no, FALSE, FALSE, 5);
+
+	gtk_signal_connect(GTK_OBJECT(yes), "clicked",
+			   GTK_SIGNAL_FUNC(accept_directim), window);
+	gtk_signal_connect(GTK_OBJECT(no), "clicked",
+			   GTK_SIGNAL_FUNC(cancel_directim), window);
+
+	gtk_widget_show(window);
+}
+
+int gaim_parse_incoming_im(struct aim_session_t *sess,
+			   struct command_rx_struct *command, ...) {
+	int channel;
+	va_list ap;
+	struct gaim_connection *gc = find_gaim_conn_by_aim_sess(sess);
+
+	va_start(ap, command);
+	channel = va_arg(ap, int);
+
+	/* channel 1: standard message */
+	if (channel == 1) {
+		struct aim_userinfo_s *userinfo;
+		char *msg = NULL;
+		char *tmp = g_malloc(BUF_LONG);
+		u_int icbmflags = 0;
+		u_short flag1, flag2;
+
+		userinfo  = va_arg(ap, struct aim_userinfo_s *);
+		msg       = va_arg(ap, char *);
+		icbmflags = va_arg(ap, u_int);
+		flag1     = (u_short)va_arg(ap, u_int);
+		flag2     = (u_short)va_arg(ap, u_int);
+		va_end(ap);
+
+		g_snprintf(tmp, BUF_LONG, "%s", msg);
+		serv_got_im(gc, userinfo->sn, tmp, icbmflags & AIM_IMFLAGS_AWAY);
+		g_free(tmp);
+	} else if (channel == 2) {
+		struct aim_userinfo_s *userinfo;
+		int rendtype = va_arg(ap, int);
+		if (rendtype & AIM_CAPS_CHAT) {
+			char *msg, *encoding, *lang;
+			struct aim_chat_roominfo *roominfo;
+
+			userinfo = va_arg(ap, struct aim_userinfo_s *);
+			roominfo = va_arg(ap, struct aim_chat_roominfo *);
+			msg      = va_arg(ap, char *);
+			encoding = va_arg(ap, char *);
+			lang     = va_arg(ap, char *);
+			va_end(ap);
+
+			serv_got_chat_invite(gc,
+					     roominfo->name,
+					     roominfo->exchange,
+					     userinfo->sn,
+					     msg);
+		} else if (rendtype & AIM_CAPS_SENDFILE) {
+			/* libfaim won't tell us that we got this just yet */
+		} else if (rendtype & AIM_CAPS_GETFILE) {
+			/* nor will it tell us this. but it's still there */
+		} else if (rendtype & AIM_CAPS_VOICE) {
+			/* this one libfaim tells us unuseful info about  */
+		} else if (rendtype & AIM_CAPS_BUDDYICON) {
+			/* bah */
+		} else if (rendtype & AIM_CAPS_IMIMAGE) {
+			/* DirectIM stuff */
+			struct aim_directim_priv *priv, *priv2;
+
+			userinfo = va_arg(ap, struct aim_userinfo_s *);
+			priv = va_arg(ap, struct aim_directim_priv *);
+			va_end(ap);
+
+			sprintf(debug_buff, "DirectIM request from %s (%s)\n", userinfo->sn, priv->ip);
+			debug_print(debug_buff);
+
+			priv2 = g_new0(struct aim_directim_priv, 1);
+			strcpy(priv2->cookie, priv->cookie);
+			strcpy(priv2->sn, priv->sn);
+			strcpy(priv2->ip, priv->ip);
+			directim_dialog(gc, priv2);
+		} else {
+			sprintf(debug_buff, "Unknown rendtype %d\n", rendtype);
+			debug_print(debug_buff);
+		}
+	}
+
+	return 1;
+}
+
+int gaim_parse_misses(struct aim_session_t *sess,
+		      struct command_rx_struct *command, ...) {
+	va_list ap;
+	u_short chan, nummissed, reason;
+	struct aim_userinfo_s *userinfo;
+	char buf[1024];
+
+	va_start(ap, command);
+	chan = (u_short)va_arg(ap, u_int);
+	userinfo = va_arg(ap, struct aim_userinfo_s *);
+	nummissed = (u_short)va_arg(ap, u_int);
+	reason = (u_short)va_arg(ap, u_int);
+	va_end(ap);
+
+	switch(reason) {
+		case 1:
+			/* message too large */
+			sprintf(buf, _("You missed a message from %s because it was too large."), userinfo->sn);
+			do_error_dialog(buf, _("Gaim - Error"));
+			plugin_event(event_error, (void *)961, 0, 0);
+			break;
+		default:
+			sprintf(buf, _("You missed a message from %s for unknown reasons."), userinfo->sn);
+			do_error_dialog(buf, _("Gaim - Error"));
+			plugin_event(event_error, (void *)970, 0, 0);
+			break;
+	}
+
+	return 1;
+}
+
+int gaim_parse_msgerr(struct aim_session_t *sess,
+		      struct command_rx_struct *command, ...) {
+	va_list ap;
+	char *destn;
+	u_short reason;
+	char buf[1024];
+
+	va_start(ap, command);
+	destn = va_arg(ap, char *);
+	reason = (u_short)va_arg(ap, u_int);
+	va_end(ap);
+
+	sprintf(buf, _("Your message to %s did not get sent: %s"), destn,
+			(reason < msgerrreasonlen) ? msgerrreason[reason] : _("Reason unknown"));
+	do_error_dialog(buf, _("Gaim - Error"));
+
+	return 1;
+}
+
+int gaim_parse_locerr(struct aim_session_t *sess,
+		      struct command_rx_struct *command, ...) {
+	va_list ap;
+	char *destn;
+	u_short reason;
+	char buf[1024];
+
+	va_start(ap, command);
+	destn = va_arg(ap, char *);
+	reason = (u_short)va_arg(ap, u_int);
+	va_end(ap);
+
+	sprintf(buf, _("User information for %s unavailable: %s"), destn,
+			(reason < msgerrreasonlen) ? msgerrreason[reason] : _("Reason unknown"));
+	do_error_dialog(buf, _("Gaim - Error"));
+
+	return 1;
+}
+
+int gaim_parse_user_info(struct aim_session_t *sess,
+			 struct command_rx_struct *command, ...) {
+	struct aim_userinfo_s *info;
+	char *prof_enc = NULL, *prof = NULL;
+	u_short infotype;
+	char buf[BUF_LONG];
+	struct gaim_connection *gc = find_gaim_conn_by_aim_sess(sess);
+	va_list ap;
+
+	va_start(ap, command);
+	info = va_arg(ap, struct aim_userinfo_s *);
+	prof_enc = va_arg(ap, char *);
+	prof = va_arg(ap, char *);
+	infotype = (u_short)va_arg(ap, u_int);
+	va_end(ap);
+
+	if (prof == NULL || !strlen(prof)) {
+		/* no info/away message */
+		char buf[1024];
+		sprintf(buf, _("%s has no info/away message."), info->sn);
+		do_error_dialog(buf, _("Gaim - Error"));
+		plugin_event(event_error, (void *)977, 0, 0);
+		return 1;
+	}
+
+	snprintf(buf, sizeof buf, _("Username : <B>%s</B>\n<BR>"
+				  "Warning Level : <B>%d %%</B>\n<BR>"
+				  "Online Since : <B>%s</B><BR>"
+				  "Idle Minutes : <B>%d</B>\n<BR><HR><BR>"
+				  "%s\n"),
+				  info->sn,
+				  info->warnlevel/10,
+				  asctime(localtime(&info->onlinesince)),
+				  info->idletime,
+				  infotype == AIM_GETINFO_GENERALINFO ? prof :
+ 				  away_subs(prof, gc->username));
+	g_show_info_text(away_subs(buf, gc->username));
+
+	return 1;
+}
+
+int gaim_parse_motd(struct aim_session_t *sess,
+		    struct command_rx_struct *command, ...) {
+	char *msg;
+	u_short id;
+	va_list ap;
+	struct gaim_connection *gc = find_gaim_conn_by_aim_sess(sess);
+
+	va_start(ap, command);
+	id  = (u_short)va_arg(ap, u_int);
+	msg = va_arg(ap, char *);
+	va_end(ap);
+
+	sprintf(debug_buff, "MOTD: %s (%d)\n", msg, id);
+	debug_print(debug_buff);
+	sprintf(debug_buff, "Gaim %s / Libfaim %s\n",
+			VERSION, aim_getbuildstring());
+	debug_print(debug_buff);
+	if (id != 4)
+		do_error_dialog(_("Your connection may be lost."),
+				_("AOL error"));
+
+	if (gc->keepalive < 0)
+		update_keepalive(gc, gc->keepalive);
+
+	return 1;
+}
+
+int gaim_chatnav_info(struct aim_session_t *sess,
+		      struct command_rx_struct *command, ...) {
+	va_list ap;
+	u_short type;
+	struct gaim_connection *gc = find_gaim_conn_by_aim_sess(sess);
+
+	va_start(ap, command);
+	type = (u_short)va_arg(ap, u_int);
+
+	switch(type) {
+		case 0x0002: {
+			int maxrooms;
+			struct aim_chat_exchangeinfo *exchanges;
+			int exchangecount, i = 0;
+
+			maxrooms = (u_char)va_arg(ap, u_int);
+			exchangecount = va_arg(ap, int);
+			exchanges = va_arg(ap, struct aim_chat_exchangeinfo *);
+			va_end(ap);
+
+			debug_print("chat info: Chat Rights:\n");
+			sprintf(debug_buff, "chat info: \tMax Concurrent Rooms: %d\n", maxrooms);
+			debug_print(debug_buff);
+			sprintf(debug_buff, "chat info: \tExchange List: (%d total)\n", exchangecount);
+			debug_print(debug_buff);
+			while (i < exchangecount) {
+				sprintf(debug_buff, "chat info: \t\t%x: %s (%s/%s)\n",
+						exchanges[i].number,
+						exchanges[i].name,
+						exchanges[i].charset1,
+						exchanges[i].lang1);
+				debug_print(debug_buff);
+				i++;
+			}
+			if (gc->create_exchange) {
+				sprintf(debug_buff, "creating room %s\n",
+						gc->create_name);
+				debug_print(debug_buff);
+				aim_chatnav_createroom(sess, command->conn, gc->create_name, gc->create_exchange);
+				gc->create_exchange = 0;
+				g_free(gc->create_name);
+				gc->create_name = NULL;
+			}
+			}
+			break;
+		case 0x0008: {
+			char *fqcn, *name, *ck;
+			u_short instance, flags, maxmsglen, maxoccupancy, unknown;
+			unsigned char createperms;
+			unsigned long createtime;
+
+			fqcn = va_arg(ap, char *);
+			instance = (u_short)va_arg(ap, u_int);
+			flags = (u_short)va_arg(ap, u_int);
+			createtime = va_arg(ap, unsigned long);
+			maxmsglen = (u_short)va_arg(ap, u_int);
+			maxoccupancy = (u_short)va_arg(ap, u_int);
+			createperms = (unsigned char)va_arg(ap, int);
+			unknown = (u_short)va_arg(ap, u_int);
+			name = va_arg(ap, char *);
+			ck = va_arg(ap, char *);
+			va_end(ap);
+
+			sprintf(debug_buff, "created room: %s %d %d %lu %d %d %d %d %s %s\n", fqcn, instance, flags, createtime, maxmsglen, maxoccupancy, createperms, unknown, name, ck);
+			debug_print(debug_buff);
+			if (flags & 0x4) {
+				sprintf(debug_buff, "joining %s on exchange 5\n", name);
+				debug_print(debug_buff);
+				aim_chat_join(gc->oscar_sess, gc->oscar_conn, 5, ck);
+			} else 
+				sprintf(debug_buff, "joining %s on exchange 4\n", name);{
+				debug_print(debug_buff);
+				aim_chat_join(gc->oscar_sess, gc->oscar_conn, 4, ck);
+			}
+			}
+			break;
+		default:
+			va_end(ap);
+			sprintf(debug_buff, "chatnav info: unknown type (%04x)\n", type);
+			debug_print(debug_buff);
+			break;
+	}
+	return 1;
+}
+
+int gaim_chat_join(struct aim_session_t *sess,
+		   struct command_rx_struct *command, ...) {
+	va_list ap;
+	int count, i = 0;
+	struct aim_userinfo_s *info;
+	struct gaim_connection *g = find_gaim_conn_by_aim_sess(sess);
+
+	GSList *bcs = g->buddy_chats;
+	struct conversation *b = NULL;
+
+	va_start(ap, command);
+	count = va_arg(ap, int);
+	info  = va_arg(ap, struct aim_userinfo_s *);
+	va_end(ap);
+
+	while(bcs) {
+		b = (struct conversation *)bcs->data;
+		if (!strcasecmp(b->name, (char *)command->conn->priv))
+			break;	
+		bcs = bcs->next;
+		b = NULL;
+	}
+	if (!b)
+		return 1;
+		
+	while (i < count)
+		add_chat_buddy(b, info[i++].sn);
+
+	return 1;
+}
+
+int gaim_chat_leave(struct aim_session_t *sess,
+		    struct command_rx_struct *command, ...) {
+	va_list ap;
+	int count, i = 0;
+	struct aim_userinfo_s *info;
+	struct gaim_connection *g = find_gaim_conn_by_aim_sess(sess);
+
+	GSList *bcs = g->buddy_chats;
+	struct conversation *b = NULL;
+
+	va_start(ap, command);
+	count = va_arg(ap, int);
+	info  = va_arg(ap, struct aim_userinfo_s *);
+	va_end(ap);
+
+	while(bcs) {
+		b = (struct conversation *)bcs->data;
+		if (!strcasecmp(b->name, (char *)command->conn->priv))
+			break;	
+		bcs = bcs->next;
+		b = NULL;
+	}
+	if (!b)
+		return 1;
+		
+	while (i < count)
+		remove_chat_buddy(b, info[i++].sn);
+
+	return 1;
+}
+
+int gaim_chat_info_update(struct aim_session_t *sess,
+			  struct command_rx_struct *command, ...) {
+	debug_print("inside chat_info_update\n");
+	return 1;
+}
+
+int gaim_chat_incoming_msg(struct aim_session_t *sess,
+			   struct command_rx_struct *command, ...) {
+	va_list ap;
+	struct aim_userinfo_s *info;
+	char *msg;
+	struct gaim_connection *gc = find_gaim_conn_by_aim_sess(sess);
+
+	GSList *bcs = gc->buddy_chats;
+	struct conversation *b = NULL;
+
+	va_start(ap, command);
+	info = va_arg(ap, struct aim_userinfo_s *);
+	msg  = va_arg(ap, char *);
+
+	while(bcs) {
+		b = (struct conversation *)bcs->data;
+		if (!strcasecmp(b->name, (char *)command->conn->priv))
+			break;
+		bcs = bcs->next;
+		b = NULL;
+	}
+	if (!b)
+		return 0;
+
+	serv_got_chat_in(gc, b->id, info->sn, 0, msg);
+
+	return 1;
+}
+
+ /*
+  * Recieved in response to an IM sent with the AIM_IMFLAGS_ACK option.
+  */
+int gaim_parse_msgack(struct aim_session_t *sess, struct command_rx_struct *command, ...) {
+	va_list ap;
+	u_short type;
+	char *sn = NULL;
+
+	va_start(ap, command);
+	type = (u_short)va_arg(ap, u_int);
+	sn = va_arg(ap, char *);
+	va_end(ap);
+
+	sprintf(debug_buff, "Sent message to %s.\n", sn);
+	debug_print(debug_buff);
+
+	return 1;
+}
+
+int gaim_parse_ratechange(struct aim_session_t *sess, struct command_rx_struct *command, ...) {
+	va_list ap;
+	unsigned long newrate;
+
+	va_start(ap, command); 
+	newrate = va_arg(ap, unsigned long);
+	va_end(ap);
+
+	sprintf(debug_buff, "ratechange: %lu\n", newrate);
+	debug_print(debug_buff);
+
+	return 1;
+}
+
+int gaim_parse_evilnotify(struct aim_session_t *sess, struct command_rx_struct *command, ...) {
+	va_list ap;
+	char *sn;
+
+	va_start(ap, command);
+	sn = va_arg(ap, char *);
+	va_end(ap);
+
+	serv_got_eviled(sn, 0);
+
+	return 1;
+}
+
+int gaim_rateresp(struct aim_session_t *sess, struct command_rx_struct *command, ...) {
+	struct gaim_connection *gc = find_gaim_conn_by_aim_sess(sess);
+	switch (command->conn->type) {
+	case AIM_CONN_TYPE_BOS:
+		aim_bos_ackrateresp(sess, command->conn);
+		aim_bos_reqpersonalinfo(sess, command->conn);
+		aim_bos_reqlocaterights(sess, command->conn);
+		aim_bos_setprofile(sess, command->conn, gc->user_info, NULL, gaim_caps);
+		aim_bos_reqbuddyrights(sess, command->conn);
+
+		if (mainwindow)
+			gtk_widget_hide(mainwindow);
+		show_buddy_list();
+
+#ifdef USE_APPLET
+		if (general_options & OPT_GEN_APP_BUDDY_SHOW) {
+			refresh_buddy_window();
+			createOnlinePopup();
+			applet_buddy_show = TRUE;
+		} else {
+			gtk_widget_hide(blist);
+			applet_buddy_show = FALSE;
+		}
+		set_user_state(online);
+#else
+		refresh_buddy_window();
+#endif
+
+		serv_finish_login(gc);
+		gaim_setup(gc);
+
+		if (bud_list_cache_exists(gc))
+			do_import(NULL, gc);
+
+		debug_print("buddy list loaded\n");
+
+		setup_buddy_chats();
+
+		aim_addicbmparam(sess, command->conn);
+		aim_bos_reqicbmparaminfo(sess, command->conn);
+
+		aim_bos_reqrights(sess, command->conn);
+		aim_bos_setgroupperm(sess, command->conn, AIM_FLAG_ALLUSERS);
+		aim_bos_setprivacyflags(sess, command->conn, AIM_PRIVFLAGS_ALLOWIDLE |
+							     AIM_PRIVFLAGS_ALLOWMEMBERSINCE);
+
+		break;
+	default:
+		sprintf(debug_buff, "got rate response for unhandled connection type %04x\n",
+				command->conn->type);
+		debug_print(debug_buff);
+		break;
+	}
+
+	return 1;
+}
+
+int gaim_reportinterval(struct aim_session_t *sess, struct command_rx_struct *command, ...) {
+	if (command->data) {
+		sprintf(debug_buff, "minimum report interval: %d (seconds?)\n", aimutil_get16(command->data+10));
+		debug_print(debug_buff);
+	} else
+		debug_print("NULL minimum report interval!\n");
+	return 1;
+}
+
+int gaim_parse_buddyrights(struct aim_session_t *sess, struct command_rx_struct *command, ...) {
+	va_list ap;
+	u_short maxbuddies, maxwatchers;
+
+	va_start(ap, command);
+	maxbuddies = (u_short)va_arg(ap, u_int);
+	maxwatchers = (u_short)va_arg(ap, u_int);
+	va_end(ap);
+
+	sprintf(debug_buff, "buddy list rights: Max buddies = %d / Max watchers = %d\n", maxbuddies, maxwatchers);
+	debug_print(debug_buff);
+
+	return 1;
+}
+
+int gaim_bosrights(struct aim_session_t *sess, struct command_rx_struct *command, ...) {
+	u_short maxpermits, maxdenies;
+	va_list ap;
+
+	va_start(ap, command);
+	maxpermits = (u_short)va_arg(ap, u_int);
+	maxdenies = (u_short)va_arg(ap, u_int);
+	va_end(ap);
+
+	sprintf(debug_buff, "BOS rights: Max permit = %d / Max deny = %d\n", maxpermits, maxdenies);
+	debug_print(debug_buff);
+
+	aim_bos_clientready(sess, command->conn);
+
+	aim_bos_reqservice(sess, command->conn, AIM_CONN_TYPE_CHATNAV);
+
+	return 1;
+}
+
+int gaim_directim_incoming(struct aim_session_t *sess, struct command_rx_struct *command, ...) {
+	va_list ap;
+	char *sn = NULL, *msg = NULL;
+	struct aim_conn_t *conn;
+	struct gaim_connection *gc = find_gaim_conn_by_aim_sess(sess);
+
+	va_start(ap, command);
+	conn = va_arg(ap, struct aim_conn_t *);
+	sn = va_arg(ap, char *);
+	msg = va_arg(ap, char *);
+	va_end(ap);
+
+	sprintf(debug_buff, "Got DirectIM message from %s\n", sn);
+	debug_print(debug_buff);
+
+	serv_got_im(gc, sn, msg, 0);
+
+	return 1;
+}
+
+/* this is such a f*cked up function */
+int gaim_directim_initiate(struct aim_session_t *sess, struct command_rx_struct *command, ...) {
+	va_list ap;
+	struct aim_directim_priv *priv;
+	struct aim_conn_t *newconn;
+	struct conversation *cnv;
+	int watcher;
+
+	va_start(ap, command);
+	newconn = va_arg(ap, struct aim_conn_t *);
+	va_end(ap);
+
+	priv = (struct aim_directim_priv *)newconn->priv;
+
+	sprintf(debug_buff, "DirectIM: initiate success to %s\n", priv->sn);
+	debug_print(debug_buff);
+
+	cnv = find_conversation(priv->sn);
+	gdk_input_remove(cnv->watcher);
+	watcher = gdk_input_add(newconn->fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
+					oscar_callback, newconn);
+	make_direct(cnv, TRUE, newconn, watcher);
+
+	aim_conn_addhandler(sess, newconn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMINCOMING, gaim_directim_incoming, 0);
+	aim_conn_addhandler(sess, newconn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMTYPING, gaim_directim_typing, 0);
+
+	return 1;
+}
+
+int gaim_directim_typing(struct aim_session_t *sess, struct command_rx_struct *command, ...) {
+	va_list ap;
+	char *sn;
+
+	va_start(ap, command);
+	sn = va_arg(ap, char *);
+	va_end(ap);
+
+	/* I had to leave this. It's just too funny. It reminds me of my sister. */
+	sprintf(debug_buff, "ohmigod! %s has started typing (DirectIM). He's going to send you a message! *squeal*\n", sn);
+	debug_print(debug_buff);
+
+	return 1;
+}
+
+void oscar_do_directim(struct gaim_connection *gc, char *name) {
+	struct aim_conn_t *newconn = aim_directim_initiate(gc->oscar_sess, gc->oscar_conn, NULL, name);
+	struct conversation *cnv = find_conversation(name); /* this will never be null because it just got set up */
+	cnv->conn = newconn;
+	cnv->watcher = gdk_input_add(newconn->fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION, oscar_callback, newconn);
+	aim_conn_addhandler(gc->oscar_sess, newconn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMINITIATE, gaim_directim_initiate, 0);
+}
+
+static void oscar_keepalive(struct gaim_connection *gc) {
+	aim_flap_nop(gc->oscar_sess, gc->oscar_conn);
+}
+
+static char *oscar_name() {
+	return "Oscar";
+}
+
+char *name() {
+	return "Oscar";
+}
+
+char *description() {
+	return "Allows gaim to use the Oscar protocol";
+}
+
+static void oscar_send_im(struct gaim_connection *gc, char *name, char *message, int away) {
+	struct conversation *cnv = find_conversation(name);
+	if (cnv && cnv->is_direct) {
+		debug_printf("Sending DirectIM to %s\n", name);
+		aim_send_im_direct(gc->oscar_sess, cnv->conn, message);
+	} else {
+		if (away)
+			aim_send_im(gc->oscar_sess, gc->oscar_conn, name, AIM_IMFLAGS_AWAY, message);
+		else
+			aim_send_im(gc->oscar_sess, gc->oscar_conn, name, AIM_IMFLAGS_ACK, message);
+	}
+}
+
+static void oscar_get_info(struct gaim_connection *g, char *name) {
+	aim_getinfo(g->oscar_sess, g->oscar_conn, name, AIM_GETINFO_GENERALINFO);
+}
+
+static void oscar_get_away_msg(struct gaim_connection *g, char *name) {
+	aim_getinfo(g->oscar_sess, g->oscar_conn, name, AIM_GETINFO_AWAYMESSAGE);
+}
+
+static void oscar_set_dir(struct gaim_connection *g, char *first, char *middle, char *last,
+			  char *maiden, char *city, char *state, char *country, int web) {
+	/* FIXME : some of these things are wrong, but i'm lazy */
+	aim_setdirectoryinfo(g->oscar_sess, g->oscar_conn, first, middle, last,
+				maiden, NULL, NULL, city, state, NULL, 0, web);
+}
+
+
+static void oscar_set_idle(struct gaim_connection *g, int time) {
+	aim_bos_setidle(g->oscar_sess, g->oscar_conn, time);
+}
+
+static void oscar_set_info(struct gaim_connection *g, char *info) {
+	if (awaymessage)
+		aim_bos_setprofile(g->oscar_sess, g->oscar_conn, info,
+					awaymessage->message, gaim_caps);
+	else
+		aim_bos_setprofile(g->oscar_sess, g->oscar_conn, info,
+					NULL, gaim_caps);
+}
+
+static void oscar_set_away(struct gaim_connection *g, char *message) {
+	aim_bos_setprofile(g->oscar_sess, g->oscar_conn, g->user_info, message, gaim_caps);
+}
+
+static void oscar_warn(struct gaim_connection *g, char *name, int anon) {
+	aim_send_warning(g->oscar_sess, g->oscar_conn, name, anon);
+}
+
+static void oscar_dir_search(struct gaim_connection *g, char *first, char *middle, char *last,
+			     char *maiden, char *city, char *state, char *country, char *email) {
+	if (strlen(email))
+		aim_usersearch_address(g->oscar_sess, g->oscar_conn, email);
+}
+
+static void oscar_add_buddy(struct gaim_connection *g, char *name) {
+	aim_add_buddy(g->oscar_sess, g->oscar_conn, name);
+}
+
+static void oscar_add_buddies(struct gaim_connection *g, GList *buddies) {
+	char buf[MSG_LEN];
+	int n = 0;
+	while (buddies) {
+		if (n > MSG_LEN - 18) {
+			aim_bos_setbuddylist(g->oscar_sess, g->oscar_conn, buf);
+			n = 0;
+		}
+		n += g_snprintf(buf + n, sizeof(buf) - n, "%s&", (char *)buddies->data);
+		buddies = buddies->next;
+	}
+	aim_bos_setbuddylist(g->oscar_sess, g->oscar_conn, buf);
+}
+
+static void oscar_remove_buddy(struct gaim_connection *g, char *name) {
+	aim_remove_buddy(g->oscar_sess, g->oscar_conn, name);
+}
+
+static void oscar_join_chat(struct gaim_connection *g, int exchange, char *name) {
+	struct aim_conn_t *cur = NULL;
+	sprintf(debug_buff, "Attempting to join chat room %s.\n", name);
+	debug_print(debug_buff);
+	if ((cur = aim_getconn_type(g->oscar_sess, AIM_CONN_TYPE_CHATNAV))) {
+		debug_print("chatnav exists, creating room\n");
+		aim_chatnav_createroom(g->oscar_sess, cur, name, exchange);
+	} else {
+		/* this gets tricky */
+		debug_print("chatnav does not exist, opening chatnav\n");
+		g->create_exchange = exchange;
+		g->create_name = g_strdup(name);
+		aim_bos_reqservice(g->oscar_sess, g->oscar_conn, AIM_CONN_TYPE_CHATNAV);
+	}
+}
+
+static void oscar_chat_invite(struct gaim_connection *g, int id, char *message, char *name) {
+	GSList *bcs = g->buddy_chats;
+	struct conversation *b = NULL;
+
+	while (bcs) {
+		b = (struct conversation *)bcs->data;
+		if (id == b->id)
+			break;
+		bcs = bcs->next;
+		b = NULL;
+	}
+
+	if (!b)
+		return;
+
+	aim_chat_invite(g->oscar_sess, g->oscar_conn, name,
+			message ? message : "", 0x4, b->name, 0x0);
+}
+
+static void oscar_chat_leave(struct gaim_connection *g, int id) {
+	GSList *bcs = g->buddy_chats;
+	struct conversation *b = NULL;
+	struct chat_connection *c = NULL;
+	int count = 0;
+
+	while (bcs) {
+		count++;
+		b = (struct conversation *)bcs->data;
+		if (id == b->id)
+			break;
+		bcs = bcs->next;
+		b = NULL;
+	}
+
+	if (!b)
+		return;
+
+	sprintf(debug_buff, "Attempting to leave room %s (currently in %d rooms)\n",
+				b->name, count);
+	debug_print(debug_buff);
+	
+	c = find_oscar_chat(g, b->name);
+	if (c != NULL) {
+		g->oscar_chats = g_slist_remove(g->oscar_chats, c);
+		gdk_input_remove(c->inpa);
+		if (g && g->oscar_sess)
+			aim_conn_kill(g->oscar_sess, &c->conn);
+		g_free(c->name);
+		g_free(c);
+	}
+	/* we do this because with Oscar it doesn't tell us we left */
+	serv_got_chat_left(g, b->id);
+}
+
+static void oscar_chat_whisper(struct gaim_connection *g, int id, char *who, char *message) {
+	do_error_dialog("Sorry, Oscar doesn't whisper. Send an IM. (The last message was not received.)",
+			"Gaim - Chat");
+}
+
+static void oscar_chat_send(struct gaim_connection *g, int id, char *message) {
+	struct aim_conn_t *cn; 
+	GSList *bcs = g->buddy_chats;
+	struct conversation *b = NULL;
+
+	while (bcs) {
+		b = (struct conversation *)bcs->data;
+		if (id == b->id)
+			break;
+		bcs = bcs->next;
+		b = NULL;
+	}
+	if (!b)
+		return;
+
+	cn = aim_chat_getconn(g->oscar_sess, b->name);
+	aim_chat_send_im(g->oscar_sess, cn, message);
+}
+
+struct prpl *oscar_init() {
+	struct prpl *ret = g_new0(struct prpl, 1);
+
+	ret->protocol = PROTO_OSCAR;
+	ret->name = oscar_name;
+	ret->login = oscar_login;
+	ret->close = oscar_close;
+	ret->send_im = oscar_send_im;
+	ret->set_info = oscar_set_info;
+	ret->get_info = oscar_get_info;
+	ret->set_away = oscar_set_away;
+	ret->get_away_msg = oscar_get_away_msg;
+	ret->set_dir = oscar_set_dir;
+	ret->get_dir = NULL; /* Oscar really doesn't have this */
+	ret->dir_search = oscar_dir_search;
+	ret->set_idle = oscar_set_idle;
+	ret->change_passwd = NULL; /* Oscar doesn't have this either */
+	ret->add_buddy = oscar_add_buddy;
+	ret->add_buddies = oscar_add_buddies;
+	ret->remove_buddy = oscar_remove_buddy;
+	ret->add_permit = NULL; /* Oscar's permit/deny stuff is messed up */
+	ret->add_deny = NULL; /* at least, i can't figure it out :-P */
+	ret->warn = oscar_warn;
+	ret->accept_chat = NULL; /* oscar doesn't have accept, it just joins */
+	ret->join_chat = oscar_join_chat;
+	ret->chat_invite = oscar_chat_invite;
+	ret->chat_leave = oscar_chat_leave;
+	ret->chat_whisper = oscar_chat_whisper;
+	ret->chat_send = oscar_chat_send;
+	ret->keepalive = oscar_keepalive;
+	
+	return ret;
+}
+
+int gaim_plugin_init(void *handle) {
+	protocols = g_slist_append(protocols, oscar_init());
+	return 0;
+}
--- a/sounds/Makefile.am	Thu Oct 12 18:59:36 2000 +0000
+++ b/sounds/Makefile.am	Fri Oct 13 07:24:40 2000 +0000
@@ -1,4 +1,4 @@
-bin_PROGRAMS = au2h
+noinst_PROGRAMS = au2h
 
 au2h_SOURCES = au2h.c
 
--- a/src/Makefile.am	Thu Oct 12 18:59:36 2000 +0000
+++ b/src/Makefile.am	Fri Oct 13 07:24:40 2000 +0000
@@ -21,6 +21,7 @@
 			plugins.c \
 			prefs.c \
 			proxy.c \
+			prpl.c \
 			rvous.c \
 			server.c \
 			sound.c \
@@ -51,6 +52,7 @@
 		plugins.c \
 		prefs.c \
 		proxy.c \
+		prpl.c \
 		rvous.c \
 		server.c \
 		sound.c \
@@ -63,4 +65,4 @@
 CFLAGS += -DLOCALEDIR=\"$(datadir)/locale\" $(DEBUG_CFLAGS)
 LDFLAGS += $(DEBUG_LDFLAGS)
 
-EXTRA_DIST = gaim.h proxy.h gnome_applet_mgr.h gtkhtml.h gtkticker.h convo.h multi.h
+EXTRA_DIST = gaim.h proxy.h gnome_applet_mgr.h gtkhtml.h gtkticker.h convo.h multi.h prpl.h
--- a/src/aim.c	Thu Oct 12 18:59:36 2000 +0000
+++ b/src/aim.c	Fri Oct 13 07:24:40 2000 +0000
@@ -368,6 +368,8 @@
 #else
         
 
+	static_proto_init();
+
         show_login();
 	auto_login();
         gtk_main();
--- a/src/dialogs.c	Thu Oct 12 18:59:36 2000 +0000
+++ b/src/dialogs.c	Fri Oct 13 07:24:40 2000 +0000
@@ -1153,6 +1153,7 @@
 		save_prefs();
 
 		if (gc) {
+			g_snprintf(gc->user_info, sizeof(gc->user_info), "%s", junk);
 			buf = g_malloc(strlen(junk) * 4);
 			if (!buf) {
 				buf = g_malloc(1);
@@ -1609,11 +1610,9 @@
 	gtk_text_set_word_wrap(GTK_TEXT(b->text), TRUE);
 	gtk_text_set_editable(GTK_TEXT(b->text), TRUE);
 	gtk_widget_set_usize(b->text, 350, 100);
-	/* is this necessary?
-	if (users)
+	if (aim_users)
 		gtk_text_insert(GTK_TEXT(b->text), NULL, NULL, NULL,
-				((struct aim_user *)users->data)->user_info, -1);
-	*/
+				((struct aim_user *)aim_users->data)->user_info, -1);
 
 	gtk_widget_show(b->text);
 
--- a/src/multi.c	Thu Oct 12 18:59:36 2000 +0000
+++ b/src/multi.c	Fri Oct 13 07:24:40 2000 +0000
@@ -20,6 +20,7 @@
  */
 
 #include <gtk/gtk.h>
+#include "prpl.h"
 #include "multi.h"
 #include "gaim.h"
 #include "gnome_applet_mgr.h"
@@ -49,6 +50,7 @@
 {
 	struct gaim_connection *gc = g_new0(struct gaim_connection, 1);
 	gc->protocol = proto;
+	gc->prpl = find_prpl(proto);
 	g_snprintf(gc->username, sizeof(gc->username), "%s", username);
 	g_snprintf(gc->password, sizeof(gc->password), "%s", password);
 	gc->keepalive = -1;
@@ -122,15 +124,11 @@
 
 static char *proto_name(int proto)
 {
-	switch (proto) {
-		case PROTO_TOC:
-			return "TOC";
-		case PROTO_OSCAR:
-			return "Oscar";
-		default:
-			/* PRPL */
-			return "Other";
-	}
+	struct prpl *p = find_prpl(proto);
+	if (p && p->name)
+		return (*p->name)();
+	else
+		return "Unknown";
 }
 
 static GtkWidget *generate_list()
@@ -228,7 +226,6 @@
 			return;
 		}
 		u = g_new0(struct aim_user, 1);
-		u->protocol = PROTO_TOC;
 		g_snprintf(u->username, sizeof(u->username), "%s", txt);
 		txt = gtk_entry_get_text(GTK_ENTRY(tmpusr.pass));
 		g_snprintf(u->password, sizeof(u->password), "%s", txt);
@@ -269,6 +266,8 @@
 	GtkWidget *optmenu;
 	GtkWidget *menu;
 	GtkWidget *opt;
+	GSList *p = protocols;
+	struct prpl *e;
 
 	/* PRPL: should we set some way to update these when new protocols get added? */
 	optmenu = gtk_option_menu_new();
@@ -277,18 +276,19 @@
 
 	menu = gtk_menu_new();
 
-	/* PRPL: we need to have some way of getting all the plugin names, etc */
-	opt = gtk_menu_item_new_with_label("TOC");
-	gtk_object_set_user_data(GTK_OBJECT(opt), u);
-	gtk_signal_connect(GTK_OBJECT(opt), "activate", GTK_SIGNAL_FUNC(set_prot), (void *)PROTO_TOC);
-	gtk_menu_append(GTK_MENU(menu), opt);
-	gtk_widget_show(opt);
-
-	opt = gtk_menu_item_new_with_label("Oscar");
-	gtk_object_set_user_data(GTK_OBJECT(opt), u);
-	gtk_signal_connect(GTK_OBJECT(opt), "activate", GTK_SIGNAL_FUNC(set_prot), (void *)PROTO_OSCAR);
-	gtk_menu_append(GTK_MENU(menu), opt);
-	gtk_widget_show(opt);
+	while (p) {
+		e = (struct prpl *)p->data;
+		if (e->name)
+			opt = gtk_menu_item_new_with_label((*e->name)());
+		else
+			opt = gtk_menu_item_new_with_label("Unknown");
+		gtk_object_set_user_data(GTK_OBJECT(opt), u);
+		gtk_signal_connect(GTK_OBJECT(opt), "activate",
+				   GTK_SIGNAL_FUNC(set_prot), (void *)e->protocol);
+		gtk_menu_append(GTK_MENU(menu), opt);
+		gtk_widget_show(opt);
+		p = p->next;
+	}
 
 	gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), menu);
 	u->tmp_protocol = u->protocol;
--- a/src/multi.h	Thu Oct 12 18:59:36 2000 +0000
+++ b/src/multi.h	Fri Oct 13 07:24:40 2000 +0000
@@ -34,6 +34,7 @@
 	/* we need to do either oscar or TOC */
 	/* we make this as an int in case if we want to add more protocols later */
 	int protocol;
+	struct prpl *prpl;
 
 	/* let's do the oscar-specific stuff first since i know it better */
 	struct aim_session_t *oscar_sess;
--- a/src/oscar.c	Thu Oct 12 18:59:36 2000 +0000
+++ b/src/oscar.c	Fri Oct 13 07:24:40 2000 +0000
@@ -20,6 +20,8 @@
  *
  */
 
+#ifndef DYNAMIC_OSCAR
+
 #ifdef HAVE_CONFIG_H
 #include "../config.h"
 #endif
@@ -38,6 +40,7 @@
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include "multi.h"
+#include "prpl.h"
 #include "gaim.h"
 #include "aim.h"
 #include "gnome_applet_mgr.h"
@@ -1301,23 +1304,218 @@
 	aim_conn_addhandler(gc->oscar_sess, newconn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMINITIATE, gaim_directim_initiate, 0);
 }
 
-void send_keepalive(gpointer d) {
-	struct gaim_connection *gc = (struct gaim_connection *)d;
-	debug_print("sending oscar NOP\n");
-	if (gc->protocol == PROTO_OSCAR) { /* keeping it open for TOC */
-		aim_flap_nop(gc->oscar_sess, gc->oscar_conn);
-	} else if (gc->protocol == PROTO_TOC) {
-		sflap_send(gc, "", 0, TYPE_KEEPALIVE);
+static void oscar_keepalive(struct gaim_connection *gc) {
+	aim_flap_nop(gc->oscar_sess, gc->oscar_conn);
+}
+
+static char *oscar_name() {
+	return "Oscar";
+}
+
+static void oscar_send_im(struct gaim_connection *gc, char *name, char *message, int away) {
+	struct conversation *cnv = find_conversation(name);
+	if (cnv && cnv->is_direct) {
+		debug_printf("Sending DirectIM to %s\n", name);
+		aim_send_im_direct(gc->oscar_sess, cnv->conn, message);
+	} else {
+		if (away)
+			aim_send_im(gc->oscar_sess, gc->oscar_conn, name, AIM_IMFLAGS_AWAY, message);
+		else
+			aim_send_im(gc->oscar_sess, gc->oscar_conn, name, AIM_IMFLAGS_ACK, message);
+	}
+}
+
+static void oscar_get_info(struct gaim_connection *g, char *name) {
+	aim_getinfo(g->oscar_sess, g->oscar_conn, name, AIM_GETINFO_GENERALINFO);
+}
+
+static void oscar_get_away_msg(struct gaim_connection *g, char *name) {
+	aim_getinfo(g->oscar_sess, g->oscar_conn, name, AIM_GETINFO_AWAYMESSAGE);
+}
+
+static void oscar_set_dir(struct gaim_connection *g, char *first, char *middle, char *last,
+			  char *maiden, char *city, char *state, char *country, int web) {
+	/* FIXME : some of these things are wrong, but i'm lazy */
+	aim_setdirectoryinfo(g->oscar_sess, g->oscar_conn, first, middle, last,
+				maiden, NULL, NULL, city, state, NULL, 0, web);
+}
+
+
+static void oscar_set_idle(struct gaim_connection *g, int time) {
+	aim_bos_setidle(g->oscar_sess, g->oscar_conn, time);
+}
+
+static void oscar_set_info(struct gaim_connection *g, char *info) {
+	if (awaymessage)
+		aim_bos_setprofile(g->oscar_sess, g->oscar_conn, info,
+					awaymessage->message, gaim_caps);
+	else
+		aim_bos_setprofile(g->oscar_sess, g->oscar_conn, info,
+					NULL, gaim_caps);
+}
+
+static void oscar_set_away(struct gaim_connection *g, char *message) {
+	aim_bos_setprofile(g->oscar_sess, g->oscar_conn, g->user_info, message, gaim_caps);
+}
+
+static void oscar_warn(struct gaim_connection *g, char *name, int anon) {
+	aim_send_warning(g->oscar_sess, g->oscar_conn, name, anon);
+}
+
+static void oscar_dir_search(struct gaim_connection *g, char *first, char *middle, char *last,
+			     char *maiden, char *city, char *state, char *country, char *email) {
+	if (strlen(email))
+		aim_usersearch_address(g->oscar_sess, g->oscar_conn, email);
+}
+
+static void oscar_add_buddy(struct gaim_connection *g, char *name) {
+	aim_add_buddy(g->oscar_sess, g->oscar_conn, name);
+}
+
+static void oscar_add_buddies(struct gaim_connection *g, GList *buddies) {
+	char buf[MSG_LEN];
+	int n = 0;
+	while (buddies) {
+		if (n > MSG_LEN - 18) {
+			aim_bos_setbuddylist(g->oscar_sess, g->oscar_conn, buf);
+			n = 0;
+		}
+		n += g_snprintf(buf + n, sizeof(buf) - n, "%s&", (char *)buddies->data);
+		buddies = buddies->next;
+	}
+	aim_bos_setbuddylist(g->oscar_sess, g->oscar_conn, buf);
+}
+
+static void oscar_remove_buddy(struct gaim_connection *g, char *name) {
+	aim_remove_buddy(g->oscar_sess, g->oscar_conn, name);
+}
+
+static void oscar_join_chat(struct gaim_connection *g, int exchange, char *name) {
+	struct aim_conn_t *cur = NULL;
+	sprintf(debug_buff, "Attempting to join chat room %s.\n", name);
+	debug_print(debug_buff);
+	if ((cur = aim_getconn_type(g->oscar_sess, AIM_CONN_TYPE_CHATNAV))) {
+		debug_print("chatnav exists, creating room\n");
+		aim_chatnav_createroom(g->oscar_sess, cur, name, exchange);
+	} else {
+		/* this gets tricky */
+		debug_print("chatnav does not exist, opening chatnav\n");
+		g->create_exchange = exchange;
+		g->create_name = g_strdup(name);
+		aim_bos_reqservice(g->oscar_sess, g->oscar_conn, AIM_CONN_TYPE_CHATNAV);
 	}
 }
 
-void update_keepalive(struct gaim_connection *gc, gboolean on) {
-	if (on && gc->keepalive < 0 && blist) {
-		debug_print("allowing NOP\n");
-		gc->keepalive = gtk_timeout_add(60000, (GtkFunction)send_keepalive, gc);
-	} else if (!on && gc->keepalive > -1) {
-		debug_print("removing NOP\n");
-		gtk_timeout_remove(gc->keepalive);
-		gc->keepalive = -1;
+static void oscar_chat_invite(struct gaim_connection *g, int id, char *message, char *name) {
+	GSList *bcs = g->buddy_chats;
+	struct conversation *b = NULL;
+
+	while (bcs) {
+		b = (struct conversation *)bcs->data;
+		if (id == b->id)
+			break;
+		bcs = bcs->next;
+		b = NULL;
+	}
+
+	if (!b)
+		return;
+
+	aim_chat_invite(g->oscar_sess, g->oscar_conn, name,
+			message ? message : "", 0x4, b->name, 0x0);
+}
+
+static void oscar_chat_leave(struct gaim_connection *g, int id) {
+	GSList *bcs = g->buddy_chats;
+	struct conversation *b = NULL;
+	struct chat_connection *c = NULL;
+	int count = 0;
+
+	while (bcs) {
+		count++;
+		b = (struct conversation *)bcs->data;
+		if (id == b->id)
+			break;
+		bcs = bcs->next;
+		b = NULL;
+	}
+
+	if (!b)
+		return;
+
+	sprintf(debug_buff, "Attempting to leave room %s (currently in %d rooms)\n",
+				b->name, count);
+	debug_print(debug_buff);
+	
+	c = find_oscar_chat(g, b->name);
+	if (c != NULL) {
+		g->oscar_chats = g_slist_remove(g->oscar_chats, c);
+		gdk_input_remove(c->inpa);
+		if (g && g->oscar_sess)
+			aim_conn_kill(g->oscar_sess, &c->conn);
+		g_free(c->name);
+		g_free(c);
 	}
+	/* we do this because with Oscar it doesn't tell us we left */
+	serv_got_chat_left(g, b->id);
 }
+
+static void oscar_chat_whisper(struct gaim_connection *g, int id, char *who, char *message) {
+	do_error_dialog("Sorry, Oscar doesn't whisper. Send an IM. (The last message was not received.)",
+			"Gaim - Chat");
+}
+
+static void oscar_chat_send(struct gaim_connection *g, int id, char *message) {
+	struct aim_conn_t *cn; 
+	GSList *bcs = g->buddy_chats;
+	struct conversation *b = NULL;
+
+	while (bcs) {
+		b = (struct conversation *)bcs->data;
+		if (id == b->id)
+			break;
+		bcs = bcs->next;
+		b = NULL;
+	}
+	if (!b)
+		return;
+
+	cn = aim_chat_getconn(g->oscar_sess, b->name);
+	aim_chat_send_im(g->oscar_sess, cn, message);
+}
+
+struct prpl *oscar_init() {
+	struct prpl *ret = g_new0(struct prpl, 1);
+
+	ret->protocol = PROTO_OSCAR;
+	ret->name = oscar_name;
+	ret->login = oscar_login;
+	ret->close = oscar_close;
+	ret->send_im = oscar_send_im;
+	ret->set_info = oscar_set_info;
+	ret->get_info = oscar_get_info;
+	ret->set_away = oscar_set_away;
+	ret->get_away_msg = oscar_get_away_msg;
+	ret->set_dir = oscar_set_dir;
+	ret->get_dir = NULL; /* Oscar really doesn't have this */
+	ret->dir_search = oscar_dir_search;
+	ret->set_idle = oscar_set_idle;
+	ret->change_passwd = NULL; /* Oscar doesn't have this either */
+	ret->add_buddy = oscar_add_buddy;
+	ret->add_buddies = oscar_add_buddies;
+	ret->remove_buddy = oscar_remove_buddy;
+	ret->add_permit = NULL; /* Oscar's permit/deny stuff is messed up */
+	ret->add_deny = NULL; /* at least, i can't figure it out :-P */
+	ret->warn = oscar_warn;
+	ret->accept_chat = NULL; /* oscar doesn't have accept, it just joins */
+	ret->join_chat = oscar_join_chat;
+	ret->chat_invite = oscar_chat_invite;
+	ret->chat_leave = oscar_chat_leave;
+	ret->chat_whisper = oscar_chat_whisper;
+	ret->chat_send = oscar_chat_send;
+	ret->keepalive = oscar_keepalive;
+	
+	return ret;
+}
+
+#endif
--- a/src/plugins.c	Thu Oct 12 18:59:36 2000 +0000
+++ b/src/plugins.c	Fri Oct 13 07:24:40 2000 +0000
@@ -408,12 +408,6 @@
 	gaim_plugin_unload(p->handle);
 }
 
-static void remove_callback(struct gaim_plugin *p) {
-	gtk_timeout_remove(p->remove);
-	dlclose(p->handle);
-	g_free(p);
-}
-
 /* gaim_plugin_unload serves 2 purposes: 1. so plugins can unload themselves
  * 					 2. to make my life easier */
 void gaim_plugin_unload(void *handle) {
@@ -456,11 +450,11 @@
 			c = g_list_next(c);
 		}
 	}
-	/* remove callbacks later (this will g_free p) */
-	p->remove = gtk_timeout_add(5000, (GtkFunction)remove_callback, p);
 
 	plugins = g_list_remove(plugins, p);
 	g_free(p->filename);
+	/* we don't dlclose(p->handle) in case if we still need code from the plugin later */
+	g_free(p);
 	if (config) gtk_widget_set_sensitive(config, 0);
 	update_show_plugins();
 	save_prefs();
@@ -504,7 +498,7 @@
 
 void gaim_signal_connect(void *handle, enum gaim_event which,
 			 void *func, void *data) {
-	struct gaim_callback *call = g_malloc(sizeof *call);
+	struct gaim_callback *call = g_new0(struct gaim_callback, 1);
 	call->handle = handle;
 	call->event = which;
 	call->function = func;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/prpl.c	Fri Oct 13 07:24:40 2000 +0000
@@ -0,0 +1,49 @@
+/*
+ * gaim
+ *
+ * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include "prpl.h"
+extern struct prpl *toc_init();
+extern struct prpl *oscar_init();
+
+GSList *protocols = NULL;
+
+struct prpl *find_prpl(int prot)
+{
+	GSList *e = protocols;
+	struct prpl *r;
+
+	while (e) {
+		r = (struct prpl *)e->data;
+		if (r->protocol == prot)
+			return r;
+		e = e->next;
+	}
+
+	return NULL;
+}
+
+void static_proto_init()
+{
+	protocols = g_slist_append(protocols, toc_init());
+#ifndef DYNAMIC_OSCAR
+	protocols = g_slist_append(protocols, oscar_init());
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/prpl.h	Fri Oct 13 07:24:40 2000 +0000
@@ -0,0 +1,87 @@
+/*
+ * gaim
+ *
+ * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef _GAIMPRPL_H_
+#define _GAIMPRPL_H_
+
+#include "multi.h"
+
+#define PROTO_TOC	0
+#define PROTO_OSCAR	1
+#define PROTO_YAHOO	2
+#define PROTO_ICQ	3
+#define PROTO_MSN	4
+#define PROTO_IRC	5
+#define PROTO_FTP	6
+
+struct prpl {
+	int protocol;
+	char *(* name)();
+
+	void (* login)		(struct aim_user *);
+	void (* close)		(struct gaim_connection *);
+	void (* send_im)	(struct gaim_connection *, char *who, char *message, int away);
+	void (* set_info)	(struct gaim_connection *, char *info);
+	void (* get_info)	(struct gaim_connection *, char *who);
+	void (* set_away)	(struct gaim_connection *, char *message);
+	void (* get_away_msg)	(struct gaim_connection *, char *who);
+	void (* set_dir)	(struct gaim_connection *, char *first,
+							   char *middle,
+							   char *last,
+							   char *maiden,
+							   char *city,
+							   char *state,
+							   char *country,
+							   int web);
+	void (* get_dir)	(struct gaim_connection *, char *who);
+	void (* dir_search)	(struct gaim_connection *, char *first,
+							   char *middle,
+							   char *last,
+							   char *maiden,
+							   char *city,
+							   char *state,
+							   char *country,
+							   char *email);
+	void (* set_idle)	(struct gaim_connection *, int idletime);
+	void (* change_passwd)	(struct gaim_connection *, char *old, char *new);
+	void (* add_buddy)	(struct gaim_connection *, char *name);
+	void (* add_buddies)	(struct gaim_connection *, GList *buddies);
+	void (* remove_buddy)	(struct gaim_connection *, char *name);
+	void (* add_permit)	(struct gaim_connection *, char *name);
+	void (* add_deny)	(struct gaim_connection *, char *name);
+	void (* warn)		(struct gaim_connection *, char *who, int anonymous);
+	void (* accept_chat)	(struct gaim_connection *, int id);
+	void (* join_chat)	(struct gaim_connection *, int id, char *name);
+	void (* chat_invite)	(struct gaim_connection *, int id, char *who, char *message);
+	void (* chat_leave)	(struct gaim_connection *, int id);
+	void (* chat_whisper)	(struct gaim_connection *, int id, char *who, char *message);
+	void (* chat_send)	(struct gaim_connection *, int id, char *message);
+
+	void (* keepalive)	(struct gaim_connection *);
+};
+
+extern GSList *protocols;
+
+void static_proto_init();
+
+struct prpl *find_prpl(int);
+
+#endif
--- a/src/server.c	Thu Oct 12 18:59:36 2000 +0000
+++ b/src/server.c	Fri Oct 13 07:24:40 2000 +0000
@@ -32,6 +32,7 @@
 #include <gtk/gtk.h>
 #include <aim.h>
 extern int gaim_caps;
+#include "prpl.h"
 #include "multi.h"
 #include "gaim.h"
 
@@ -42,20 +43,17 @@
 
 void serv_login(struct aim_user *user)
 {
-	if (user->protocol == PROTO_TOC) {
-	        toc_login(user);
-	} else if (user->protocol == PROTO_OSCAR) {
-		debug_print("Logging in using Oscar. Expect problems.\n");
-		oscar_login(user);
+	struct prpl *p = find_prpl(user->protocol);
+	if (p && p->login) {
+		debug_printf("Logging in using %s\n", (*p->name)());
+		(*p->login)(user);
 	}
 }
 
 void serv_close(struct gaim_connection *gc)
 {
-	if (gc->protocol == PROTO_TOC)
-		toc_close(gc);
-	else if (gc->protocol == PROTO_OSCAR)
-		oscar_close(gc);
+	if (gc->prpl && gc->prpl->close)
+		(*gc->prpl->close)(gc);
 
 	account_offline(gc);
 	destroy_gaim_conn(gc);
@@ -133,27 +131,9 @@
 
 void serv_send_im(struct gaim_connection *gc, char *name, char *message, int away)
 {
-	struct conversation *cnv = find_conversation(name);
-	if (cnv && cnv->is_direct && (gc->protocol == PROTO_OSCAR)) {
-			debug_printf("Sending DirectIM to %s\n", name);
-			aim_send_im_direct(gc->oscar_sess, cnv->conn, message);
-	} else {
-		if (gc->protocol == PROTO_TOC) {
-			char buf[MSG_LEN - 7];
+	if (gc->prpl && gc->prpl->send_im)
+		(*gc->prpl->send_im)(gc, name, message, away);
 
-			escape_text(message);
-		        g_snprintf(buf, MSG_LEN - 8, "toc_send_im %s \"%s\"%s", normalize(name),
-		                   message, ((away) ? " auto" : ""));
-			sflap_send(gc, buf, -1, TYPE_DATA);
-		} else if (gc->protocol == PROTO_OSCAR) {
-			if (away)
-				aim_send_im(gc->oscar_sess, gc->oscar_conn,
-						name, AIM_IMFLAGS_AWAY, message);
-			else
-				aim_send_im(gc->oscar_sess, gc->oscar_conn,
-						name, AIM_IMFLAGS_ACK, message);
-		}
-	}
         if (!away)
                 serv_touch_idle(gc);
 }
@@ -163,35 +143,27 @@
 	/* FIXME: getting someone's info? how do you decide something like that? I think that
 	 * the buddy list/UI needs to be really changed before this gets fixed*/
 	struct gaim_connection *g = connections->data;
-	if (g->protocol == PROTO_TOC) {
-        	char buf[MSG_LEN];
-	        g_snprintf(buf, MSG_LEN, "toc_get_info %s", normalize(name));
-	        sflap_send(g, buf, -1, TYPE_DATA);
-	} else if (g->protocol == PROTO_OSCAR) {
-		aim_getinfo(g->oscar_sess, g->oscar_conn, name, AIM_GETINFO_GENERALINFO);
-	}
+
+	if (g->prpl && g->prpl->get_info)
+		(*g->prpl->get_info)(g, name);
 }
 
 void serv_get_away_msg(char *name)
 {
 	/* FIXME: see the serv_get_info comment above :-P */
 	struct gaim_connection *g = connections->data;
-	if (g->protocol == PROTO_TOC) {
-		/* HAHA! TOC doesn't have this yet */
-	} else if (g->protocol == PROTO_OSCAR) {
-		aim_getinfo(g->oscar_sess, g->oscar_conn, name, AIM_GETINFO_AWAYMESSAGE);
-	}
+
+	if (g->prpl && g->prpl->get_info)
+		(*g->prpl->get_info)(g, name);
 }
 
 void serv_get_dir(char *name)
 {
 	/* FIXME: see the serv_get_info comment above :-P */
 	struct gaim_connection *g = connections->data;
-	if (g->protocol == PROTO_TOC) {
-		char buf[MSG_LEN];
-		g_snprintf(buf, MSG_LEN, "toc_get_dir %s", normalize(name));
-		sflap_send(g, buf, -1, TYPE_DATA);
-	}
+
+	if (g->prpl && g->prpl->get_dir)
+		(*g->prpl->get_dir)(g, name);
 }
 
 void serv_set_dir(char *first, char *middle, char *last, char *maiden,
@@ -199,19 +171,9 @@
 {
 	/* FIXME */
 	struct gaim_connection *g = connections->data;
-	if (g->protocol == PROTO_TOC) {
-		char buf2[BUF_LEN*4], buf[BUF_LEN];
-		g_snprintf(buf2, sizeof(buf2), "%s:%s:%s:%s:%s:%s:%s:%s", first,
-			   middle, last, maiden, city, state, country,
-			   (web == 1) ? "Y" : "");
-		escape_text(buf2);
-		g_snprintf(buf, sizeof(buf), "toc_set_dir %s", buf2);
-		sflap_send(g, buf, -1, TYPE_DATA);
-	} else if (g->protocol == PROTO_OSCAR) {
-		/* FIXME : some of these things are wrong, but i'm lazy */
-		aim_setdirectoryinfo(g->oscar_sess, g->oscar_conn, first, middle, last,
-				maiden, NULL, NULL, city, state, NULL, 0, web);
-	}
+
+	if (g->prpl && g->prpl->set_dir)
+		(*g->prpl->set_dir)(g, first, middle, last, maiden, city, state, country, web);
 }
 
 void serv_dir_search(char *first, char *middle, char *last, char *maiden,
@@ -219,16 +181,9 @@
 {
 	/* FIXME */
 	struct gaim_connection *g = connections->data;
-	if (g->protocol == PROTO_TOC) {
-		char buf[BUF_LONG];
-		g_snprintf(buf, sizeof(buf)/2, "toc_dir_search %s:%s:%s:%s:%s:%s:%s:%s", first, middle, last, maiden, city, state, country, email);
-		sprintf(debug_buff,"Searching for: %s,%s,%s,%s,%s,%s,%s\n", first, middle, last, maiden, city, state, country);
-		debug_print(debug_buff);
-		sflap_send(g, buf, -1, TYPE_DATA);
-	} else if (g->protocol == PROTO_OSCAR) {
-		if (strlen(email))
-			aim_usersearch_address(g->oscar_sess, g->oscar_conn, email);
-	}
+
+	if (g->prpl && g->prpl->dir_search)
+		(*g->prpl->dir_search)(g, first, middle, last, maiden, city, state, country, email);
 }
 
 
@@ -240,47 +195,21 @@
 
 	while (c) {
 		g = (struct gaim_connection *)c->data;
-		if (g->protocol == PROTO_TOC) {
-			char buf[MSG_LEN];
-			if (message) {
-				escape_text(message);
-				g_snprintf(buf, MSG_LEN, "toc_set_away \"%s\"", message);
-			} else
-				g_snprintf(buf, MSG_LEN, "toc_set_away \"\"");
-			sflap_send(g, buf, -1, TYPE_DATA);
-		} else if (g->protocol == PROTO_OSCAR) {
-			aim_bos_setprofile(g->oscar_sess, g->oscar_conn, g->user_info, message, gaim_caps);
-		}
+		if (g->prpl && g->prpl->set_away)
+			(*g->prpl->set_away)(g, message);
 		c = c->next;
 	}
 }
 
 void serv_set_info(struct gaim_connection *g, char *info)
 {
-	if (g->protocol == PROTO_TOC) {
-		char buf[MSG_LEN];
-		escape_text(info);
-		g_snprintf(buf, sizeof(buf), "toc_set_info \"%s\n\"", info);
-		sflap_send(g, buf, -1, TYPE_DATA);
-	} else if (g->protocol == PROTO_OSCAR) {
-		if (awaymessage)
-			aim_bos_setprofile(g->oscar_sess, g->oscar_conn, info,
-						awaymessage->message, gaim_caps);
-		else
-			aim_bos_setprofile(g->oscar_sess, g->oscar_conn, info,
-						NULL, gaim_caps);
-	}
+	if (g->prpl && g->prpl->set_info)
+		(*g->prpl->set_info)(g, info);
 }
 
 void serv_change_passwd(struct gaim_connection *g, char *orig, char *new) {
-	if (g->protocol == PROTO_TOC) {
-		char *buf = g_malloc(BUF_LONG); 
-		g_snprintf(buf, BUF_LONG, "toc_change_passwd %s %s", orig, new);
-		sflap_send(g, buf, strlen(buf), TYPE_DATA);
-		g_free(buf);
-	} else if (g->protocol == PROTO_OSCAR) {
-		/* Oscar change_passwd FIXME */
-	}
+	if (g->prpl && g->prpl->change_passwd)
+		(*g->prpl->change_passwd)(g, orig, new);
 }
 
 void serv_add_buddy(char *name)
@@ -292,13 +221,8 @@
 
 	while (c) {
 		g = (struct gaim_connection *)c->data;
-		if (g->protocol == PROTO_TOC) {
-			char buf[1024];
-			g_snprintf(buf, sizeof(buf), "toc_add_buddy %s", normalize(name));
-			sflap_send(g, buf, -1, TYPE_DATA);
-		} else if (g->protocol == PROTO_OSCAR) {
-			aim_add_buddy(g->oscar_sess, g->oscar_conn, name);
-		}
+		if (g->prpl && g->prpl->add_buddy)
+			(*g->prpl->add_buddy)(g, name);
 		c = c->next;
 	}
 }
@@ -311,37 +235,8 @@
 
 	while (c) {
 		g = (struct gaim_connection *)c->data;
-		if (g->protocol == PROTO_TOC) {
-			char buf[MSG_LEN];
-			int n, num = 0;
-
-			n = g_snprintf(buf, sizeof(buf), "toc_add_buddy");
-			while(buddies) {
-				/* i don't know why we choose 8, it just seems good */
-				if (strlen(normalize(buddies->data)) > MSG_LEN - n - 8) {
-					sflap_send(g, buf, -1, TYPE_DATA);
-					n = g_snprintf(buf, sizeof(buf), "toc_add_buddy");
-					num = 0;
-				}
-				++num;
-				n += g_snprintf(buf + n, sizeof(buf)-n, " %s", normalize(buddies->data));
-				buddies = buddies->next;
-			}
-			sflap_send(g, buf, -1, TYPE_DATA);
-		} else if (g->protocol == PROTO_OSCAR) {
-			char buf[MSG_LEN];
-			int n = 0;
-			while(buddies) {
-				if (n > MSG_LEN - 18) {
-					aim_bos_setbuddylist(g->oscar_sess, g->oscar_conn, buf);
-					n = 0;
-				}
-				n += g_snprintf(buf + n, sizeof(buf) - n, "%s&",
-						(char *)buddies->data);
-				buddies = buddies->next;
-			}
-			aim_bos_setbuddylist(g->oscar_sess, g->oscar_conn, buf);
-		}
+		if (g->prpl && g->prpl->add_buddies)
+			(*g->prpl->add_buddies)(g, buddies);
 		c = c->next;
 	}
 }
@@ -355,13 +250,8 @@
 
 	while (c) {
 		g = (struct gaim_connection *)c->data;
-		if (g->protocol == PROTO_TOC) {
-			char buf[1024];
-			g_snprintf(buf, sizeof(buf), "toc_remove_buddy %s", normalize(name));
-			sflap_send(g, buf, -1, TYPE_DATA);
-		} else if (g->protocol == PROTO_OSCAR) {
-			aim_remove_buddy(g->oscar_sess, g->oscar_conn, name);
-		}
+		if (g->prpl && g->prpl->remove_buddy)
+			(*g->prpl->remove_buddy)(g, name);
 		c = c->next;
 	}
 }
@@ -481,27 +371,15 @@
 
 void serv_set_idle(struct gaim_connection *g, int time)
 {
-	if (g->protocol == PROTO_TOC) {
-		char buf[256];
-		g_snprintf(buf, sizeof(buf), "toc_set_idle %d", time);
-		sflap_send(g, buf, -1, TYPE_DATA);
-	} else if (g->protocol == PROTO_OSCAR) {
-		aim_bos_setidle(g->oscar_sess, g->oscar_conn, time);
-	}
+	if (g->prpl && g->prpl->set_idle)
+		(*g->prpl->set_idle)(g, time);
 }
 
 
 void serv_warn(struct gaim_connection *g, char *name, int anon)
 {
-	if (g->protocol == PROTO_TOC) {
-		char *send = g_malloc(256);
-		g_snprintf(send, 255, "toc_evil %s %s", name,
-			   ((anon) ? "anon" : "norm"));
-		sflap_send(g, send, -1, TYPE_DATA);
-		g_free(send);
-	} else if (g->protocol == PROTO_OSCAR) {
-		aim_send_warning(g->oscar_sess, g->oscar_conn, name, anon);
-	}
+	if (g->prpl && g->prpl->warn)
+		(*g->prpl->warn)(g, name, anon);
 }
 
 void serv_build_config(char *buf, int len, gboolean show) {
@@ -527,146 +405,38 @@
 
 void serv_accept_chat(struct gaim_connection *g, int i)
 {
-	if (g->protocol == PROTO_TOC) {
-	        char *buf = g_malloc(256);
-	        g_snprintf(buf, 255, "toc_chat_accept %d",  i);
-	        sflap_send(g, buf, -1, TYPE_DATA);
-	        g_free(buf);
-	} else if (g->protocol == PROTO_OSCAR) {
-		/* this should never get called because libfaim doesn't use the id
-		 * (i'm not even sure Oscar does). go through serv_join_chat instead */
-	}
+	if (g->prpl && g->prpl->accept_chat)
+		(*g->prpl->accept_chat)(g, i);
 }
 
 void serv_join_chat(struct gaim_connection *g, int exchange, char *name)
 {
-	if (g->protocol == PROTO_TOC) {
-	        char buf[BUF_LONG];
-	        g_snprintf(buf, sizeof(buf)/2, "toc_chat_join %d \"%s\"", exchange, name);
-	        sflap_send(g, buf, -1, TYPE_DATA);
-	} else if (g->protocol == PROTO_OSCAR) {
-		struct aim_conn_t *cur = NULL;
-		sprintf(debug_buff, "Attempting to join chat room %s.\n", name);
-		debug_print(debug_buff);
-		if ((cur = aim_getconn_type(g->oscar_sess, AIM_CONN_TYPE_CHATNAV))) {
-			debug_print("chatnav exists, creating room\n");
-			aim_chatnav_createroom(g->oscar_sess, cur, name, exchange);
-		} else {
-			/* this gets tricky */
-			debug_print("chatnav does not exist, opening chatnav\n");
-			g->create_exchange = exchange;
-			g->create_name = g_strdup(name);
-			aim_bos_reqservice(g->oscar_sess, g->oscar_conn, AIM_CONN_TYPE_CHATNAV);
-		}
-	}
+	if (g->prpl && g->prpl->join_chat)
+		(*g->prpl->join_chat)(g, exchange, name);
 }
 
 void serv_chat_invite(struct gaim_connection *g, int id, char *message, char *name)
 {
-	if (g->protocol == PROTO_TOC) {
-	        char buf[BUF_LONG];
-	        g_snprintf(buf, sizeof(buf)/2, "toc_chat_invite %d \"%s\" %s", id, message, normalize(name));
-	        sflap_send(g, buf, -1, TYPE_DATA);
-	} else if (g->protocol == PROTO_OSCAR) {
-		GSList *bcs = g->buddy_chats;
-		struct conversation *b = NULL;
-
-		while (bcs) {
-			b = (struct conversation *)bcs->data;
-			if (id == b->id)
-				break;
-			bcs = bcs->next;
-			b = NULL;
-		}
-
-		if (!b)
-			return;
-		
-		aim_chat_invite(g->oscar_sess, g->oscar_conn, name,
-				message ? message : "", 0x4, b->name, 0x0);
-	}
+	if (g->prpl && g->prpl->chat_invite)
+		(*g->prpl->chat_invite)(g, id, message, name);
 }
 
 void serv_chat_leave(struct gaim_connection *g, int id)
 {
-	if (g->protocol == PROTO_TOC) {
-	        char *buf = g_malloc(256);
-	        g_snprintf(buf, 255, "toc_chat_leave %d",  id);
-	        sflap_send(g, buf, -1, TYPE_DATA);
-	        g_free(buf);
-	} else if (g->protocol == PROTO_OSCAR) {
-		GSList *bcs = g->buddy_chats;
-		struct conversation *b = NULL;
-		struct chat_connection *c = NULL;
-		int count = 0;
-
-		while (bcs) {
-			count++;
-			b = (struct conversation *)bcs->data;
-			if (id == b->id)
-				break;
-			bcs = bcs->next;
-			b = NULL;
-		}
-
-		if (!b)
-			return;
-
-		sprintf(debug_buff, "Attempting to leave room %s (currently in %d rooms)\n",
-					b->name, count);
-		debug_print(debug_buff);
-
-		c = find_oscar_chat(g, b->name);
-		if (c != NULL) {
-			g->oscar_chats = g_slist_remove(g->oscar_chats, c);
-			gdk_input_remove(c->inpa);
-			if (g && g->oscar_sess)
-				aim_conn_kill(g->oscar_sess, &c->conn);
-			g_free(c->name);
-			g_free(c);
-		}
-		/* we do this because with Oscar it doesn't tell us we left */
-		serv_got_chat_left(g, b->id);
-	}
+	if (g->prpl && g->prpl->chat_leave)
+		(*g->prpl->chat_leave)(g, id);
 }
 
 void serv_chat_whisper(struct gaim_connection *g, int id, char *who, char *message)
 {
-	if (g->protocol == PROTO_TOC) {
-	        char buf2[MSG_LEN];
-	        g_snprintf(buf2, sizeof(buf2), "toc_chat_whisper %d %s \"%s\"", id, who, message);
-	        sflap_send(g, buf2, -1, TYPE_DATA);
-	} else if (g->protocol == PROTO_OSCAR) {
-		do_error_dialog("Sorry, Oscar doesn't whisper. Send an IM. (The last message was not received.)",
-				"Gaim - Chat");
-	}
+	if (g->prpl && g->prpl->chat_whisper)
+		(*g->prpl->chat_whisper)(g, id, who, message);
 }
 
 void serv_chat_send(struct gaim_connection *g, int id, char *message)
 {
-	if (g->protocol == PROTO_TOC) {
-	        char buf[MSG_LEN];
-		escape_text(message);
-	        g_snprintf(buf, sizeof(buf), "toc_chat_send %d \"%s\"",id, message);
-	        sflap_send(g, buf, -1, TYPE_DATA);
-	} else if (g->protocol == PROTO_OSCAR) {
-		struct aim_conn_t *cn;
-		GSList *bcs = g->buddy_chats;
-		struct conversation *b = NULL;
-
-		while (bcs) {
-			b = (struct conversation *)bcs->data;
-			if (id == b->id)
-				break;
-			bcs = bcs->next;
-			b = NULL;
-		}
-		if (!b)
-			return;
-
-		cn = aim_chat_getconn(g->oscar_sess, b->name);
-		aim_chat_send_im(g->oscar_sess, cn, message);
-	}
+	if (g->prpl && g->prpl->chat_send)
+		(*g->prpl->chat_send)(g, id, message);
 	serv_touch_idle(g);
 }
 
@@ -758,6 +528,7 @@
 		/* apply default fonts and colors */
 		tmpmsg = stylize(awaymessage->message, MSG_LEN);
 		
+		/* PRPL */
 		if (gc->protocol == PROTO_TOC) {
 			escape_text(tmpmsg);
 			escape_message(tmpmsg);
@@ -1088,7 +859,9 @@
 	if (cnv->gc->protocol == PROTO_TOC) {
 		/* Direct IM TOC FIXME */
 	} else if (cnv->gc->protocol == PROTO_OSCAR) {
+		/* PRPL
 		oscar_do_directim(cnv->gc, name);
+		*/
 	}
 }
 
@@ -1103,3 +876,21 @@
 		make_direct(cnv, TRUE, conn, watcher);
 	}
 }
+
+void send_keepalive(gpointer d) {
+	struct gaim_connection *gc = (struct gaim_connection *)d;
+	debug_print("sending oscar NOP\n");
+	if (gc->prpl && gc->prpl->keepalive)
+		(*gc->prpl->keepalive)(gc);
+}
+
+void update_keepalive(struct gaim_connection *gc, gboolean on) {
+	if (on && gc->keepalive < 0 && blist) {
+		debug_print("allowing NOP\n");
+		gc->keepalive = gtk_timeout_add(60000, (GtkFunction)send_keepalive, gc);
+	} else if (!on && gc->keepalive > -1) {
+		debug_print("removing NOP\n");
+		gtk_timeout_remove(gc->keepalive);
+		gc->keepalive = -1;
+	}
+}
--- a/src/toc.c	Thu Oct 12 18:59:36 2000 +0000
+++ b/src/toc.c	Fri Oct 13 07:24:40 2000 +0000
@@ -35,11 +35,12 @@
 #include <stdio.h>
 #include <time.h>
 #include <sys/socket.h>
+#include "prpl.h"
 #include "multi.h"
 #include "gaim.h"
 #include "gnome_applet_mgr.h"
 
-#define REVISION "gaim:$Revision: 990 $"
+#define REVISION "gaim:$Revision: 991 $"
 
 
 static unsigned int peer_ver=0;
@@ -932,3 +933,188 @@
 		do_export( (GtkWidget *) NULL, 0 );	
 	}
  }
+
+static char *toc_name() {
+	return "TOC";
+}
+
+static void toc_send_im(struct gaim_connection *gc, char *name, char *message, int away) {
+	char buf[MSG_LEN - 7];
+
+	escape_text(message);
+	g_snprintf(buf, MSG_LEN - 8, "toc_send_im %s \"%s\"%s", normalize(name),
+			message, ((away) ? " auto" : ""));
+	sflap_send(gc, buf, -1, TYPE_DATA);
+}
+
+static void toc_get_info(struct gaim_connection *g, char *name) {
+	char buf[MSG_LEN];
+	g_snprintf(buf, MSG_LEN, "toc_get_info %s", normalize(name));
+	sflap_send(g, buf, -1, TYPE_DATA);
+}
+
+static void toc_get_dir(struct gaim_connection *g, char *name) {
+	char buf[MSG_LEN];
+	g_snprintf(buf, MSG_LEN, "toc_get_dir %s", normalize(name));
+	sflap_send(g, buf, -1, TYPE_DATA);
+}
+
+static void toc_set_dir(struct gaim_connection *g, char *first, char *middle, char *last,
+			char *maiden, char *city, char *state, char *country, int web) {
+	char buf2[BUF_LEN*4], buf[BUF_LEN];
+	g_snprintf(buf2, sizeof(buf2), "%s:%s:%s:%s:%s:%s:%s:%s", first,
+			middle, last, maiden, city, state, country,
+			(web == 1) ? "Y" : "");
+	escape_text(buf2);
+	g_snprintf(buf, sizeof(buf), "toc_set_dir %s", buf2);
+	sflap_send(g, buf, -1, TYPE_DATA);
+}
+
+static void toc_dir_search(struct gaim_connection *g, char *first, char *middle, char *last,
+			char *maiden, char *city, char *state, char *country, char *email) {
+	char buf[BUF_LONG];
+	g_snprintf(buf, sizeof(buf)/2, "toc_dir_search %s:%s:%s:%s:%s:%s:%s:%s", first, middle,
+			last, maiden, city, state, country, email);
+	sprintf(debug_buff,"Searching for: %s,%s,%s,%s,%s,%s,%s\n", first, middle, last, maiden,
+			city, state, country);
+	debug_print(debug_buff);
+	sflap_send(g, buf, -1, TYPE_DATA);
+}
+
+static void toc_set_away(struct gaim_connection *g, char *message) {
+	char buf[MSG_LEN];
+	if (message) {
+		escape_text(message);
+		g_snprintf(buf, MSG_LEN, "toc_set_away \"%s\"", message);
+	} else
+		g_snprintf(buf, MSG_LEN, "toc_set_away \"\"");
+	sflap_send(g, buf, -1, TYPE_DATA);
+}
+
+static void toc_set_info(struct gaim_connection *g, char *info) {
+	char buf[MSG_LEN];
+	escape_text(info);
+	g_snprintf(buf, sizeof(buf), "toc_set_info \"%s\n\"", info);
+	sflap_send(g, buf, -1, TYPE_DATA);
+}
+
+static void toc_change_passwd(struct gaim_connection *g, char *orig, char *new) {
+	char buf[MSG_LEN];
+	g_snprintf(buf, BUF_LONG, "toc_change_passwd %s %s", orig, new);
+	sflap_send(g, buf, strlen(buf), TYPE_DATA);
+}
+
+static void toc_add_buddy(struct gaim_connection *g, char *name) {
+	char buf[1024]; 
+	g_snprintf(buf, sizeof(buf), "toc_add_buddy %s", normalize(name));
+	sflap_send(g, buf, -1, TYPE_DATA);
+}
+
+static void toc_add_buddies(struct gaim_connection *g, GList *buddies) {
+	char buf[MSG_LEN];
+	int n;
+
+	n = g_snprintf(buf, sizeof(buf), "toc_add_buddy");
+	while (buddies) {
+		if (strlen(normalize(buddies->data)) > MSG_LEN - n - 16) {
+			sflap_send(g, buf, -1, TYPE_DATA);
+			n = g_snprintf(buf, sizeof(buf), "toc_add_buddy");
+		}
+		n += g_snprintf(buf + n, sizeof(buf)-n, " %s", normalize(buddies->data));
+		buddies = buddies->next;
+	}
+	sflap_send(g, buf, -1, TYPE_DATA);
+}
+
+static void toc_remove_buddy(struct gaim_connection *g, char *name) {
+	char buf[1024]; 
+	g_snprintf(buf, sizeof(buf), "toc_remove_buddy %s", normalize(name));
+	sflap_send(g, buf, -1, TYPE_DATA);
+}
+
+static void toc_set_idle(struct gaim_connection *g, int time) {
+	char buf[256];
+	g_snprintf(buf, sizeof(buf), "toc_set_idle %d", time);
+	sflap_send(g, buf, -1, TYPE_DATA);
+}
+
+static void toc_warn(struct gaim_connection *g, char *name, int anon) {
+	char send[256];
+	g_snprintf(send, 255, "toc_evil %s %s", name, ((anon) ? "anon" : "norm"));
+	sflap_send(g, send, -1, TYPE_DATA);
+}
+
+static void toc_accept_chat(struct gaim_connection *g, int i) {
+	char buf[256];
+	g_snprintf(buf, 255, "toc_chat_accept %d",  i);
+	sflap_send(g, buf, -1, TYPE_DATA);
+}
+
+static void toc_join_chat(struct gaim_connection *g, int exchange, char *name) {
+	char buf[BUF_LONG];
+	g_snprintf(buf, sizeof(buf)/2, "toc_chat_join %d \"%s\"", exchange, name);
+	sflap_send(g, buf, -1, TYPE_DATA);
+}
+
+static void toc_chat_invite(struct gaim_connection *g, int id, char *message, char *name) {
+	char buf[BUF_LONG];
+	g_snprintf(buf, sizeof(buf)/2, "toc_chat_invite %d \"%s\" %s", id, message, normalize(name));
+	sflap_send(g, buf, -1, TYPE_DATA);
+}
+
+static void toc_chat_leave(struct gaim_connection *g, int id) {
+	char buf[256];
+	g_snprintf(buf, 255, "toc_chat_leave %d",  id);
+	sflap_send(g, buf, -1, TYPE_DATA);
+}
+
+static void toc_chat_whisper(struct gaim_connection *g, int id, char *who, char *message) {
+	char buf2[MSG_LEN];
+	g_snprintf(buf2, sizeof(buf2), "toc_chat_whisper %d %s \"%s\"", id, who, message);
+	sflap_send(g, buf2, -1, TYPE_DATA);
+}
+
+static void toc_chat_send(struct gaim_connection *g, int id, char *message) {
+	char buf[MSG_LEN];
+	escape_text(message);
+	g_snprintf(buf, sizeof(buf), "toc_chat_send %d \"%s\"",id, message);
+	sflap_send(g, buf, -1, TYPE_DATA);
+}
+
+static void toc_keepalive(struct gaim_connection *gc) {
+	sflap_send(gc, "", 0, TYPE_KEEPALIVE);
+}
+
+struct prpl *toc_init() {
+        struct prpl *ret = g_new0(struct prpl, 1);
+
+        ret->protocol = PROTO_TOC;
+        ret->name = toc_name;
+        ret->login = toc_login;
+        ret->close = toc_close;
+        ret->send_im = toc_send_im;
+        ret->set_info = toc_set_info;
+        ret->get_info = toc_get_info;
+        ret->set_away = toc_set_away;
+        ret->get_away_msg = NULL;
+        ret->set_dir = toc_set_dir;
+        ret->get_dir = toc_get_dir;
+        ret->dir_search = toc_dir_search;
+        ret->set_idle = toc_set_idle;
+        ret->change_passwd = toc_change_passwd;
+        ret->add_buddy = toc_add_buddy;
+        ret->add_buddies = toc_add_buddies;
+        ret->remove_buddy = toc_remove_buddy;
+        ret->add_permit = NULL; /* FIXME */
+        ret->add_deny = NULL;
+        ret->warn = toc_warn;
+        ret->accept_chat = toc_accept_chat;
+        ret->join_chat = toc_join_chat;
+        ret->chat_invite = toc_chat_invite;
+        ret->chat_leave = toc_chat_leave;
+        ret->chat_whisper = toc_chat_whisper;
+        ret->chat_send = toc_chat_send;
+	ret->keepalive = toc_keepalive;
+
+        return ret;
+}