Mercurial > pidgin.yaz
view src/protocols/gg/libgg.c @ 2568:c2abbf94b93d
[gaim-migrate @ 2581]
I forgot to commit this file. This removes the warning when exiting.
committer: Tailor Script <tailor@pidgin.im>
author | Rob Flynn <gaim@robflynn.com> |
---|---|
date | Sun, 21 Oct 2001 19:49:20 +0000 |
parents | ba7ee4c1908c |
children | 04a5f68f640c |
line wrap: on
line source
/* $Id: libgg.c 2488 2001-10-10 20:03:17Z 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 <sys/types.h> #include <fcntl.h> #include <netdb.h> #include <errno.h> #include <string.h> #include <stdarg.h> #include <pwd.h> #include <glib.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 G_BYTE_ORDER == G_LITTLE_ENDIAN return x; #else return (unsigned long) (((x & (unsigned long) 0x000000ffU) << 24) | ((x & (unsigned long) 0x0000ff00U) << 8) | ((x & (unsigned long) 0x00ff0000U) >> 8) | ((x & (unsigned long) 0xff000000U) >> 24)); #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 G_BYTE_ORDER == G_LITTLE_ENDIAN return x; #else return (unsigned short) (((x & (unsigned short) 0x00ffU) << 8) | ((x & (unsigned short) 0xff00U) >> 8)); #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) { #ifdef FIONBIO if (ioctl(sock, FIONBIO, &one) == -1) { #else int flags = fcntl (sock, F_GETFL); if (flags < 0 || fcntl (sock, F_SETFL, flags | O_NONBLOCK) < 0) { #endif 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; int count, i; 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; } count = h->length / sizeof(*n); memcpy(e->event.notify, p, h->length); e->event.notify[count].uin = 0; for (i = 0; i < count; i++) { e->event.notify[i].uin = fix32(e->event.notify[i].uin); e->event.notify[i].status = fix32(e->event.notify[i].status); } } 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); e->event.status.uin = fix32(e->event.status.uin); e->event.status.status = fix32(e->event.status.status); } } 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; }