changeset 2393:a7ecfd3f7714

[gaim-migrate @ 2406] Arkadiusz Miskiewicz\'s Gadu-Gadu plugin. I was able to figure out enough polish to be able to download Gadu-Gadu, create an account, and test the plugin. Imagine my shock when I got my info and it said I was a woman. Whoops. Also splitting plugins.c so that non-gtk stuff is in modules.c. gaim-core is almost ready for protocol implantaion. Also fixing an IRC bug. Also patiently waiting for anoncvs_gaim's lock in /cvsroot/gaim/gaim/pixmaps committer: Tailor Script <tailor@pidgin.im>
author Eric Warmenhoven <eric@warmenhoven.org>
date Sat, 29 Sep 2001 23:06:30 +0000
parents 9965c0bbdb7c
children 579f8d4347ad
files ChangeLog acconfig.h configure.ac doc/CREDITS pixmaps/Makefile.am pixmaps/gg_suncloud.xpm pixmaps/gg_sunred.xpm pixmaps/gg_sunwhitered.xpm pixmaps/gg_sunyellow.xpm po/POTFILES.in src/Makefile.am src/buddy.c src/dialogs.c src/gaim.h src/module.c src/plugins.c src/protocols/Makefile.am src/protocols/gg/.cvsignore src/protocols/gg/Makefile.am src/protocols/gg/gg.c src/protocols/gg/libgg.c src/protocols/gg/libgg.h src/protocols/gg/protocol.txt src/protocols/irc/irc.c
diffstat 24 files changed, 3574 insertions(+), 517 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat Sep 29 02:08:00 2001 +0000
+++ b/ChangeLog	Sat Sep 29 23:06:30 2001 +0000
@@ -11,6 +11,7 @@
 	* Nick Highlighting in chat
 	* Tab-completion for nicks in chat (thanks to Sean Egan)
 	* Large internal reworkings
+	* New Protocol: Gadu-Gadu, written by Arkadiusz Miskiewicz
 
 version 0.44 (09/20/2001):
 	* More sane scaling of buddy icons (intelligently scale to
--- a/acconfig.h	Sat Sep 29 02:08:00 2001 +0000
+++ b/acconfig.h	Sat Sep 29 23:06:30 2001 +0000
@@ -17,7 +17,6 @@
 #undef ARTSC_SOUND
 #undef _REENTRANT
 #undef NEED_GNOMESUPPORT_H
-#undef NEED_SOCKLEN_T
 #undef ZEPHYR_INT32
 #undef ZEPHYR_USES_KERBEROS
 #ifndef STATIC_PROTO_INIT
--- a/configure.ac	Sat Sep 29 02:08:00 2001 +0000
+++ b/configure.ac	Sat Sep 29 23:06:30 2001 +0000
@@ -32,8 +32,6 @@
 AC_TYPE_SIGNAL
 AC_FUNC_STRFTIME
 AC_CHECK_FUNCS(socket strdup strstr atexit getaddrinfo)
-AC_TRY_COMPILE([#include <sys/types.h>
-#include <sys/socket.h>], [socklen_t slen;],,[AC_DEFINE(NEED_SOCKLEN_T)])
 
 dnl Checks for getopt in standard library
 AC_CHECK_FUNCS(getopt_long , , [LIBOBJS="$LIBOBJS getopt.o getopt1.o"] ) 
@@ -48,7 +46,7 @@
 AC_ARG_ENABLE(prpls,   [  --disable-prpls         don't build dynamic protocol plugins],,enable_prpls=yes)
 AC_ARG_WITH(static-prpls,    [  --with-static-prpls     link in certain protocols statically],[STATIC_PRPLS=`echo $withval | $sedpath 's/,/ /g'`],STATIC_PRPLS="oscar toc")
 if test "x$STATIC_PRPLS" = "xall" ; then
-	STATIC_PRPLS="icq irc jabber msn napster oscar toc yahoo zephyr"
+	STATIC_PRPLS="gg icq irc jabber msn napster oscar toc yahoo zephyr"
 fi
 AC_SUBST(STATIC_PRPLS)
 STATIC_LINK_LIBS=
@@ -59,6 +57,7 @@
 	extern_init="$extern_init extern void ${i}_init(struct prpl *);"
 	load_proto="$load_proto load_protocol(${i}_init, sizeof(struct prpl));"
 	case $i in
+		gg) static_gg=yes ;;
 		icq) static_icq=yes ;;
 		irc) static_irc=yes ;;
 		jabber) static_jabber=yes ;;
@@ -71,6 +70,7 @@
 		*) echo "Invalid static protocol $i!!" ; exit ;;
 	esac
 done
+AM_CONDITIONAL(STATIC_GG, test "x$static_gg" = "xyes")
 AM_CONDITIONAL(STATIC_ICQ, test "x$static_icq" = "xyes")
 AM_CONDITIONAL(STATIC_IRC, test "x$static_irc" = "xyes")
 AM_CONDITIONAL(STATIC_JABBER, test "x$static_jabber" = "xyes")
@@ -358,6 +358,7 @@
            sounds/Makefile
 	   src/Makefile
 	   src/protocols/Makefile
+	   src/protocols/gg/Makefile
 	   src/protocols/icq/Makefile
 	   src/protocols/irc/Makefile
 	   src/protocols/jabber/Makefile
--- a/doc/CREDITS	Sat Sep 29 02:08:00 2001 +0000
+++ b/doc/CREDITS	Sat Sep 29 23:06:30 2001 +0000
@@ -36,6 +36,8 @@
 	A healthy amount of patches for the Jabber plugin
 Neil Sanchala
 	Wrote most of the Zephyr plugin
+Arkadiusz Miskiewicz
+	Wrote the Gadu-Gadu plugin
 	
 David Prater    <IM: dRaven43>          draven@tcsx.net   
 	Log and Colour Button Images
--- a/pixmaps/Makefile.am	Sat Sep 29 02:08:00 2001 +0000
+++ b/pixmaps/Makefile.am	Sat Sep 29 23:06:30 2001 +0000
@@ -34,6 +34,10 @@
 		fontface2.xpm			\
 		free_icon.xpm			\
 		gaim.xpm			\
+		gg_suncloud.xpm			\
+		gg_sunred.xpm			\
+		gg_sunwhitered.xpm		\
+		gg_sunyellow.xpm		\
 		gnome_add.xpm			\
 		gnome_preferences.xpm		\
 		gnome_remove.xpm 		\
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pixmaps/gg_suncloud.xpm	Sat Sep 29 23:06:30 2001 +0000
@@ -0,0 +1,28 @@
+/* XPM */
+static char * gg_suncloud_xpm[] = {
+"18 18 7 1",
+" 	c None",
+".	c #FF0000",
+"+	c #7F7F7F",
+"@	c #FFFF00",
+"#	c #000000",
+"$	c #00007F",
+"%	c #00FFFF",
+"                  ",
+"        .         ",
+"        .+        ",
+"   .    .+   .    ",
+"    . ..@.. .+    ",
+"     .@@@@@.+     ",
+"    .@@#@#@@.+    ",
+"    .@@@@@@@.+    ",
+" ...@$@@@@@@@...  ",
+"   $$%$@@@#@$$$++ ",
+"  $%%%%$##@$%%%$  ",
+" $%%%%%%$$$%%%%$  ",
+" $%%%%%%%%%%%%%%$ ",
+" $%%%%%%%%%%%%%$  ",
+"  $%%%%%%%%%%%%$  ",
+"  $%%%$$$%%%$$$   ",
+"   $$$   $$$      ",
+"                  "};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pixmaps/gg_sunred.xpm	Sat Sep 29 23:06:30 2001 +0000
@@ -0,0 +1,25 @@
+/* XPM */
+static char * gg_sunred_xpm[] = {
+"19 18 4 1",
+" 	c None",
+".	c #FF0000",
+"+	c #7F7F7F",
+"@	c #000000",
+"                   ",
+"         .         ",
+"         .+        ",
+"    .    .+   .    ",
+"     . ..... .+    ",
+"      .......+     ",
+"     ...@.@...+    ",
+"     .........+    ",
+"  ...............  ",
+"     ..@...@..++++ ",
+"     ...@@@...+    ",
+"      .......+     ",
+"     .+..... .+    ",
+"    .+   .++  .+   ",
+"    +    .+        ",
+"         .+        ",
+"          +        ",
+"                   "};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pixmaps/gg_sunwhitered.xpm	Sat Sep 29 23:06:30 2001 +0000
@@ -0,0 +1,27 @@
+/* XPM */
+static char * gg_sunwhitered_xpm[] = {
+"19 18 6 1",
+" 	c None",
+".	c #7F0000",
+"+	c #FF0000",
+"@	c #7F7F7F",
+"#	c #FFFFFF",
+"$	c #000000",
+"                   ",
+"         .         ",
+"         +@        ",
+"    +    .@   +    ",
+"     . .+#+. .@    ",
+"      +#####+@     ",
+"     .##$#$##.@    ",
+"     +#######+@    ",
+"  .+.#########.+.  ",
+"     +#$###$#+@@@@ ",
+"     .##$$$##.@    ",
+"      +#####+@     ",
+"     .@.+#+. .@    ",
+"    +@   .@@  +@   ",
+"    @    +@        ",
+"         .@        ",
+"          @        ",
+"                   "};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pixmaps/gg_sunyellow.xpm	Sat Sep 29 23:06:30 2001 +0000
@@ -0,0 +1,26 @@
+/* XPM */
+static char * gg_sunyellow_xpm[] = {
+"18 18 5 1",
+" 	c None",
+".	c #FF0000",
+"+	c #7F7F7F",
+"@	c #FFFF00",
+"#	c #000000",
+"                  ",
+"        .         ",
+"        .+        ",
+"   .    .+   .    ",
+"    . ..@.. .+    ",
+"     .@@@@@.+     ",
+"    .@@#@#@@.+    ",
+"    .@@@@@@@.+    ",
+" ...@@@@@@@@@...  ",
+"    .@#@@@#@.++++ ",
+"    .@@###@@.+    ",
+"     .@@@@@.+     ",
+"    .+..@.. .+    ",
+"   .+   .++  .+   ",
+"   +    .+        ",
+"        .+        ",
+"         +        ",
+"                  "};
--- a/po/POTFILES.in	Sat Sep 29 02:08:00 2001 +0000
+++ b/po/POTFILES.in	Sat Sep 29 23:06:30 2001 +0000
@@ -1,3 +1,4 @@
+src/protocols/gg/gg.c
 src/protocols/icq/gaim_icq.c
 src/protocols/irc/irc.c
 src/protocols/jabber/jabber.c
--- a/src/Makefile.am	Sat Sep 29 02:08:00 2001 +0000
+++ b/src/Makefile.am	Sat Sep 29 23:06:30 2001 +0000
@@ -18,6 +18,7 @@
 			html.c \
 			idle.c \
 			list.c \
+			module.c \
 			multi.c \
 			perl.c \
 			plugins.c \
@@ -49,6 +50,7 @@
 		html.c \
 		idle.c \
 		list.c \
+		module.c \
 		multi.c \
 		perl.c \
 		plugins.c \
--- a/src/buddy.c	Sat Sep 29 02:08:00 2001 +0000
+++ b/src/buddy.c	Sat Sep 29 23:06:30 2001 +0000
@@ -2389,7 +2389,8 @@
 	gtk_widget_show(menuitem);
 	menuitem = gtk_menu_item_new_with_label(_("by Dir Info"));
 	gtk_menu_append(GTK_MENU(findmenu), menuitem);
-	gtk_signal_connect(GTK_OBJECT(menuitem), "activate", GTK_SIGNAL_FUNC(show_find_info), NULL);
+	gtk_signal_connect(GTK_OBJECT(menuitem), "activate", GTK_SIGNAL_FUNC(show_find_info),
+			   connections->data);
 	gtk_widget_show(menuitem);
 
 	setmenu = gtk_menu_new();
--- a/src/dialogs.c	Sat Sep 29 02:08:00 2001 +0000
+++ b/src/dialogs.c	Sat Sep 29 23:06:30 2001 +0000
@@ -150,6 +150,7 @@
 };
 
 struct findbyinfo {
+	struct gaim_connection *gc;
 	GtkWidget *window;
 	GtkWidget *firstentry;
 	GtkWidget *middleentry;
@@ -2058,10 +2059,7 @@
 	state = gtk_entry_get_text(GTK_ENTRY(b->stateentry));
 	country = gtk_entry_get_text(GTK_ENTRY(b->countryentry));
 
-	/* FIXME : dir search. not sure if even works; not important */
-	if (connections)
-		serv_dir_search(connections->data, first, middle, last, maiden, city, state, country,
-				"");
+	serv_dir_search(b->gc, first, middle, last, maiden, city, state, country, "");
 	destroy_dialog(NULL, b->window);
 }
 
@@ -2079,7 +2077,7 @@
 	destroy_dialog(NULL, b->window);
 }
 
-void show_find_info()
+void show_find_info(struct gaim_connection *gc)
 {
 	GtkWidget *cancel;
 	GtkWidget *ok;
@@ -2091,6 +2089,7 @@
 	GtkWidget *frame;
 
 	struct findbyinfo *b = g_new0(struct findbyinfo, 1);
+	b->gc = gc;
 	b->window = gtk_window_new(GTK_WINDOW_DIALOG);
 	gtk_window_set_policy(GTK_WINDOW(b->window), FALSE, TRUE, TRUE);
 	gtk_window_set_wmclass(GTK_WINDOW(b->window), "find_info", "Gaim");
--- a/src/gaim.h	Sat Sep 29 02:08:00 2001 +0000
+++ b/src/gaim.h	Sat Sep 29 23:06:30 2001 +0000
@@ -807,7 +807,9 @@
 /* Functions in plugins.c */
 #ifdef GAIM_PLUGINS
 extern void show_plugins(GtkWidget *, gpointer);
-extern void load_plugin (char *);
+extern struct gaim_plugin *load_plugin(char *);
+extern void unload_plugin(struct gaim_plugin *);
+extern struct gaim_plugin *reload_plugin(struct gaim_plugin *);
 extern void gaim_signal_connect(GModule *, enum gaim_event, void *, void *);
 extern void gaim_signal_disconnect(GModule *, enum gaim_event, void *);
 extern void gaim_plugin_unload(GModule *);
@@ -857,8 +859,8 @@
 extern void show_new_bp();
 extern void show_log(char *);
 extern void show_log_dialog(struct conversation *);
-extern void show_find_email(struct gaim_connection *gc);
-extern void show_find_info();
+extern void show_find_email(struct gaim_connection *);
+extern void show_find_info(struct gaim_connection *);
 extern void g_show_info_text(char *, ...);
 extern void show_set_info(struct gaim_connection *);
 extern void show_set_dir();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/module.c	Sat Sep 29 23:06:30 2001 +0000
@@ -0,0 +1,516 @@
+/*
+ * 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
+ *
+ * ----------------
+ * The Plug-in plug
+ *
+ * Plugin support is currently being maintained by Mike Saraf
+ * msaraf@dwc.edu
+ *
+ * Well, I didn't see any work done on it for a while, so I'm going to try
+ * my hand at it. - Eric warmenhoven@yahoo.com
+ *
+ * Mike is my roomate.  I can assure you that he's lazy :-P  -- Rob rob@marko.net
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef GAIM_PLUGINS
+
+#include <string.h>
+#include <sys/time.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "gaim.h"
+
+/* ------------------ Global Variables ----------------------- */
+
+GList *plugins = NULL;
+GList *callbacks = NULL;
+
+/* --------------- Function Declarations --------------------- */
+
+struct gaim_plugin *  load_plugin(char *);
+              void  unload_plugin(struct gaim_plugin *p);
+struct gaim_plugin *reload_plugin(struct gaim_plugin *p);
+
+void gaim_signal_connect(GModule *, enum gaim_event, void *, void *);
+void gaim_signal_disconnect(GModule *, enum gaim_event, void *);
+void gaim_plugin_unload(GModule *);
+
+/* --------------- Static Function Declarations ------------- */
+
+static void plugin_remove_callbacks(GModule *);
+
+/* ------------------ Code Below ---------------------------- */
+
+struct gaim_plugin *load_plugin(char *filename)
+{
+	struct gaim_plugin *plug;
+	GList *c = plugins;
+	char *(*gaim_plugin_init)(GModule *);
+	char *(*cfunc)();
+	char *error;
+	char *retval;
+
+	if (!g_module_supported())
+		return NULL;
+	if (filename && !strlen(filename))
+		return NULL;
+
+	while (filename && c) {
+		plug = (struct gaim_plugin *)c->data;
+		if (!strcmp(filename, g_module_name(plug->handle))) {
+			/* just need to reload plugin */
+			return reload_plugin(plug);
+		} else
+			c = g_list_next(c);
+	}
+	plug = g_malloc(sizeof *plug);
+
+	debug_printf("Loading %s\n", filename);
+	plug->handle = g_module_open(filename, 0);
+	if (!plug->handle) {
+		error = (char *)g_module_error();
+		do_error_dialog(error, _("Plugin Error"));
+		g_free(plug);
+		return NULL;
+	}
+
+	if (!g_module_symbol(plug->handle, "gaim_plugin_init", (gpointer *)&gaim_plugin_init)) {
+		do_error_dialog(g_module_error(), _("Plugin Error"));
+		g_module_close(plug->handle);
+		g_free(plug);
+		return NULL;
+	}
+
+	retval = (*gaim_plugin_init)(plug->handle);
+	debug_printf("loaded plugin returned %s\n", retval ? retval : "NULL");
+	if (retval) {
+		plugin_remove_callbacks(plug->handle);
+		do_error_dialog(retval, _("Plugin Error"));
+		g_module_close(plug->handle);
+		g_free(plug);
+		return NULL;
+	}
+
+	plugins = g_list_append(plugins, plug);
+
+	if (g_module_symbol(plug->handle, "name", (gpointer *)&cfunc)) {
+		plug->name = (*cfunc)();
+	} else {
+		plug->name = NULL;
+	}
+
+	if (g_module_symbol(plug->handle, "description", (gpointer *)&cfunc))
+		plug->description = (*cfunc)();
+	else
+		plug->description = NULL;
+
+	save_prefs();
+	return plug;
+}
+
+static void unload_gaim_plugin(struct gaim_plugin *p)
+{
+	void (*gaim_plugin_remove)();
+
+	debug_printf("Unloading %s\n", g_module_name(p->handle));
+
+	/* Attempt to call the plugin's remove function (if there) */
+	if (g_module_symbol(p->handle, "gaim_plugin_remove", (gpointer *)&gaim_plugin_remove))
+		(*gaim_plugin_remove)();
+
+	plugin_remove_callbacks(p->handle);
+
+	plugins = g_list_remove(plugins, p);
+	g_free(p);
+	save_prefs();
+}
+
+void unload_plugin(struct gaim_plugin *p)
+{
+	GModule *handle = p->handle;
+	unload_gaim_plugin(p);
+	g_module_close(handle);
+}
+
+static gboolean unload_timeout(gpointer handle)
+{
+	g_module_close(handle);
+	return FALSE;
+}
+
+void gaim_plugin_unload(GModule *handle)
+{
+	g_timeout_add(5000, unload_timeout, handle);
+}
+
+/* Do unload/load cycle of plugin. */
+struct gaim_plugin *reload_plugin(struct gaim_plugin *p)
+{
+	char file[1024];
+	GModule *handle = p->handle;
+
+	strncpy(file, g_module_name(handle), sizeof(file));
+	file[sizeof(file) - 1] = '\0';
+
+	debug_printf("Reloading %s\n", file);
+
+	/* Unload */
+	unload_plugin(p);
+
+	/* Load */
+	return load_plugin(file);
+}
+
+/* Remove all callbacks associated with plugin handle */
+static void plugin_remove_callbacks(GModule *handle)
+{
+	GList *c = callbacks;
+	struct gaim_callback *g;
+
+	debug_printf("%d callbacks to search\n", g_list_length(callbacks));
+
+	while (c) {
+		g = (struct gaim_callback *)c->data;
+		if (g->handle == handle) {
+			c = g_list_next(c);
+			callbacks = g_list_remove(callbacks, (gpointer)g);
+			debug_printf("Removing callback, %d remain\n", g_list_length(callbacks));
+		} else
+			c = g_list_next(c);
+	}
+}
+
+void gaim_signal_connect(GModule *handle, enum gaim_event which, void *func, void *data)
+{
+	struct gaim_callback *call = g_new0(struct gaim_callback, 1);
+	call->handle = handle;
+	call->event = which;
+	call->function = func;
+	call->data = data;
+
+	callbacks = g_list_append(callbacks, call);
+	debug_printf("Adding callback %d\n", g_list_length(callbacks));
+}
+
+void gaim_signal_disconnect(GModule *handle, enum gaim_event which, void *func)
+{
+	GList *c = callbacks;
+	struct gaim_callback *g = NULL;
+
+	while (c) {
+		g = (struct gaim_callback *)c->data;
+		if (handle == g->handle && func == g->function) {
+			callbacks = g_list_remove(callbacks, c->data);
+			g_free(g);
+			c = callbacks;
+			if (c == NULL)
+				break;
+		}
+		c = g_list_next(c);
+	}
+}
+
+#endif /* GAIM_PLUGINS */
+
+static char *event_name(enum gaim_event event)
+{
+	static char buf[128];
+	switch (event) {
+	case event_signon:
+		sprintf(buf, "event_signon");
+		break;
+	case event_signoff:
+		sprintf(buf, "event_signoff");
+		break;
+	case event_away:
+		sprintf(buf, "event_away");
+		break;
+	case event_back:
+		sprintf(buf, "event_back");
+		break;
+	case event_im_recv:
+		sprintf(buf, "event_im_recv");
+		break;
+	case event_im_send:
+		sprintf(buf, "event_im_send");
+		break;
+	case event_buddy_signon:
+		sprintf(buf, "event_buddy_signon");
+		break;
+	case event_buddy_signoff:
+		sprintf(buf, "event_buddy_signoff");
+		break;
+	case event_buddy_away:
+		sprintf(buf, "event_buddy_away");
+		break;
+	case event_buddy_back:
+		sprintf(buf, "event_buddy_back");
+		break;
+	case event_buddy_idle:
+		sprintf(buf, "event_buddy_idle");
+		break;
+	case event_buddy_unidle:
+		sprintf(buf, "event_buddy_unidle");
+		break;
+	case event_blist_update:
+		sprintf(buf, "event_blist_update");
+		break;
+	case event_chat_invited:
+		sprintf(buf, "event_chat_invited");
+		break;
+	case event_chat_join:
+		sprintf(buf, "event_chat_join");
+		break;
+	case event_chat_leave:
+		sprintf(buf, "event_chat_leave");
+		break;
+	case event_chat_buddy_join:
+		sprintf(buf, "event_chat_buddy_join");
+		break;
+	case event_chat_buddy_leave:
+		sprintf(buf, "event_chat_buddy_leave");
+		break;
+	case event_chat_recv:
+		sprintf(buf, "event_chat_recv");
+		break;
+	case event_chat_send:
+		sprintf(buf, "event_chat_send");
+		break;
+	case event_warned:
+		sprintf(buf, "event_warned");
+		break;
+	case event_error:
+		sprintf(buf, "event_error");
+		break;
+	case event_quit:
+		sprintf(buf, "event_quit");
+		break;
+	case event_new_conversation:
+		sprintf(buf, "event_new_conversation");
+		break;
+	case event_set_info:
+		sprintf(buf, "event_set_info");
+		break;
+	case event_draw_menu:
+		sprintf(buf, "event_draw_menu");
+		break;
+	case event_im_displayed_sent:
+		sprintf(buf, "event_im_displayed_sent");
+		break;
+	case event_im_displayed_rcvd:
+		sprintf(buf, "event_im_displayed_rcvd");
+		break;
+	case event_chat_send_invite:
+		sprintf(buf, "event_chat_send_invite");
+		break;
+	default:
+		sprintf(buf, "event_unknown");
+		break;
+	}
+	return buf;
+}
+
+int plugin_event(enum gaim_event event, void *arg1, void *arg2, void *arg3, void *arg4)
+{
+#ifdef USE_PERL
+	char buf[BUF_LONG];
+#endif
+#ifdef GAIM_PLUGINS
+	GList *c = callbacks;
+	struct gaim_callback *g;
+
+	while (c) {
+		void (*zero)(void *);
+		void (*one)(void *, void *);
+		void (*two)(void *, void *, void *);
+		void (*three)(void *, void *, void *, void *);
+		void (*four)(void *, void *, void *, void *, void *);
+
+		g = (struct gaim_callback *)c->data;
+		if (g->event == event && g->function !=NULL) {
+			switch (event) {
+
+				/* no args */
+			case event_blist_update:
+			case event_quit:
+				zero = g->function;
+				(*zero)(g->data);
+				break;
+
+				/* one arg */
+			case event_signon:
+			case event_signoff:
+			case event_new_conversation:
+			case event_error:
+				one = g->function;
+				(*one)(arg1, g->data);
+				break;
+
+				/* two args */
+			case event_buddy_signon:
+			case event_buddy_signoff:
+			case event_buddy_away:
+			case event_buddy_back:
+			case event_buddy_idle:
+			case event_buddy_unidle:
+			case event_chat_leave:
+			case event_set_info:
+			case event_draw_menu:
+				two = g->function;
+				(*two)(arg1, arg2, g->data);
+				break;
+
+				/* three args */
+			case event_im_send:
+			case event_im_displayed_sent:
+			case event_chat_join:
+			case event_chat_buddy_join:
+			case event_chat_buddy_leave:
+			case event_chat_send:
+			case event_away:
+			case event_back:
+			case event_warned:
+				three = g->function;
+				(*three)(arg1, arg2, arg3, g->data);
+				break;
+
+				/* four args */
+			case event_im_recv:
+			case event_chat_recv:
+			case event_im_displayed_rcvd:
+			case event_chat_send_invite:
+			case event_chat_invited:
+				four = g->function;
+				(*four)(arg1, arg2, arg3, arg4, g->data);
+				break;
+
+			default:
+				debug_printf("unknown event %d\n", event);
+				break;
+			}
+		}
+		c = c->next;
+	}
+#endif /* GAIM_PLUGINS */
+#ifdef USE_PERL
+	switch (event) {
+	case event_signon:
+	case event_signoff:
+	case event_away:
+	case event_back:
+		g_snprintf(buf, sizeof buf, "%lu", (unsigned long)arg1);
+		break;
+	case event_im_recv:
+		g_snprintf(buf, sizeof buf, "%lu \"%s\" %s", (unsigned long)arg1,
+			   *(char **)arg2 ? *(char **)arg2 : "(null)",
+			   *(char **)arg3 ? *(char **)arg3 : "(null)");
+		break;
+	case event_im_send:
+		g_snprintf(buf, sizeof buf, "%lu \"%s\" %s", (unsigned long)arg1,
+			   (char *)arg2, *(char **)arg3 ? *(char **)arg3 : "(null)");
+		break;
+	case event_buddy_signon:
+	case event_buddy_signoff:
+	case event_set_info:
+	case event_buddy_away:
+	case event_buddy_back:
+	case event_buddy_idle:
+	case event_buddy_unidle:
+		g_snprintf(buf, sizeof buf, "%lu \"%s\"", (unsigned long)arg1, (char *)arg2);
+		break;
+	case event_chat_invited:
+		g_snprintf(buf, sizeof buf, "%lu \"%s\" \"%s\" %s", (unsigned long)arg1,
+				(char *)arg2, (char *)arg3, arg4 ? (char *)arg4 : "");
+		break;
+	case event_chat_join:
+	case event_chat_buddy_join:
+	case event_chat_buddy_leave:
+		g_snprintf(buf, sizeof buf, "%lu %d \"%s\"", (unsigned long)arg1,
+				(int)arg2, (char *)arg3);
+		break;
+	case event_chat_leave:
+		g_snprintf(buf, sizeof buf, "%lu %d", (unsigned long)arg1, (int)arg2);
+		break;
+	case event_chat_recv:
+		g_snprintf(buf, sizeof buf, "%lu %d \"%s\" %s", (unsigned long)arg1,
+				(int)arg2, (char *)arg3, (char *)arg4 ? (char *)arg4 : "(null)");
+		break;
+	case event_chat_send_invite:
+		g_snprintf(buf, sizeof buf, "%lu %d \"%s\" %s", (unsigned long)arg1,
+				(int)arg2, (char *)arg3, *(char **)arg4 ? *(char **)arg4 : "(null)");
+		break;
+	case event_chat_send:
+		g_snprintf(buf, sizeof buf, "%lu %d %s", (unsigned long)arg1, (int)arg2,
+				*(char **)arg3 ? *(char **)arg3 : "(null)");
+		break;
+	case event_warned:
+		g_snprintf(buf, sizeof buf, "%lu \"%s\" %d", (unsigned long)arg1,
+				arg2 ? (char *)arg2 : "", (int)arg3);
+		break;
+	case event_quit:
+		buf[0] = 0;
+		break;
+	case event_new_conversation:
+		g_snprintf(buf, sizeof buf, "\"%s\"", (char *)arg1);
+		break;
+	case event_im_displayed_sent:
+		g_snprintf(buf, sizeof buf, "%lu \"%s\" %s", (unsigned long)arg1,
+				(char *)arg2, *(char **)arg3 ? *(char **)arg3 : "(null)");
+		break;
+	case event_im_displayed_rcvd:
+		g_snprintf(buf, sizeof buf, "%lu \"%s\" %s", (unsigned long)arg1,
+				(char *)arg2, (char *)arg3 ? (char *)arg3 : "(null)");
+		break;
+	default:
+		return 0;
+	}
+	return perl_event(event_name(event), buf);
+#else
+	return 0;
+#endif
+}
+
+/* Calls the gaim_plugin_remove function in any loaded plugin that has one */
+#ifdef GAIM_PLUGINS
+void remove_all_plugins()
+{
+	GList *c = plugins;
+	struct gaim_plugin *p;
+	void (*gaim_plugin_remove)();
+
+	while (c) {
+		p = (struct gaim_plugin *)c->data;
+		if (g_module_symbol(p->handle, "gaim_plugin_remove", (gpointer *)&gaim_plugin_remove))
+			(*gaim_plugin_remove)();
+		g_free(p);
+		c = c->next;
+	}
+}
+#endif
--- a/src/plugins.c	Sat Sep 29 02:08:00 2001 +0000
+++ b/src/plugins.c	Sat Sep 29 23:06:30 2001 +0000
@@ -34,6 +34,8 @@
 #include <config.h>
 #endif
 
+#ifdef GAIM_PLUGINS
+
 #include <string.h>
 #include <sys/time.h>
 
@@ -46,10 +48,6 @@
 #include <gtk/gtk.h>
 #include "gaim.h"
 
-#ifdef GAIM_PLUGINS
-
-#include <dlfcn.h>
-
 #include "pixmaps/gnome_add.xpm"
 #include "pixmaps/gnome_remove.xpm"
 #include "pixmaps/gnome_preferences.xpm"
@@ -59,11 +57,6 @@
 #define PATHSIZE 1024	/* XXX: stolen from dialogs.c */
 
 
-/* ------------------ Global Variables ----------------------- */
-
-GList *plugins = NULL;
-GList *callbacks = NULL;
-
 /* ------------------ Local Variables ------------------------ */
 
 static GtkWidget *plugin_dialog = NULL;
@@ -84,25 +77,16 @@
 /* --------------- Function Declarations --------------------- */
 
 void show_plugins(GtkWidget *, gpointer);
-void load_plugin(char *);
-
-void gaim_signal_connect(GModule *, enum gaim_event, void *, void *);
-void gaim_signal_disconnect(GModule *, enum gaim_event, void *);
-void gaim_plugin_unload(GModule *);
 
 /* UI button callbacks */
+static void unload_plugin_cb(GtkWidget *, gpointer);
 static void plugin_reload_cb(GtkWidget *, gpointer);
 
 static const gchar *plugin_makelistname(GModule *);
-static void plugin_remove_callbacks(GModule *);
-
-static void plugin_reload(struct gaim_plugin *p);
 
 static void destroy_plugins(GtkWidget *, gpointer);
 static void load_file(GtkWidget *, gpointer);
 static void load_which_plugin(GtkWidget *, gpointer);
-static void unload_plugin(GtkWidget *, gpointer);
-static void unload_immediate(GModule *);
 static void list_clicked(GtkWidget *, struct gaim_plugin *);
 static void update_show_plugins();
 static void hide_plugins(GtkWidget *, gpointer);
@@ -166,80 +150,8 @@
 	if (plugin_dialog)
 		gtk_widget_destroy(plugin_dialog);
 	plugin_dialog = NULL;
-}
-
-void load_plugin(char *filename)
-{
-	struct gaim_plugin *plug;
-	GList *c = plugins;
-	char *(*gaim_plugin_init)(GModule *);
-	char *(*cfunc)();
-	char *error;
-	char *retval;
-
-	if (!g_module_supported())
-		return;
-	if (filename && !strlen(filename))
-		return;
-
-	while (filename && c) {
-		plug = (struct gaim_plugin *)c->data;
-		if (!strcmp(filename, g_module_name(plug->handle))) {
-			/* just need to reload plugin */
-			plugin_reload(plug);
-			return;
-		} else
-			c = g_list_next(c);
-	}
-	plug = g_malloc(sizeof *plug);
-
-	if (filename) {
-		if (last_dir)
-			g_free(last_dir);
-		last_dir = g_dirname(filename);
-	}
-
-	debug_printf("Loading %s\n", filename);
-	plug->handle = g_module_open(filename, 0);
-	if (!plug->handle) {
-		error = (char *)g_module_error();
-		do_error_dialog(error, _("Plugin Error"));
-		g_free(plug);
-		return;
-	}
-
-	if (!g_module_symbol(plug->handle, "gaim_plugin_init", (gpointer *)&gaim_plugin_init)) {
-		do_error_dialog(g_module_error(), _("Plugin Error"));
-		g_module_close(plug->handle);
-		g_free(plug);
-		return;
-	}
-
-	retval = (*gaim_plugin_init)(plug->handle);
-	debug_printf("loaded plugin returned %s\n", retval ? retval : "NULL");
-	if (retval) {
-		plugin_remove_callbacks(plug->handle);
-		do_error_dialog(retval, _("Plugin Error"));
-		g_module_close(plug->handle);
-		g_free(plug);
-		return;
-	}
-
-	plugins = g_list_append(plugins, plug);
-
-	if (g_module_symbol(plug->handle, "name", (gpointer *)&cfunc)) {
-		plug->name = (*cfunc)();
-	} else {
-		plug->name = NULL;
-	}
-
-	if (g_module_symbol(plug->handle, "description", (gpointer *)&cfunc))
-		plug->description = (*cfunc)();
-	else
-		plug->description = NULL;
 
 	update_show_plugins();
-	save_prefs();
 }
 
 void show_plugins(GtkWidget *w, gpointer data)
@@ -358,7 +270,7 @@
 	gtk_tooltips_set_tip(tooltips, reload, _("Reload the selected plugin"), "");
 
 	unload = picture_button(plugwindow, _("Unload"), gnome_remove_xpm);
-	gtk_signal_connect(GTK_OBJECT(unload), "clicked", GTK_SIGNAL_FUNC(unload_plugin), pluglist);
+	gtk_signal_connect(GTK_OBJECT(unload), "clicked", GTK_SIGNAL_FUNC(unload_plugin_cb), pluglist);
 	gtk_box_pack_start(GTK_BOX(bothbox), unload, TRUE, TRUE, 0);
 	gtk_tooltips_set_tip(tooltips, unload, _("Unload the selected plugin"), "");
 
@@ -415,11 +327,10 @@
 	}
 }
 
-static void unload_plugin(GtkWidget *w, gpointer data)
+static void unload_plugin_cb(GtkWidget *w, gpointer data)
 {
 	GList *i;
 	struct gaim_plugin *p;
-	void (*gaim_plugin_remove)();
 
 	i = GTK_LIST(pluglist)->selection;
 
@@ -428,105 +339,28 @@
 
 	p = gtk_object_get_user_data(GTK_OBJECT(i->data));
 
-	/* Attempt to call the plugin's remove function (if there) */
-	if (g_module_symbol(p->handle, "gaim_plugin_remove", (gpointer *)&gaim_plugin_remove))
-		(*gaim_plugin_remove)();
-
-	unload_immediate(p->handle);
+	unload_plugin(p);
 	update_show_plugins();
 }
 
-static void unload_for_real(void *handle)
-{
-	GList *i;
-	struct gaim_plugin *p = NULL;
-
-	i = plugins;
-	while (i) {
-		p = (struct gaim_plugin *)i->data;
-		if (handle == p->handle)
-			break;
-		p = NULL;
-		i = g_list_next(i);
-	}
-
-	if (!p)
-		return;
-
-	debug_printf("Unloading %s\n", g_module_name(p->handle));
-
-	plugin_remove_callbacks(p->handle);
-
-	plugins = g_list_remove(plugins, p);
-	g_free(p);
-	update_show_plugins();
-	save_prefs();
-}
-
-static void unload_immediate(GModule *handle)
-{
-	unload_for_real(handle);
-	g_module_close(handle);
-}
-
-static gboolean unload_timeout(gpointer handle)
-{
-	g_module_close(handle);
-	return FALSE;
-}
-
-void gaim_plugin_unload(GModule *handle)
-{
-	unload_for_real(handle);
-	g_timeout_add(5000, unload_timeout, handle);
-}
-
 static void plugin_reload_cb(GtkWidget *w, gpointer data)
 {
 	GList *i;
+	struct gaim_plugin *p;
 
 	i = GTK_LIST(pluglist)->selection;
 	if (i == NULL)
 		return;
 
 	/* Just pass off plugin to the actual function */
-	plugin_reload(gtk_object_get_user_data(GTK_OBJECT(i->data)));
-}
-
-/* Do unload/load cycle of plugin. */
-static void plugin_reload(struct gaim_plugin *p)
-{
-	char file[PATHSIZE];
-	void (*gaim_plugin_remove)();
-	GModule *handle = p->handle;
-	struct gaim_plugin *plug;
-	GList *plugs;
+	p = reload_plugin(gtk_object_get_user_data(GTK_OBJECT(i->data)));
 
-	strncpy(file, g_module_name(handle), sizeof(file));
-	file[sizeof(file) - 1] = '\0';
-
-	debug_printf("Reloading %s\n", file);
-
-	/* Unload */
-	if (g_module_symbol(handle, "gaim_plugin_remove", (gpointer *)&gaim_plugin_remove))
-		(*gaim_plugin_remove)();
-	unload_immediate(handle);
-
-	/* Load */
-	load_plugin(file);
+	update_show_plugins();
 
 	/* Try and reselect the plugin in list */
 	if (!pluglist)
 		return;
-	plugs = plugins;
-	while (plugs) {
-		plug = plugs->data;
-		if (!strcmp(file, g_module_name(plug->handle))) {
-			gtk_list_select_item(GTK_LIST(pluglist), g_list_index(plugins, plug));
-			return;
-		}
-		plugs = plugs->next;
-	}
+	gtk_list_select_item(GTK_LIST(pluglist), g_list_index(plugins, p));
 }
 
 static void list_clicked(GtkWidget *w, struct gaim_plugin *p)
@@ -594,330 +428,5 @@
 
 	return filename;
 }		
-		
-/* Remove all callbacks associated with plugin handle */
-static void plugin_remove_callbacks(GModule *handle)
-{
-	GList *c = callbacks;
-	struct gaim_callback *g;
 
-	debug_printf("%d callbacks to search\n", g_list_length(callbacks));
-
-	while (c) {
-		g = (struct gaim_callback *)c->data;
-		if (g->handle == handle) {
-			c = g_list_next(c);
-			callbacks = g_list_remove(callbacks, (gpointer)g);
-			debug_printf("Removing callback, %d remain\n", g_list_length(callbacks));
-		} else
-			c = g_list_next(c);
-	}
-}
-
-void gaim_signal_connect(GModule *handle, enum gaim_event which, void *func, void *data)
-{
-	struct gaim_callback *call = g_new0(struct gaim_callback, 1);
-	call->handle = handle;
-	call->event = which;
-	call->function = func;
-	call->data = data;
-
-	callbacks = g_list_append(callbacks, call);
-	debug_printf("Adding callback %d\n", g_list_length(callbacks));
-}
-
-void gaim_signal_disconnect(GModule *handle, enum gaim_event which, void *func)
-{
-	GList *c = callbacks;
-	struct gaim_callback *g = NULL;
-
-	while (c) {
-		g = (struct gaim_callback *)c->data;
-		if (handle == g->handle && func == g->function) {
-			callbacks = g_list_remove(callbacks, c->data);
-			g_free(g);
-			c = callbacks;
-			if (c == NULL)
-				break;
-		}
-		c = g_list_next(c);
-	}
-}
-
-#endif /* GAIM_PLUGINS */
-
-static char *event_name(enum gaim_event event)
-{
-	static char buf[128];
-	switch (event) {
-	case event_signon:
-		sprintf(buf, "event_signon");
-		break;
-	case event_signoff:
-		sprintf(buf, "event_signoff");
-		break;
-	case event_away:
-		sprintf(buf, "event_away");
-		break;
-	case event_back:
-		sprintf(buf, "event_back");
-		break;
-	case event_im_recv:
-		sprintf(buf, "event_im_recv");
-		break;
-	case event_im_send:
-		sprintf(buf, "event_im_send");
-		break;
-	case event_buddy_signon:
-		sprintf(buf, "event_buddy_signon");
-		break;
-	case event_buddy_signoff:
-		sprintf(buf, "event_buddy_signoff");
-		break;
-	case event_buddy_away:
-		sprintf(buf, "event_buddy_away");
-		break;
-	case event_buddy_back:
-		sprintf(buf, "event_buddy_back");
-		break;
-	case event_buddy_idle:
-		sprintf(buf, "event_buddy_idle");
-		break;
-	case event_buddy_unidle:
-		sprintf(buf, "event_buddy_unidle");
-		break;
-	case event_blist_update:
-		sprintf(buf, "event_blist_update");
-		break;
-	case event_chat_invited:
-		sprintf(buf, "event_chat_invited");
-		break;
-	case event_chat_join:
-		sprintf(buf, "event_chat_join");
-		break;
-	case event_chat_leave:
-		sprintf(buf, "event_chat_leave");
-		break;
-	case event_chat_buddy_join:
-		sprintf(buf, "event_chat_buddy_join");
-		break;
-	case event_chat_buddy_leave:
-		sprintf(buf, "event_chat_buddy_leave");
-		break;
-	case event_chat_recv:
-		sprintf(buf, "event_chat_recv");
-		break;
-	case event_chat_send:
-		sprintf(buf, "event_chat_send");
-		break;
-	case event_warned:
-		sprintf(buf, "event_warned");
-		break;
-	case event_error:
-		sprintf(buf, "event_error");
-		break;
-	case event_quit:
-		sprintf(buf, "event_quit");
-		break;
-	case event_new_conversation:
-		sprintf(buf, "event_new_conversation");
-		break;
-	case event_set_info:
-		sprintf(buf, "event_set_info");
-		break;
-	case event_draw_menu:
-		sprintf(buf, "event_draw_menu");
-		break;
-	case event_im_displayed_sent:
-		sprintf(buf, "event_im_displayed_sent");
-		break;
-	case event_im_displayed_rcvd:
-		sprintf(buf, "event_im_displayed_rcvd");
-		break;
-	case event_chat_send_invite:
-		sprintf(buf, "event_chat_send_invite");
-		break;
-	default:
-		sprintf(buf, "event_unknown");
-		break;
-	}
-	return buf;
-}
-
-int plugin_event(enum gaim_event event, void *arg1, void *arg2, void *arg3, void *arg4)
-{
-#ifdef USE_PERL
-	char buf[BUF_LONG];
 #endif
-#ifdef GAIM_PLUGINS
-	GList *c = callbacks;
-	struct gaim_callback *g;
-
-	while (c) {
-		void (*zero)(void *);
-		void (*one)(void *, void *);
-		void (*two)(void *, void *, void *);
-		void (*three)(void *, void *, void *, void *);
-		void (*four)(void *, void *, void *, void *, void *);
-
-		g = (struct gaim_callback *)c->data;
-		if (g->event == event && g->function !=NULL) {
-			switch (event) {
-
-				/* no args */
-			case event_blist_update:
-			case event_quit:
-				zero = g->function;
-				(*zero)(g->data);
-				break;
-
-				/* one arg */
-			case event_signon:
-			case event_signoff:
-			case event_new_conversation:
-			case event_error:
-				one = g->function;
-				(*one)(arg1, g->data);
-				break;
-
-				/* two args */
-			case event_buddy_signon:
-			case event_buddy_signoff:
-			case event_buddy_away:
-			case event_buddy_back:
-			case event_buddy_idle:
-			case event_buddy_unidle:
-			case event_chat_leave:
-			case event_set_info:
-			case event_draw_menu:
-				two = g->function;
-				(*two)(arg1, arg2, g->data);
-				break;
-
-				/* three args */
-			case event_im_send:
-			case event_im_displayed_sent:
-			case event_chat_join:
-			case event_chat_buddy_join:
-			case event_chat_buddy_leave:
-			case event_chat_send:
-			case event_away:
-			case event_back:
-			case event_warned:
-				three = g->function;
-				(*three)(arg1, arg2, arg3, g->data);
-				break;
-
-				/* four args */
-			case event_im_recv:
-			case event_chat_recv:
-			case event_im_displayed_rcvd:
-			case event_chat_send_invite:
-			case event_chat_invited:
-				four = g->function;
-				(*four)(arg1, arg2, arg3, arg4, g->data);
-				break;
-
-			default:
-				debug_printf("unknown event %d\n", event);
-				break;
-			}
-		}
-		c = c->next;
-	}
-#endif /* GAIM_PLUGINS */
-#ifdef USE_PERL
-	switch (event) {
-	case event_signon:
-	case event_signoff:
-	case event_away:
-	case event_back:
-		g_snprintf(buf, sizeof buf, "%lu", (unsigned long)arg1);
-		break;
-	case event_im_recv:
-		g_snprintf(buf, sizeof buf, "%lu \"%s\" %s", (unsigned long)arg1,
-			   *(char **)arg2 ? *(char **)arg2 : "(null)",
-			   *(char **)arg3 ? *(char **)arg3 : "(null)");
-		break;
-	case event_im_send:
-		g_snprintf(buf, sizeof buf, "%lu \"%s\" %s", (unsigned long)arg1,
-			   (char *)arg2, *(char **)arg3 ? *(char **)arg3 : "(null)");
-		break;
-	case event_buddy_signon:
-	case event_buddy_signoff:
-	case event_set_info:
-	case event_buddy_away:
-	case event_buddy_back:
-	case event_buddy_idle:
-	case event_buddy_unidle:
-		g_snprintf(buf, sizeof buf, "%lu \"%s\"", (unsigned long)arg1, (char *)arg2);
-		break;
-	case event_chat_invited:
-		g_snprintf(buf, sizeof buf, "%lu \"%s\" \"%s\" %s", (unsigned long)arg1,
-				(char *)arg2, (char *)arg3, arg4 ? (char *)arg4 : "");
-		break;
-	case event_chat_join:
-	case event_chat_buddy_join:
-	case event_chat_buddy_leave:
-		g_snprintf(buf, sizeof buf, "%lu %d \"%s\"", (unsigned long)arg1,
-				(int)arg2, (char *)arg3);
-		break;
-	case event_chat_leave:
-		g_snprintf(buf, sizeof buf, "%lu %d", (unsigned long)arg1, (int)arg2);
-		break;
-	case event_chat_recv:
-		g_snprintf(buf, sizeof buf, "%lu %d \"%s\" %s", (unsigned long)arg1,
-				(int)arg2, (char *)arg3, (char *)arg4 ? (char *)arg4 : "(null)");
-		break;
-	case event_chat_send_invite:
-		g_snprintf(buf, sizeof buf, "%lu %d \"%s\" %s", (unsigned long)arg1,
-				(int)arg2, (char *)arg3, *(char **)arg4 ? *(char **)arg4 : "(null)");
-		break;
-	case event_chat_send:
-		g_snprintf(buf, sizeof buf, "%lu %d %s", (unsigned long)arg1, (int)arg2,
-				*(char **)arg3 ? *(char **)arg3 : "(null)");
-		break;
-	case event_warned:
-		g_snprintf(buf, sizeof buf, "%lu \"%s\" %d", (unsigned long)arg1,
-				arg2 ? (char *)arg2 : "", (int)arg3);
-		break;
-	case event_quit:
-		buf[0] = 0;
-		break;
-	case event_new_conversation:
-		g_snprintf(buf, sizeof buf, "\"%s\"", (char *)arg1);
-		break;
-	case event_im_displayed_sent:
-		g_snprintf(buf, sizeof buf, "%lu \"%s\" %s", (unsigned long)arg1,
-				(char *)arg2, *(char **)arg3 ? *(char **)arg3 : "(null)");
-		break;
-	case event_im_displayed_rcvd:
-		g_snprintf(buf, sizeof buf, "%lu \"%s\" %s", (unsigned long)arg1,
-				(char *)arg2, (char *)arg3 ? (char *)arg3 : "(null)");
-		break;
-	default:
-		return 0;
-	}
-	return perl_event(event_name(event), buf);
-#else
-	return 0;
-#endif
-}
-
-/* Calls the gaim_plugin_remove function in any loaded plugin that has one */
-#ifdef GAIM_PLUGINS
-void remove_all_plugins()
-{
-	GList *c = plugins;
-	struct gaim_plugin *p;
-	void (*gaim_plugin_remove)();
-
-	while (c) {
-		p = (struct gaim_plugin *)c->data;
-		if (g_module_symbol(p->handle, "gaim_plugin_remove", (gpointer *)&gaim_plugin_remove))
-			(*gaim_plugin_remove)();
-		g_free(p);
-		c = c->next;
-	}
-}
-#endif
--- a/src/protocols/Makefile.am	Sat Sep 29 02:08:00 2001 +0000
+++ b/src/protocols/Makefile.am	Sat Sep 29 23:06:30 2001 +0000
@@ -1,6 +1,6 @@
 if PRPLS
 
-SUBDIRS = icq irc jabber msn napster oscar toc yahoo zephyr
+SUBDIRS = gg icq irc jabber msn napster oscar toc yahoo zephyr
 
 else
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/gg/.cvsignore	Sat Sep 29 23:06:30 2001 +0000
@@ -0,0 +1,7 @@
+.deps
+.libs
+Makefile
+Makefile.in
+gg.lo
+libgg.la
+libgg.lo
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/gg/Makefile.am	Sat Sep 29 23:06:30 2001 +0000
@@ -0,0 +1,28 @@
+EXTRA_DIST = protocol.txt
+
+pkgdir = $(libdir)/gaim
+
+CFLAGS += -I\$(top_srcdir)/src $(st) $(DEBUG_CFLAGS)
+libgg_la_LDFLAGS = -avoid-version
+
+if STATIC_GG
+
+st = -DSTATIC
+pkg_LTLIBRARIES =
+noinst_LIBRARIES = libgg.a
+
+libgg_a_SOURCES = libgg.c \
+		  libgg.h \
+		  gg.c
+
+else
+
+st =
+pkg_LTLIBRARIES = libgg.la
+noinst_LIBRARIES =
+
+libgg_la_SOURCES = libgg.c \
+		   libgg.h \
+		   gg.c
+
+endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/gg/gg.c	Sat Sep 29 23:06:30 2001 +0000
@@ -0,0 +1,1040 @@
+/*
+ * gaim - Gadu-Gadu Protocol Plugin
+ * $Id: gg.c 2406 2001-09-29 23:06:30Z warmenhoven $
+ *
+ * Copyright (C) 2001, Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * 
+ * 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 <config.h>
+
+#include <netdb.h>
+#include <unistd.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#ifdef HAVE_LANGINFO_CODESET
+#include <langinfo.h>
+#endif
+#ifdef HAVE_ICONV
+#include <iconv.h>
+#endif
+/* Library from EKG (Eksperymentalny Klient Gadu-Gadu) */
+#include "libgg.h"
+#include "multi.h"
+#include "prpl.h"
+#include "gaim.h"
+#include "proxy.h"
+
+#include "pixmaps/gg_suncloud.xpm"
+#include "pixmaps/gg_sunred.xpm"
+#include "pixmaps/gg_sunwhitered.xpm"
+#include "pixmaps/gg_sunyellow.xpm"
+
+#define USEROPT_NICK 0
+
+#define AGG_BUF_LEN 1024
+
+#define AGG_GENDER_NONE -1
+
+#define AGG_PUBDIR_FORM "/appsvc/fmpubquery2.asp"
+#define AGG_PUBDIR_MAX_ENTRIES 200
+
+#define AGG_STATUS_AVAIL              _("Available")
+#define AGG_STATUS_AVAIL_FRIENDS      _("Available for friends only")
+#define AGG_STATUS_BUSY               _("Away")
+#define AGG_STATUS_BUSY_FRIENDS       _("Away for friends only")
+#define AGG_STATUS_INVISIBLE          _("Invisible")
+#define AGG_STATUS_INVISIBLE_FRIENDS  _("Invisible for friends only")
+#define AGG_STATUS_NOT_AVAIL          _("Unavailable")
+
+struct agg_data {
+	struct gg_session *sess;
+};
+
+struct agg_search {
+	struct gaim_connection *gc;
+	gchar *search_data;
+	int inpa;
+};
+
+static char *agg_name()
+{
+	return "Gadu-Gadu";
+}
+
+static gchar *charset_convert(const gchar *locstr, char *encsrc, char *encdst)
+{
+#ifdef HAVE_ICONV
+	gchar *dststr;
+	size_t loclen, dstlen;
+	gchar *fsave, *tsave;
+	size_t count;
+	static iconv_t cd = (iconv_t)(-1);
+
+	if (cd == (iconv_t)(-1)) {
+		cd = iconv_open(encdst, encsrc);
+		if (cd == (iconv_t)(-1)) {
+			return g_strdup(locstr);
+		}
+	}
+
+	loclen = strlen(locstr);
+	/* we are ready for multibyte conversions */
+	dstlen = MB_LEN_MAX * loclen;
+	dststr = g_new0(gchar, dstlen + 1);
+	fsave = (gchar *)locstr;
+	tsave = dststr;
+	count = iconv(cd, &fsave, &loclen, &tsave, &dstlen);
+	if (count == -1) {
+		g_free(dststr);
+		return g_strdup(locstr);
+	}
+	return dststr;
+#else
+	return g_strdup(locstr);
+#endif
+}
+
+static gboolean invalid_uin(char *uin)
+{
+	unsigned long int res = strtol(uin, (char **)NULL, 10);
+	if (res == LONG_MIN || res == LONG_MAX || res == 0)
+		return TRUE;
+	return FALSE;
+}
+
+static gint args_compare(gconstpointer a, gconstpointer b)
+{
+	gchar *arg_a = (gchar *)a;
+	gchar *arg_b = (gchar *)b;
+
+	return g_strcasecmp(arg_a, arg_b);
+}
+
+static gboolean allowed_uin(struct gaim_connection *gc, char *uin)
+{
+	switch (gc->permdeny) {
+	case 1:
+		/* permit all, deny none */
+		return TRUE;
+		break;
+	case 2:
+		/* deny all, permit none. */
+		return FALSE;
+		break;
+	case 3:
+		/* permit some. */
+		if (g_slist_find_custom(gc->permit, uin, args_compare))
+			return TRUE;
+		return FALSE;
+		break;
+	case 4:
+		/* deny some. */
+		if (g_slist_find_custom(gc->deny, uin, args_compare))
+			return FALSE;
+		return TRUE;
+		break;
+	default:
+		return TRUE;
+		break;
+	}
+}
+
+static gchar *find_local_charset(void)
+{
+	gchar *gg_localenc = g_getenv("GG_CHARSET");
+
+	if (gg_localenc == NULL) {
+#ifdef HAVE_LANGINFO_CODESET
+		gg_localenc = nl_langinfo(CODESET);
+#else
+		gg_localenc = "US-ASCII";
+#endif
+	}
+	return gg_localenc;
+}
+
+static char *handle_errcode(int errcode, gboolean show)
+{
+	static char msg[AGG_BUF_LEN];
+
+	switch (errcode) {
+	case GG_FAILURE_RESOLVING:
+		g_snprintf(msg, sizeof(msg), _("Unable to resolve hostname."));
+		break;
+	case GG_FAILURE_CONNECTING:
+		g_snprintf(msg, sizeof(msg), _("Unable to connect to server."));
+		break;
+	case GG_FAILURE_INVALID:
+		g_snprintf(msg, sizeof(msg), _("Invalid response from server."));
+		break;
+	case GG_FAILURE_READING:
+		g_snprintf(msg, sizeof(msg), _("Error while reading from socket."));
+		break;
+	case GG_FAILURE_WRITING:
+		g_snprintf(msg, sizeof(msg), _("Error while writting to socket."));
+		break;
+	case GG_FAILURE_PASSWORD:
+		g_snprintf(msg, sizeof(msg), _("Authentification failed."));
+		break;
+	default:
+		g_snprintf(msg, sizeof(msg), _("Unknown Error Code."));
+		break;
+	}
+
+	if (show)
+		do_error_dialog(msg, _("Gadu-Gadu Error"));
+
+	return msg;
+}
+
+static gchar *encode_postdata(const gchar *data)
+{
+	gchar *p = NULL;
+	int i, j = 0;
+	for (i = 0; i < strlen(data); i++) {
+		/* locale insensitive, doesn't reflect RFC (1738 section 2.2, 1866 section 8.2.1) */
+		if ((data[i] >= 'a' && data[i] <= 'z')
+		    || (data[i] >= 'A' && data[i] <= 'Z')
+		    || (data[i] >= '0' && data[i] <= '9')
+		    || data[i] == '=' || data[i] == '&'
+		    || data[i] == '\n' || data[i] == '\r' || data[i] == '\t' || data[i] == '\014') {
+			p = g_realloc(p, j + 1);
+			p[j] = data[i];
+			j++;
+		} else {
+			p = g_realloc(p, j + 4);	/* remember, sprintf appends a '\0' */
+			sprintf(p + j, "%%%02x", (unsigned char)data[i]);
+			j += 3;
+		}
+	}
+	p = g_realloc(p, j + 1);
+	p[j] = '\0';
+
+	if (p && strlen(p))
+		return p;
+	else
+		return g_strdup(data);
+}
+
+static void agg_set_away(struct gaim_connection *gc, char *state, char *msg)
+{
+	struct agg_data *gd = (struct agg_data *)gc->proto_data;
+
+	if (gc->away)
+		gc->away = NULL;
+
+	if (!g_strcasecmp(state, AGG_STATUS_AVAIL))
+		gg_change_status(gd->sess, GG_STATUS_AVAIL);
+	else if (!g_strcasecmp(state, AGG_STATUS_AVAIL_FRIENDS))
+		gg_change_status(gd->sess, GG_STATUS_AVAIL | GG_STATUS_FRIENDS_MASK);
+	else if (!g_strcasecmp(state, AGG_STATUS_BUSY)) {
+		gg_change_status(gd->sess, GG_STATUS_BUSY);
+		gc->away = "";
+	} else if (!g_strcasecmp(state, AGG_STATUS_BUSY_FRIENDS)) {
+		gg_change_status(gd->sess, GG_STATUS_BUSY | GG_STATUS_FRIENDS_MASK);
+		gc->away = "";
+	} else if (!g_strcasecmp(state, AGG_STATUS_INVISIBLE)) {
+		gg_change_status(gd->sess, GG_STATUS_INVISIBLE);
+		gc->away = "";
+	} else if (!g_strcasecmp(state, AGG_STATUS_INVISIBLE_FRIENDS)) {
+		gg_change_status(gd->sess, GG_STATUS_INVISIBLE | GG_STATUS_FRIENDS_MASK);
+		gc->away = "";
+	} else if (!g_strcasecmp(state, AGG_STATUS_NOT_AVAIL)) {
+		gg_change_status(gd->sess, GG_STATUS_NOT_AVAIL);
+		gc->away = "";
+	} else if (!g_strcasecmp(state, GAIM_AWAY_CUSTOM)) {
+		if (msg) {
+			gg_change_status(gd->sess, GG_STATUS_BUSY);
+			gc->away = "";
+		} else
+			gg_change_status(gd->sess, GG_STATUS_AVAIL);
+	}
+}
+
+static gchar *get_away_text(int uc)
+{
+	if (uc == UC_UNAVAILABLE)
+		return AGG_STATUS_NOT_AVAIL;
+	uc = uc >> 5;
+	switch (uc) {
+	case GG_STATUS_AVAIL:
+	default:
+		return AGG_STATUS_AVAIL;
+	case GG_STATUS_AVAIL | GG_STATUS_FRIENDS_MASK:
+		return AGG_STATUS_AVAIL_FRIENDS;
+	case GG_STATUS_BUSY:
+		return AGG_STATUS_BUSY;
+	case GG_STATUS_BUSY | GG_STATUS_FRIENDS_MASK:
+		return AGG_STATUS_BUSY_FRIENDS;
+	case GG_STATUS_INVISIBLE:
+		return AGG_STATUS_INVISIBLE;
+	case GG_STATUS_INVISIBLE | GG_STATUS_FRIENDS_MASK:
+		return AGG_STATUS_INVISIBLE_FRIENDS;
+	case GG_STATUS_NOT_AVAIL:
+		return AGG_STATUS_NOT_AVAIL;
+	}
+}
+
+static GList *agg_away_states()
+{
+	GList *m = NULL;
+
+	m = g_list_append(m, AGG_STATUS_AVAIL);
+	m = g_list_append(m, AGG_STATUS_BUSY);
+	m = g_list_append(m, AGG_STATUS_INVISIBLE);
+	m = g_list_append(m, AGG_STATUS_AVAIL_FRIENDS);
+	m = g_list_append(m, AGG_STATUS_BUSY_FRIENDS);
+	m = g_list_append(m, AGG_STATUS_INVISIBLE_FRIENDS);
+	m = g_list_append(m, AGG_STATUS_NOT_AVAIL);
+	return m;
+}
+
+/* Enhance these functions, more options and such stuff */
+static GList *agg_buddy_menu(struct gaim_connection *gc, char *who)
+{
+	GList *m = NULL;
+	struct proto_buddy_menu *pbm;
+	struct buddy *b = find_buddy(gc, who);
+	static char buf[AGG_BUF_LEN];
+
+	if (!b) {
+		return m;
+	}
+
+	pbm = g_new0(struct proto_buddy_menu, 1);
+	g_snprintf(buf, sizeof(buf), _("Status: %s"), get_away_text(b->uc));
+	pbm->label = buf;
+	pbm->callback = NULL;
+	pbm->gc = gc;
+	m = g_list_append(m, pbm);
+
+	return m;
+}
+
+static GList *agg_user_opts()
+{
+	GList *m = NULL;
+	struct proto_user_opt *puo;
+
+	puo = g_new0(struct proto_user_opt, 1);
+	puo->label = _("Nick:");
+	puo->def = _("Gadu-Gadu User");
+	puo->pos = USEROPT_NICK;
+	m = g_list_append(m, puo);
+
+	return m;
+}
+
+static void main_callback(gpointer data, gint source, GaimInputCondition cond)
+{
+	struct gaim_connection *gc = data;
+	struct agg_data *gd = gc->proto_data;
+	struct gg_event *e;
+
+	debug_printf("main_callback enter: begin\n");
+
+	if (gd->sess->fd != source)
+		gd->sess->fd = source;
+
+	if (source == -1) {
+		signoff(gc);
+		return;
+	}
+
+	if (!(e = gg_watch_fd(gd->sess))) {
+		debug_printf("main_callback: gg_watch_fd failed - CRITICAL!\n");
+		signoff(gc);
+		return;
+	}
+
+	switch (e->type) {
+	case GG_EVENT_NONE:
+		/* do nothing */
+		break;
+	case GG_EVENT_CONN_SUCCESS:
+		debug_printf("main_callback: CONNECTED AGAIN!?\n");
+		break;
+	case GG_EVENT_CONN_FAILED:
+		if (gc->inpa)
+			gaim_input_remove(gc->inpa);
+		handle_errcode(e->event.failure, TRUE);
+		signoff(gc);
+		break;
+	case GG_EVENT_MSG:
+		{
+			gchar *imsg;
+			gchar user[20];
+
+			g_snprintf(user, sizeof(user), "%u", e->event.msg.sender);
+			if (!allowed_uin(gc, user))
+				break;
+			imsg = charset_convert(e->event.msg.message, "CP1250", find_local_charset());
+			serv_got_im(gc, user, imsg, 0, time((time_t) NULL));
+			g_free(imsg);
+		}
+		break;
+	case GG_EVENT_NOTIFY:
+		{
+			gchar user[20];
+			struct gg_notify_reply *n = e->event.notify;
+			guint status;
+
+			while (n->uin) {
+				switch (n->status) {
+				case GG_STATUS_NOT_AVAIL:
+					status = UC_UNAVAILABLE;
+					break;
+				case GG_STATUS_AVAIL:
+				case GG_STATUS_BUSY:
+				case GG_STATUS_INVISIBLE:
+				case GG_STATUS_FRIENDS_MASK:
+					status = UC_NORMAL | (e->event.status.status << 5);
+					break;
+				default:
+					status = UC_NORMAL;
+					break;
+				}
+
+				g_snprintf(user, sizeof(user), "%u", n->uin);
+				serv_got_update(gc, user, (status == UC_UNAVAILABLE) ? 0 : 1, 0, 0, 0,
+						status, 0);
+				n++;
+			}
+		}
+		break;
+	case GG_EVENT_STATUS:
+		{
+			gchar user[20];
+			guint status;
+
+			switch (e->event.status.status) {
+			case GG_STATUS_NOT_AVAIL:
+				status = UC_UNAVAILABLE;
+				break;
+			case GG_STATUS_AVAIL:
+			case GG_STATUS_BUSY:
+			case GG_STATUS_INVISIBLE:
+			case GG_STATUS_FRIENDS_MASK:
+				status = UC_NORMAL | (e->event.status.status << 5);
+				break;
+			default:
+				status = UC_NORMAL;
+				break;
+			}
+
+			g_snprintf(user, sizeof(user), "%u", e->event.status.uin);
+			serv_got_update(gc, user, (status == UC_UNAVAILABLE) ? 0 : 1, 0, 0, 0, status,
+					0);
+		}
+		break;
+	case GG_EVENT_ACK:
+		debug_printf("main_callback: message %d to %u sent with status %d\n",
+			     e->event.ack.seq, e->event.ack.recipient, e->event.ack.status);
+		break;
+	default:
+		debug_printf("main_callback: unsupported event %d\n", e->type);
+		break;
+	}
+	gg_free_event(e);
+}
+
+static void login_callback(gpointer data, gint source, GaimInputCondition cond)
+{
+	struct gaim_connection *gc = data;
+	struct agg_data *gd = gc->proto_data;
+	struct gg_event *e;
+
+	if (!g_slist_find(connections, data)) {
+		close(source);
+		return;
+	}
+
+	if (gd->sess->fd != source)
+		gd->sess->fd = source;
+
+	if (source == -1) {
+		hide_login_progress(gc, _("Unable to connect."));
+		signoff(gc);
+		return;
+	}
+
+	if (gc->inpa == 0)
+		gc->inpa = gaim_input_add(gd->sess->fd, GAIM_INPUT_READ, login_callback, gc);
+
+	switch (gd->sess->state) {
+	case GG_STATE_CONNECTING_HTTP:
+	case GG_STATE_WRITING_HTTP:
+		set_login_progress(gc, 2, _("Handshake"));
+	case GG_STATE_CONNECTING_GG:
+		set_login_progress(gc, 3, _("Connecting to GG server"));
+		break;
+	case GG_STATE_WAITING_FOR_KEY:
+		set_login_progress(gc, 4, _("Waiting for server key"));
+	case GG_STATE_SENDING_KEY:
+		set_login_progress(gc, 5, _("Sending key"));
+		break;
+	default:
+		break;
+	}
+
+	if (!(e = gg_watch_fd(gd->sess))) {
+		debug_printf("login_callback: gg_watch_fd failed - CRITICAL!\n");
+		signoff(gc);
+		return;
+	}
+
+	switch (e->type) {
+	case GG_EVENT_NONE:
+		/* nothing */
+		break;
+	case GG_EVENT_CONN_SUCCESS:
+		/* Setup new input handler */
+		if (gc->inpa)
+			gaim_input_remove(gc->inpa);
+		gc->inpa = gaim_input_add(gd->sess->fd, GAIM_INPUT_READ, main_callback, gc);
+
+		/* Our signon is complete */
+		account_online(gc);
+		serv_finish_login(gc);
+
+		do_import(gc, NULL);
+		break;
+	case GG_EVENT_CONN_FAILED:
+		gaim_input_remove(gc->inpa);
+		gc->inpa = 0;
+		handle_errcode(e->event.failure, TRUE);
+		signoff(gc);
+		break;
+	default:
+		break;
+	}
+
+	gg_free_event(e);
+}
+
+static void agg_keepalive(struct gaim_connection *gc)
+{
+	struct agg_data *gd = (struct agg_data *)gc->proto_data;
+	if (gg_ping(gd->sess) < 0) {
+		signoff(gc);
+		return;
+	}
+}
+
+static void agg_login(struct aim_user *user)
+{
+	struct gaim_connection *gc = new_gaim_conn(user);
+	struct agg_data *gd = gc->proto_data = g_new0(struct agg_data, 1);
+	char buf[80];
+
+	gd->sess = g_new0(struct gg_session, 1);
+
+	if (user->proto_opt[USEROPT_NICK][0])
+		g_snprintf(gc->displayname, sizeof(gc->displayname), "%s",
+			   user->proto_opt[USEROPT_NICK]);
+
+	set_login_progress(gc, 1, _("Looking up GG server"));
+
+	if (invalid_uin(user->username)) {
+		hide_login_progress(gc, _("Invalid Gadu-Gadu UIN specified"));
+		signoff(gc);
+		return;
+	}
+
+	gc->inpa = 0;
+
+	/*
+	   if (gg_login(gd->sess, strtol(user->username, (char **)NULL, 10), user->password, 1) < 0) {
+	   debug_printf("uin=%u, pass=%s", strtol(user->username, (char **)NULL, 10), user->password); 
+	   hide_login_progress(gc, "Unable to connect.");
+	   signoff(gc);
+	   return;
+	   }
+
+	   gg_login() sucks for me, so I'm using proxy_connect()
+	 */
+
+	gd->sess->uin = (uin_t) strtol(user->username, (char **)NULL, 10);
+	gd->sess->password = g_strdup(user->password);
+	gd->sess->state = GG_STATE_CONNECTING_HTTP;
+	gd->sess->check = GG_CHECK_WRITE;
+	gd->sess->async = 1;
+	gd->sess->recv_left = 0;
+	gd->sess->fd = proxy_connect(GG_APPMSG_HOST, GG_APPMSG_PORT, login_callback, gc);
+
+	if (gd->sess->fd < 0) {
+		g_snprintf(buf, sizeof(buf), _("Connect to %s failed"), GG_APPMSG_HOST);
+		hide_login_progress(gc, buf);
+		signoff(gc);
+		return;
+	}
+}
+
+static void agg_close(struct gaim_connection *gc)
+{
+	struct agg_data *gd = (struct agg_data *)gc->proto_data;
+	if (gc->inpa)
+		gaim_input_remove(gc->inpa);
+	gg_logoff(gd->sess);
+	gg_free_session(gd->sess);
+	g_free(gc->proto_data);
+}
+
+static int agg_send_im(struct gaim_connection *gc, char *who, char *msg, int flags)
+{
+	struct agg_data *gd = (struct agg_data *)gc->proto_data;
+	gchar *imsg;
+
+	if (invalid_uin(who)) {
+		do_error_dialog(_("You are trying to send message to invalid Gadu-Gadu UIN!"),
+				_("Gadu-Gadu Error"));
+		return -1;
+	}
+
+	if (strlen(msg) > 0) {
+		imsg = charset_convert(msg, find_local_charset(), "CP1250");
+		if (gg_send_message(gd->sess, (flags & IM_FLAG_CHECKBOX) ? GG_CLASS_MSG : GG_CLASS_CHAT,
+				    strtol(who, (char **)NULL, 10), imsg) < 0)
+			return -1;
+		g_free(imsg);
+	}
+	return 1;
+}
+
+static void agg_add_buddy(struct gaim_connection *gc, char *who)
+{
+	struct agg_data *gd = (struct agg_data *)gc->proto_data;
+	if (invalid_uin(who))
+		return;
+	gg_add_notify(gd->sess, strtol(who, (char **)NULL, 10));
+}
+
+static void agg_rem_buddy(struct gaim_connection *gc, char *who)
+{
+	struct agg_data *gd = (struct agg_data *)gc->proto_data;
+	if (invalid_uin(who))
+		return;
+	gg_remove_notify(gd->sess, strtol(who, (char **)NULL, 10));
+}
+
+static void agg_add_buddies(struct gaim_connection *gc, GList *whos)
+{
+	struct agg_data *gd = (struct agg_data *)gc->proto_data;
+	uin_t *userlist = NULL;
+	int userlist_size = 0;
+
+	while (whos) {
+		if (!invalid_uin(whos->data)) {
+			userlist_size++;
+			userlist = g_renew(uin_t, userlist, userlist_size);
+			userlist[userlist_size - 1] =
+			    (uin_t) strtol((char *)whos->data, (char **)NULL, 10);
+		}
+		whos = g_list_next(whos);
+	}
+
+	if (userlist) {
+		gg_notify(gd->sess, userlist, userlist_size);
+		g_free(userlist);
+	}
+}
+
+static void search_results(gpointer data, gint source, GaimInputCondition cond)
+{
+	struct agg_search *srch = data;
+	struct gaim_connection *gc = srch->gc;
+	gchar *buf;
+	char *ptr;
+	char *webdata;
+	int len;
+	char read_data;
+	gchar **webdata_tbl;
+	int i, j;
+
+	if (!g_slist_find(connections, gc)) {
+		debug_printf("search_callback: g_slist_find error\n");
+		gaim_input_remove(srch->inpa);
+		g_free(srch);
+		close(source);
+		return;
+	}
+
+	webdata = NULL;
+	len = 0;
+
+	while (read(source, &read_data, 1) > 0 || errno == EWOULDBLOCK) {
+		if (errno == EWOULDBLOCK) {
+			errno = 0;
+			continue;
+		}
+
+		if (!read_data)
+			continue;
+
+		len++;
+		webdata = g_realloc(webdata, len);
+		webdata[len - 1] = read_data;
+	}
+
+	webdata = g_realloc(webdata, len + 1);
+	webdata[len] = 0;
+
+	gaim_input_remove(srch->inpa);
+	g_free(srch);
+	close(source);
+
+	if ((ptr = strstr(webdata, "query_results:")) == NULL || (ptr = strchr(ptr, '\n')) == NULL) {
+		debug_printf("search_callback: pubdir result [%s]\n", webdata);
+		g_free(webdata);
+		do_error_dialog(_("Couldn't get search results"), _("Gadu-Gadu Error"));
+		return;
+	}
+	ptr++;
+
+	buf = g_strconcat("<B>", _("Gadu-Gadu Search Engine"), "</B><BR>\n", NULL);
+
+	webdata_tbl = g_strsplit(ptr, "\n", AGG_PUBDIR_MAX_ENTRIES);
+
+	g_free(webdata);
+
+	j = 0;
+
+	/* Parse array */
+	for (i = 0; webdata_tbl[i] != NULL; i++) {
+		gchar *p, *oldibuf;
+		static gchar *ibuf;
+
+		g_strdelimit(webdata_tbl[i], "\t\n", ' ');
+
+		/* GG_PUBDIR_HOST service returns 7 lines of data per directory entry */
+		if (i % 8 == 0)
+			j = 0;
+
+		p = charset_convert(g_strstrip(webdata_tbl[i]), "CP1250", find_local_charset());
+
+		oldibuf = ibuf;
+
+		switch (j) {
+		case 0:
+			ibuf = g_strconcat("---------------------------------<BR>\n", NULL);
+			oldibuf = ibuf;
+			ibuf = g_strconcat(oldibuf, "<B>", _("Active"), ":</B> ",
+					   (atoi(p) == 2) ? _("yes") : _("no"), "<BR>\n", NULL);
+			g_free(oldibuf);
+			break;
+		case 1:
+			ibuf = g_strconcat(oldibuf, "<B>", _("UIN"), ":</B> ", p, "<BR>\n", NULL);
+			g_free(oldibuf);
+			break;
+		case 2:
+			ibuf = g_strconcat(oldibuf, "<B>", _("First name"), ":</B> ", p, "<BR>\n", NULL);
+			g_free(oldibuf);
+			break;
+		case 3:
+			ibuf =
+			    g_strconcat(oldibuf, "<B>", _("Second Name"), ":</B> ", p, "<BR>\n", NULL);
+			g_free(oldibuf);
+			break;
+		case 4:
+			ibuf = g_strconcat(oldibuf, "<B>", _("Nick"), ":</B> ", p, "<BR>\n", NULL);
+			g_free(oldibuf);
+			break;
+		case 5:
+			/* Hack, invalid_uin does what we really want here but may change in future */
+			if (invalid_uin(p))
+				ibuf =
+				    g_strconcat(oldibuf, "<B>", _("Birth year"), ":</B> <BR>\n", NULL);
+			else
+				ibuf =
+				    g_strconcat(oldibuf, "<B>", _("Birth year"), ":</B> ", p, "<BR>\n",
+						NULL);
+			g_free(oldibuf);
+			break;
+		case 6:
+			if (atoi(p) == GG_GENDER_FEMALE)
+				ibuf = g_strconcat(oldibuf, "<B>", _("Sex"), ":</B> woman<BR>\n", NULL);
+			else if (atoi(p) == GG_GENDER_MALE)
+				ibuf = g_strconcat(oldibuf, "<B>", _("Sex"), ":</B> man<BR>\n", NULL);
+			else
+				ibuf = g_strconcat(oldibuf, "<B>", _("Sex"), ":</B> <BR>\n", NULL);
+			g_free(oldibuf);
+			break;
+		case 7:
+			ibuf = g_strconcat(oldibuf, "<B>", _("City"), ":</B> ", p, "<BR>\n", NULL);
+			g_free(oldibuf);
+
+			/* We have all lines, so add them to buffer */
+			{
+				gchar *oldbuf = buf;
+				buf = g_strconcat(oldbuf, ibuf, NULL);
+				g_free(oldbuf);
+			}
+
+			g_free(ibuf);
+			break;
+		}
+
+		g_free(p);
+
+		j++;
+	}
+
+	g_strfreev(webdata_tbl);
+
+	g_show_info_text(buf, NULL);
+
+	g_free(buf);
+}
+
+static void search_callback(gpointer data, gint source, GaimInputCondition cond)
+{
+	struct agg_search *srch = data;
+	struct gaim_connection *gc = srch->gc;
+	gchar *search_data = srch->search_data;
+	gchar *buf;
+	char *ptr;
+
+	debug_printf("search_callback enter: begin\n");
+
+	if (!g_slist_find(connections, gc)) {
+		debug_printf("search_callback: g_slist_find error\n");
+		g_free(search_data);
+		g_free(srch);
+		close(source);
+		return;
+	}
+
+	if (source == -1) {
+		g_free(search_data);
+		g_free(srch);
+		return;
+	}
+
+	ptr = encode_postdata(search_data);
+	g_free(search_data);
+
+	debug_printf("search_callback: pubdir request [%s]\n", ptr);
+
+	buf = g_strdup_printf("POST " AGG_PUBDIR_FORM " HTTP/1.0\r\n"
+			      "Host: " GG_PUBDIR_HOST "\r\n"
+			      "Content-Type: application/x-www-form-urlencoded\r\n"
+			      "User-Agent: Mozilla/4.7 [en] (Win98; I)\r\n"
+			      "Content-Length: %d\r\n"
+			      "Pragma: no-cache\r\n" "\r\n" "%s\r\n", strlen(ptr), ptr);
+
+	g_free(ptr);
+
+	if (write(source, buf, strlen(buf)) < strlen(buf)) {
+		g_free(buf);
+		g_free(srch);
+		close(source);
+		do_error_dialog(_("Couldn't send search request"), _("Gadu-Gadu Error"));
+		return;
+	}
+
+	g_free(buf);
+
+	srch->inpa = gaim_input_add(source, GAIM_INPUT_READ, search_results, srch);
+}
+
+static void agg_dir_search(struct gaim_connection *gc, char *first, char *middle,
+			   char *last, char *maiden, char *city, char *state, char *country, char *email)
+{
+	struct agg_search *srch = g_new0(struct agg_search, 1);
+	static char msg[AGG_BUF_LEN];
+
+	srch->gc = gc;
+
+	if (email && strlen(email)) {
+		srch->search_data = g_strdup_printf("Mode=1&Email=%s", email);
+	} else {
+		gchar *new_first = charset_convert(first, find_local_charset(), "CP1250");
+		gchar *new_last = charset_convert(last, find_local_charset(), "CP1250");
+		gchar *new_city = charset_convert(city, find_local_charset(), "CP1250");
+
+		/* For active only add &ActiveOnly= */
+		srch->search_data = g_strdup_printf("Mode=0&FirstName=%s&LastName=%s&Gender=%d"
+						    "&NickName=%s&City=%s&MinBirth=%d&MaxBirth=%d",
+						    new_first, new_last, AGG_GENDER_NONE,
+						    "", new_city, 0, 0);
+
+		g_free(new_first);
+		g_free(new_last);
+		g_free(new_city);
+	}
+
+	if (proxy_connect(GG_PUBDIR_HOST, GG_PUBDIR_PORT, search_callback, srch) < 0) {
+		g_snprintf(msg, sizeof(msg), _("Connect to search service failed (%s)"), GG_PUBDIR_HOST);
+		do_error_dialog(msg, _("Gadu-Gadu Error"));
+		g_free(srch->search_data);
+		g_free(srch);
+		return;
+	}
+}
+
+static void agg_do_action(struct gaim_connection *gc, char *action)
+{
+	if (!strcmp(action, _("Directory Search"))) {
+		show_find_info(gc);
+	}
+}
+
+static GList *agg_actions()
+{
+	GList *m = NULL;
+
+	m = g_list_append(m, _("Directory Search"));
+
+	return m;
+}
+
+static void agg_get_info(struct gaim_connection *gc, char *who)
+{
+	struct agg_search *srch = g_new0(struct agg_search, 1);
+	static char msg[AGG_BUF_LEN];
+
+	srch->gc = gc;
+
+	/* If it's invalid uin then maybe it's nickname? */
+	if (invalid_uin(who)) {
+		gchar *new_who = charset_convert(who, find_local_charset(), "CP1250");
+
+		srch->search_data = g_strdup_printf("Mode=0&FirstName=%s&LastName=%s&Gender=%d"
+						    "&NickName=%s&City=%s&MinBirth=%d&MaxBirth=%d",
+						    "", "", AGG_GENDER_NONE, new_who, "", 0, 0);
+
+		g_free(new_who);
+	} else
+		srch->search_data = g_strdup_printf("Mode=3&UserId=%s", who);
+
+	if (proxy_connect(GG_PUBDIR_HOST, GG_PUBDIR_PORT, search_callback, srch) < 0) {
+		g_snprintf(msg, sizeof(msg), _("Connect to search service failed (%s)"), GG_PUBDIR_HOST);
+		do_error_dialog(msg, _("Gadu-Gadu Error"));
+		g_free(srch->search_data);
+		g_free(srch);
+		return;
+	}
+}
+
+static char **agg_list_icon(int uc)
+{
+	guint status;
+	if (uc == UC_UNAVAILABLE)
+		return (char **)gg_sunred_xpm;
+	status = uc >> 5;
+	/* Drop all masks */
+	status &= ~(GG_STATUS_FRIENDS_MASK);
+	if (status == GG_STATUS_AVAIL)
+		return (char **)gg_sunyellow_xpm;
+	if (status == GG_STATUS_BUSY)
+		return (char **)gg_suncloud_xpm;
+	if (status == GG_STATUS_INVISIBLE)
+		return (char **)gg_sunwhitered_xpm;
+	return (char **)gg_sunyellow_xpm;
+}
+
+static void agg_set_permit_deny_dummy(struct gaim_connection *gc)
+{
+	/* It's implemented on client side because GG server doesn't support this */
+}
+
+static void agg_permit_deny_dummy(struct gaim_connection *gc, char *who)
+{
+	/* It's implemented on client side because GG server doesn't support this */
+}
+
+static struct prpl *my_protocol = NULL;
+
+void agg_init(struct prpl *ret)
+{
+	ret->protocol = PROTO_GADUGADU;
+	ret->options = 0;
+	ret->name = agg_name;
+	ret->checkbox = _("Send as message");
+	ret->list_icon = agg_list_icon;
+	ret->away_states = agg_away_states;
+	ret->actions = agg_actions;
+	ret->do_action = agg_do_action;
+	ret->user_opts = agg_user_opts;
+	ret->buddy_menu = agg_buddy_menu;
+	ret->chat_info = NULL;
+	ret->login = agg_login;
+	ret->close = agg_close;
+	ret->send_im = agg_send_im;
+	ret->set_info = NULL;
+	ret->get_info = agg_get_info;
+	ret->set_away = agg_set_away;
+	ret->set_dir = NULL;
+	ret->get_dir = agg_get_info;
+	ret->dir_search = agg_dir_search;
+	ret->set_idle = NULL;
+	ret->change_passwd = NULL;
+	ret->add_buddy = agg_add_buddy;
+	ret->add_buddies = agg_add_buddies;
+	ret->remove_buddy = agg_rem_buddy;
+	ret->add_permit = agg_permit_deny_dummy;
+	ret->add_deny = agg_permit_deny_dummy;
+	ret->rem_permit = agg_permit_deny_dummy;
+	ret->rem_deny = agg_permit_deny_dummy;
+	ret->set_permit_deny = agg_set_permit_deny_dummy;
+	ret->warn = NULL;
+	ret->join_chat = NULL;
+	ret->chat_invite = NULL;
+	ret->chat_leave = NULL;
+	ret->chat_whisper = NULL;
+	ret->chat_send = NULL;
+	ret->keepalive = agg_keepalive;
+	ret->normalize = NULL;
+	my_protocol = ret;
+}
+
+#ifndef STATIC
+
+char *gaim_plugin_init(GModule *handle)
+{
+	load_protocol(agg_init, sizeof(struct prpl));
+	return NULL;
+}
+
+void gaim_plugin_remove()
+{
+	struct prpl *p = find_prpl(PROTO_GADUGADU);
+	if (p == my_protocol)
+		unload_protocol(p);
+}
+
+char *name()
+{
+	return "Gadu-Gadu";
+}
+
+char *description()
+{
+	return PRPL_DESC("Gadu-Gadu");
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/gg/libgg.c	Sat Sep 29 23:06:30 2001 +0000
@@ -0,0 +1,1247 @@
+/* $Id: libgg.c 2406 2001-09-29 23:06:30Z warmenhoven $ */
+
+/*
+ *  (C) Copyright 2001 Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License Version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+#include <stdarg.h>
+#include <pwd.h>
+#include "endian.h"
+#include "libgg.h"
+
+int gg_debug_level = 0;
+
+#ifdef GG_DEBUG
+
+/*
+ * gg_debug_real()
+ *
+ * wyrzuca komunikat o danym poziomie, o ile użytkownik sobie tego życzy.
+ *
+ *  - level - poziom wiadomości,
+ *  - format... - treść wiadomości (printf-alike.)
+ *
+ * niczego nie zwraca.
+ */
+void gg_debug_real(int level, char *format, ...)
+{
+	va_list ap;
+	
+	if ((gg_debug_level & level)) {
+		va_start(ap, format);
+		vprintf(format, ap);
+		va_end(ap);
+	}
+}
+
+#endif /* GG_DEBUG */
+
+/*
+ * fix32() // funkcja wewnętrzna
+ *
+ * dla maszyn big-endianowych zamienia kolejność bajtów w ,,long''ach.
+ */
+static inline unsigned long fix32(unsigned long x)
+{
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	return x;
+#else
+	char *y = &x;
+
+	return (y[0] << 24 + y[1] << 16 + y[2] << 8 + y[3]);
+#endif		
+}
+
+/*
+ * fix16() // funkcja wewnętrzna
+ *
+ * dla maszyn big-endianowych zamienia kolejność bajtów w ,,short''ach.
+ */
+static inline unsigned short fix16(unsigned short x)
+{
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	return x;
+#else
+	char *y = &x;
+
+	return (y[0] << 8 + y[1]);
+#endif	
+}
+
+/*
+ * gg_alloc_sprintf() // funkcja wewnętrzna
+ *
+ * robi dokładnie to samo, co sprintf(), tyle że alokuje sobie wcześniej
+ * miejsce na dane. jak znam życie, ze względu na różnice między funkcjami
+ * vsnprintf() na różnych platformach, nie będzie działało ;)
+ *
+ *  - format, ... - parametry takie same jak w innych funkcjach *printf()
+ *
+ * zwraca zaalokowany buforek, który wypadałoby później zwolnić, lub NULL
+ * jeśli nie udało się wykonać zadania.
+ */
+char *gg_alloc_sprintf(char *format, ...)
+{
+	va_list ap;
+	char *buf = NULL;
+	int size;
+
+	va_start(ap, format);
+
+	if ((size = vsnprintf(buf, 0, format, ap)) < 0)
+		return NULL;
+
+	if (!(buf = malloc(size + 1)))
+		return NULL;
+
+	vsnprintf(buf, size + 1, format, ap);
+
+	va_end(ap);
+
+	return buf;
+}
+
+/*
+ * gg_resolve() // funkcja wewnętrzna
+ *
+ * tworzy pipe'y, forkuje się i w drugim procesie zaczyna resolvować 
+ * podanego hosta. zapisuje w sesji deskryptor pipe'u. jeśli coś tam
+ * będzie gotowego, znaczy, że można wczytać ,,struct in_addr''. jeśli
+ * nie znajdzie, zwraca INADDR_NONE.
+ *
+ *  - fd - wskaźnik gdzie wrzucić deskryptor,
+ *  - pid - gdzie wrzucić pid dzieciaka,
+ *  - hostname - nazwa hosta do zresolvowania.
+ *
+ * zwraca 0 jeśli udało się odpalić proces lub -1 w przypadku błędu.
+ */
+int gg_resolve(int *fd, int *pid, char *hostname)
+{
+	int pipes[2], res;
+	struct in_addr a;
+
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_resolve(..., \"%s\");\n", hostname);
+	
+	if (!fd | !pid) {
+		errno = EFAULT;
+		return -1;
+	}
+
+	if (pipe(pipes) == -1)
+		return -1;
+
+	if ((res = fork()) == -1)
+		return -1;
+
+	if (!res) {
+		if ((a.s_addr = inet_addr(hostname)) == INADDR_NONE) {
+			struct hostent *he;
+		
+			if (!(he = gethostbyname(hostname)))
+				a.s_addr = INADDR_NONE;
+			else
+				memcpy((char*) &a, he->h_addr, sizeof(a));
+		}
+
+		write(pipes[1], &a, sizeof(a));
+
+		exit(0);
+	}
+
+	close(pipes[1]);
+
+	*fd = pipes[0];
+	*pid = res;
+
+	return 0;
+}
+
+/*
+ * gg_connect() // funkcja wewnętrzna
+ *
+ * łączy się z serwerem. pierwszy argument jest typu (void *), żeby nie
+ * musieć niczego inkludować w libgg.h i nie psuć jakiś głupich zależności
+ * na dziwnych systemach.
+ *
+ *  - addr - adres serwera (struct in_addr *),
+ *  - port - port serwera,
+ *  - async - ma być asynchroniczne połączenie?
+ *
+ * zwraca połączonego socketa lub -1 w przypadku błędu. zobacz errno.
+ */
+int gg_connect(void *addr, int port, int async)
+{
+	int sock, one = 1;
+	struct sockaddr_in sin;
+	struct in_addr *a = addr;
+
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_connect(%s, %d, %d);\n", inet_ntoa(*a), port, async);
+	
+	if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
+		gg_debug(GG_DEBUG_MISC, "-- socket() failed. errno = %d (%s)\n", errno, strerror(errno));
+		return -1;
+	}
+
+	if (async) {
+		if (ioctl(sock, FIONBIO, &one) == -1) {
+			gg_debug(GG_DEBUG_MISC, "-- ioctl() failed. errno = %d (%s)\n", errno, strerror(errno));
+			return -1;
+		}
+	}
+
+	sin.sin_port = htons(port);
+	sin.sin_family = AF_INET;
+	sin.sin_addr.s_addr = a->s_addr;
+	
+	if (connect(sock, (struct sockaddr*) &sin, sizeof(sin)) == -1) {
+		gg_debug(GG_DEBUG_MISC, "-- connect() failed. errno = %d (%s)\n", errno, strerror(errno));
+		if (errno && (!async || errno != EINPROGRESS))
+			return -1;
+	}
+	
+	return sock;
+}
+
+/*
+ * gg_read_line() // funkcja wewnętrzna
+ *
+ * czyta jedną linię tekstu z socketa.
+ *
+ *  - sock - socket,
+ *  - buf - wskaźnik bufora,
+ *  - length - długość bufora.
+ *
+ * olewa błędy. jeśli na jakiś trafi, potraktuje go jako koniec linii.
+ */
+static void gg_read_line(int sock, char *buf, int length)
+{
+	int ret;
+
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_read_line(...);\n");
+	
+	for (; length > 1; buf++, length--) {
+		do {
+			if ((ret = read(sock, buf, 1)) == -1 && errno != EINTR) {
+				*buf = 0;
+				return;
+			}
+		} while (ret == -1 && errno == EINTR);
+
+		if (*buf == '\n') {
+			buf++;
+			break;
+		}
+	}
+
+	*buf = 0;
+	return;
+}
+
+/*
+ * gg_recv_packet() // funkcja wewnętrzna
+ *
+ * odbiera jeden pakiet gg i zwraca wskaźnik do niego. pamięć po nim
+ * wypadałoby uwolnić.
+ *
+ *  - sock - połączony socket.
+ *
+ * jeśli wystąpił błąd, zwraca NULL. reszta w errno.
+ */
+static void *gg_recv_packet(struct gg_session *sess)
+{
+	struct gg_header h;
+	char *buf = NULL;
+	int ret = 0, offset, size = 0;
+
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_recv_packet(...);\n");
+	
+	if (!sess) {
+		errno = EFAULT;
+		return NULL;
+	}
+
+	if (sess->recv_left < 1) {
+		while (ret != sizeof(h)) {
+			ret = read(sess->fd, &h, sizeof(h));
+			gg_debug(GG_DEBUG_MISC, "-- header recv(..., %d) = %d\n", sizeof(h), ret);
+			if (ret < sizeof(h)) {
+				if (errno != EINTR) {
+					gg_debug(GG_DEBUG_MISC, "-- errno = %d (%s)\n", errno, strerror(errno));
+					return NULL;
+				}
+			}
+		}
+
+		h.type = fix32(h.type);
+		h.length = fix32(h.length);
+	} else {
+		memcpy(&h, sess->recv_buf, sizeof(h));
+	}
+
+	/* jakieś sensowne limity na rozmiar pakietu */
+	if (h.length < 0 || h.length > 65535) {
+		gg_debug(GG_DEBUG_MISC, "-- invalid packet length (%d)\n", h.length);
+		errno = ERANGE;
+		return NULL;
+	}
+
+	if (sess->recv_left > 0) {
+		gg_debug(GG_DEBUG_MISC, "-- resuming last gg_recv_packet()\n");
+		size = sess->recv_left;
+		offset = sess->recv_done;
+		buf = sess->recv_buf;
+	} else {
+		if (!(buf = malloc(sizeof(h) + h.length + 1))) {
+			gg_debug(GG_DEBUG_MISC, "-- not enough memory\n");
+			return NULL;
+		}
+
+		memcpy(buf, &h, sizeof(h));
+
+		offset = 0;
+		size = h.length;
+	}
+
+	while (size > 0) {
+		ret = read(sess->fd, buf + sizeof(h) + offset, size);
+		gg_debug(GG_DEBUG_MISC, "-- body recv(..., %d) = %d\n", size, ret);
+		if (ret > -1 && ret <= size) {
+			offset += ret;
+			size -= ret;
+		} else if (ret == -1) {	
+			gg_debug(GG_DEBUG_MISC, "-- errno = %d (%s)\n", errno, strerror(errno));
+			if (errno == EAGAIN) {
+				gg_debug(GG_DEBUG_MISC, "-- %d bytes received, %d left\n", offset, size);
+				sess->recv_buf = buf;
+				sess->recv_left = size;
+				sess->recv_done = offset;
+				return NULL;
+			}
+			if (errno != EINTR) {
+				/* errno = EINVAL; */
+				free(buf);
+				return NULL;
+			}
+		}
+	}
+
+	sess->recv_left = 0;
+
+#ifdef GG_DEBUG
+	if ((gg_debug_level & GG_DEBUG_DUMP)) {
+		int i;
+
+		gg_debug(GG_DEBUG_DUMP, ">> received packet (type=%.2x):", h.type);
+		for (i = 0; i < sizeof(h) + h.length; i++) 
+			gg_debug(GG_DEBUG_DUMP, " %.2x", (unsigned char) buf[i]);
+		gg_debug(GG_DEBUG_DUMP, "\n");
+	}
+#endif
+
+	return buf;
+}
+
+/*
+ * gg_send_packet() // funkcja wewnętrzna
+ *
+ * konstruuje pakiet i wysyła go w do serwera.
+ *
+ *  - sock - połączony socket,
+ *  - type - typ pakietu,
+ *  - packet - wskaźnik do struktury pakietu,
+ *  - length - długość struktury pakietu,
+ *  - payload - dodatkowy tekst doklejany do pakietu (np. wiadomość),
+ *  - payload_length - długość dodatkowego tekstu.
+ *
+ * jeśli poszło dobrze, zwraca 0. w przypadku błędu -1. jeśli errno=ENOMEM,
+ * zabrakło pamięci. inaczej był błąd przy wysyłaniu pakietu. dla errno=0
+ * nie wysłano całego pakietu.
+ */
+static int gg_send_packet(int sock, int type, void *packet, int length, void *payload, int payload_length)
+{
+	struct gg_header *h;
+	int res, plen;
+	char *tmp;
+
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_send_packet(0x%.2x, %d, %d);\n", type, length, payload_length);
+	
+	if (length < 0 || payload_length < 0) {
+		gg_debug(GG_DEBUG_MISC, "-- invalid packet/payload length\n");
+		errno = ERANGE;
+		return -1;
+	}
+
+	if (!(tmp = malloc(sizeof(struct gg_header) + length + payload_length))) {
+		gg_debug(GG_DEBUG_MISC, "-- not enough memory\n");
+		return -1;
+	}
+
+	h = (struct gg_header*) tmp;
+	h->type = fix32(type);
+	h->length = fix32(length + payload_length);
+
+	if (packet)
+		memcpy(tmp + sizeof(struct gg_header), packet, length);
+	if (payload)
+		memcpy(tmp + sizeof(struct gg_header) + length, payload, payload_length);
+
+#ifdef GG_DEBUG
+	if ((gg_debug_level & GG_DEBUG_DUMP)) {
+		int i;
+
+		gg_debug(GG_DEBUG_DUMP, "%%%% sending packet (type=%.2x):", fix32(h->type));
+		for (i = 0; i < sizeof(struct gg_header) + fix32(h->length); i++)
+			gg_debug(GG_DEBUG_DUMP, " %.2x", (unsigned char) tmp[i]);
+		gg_debug(GG_DEBUG_DUMP, "\n");
+	}
+#endif
+
+	plen = sizeof(struct gg_header) + length + payload_length;
+	
+	if ((res = write(sock, tmp, plen)) < plen) {
+		gg_debug(GG_DEBUG_MISC, "-- write() failed. res = %d, errno = %d (%s)\n", res, errno, strerror(errno));
+		free(tmp);
+		return -1;
+	}
+
+	free(tmp);	
+	return 0;
+}
+
+
+/*
+ * gg_login()
+ *
+ * rozpoczyna procedurę łączenia się z serwerem. resztę obsłguje się przez
+ * gg_watch_event.
+ *
+ *  - uin - numerek usera,
+ *  - password - jego hasełko,
+ *  - async - ma być asynchronicznie?
+ *
+ * UWAGA! program musi obsłużyć SIGCHLD, jeśli łączy się asynchronicznie,
+ * żeby zrobić pogrzeb zmarłemu procesowi resolvera.
+ *
+ * w przypadku błędu zwraca NULL, jeśli idzie dobrze (async) albo poszło
+ * dobrze (sync), zwróci wskaźnik do zaalokowanej struktury `gg_session'.
+ */
+struct gg_session *gg_login(uin_t uin, char *password, int async)
+{
+	struct gg_session *sess;
+
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%u, \"...\", %d);\n", uin, async);
+
+	if (!(sess = malloc(sizeof(*sess))))
+		return NULL;
+
+	sess->uin = uin;
+	if (!(sess->password = strdup(password))) {
+		free(sess);
+		return NULL;
+	}
+	sess->state = GG_STATE_RESOLVING;
+	sess->check = GG_CHECK_READ;
+	sess->async = async;
+	sess->seq = 0;
+	sess->recv_left = 0;
+
+	if (async) {
+		if (gg_resolve(&sess->fd, &sess->pid, GG_APPMSG_HOST)) {
+			gg_debug(GG_DEBUG_MISC, "-- resolving failed\n");
+			free(sess);
+			return NULL;
+		}
+	} else {
+		struct in_addr a;
+
+		if ((a.s_addr = inet_addr(GG_APPMSG_HOST)) == INADDR_NONE) {
+			struct hostent *he;
+	
+			if (!(he = gethostbyname(GG_APPMSG_HOST))) {
+				gg_debug(GG_DEBUG_MISC, "-- host %s not found\n", GG_APPMSG_HOST);
+				free(sess);
+				return NULL;
+			} else
+				memcpy((char*) &a, he->h_addr, sizeof(a));
+		}
+
+		if (!(sess->fd = gg_connect(&a, GG_APPMSG_PORT, 0)) == -1) {
+			gg_debug(GG_DEBUG_MISC, "-- connection failed\n");
+			free(sess);
+			return NULL;
+		}
+
+		sess->state = GG_STATE_CONNECTING_HTTP;
+
+		while (sess->state != GG_STATE_CONNECTED) {
+			struct gg_event *e;
+
+			if (!(e = gg_watch_fd(sess))) {
+				gg_debug(GG_DEBUG_MISC, "-- some nasty error in gg_watch_fd()\n");
+				free(sess);
+				return NULL;
+			}
+
+			if (e->type == GG_EVENT_CONN_FAILED) {
+				gg_debug(GG_DEBUG_MISC, "-- could not login\n");
+				gg_free_event(e);
+				free(sess);
+				return NULL;
+			}
+
+			gg_free_event(e);
+		}
+	}
+
+	return sess;
+}
+
+/* 
+ * gg_free_session()
+ *
+ * zwalnia pamięć zajmowaną przez opis sesji.
+ *
+ *  - sess - opis sesji.
+ *
+ * nie zwraca niczego, bo i po co?
+ */
+void gg_free_session(struct gg_session *sess)
+{
+	if (!sess)
+		return;
+
+	free(sess->password);
+	free(sess);
+}
+
+/*
+ * gg_change_status()
+ *
+ * zmienia status użytkownika. przydatne do /away i /busy oraz /quit.
+ *
+ *  - sess - opis sesji,
+ *  - status - nowy status użytkownika.
+ *
+ * jeśli wysłał pakiet zwraca 0, jeśli nie udało się, zwraca -1.
+ */
+int gg_change_status(struct gg_session *sess, int status)
+{
+	struct gg_new_status p;
+
+	if (!sess) {
+		errno = EFAULT;
+		return -1;
+	}
+
+	if (sess->state != GG_STATE_CONNECTED) {
+		errno = ENOTCONN;
+		return -1;
+	}
+
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status(..., %d);\n", status);
+
+	p.status = fix32(status);
+
+	return gg_send_packet(sess->fd, GG_NEW_STATUS, &p, sizeof(p), NULL, 0);
+}
+
+/*
+ * gg_logoff()
+ *
+ * wylogowuje użytkownika i zamyka połączenie.
+ *
+ *  - sock - deskryptor socketu.
+ *
+ * nie zwraca błędów. skoro się żegnamy, to olewamy wszystko.
+ */
+void gg_logoff(struct gg_session *sess)
+{
+	if (!sess)
+		return;
+
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_logoff(...);\n");
+
+	if (sess->state == GG_STATE_CONNECTED)
+		gg_change_status(sess, GG_STATUS_NOT_AVAIL);
+	
+	if (sess->fd) {
+		shutdown(sess->fd, 2);
+		close(sess->fd);
+	}
+}
+
+/*
+ * gg_send_message()
+ *
+ * wysyła wiadomość do innego użytkownika. zwraca losowy numer
+ * sekwencyjny, który można olać albo wykorzystać do potwierdzenia.
+ *
+ *  - sess - opis sesji,
+ *  - msgclass - rodzaj wiadomości,
+ *  - recipient - numer adresata,
+ *  - message - treść wiadomości.
+ *
+ * w przypadku błędu zwraca -1, inaczej numer sekwencyjny.
+ */
+int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, unsigned char *message)
+{
+	struct gg_send_msg s;
+
+	if (!sess) {
+		errno = EFAULT;
+		return -1;
+	}
+	
+	if (sess->state != GG_STATE_CONNECTED) {
+		errno = ENOTCONN;
+		return -1;
+	}
+
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message(..., %d, %u, \"...\");\n", msgclass, recipient);
+
+	s.recipient = fix32(recipient);
+	if (!sess->seq)
+		sess->seq = 0x01740000 | (rand() & 0xffff);
+	s.seq = fix32(sess->seq);
+	s.msgclass = fix32(msgclass);
+	sess->seq += (rand() % 0x300) + 0x300;
+	
+	if (gg_send_packet(sess->fd, GG_SEND_MSG, &s, sizeof(s), message, strlen(message) + 1) == -1)
+		return -1;
+
+	return fix32(s.seq);
+}
+
+/*
+ * gg_ping()
+ *
+ * wysyła do serwera pakiet typu yeah-i'm-still-alive.
+ *
+ *  - sess - zgadnij.
+ *
+ * jeśli nie powiodło się wysłanie pakietu, zwraca -1. otherwise 0.
+ */
+int gg_ping(struct gg_session *sess)
+{
+	if (!sess) {
+		errno = EFAULT;
+		return -1;
+	}
+
+	if (sess->state != GG_STATE_CONNECTED) {
+		errno = ENOTCONN;
+		return -1;
+	}
+
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_ping(...);\n");
+
+	return gg_send_packet(sess->fd, GG_PING, NULL, 0, NULL, 0);
+}
+
+/*
+ * gg_free_event()
+ *
+ * zwalnia pamięć zajmowaną przez informację o zdarzeniu
+ *
+ *  - event - wskaźnik do informacji o zdarzeniu
+ *
+ * nie ma czego zwracać.
+ */
+void gg_free_event(struct gg_event *e)
+{
+	if (!e)
+		return;
+	if (e->type == GG_EVENT_MSG)
+		free(e->event.msg.message);
+	if (e->type == GG_EVENT_NOTIFY)
+		free(e->event.notify);
+	free(e);
+}
+
+/*
+ * gg_notify()
+ *
+ * wysyła serwerowi listę ludków, za którymi tęsknimy.
+ *
+ *  - sess - identyfikator sesji,
+ *  - userlist - wskaźnik do tablicy numerów,
+ *  - count - ilość numerków.
+ *
+ * jeśli udało się, zwraca 0. jeśli błąd, dostajemy -1.
+ */
+int gg_notify(struct gg_session *sess, uin_t *userlist, int count)
+{
+	struct gg_notify *n;
+	uin_t *u;
+	int i, res = 0;
+
+	if (!sess) {
+		errno = EFAULT;
+		return -1;
+	}
+	
+	if (sess->state != GG_STATE_CONNECTED) {
+		errno = ENOTCONN;
+		return -1;
+	}
+
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_notify(..., %d);\n", count);
+	
+	if (!userlist || !count)
+		return 0;
+	
+	if (!(n = (struct gg_notify*) malloc(sizeof(*n) * count)))
+		return -1;
+	
+	for (u = userlist, i = 0; i < count; u++, i++) { 
+		n[i].uin = fix32(*u);
+		n[i].dunno1 = 3;
+	}
+	
+	if (gg_send_packet(sess->fd, GG_NOTIFY, n, sizeof(*n) * count, NULL, 0) == -1)
+		res = -1;
+
+	free(n);
+
+	return res;
+}
+
+/*
+ * gg_add_notify()
+ *
+ * dodaje w locie do listy ukochanych dany numerek.
+ *
+ *  - sess - identyfikator sesji,
+ *  - uin - numerek ukochanej.
+ *
+ * jeśli udało się wysłać, daje 0. inaczej -1.
+ */
+int gg_add_notify(struct gg_session *sess, uin_t uin)
+{
+	struct gg_add_remove a;
+
+	if (!sess) {
+		errno = EFAULT;
+		return -1;
+	}
+
+	if (sess->state != GG_STATE_CONNECTED) {
+		errno = ENOTCONN;
+		return -1;
+	}
+	
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_add_notify(..., %u);\n", uin);
+	
+	a.uin = fix32(uin);
+	a.dunno1 = 3;
+	
+	return gg_send_packet(sess->fd, GG_ADD_NOTIFY, &a, sizeof(a), NULL, 0);
+}
+
+/*
+ * gg_remove_notify()
+ *
+ * w locie usuwa z listy zainteresowanych.
+ *
+ *  - sess - id sesji,
+ *  - uin - numerek.
+ *
+ * zwraca -1 jeśli był błąd, 0 jeśli się udało wysłać pakiet.
+ */
+int gg_remove_notify(struct gg_session *sess, uin_t uin)
+{
+	struct gg_add_remove a;
+
+	if (!sess) {
+		errno = EFAULT;
+		return -1;
+	}
+
+	if (sess->state != GG_STATE_CONNECTED) {
+		errno = ENOTCONN;
+		return -1;
+	}
+
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_remove_notify(..., %u);\n", uin);
+	
+	a.uin = fix32(uin);
+	a.dunno1 = 3;
+	
+	return gg_send_packet(sess->fd, GG_REMOVE_NOTIFY, &a, sizeof(a), NULL, 0);
+}
+
+/*
+ * gg_watch_fd_connected() // funkcja wewnętrzna
+ *
+ * patrzy na socketa, odbiera pakiet i wypełnia strukturę zdarzenia.
+ *
+ *  - sock - lalala, trudno zgadnąć.
+ *
+ * jeśli błąd -1, jeśli dobrze 0.
+ */
+static int gg_watch_fd_connected(struct gg_session *sess, struct gg_event *e)
+{
+	struct gg_header *h;
+	void *p;
+
+	if (!sess) {
+		errno = EFAULT;
+		return -1;
+	}
+
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_watch_fd_connected(...);\n");
+
+	if (!(h = gg_recv_packet(sess))) {
+		gg_debug(GG_DEBUG_MISC, "-- gg_recv_packet failed. errno = %d (%d)\n", errno, strerror(errno));
+		return -1;
+	}
+
+	p = (void*) h + sizeof(struct gg_header);
+	
+	if (h->type == GG_RECV_MSG) {
+		struct gg_recv_msg *r = p;
+
+		gg_debug(GG_DEBUG_MISC, "-- received a message\n");
+
+		if (h->length >= sizeof(*r)) {
+			e->type = GG_EVENT_MSG;
+			e->event.msg.msgclass = fix32(r->msgclass);
+			e->event.msg.sender = fix32(r->sender);
+			e->event.msg.message = strdup((char*) r + sizeof(*r));
+		}
+	}
+
+	if (h->type == GG_NOTIFY_REPLY) {
+		struct gg_notify_reply *n = p;
+
+		gg_debug(GG_DEBUG_MISC, "-- received a notify reply\n");
+		
+		e->type = GG_EVENT_NOTIFY;
+		if (!(e->event.notify = (void*) malloc(h->length + 2 * sizeof(*n)))) {
+			gg_debug(GG_DEBUG_MISC, "-- not enough memory\n");
+			free(h);
+			return -1;
+		}
+		memcpy(e->event.notify, p, h->length);
+		e->event.notify[h->length / sizeof(*n)].uin = 0;
+	}
+
+	if (h->type == GG_STATUS) {
+		struct gg_status *s = p;
+
+		gg_debug(GG_DEBUG_MISC, "-- received a status change\n");
+
+		if (h->length >= sizeof(*s)) {
+			e->type = GG_EVENT_STATUS;
+			memcpy(&e->event.status, p, h->length);
+		}
+	}
+
+	if (h->type == GG_SEND_MSG_ACK) {
+		struct gg_send_msg_ack *s = p;
+
+		gg_debug(GG_DEBUG_MISC, "-- received a message ack\n");
+
+		if (h->length >= sizeof(*s)) {
+			e->type = GG_EVENT_ACK;
+			e->event.ack.status = fix32(s->status);
+			e->event.ack.recipient = fix32(s->recipient);
+			e->event.ack.seq = fix32(s->seq);
+		}
+	}
+
+	free(h);
+
+	return 0;
+}
+
+/*
+ * gg_chomp() // funkcja wewnętrzna
+ *
+ * ucina "\r\n" lub "\n" z końca linii.
+ *
+ *  - line - ofiara operacji plastycznej.
+ *
+ * niczego nie zwraca.
+ */
+static void gg_chomp(char *line)
+{
+	if (!line || strlen(line) < 1)
+		return;
+
+	if (line[strlen(line) - 1] == '\n')
+		line[strlen(line) - 1] = 0;
+	if (line[strlen(line) - 1] == '\r')
+		line[strlen(line) - 1] = 0;
+}
+
+/*
+ * gg_watch_fd()
+ *
+ * funkcja wywoływana, gdy coś się stanie na obserwowanym deskryptorze.
+ * zwraca klientowi informację o tym, co się dzieje.
+ *
+ *  - sess - identyfikator sesji.
+ *
+ * zwraca wskaźnik do struktury gg_event, którą trzeba zwolnić później
+ * za pomocą gg_free_event(). jesli rodzaj zdarzenia jest równy
+ * GG_EVENT_NONE, należy je olać kompletnie. jeśli zwróciło NULL,
+ * stało się coś niedobrego -- albo brakło pamięci albo zerwało
+ * połączenie albo coś takiego.
+ */
+struct gg_event *gg_watch_fd(struct gg_session *sess)
+{
+	struct gg_event *e;
+	int res = 0;
+
+	if (!sess) {
+		errno = EFAULT;
+		return NULL;
+	}
+
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_watch_fd(...);\n");
+
+	if (!(e = (void*) malloc(sizeof(*e)))) {
+		gg_debug(GG_DEBUG_MISC, "-- not enough memory\n");
+		return NULL;
+	}
+
+	e->type = GG_EVENT_NONE;
+
+	switch (sess->state) {
+		case GG_STATE_RESOLVING:
+		{
+			struct in_addr a;
+
+			gg_debug(GG_DEBUG_MISC, "== GG_STATE_RESOLVING\n");
+
+			if (read(sess->fd, &a, sizeof(a)) < sizeof(a) || a.s_addr == INADDR_NONE) {
+				gg_debug(GG_DEBUG_MISC, "-- resolving failed\n");				
+
+				e->type = GG_EVENT_CONN_FAILED;
+				e->event.failure = GG_FAILURE_RESOLVING;
+				sess->state = GG_STATE_IDLE;
+
+				close(sess->fd);
+
+				break;
+			}
+
+			close(sess->fd);
+
+			waitpid(sess->pid, NULL, 0);
+
+			gg_debug(GG_DEBUG_MISC, "-- resolved, now connecting\n");
+
+			if ((sess->fd = gg_connect(&a, GG_APPMSG_PORT, sess->async)) == -1) {
+				gg_debug(GG_DEBUG_MISC, "-- connection failed\n");
+
+				e->type = GG_EVENT_CONN_FAILED;
+				e->event.failure = GG_FAILURE_CONNECTING;
+				sess->state = GG_STATE_IDLE;
+				break;
+			}
+
+			sess->state = GG_STATE_CONNECTING_HTTP;
+			sess->check = GG_CHECK_WRITE;
+
+			break;
+		}
+
+		case GG_STATE_CONNECTING_HTTP:
+		{
+			char buf[1024];
+			int res, res_size = sizeof(res);
+
+			gg_debug(GG_DEBUG_MISC, "== GG_STATE_CONNECTING_HTTP\n");
+
+			if (sess->async && (getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) {
+				gg_debug(GG_DEBUG_MISC, "-- http connection failed, errno = %d (%s)\n", res, strerror(res));
+
+				errno = res;
+				e->type = GG_EVENT_CONN_FAILED;
+				e->event.failure = GG_FAILURE_CONNECTING;
+				sess->state = GG_STATE_IDLE;
+				break;
+			}
+
+			gg_debug(GG_DEBUG_MISC, "-- http connection succeded, sending query\n");
+
+
+			snprintf(buf, sizeof(buf) - 1,
+				"GET /appsvc/appmsg.asp?fmnumber=%u HTTP/1.0\r\n"
+				"Host: " GG_APPMSG_HOST "\r\n"
+				"User-Agent: Mozilla/4.7 [en] (Win98; I)\r\n"
+				"Pragma: no-cache\r\n"
+				"\r\n", sess->uin);
+
+			if (write(sess->fd, buf, strlen(buf)) < strlen(buf)) {
+				gg_debug(GG_DEBUG_MISC, "-- sending query failed\n");
+
+				e->type = GG_EVENT_CONN_FAILED;
+				e->event.failure = GG_FAILURE_WRITING;
+				sess->state = GG_STATE_IDLE;
+				break;
+			}
+
+			sess->state = GG_STATE_WRITING_HTTP;
+			sess->check = GG_CHECK_READ;
+
+			break;
+		}
+
+		case GG_STATE_WRITING_HTTP:
+		{
+			char buf[1024], *tmp, *host;
+			int port = GG_DEFAULT_PORT;
+			struct in_addr a;
+
+			gg_debug(GG_DEBUG_MISC, "== GG_STATE_WRITING_HTTP\n");
+
+			gg_read_line(sess->fd, buf, sizeof(buf) - 1);
+			gg_chomp(buf);
+	
+			gg_debug(GG_DEBUG_TRAFFIC, "-- got http response (%s)\n", buf);
+	
+			if (strncmp(buf, "HTTP/1.", 7) || strncmp(buf + 9, "200", 3)) {
+				gg_debug(GG_DEBUG_MISC, "-- but that's not what we've expected\n");
+
+				e->type = GG_EVENT_CONN_FAILED;
+				e->event.failure = GG_FAILURE_INVALID;
+				sess->state = GG_STATE_IDLE;
+				break;
+			}
+	
+			while (strcmp(buf, "\r\n") && strcmp(buf, ""))
+				gg_read_line(sess->fd, buf, sizeof(buf) - 1);
+
+			gg_read_line(sess->fd, buf, sizeof(buf) - 1);
+			gg_chomp(buf);
+	
+			close(sess->fd);
+	
+			gg_debug(GG_DEBUG_TRAFFIC, "-- received http data (%s)\n", buf);
+
+			tmp = buf;
+			while (*tmp && *tmp != ' ')
+				tmp++;
+			while (*tmp && *tmp == ' ')
+				tmp++;
+			while (*tmp && *tmp != ' ')
+				tmp++;
+			while (*tmp && *tmp == ' ')
+				tmp++;
+			while (*tmp && *tmp != ' ')
+				tmp++;
+			while (*tmp && *tmp == ' ')
+				tmp++;
+			host = tmp;
+			while (*tmp && *tmp != ' ')
+				tmp++;
+			*tmp = 0;
+
+			if ((tmp = strchr(host, ':'))) {
+				*tmp = 0;
+				port = atoi(tmp+1);
+			}
+
+			a.s_addr = inet_addr(host);
+
+			if ((sess->fd = gg_connect(&a, port, sess->async)) == -1) {
+				gg_debug(GG_DEBUG_MISC, "-- connect() failed. errno = %d (%s)\n", errno, strerror(errno));
+
+				e->type = GG_EVENT_CONN_FAILED;
+				e->event.failure = GG_FAILURE_CONNECTING;
+				sess->state = GG_STATE_IDLE;
+				break;
+			}
+
+			sess->state = GG_STATE_CONNECTING_GG;
+			sess->check = GG_CHECK_WRITE;
+		
+			break;
+		}
+
+		case GG_STATE_CONNECTING_GG:
+		{
+			int res, res_size = sizeof(res);
+
+			gg_debug(GG_DEBUG_MISC, "== GG_STATE_CONNECTING_GG\n");
+
+			if (sess->async && (getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) {
+				gg_debug(GG_DEBUG_MISC, "-- connection failed, errno = %d (%s)\n", errno, strerror(errno));
+
+				errno = res;
+				e->type = GG_EVENT_CONN_FAILED;
+				e->event.failure = GG_FAILURE_CONNECTING;
+				sess->state = GG_STATE_IDLE;
+				break;
+			}
+
+			gg_debug(GG_DEBUG_MISC, "-- connected\n");
+			
+			sess->state = GG_STATE_WAITING_FOR_KEY;
+			sess->check = GG_CHECK_READ;
+
+			break;
+		}
+
+		case GG_STATE_WAITING_FOR_KEY:
+		{
+			struct gg_header *h;			
+			struct gg_welcome *w;
+			struct gg_login l;
+			unsigned int hash;
+			char *password = sess->password;
+
+			gg_debug(GG_DEBUG_MISC, "== GG_STATE_WAITING_FOR_KEY\n");
+
+			if (!(h = gg_recv_packet(sess))) {
+				gg_debug(GG_DEBUG_MISC, "-- gg_recv_packet() failed. errno = %d (%s)\n", errno, strerror(errno));
+
+				e->type = GG_EVENT_CONN_FAILED;
+				e->event.failure = GG_FAILURE_READING;
+				sess->state = GG_STATE_IDLE;
+				close(sess->fd);
+				break;
+			}
+	
+			if (h->type != GG_WELCOME) {
+				gg_debug(GG_DEBUG_MISC, "-- invalid packet received\n");
+
+				free(h);
+				close(sess->fd);
+				e->type = GG_EVENT_CONN_FAILED;
+				e->event.failure = GG_FAILURE_INVALID;
+				sess->state = GG_STATE_IDLE;
+				break;
+			}
+	
+			w = (void*) h + sizeof(struct gg_header);
+			w->key = fix32(w->key);
+
+			for (hash = 1; *password; password++)
+				hash *= (*password) + 1;
+			hash *= w->key;
+	
+			gg_debug(GG_DEBUG_DUMP, "%%%% klucz serwera %.4x, hash hasła %.8x\n", w->key, hash);
+	
+			free(h);
+
+			free(sess->password);
+			sess->password = NULL;
+	
+			l.uin = fix32(sess->uin);
+			l.hash = fix32(hash);
+			l.status = fix32(GG_STATUS_AVAIL);
+			l.dunno = fix32(0x0b);
+			l.local_ip = 0;
+			l.local_port = 0;
+	
+			gg_debug(GG_DEBUG_TRAFFIC, "-- sending GG_LOGIN packet\n");
+
+			if (gg_send_packet(sess->fd, GG_LOGIN, &l, sizeof(l), NULL, 0) == -1) {
+				gg_debug(GG_DEBUG_TRAFFIC, "-- oops, failed. errno = %d (%s)\n", errno, strerror(errno));
+
+				close(sess->fd);
+				e->type = GG_EVENT_CONN_FAILED;
+				e->event.failure = GG_FAILURE_WRITING;
+				sess->state = GG_STATE_IDLE;
+				break;
+			}
+	
+			sess->state = GG_STATE_SENDING_KEY;
+
+			break;
+		}
+
+		case GG_STATE_SENDING_KEY:
+		{
+			struct gg_header *h;
+
+			gg_debug(GG_DEBUG_MISC, "== GG_STATE_SENDING_KEY\n");
+
+			if (!(h = gg_recv_packet(sess))) {
+				gg_debug(GG_DEBUG_MISC, "-- recv_packet failed\n");
+				e->type = GG_EVENT_CONN_FAILED;
+				e->event.failure = GG_FAILURE_READING;
+				sess->state = GG_STATE_IDLE;
+				close(sess->fd);
+				break;
+			}
+	
+			if (h->type == GG_LOGIN_OK) {
+				gg_debug(GG_DEBUG_MISC, "-- login succeded\n");
+				e->type = GG_EVENT_CONN_SUCCESS;
+				sess->state = GG_STATE_CONNECTED;
+				break;
+			}
+
+			if (h->type == GG_LOGIN_FAILED) {
+				gg_debug(GG_DEBUG_MISC, "-- login failed\n");
+				e->event.failure = GG_FAILURE_PASSWORD;
+				errno = EACCES;
+			} else {
+				gg_debug(GG_DEBUG_MISC, "-- invalid packet\n");
+				e->event.failure = GG_FAILURE_INVALID;
+			}
+
+			e->type = GG_EVENT_CONN_FAILED;
+			sess->state = GG_STATE_IDLE;
+			close(sess->fd);
+
+			break;
+		}
+
+		case GG_STATE_CONNECTED:
+		{
+			gg_debug(GG_DEBUG_MISC, "== GG_STATE_CONNECTED\n");
+
+			if ((res = gg_watch_fd_connected(sess, e)) == -1) {
+
+				gg_debug(GG_DEBUG_MISC, "-- watch_fd_connected failed. errno = %d (%s)\n", errno, strerror(errno));
+
+ 				if (errno == EAGAIN) {
+					e->type = GG_EVENT_NONE;
+					res = 0;
+				} else
+					res = -1;
+			}
+			break;
+		}
+	}
+
+	if (res == -1) {
+		free(e);
+		e = NULL;
+	}
+
+	return e;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/gg/libgg.h	Sat Sep 29 23:06:30 2001 +0000
@@ -0,0 +1,339 @@
+/* $Id: libgg.h 2406 2001-09-29 23:06:30Z warmenhoven $ */
+
+/*
+ *  (C) Copyright 2001 Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License Version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __LIBGG_H
+#define __LIBGG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define GG_DEBUG 1
+
+/*
+ * typ zmiennej określającej numerek danej osoby.
+ */
+typedef unsigned int uin_t;
+
+/*
+ * cośtam.
+ */
+struct gg_session {
+	int state, check;
+	int fd, pid;
+	int port;
+	int seq, async;
+
+	uin_t uin;
+	char *password;
+
+	char *recv_buf;
+	int recv_done, recv_left;
+};
+
+/*
+ * różne stany asynchronicznej maszynki.
+ */
+enum {
+	GG_STATE_IDLE = 0,		/* wspólne */
+	GG_STATE_RESOLVING,
+	GG_STATE_CONNECTING_HTTP,
+
+	GG_STATE_WRITING_HTTP,		/* gg_login */	
+	GG_STATE_CONNECTING_GG,
+	GG_STATE_WAITING_FOR_KEY,
+	GG_STATE_SENDING_KEY,
+	GG_STATE_CONNECTED,
+
+	GG_STATE_READING_HEADER,	/* gg_search */
+	GG_STATE_READING_DATA,
+	GG_STATE_PARSING,
+	GG_STATE_FINISHED,
+};
+
+/*
+ * co proces klienta powinien sprawdzać w deskryptorach?
+ */
+enum {
+	GG_CHECK_NONE = 0,
+	GG_CHECK_WRITE = 1,
+	GG_CHECK_READ = 2,
+};
+
+struct gg_session *gg_login(uin_t uin, char *password, int async);
+void gg_free_session(struct gg_session *sess);
+void gg_logoff(struct gg_session *sess);
+int gg_change_status(struct gg_session *sess, int status);
+int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, unsigned char *message);
+int gg_ping(struct gg_session *sess);
+
+struct gg_notify_reply {
+	uin_t uin;		/* numerek */
+	int status;		/* status danej osoby */
+	int remote_ip;		/* adres ip delikwenta */
+	short remote_port;	/* port, na którym słucha klient */
+	int dunno1;		/* == 0x0b */
+	short dunno2;		/* znowu port? */
+} __attribute__ ((packed));
+
+struct gg_status {
+	uin_t uin;		/* numerek */
+	int status;		/* nowy stan */
+} __attribute__ ((packed));
+
+enum {
+	GG_EVENT_NONE = 0,
+	GG_EVENT_MSG,
+	GG_EVENT_NOTIFY,
+	GG_EVENT_STATUS,
+	GG_EVENT_ACK,
+	GG_EVENT_CONN_FAILED,
+	GG_EVENT_CONN_SUCCESS,
+};
+
+enum {
+	GG_FAILURE_RESOLVING = 1,
+	GG_FAILURE_CONNECTING,
+	GG_FAILURE_INVALID,
+	GG_FAILURE_READING,
+	GG_FAILURE_WRITING,
+	GG_FAILURE_PASSWORD,
+};
+
+struct gg_event {
+        int type;
+        union {
+                struct {
+                        uin_t sender;
+			int msgclass;
+                        unsigned char *message;
+                } msg;
+                struct gg_notify_reply *notify;
+                struct gg_status status;
+                struct {
+                        uin_t recipient;
+                        int status;
+                        int seq;
+                } ack;
+		int failure;
+        } event;
+};
+
+struct gg_event *gg_watch_fd(struct gg_session *sess);
+void gg_free_event(struct gg_event *e);
+
+int gg_notify(struct gg_session *sess, uin_t *userlist, int count);
+int gg_add_notify(struct gg_session *sess, uin_t uin);
+int gg_remove_notify(struct gg_session *sess, uin_t uin);
+
+/*
+ * jakieśtam bzdurki dotyczące szukania userów.
+ */
+
+struct gg_search_result {
+	uin_t uin;
+	char *first_name;
+	char *last_name;
+	char *nickname;
+	int born;
+	int gender;
+	char *city;
+	int active;
+};
+
+struct gg_search_request {
+	/* czy ma szukać tylko aktywnych? */
+	int active;
+	/* mode 0 */
+	char *nickname, *first_name, *last_name, *city;
+	int gender, min_birth, max_birth;
+	/* mode 1 */
+	char *email;
+	/* mode 2 */
+	char *phone;
+	/* mode 3 */
+	uin_t uin;
+};
+
+struct gg_search {
+	struct gg_search_request request;
+
+	/* bzdurki */
+	int mode, fd, async, state, check, error, pid;
+	char *header_buf, *data_buf;
+	int header_size, data_size;
+
+	/* wyniki */
+	int count;
+	struct gg_search_result *results;
+};
+
+#define GG_GENDER_NONE 0
+#define GG_GENDER_FEMALE 1
+#define GG_GENDER_MALE 2
+
+struct gg_search *gg_search(struct gg_search_request *r, int async);
+int gg_search_watch_fd(struct gg_search *f);
+void gg_free_search(struct gg_search *f);
+void gg_search_cancel(struct gg_search *f);
+
+/*
+ * jeśli chcemy sobie podebugować, wystarczy zdefiniować GG_DEBUG.
+ */
+
+int gg_debug_level;
+
+#ifdef GG_DEBUG
+
+#	define GG_DEBUG_NET 1
+#	define GG_DEBUG_TRAFFIC 2
+#	define GG_DEBUG_DUMP 4
+#	define GG_DEBUG_FUNCTION 8
+#	define GG_DEBUG_MISC 16
+
+	void gg_debug_real(int level, char *format, ...);
+#	define gg_debug(x, y...) gg_debug_real(x, y)
+
+#else
+
+#	define gg_debug(x, y...) while(0) { };
+
+#endif /* GG_DEBUG */
+
+/*
+ * -------------------------------------------------------------------------
+ * poniżej znajdują się wewnętrzne sprawy biblioteki. zwykły klient nie
+ * powinien ich w ogóle ruszać, bo i nie ma po co. wszystko można załatwić
+ * procedurami wyższego poziomu, których definicje znajdują się na początku
+ * tego pliku.
+ * -------------------------------------------------------------------------
+ */
+
+int gg_resolve(int *fd, int *pid, char *hostname);
+int gg_connect(void *addr, int port, int async);
+char *gg_alloc_sprintf(char *format, ...);
+
+#define GG_APPMSG_HOST "appmsg.gadu-gadu.pl"
+#define GG_APPMSG_PORT 80
+#define GG_PUBDIR_HOST "pubdir.gadu-gadu.pl"
+#define GG_PUBDIR_PORT 80
+#define GG_DEFAULT_PORT 8074
+
+struct gg_header {
+	int type;		/* typ pakietu */
+	int length;		/* długość reszty pakietu */
+} __attribute__ ((packed));
+
+#define GG_WELCOME 0x0001
+
+struct gg_welcome {
+	int key;		/* klucz szyfrowania hasła */
+} __attribute__ ((packed));
+	
+#define GG_LOGIN 0x000c
+
+struct gg_login {
+	uin_t uin;		/* twój numerek */
+	int hash;		/* hash hasła */
+	int status;		/* status na dzień dobry */
+	int dunno;		/* == 0x0b */
+	int local_ip;		/* mój adres ip */
+	short local_port;	/* port, na którym słucham */
+} __attribute__ ((packed));
+
+#define GG_LOGIN_OK 0x0003
+
+#define GG_LOGIN_FAILED 0x0009
+
+#define GG_NEW_STATUS 0x0002
+
+#define GG_STATUS_NOT_AVAIL 0x0001	/* rozłączony */
+#define GG_STATUS_AVAIL 0x0002		/* dostępny */
+#define GG_STATUS_BUSY 0x0003		/* zajęty */
+#define GG_STATUS_INVISIBLE 0x0014	/* niewidoczny (GG 4.6) */
+
+#define GG_STATUS_FRIENDS_MASK 0x8000	/* tylko dla znajomych (GG 4.6) */
+
+struct gg_new_status {
+	int status;			/* na jaki zmienić? */
+} __attribute__ ((packed));
+
+#define GG_NOTIFY 0x0010
+	
+struct gg_notify {
+	uin_t uin;		/* numerek danej osoby */
+	char dunno1;		/* == 3 */
+} __attribute__ ((packed));
+	
+#define GG_NOTIFY_REPLY 0x000c	/* tak, to samo co GG_LOGIN */
+	
+/* struct gg_notify_reply zadeklarowane wyżej */
+
+#define GG_ADD_NOTIFY 0x000d
+#define GG_REMOVE_NOTIFY 0x000e
+	
+struct gg_add_remove {
+	uin_t uin;		/* numerek */
+	char dunno1;		/* == 3 */
+} __attribute__ ((packed));
+
+#define GG_STATUS 0x0002
+
+/* struct gg_status zadeklarowane wcześniej */
+	
+#define GG_SEND_MSG 0x000b
+
+#define GG_CLASS_MSG 0x0004
+#define GG_CLASS_CHAT 0x0008
+
+struct gg_send_msg {
+	int recipient;
+	int seq;
+	int msgclass;
+} __attribute__ ((packed));
+
+#define GG_SEND_MSG_ACK 0x0005
+
+#define GG_ACK_DELIVERED 0x0002
+#define GG_ACK_QUEUED 0x0003
+	
+struct gg_send_msg_ack {
+	int status;
+	int recipient;
+	int seq;
+} __attribute__ ((packed));
+
+#define GG_RECV_MSG 0x000a
+	
+struct gg_recv_msg {
+	int sender;
+	int dunno1;
+	int dunno2;
+	int msgclass;
+} __attribute__ ((packed));
+
+#define GG_PING 0x0008
+	
+#define GG_PONG 0x0007
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/gg/protocol.txt	Sat Sep 29 23:06:30 2001 +0000
@@ -0,0 +1,248 @@
+---------------------------------------------------------------------------
+
+                         protokół g*du-g*du 4.x
+        (c) copyright 2001 by wojtek kaniewski <wojtekka@irc.pl>
+
+--- 0) disclaimer ---------------------------------------------------------
+
+wszystkie informacje bazują na doświadczeniach przeprowadzonych na moim
+domowym komputerze. żaden klient g*du-g*du nie został skrzywdzony podczas
+przeprowadzania badań, blabla.
+
+--- 1) transmisja, format wszystkich pakietów -----------------------------
+
+w przeciwieństwie do zabawek typu icq, g*du-g*du korzysta z protokołu tcp.
+każdy pakiet zawiera dwa stałe pola:
+
+        struct gg_header {
+		int type;		/* typ pakietu */
+		int length;		/* długość reszty pakietu */
+	};
+
+dla ułatwienia przyjmuję następujące długości zmiennych: sizeof(char) = 1,
+sizeof(short) = 2, sizeof(int) = 4. oczywiście wszystkie liczby są zgodnie
+z intelowym endianem. zakładam też, że wszystkie zmienne są bez znaku. nie
+chce mi się wszędzie pisać `unsigned'.
+
+pola, co do których znaczenia nie mam pewności, lub w ogóle nie mam pojęcia,
+skąd się tam wzięły, oznaczam `dunno'.
+
+--- 2) zanim się połączymy -------------------------------------------------
+
+żeby wiedzieć, z jakim serwerem mamy się połączyć, należy poudawać przez
+chwilę Internet Explorera, połączyć się z hostem `appmsg.gadu-gadu.pl'.
+
+        GET /appsvc/appmsg.asp?fmnumber=<tutaj_numerek_gg> HTTP/1.0
+	Host: appmsg.gadu-gadu.pl
+	User-Agent: Mozilla/4.7 [en] (Win98; I)
+	Pragma: no-cache
+
+na co powinniśmy dostać odpowiedź w stylu:
+
+	HTTP/1.0 200 OK
+	
+	0 1 0 217.17.33.21:8074 217.17.33.21 217.17.33.21
+
+co to oznacza? nie mam pojęcia ;) wygląda na to, że cały g*du-g*du jest
+przemyślany i w przyszłości będzie można używać różnych serwerów do różnych
+rzeczy, typu szukanie, obsługa klientów itd. póki co, łączyć się trzeba na
+pierwszy adres (tak, ten z portem).
+
+--- 3) logowanie się -------------------------------------------------------
+
+po połączeniu się portem 8074 serwera g*du-g*du, dostajemy pakiet typu 0x0001,
+który na potrzeby tego dokumentu nazwiemy:
+
+	#define GG_WELCOME 0x0001
+
+reszta pakietu zawiera liczbę, na podstawie której liczony jest hash z hasła
+klienta:
+
+	struct gg_welcome {
+		int key;		/* klucz szyfrowania hasła */
+	};
+	
+kiedy mamy już tą wartość możemy odesłać pakiet logowania
+
+	#define GG_LOGIN 0x000c
+
+musimy podać kilka informacji:
+
+	struct gg_login {
+		int uin;		/* twój numerek */
+		int hash;		/* hash hasła */
+		int status;		/* status na dzień dobry */
+		int dunno1;		/* == 0x0b */
+		int local_ip;		/* mój adres ip */
+		short local_port;	/* port, na którym słucham */
+	};
+
+jak obliczyć hash hasła? hmm... nic prostszego. do każdej literki hasła
+dodaje się jedynkę, mnoży wszystko razem, a potem przez liczbę podaną przez
+serwer. 
+
+	for (hash = 1; *passwd; passwd++)
+		hash *= (*passwd) + 1;
+
+zrozumiałe, racja? jeśli wszystko się powiedzie, dostaniemy w odpowiedzi
+pakiet typu
+
+	#define GG_LOGIN_OK 0x0003
+
+z polem header->length = 0, lub pakiet
+	
+	#define GG_LOGIN_FAILED 0x0009
+
+--- 4) zmiana statusu -----------------------------------------------------
+
+g*du-g*du przewiduje trzy stany klienta, które zmieniamy pakietem
+
+	#define GG_NEW_STATUS 0x0002
+
+	#define GG_STATUS_NOT_AVAIL 0x0001	/* rozłączony */
+	#define GG_STATUS_AVAIL 0x0002		/* dostępny */
+	#define GG_STATUS_BUSY 0x0003		/* zajęty */
+	#define GG_STATUS_INVISIBLE 0x0014	/* niewidoczny */
+
+	#define GG_STATUS_FRIENDS_MASK 0x8000	/* tylko dla przyjaciół */
+	
+	struct gg_new_status {
+		int status;			/* na jaki zmienić? */
+	}
+
+należy pamiętać, żeby przed rozłączeniem się z serwerem należy zmienić
+stan na GG_STATUS_NOT_AVAIL. jeśli ma być widoczny tylko dla przyjaciół,
+należy dodać GG_STATUS_FRIENDS do normalnej wartości stanu.
+
+--- 5) ludzie przychodzą, ludzie odchodzą ---------------------------------
+
+zaraz po zalogowaniu możemy wysłać serwerowi listę ludzików w naszej liście
+kontaktów, żeby dowiedzieć się, czy są w tej chwili dostępni. pakiet zawiera
+dowolną ilość struktur gg_notify:
+
+	#define GG_NOTIFY 0x0010
+	
+	struct gg_notify {
+		int uin;		/* numerek danej osoby */
+		char dunno1;		/* == 3 */
+	};
+	
+jeśli ktoś jest, serwer odpowie pakietem zawierającym jedną lub więcej
+struktur gg_notify_reply:
+
+	#define GG_NOTIFY_REPLY 0x000c	/* tak, to samo co GG_LOGIN */
+	
+	struct gg_notify_reply {
+		int uin;		/* numerek */
+		int status;		/* status danej osoby */
+		int remote_ip;		/* adres ip delikwenta */
+		short remote_port;	/* port, na którym słucha klient */
+		int dunno1;		/* == 0x0b */
+		short dunno2;		/* znowu port? */
+	};
+
+jeśli klient nie obsługuje połączeń między klientami (np. g*du-g*du 3.x)
+zamiast adresu ip jest 0, zamiast portu jest 2. nieważne ;) w każdym razie,
+jeśli ktoś się pojawi w trakcie pracy, również zostanie przysłany ten
+pakiet. proste? proste :)
+
+żeby dodać kogoś do listy w trakcie pracy, trzeba wysłać niżej opisany
+pakiet. jego format jest identyczny jak przy GG_NOTIFY.
+
+	#define GG_ADD 0x000d
+	
+	struct gg_add {
+		int uin;		/* numerek */
+		char dunno1;		/* == 3 */
+	};
+
+jeśli ktoś opuści g*du-g*du lub zmieni stan, otrzymamy pakiet
+
+	#define GG_STATUS 0x0002
+	
+	struct gg_status {
+		int uin;		/* numerek */
+		int status;		/* nowy stan */
+	};
+
+--- 6) wysyłanie wiadomości ------------------------------------------------
+	
+przejdźmy do sedna sprawy ;)
+
+	#define GG_SEND_MSG 0x000b
+
+	#define GG_CLASS_MSG 0x0004
+	#define GG_CLASS_CHAT 0x0008
+
+	struct gg_send_msg {
+		int recipient;
+		int seq;
+		int class;
+		char message[];
+	};
+
+wiadomo, odbiorca. numer sekwencyjny, który wykorzystujemy potem do
+potwierdzenia. nie wykluczone, że w jakis sposób odróżnia się różne
+rozmowy za pomocą części bajtów, ale raczej nie ma znaczenia. klasa
+wiadomości pozwala odróżnić, czy wiadomość ma się pokazać w osobym
+okienku czy jako kolejna linijka w okienku rozmowy. wygląda na to,
+że to jakaś bitmapa, więc najlepiej olać inne bity niż 0x0f. (czasem
+klienty wysyłają 0x04, czasem 0x24)
+
+serwer po otrzymaniu wiadomości odsyła informację o tym. przy okazji
+mówi, czy wiadomość dotarła do odbiorcy (status == GG_ACK_DELIVERED),
+czy może jest offline i została zakolejkowana (GG_ACK_QUEUED):
+
+	#define GG_SEND_MSG_ACK 0x0005
+	
+	#define GG_ACK_DELIVERED 0x0002
+	#define GG_ACK_QUEUED 0x0003
+
+	struct gg_send_msg_ack {
+		int status;
+		int recipient;
+		int seq;
+	};
+
+numer sekwencyjny i adresat ten sam, co przy wysyłaniu.
+
+--- 7) otrzymywanie wiadomości ---------------------------------------------
+
+zbyt wiele wyjaśnień chyba nie trzeba. wiadomo od kogo. nieznane pola to
+coś a'la numer sekwencyjny albo identyfikator okienka z rozmową albo nowe
+dane dla setiathome. klasa wiadomości taka sama jak przy wysyłaniu:
+
+	#define GG_RECV_MSG 0x000a
+	
+	struct gg_recv_msg {
+		int sender;
+		int dunno1;
+		int dunno2;
+		int class;
+		char message[];
+	};
+
+--- 8) otrzymywanie wiadomości ---------------------------------------------
+
+od czasu do czasu klient wysyła pakiet a'la ping do serwera i dostaje pustą
+odpowiedź. ciężko stwierdzić, czy serwer wywala, jeśli nie dostanie pinga
+przez jakiś czas, czy klient się rozłącza, jeśli serwer mu nie odpowie.
+jakoś nie chce mi się sprawdzać ;)
+
+	#define GG_PING 0x0008
+	
+	/* nie ma niczego */
+	
+	#define GG_PONG 0x0007
+	
+	/* nie ma niczego */
+
+--- 9) podziękowania -------------------------------------------------------
+
+swój wkład w poznanie protokołu miał Robert Woźny, który opisał nowości
+w GG 4.6.
+
+----------------------------------------------------------------------------
+
+$Id: protocol.txt 2406 2001-09-29 23:06:30Z warmenhoven $
+
--- a/src/protocols/irc/irc.c	Sat Sep 29 02:08:00 2001 +0000
+++ b/src/protocols/irc/irc.c	Sat Sep 29 23:06:30 2001 +0000
@@ -1319,9 +1319,14 @@
 	struct irc_data *idata = gc->proto_data;
 	char buf[IRC_BUF_LEN];
 
-	if (msg)
+	if (gc->away)
+		g_free(gc->away);
+	gc->away = NULL;
+
+	if (msg) {
 		g_snprintf(buf, sizeof(buf), "AWAY :%s\r\n", msg);
-	else
+		gc->away = g_strdup(msg);
+	} else
 		g_snprintf(buf, sizeof(buf), "AWAY\r\n");
 	irc_write(idata->fd, buf, strlen(buf));
 }