/* $Id: libgg.c,v 1.19 2004/08/04 18:27:09 lschiere Exp $ */ /* * (C) Copyright 2001 Wojtek Kaniewski , * Robert J. Woźny * * 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 #ifndef _WIN32 #include #include #include #include #include #include #include #else #include #endif #include #include #include #include #include #include #ifndef _AIX # include #endif #include #include #ifdef sun #include #endif #include #if G_BYTE_ORDER == G_BIG_ENDIAN # define WORDS_BIGENDIAN 1 #endif #include "internal.h" #include "libgg.h" #include "proxy.h" #include "debug.h" #ifdef _WIN32 #include "win32dep.h" #endif int gg_debug_level = (GG_DEBUG_NET | GG_DEBUG_TRAFFIC | GG_DEBUG_DUMP | GG_DEBUG_FUNCTION | GG_DEBUG_MISC); int gg_http_use_proxy = 0; int gg_http_proxy_port = 0; char *gg_http_proxy_host = NULL; /* temp -Herman */ static int ping_outstanding = 0; #ifndef lint static char rcsid[] #ifdef __GNUC__ __attribute__ ((unused)) #endif = "$Id: libgg.c,v 1.19 2004/08/04 18:27:09 lschiere Exp $"; #endif /* * fix32() // funkcja wewnętrzna * * dla maszyn big-endianowych zamienia kolejność bajtów w ,,long''ach. */ static inline unsigned long fix32(unsigned long x) { #ifndef WORDS_BIGENDIAN 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) { #ifndef WORDS_BIGENDIAN return x; #else return (unsigned short) (((x & (unsigned short) 0x00ffU) << 8) | ((x & (unsigned short) 0xff00U) >> 8)); #endif } #ifndef _WIN32 /* * 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; } #endif /*!_WIN32*/ /* * 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; int sizeh = sizeof(struct gg_header); gg_debug(GG_DEBUG_FUNCTION, "** gg_recv_packet(...);\n"); if (!sess) { errno = EFAULT; return NULL; } if (sess->recv_left < 1) { while (ret != sizeh) { ret = read(sess->fd, &h, sizeh); gg_debug(GG_DEBUG_MISC, "-- header recv(..., %d) = %d\n", sizeh, ret); if (ret < sizeh) { 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, sizeh); } /* 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(sizeh + h.length + 1))) { gg_debug(GG_DEBUG_MISC, "-- not enough memory\n"); return NULL; } memcpy(buf, &h, sizeh); offset = 0; size = h.length; } while (size > 0) { ret = read(sess->fd, buf + sizeh + 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; if ((gg_debug_level & GG_DEBUG_DUMP)) { int i; gg_debug(GG_DEBUG_DUMP, ">> received packet (type=%.2x):", h.type); for (i = 0; i < sizeh + h.length; i++) gg_debug(GG_DEBUG_DUMP, " %.2x", (unsigned char) buf[i]); gg_debug(GG_DEBUG_DUMP, "\n"); } return buf; } /* * gg_send_packet() // funkcja wewnętrzna * * konstruuje pakiet i wysyła go do serwera. * * - 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 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. */ int gg_send_packet(struct gg_session *sess, int type, ...) { struct gg_header *h; char *tmp; int tmp_length; void *payload; int payload_length; va_list ap; int res; gg_debug(GG_DEBUG_FUNCTION, "** gg_send_packet(%p, 0x%.2x, ...)\n", sess, type); 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(0); va_start(ap, type); payload = va_arg(ap, void *); while (payload) { char *tmp2; payload_length = va_arg(ap, int); 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)) { 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() * * 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; char *hostname; int port; 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; sess->last_pong = 0; sess->server_ip = 0; sess->initial_status = 0; sess->type = GG_SESSION_GG; if (gg_http_use_proxy) { hostname = gg_http_proxy_host; port = gg_http_proxy_port; } else { hostname = GG_APPMSG_HOST; port = GG_APPMSG_PORT; }; if (async) { if (gg_resolve(&sess->fd, &sess->pid, hostname)) { gg_debug(GG_DEBUG_MISC, "-- resolving failed\n"); free(sess); return NULL; } } else { struct in_addr a; if ((a.s_addr = inet_addr(hostname)) == INADDR_NONE) { struct hostent *he; if (!(he = gethostbyname(hostname))) { gg_debug(GG_DEBUG_MISC, "-- host %s not found\n", hostname); free(sess); return NULL; } else memcpy((char*) &a, he->h_addr, sizeof(a)); } if (!(sess->fd = gg_connect(&a, port, 0)) == -1) { gg_debug(GG_DEBUG_MISC, "-- connection failed\n"); free(sess); return NULL; } sess->state = GG_STATE_CONNECTING; 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) { errno = EACCES; gg_debug(GG_DEBUG_MISC, "-- could not login\n"); gg_free_event(e); free(sess); return NULL; } gg_free_event(e); } } return sess; } #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. * * - 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, GG_NEW_STATUS, &p, sizeof(p), NULL, 0); } /* * gg_change_status_descr() * * zmienia status uøytkownika na opisowy. * * - sess - opis sesji * - status - nowy status uøytkownika * - descr - opis statusu * * 0, -1. */ int gg_change_status_descr(struct gg_session *sess, int status, const char *descr) { struct gg_new_status p; gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status_descr(%p, %d, \"%s\");\n", sess, status, descr); if (!sess || !descr) { errno = EFAULT; return -1; } if (sess->state != GG_STATE_CONNECTED) { errno = ENOTCONN; return -1; } p.status = fix32(status); return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), descr, (strlen(descr) > GG_STATUS_DESCR_MAXSIZE) ? GG_STATUS_DESCR_MAXSIZE : strlen(descr), NULL); } /* * 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 zignorować albo wykorzystać do potwierdzenia. * * - 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() * * 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_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; } if (sess->state != GG_STATE_CONNECTED) { errno = ENOTCONN; return -1; } 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, GG_SEND_MSG, &s, sizeof(s), message, strlen(message) + 1, format, formatlen, NULL) == -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, GG_PING, NULL); } /* * 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, 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, GG_ADD_NOTIFY, &a, sizeof(a), NULL); } /* * 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, 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); } /* * 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; char *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); switch (h->type) { case GG_RECV_MSG: { struct gg_recv_msg *r = (void *)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)); e->event.msg.time = fix32(r->time); } 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"); 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); } 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 (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"); 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); } 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 (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"); 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); } break; } 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; } /* * 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; #ifndef _WIN32 int port; #endif 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) { #ifndef _WIN32 /* Apparantly we will never be in this state as long as we are using gaim_proxy_connect instead of gg_login - Herman */ 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"); errno = ENOENT; e->type = GG_EVENT_CONN_FAILED; e->event.failure = GG_FAILURE_RESOLVING; sess->state = GG_STATE_IDLE; close(sess->fd); break; } sess->server_ip = a.s_addr; close(sess->fd); waitpid(sess->pid, NULL, 0); gg_debug(GG_DEBUG_MISC, "-- resolved, now connecting\n"); if (gg_http_use_proxy) { port = gg_http_proxy_port; } else { port = GG_APPMSG_PORT; }; if ((sess->fd = gg_connect(&a, port, sess->async)) == -1) { struct in_addr *addr = (struct in_addr*) &sess->server_ip; gg_debug(GG_DEBUG_MISC, "-- connection failed, trying direct connection\n"); if ((sess->fd = gg_connect(addr, GG_DEFAULT_PORT, sess->async)) == -1) { gg_debug(GG_DEBUG_MISC, "-- connection failed, trying https connection\n"); if ((sess->fd = gg_connect(&a, GG_HTTPS_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; } else { sess->state = GG_STATE_CONNECTING; sess->check = GG_CHECK_WRITE; } break; } #endif /* !_WIN32 */ case GG_STATE_CONNECTING: { char buf[1024]; unsigned int res, res_size = sizeof(res); gg_debug(GG_DEBUG_MISC, "== GG_STATE_CONNECTING\n"); if (sess->async && (getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { #if 0 struct in_addr *addr = (struct in_addr*) &sess->server_ip; gg_debug(GG_DEBUG_MISC, "-- http connection failed, errno = %d (%s), trying direct connection\n", res, strerror(res)); if ((sess->fd = gg_connect(addr, GG_DEFAULT_PORT, sess->async)) == -1) { gg_debug(GG_DEBUG_MISC, "-- connection failed, trying https connection\n"); if ((sess->fd = gg_connect(addr, GG_HTTPS_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; #else gg_debug(GG_DEBUG_MISC, "-- http connection failed, errno = %d\n", res); e->type = GG_EVENT_CONN_FAILED; e->event.failure = GG_FAILURE_CONNECTING; sess->state = GG_STATE_IDLE; #endif break; } gg_debug(GG_DEBUG_MISC, "-- http connection succeded, sending query\n"); if (gg_http_use_proxy) { g_snprintf(buf, sizeof(buf) - 1, "GET http://" GG_APPMSG_HOST "/appsvc/appmsg2.asp?fmnumber=%lu&version=%s&lastmsg=0 HTTP/1.0\r\n" "Host: " GG_APPMSG_HOST "\r\n" "User-Agent: " GG_HTTP_USERAGENT "\r\n" "Pragma: no-cache\r\n" "\r\n", sess->uin, gg_urlencode(GG_DEFAULT_CLIENT_VERSION)); } else { g_snprintf(buf, sizeof(buf) - 1, "GET /appsvc/appmsg2.asp?fmnumber=%lu&version=%s&lastmsg=0 HTTP/1.0\r\n" "Host: " GG_APPMSG_HOST "\r\n" "User-Agent: " GG_HTTP_USERAGENT "\r\n" "Pragma: no-cache\r\n" "\r\n", sess->uin, gg_urlencode(GG_DEFAULT_CLIENT_VERSION)); }; gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", buf); if (write(sess->fd, buf, strlen(buf)) < strlen(buf)) { gg_debug(GG_DEBUG_MISC, "-- sending query failed\n"); errno = EIO; e->type = GG_EVENT_CONN_FAILED; e->event.failure = GG_FAILURE_WRITING; sess->state = GG_STATE_IDLE; break; } sess->state = GG_STATE_READING_DATA; sess->check = GG_CHECK_READ; break; } case GG_STATE_READING_DATA: { char buf[1024], *tmp, *host; int port = GG_DEFAULT_PORT; struct in_addr a; gg_debug(GG_DEBUG_MISC, "== GG_STATE_READING_DATA\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"); errno = EINVAL; 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); /* analizujemy otrzymane dane. */ tmp = buf; 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); 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 */ if((sess->fd = gg_connect(&a, port, sess->assync)) == -1) { gg_debug(GG_DEBUG_MISC, "-- connection failed, trying https connection\n"); if ((sess->fd = gg_connect(&a, GG_HTTPS_PORT, sess->async)) == -1) { gg_debug(GG_DEBUG_MISC, "-- connection 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; } } #else sess->port = port; #endif sess->state = GG_STATE_CONNECTING_GG; sess->check = GG_CHECK_WRITE; break; } case GG_STATE_CONNECTING_GG: { unsigned 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)) { struct in_addr *addr = (struct in_addr*) &sess->server_ip; gg_debug(GG_DEBUG_MISC, "-- connection failed, trying https connection\n"); if ((sess->fd = gg_connect(addr, GG_HTTPS_PORT, sess->async)) == -1) { gg_debug(GG_DEBUG_MISC, "-- connection 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; } } gg_debug(GG_DEBUG_MISC, "-- connected\n"); sess->state = GG_STATE_READING_KEY; sess->check = GG_CHECK_READ; break; } case GG_STATE_READING_KEY: { struct gg_header *h; struct gg_welcome *w; struct gg_login60 l; unsigned int hash; char *password = sess->password; gg_debug(GG_DEBUG_MISC, "== GG_STATE_READING_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); errno = EINVAL; e->type = GG_EVENT_CONN_FAILED; e->event.failure = GG_FAILURE_INVALID; sess->state = GG_STATE_IDLE; break; } w = (struct gg_welcome *)((void *)h + sizeof(struct gg_header)); w->key = fix32(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); free(h); free(sess->password); sess->password = NULL; l.uin = fix32(sess->uin); l.hash = fix32(hash); l.status = fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL); 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, 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); e->type = GG_EVENT_CONN_FAILED; e->event.failure = GG_FAILURE_WRITING; sess->state = GG_STATE_IDLE; break; } sess->state = GG_STATE_READING_REPLY; break; } case GG_STATE_READING_REPLY: { struct gg_header *h; gg_debug(GG_DEBUG_MISC, "== GG_STATE_READING_REPLY\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; free(h); 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; errno = EINVAL; } e->type = GG_EVENT_CONN_FAILED; sess->state = GG_STATE_IDLE; close(sess->fd); free(h); 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; } /* * Local variables: * c-indentation-style: k&r * c-basic-offset: 8 * indent-tabs-mode: notnil * End: * * vim: shiftwidth=8: */