Mercurial > pidgin
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; +} + +