diff src/protocols/gg/libgg.c @ 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
children b2926d21f067
line wrap: on
line diff
--- /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;
+}
+
+