changeset 8775:ce90b119b103

[gaim-migrate @ 9537] " Updates gadu-gadu to use the new version 6.0 protocol. This patch was compiled with the use of much code from ekg (http: //dev.null.pl/ekg/). It hasn't been extensively tested (I only speak English, so how it behaves for polish speakers would be good to know!), so more testing would be great." --Andrew Wellington committer: Tailor Script <tailor@pidgin.im>
author Luke Schierer <lschiere@pidgin.im>
date Fri, 23 Apr 2004 17:24:19 +0000
parents 5205743477bb
children 601f6c4b140b
files src/protocols/gg/gg.c src/protocols/gg/libgg.c src/protocols/gg/libgg.h
diffstat 3 files changed, 797 insertions(+), 99 deletions(-) [+]
line wrap: on
line diff
--- a/src/protocols/gg/gg.c	Fri Apr 23 17:13:33 2004 +0000
+++ b/src/protocols/gg/gg.c	Fri Apr 23 17:24:19 2004 +0000
@@ -1,6 +1,6 @@
 /*
  * gaim - Gadu-Gadu Protocol Plugin
- * $Id: gg.c 9504 2004-04-22 01:53:18Z chipx86 $
+ * $Id: gg.c 9537 2004-04-23 17:24:19Z lschiere $
  *
  * Copyright (C) 2001 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
  *
@@ -265,6 +265,125 @@
 	return m;
 }
 
+static void agg_load_buddy_list(GaimConnection *gc, char *buddylist)
+{
+	struct agg_data *gd = (struct agg_data *)gc->proto_data;
+	gchar *ptr = buddylist;
+	gchar **users_tbl;
+	int i;
+	uin_t *userlist = NULL;
+	int userlist_size = 0;
+
+	users_tbl = g_strsplit(ptr, "\r\n", AGG_PUBDIR_MAX_ENTRIES);
+
+	/* Parse array of Buddies List */
+	for (i = 0; users_tbl[i] != NULL; i++) {
+		gchar **data_tbl;
+		gchar *name, *show;
+
+		if (strlen(users_tbl[i])==0) {
+			gaim_debug(GAIM_DEBUG_MISC, "gg",
+					   "import_buddies_server_results: users_tbl[i] is empty\n");
+			continue;
+		}
+
+		data_tbl = g_strsplit(users_tbl[i], ";", 8);
+
+		show = charset_convert(data_tbl[3], "CP1250", "UTF-8");
+		name = data_tbl[6];
+		
+		if (invalid_uin(name)) {
+			continue;
+		}
+
+		gaim_debug(GAIM_DEBUG_MISC, "gg",
+				   "import_buddies_server_results: uin: %s\n", name);
+		if (!gaim_find_buddy(gc->account, name)) {
+			GaimBuddy *b;
+			GaimGroup *g;
+			/* Default group if none specified on server */
+			gchar *group = g_strdup("Gadu-Gadu");
+			if (strlen(data_tbl[5])) {
+				gchar **group_tbl = g_strsplit(data_tbl[5], ",", 2);
+				if (strlen(group_tbl[0])) {
+					g_free(group);
+					group = g_strdup(group_tbl[0]);
+				}
+				g_strfreev(group_tbl);
+			}
+			/* Add Buddy to our userlist */
+			if (!(g = gaim_find_group(group))) {
+				g = gaim_group_new(group);
+				gaim_blist_add_group(g, NULL);
+			}
+			b = gaim_buddy_new(gc->account, name, strlen(show) ? show : NULL);
+			gaim_blist_add_buddy(b,NULL,g,NULL);
+			gaim_blist_save();
+			
+			userlist_size++;
+			userlist = g_renew(uin_t, userlist, userlist_size);
+			userlist[userlist_size - 1] =
+			    (uin_t) strtol((char *)name, (char **)NULL, 10);
+			
+			g_free(group);
+		}
+		g_free(show);
+		g_strfreev(data_tbl);
+	}
+	g_strfreev(users_tbl);
+	
+	if (userlist) {
+	    gg_notify(gd->sess, userlist, userlist_size);
+	    g_free(userlist);
+	}
+}
+
+static void agg_save_buddy_list (GaimConnection *gc, char *existlist)
+{
+    GaimBlistNode *gnode, *cnode, *bnode;
+    char *buddylist = g_strdup(existlist ? existlist : "");
+    char *ptr;
+    struct agg_data *gd = (struct agg_data *)gc->proto_data;
+
+    for(gnode = gaim_get_blist()->root; gnode; gnode = gnode->next) {
+		GaimGroup *g = (GaimGroup *)gnode;
+		if(!GAIM_BLIST_NODE_IS_GROUP(gnode))
+			continue;
+		for(cnode = gnode->child; cnode; cnode = cnode->next) {
+			if(!GAIM_BLIST_NODE_IS_CONTACT(cnode))
+				continue;
+			for(bnode = cnode->child; bnode; bnode = bnode->next) {
+				GaimBuddy *b = (GaimBuddy *)bnode;
+
+				if(!GAIM_BLIST_NODE_IS_BUDDY(bnode))
+					continue;
+
+				if(b->account == gc->account) {
+					gchar *newdata;
+					/* GG Number */
+					gchar *name = b->name;
+					/* GG Pseudo */
+					gchar *show = b->alias ? b->alias : b->name;
+					/* Group Name */
+					gchar *gname = g->name;
+
+					newdata = g_strdup_printf("%s;%s;%s;%s;%s;%s;%s;%s%s\r\n",
+							show, show, show, show, "", gname, name, "", "");
+							
+					ptr = buddylist;
+					buddylist = g_strconcat(ptr, newdata, NULL);
+					
+					g_free(newdata);
+					g_free(ptr);
+				}
+			}
+		}
+	}
+	
+	/* save the list to the gadu gadu server */
+	gg_userlist_request(gd->sess, GG_USERLIST_PUT, buddylist);
+}
+
 static void main_callback(gpointer data, gint source, GaimInputCondition cond)
 {
 	GaimConnection *gc = data;
@@ -347,6 +466,34 @@
 			}
 		}
 		break;
+	case GG_EVENT_NOTIFY60:
+		{
+			gchar user[20];
+			struct gg_notify_reply60 *n = (void *)e->event.notify60;
+			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:
+					status = UC_NORMAL | (n->status << 5);
+					break;
+				default:
+					status = UC_NORMAL;
+					break;
+				}
+
+				g_snprintf(user, sizeof(user), "%lu", (long unsigned int)n->uin);
+				serv_got_update(gc, user, (status == UC_UNAVAILABLE) ? 0 : 1, 0, 0, 0,
+						status);
+				n++;
+			}
+		}
+		break;
 	case GG_EVENT_STATUS:
 		{
 			gchar user[20];
@@ -371,11 +518,55 @@
 					status);
 		}
 		break;
+	case GG_EVENT_STATUS60:
+		{
+			gchar user[20];
+			guint status;
+
+			switch (e->event.status60.status) {
+			case GG_STATUS_NOT_AVAIL:
+				status = UC_UNAVAILABLE;
+				break;
+			case GG_STATUS_AVAIL:
+			case GG_STATUS_BUSY:
+			case GG_STATUS_INVISIBLE:
+				status = UC_NORMAL | (e->event.status60.status << 5);
+				break;
+			default:
+				status = UC_NORMAL;
+				break;
+			}
+
+			g_snprintf(user, sizeof(user), "%lu", e->event.status60.uin);
+			serv_got_update(gc, user, (status == UC_UNAVAILABLE) ? 0 : 1, 0, 0, 0,
+					status);
+		}
+		break;	
 	case GG_EVENT_ACK:
 		gaim_debug(GAIM_DEBUG_MISC, "gg",
 				   "main_callback: message %d to %lu sent with status %d\n",
 			     e->event.ack.seq, e->event.ack.recipient, e->event.ack.status);
 		break;
+	case GG_EVENT_USERLIST:
+	{
+	    gaim_debug(GAIM_DEBUG_MISC, "gg", "main_callback: received userlist reply\n");
+	    switch (e->event.userlist.type) {
+		case GG_USERLIST_GET_REPLY:
+		{
+			
+			if (e->event.userlist.reply) {
+			    agg_load_buddy_list(gc, e->event.userlist.reply);
+			}
+			break;
+		}
+
+		case GG_USERLIST_PUT_REPLY:
+		{
+		    /* ignored */
+		}
+	    }
+	}
+
 	default:
 		gaim_debug(GAIM_DEBUG_ERROR, "gg",
 				   "main_callback: unsupported event %d\n", e->type);
@@ -588,6 +779,7 @@
 	if (invalid_uin(who))
 		return;
 	gg_add_notify(gd->sess, strtol(who, (char **)NULL, 10));
+	agg_save_buddy_list(gc, NULL);
 }
 
 static void agg_rem_buddy(GaimConnection *gc, const char *who, const char *group)
@@ -596,6 +788,7 @@
 	if (invalid_uin(who))
 		return;
 	gg_remove_notify(gd->sess, strtol(who, (char **)NULL, 10));
+	agg_save_buddy_list(gc, NULL);
 }
 
 static void agg_add_buddies(GaimConnection *gc, GList *whos)
@@ -618,6 +811,8 @@
 		gg_notify(gd->sess, userlist, userlist_size);
 		g_free(userlist);
 	}
+	
+	agg_save_buddy_list(gc, NULL);
 }
 
 static void search_results(GaimConnection *gc, gchar *webdata)
@@ -734,6 +929,7 @@
 	gaim_account_request_change_password(gaim_connection_get_account(gc));
 }
 
+#if 0
 static void import_buddies_server_results(GaimConnection *gc, gchar *webdata)
 {
 	gchar *ptr;
@@ -839,6 +1035,7 @@
 					  _("Couldn't delete Buddy List from Gadu-Gadu server"),
 					  NULL);
 }
+#endif
 
 static void password_change_server_results(GaimConnection *gc, gchar *webdata)
 {
@@ -903,6 +1100,7 @@
 	case AGG_HTTP_SEARCH:
 		search_results(gc, webdata);
 		break;
+#if 0
 	case AGG_HTTP_USERLIST_IMPORT:
 		import_buddies_server_results(gc, webdata);
 		break;
@@ -912,6 +1110,7 @@
 	case AGG_HTTP_USERLIST_DELETE:
 	        delete_buddies_server_results(gc, webdata);
 		break;
+#endif
 	case AGG_HTTP_PASSWORD_CHANGE:
 		password_change_server_results(gc, webdata);
 		break;
@@ -980,6 +1179,7 @@
 	hdata->inpa = gaim_input_add(source, GAIM_INPUT_READ, http_results, hdata);
 }
 
+#if 0
 static void import_buddies_server(GaimConnection *gc)
 {
 	struct agg_http *hi = g_new0(struct agg_http, 1);
@@ -1102,6 +1302,7 @@
 		return;
 	}
 }
+#endif
 
 static void agg_dir_search(GaimConnection *gc, const char *first, const char *middle,
 			   const char *last, const char *maiden, const char *city, const char *state,
@@ -1209,8 +1410,8 @@
 	pam->gc = gc;
 	m = g_list_append(m, pam);
 
+#if 0
 	m = g_list_append(m, NULL);
-
 	pam = g_new0(struct proto_actions_menu, 1);
 	pam->label = _("Import Buddy List from Server");
 	pam->callback = import_buddies_server;
@@ -1228,6 +1429,7 @@
 	pam->callback = delete_buddies_server;
 	pam->gc = gc;
 	m = g_list_append(m, pam);
+#endif
 
 	return m;
 }
@@ -1301,6 +1503,30 @@
 	/* It's implemented on client side because GG server doesn't support this */
 }
 
+static void agg_group_buddy (GaimConnection * gc, const char *who,
+			const char *old_group, const char *new_group)
+{
+    GaimBuddy *buddy = gaim_find_buddy(gaim_connection_get_account(gc), who);
+    gchar *newdata;
+    /* GG Number */
+    gchar *name = buddy->name;
+    /* GG Pseudo */
+    gchar *show = buddy->alias ? buddy->alias : buddy->name;
+    /* Group Name */
+    const gchar *gname = new_group;
+
+    newdata = g_strdup_printf("%s;%s;%s;%s;%s;%s;%s;%s%s\r\n",
+		    show, show, show, show, "", gname, name, "", "");
+    agg_save_buddy_list(gc, newdata);
+    g_free(newdata);
+}
+
+static void agg_rename_group (GaimConnection *gc, const char *old_group,
+		     const char *new_group, GList *members)
+{
+    agg_save_buddy_list(gc, NULL);
+}
+
 static GaimPlugin *my_protocol = NULL;
 
 static GaimPluginProtocolInfo prpl_info =
@@ -1351,8 +1577,8 @@
 	NULL,
 	NULL,
 	NULL,
-	NULL,
-	NULL,
+	agg_group_buddy,
+	agg_rename_group,
 	NULL,
 	NULL,
 	NULL
--- a/src/protocols/gg/libgg.c	Fri Apr 23 17:13:33 2004 +0000
+++ b/src/protocols/gg/libgg.c	Fri Apr 23 17:24:19 2004 +0000
@@ -1,4 +1,4 @@
-/* $Id: libgg.c 6741 2003-07-20 19:11:13Z thekingant $ */
+/* $Id: libgg.c 9537 2004-04-23 17:24:19Z lschiere $ */
 
 /*
  *  (C) Copyright 2001 Wojtek Kaniewski <wojtekka@irc.pl>,
@@ -73,7 +73,7 @@
 #ifdef __GNUC__
 __attribute__ ((unused))
 #endif
-= "$Id: libgg.c 6741 2003-07-20 19:11:13Z thekingant $";
+= "$Id: libgg.c 9537 2004-04-23 17:24:19Z lschiere $";
 
 #endif 
 
@@ -101,7 +101,6 @@
  * dla maszyn big-endianowych zamienia kolejność bajtów w ,,short''ach.
  */
 
-/* not currently used
 static inline unsigned short fix16(unsigned short x)
 {
 #ifndef WORDS_BIGENDIAN
@@ -112,7 +111,6 @@
                  ((x & (unsigned short) 0xff00U) >> 8));
 #endif
 }
-*/
 
 #ifndef _WIN32
 /*
@@ -277,67 +275,135 @@
 /*
  * gg_send_packet() // funkcja wewnętrzna
  *
- * konstruuje pakiet i wysyła go w do serwera.
+ * konstruuje pakiet i wysyła go 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.
+ *  - sock - deskryptor gniazda
+ *  - type - typ pakietu
+ *  - payload_1 - pierwsza część pakietu
+ *  - payload_length_1 - długość pierwszej części
+ *  - payload_2 - druga część pakietu
+ *  - payload_length_2 - długość drugiej części
+ *  - ... - kolejne części pakietu i ich długości
+ *  - NULL - końcowym parametr (konieczny!)
  *
- * 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
+ * jeśli się powiodło, 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)
+int gg_send_packet(struct gg_session *sess, int type, ...)
 {
 	struct gg_header *h;
-	int res, plen;
 	char *tmp;
+	int tmp_length;
+	void *payload;
+	int payload_length;
+	va_list ap;
+	int res;
 
-	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;
-	}
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_send_packet(%p, 0x%.2x, ...)\n", sess, type);
 
-	if (!(tmp = malloc(sizeof(struct gg_header) + length + payload_length))) {
-		gg_debug(GG_DEBUG_MISC, "-- not enough memory\n");
+	tmp_length = 0;
+
+	if (!(tmp = malloc(sizeof(struct gg_header)))) {
+		gg_debug(GG_DEBUG_MISC, "// gg_send_packet() not enough memory for packet header\n");
 		return -1;
 	}
 
 	h = (struct gg_header*) tmp;
 	h->type = fix32(type);
-	h->length = fix32(length + payload_length);
+	h->length = fix32(0);
+
+	va_start(ap, type);
+
+	payload = va_arg(ap, void *);
+
+	while (payload) {
+		char *tmp2;
+
+		payload_length = va_arg(ap, int);
 
-	if (packet)
-		memcpy(tmp + sizeof(struct gg_header), packet, length);
-	if (payload)
-		memcpy(tmp + sizeof(struct gg_header) + length, payload, payload_length);
+		if (payload_length < 0)
+			gg_debug(GG_DEBUG_MISC, "// gg_send_packet() invalid payload length (%d)\n", payload_length);
+	
+		if (!(tmp2 = realloc(tmp, sizeof(struct gg_header) + tmp_length + payload_length))) {
+                        gg_debug(GG_DEBUG_MISC, "// gg_send_packet() not enough memory for payload\n");
+			free(tmp);
+			va_end(ap);
+                        return -1;
+                }
+
+		tmp = tmp2;
+		
+		memcpy(tmp + sizeof(struct gg_header) + tmp_length, payload, payload_length);
+		tmp_length += payload_length;
+
+		payload = va_arg(ap, void *);
+	}
+
+	va_end(ap);
+
+	h = (struct gg_header*) tmp;
+	h->length = fix32(tmp_length);
 
 	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");
-	}
-
-	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));
+                unsigned int i;
+		
+                gg_debug(GG_DEBUG_DUMP, "// gg_send_packet(0x%.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");
+        }
+	
+	tmp_length += sizeof(struct gg_header);
+	
+	if ((res = gg_write(sess, tmp, tmp_length)) < tmp_length) {
+		gg_debug(GG_DEBUG_MISC, "// gg_send_packet() write() failed. res = %d, errno = %d (%s)\n", res, errno, strerror(errno));
 		free(tmp);
 		return -1;
 	}
-
+	
 	free(tmp);	
 	return 0;
 }
 
+/*
+ * gg_write() // funkcja pomocnicza
+ *
+ * zapisuje do gniazda określoną ilość bajtów. bierze pod uwagę, czy mamy
+ * połączenie zwykłe czy TLS.
+ *
+ *  - sess - sesja,
+ *  - buf - bufor,
+ *  - length - ilość bajtów,
+ *
+ * takie same wartości jak write().
+ */
+int gg_write(struct gg_session *sess, const char *buf, int length)
+{
+	int res;
+
+#ifdef __GG_LIBGADU_HAVE_OPENSSL
+	if (sess->ssl) {
+		int err;
+
+		res = SSL_write(sess->ssl, buf, length);
+
+		if (res < 0) {
+			err = SSL_get_error(sess->ssl, res);
+
+			if (err == SSL_ERROR_WANT_WRITE)
+				errno = EAGAIN;
+
+			return -1;
+		}
+	} else
+#endif
+		res = write(sess->fd, buf, length);
+
+	return res;
+}
+
+
 #ifndef _WIN32
 /*
  * gg_login()
@@ -443,6 +509,40 @@
 #endif /*!_WIN32*/
 
 /* 
+ * gg_login_hash() // funkcja wewnętrzna
+ * 
+ * liczy hash z hasła i danego seeda.
+ * 
+ *  - password - hasło do hashowania
+ *  - seed - wartość podana przez serwer
+ *
+ * hash.
+ */
+unsigned int gg_login_hash(const unsigned char *password, unsigned int seed)
+{
+	unsigned int x, y, z;
+
+	y = seed;
+
+	for (x = 0; *password; password++) {
+		x = (x & 0xffffff00) | *password;
+		y ^= x;
+		y += x;
+		x <<= 8;
+		y ^= x;
+		x <<= 8;
+		y -= x;
+		x <<= 8;
+		y ^= x;
+
+		z = y & 0x1F;
+		y = (y << z) | (y >> (32 - z));
+	}
+
+	return y;
+}
+
+/* 
  * gg_free_session()
  *
  * zwalnia pamięć zajmowaną przez opis sesji.
@@ -488,7 +588,7 @@
 
 	p.status = fix32(status);
 
-	return gg_send_packet(sess->fd, GG_NEW_STATUS, &p, sizeof(p), NULL, 0);
+	return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), NULL, 0);
 }
 
 /*
@@ -520,19 +620,43 @@
  * gg_send_message()
  *
  * wysyła wiadomość do innego użytkownika. zwraca losowy numer
- * sekwencyjny, który można olać albo wykorzystać do potwierdzenia.
+ * sekwencyjny, który można zignorować albo wykorzystać do potwierdzenia.
+ *
+ *  - sess - opis sesji
+ *  - msgclass - rodzaj wiadomości
+ *  - recipient - numer adresata
+ *  - message - treść wiadomości
  *
- *  - sess - opis sesji,
- *  - msgclass - rodzaj wiadomości,
- *  - recipient - numer adresata,
- *  - message - treść wiadomości.
+ * numer sekwencyjny wiadomości lub -1 w przypadku błędu.
+ */
+int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message)
+{
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message(%p, %d, %u, %p)\n", sess, msgclass, recipient, message);
+
+	return gg_send_message_richtext(sess, msgclass, recipient, message, NULL, 0);
+}
+
+/*
+ * gg_send_message_richtext()
  *
- * w przypadku błędu zwraca -1, inaczej numer sekwencyjny.
+ * wysyła kolorową wiadomość do innego użytkownika. zwraca losowy numer
+ * sekwencyjny, który można zignorować albo wykorzystać do potwierdzenia.
+ *
+ *  - sess - opis sesji
+ *  - msgclass - rodzaj wiadomości
+ *  - recipient - numer adresata
+ *  - message - treść wiadomości
+ *  - format - informacje o formatowaniu
+ *  - formatlen - długość informacji o formatowaniu
+ *
+ * numer sekwencyjny wiadomości lub -1 w przypadku błędu.
  */
-int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, char *message)
+int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen)
 {
 	struct gg_send_msg s;
 
+	gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message_richtext(%p, %d, %u, %p, %p, %d);\n", sess, msgclass, recipient, message, format, formatlen);
+
 	if (!sess) {
 		errno = EFAULT;
 		return -1;
@@ -543,8 +667,6 @@
 		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);
@@ -552,7 +674,7 @@
 	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)
+	if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, strlen(message) + 1, format, formatlen, NULL) == -1)
 		return -1;
 
 	return fix32(s.seq);
@@ -581,15 +703,7 @@
 
 	gg_debug(GG_DEBUG_FUNCTION, "** gg_ping(...);\n");
 	
-	if(ping_outstanding) {
-		gaim_debug(GAIM_DEBUG_INFO, "gg",
-				   "Trying to send ping, when we havn't been ponged on last ping\n");
-		return 1;
-	}
-	else {
-		ping_outstanding = 1;
-		return gg_send_packet(sess->fd, GG_PING, NULL, 0, NULL, 0);
-	}
+	return gg_send_packet(sess, GG_PING, NULL);
 }
 
 /*
@@ -652,7 +766,7 @@
 		n[i].dunno1 = 3;
 	}
 	
-	if (gg_send_packet(sess->fd, GG_NOTIFY, n, sizeof(*n) * count, NULL, 0) == -1)
+	if (gg_send_packet(sess, GG_NOTIFY, n, sizeof(*n) * count, NULL, 0) == -1)
 		res = -1;
 
 	free(n);
@@ -689,7 +803,7 @@
 	a.uin = fix32(uin);
 	a.dunno1 = 3;
 	
-	return gg_send_packet(sess->fd, GG_ADD_NOTIFY, &a, sizeof(a), NULL, 0);
+	return gg_send_packet(sess, GG_ADD_NOTIFY, &a, sizeof(a), NULL);
 }
 
 /*
@@ -721,7 +835,54 @@
 	a.uin = fix32(uin);
 	a.dunno1 = 3;
 	
-	return gg_send_packet(sess->fd, GG_REMOVE_NOTIFY, &a, sizeof(a), NULL, 0);
+	return gg_send_packet(sess, GG_REMOVE_NOTIFY, &a, sizeof(a), NULL, 0);
+}
+
+/*
+ * gg_userlist_request()
+ *
+ * wysyła żądanie/zapytanie listy kontaktów na serwerze.
+ *
+ *  - sess - opis sesji
+ *  - type - rodzaj zapytania/żądania
+ *  - request - treść zapytania/żądania (może być NULL)
+ *
+ * 0, -1
+ */
+int gg_userlist_request(struct gg_session *sess, char type, const char *request)
+{
+	int len;
+
+	if (!sess) {
+		errno = EINVAL;
+		return -1;
+	}
+	
+	if (!request) {
+		sess->userlist_blocks = 1;
+		return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), NULL);
+	}
+	
+	len = strlen(request);
+
+	sess->userlist_blocks = 0;
+
+	while (len > 2047) {
+		sess->userlist_blocks++;
+
+		if (gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, 2047, NULL) == -1)
+			return -1;
+
+		if (type == GG_USERLIST_PUT)
+			type = GG_USERLIST_PUT_MORE;
+
+		request += 2047;
+		len -= 2047;
+	}
+
+	sess->userlist_blocks++;
+
+	return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, len, NULL);
 }
 
 /*
@@ -736,7 +897,7 @@
 static int gg_watch_fd_connected(struct gg_session *sess, struct gg_event *e)
 {
 	struct gg_header *h;
-	void *p;
+	char *p;
 
 	if (!sess) {
 		errno = EFAULT;
@@ -752,8 +913,11 @@
 
 	p = (void *)h + sizeof(struct gg_header);
 	
-	if (h->type == GG_RECV_MSG) {
-		struct gg_recv_msg *r = p;
+	
+	switch (h->type) {
+	    case GG_RECV_MSG:
+	    {
+		struct gg_recv_msg *r = (void *)p;
 
 		gg_debug(GG_DEBUG_MISC, "-- received a message\n");
 
@@ -764,10 +928,11 @@
 			e->event.msg.message = strdup((char*) r + sizeof(*r));
 			e->event.msg.time = fix32(r->time);
 		}
-	}
-
-	if (h->type == GG_NOTIFY_REPLY) {
-		struct gg_notify_reply *n = p;
+		break;
+	    }
+	    case GG_NOTIFY_REPLY:
+	    {
+		struct gg_notify_reply *n = (void *)p;
 		int count, i;
 
 		gg_debug(GG_DEBUG_MISC, "-- received a notify reply\n");
@@ -785,10 +950,77 @@
 			e->event.notify[i].uin = fix32(e->event.notify[i].uin);
 			e->event.notify[i].status = fix32(e->event.notify[i].status);
 		}
-	}
+		break;
+	    }
+	    
+	    case GG_NOTIFY_REPLY60:
+	    {
+		    struct gg_notify_reply60 *n = (void*) p;
+		    unsigned int length = h->length, i = 0;
+
+		    gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n");
+
+		    e->type = GG_EVENT_NOTIFY60;
+		    e->event.notify60 = malloc(sizeof(*e->event.notify60));
+
+		    if (!e->event.notify60) {
+			    gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+			    goto fail;
+		    }
+
+		    e->event.notify60[0].uin = 0;
+		    
+		    while (length >= sizeof(struct gg_notify_reply60)) {
+			    uin_t uin = fix32(n->uin);
+			    char *tmp;
+
+			    e->event.notify60[i].uin = uin & 0x00ffffff;
+			    e->event.notify60[i].status = n->status;
+			    e->event.notify60[i].remote_ip = n->remote_ip;
+			    e->event.notify60[i].remote_port = fix16(n->remote_port);
+			    e->event.notify60[i].version = n->version;
+			    e->event.notify60[i].image_size = n->image_size;
+			    e->event.notify60[i].descr = NULL;
+			    e->event.notify60[i].time = 0;
+
+			    if (GG_S_D(n->status)) {
+				    unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply60));
 
-	if (h->type == GG_STATUS) {
-		struct gg_status *s = p;
+				    if (descr_len < length) {
+					    if (!(e->event.notify60[i].descr = malloc(descr_len + 1))) {
+						    gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+						    goto fail;
+					    }
+
+					    memcpy(e->event.notify60[i].descr, (char*) n + sizeof(struct gg_notify_reply60) + 1, descr_len);
+					    e->event.notify60[i].descr[descr_len] = 0;
+
+					    /* XXX czas */
+				    }
+				    
+				    length -= sizeof(struct gg_notify_reply60) + descr_len + 1;
+				    n = (void*) ((char*) n + sizeof(struct gg_notify_reply60) + descr_len + 1);
+			    } else {
+				    length -= sizeof(struct gg_notify_reply60);
+				    n = (void*) ((char*) n + sizeof(struct gg_notify_reply60));
+			    }
+
+			    if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) {
+				    gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
+				    free(e->event.notify60);
+				    goto fail;
+			    }
+
+			    e->event.notify60 = (void*) tmp;
+			    e->event.notify60[++i].uin = 0;
+		    }
+
+		    break;
+	    }
+
+	    case GG_STATUS:
+	    {
+		struct gg_status *s = (void *)p;
 
 		gg_debug(GG_DEBUG_MISC, "-- received a status change\n");
 
@@ -798,10 +1030,55 @@
 			e->event.status.uin = fix32(e->event.status.uin);
 			e->event.status.status = fix32(e->event.status.status);
 		}
-	}
+		break;
+	    }
+	    
+	    case GG_STATUS60:
+	    {
+		    struct gg_status60 *s = (void*) p;
+		    uint32_t uin;
+
+		    gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n");
+
+		    if (h->length < sizeof(*s))
+			    break;
+
+		    uin = fix32(s->uin);
+
+		    e->type = GG_EVENT_STATUS60;
+		    e->event.status60.uin = uin & 0x00ffffff;
+		    e->event.status60.status = s->status;
+		    e->event.status60.remote_ip = s->remote_ip;
+		    e->event.status60.remote_port = fix16(s->remote_port);
+		    e->event.status60.version = s->version;
+		    e->event.status60.image_size = s->image_size;
+		    e->event.status60.descr = NULL;
+		    e->event.status60.time = 0;
 
-	if (h->type == GG_SEND_MSG_ACK) {
-		struct gg_send_msg_ack *s = p;
+		    if (uin & 0x40000000)
+			    e->event.status60.version |= GG_HAS_AUDIO_MASK;
+
+		    if (h->length > sizeof(*s)) {
+			    int len = h->length - sizeof(*s);
+			    char *buf = malloc(len + 1);
+
+			    if (buf) {
+				    memcpy(buf, (char*) p + sizeof(*s), len);
+				    buf[len] = 0;
+			    }
+
+			    e->event.status60.descr = buf;
+
+			    if (len > 4 && p[h->length - 5] == 0)
+				    e->event.status60.time = *((int*) (p + h->length - 4));
+		    }
+
+		    break;
+	    }
+
+	    case GG_SEND_MSG_ACK:
+	    {
+		struct gg_send_msg_ack *s = (void *)p;
 
 		gg_debug(GG_DEBUG_MISC, "-- received a message ack\n");
 
@@ -811,17 +1088,73 @@
 			e->event.ack.recipient = fix32(s->recipient);
 			e->event.ack.seq = fix32(s->seq);
 		}
-	}
+		break;
+	    }
 
-	if (h->type == GG_PONG) {
+	    case GG_PONG:
+	    {
 		gg_debug(GG_DEBUG_MISC, "-- received a pong\n");
 		ping_outstanding = 0;
 		sess->last_pong = time(NULL);
+		break;
+	    }
+
+	    case GG_USERLIST_REPLY:
+	    {
+		    gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received userlist reply\n");
+
+		    if (h->length < 1)
+			    break;
+
+		    /* jeśli odpowiedź na eksport, wywołaj zdarzenie tylko
+		     * gdy otrzymano wszystkie odpowiedzi */
+		    if (p[0] == GG_USERLIST_PUT_REPLY || p[0] == GG_USERLIST_PUT_MORE_REPLY) {
+			    if (--sess->userlist_blocks)
+				    break;
+
+			    p[0] = GG_USERLIST_PUT_REPLY;
+		    }
+
+		    if (h->length > 1) {
+			    char *tmp;
+			    int len = (sess->userlist_reply) ? strlen(sess->userlist_reply) : 0;
+			    
+			    gg_debug(GG_DEBUG_MISC, "userlist_reply=%p, len=%d\n", sess->userlist_reply, len);
+			    
+			    if (!(tmp = realloc(sess->userlist_reply, len + h->length))) {
+				    gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for userlist reply\n");
+				    free(sess->userlist_reply);
+				    sess->userlist_reply = NULL;
+				    goto fail;
+			    }
+
+			    sess->userlist_reply = tmp;
+			    sess->userlist_reply[len + h->length - 1] = 0;
+			    memcpy(sess->userlist_reply + len, p + 1, h->length - 1);
+		    }
+
+		    if (p[0] == GG_USERLIST_GET_MORE_REPLY)
+			    break;
+
+		    e->type = GG_EVENT_USERLIST;
+		    e->event.userlist.type = p[0];
+		    e->event.userlist.reply = sess->userlist_reply;
+		    sess->userlist_reply = NULL;
+
+		    break;
+		}
+
+	    default:
+		gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received unknown packet 0x%.2x\n", h->type);
 	}
 
 	free(h);
 
 	return 0;
+	
+fail:
+	free(h);
+	return -1;
 }
 
 /*
@@ -1023,6 +1356,7 @@
 	
 			gg_debug(GG_DEBUG_TRAFFIC, "-- received http data (%s)\n", buf);
 						
+			/* analizujemy otrzymane dane. */
 			tmp = buf;
 			
 			while (*tmp && *tmp != ' ')
@@ -1049,6 +1383,7 @@
 
 			a.s_addr = inet_addr(host);
 			sess->server_ip = a.s_addr;
+
 #if 0
 			/* We need to watch this non-blocking socket so lets use gaim_proxy_connect 
 			   in gg.c - Herman */
@@ -1104,7 +1439,7 @@
 		{
 			struct gg_header *h;			
 			struct gg_welcome *w;
-			struct gg_login l;
+			struct gg_login60 l;
 			unsigned int hash;
 			char *password = sess->password;
 
@@ -1133,10 +1468,8 @@
 	
 			w = (struct gg_welcome *)((void *)h + sizeof(struct gg_header));
 			w->key = fix32(w->key);
-
-			for (hash = 1; *password; password++)
-				hash *= (*password) + 1;
-			hash *= w->key;
+			
+			hash = gg_login_hash(password, w->key);
 	
 			gg_debug(GG_DEBUG_DUMP, "%%%% klucz serwera %.4x, hash hasła %.8x\n", w->key, hash);
 	
@@ -1148,13 +1481,13 @@
 			l.uin = fix32(sess->uin);
 			l.hash = fix32(hash);
 			l.status = fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL);
-			l.dunno = fix32(0x0b);
+			l.version = fix32(0x20);
 			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) {
+			if (gg_send_packet(sess, GG_LOGIN60, &l, sizeof(l), NULL, 0) == -1) {
 				gg_debug(GG_DEBUG_TRAFFIC, "-- oops, failed. errno = %d (%s)\n", errno, strerror(errno));
 
 				close(sess->fd);
--- a/src/protocols/gg/libgg.h	Fri Apr 23 17:13:33 2004 +0000
+++ b/src/protocols/gg/libgg.h	Fri Apr 23 17:24:19 2004 +0000
@@ -1,4 +1,4 @@
-/* $Id: libgg.h 8872 2004-01-21 05:34:32Z lschiere $ */
+/* $Id: libgg.h 9537 2004-04-23 17:24:19Z lschiere $ */
 
 /*
  *  (C) Copyright 2001 Wojtek Kaniewski <wojtekka@irc.pl>,
@@ -29,6 +29,7 @@
   #define INADDR_NONE 0xffffffff
 #endif
 
+#include <stdint.h>
 #include <sys/types.h>
 
 /*
@@ -64,6 +65,10 @@
 	char *recv_buf;		/* bufor na otrzymywane pakiety */
 	int recv_done;		/* ile już wczytano do bufora */
         int recv_left;		/* i ile jeszcze trzeba wczytać */
+	
+	char *userlist_reply;	/* fragment odpowiedzi listy kontaktów */
+
+	int userlist_blocks;	/* na ile kawałków podzielono listę kontaktów */
 };
 
 /*
@@ -153,9 +158,12 @@
 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_write(struct gg_session *sess, const char *buf, int length);
 int gg_change_status(struct gg_session *sess, int status);
-int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, char *message);
+int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message);
+int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen);
 int gg_ping(struct gg_session *sess);
+int gg_userlist_request(struct gg_session *sess, char type, const char *request);
 
 struct gg_notify_reply {
 	uin_t uin;			/* numerek */
@@ -170,6 +178,39 @@
 #endif
 ;
 
+#define GG_NOTIFY_REPLY60 0x0011
+	
+struct gg_notify_reply60 {
+	uint32_t uin;			/* numerek plus flagi w MSB */
+	uint8_t status;			/* status danej osoby */
+	uint32_t remote_ip;		/* adres ip delikwenta */
+	uint16_t remote_port;		/* port, na którym słucha klient */
+	uint8_t version;		/* wersja klienta */
+	uint8_t image_size;		/* maksymalny rozmiar grafiki w KiB */
+	uint8_t dunno1;			/* 0x00 */
+}
+#ifdef __GNUC__
+__attribute__ ((packed))
+#endif
+;
+
+#define GG_STATUS60 0x000f
+	
+struct gg_status60 {
+	uint32_t uin;			/* numerek plus flagi w MSB */
+	uint8_t status;			/* status danej osoby */
+	uint32_t remote_ip;		/* adres ip delikwenta */
+	uint16_t remote_port;		/* port, na którym słucha klient */
+	uint8_t version;		/* wersja klienta */
+	uint8_t image_size;		/* maksymalny rozmiar grafiki w KiB */
+	uint8_t dunno1;			/* 0x00 */
+}
+#ifdef __GNUC__
+__attribute__ ((packed))
+#endif
+;
+
+
 struct gg_status {
 	uin_t uin;			/* numerek */
 	unsigned long status;		/* nowy stan */
@@ -186,7 +227,10 @@
 	GG_EVENT_STATUS,
 	GG_EVENT_ACK,
 	GG_EVENT_CONN_FAILED,
-	GG_EVENT_CONN_SUCCESS
+	GG_EVENT_CONN_SUCCESS,
+	GG_EVENT_STATUS60,		/* ktoś zmienił stan w GG 6.0 */
+	GG_EVENT_NOTIFY60,		/* ktoś się pojawił w GG 6.0 */
+	GG_EVENT_USERLIST,		/* odpowiedź listy kontaktów w GG 6.0 */
 };
 
 /*
@@ -228,7 +272,31 @@
                         unsigned char *message;
                 } msg;
                 struct gg_notify_reply *notify;
+		struct {			/* @notify60 informacja o liście kontaktów -- GG_EVENT_NOTIFY60 */
+			uin_t uin;		/* numer */
+			int status;	/* stan */
+			uint32_t remote_ip;	/* adres ip */
+			uint16_t remote_port;	/* port */
+			int version;	/* wersja klienta */
+			int image_size;	/* maksymalny rozmiar grafiki w KiB */
+			char *descr;		/* opis stanu */
+			time_t time;		/* czas powrotu */
+		} *notify60;
                 struct gg_status status;
+		struct {			/* @status60 zmiana stanu -- GG_EVENT_STATUS60 */
+			uin_t uin;		/* numer */
+			int status;	/* nowy stan */
+			uint32_t remote_ip;	/* adres ip */
+			uint16_t remote_port;	/* port */
+			int version;	/* wersja klienta */
+			int image_size;	/* maksymalny rozmiar grafiki w KiB */
+			char *descr;		/* opis stanu */
+			time_t time;		/* czas powrotu */
+		} status60;
+		struct {			/* @userlist odpowiedź listy kontaktów serwera */
+			char type;		/* rodzaj odpowiedzi */
+			char *reply;		/* treść odpowiedzi */
+		} userlist;
                 struct {
                         uin_t recipient;
                         int status;
@@ -387,6 +455,7 @@
 #define GG_DEFAULT_PORT 8074
 #define GG_HTTPS_PORT 443
 #define GG_HTTP_USERAGENT "Mozilla/4.0 (compatible MSIE 5.0; Windows 98; I)"
+#define GG_HAS_AUDIO_MASK 0x40000000
 
 struct gg_header {
 	unsigned long type;		/* typ pakietu */
@@ -413,7 +482,7 @@
 	uin_t uin;			/* twój numerek */
 	unsigned long hash;		/* hash hasła */
 	unsigned long status;		/* status na dzień dobry */
-	unsigned long dunno;		/* == 0x0b */
+	unsigned long version;		/* == 0x20 */
 	unsigned long local_ip;		/* mój adres ip */
 	unsigned short local_port;	/* port, na którym słucham */
 }
@@ -422,18 +491,49 @@
 #endif
 ;
 
+#define GG_LOGIN60 0x0015
+
+struct gg_login60 {
+	uint32_t uin;			/* mój numerek */
+	uint32_t hash;			/* hash hasła */
+	uint32_t status;		/* status na dzień dobry */
+	uint32_t version;		/* moja wersja klienta */
+	uint8_t dunno1;			/* 0x00 */
+	uint32_t local_ip;		/* mój adres ip */
+	uint16_t local_port;		/* port, na którym słucham */
+	uint32_t external_ip;		/* zewnętrzny adres ip */
+	uint16_t external_port;		/* zewnętrzny port */
+	uint8_t image_size;		/* maksymalny rozmiar grafiki w KiB */
+	uint8_t dunno2;			/* 0xbe */
+}
+#ifdef __GNUC__
+__attribute__ ((packed))
+#endif
+;
+
 #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_NOT_AVAIL 0x0001		/* niedostępny */
+#define GG_STATUS_NOT_AVAIL_DESCR 0x0015	/* niedostępny z opisem (4.8) */
+#define GG_STATUS_AVAIL 0x0002			/* dostępny */
+#define GG_STATUS_AVAIL_DESCR 0x0004		/* dostępny z opisem (4.9) */
+#define GG_STATUS_BUSY 0x0003			/* zajęty */
+#define GG_STATUS_BUSY_DESCR 0x0005		/* zajęty z opisem (4.8) */
+#define GG_STATUS_INVISIBLE 0x0014		/* niewidoczny (4.6) */
+#define GG_STATUS_INVISIBLE_DESCR 0x0016	/* niewidoczny z opisem (4.9) */
+#define GG_STATUS_BLOCKED 0x0006		/* zablokowany */
 
-#define GG_STATUS_FRIENDS_MASK 0x8000	/* tylko dla znajomych (GG 4.6) */
+#define GG_STATUS_FRIENDS_MASK 0x8000		/* tylko dla znajomych (4.6) */
+
+/* GG_S() stan bez uwzględnienia trybu tylko dla znajomych */
+#define GG_S(x) ((x) & ~GG_STATUS_FRIENDS_MASK)
+
+/* GG_S_D() stan opisowy */
+#define GG_S_D(x) (GG_S(x) == GG_STATUS_NOT_AVAIL_DESCR || GG_S(x) == GG_STATUS_AVAIL_DESCR || GG_S(x) == GG_STATUS_BUSY_DESCR || GG_S(x) == GG_STATUS_INVISIBLE_DESCR)
 
 struct gg_new_status {
 	unsigned long status;			/* na jaki zmienić? */
@@ -523,6 +623,45 @@
 	
 #define GG_PONG 0x0007
 
+#define GG_USERLIST_REQUEST 0x0016
+
+#define GG_USERLIST_PUT 0x00
+#define GG_USERLIST_PUT_MORE 0x01
+#define GG_USERLIST_GET 0x02
+
+struct gg_userlist_request {
+	uint8_t type;
+}
+#ifdef __GNUC__
+__attribute__ ((packed))
+#endif
+;
+
+#define GG_USERLIST_REPLY 0x0010
+
+#define GG_USERLIST_PUT_REPLY 0x00
+#define GG_USERLIST_PUT_MORE_REPLY 0x02
+#define GG_USERLIST_GET_REPLY 0x06
+#define GG_USERLIST_GET_MORE_REPLY 0x04
+
+struct gg_userlist_reply {
+	uint8_t type;
+}
+#ifdef __GNUC__
+__attribute__ ((packed))
+#endif
+;
+
+/* listy */
+
+struct list {
+	void *data;
+	struct list *next;
+};
+
+typedef struct list * list_t;
+
+
 #ifdef __cplusplus
 }
 #endif