# HG changeset patch # User Bartoz Oler # Date 1125269161 0 # Node ID cf15c1cdcfbdd490ea1df79764bd7571d2a01e8d # Parent 9480e0d0f563412e66cebd53b23da0278e12d292 [gaim-migrate @ 13582] New Gadu-Gadu implementation. committer: Tailor Script diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/Makefile.am --- a/src/protocols/gg/Makefile.am Sun Aug 28 22:21:24 2005 +0000 +++ b/src/protocols/gg/Makefile.am Sun Aug 28 22:46:01 2005 +0000 @@ -1,13 +1,18 @@ -EXTRA_DIST = \ - protocol.txt \ - Makefile.mingw +EXTRA_DIST = Makefile.mingw pkgdir = $(libdir)/gaim GGSOURCES = \ - libgg.c \ - libgg.h \ - common.c \ + lib/common.c \ + lib/dcc.c \ + lib/events.c \ + lib/http.c \ + lib/libgadu.c \ + lib/obsolete.c \ + lib/pubdir.c \ + lib/pubdir50.c \ + lib/compat.h \ + lib/libgadu.h \ gg.c AM_CFLAGS = $(st) @@ -18,6 +23,7 @@ st = -DGAIM_STATIC_PRPL noinst_LIBRARIES = libgg.a +pkg_LTLIBRARIES = libgg_a_SOURCES = $(GGSOURCES) libgg_a_CFLAGS = $(AM_CFLAGS) @@ -26,6 +32,7 @@ st = pkg_LTLIBRARIES = libgg.la +noinst_LIBRARIES = libgg_la_SOURCES = $(GGSOURCES) @@ -33,5 +40,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/protocols/gg/lib \ $(DEBUG_CFLAGS) \ $(GLIB_CFLAGS) + diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/Makefile.mingw --- a/src/protocols/gg/Makefile.mingw Sun Aug 28 22:21:24 2005 +0000 +++ b/src/protocols/gg/Makefile.mingw Sun Aug 28 22:46:01 2005 +0000 @@ -48,6 +48,7 @@ ## INCLUDE_PATHS += -I$(GG_ROOT) \ + -I$(GG_ROOT)/lib \ -I$(GTK_TOP)/include \ -I$(GTK_TOP)/include/gtk-2.0 \ -I$(GTK_TOP)/include/glib-2.0 \ @@ -68,9 +69,18 @@ ## SOURCES, OBJECTS ## -C_SRC = libgg.c \ - common.c \ - gg.c +C_SRC = \ + lib/common.c \ + lib/dcc.c \ + lib/events.c \ + lib/http.c \ + lib/libgadu.c \ + lib/obsolete.c \ + lib/pubdir.c \ + lib/pubdir50.c \ + lib/compat.h \ + lib/libgadu.h \ + gg.c OBJECTS = $(C_SRC:%.c=%.o) diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/common.c --- a/src/protocols/gg/common.c Sun Aug 28 22:21:24 2005 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,338 +0,0 @@ -/* - * (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 "debug.h" - -#include -#include -#ifndef _WIN32 -#include -#include -#include -#include -#include -#include -#include -#include -#endif -#include -#include -#ifndef _AIX -# include -#endif -#include -#include -#ifdef sun - #include -#endif -#include "config.h" -#include "libgg.h" -#include - -#ifdef _WIN32 -#include "win32dep.h" -#endif - -/* - * gg_debug() - * - * 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(int level, char *format, ...) -{ - va_list ap; - - if ((gg_debug_level & level)) { - va_start(ap, format); - /* vprintf(format, ap); */ - gaim_debug_vargs(GAIM_DEBUG_INFO, "gg", format, ap); - va_end(ap); - } -} - -/* - * gg_alloc_sprintf() - * - * robi dokładnie to samo, co sprintf(), tyle że alokuje sobie wcześniej - * miejsce na dane. powinno działać na tych maszynach, które mają funkcję - * vsnprintf() zgodną z C99, jak i na wcześniejszych. - * - * - 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, *tmp; - int size = 0, res; - - va_start(ap, format); - - if ((size = g_vsnprintf(buf, 0, format, ap)) < 1) { - size = 128; - do { - size *= 2; - if (!(tmp = realloc(buf, size))) { - free(buf); - return NULL; - } - buf = tmp; - res = g_vsnprintf(buf, size, format, ap); - } while (res == size - 1); - } else { - if (!(buf = malloc(size + 1))) - return NULL; - } - - g_vsnprintf(buf, size + 1, format, ap); - - va_end(ap); - - return buf; -} - -/* - * gg_get_line() - * - * podaje kolejną linię z bufora tekstowego. psuje co bezpowrotnie, dzieląc - * na kolejne stringi. zdarza się, nie ma potrzeby pisania funkcji dublującej - * bufor żeby tylko mieć nieruszone dane wejściowe, skoro i tak nie będą nam - * poźniej potrzebne. obcina `\r\n'. - * - * - ptr - wskaźnik do zmiennej, która przechowuje aktualną pozycję - * w przemiatanym buforze. - * - * wskaźnik do kolejnej linii tekstu lub NULL, jeśli to już koniec bufora. - */ -char *gg_get_line(char **ptr) -{ - char *foo, *res; - - if (!ptr || !*ptr || !strcmp(*ptr, "")) - return NULL; - - res = *ptr; - - if (!(foo = strchr(*ptr, '\n'))) - *ptr += strlen(*ptr); - else { - *ptr = foo + 1; - *foo = 0; - if (res[strlen(res) - 1] == '\r') - res[strlen(res) - 1] = 0; - } - - return res; -} - -/* - * gg_connect() - * - * łą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, ret, 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 ((ret = connect(sock, (struct sockaddr*) &sin, sizeof(sin))) == -1) { - if (errno && (!async || errno != EINPROGRESS)) { - gg_debug(GG_DEBUG_MISC, "-- connect() failed. errno = %d (%s)\n", errno, strerror(errno)); - return -1; - } - gg_debug(GG_DEBUG_MISC, "-- connect() in progress\n"); - } - - return sock; -} - -/* - * gg_read_line() - * - * 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. - */ -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_chomp() - * - * ucina "\r\n" lub "\n" z końca linii. - * - * - line - ofiara operacji plastycznej. - * - * niczego nie zwraca. - */ -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_urlencode() // funkcja wewnętrzna - * - * zamienia podany tekst na ciąg znaków do formularza http. przydaje się - * przy szukaniu userów z dziwnymi znaczkami. - * - * - str - ciąg znaków do poprawki. - * - * zwraca zaalokowany bufor, który wypadałoby kiedyś zwolnić albo NULL - * w przypadku błędu. - */ -char *gg_urlencode(const char *str) -{ - const char *p, hex[] = "0123456789abcdef"; - char *q, *buf; - - int size = 0; - - if (!str) - str = ""; - - for (p = str; *p; p++, size++) { - if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9'))) - size += 2; - } - - buf = g_new(char, size + 1); - - for (p = str, q = buf; *p; p++, q++) { - if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9')) - *q = *p; - else { - *q++ = '%'; - *q++ = hex[*p >> 4 & 15]; - *q = hex[*p & 15]; - } - } - - *q = 0; - - return buf; -} - -/* - * gg_http_hash() - * - * funkcja, która liczy hash dla adresu e-mail i hasła. - * - * - email - adres email, - * - password - hasło. - * - * zwraca hash wykorzystywany przy rejestracji i wszelkich - * manipulacjach własnego wpisu w katalogu publicznym. - */ - -int gg_http_hash(const char *email, const char *password) -{ - unsigned int a, c; - int b, i; - b = (-1); - - i = 0; - while ((c = (int) email[i++]) != 0) { - a = (c ^ b) + (c << 8); - b = (a >> 24) | (a << 8); - } - - i = 0; - while ((c = (int) password[i++]) != 0) { - a = (c ^ b) + (c << 8); - b = (a >> 24) | (a << 8); - } - - return (b < 0 ? -b : b); -} - -/* - * Local variables: - * c-indentation-style: k&r - * c-basic-offset: 8 - * indent-tabs-mode: notnil - * End: - * - * vim: shiftwidth=8: - */ diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/gg.c --- a/src/protocols/gg/gg.c Sun Aug 28 22:21:24 2005 +0000 +++ b/src/protocols/gg/gg.c Sun Aug 28 22:46:01 2005 +0000 @@ -1,89 +1,79 @@ + /* - * gaim - Gadu-Gadu Protocol Plugin - * - * Copyright (C) 2001 Arkadiusz Miśkiewicz - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * NOTES * - * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * + * I don't like automatic updates of the buddylist stored on the server, so not + * going to implement this. Maybe some kind of option to enable/disable this + * feature. */ + #include "internal.h" -/* Library from EKG (Eksperymentalny Klient Gadu-Gadu) */ -#include "libgg.h" - -#include "account.h" -#include "accountopt.h" +#include "plugin.h" +#include "version.h" +#include "notify.h" +#include "status.h" #include "blist.h" -#include "connection.h" +#include "accountopt.h" #include "debug.h" -#include "notify.h" -#include "proxy.h" -#include "prpl.h" -#include "server.h" #include "util.h" -#include "version.h" +#include "request.h" -#define GG_CONNECT_STEPS 5 +#include "lib/libgadu.h" -#define AGG_BUF_LEN 1024 +static GaimPlugin *my_protocol = NULL; -#define AGG_GENDER_NONE -1 +typedef struct +{ + char *token_id; -#define AGG_PUBDIR_USERLIST_EXPORT_FORM "/appsvc/fmcontactsput.asp" -#define AGG_PUBDIR_USERLIST_IMPORT_FORM "/appsvc/fmcontactsget.asp" -#define AGG_PUBDIR_SEARCH_FORM "/appsvc/fmpubquery2.asp" -#define AGG_REGISTER_DATA_FORM "/appsvc/fmregister.asp" -#define AGG_PUBDIR_MAX_ENTRIES 200 +} GGPToken; + +typedef struct { -#define AGG_STATUS_AVAIL _("Available") -#define AGG_STATUS_AVAIL_FRIENDS _("Available for friends only") -#define AGG_STATUS_BUSY _("Away") -#define AGG_STATUS_BUSY_FRIENDS _("Away for friends only") -#define AGG_STATUS_INVISIBLE _("Invisible") -#define AGG_STATUS_INVISIBLE_FRIENDS _("Invisible for friends only") -#define AGG_STATUS_NOT_AVAIL _("Unavailable") + char *uin; + char *lastname; + char *firstname; + char *nickname; + char *city; + char *birthyear; + char *gender; + char *active; + char *offset; -#define AGG_HTTP_NONE 0 -#define AGG_HTTP_SEARCH 1 -#define AGG_HTTP_USERLIST_IMPORT 2 -#define AGG_HTTP_USERLIST_EXPORT 3 -#define AGG_HTTP_USERLIST_DELETE 4 -#define AGG_HTTP_PASSWORD_CHANGE 5 + char *last_uin; + +} GGPSearchForm; + +typedef struct { -#define UC_NORMAL 2 + struct gg_session *session; + GGPSearchForm *search_form; + GGPToken *register_token; + GGPToken *chpasswd_token; + void *searchresults_window; -struct agg_data { - struct gg_session *sess; - int own_status; -}; +} GGPInfo; -struct agg_http { - GaimConnection *gc; - gchar *request; - gchar *form; - gchar *host; - int inpa; - int type; -}; - - +/** + * Convert enconding of a given string. + * + * @param locstr Input string. + * @param encsrc Current encoding of the string. + * @param encdst Target encoding of the string. + * + * @return Converted string (it must be g_free()ed when not used. Or NULL if + * locstr is NULL. + */ +/* static gchar *charset_convert(const gchar *locstr, const char *encsrc, const char *encdst) {{{ */ static gchar *charset_convert(const gchar *locstr, const char *encsrc, const char *encdst) { gchar *msg; GError *err = NULL; + if (locstr == NULL) + return NULL; + msg = g_convert_with_fallback(locstr, strlen(locstr), encdst, encsrc, "?", NULL, NULL, &err); if (err != NULL) { gaim_debug_error("gg", "Error converting from %s to %s: %s\n", @@ -97,673 +87,96 @@ return msg; } - -static gboolean invalid_uin(const char *uin) -{ - unsigned long int res = strtol(uin, (char **)NULL, 10); - if (res == ULONG_MAX || res == 0) - return TRUE; - return FALSE; -} - -static gint args_compare(gconstpointer a, gconstpointer b) -{ - return g_ascii_strcasecmp((const gchar *)a,(const gchar *)b); -} - -static gboolean allowed_uin(GaimConnection *gc, char *uin) -{ - GaimAccount *account = gaim_connection_get_account(gc); - - switch (account->perm_deny) { - case 1: - /* permit all, deny none */ - return TRUE; - break; - case 2: - /* deny all, permit none. */ - return FALSE; - break; - case 3: - /* permit some. */ - if (g_slist_find_custom(gc->account->permit, uin, args_compare)) - return TRUE; - return FALSE; - break; - case 4: - /* deny some. */ - if (g_slist_find_custom(gc->account->deny, uin, args_compare)) - return FALSE; - return TRUE; - break; - default: - return TRUE; - break; - } -} - -static char *handle_errcode(GaimConnection *gc, int errcode) -{ - static char msg[AGG_BUF_LEN]; - - switch (errcode) { - case GG_FAILURE_RESOLVING: - g_snprintf(msg, sizeof(msg), _("Unable to resolve hostname.")); - break; - case GG_FAILURE_CONNECTING: - g_snprintf(msg, sizeof(msg), _("Unable to connect to server.")); - break; - case GG_FAILURE_INVALID: - g_snprintf(msg, sizeof(msg), _("Invalid response from server.")); - break; - case GG_FAILURE_READING: - g_snprintf(msg, sizeof(msg), _("Error while reading from socket.")); - break; - case GG_FAILURE_WRITING: - g_snprintf(msg, sizeof(msg), _("Error while writing to socket.")); - break; - case GG_FAILURE_PASSWORD: - g_snprintf(msg, sizeof(msg), _("Authentication failed.")); - break; - default: - g_snprintf(msg, sizeof(msg), _("Unknown Error Code.")); - break; - } - - gaim_connection_error(gc, msg); - - return msg; -} +/* }}} */ -static void agg_set_status(GaimAccount *account, GaimStatus *status) +/* + * Convert string to number. Check wheter a given + * string is a correct UIN. + * + * Return UIN or 0 if an error occurred. + */ +/* static uin_t ggp_str_to_uin(const char *text) {{{ */ +static uin_t ggp_str_to_uin(const char *text) { - GaimConnection *gc; - struct agg_data *gd; - gboolean connected; - GaimStatusType *type; - int primitive; - int status_num; - const char *status_id; - char *msg = NULL; - - connected = gaim_account_is_connected(account); - type = gaim_status_get_type(status); - primitive = gaim_status_type_get_primitive(type); - - if (!gaim_status_is_active(status)) - return; - - if (!connected) { - if (primitive != GAIM_STATUS_OFFLINE) - gaim_account_connect(account); - return; - } - - if (primitive == GAIM_STATUS_OFFLINE) { - gaim_account_disconnect(account); - return; - } - - gc = gaim_account_get_connection(account); - gd = (struct agg_data *)gc->proto_data; - status_num = gd->own_status; - status_id = gaim_status_get_id(status); - - if (!strcmp(status_id, "available")) - status_num = GG_STATUS_AVAIL; - else if (!strcmp(status_id, "available-friends")) - status_num = GG_STATUS_AVAIL | GG_STATUS_FRIENDS_MASK; - else if (!strcmp(status_id, "away")) - status_num = GG_STATUS_BUSY; - else if (!strcmp(status_id, "away-friends")) - status_num = GG_STATUS_BUSY | GG_STATUS_FRIENDS_MASK; - else if (!strcmp(status_id, "invisible")) - status_num = GG_STATUS_INVISIBLE; - else if (!strcmp(status_id, "invisible-friends")) - status_num = GG_STATUS_INVISIBLE | GG_STATUS_FRIENDS_MASK; - else if (!strcmp(status_id, "unavailable")) - status_num = GG_STATUS_NOT_AVAIL; - else - /* Don't need to do anything */ - return; - - /* XXX: this was added between the status_rewrite and now and needs to be fixed */ - if (msg != NULL) { - switch (status_num) { - case GG_STATUS_AVAIL: - status_num = GG_STATUS_AVAIL_DESCR; - break; - case GG_STATUS_BUSY: - status_num = GG_STATUS_BUSY_DESCR; - break; - case GG_STATUS_INVISIBLE: - status_num = GG_STATUS_INVISIBLE_DESCR; - break; - case GG_STATUS_NOT_AVAIL: - status_num = GG_STATUS_NOT_AVAIL_DESCR; - break; - } - } - - gd->own_status = status_num; - - if (msg) - gg_change_status_descr(gd->sess, status_num, msg); - else - gg_change_status(gd->sess, status_num); -} - + char *tmp; + long num; -#if 0 -static void agg_get_away(GaimConnection *gc, const char *who) -{ - GaimBuddy *buddy; - char *dialog_msg, **splitmsg; - - if (invalid_uin(who)) - return; - - buddy = gaim_find_buddy(gaim_connection_get_account(gc), who); - if (buddy->proto_data) { - /* Split at (carriage return/newline)'s, then rejoin later with BRs between. */ - splitmsg = g_strsplit(buddy->proto_data, "\r\n", 0); - - dialog_msg = g_strdup_printf(_("UIN: %s
Status: %s
%s"), who, (char *)buddy->proto_data, g_strjoinv("
", splitmsg)); - gaim_notify_userinfo(gc, who, NULL, _("Buddy Information"), buddy->proto_data, dialog_msg, NULL, NULL); - } -} -#endif - -static gchar *get_away_text(GaimBuddy *buddy) -{ - GaimPresence *presence = gaim_buddy_get_presence(buddy); - - if (gaim_presence_is_status_active(presence, "available")) - return AGG_STATUS_AVAIL; - else if (gaim_presence_is_status_active(presence, "available-friends")) - return AGG_STATUS_AVAIL_FRIENDS; - else if (gaim_presence_is_status_active(presence, "away")) - return AGG_STATUS_BUSY; - else if (gaim_presence_is_status_active(presence, "away-friends")) - return AGG_STATUS_BUSY_FRIENDS; - else if (gaim_presence_is_status_active(presence, "invisible")) - return AGG_STATUS_INVISIBLE; - else if (gaim_presence_is_status_active(presence, "invisible-friends")) - return AGG_STATUS_INVISIBLE_FRIENDS; - else if (gaim_presence_is_status_active(presence, "unavailable")) - return AGG_STATUS_NOT_AVAIL; + if (!text) + return 0; - return AGG_STATUS_AVAIL; -} - - -static GList *agg_status_types(GaimAccount *account) -{ - GaimStatusType *type; - GList *types = NULL; - - type = gaim_status_type_new(GAIM_STATUS_OFFLINE, "offline", - _("Offline"), FALSE); - types = g_list_append(types, type); - - type = gaim_status_type_new(GAIM_STATUS_ONLINE, "online", - _("Online"), FALSE); - types = g_list_append(types, type); - - type = gaim_status_type_new_with_attrs( - GAIM_STATUS_AVAILABLE, "available", AGG_STATUS_AVAIL, - TRUE, TRUE, FALSE, - "message", _("Message"), gaim_value_new(GAIM_TYPE_STRING), NULL); - types = g_list_append(types, type); - - type = gaim_status_type_new_with_attrs( - GAIM_STATUS_AWAY, "away", AGG_STATUS_BUSY, - TRUE, TRUE, FALSE, - "message", _("Message"), gaim_value_new(GAIM_TYPE_STRING), NULL); - types = g_list_append(types, type); - - type = gaim_status_type_new_with_attrs( - GAIM_STATUS_HIDDEN, "invisible", AGG_STATUS_INVISIBLE, - TRUE, TRUE, FALSE, - "message", _("Message"), gaim_value_new(GAIM_TYPE_STRING), NULL); - types = g_list_append(types, type); - - type = gaim_status_type_new_with_attrs( - GAIM_STATUS_AVAILABLE, "available-friends", AGG_STATUS_AVAIL_FRIENDS, - TRUE, TRUE, FALSE, - "message", _("Message"), gaim_value_new(GAIM_TYPE_STRING), NULL); - types = g_list_append(types, type); + errno = 0; + num = strtol(text, &tmp, 0); - type = gaim_status_type_new_with_attrs( - GAIM_STATUS_AWAY, "away-friends", AGG_STATUS_BUSY_FRIENDS, - TRUE, TRUE, FALSE, - "message", _("Message"), gaim_value_new(GAIM_TYPE_STRING), NULL); - types = g_list_append(types, type); - - type = gaim_status_type_new_with_attrs( - GAIM_STATUS_HIDDEN, "invisible-friends", AGG_STATUS_INVISIBLE_FRIENDS, - TRUE, TRUE, FALSE, - "message", _("Message"), gaim_value_new(GAIM_TYPE_STRING), NULL); - types = g_list_append(types, type); - - type = gaim_status_type_new_with_attrs( - GAIM_STATUS_UNAVAILABLE, "unavailable", AGG_STATUS_NOT_AVAIL, - TRUE, TRUE, FALSE, - "message", _("Message"), gaim_value_new(GAIM_TYPE_STRING), NULL); - types = g_list_append(types, type); - - return types; -} - -/* Enhance these functions, more options and such stuff */ -static GList *agg_buddy_menu(GaimBuddy *buddy) -{ - GList *m = NULL; - GaimBlistNodeAction *act; - - static char buf[AGG_BUF_LEN]; - g_snprintf(buf, sizeof(buf), _("Status: %s"), get_away_text(buddy)); - - /* um... this seems silly. since in this pass, I'm only converting - over the menu building, I'm not going to mess with it though */ - /* XXX: shouldn't this be in the tooltip instead? */ - act = gaim_blist_node_action_new(buf, NULL, NULL, NULL); - m = g_list_append(m, act); - - return m; -} - + if (*text == '\0' || *tmp != '\0') + return 0; -static GList * -agg_blist_node_menu(GaimBlistNode *node) -{ - if(GAIM_BLIST_NODE_IS_BUDDY(node)) { - return agg_buddy_menu((GaimBuddy *) node); - } else { - return NULL; - } -} - - -static void agg_load_buddy_list(GaimConnection *gc, char *buddylist) -{ - struct agg_data *gd = (struct agg_data *)gc->proto_data; - gchar *ptr = buddylist; - gchar **users_tbl; - int i; - uin_t *userlist = NULL; - int userlist_size = 0; + if ((errno == ERANGE || (num == LONG_MAX || num == LONG_MIN)) || num > UINT_MAX || num < 0) + return 0; - users_tbl = g_strsplit(ptr, "\r\n", AGG_PUBDIR_MAX_ENTRIES); - - /* Parse array of Buddies List */ - for (i = 0; users_tbl[i] != NULL; i++) { - gchar **data_tbl; - gchar *name, *show; - - if (strlen(users_tbl[i])==0) { - gaim_debug(GAIM_DEBUG_MISC, "gg", - "import_buddies_server_results: users_tbl[i] is empty\n"); - continue; - } - - data_tbl = g_strsplit(users_tbl[i], ";", 8); - - show = charset_convert(data_tbl[3], "CP1250", "UTF-8"); - name = data_tbl[6]; - - if (invalid_uin(name)) { - continue; - } + return (uin_t) num; +} +/* }}} */ - gaim_debug(GAIM_DEBUG_MISC, "gg", - "import_buddies_server_results: uin: %s\n", name); - if (!gaim_find_buddy(gc->account, name)) { - GaimBuddy *b; - GaimGroup *g; - /* Default group if none specified on server */ - gchar *group = g_strdup("Gadu-Gadu"); - if (strlen(data_tbl[5])) { - gchar **group_tbl = g_strsplit(data_tbl[5], ",", 2); - if (strlen(group_tbl[0])) { - g_free(group); - group = g_strdup(group_tbl[0]); - } - g_strfreev(group_tbl); - } - /* Add Buddy to our userlist */ - if (!(g = gaim_find_group(group))) { - g = gaim_group_new(group); - gaim_blist_add_group(g, NULL); - } - b = gaim_buddy_new(gc->account, name, strlen(show) ? show : NULL); - gaim_blist_add_buddy(b,NULL,g,NULL); - - userlist_size++; - userlist = g_renew(uin_t, userlist, userlist_size); - userlist[userlist_size - 1] = - (uin_t) strtol((char *)name, (char **)NULL, 10); - - g_free(group); - } - g_free(show); - g_strfreev(data_tbl); - } - g_strfreev(users_tbl); - - if (userlist) { - gg_notify(gd->sess, userlist, userlist_size); - g_free(userlist); - } +/** + * Get UIN of a given account. + * + * @param account Current account. + * + * @return UIN of an account. + */ +/* static ggp_get_uin(GaimAccount *account) {{{ */ +static uin_t ggp_get_uin(GaimAccount *account) +{ + return ggp_str_to_uin(gaim_account_get_username(account)); } - -static void agg_save_buddy_list(GaimConnection *gc, char *existlist) -{ - struct agg_data *gd = (struct agg_data *)gc->proto_data; - GaimBlistNode *gnode, *cnode, *bnode; - char *buddylist = g_strdup(existlist ? existlist : ""); - char *ptr; - - for(gnode = gaim_get_blist()->root; gnode; gnode = gnode->next) { - GaimGroup *g = (GaimGroup *)gnode; - if(!GAIM_BLIST_NODE_IS_GROUP(gnode)) - continue; - for(cnode = gnode->child; cnode; cnode = cnode->next) { - if(!GAIM_BLIST_NODE_IS_CONTACT(cnode)) - continue; - for(bnode = cnode->child; bnode; bnode = bnode->next) { - GaimBuddy *b = (GaimBuddy *)bnode; - - if(!GAIM_BLIST_NODE_IS_BUDDY(bnode)) - continue; - - if(b->account == gc->account) { - gchar *newdata; - /* GG Number */ - gchar *name = b->name; - /* GG Pseudo */ - gchar *show = b->alias ? b->alias : b->name; - /* Group Name */ - gchar *gname = g->name; - - newdata = g_strdup_printf("%s;%s;%s;%s;%s;%s;%s;%s%s\r\n", - show, show, show, show, "", gname, name, "", ""); - - ptr = buddylist; - buddylist = g_strconcat(ptr, newdata, NULL); - - g_free(newdata); - g_free(ptr); - } - } - } - } +/* }}} */ - /* save the list to the gadu gadu server */ - gg_userlist_request(gd->sess, GG_USERLIST_PUT, buddylist); -} - -static void main_callback(gpointer data, gint source, GaimInputCondition cond) +/** + * Create a new GGPSearchForm structure, and set the fields + * to the sane defaults. + * + * @return Newly allocated GGPSearchForm. + */ +/* GGPSearchForm *ggp_searchform_new() {{{ */ +GGPSearchForm *ggp_searchform_new() { - GaimConnection *gc = data; - GaimAccount *account = gaim_connection_get_account(gc); - struct agg_data *gd = gc->proto_data; - struct gg_event *e; - - gaim_debug(GAIM_DEBUG_INFO, "gg", "main_callback enter: begin\n"); - - if (gd->sess->fd != source) - gd->sess->fd = source; - - if (source == 0) { - gaim_connection_error(gc, _("Could not connect")); - return; - } - - if (!(e = gg_watch_fd(gd->sess))) { - gaim_debug(GAIM_DEBUG_ERROR, "gg", - "main_callback: gg_watch_fd failed - CRITICAL!\n"); - gaim_connection_error(gc, _("Unable to read socket")); - return; - } - - switch (e->type) { - case GG_EVENT_NONE: - /* do nothing */ - break; - case GG_EVENT_CONN_SUCCESS: - gaim_debug(GAIM_DEBUG_WARNING, "gg", - "main_callback: CONNECTED AGAIN!?\n"); - break; - case GG_EVENT_CONN_FAILED: - if (gc->inpa) - gaim_input_remove(gc->inpa); - handle_errcode(gc, e->event.failure); - break; - case GG_EVENT_MSG: - { - gchar *imsg; - gchar *jmsg; - gchar user[20]; - - g_snprintf(user, sizeof(user), "%lu", e->event.msg.sender); - if (!allowed_uin(gc, user)) - break; - imsg = charset_convert(e->event.msg.message, "CP1250", "UTF-8"); - gaim_str_strip_cr(imsg); - jmsg = g_markup_escape_text(imsg, -1); - serv_got_im(gc, user, jmsg, 0, e->event.msg.time); - g_free(imsg); - g_free(jmsg); - } - break; - case GG_EVENT_NOTIFY: - { - gchar user[20]; - struct gg_notify_reply *n = e->event.notify; - const char *status_id; - - while (n->uin) { - switch (n->status) { - case GG_STATUS_NOT_AVAIL: - status_id = "unavailable"; - break; - - case GG_STATUS_AVAIL: - status_id = "available"; - break; - - case GG_STATUS_BUSY: - status_id = "away"; - break; - - case GG_STATUS_INVISIBLE: - status_id = "invisible"; - break; - - default: - status_id = "available"; - break; - - } + GGPSearchForm *form; - g_snprintf(user, sizeof(user), "%lu", n->uin); - gaim_prpl_got_user_status(account, user, status_id, NULL); - n++; - } - } - break; - case GG_EVENT_NOTIFY60: - { - gchar user[20]; - const char *status_id; - guint i = 0; - - for (i = 0; e->event.notify60[i].uin; i++) { - GaimBuddy *buddy; - - g_snprintf(user, sizeof(user), "%lu", (long unsigned int)e->event.notify60[i].uin); - - buddy = gaim_find_buddy(gaim_connection_get_account(gc), user); - if (buddy && buddy->proto_data != NULL) { - g_free(buddy->proto_data); - buddy->proto_data = NULL; - } - - switch (e->event.notify60[i].status) { - case GG_STATUS_NOT_AVAIL: - case GG_STATUS_NOT_AVAIL_DESCR: - status_id = "unavailable"; - break; - - case GG_STATUS_AVAIL: - case GG_STATUS_AVAIL_DESCR: - status_id = "available"; - break; - - case GG_STATUS_BUSY: - case GG_STATUS_BUSY_DESCR: - status_id = "away"; - break; + form = g_new0(GGPSearchForm, 1); + form->uin = NULL; + form->lastname = NULL; + form->firstname = NULL; + form->nickname = NULL; + form->city = NULL; + form->birthyear = NULL; + form->gender = NULL; + form->active = NULL; + form->offset = NULL; - case GG_STATUS_INVISIBLE: - case GG_STATUS_INVISIBLE_DESCR: - status_id = "invisible"; - break; - - default: - status_id = "available"; - break; - } - - if (buddy && e->event.notify60[i].descr != NULL) { - buddy->proto_data = g_strdup(e->event.notify60[i].descr); - } - - gaim_prpl_got_user_status(account, user, status_id, NULL); - i++; - } - } - break; - case GG_EVENT_STATUS: - { - gchar user[20]; - const char *status_id; - - switch (e->event.status.status) { - case GG_STATUS_NOT_AVAIL: - status_id = "unavailable"; - break; - - case GG_STATUS_AVAIL: - status_id = "available"; - break; - - case GG_STATUS_BUSY: - status_id = "away"; - break; - - case GG_STATUS_INVISIBLE: - status_id = "invisible"; - break; - - default: - status_id = "available"; - break; - } + form->last_uin = NULL; - g_snprintf(user, sizeof(user), "%lu", e->event.status.uin); - gaim_prpl_got_user_status(account, user, status_id, NULL); - } - break; - case GG_EVENT_STATUS60: - { - gchar user[20]; - const char *status_id; - - GaimBuddy *buddy; - - g_snprintf(user, sizeof(user), "%lu", e->event.status60.uin); - - buddy = gaim_find_buddy(gaim_connection_get_account(gc), user); - if (buddy && buddy->proto_data != NULL) { - g_free(buddy->proto_data); - buddy->proto_data = NULL; - } + return form; +} +/* }}} */ - switch (e->event.status60.status) { - case GG_STATUS_NOT_AVAIL_DESCR: - case GG_STATUS_NOT_AVAIL: - status_id = "unavailable"; - break; - - case GG_STATUS_AVAIL: - case GG_STATUS_AVAIL_DESCR: - status_id = "available"; - break; - - case GG_STATUS_BUSY: - case GG_STATUS_BUSY_DESCR: - status_id = "away"; - break; - - case GG_STATUS_INVISIBLE: - case GG_STATUS_INVISIBLE_DESCR: - status_id = "invisible"; - break; +/* ---------------------------------------------------------------------- */ +/* ----- BUDDYLIST STUFF ------------------------------------------------ */ +/* ---------------------------------------------------------------------- */ - default: - status_id = "available"; - break; - } - - if (buddy && e->event.status60.descr != NULL) { - buddy->proto_data = g_strdup(e->event.status60.descr); - } - - gaim_prpl_got_user_status(account, user, status_id, NULL); - } - break; - case GG_EVENT_ACK: - gaim_debug(GAIM_DEBUG_MISC, "gg", - "main_callback: message %d to %lu sent with status %d\n", - e->event.ack.seq, e->event.ack.recipient, e->event.ack.status); - break; - case GG_EVENT_USERLIST: - { - gaim_debug(GAIM_DEBUG_MISC, "gg", "main_callback: received userlist reply\n"); - switch (e->event.userlist.type) { - case GG_USERLIST_GET_REPLY: - { +/* + * Adapted from the previous GG implementation in Gaim + * by Arkadiusz MiÂśkiewicz + */ +/* static void ggp_buddylist_send(GaimConnection *gc) {{{ */ +static void ggp_buddylist_send(GaimConnection *gc) +{ + GGPInfo *info = gc->proto_data; - if (e->event.userlist.reply) { - agg_load_buddy_list(gc, e->event.userlist.reply); - } - break; - } - - case GG_USERLIST_PUT_REPLY: - { - /* ignored */ - } - } - } - - default: - gaim_debug(GAIM_DEBUG_ERROR, "gg", - "main_callback: unsupported event %d\n", e->type); - break; - } - gg_free_event(e); -} - -static void agg_send_buddylist(GaimConnection *gc) -{ GaimBuddyList *blist; GaimBlistNode *gnode, *cnode, *bnode; GaimBuddy *buddy; - struct agg_data *gd = (struct agg_data *)gc->proto_data; uin_t *userlist = NULL; + gchar *types = NULL; int userlist_size = 0; if ((blist = gaim_get_blist()) != NULL) @@ -781,1061 +194,1612 @@ if (!GAIM_BLIST_NODE_IS_BUDDY(bnode)) continue; buddy = (GaimBuddy *)bnode; - if (invalid_uin(buddy->name)) + + if (buddy->account != gc->account) continue; + userlist_size++; - userlist = g_renew(uin_t, userlist, userlist_size); - userlist[userlist_size - 1] = - (uin_t) strtol(buddy->name, (char **)NULL, 10); + userlist = (uin_t *) g_renew(uin_t, userlist, userlist_size); + types = (gchar *) g_renew(gchar, types, userlist_size); + userlist[userlist_size - 1] = ggp_str_to_uin(buddy->name); + types[userlist_size - 1] = GG_USER_NORMAL; + gaim_debug_info("gg", "ggp_buddylist_send: adding %d\n", userlist[userlist_size - 1]); } } } } if (userlist) { - gg_notify(gd->sess, userlist, userlist_size); + int ret = gg_notify_ex(info->session, userlist, types, userlist_size); g_free(userlist); - } - - agg_save_buddy_list(gc, NULL); -} - -void login_callback(gpointer data, gint source, GaimInputCondition cond) -{ - GaimConnection *gc = data; - struct agg_data *gd = gc->proto_data; - struct gg_event *e; - - gaim_debug(GAIM_DEBUG_INFO, "gg", "login_callback...\n"); - if (!g_list_find(gaim_connections_get_all(), gc)) { - close(source); - return; - } - - gaim_debug(GAIM_DEBUG_INFO, "gg", "Found GG connection\n"); - - if (source == 0) { - gaim_connection_error(gc, _("Unable to connect.")); - return; - } - - gd->sess->fd = source; - - gaim_debug(GAIM_DEBUG_MISC, "gg", "Source is valid.\n"); - if (gc->inpa == 0) { - gaim_debug(GAIM_DEBUG_MISC, "gg", - "login_callback.. checking gc->inpa .. is 0.. setting fd watch\n"); - gc->inpa = gaim_input_add(gd->sess->fd, GAIM_INPUT_READ, login_callback, gc); - gaim_debug(GAIM_DEBUG_INFO, "gg", "Adding watch on fd\n"); - } - gaim_debug(GAIM_DEBUG_INFO, "gg", "Checking State.\n"); - switch (gd->sess->state) { - case GG_STATE_READING_DATA: - gaim_connection_update_progress(gc, _("Reading data"), 1, GG_CONNECT_STEPS); - break; - case GG_STATE_CONNECTING_GG: - gaim_connection_update_progress(gc, _("Balancer handshake"), 2, GG_CONNECT_STEPS); - break; - case GG_STATE_READING_KEY: - gaim_connection_update_progress(gc, _("Reading server key"), 3, GG_CONNECT_STEPS); - break; - case GG_STATE_READING_REPLY: - gaim_connection_update_progress(gc, _("Exchanging key hash"), 4, GG_CONNECT_STEPS); - break; - default: - gaim_debug(GAIM_DEBUG_INFO, "gg", "No State found\n"); - break; - } - gaim_debug(GAIM_DEBUG_MISC, "gg", "gg_watch_fd\n"); - if (!(e = gg_watch_fd(gd->sess))) { - gaim_debug(GAIM_DEBUG_ERROR, "gg", - "login_callback: gg_watch_fd failed - CRITICAL!\n"); - gaim_connection_error(gc, _("Critical error in GG library\n")); - return; - } - - /* If we are GG_STATE_CONNECTING_GG then we still need to connect, as - we could not use gaim_proxy_connect in libgg.c */ - switch( gd->sess->state ) { - case GG_STATE_CONNECTING_GG: - { - struct in_addr ip; - char buf[256]; - - /* Remove watch on initial socket - now that we have ip and port of login server */ - gaim_input_remove(gc->inpa); - - ip.s_addr = gd->sess->server_ip; + g_free(types); - if (gaim_proxy_connect(gc->account, inet_ntoa(ip), gd->sess->port, login_callback, gc) < 0) { - g_snprintf(buf, sizeof(buf), _("Connect to %s failed"), inet_ntoa(ip)); - gaim_connection_error(gc, buf); - return; - } - break; - } - case GG_STATE_READING_KEY: - /* Set new watch on login server ip */ - if(gc->inpa) - gc->inpa = gaim_input_add(gd->sess->fd, GAIM_INPUT_READ, login_callback, gc); - gaim_debug(GAIM_DEBUG_INFO, "gg", - "Setting watch on connection with login server.\n"); - break; - }/* end switch() */ - - gaim_debug(GAIM_DEBUG_MISC, "gg", "checking gg_event\n"); - switch (e->type) { - case GG_EVENT_NONE: - /* nothing */ - break; - case GG_EVENT_CONN_SUCCESS: - /* Setup new input handler */ - if (gc->inpa) - gaim_input_remove(gc->inpa); - gc->inpa = gaim_input_add(gd->sess->fd, GAIM_INPUT_READ, main_callback, gc); - - /* Our signon is complete */ - gaim_connection_set_state(gc, GAIM_CONNECTED); - - /* Send the server our buddy list */ - agg_send_buddylist(gc); - - break; - case GG_EVENT_CONN_FAILED: - gaim_input_remove(gc->inpa); - gc->inpa = 0; - handle_errcode(gc, e->event.failure); - break; - default: - gaim_debug(GAIM_DEBUG_MISC, "gg", "no gg_event\n"); - break; - } - gaim_debug(GAIM_DEBUG_INFO, "gg", "Returning from login_callback\n"); - gg_free_event(e); -} - -static void agg_keepalive(GaimConnection *gc) -{ - struct agg_data *gd = (struct agg_data *)gc->proto_data; - if (gg_ping(gd->sess) < 0) { - gaim_connection_error(gc, _("Unable to ping server")); - return; - } -} - -static void agg_login(GaimAccount *account, GaimStatus *status) -{ - GaimConnection *gc = gaim_account_get_connection(account); - struct agg_data *gd = gc->proto_data = g_new0(struct agg_data, 1); - char buf[80]; - -#if 0 - gc->checkbox = _("Send as message"); -#endif - - gd->sess = g_new0(struct gg_session, 1); - - gaim_connection_update_progress(gc, _("Looking up GG server"), 0, GG_CONNECT_STEPS); - - if (invalid_uin(account->username)) { - gaim_connection_error(gc, _("Invalid Gadu-Gadu UIN specified")); - return; - } - - gc->inpa = 0; - - /* - if (gg_login(gd->sess, strtol(user->username, (char **)NULL, 10), user->password, 1) < 0) { - gaim_debug(GAIM_DEBUG_MISC, "gg", "uin=%u, pass=%s", strtol(user->username, (char **)NULL, 10), user->password); - gaim_connection_error(gc, _("Unable to connect.")); - signoff(gc); - return; - } - - gg_login() sucks for me, so I'm using gaim_proxy_connect() - */ - - gd->sess->uin = (uin_t) strtol(account->username, (char **)NULL, 10); - gd->sess->password = g_strdup(gaim_connection_get_password(gc)); - gd->sess->state = GG_STATE_CONNECTING; - gd->sess->check = GG_CHECK_WRITE; - gd->sess->async = 1; - if (gaim_proxy_connect(account, GG_APPMSG_HOST, GG_APPMSG_PORT, login_callback, gc) < 0) { - g_snprintf(buf, sizeof(buf), _("Connect to %s failed"), GG_APPMSG_HOST); - gaim_connection_error(gc, buf); - return; + gaim_debug_info("gg", "send: ret=%d; size=%d\n", ret, userlist_size); } } - -static void agg_close(GaimConnection *gc) -{ - struct agg_data *gd = (struct agg_data *)gc->proto_data; - if (gc->inpa) - gaim_input_remove(gc->inpa); - gg_logoff(gd->sess); - gd->own_status = GG_STATUS_NOT_AVAIL; - gg_free_session(gd->sess); - g_free(gc->proto_data); -} - -static int agg_send_im(GaimConnection *gc, const char *who, const char *msg, GaimConvImFlags flags) -{ - struct agg_data *gd = (struct agg_data *)gc->proto_data; - - if (invalid_uin(who)) { - gaim_notify_error(gc, NULL, - _("You are trying to send a message to an " - "invalid Gadu-Gadu UIN."), NULL); - return -1; - } - - if (strlen(msg) > 0) { - gchar *imsg = charset_convert(msg, "UTF-8", "CP1250"); - if (imsg != NULL && strlen(imsg) > 0) { - if (gg_send_message(gd->sess, GG_CLASS_CHAT, - strtol(who, (char **)NULL, 10), imsg) < 0) - return -1; - } - if (imsg != NULL) - g_free(imsg); - } - return 1; -} - -static void agg_add_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group) -{ - struct agg_data *gd = (struct agg_data *)gc->proto_data; - if (invalid_uin(buddy->name)) - return; - gg_add_notify(gd->sess, strtol(buddy->name, (char **)NULL, 10)); - agg_save_buddy_list(gc, NULL); -} - -static void agg_rem_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group) -{ - struct agg_data *gd = (struct agg_data *)gc->proto_data; - if (invalid_uin(buddy->name)) - return; - gg_remove_notify(gd->sess, strtol(buddy->name, (char **)NULL, 10)); - agg_save_buddy_list(gc, NULL); -} - -static void agg_buddy_free (GaimBuddy *buddy) -{ - if (buddy->proto_data) { - g_free(buddy->proto_data); - buddy->proto_data = NULL; - } -} - -static void search_results(GaimConnection *gc, gchar *webdata) -{ - gchar **webdata_tbl; - gchar *buf; - char *ptr; - int i, j; - - if ((ptr = strstr(webdata, "query_results:")) == NULL || (ptr = strchr(ptr, '\n')) == NULL) { - gaim_debug(GAIM_DEBUG_MISC, "gg", "search_callback: pubdir result [%s]\n", webdata); - gaim_notify_error(gc, NULL, _("Couldn't get search results"), NULL); - return; - } - ptr++; - - buf = g_strconcat("", _("Gadu-Gadu Search Engine"), "
\n", NULL); - - webdata_tbl = g_strsplit(ptr, "\n", AGG_PUBDIR_MAX_ENTRIES); - - j = 0; - - /* Parse array */ - /* XXX - Make this use a GString */ - for (i = 0; webdata_tbl[i] != NULL; i++) { - gchar *p, *oldibuf; - static gchar *ibuf; - - g_strdelimit(webdata_tbl[i], "\t\n", ' '); +/* }}} */ - /* GG_PUBDIR_HOST service returns 7 lines of data per directory entry */ - if (i % 8 == 0) - j = 0; - - p = charset_convert(g_strstrip(webdata_tbl[i]), "CP1250", "UTF-8"); - - oldibuf = ibuf; - - switch (j) { - case 0: - ibuf = g_strconcat("---------------------------------
\n", NULL); - oldibuf = ibuf; - ibuf = g_strconcat(oldibuf, "", _("Active"), ": ", - (atoi(p) == 2) ? _("Yes") : _("No"), "
\n", NULL); - g_free(oldibuf); - break; - case 1: - ibuf = g_strconcat(oldibuf, "", _("UIN"), ": ", p, "
\n", NULL); - g_free(oldibuf); - break; - case 2: - ibuf = g_strconcat(oldibuf, "", _("First Name"), ": ", p, "
\n", NULL); - g_free(oldibuf); - break; - case 3: - ibuf = - g_strconcat(oldibuf, "", _("Last Name"), ": ", p, "
\n", NULL); - g_free(oldibuf); - break; - case 4: - ibuf = g_strconcat(oldibuf, "", _("Nick"), ": ", p, "
\n", NULL); - g_free(oldibuf); - break; - case 5: - /* Hack, invalid_uin does what we really want here but may change in future */ - if (invalid_uin(p)) - ibuf = - g_strconcat(oldibuf, "", _("Birth Year"), ":
\n", NULL); - else - ibuf = - g_strconcat(oldibuf, "", _("Birth Year"), ": ", p, "
\n", - NULL); - g_free(oldibuf); - break; - case 6: - if (atoi(p) == GG_GENDER_FEMALE) - ibuf = g_strconcat(oldibuf, "", _("Sex"), ": woman
\n", NULL); - else if (atoi(p) == GG_GENDER_MALE) - ibuf = g_strconcat(oldibuf, "", _("Sex"), ": man
\n", NULL); - else - ibuf = g_strconcat(oldibuf, "", _("Sex"), ":
\n", NULL); - g_free(oldibuf); - break; - case 7: - ibuf = g_strconcat(oldibuf, "", _("City"), ": ", p, "
\n", NULL); - g_free(oldibuf); - - /* We have all lines, so add them to buffer */ - { - gchar *oldbuf = buf; - buf = g_strconcat(oldbuf, ibuf, NULL); - g_free(oldbuf); - } - - g_free(ibuf); - break; - } - - g_free(p); - - j++; - } - - g_strfreev(webdata_tbl); - - gaim_notify_formatted(gc, NULL, _("Buddy Information"), NULL, - buf, NULL, NULL); - - g_free(buf); -} - -static void -change_pass(GaimPluginAction *action) +/** + * Load buddylist from server into the rooster. + * + * @param gc GaimConnection + * @param buddylist Pointer to the buddylist that will be loaded. + */ +/* static void ggp_buddylist_load(GaimConnection *gc, char *buddylist) {{{ */ +static void ggp_buddylist_load(GaimConnection *gc, char *buddylist) { - GaimConnection *gc = (GaimConnection *) action->context; - GaimAccount *account = gaim_connection_get_account(gc); - gaim_account_request_change_password(account); -} - -#if 0 -static void import_buddies_server_results(GaimConnection *gc, gchar *webdata) -{ - gchar *ptr; + GaimBuddy *buddy; + GaimGroup *group; gchar **users_tbl; int i; - if (strstr(webdata, "no_data:")) { - gaim_notify_error(gc, NULL, - _("There is no Buddy List stored on the " - "Gadu-Gadu server."), NULL); - return; - } - if ((ptr = strstr(webdata, "get_results:")) == NULL || (ptr = strchr(ptr, ':')) == NULL) { - gaim_debug(GAIM_DEBUG_MISC, "gg", "import_buddies_server_results: import buddies result [%s]\n", webdata); - gaim_notify_error(gc, NULL, - _("Couldn't Import Buddy List from Server"), NULL); - return; - } - ptr++; + users_tbl = g_strsplit(buddylist, "\r\n", 200); - users_tbl = g_strsplit(ptr, "\n", AGG_PUBDIR_MAX_ENTRIES); - - /* Parse array of Buddies List */ for (i = 0; users_tbl[i] != NULL; i++) { gchar **data_tbl; gchar *name, *show; - if (strlen(users_tbl[i])==0) { - gaim_debug(GAIM_DEBUG_MISC, "gg", - "import_buddies_server_results: users_tbl[i] is empty\n"); + if (strlen(users_tbl[i]) == 0) continue; - } - g_strdelimit(users_tbl[i], "\r\t\n\015", ' '); data_tbl = g_strsplit(users_tbl[i], ";", 8); show = charset_convert(data_tbl[3], "CP1250", "UTF-8"); name = data_tbl[6]; - if (invalid_uin(name)) { + gaim_debug_info("gg", "got buddy: name=%s show=%s\n", name, show); + + if (gaim_find_buddy(gaim_connection_get_account(gc), name)) { + g_free(show); + g_strfreev(data_tbl); continue; } - gaim_debug(GAIM_DEBUG_MISC, "gg", - "import_buddies_server_results: uin: %s\n", name); - if (!gaim_find_buddy(gc->account, name)) { - GaimBuddy *b; - GaimGroup *g; - /* Default group if none specified on server */ - gchar *group = g_strdup("Gadu-Gadu"); - if (strlen(data_tbl[5])) { - gchar **group_tbl = g_strsplit(data_tbl[5], ",", 2); - if (strlen(group_tbl[0])) { - g_free(group); - group = g_strdup(group_tbl[0]); - } - g_strfreev(group_tbl); + gchar *g = g_strdup("Gadu-Gadu"); + + if (strlen(data_tbl[5])) { + /* Hard limit to at most 50 groups */ + gchar **group_tbl = g_strsplit(data_tbl[5], ",", 50); + if (strlen(group_tbl[0]) > 0) { + g_free(g); + g = g_strdup(group_tbl[0]); } - /* Add Buddy to our userlist */ - if (!(g = gaim_find_group(group))) { - g = gaim_group_new(group); - gaim_blist_add_group(g, NULL); - } - b = gaim_buddy_new(gc->account, name, strlen(show) ? show : NULL); - gaim_blist_add_buddy(b, NULL, g, NULL); - g_free(group); + g_strfreev(group_tbl); } + + buddy = gaim_buddy_new(gaim_connection_get_account(gc), name, strlen(show) ? show : NULL); + if (!(group = gaim_find_group(g))) { + group = gaim_group_new(g); + gaim_blist_add_group(group, NULL); + } + + gaim_blist_add_buddy(buddy, NULL, group, NULL); + g_free(g); + g_free(show); g_strfreev(data_tbl); } g_strfreev(users_tbl); -} + + ggp_buddylist_send(gc); -static void export_buddies_server_results(GaimConnection *gc, gchar *webdata) +} +/* }}} */ + +/* + */ +/* static void ggp_generic_status_handler(GaimConnection *gc, uin_t uin, int status, const char *descr) {{{ */ +static void ggp_generic_status_handler(GaimConnection *gc, uin_t uin, int status, const char *descr) { - if (strstr(webdata, "put_success:")) { - gaim_notify_info(gc, NULL, - _("Buddy List successfully transferred to " - "Gadu-Gadu server"), NULL); - return; + gchar *from; + const char *st; + gchar *msg; + + from = g_strdup_printf("%ld", (unsigned long int)uin); + switch (status) { + case GG_STATUS_NOT_AVAIL: + case GG_STATUS_NOT_AVAIL_DESCR: + st = "offline"; + break; + case GG_STATUS_AVAIL: + case GG_STATUS_AVAIL_DESCR: + st = "online"; + break; + case GG_STATUS_BUSY: + case GG_STATUS_BUSY_DESCR: + st = "away"; + break; + case GG_STATUS_BLOCKED: + /* user is blocking us.... */ + st = "blocked"; + break; + default: + st = "online"; + gaim_debug_info("gg", "GG_EVENT_NOTIFY: Unknown status: %d\n", status); + break; } - gaim_debug(GAIM_DEBUG_MISC, "gg", - "export_buddies_server_results: webdata [%s]\n", webdata); - gaim_notify_error(gc, NULL, - _("Couldn't transfer Buddy List to Gadu-Gadu server"), - NULL); + gaim_debug_info("gg", "st = %s\n", st); + msg = charset_convert(descr, "CP1250", "UTF-8"); + gaim_prpl_got_user_status(gaim_connection_get_account(gc), from, st, "message", msg, NULL); + g_free(from); + g_free(msg); } +/* }}} */ -static void delete_buddies_server_results(GaimConnection *gc, gchar *webdata) +/* + */ +/* static void ggp_pubdir_start_search(GaimConnection *gc, GGPSearchForm *form) {{{ */ +static void ggp_pubdir_start_search(GaimConnection *gc, GGPSearchForm *form) { - if (strstr(webdata, "put_success:")) { - gaim_notify_info(gc, NULL, - _("Buddy List successfully deleted from " - "Gadu-Gadu server"), NULL); + GGPInfo *info = gc->proto_data; + gg_pubdir50_t req; + + gaim_debug_info("gg", "It's time to perform a search...\n"); + + if ((req = gg_pubdir50_new(GG_PUBDIR50_SEARCH)) == NULL) { + gaim_debug_error("gg", "ggp_bmenu_show_details: Unable to create req variable.\n"); return; } - gaim_debug(GAIM_DEBUG_MISC, "gg", - "delete_buddies_server_results: webdata [%s]\n", webdata); - gaim_notify_error(gc, NULL, - _("Couldn't delete Buddy List from Gadu-Gadu server"), - NULL); -} -#endif + if (form->uin != NULL) { + gaim_debug_info("gg", " uin: %s\n", form->uin); + gg_pubdir50_add(req, GG_PUBDIR50_UIN, form->uin); + } else { + if (form->lastname != NULL) { + gaim_debug_info("gg", " lastname: %s\n", form->lastname); + gg_pubdir50_add(req, GG_PUBDIR50_LASTNAME, form->lastname); + } + + if (form->firstname != NULL) { + gaim_debug_info("gg", " firstname: %s\n", form->firstname); + gg_pubdir50_add(req, GG_PUBDIR50_FIRSTNAME, form->firstname); + } + + if (form->nickname != NULL) { + gaim_debug_info("gg", " nickname: %s\n", form->nickname); + gg_pubdir50_add(req, GG_PUBDIR50_NICKNAME, form->nickname); + } -static void password_change_server_results(GaimConnection *gc, gchar *webdata) -{ - if (strstr(webdata, "reg_success:")) { - gaim_notify_info(gc, NULL, - _("Password changed successfully"), NULL); + if (form->city != NULL) { + gaim_debug_info("gg", " city: %s\n", form->city); + gg_pubdir50_add(req, GG_PUBDIR50_CITY, form->city); + } + + if (form->birthyear != NULL) { + gaim_debug_info("gg", " birthyear: %s\n", form->birthyear); + gg_pubdir50_add(req, GG_PUBDIR50_BIRTHYEAR, form->birthyear); + } + + if (form->gender != NULL) { + gaim_debug_info("gg", " gender: %s\n", form->gender); + gg_pubdir50_add(req, GG_PUBDIR50_GENDER, form->gender); + } + + if (form->active != NULL) { + gaim_debug_info("gg", " active: %s\n", form->active); + gg_pubdir50_add(req, GG_PUBDIR50_ACTIVE, form->active); + } + } + + gaim_debug_info("gg", "offset: %s\n", form->offset); + gg_pubdir50_add(req, GG_PUBDIR50_START, g_strdup(form->offset)); + + if (gg_pubdir50(info->session, req) == 0) { + gaim_debug_warning("gg", "ggp_bmenu_show_details: Search failed.\n"); return; } - gaim_debug(GAIM_DEBUG_MISC, "gg", - "password_change_server_results: webdata [%s]\n", webdata); - gaim_notify_error(gc, NULL, - _("Password couldn't be changed"), NULL); + gg_pubdir50_free(req); +} +/* }}} */ + +/* + * Return converted to the UTF-8 value of the specified field. + * + * @param res Public directory look-up result + * @param num Id of the record + * @param fileld Name of the field + * + * @return UTF-8 encoded value of the field + */ +/* static char *ggp_get_pubdir_info(gg_pubdir50_t res, int num, const char *field) {{{ */ +static char *ggp_get_pubdir_info(gg_pubdir50_t res, int num, const char *field) +{ + char *tmp; + + tmp = charset_convert(gg_pubdir50_get(res, num, field), "CP1250", "UTF-8"); + + return (tmp == NULL) ? g_strdup("") : tmp; +} +/* }}} */ + +static void ggp_callback_show_next(GaimConnection *gc, GList *row) +{ + GGPInfo *info = gc->proto_data; + + g_free(info->search_form->offset); + info->search_form->offset = g_strdup(info->search_form->last_uin); + gaim_debug_info("gg", "london calling... offset = %s\n", info->search_form->offset); + ggp_pubdir_start_search(gc, info->search_form); } -static void http_results(gpointer data, gint source, GaimInputCondition cond) +static void ggp_callback_add_buddy(GaimConnection *gc, GList *row) { - struct agg_http *hdata = data; - GaimConnection *gc = hdata->gc; - char *webdata; - int len; - char read_data; + gaim_blist_request_add_buddy(gaim_connection_get_account(gc), + g_list_nth_data(row, 0), NULL, NULL); +} - gaim_debug(GAIM_DEBUG_INFO, "gg", "http_results: begin\n"); +/* + */ +/* static void ggp_callback_recv(gpointer _gc, gint fd, GaimInputCondition cond) {{{ */ +static void ggp_callback_recv(gpointer _gc, gint fd, GaimInputCondition cond) +{ + GaimConnection *gc = _gc; + GGPInfo *info = gc->proto_data; + struct gg_event *ev; + int i; - if (!g_list_find(gaim_connections_get_all(), gc)) { - gaim_debug(GAIM_DEBUG_ERROR, "gg", - "search_callback: g_slist_find error\n"); - gaim_input_remove(hdata->inpa); - g_free(hdata); - close(source); + if (!(ev = gg_watch_fd(info->session))) { + gaim_debug_error("gg", "ggp_callback_recv: gg_watch_fd failed -- CRITICAL!\n"); + gaim_connection_error(gc, _("Unable to read socket")); return; } - webdata = NULL; - len = 0; + switch (ev->type) { + case GG_EVENT_NONE: + /* Nothing happened. */ + break; + case GG_EVENT_MSG: + { + gchar *from; + gchar *msg; + gchar *tmp; + + from = g_strdup_printf("%lu", (unsigned long int)ev->event.msg.sender); - while (read(source, &read_data, 1) > 0 || errno == EWOULDBLOCK) { - if (errno == EWOULDBLOCK) { - errno = 0; - continue; - } + msg = charset_convert((const char *)ev->event.msg.message, + "CP1250", "UTF-8"); + gaim_str_strip_cr(msg); + tmp = g_markup_escape_text(msg, -1); + gaim_debug_info("gg", "msg form (%s): %s (class = %d)\n", from, tmp, ev->event.msg.msgclass); + serv_got_im(gc, from, tmp, 0, ev->event.msg.time); + g_free(msg); + g_free(tmp); + g_free(from); + } + break; + case GG_EVENT_ACK: + gaim_debug_info("gg", "message sent to: %ld, delivery status=%d, seq=%d\n", + ev->event.ack.recipient, ev->event.ack.status, ev->event.ack.seq); + break; + case GG_EVENT_NOTIFY: + case GG_EVENT_NOTIFY_DESCR: + { + struct gg_notify_reply *n; + char *descr; - if (!read_data) - continue; + gaim_debug_info("gg", "notify_pre: (%d) status: %d\n", + ev->event.notify->uin, + ev->event.notify->status); + + n = (ev->type == GG_EVENT_NOTIFY) ? ev->event.notify + : ev->event.notify_descr.notify; - len++; - webdata = g_realloc(webdata, len); - webdata[len - 1] = read_data; - } + for (; n->uin; n++) { + descr = (ev->type == GG_EVENT_NOTIFY) ? NULL + : ev->event.notify_descr.descr; + gaim_debug_info("gg", "notify: (%d) status: %d; descr: %s\n", + n->uin, n->status, descr); + + ggp_generic_status_handler(gc, + n->uin, n->status, descr); + } + } + break; + case GG_EVENT_NOTIFY60: + gaim_debug_info("gg", "notify60_pre: (%d) status=%d; version=%d; descr=%s\n", + ev->event.notify60->uin, ev->event.notify60->status, + ev->event.notify60->version, ev->event.notify60->descr); + + for (i = 0; ev->event.notify60[i].uin; i++) { + gaim_debug_info("gg", "notify60: (%d) status=%d; version=%d; descr=%s\n", + ev->event.notify60[i].uin, ev->event.notify60[i].status, + ev->event.notify60[i].version, ev->event.notify60[i].descr); - webdata = g_realloc(webdata, len + 1); - webdata[len] = 0; + ggp_generic_status_handler(gc, + ev->event.notify60[i].uin, + ev->event.notify60[i].status, + ev->event.notify60[i].descr); + } + break; + case GG_EVENT_STATUS: + gaim_debug_info("gg", "status: (%d) status=%d; descr=%s\n", + ev->event.status.uin, ev->event.status.status, + ev->event.status.descr); - gaim_input_remove(hdata->inpa); - close(source); - - gaim_debug(GAIM_DEBUG_MISC, "gg", - "http_results: type %d, webdata [%s]\n", hdata->type, webdata); + ggp_generic_status_handler(gc, + ev->event.status.uin, + ev->event.status.status, + ev->event.status.descr); + break; + case GG_EVENT_STATUS60: + gaim_debug_info("gg", "status60: (%d) status=%d; version=%d; descr=%s\n", + ev->event.status60.uin, + ev->event.status60.status, + ev->event.status60.version, + ev->event.status60.descr); - switch (hdata->type) { - case AGG_HTTP_SEARCH: - search_results(gc, webdata); - break; -#if 0 - case AGG_HTTP_USERLIST_IMPORT: - import_buddies_server_results(gc, webdata); - break; - case AGG_HTTP_USERLIST_EXPORT: - export_buddies_server_results(gc, webdata); - break; - case AGG_HTTP_USERLIST_DELETE: - delete_buddies_server_results(gc, webdata); - break; -#endif - case AGG_HTTP_PASSWORD_CHANGE: - password_change_server_results(gc, webdata); - break; - case AGG_HTTP_NONE: - default: - gaim_debug(GAIM_DEBUG_ERROR, "gg", - "http_results: unsupported type %d\n", hdata->type); - break; - } + ggp_generic_status_handler(gc, + ev->event.status60.uin, + ev->event.status60.status, + ev->event.status60.descr); + break; + case GG_EVENT_USERLIST: + if (ev->event.userlist.type == GG_USERLIST_GET_REPLY) { + gaim_debug_info("gg", "GG_USERLIST_GET_REPLY\n"); + if (ev->event.userlist.reply != NULL) { + ggp_buddylist_load(gc, ev->event.userlist.reply); + } + break; + } else { + gaim_debug_info("gg", "GG_USERLIST_PUT_REPLY. Userlist stored on the server.\n"); + } + break; + case GG_EVENT_PUBDIR50_SEARCH_REPLY: + { + GaimNotifySearchResults *results; + GaimNotifySearchColumn *column; + gg_pubdir50_t req = ev->event.pubdir50; + int res_count = 0; + int start; + int i; + + res_count = gg_pubdir50_count(req); + if (res_count < 1) { + gaim_debug_info("gg", "GG_EVENT_PUBDIR50_SEARCH_REPLY: Nothing found\n"); + return; + } + res_count = (res_count > 20) ? 20 : res_count; + + results = gaim_notify_searchresults_new(); + + column = gaim_notify_searchresults_column_new("UIN"); + gaim_notify_searchresults_column_add(results, column); + + column = gaim_notify_searchresults_column_new("First name"); + gaim_notify_searchresults_column_add(results, column); + + column = gaim_notify_searchresults_column_new("Nick name"); + gaim_notify_searchresults_column_add(results, column); - g_free(webdata); - g_free(hdata); -} + column = gaim_notify_searchresults_column_new("City"); + gaim_notify_searchresults_column_add(results, column); -static void http_req_callback(gpointer data, gint source, GaimInputCondition cond) -{ - struct agg_http *hdata = data; - GaimConnection *gc = hdata->gc; - gchar *request = hdata->request; - gchar *buf; + column = gaim_notify_searchresults_column_new("Birth year"); + gaim_notify_searchresults_column_add(results, column); - gaim_debug(GAIM_DEBUG_INFO, "gg", "http_req_callback: begin\n"); + gaim_debug_info("gg", "Going with %d entries\n", res_count); + + start = (int)ggp_str_to_uin(gg_pubdir50_get(req, 0, GG_PUBDIR50_START)); + gaim_debug_info("gg", "start = %d\n", start); - if (!g_list_find(gaim_connections_get_all(), gc)) { - gaim_debug(GAIM_DEBUG_ERROR, "gg", - "http_req_callback: g_slist_find error\n"); - g_free(request); - g_free(hdata); - close(source); - return; - } + for (i = 0; i < res_count; i++) { + GList *row = NULL; + char *birth = ggp_get_pubdir_info(req, i, GG_PUBDIR50_BIRTHYEAR); + + /* TODO: Status will be displayed as an icon. */ + /* row = g_list_append(row, ggp_get_pubdir_info(req, i, GG_PUBDIR50_STATUS)); */ + row = g_list_append(row, ggp_get_pubdir_info(req, i, GG_PUBDIR50_UIN)); + row = g_list_append(row, ggp_get_pubdir_info(req, i, GG_PUBDIR50_FIRSTNAME)); + row = g_list_append(row, ggp_get_pubdir_info(req, i, GG_PUBDIR50_NICKNAME)); + row = g_list_append(row, ggp_get_pubdir_info(req, i, GG_PUBDIR50_CITY)); + row = g_list_append(row, (birth && strncmp(birth, "0", 1)) ? birth : g_strdup("-")); + gaim_notify_searchresults_row_add(results, row); + if (i == res_count - 1) { + g_free(info->search_form->last_uin); + info->search_form->last_uin = ggp_get_pubdir_info(req, i, GG_PUBDIR50_UIN); + } + } - if (source == 0) { - g_free(request); - g_free(hdata); - return; + gaim_notify_searchresults_button_add(results, GAIM_NOTIFY_BUTTON_CONTINUE, ggp_callback_show_next); + gaim_notify_searchresults_button_add(results, GAIM_NOTIFY_BUTTON_ADD_BUDDY, ggp_callback_add_buddy); + if (info->searchresults_window == NULL) { + void *h = gaim_notify_searchresults(gc, _("Gadu-Gadu Public Directory"), + _("Search results"), NULL, results, NULL, NULL); + info->searchresults_window = h; + } else { + gaim_notify_searchresults_new_rows(gc, results, info->searchresults_window, NULL); + } + } + break; + default: + gaim_debug_error("gg", "unsupported event type=%d\n", ev->type); + break; } - gaim_debug(GAIM_DEBUG_MISC, "gg", - "http_req_callback: http request [%s]\n", request); - - buf = g_strdup_printf("POST %s HTTP/1.0\r\n" - "Host: %s\r\n" - "Content-Type: application/x-www-form-urlencoded\r\n" - "User-Agent: " GG_HTTP_USERAGENT "\r\n" - "Content-Length: %d\r\n" - "Pragma: no-cache\r\n" "\r\n" "%s\r\n", - hdata->form, hdata->host, (int)strlen(request), request); - - g_free(request); + gg_free_event(ev); +} +/* }}} */ - if (write(source, buf, strlen(buf)) < strlen(buf)) { - g_free(buf); - g_free(hdata); - close(source); - gaim_notify_error(gc, NULL, - _("Error communicating with Gadu-Gadu server"), - _("Gaim was unable to complete your request due " - "to a problem communicating with the Gadu-Gadu " - "HTTP server. Please try again later.")); - return; - } - - g_free(buf); +/** + * Set offline status for all buddies. + * + * @param gc Connection handler + */ +/* static void ggp_buddylist_offline(GaimConnection *gc) {{{ */ +static void ggp_buddylist_offline(GaimConnection *gc) +{ + GaimBuddyList *blist; + GaimBlistNode *gnode, *cnode, *bnode; + GaimBuddy *buddy; - hdata->inpa = gaim_input_add(source, GAIM_INPUT_READ, http_results, hdata); -} - -#if 0 -static void import_buddies_server(GaimConnection *gc) -{ - struct agg_http *hi = g_new0(struct agg_http, 1); - gchar *u = gg_urlencode(gaim_account_get_username(gc->account)); - gchar *p = gg_urlencode(gaim_connection_get_password(gc)); + if ((blist = gaim_get_blist()) != NULL) + { + for (gnode = blist->root; gnode != NULL; gnode = gnode->next) + { + if (!GAIM_BLIST_NODE_IS_GROUP(gnode)) + continue; + for (cnode = gnode->child; cnode != NULL; cnode = cnode->next) + { + if (!GAIM_BLIST_NODE_IS_CONTACT(cnode)) + continue; + for (bnode = cnode->child; bnode != NULL; bnode = bnode->next) + { + if (!GAIM_BLIST_NODE_IS_BUDDY(bnode)) + continue; - hi->gc = gc; - hi->type = AGG_HTTP_USERLIST_IMPORT; - hi->form = AGG_PUBDIR_USERLIST_IMPORT_FORM; - hi->host = GG_PUBDIR_HOST; - hi->request = g_strdup_printf("FmNum=%s&Pass=%s", u, p); + buddy = (GaimBuddy *)bnode; - g_free(u); - g_free(p); + if (buddy->account != gc->account) + continue; - if (gaim_proxy_connect(gc->account, GG_PUBDIR_HOST, GG_PUBDIR_PORT, http_req_callback, hi) < 0) { - gaim_notify_error(gc, NULL, - _("Unable to import Gadu-Gadu buddy list"), - _("Gaim was unable to connect to the Gadu-Gadu " - "buddy list server. Please try again later.")); - g_free(hi->request); - g_free(hi); - return; + gaim_prpl_got_user_status( + gaim_connection_get_account(gc), + buddy->name, "offline", NULL); + gaim_debug_info("gg", "ggp_buddylist_offline: gone: %s\n", buddy->name); + } + } + } } } - -static void export_buddies_server(GaimConnection *gc) -{ - struct agg_http *he = g_new0(struct agg_http, 1); - gchar *ptr; - gchar *u = gg_urlencode(gaim_account_get_username(gc->account)); - gchar *p = gg_urlencode(gaim_connection_get_password(gc)); +/* }}} */ +/** + * Get all the buddies in the current account. + * + * @param account Current account. + * + * @return List of buddies. + */ +/* static char *ggp_buddylist_dump(GaimAccount *account) {{{ */ +static char *ggp_buddylist_dump(GaimAccount *account) +{ + GaimBuddyList *blist; GaimBlistNode *gnode, *cnode, *bnode; - - he->gc = gc; - he->type = AGG_HTTP_USERLIST_EXPORT; - he->form = AGG_PUBDIR_USERLIST_EXPORT_FORM; - he->host = GG_PUBDIR_HOST; - he->request = g_strdup_printf("FmNum=%s&Pass=%s&Contacts=", u, p); + GaimGroup *group; + GaimBuddy *buddy; - g_free(u); - g_free(p); + char *buddylist = g_strdup(""); + char *ptr; - for(gnode = gaim_get_blist()->root; gnode; gnode = gnode->next) { - GaimGroup *g = (GaimGroup *)gnode; - int num_added=0; - if(!GAIM_BLIST_NODE_IS_GROUP(gnode)) + if ((blist = gaim_get_blist()) == NULL) + return NULL; + + for (gnode = blist->root; gnode != NULL; gnode = gnode->next) { + if (!GAIM_BLIST_NODE_IS_GROUP(gnode)) continue; - for(cnode = gnode->child; cnode; cnode = cnode->next) { - if(!GAIM_BLIST_NODE_IS_CONTACT(cnode)) + + group = (GaimGroup *)gnode; + + for (cnode = gnode->child; cnode != NULL; cnode = cnode->next) { + if (!GAIM_BLIST_NODE_IS_CONTACT(cnode)) continue; - for(bnode = cnode->child; bnode; bnode = bnode->next) { - GaimBuddy *b = (GaimBuddy *)bnode; - if(!GAIM_BLIST_NODE_IS_BUDDY(bnode)) + for (bnode = cnode->child; bnode != NULL; bnode = bnode->next) { + if (!GAIM_BLIST_NODE_IS_BUDDY(bnode)) continue; - if(b->account == gc->account) { - gchar *newdata; - /* GG Number */ - gchar *name = gg_urlencode(b->name); - /* GG Pseudo */ - gchar *show = gg_urlencode(b->alias ? b->alias : b->name); - /* Group Name */ - gchar *gname = gg_urlencode(g->name); - - ptr = he->request; - newdata = g_strdup_printf("%s;%s;%s;%s;%s;%s;%s", - show, show, show, show, "", gname, name); + buddy = (GaimBuddy *)bnode; + if (buddy->account != account) + continue; - if(num_added > 0) - he->request = g_strconcat(ptr, "%0d%0a", newdata, NULL); - else - he->request = g_strconcat(ptr, newdata, NULL); - - num_added++; + gchar *newdata; + /* GG Number */ + gchar *name = buddy->name; + /* GG Pseudo */ + gchar *show = buddy->alias ? buddy->alias : buddy->name; + /* Group Name */ + gchar *gname = group->name; - g_free(newdata); - g_free(ptr); + newdata = g_strdup_printf("%s;%s;%s;%s;%s;%s;%s;%s%s\r\n", + show, show, show, show, "", gname, name, "", ""); - g_free(gname); - g_free(show); - g_free(name); - } + ptr = buddylist; + buddylist = g_strconcat(ptr, newdata, NULL); + + g_free(newdata); + g_free(ptr); } } } - if (gaim_proxy_connect(gc->account, GG_PUBDIR_HOST, GG_PUBDIR_PORT, http_req_callback, he) < 0) { - gaim_notify_error(gc, NULL, - _("Couldn't export buddy list"), - _("Gaim was unable to connect to the buddy " - "list server. Please try again later.")); - g_free(he->request); - g_free(he); + return buddylist; +} +/* }}} */ + +/** + * Request buddylist from the server. + * Buddylist is received in the ggp_callback_recv(). + * + * @param Current action handler. + */ +/* static void ggp_action_buddylist_get(GaimPluginAction *action) {{{ */ +static void ggp_action_buddylist_get(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *)action->context; + GGPInfo *info = gc->proto_data; + + gaim_debug_info("gg", "Downloading...\n"); + + gg_userlist_request(info->session, GG_USERLIST_GET, NULL); +} +/* }}} */ + +/** + * Upload the buddylist to the server. + * + * @param action Current action handler. + */ +/* static void ggp_action_buddylist_put(GaimPluginAction *action) {{{ */ +static void ggp_action_buddylist_put(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *)action->context; + GGPInfo *info = gc->proto_data; + + char *buddylist = ggp_buddylist_dump(gaim_connection_get_account(gc)); + + gaim_debug_info("gg", "Uploading...\n"); + + if (buddylist == NULL) + return; + + gg_userlist_request(info->session, GG_USERLIST_PUT, buddylist); + g_free(buddylist); +} +/* }}} */ + +/** + * Delete buddylist from the server. + * + * @param action Current action handler. + */ +/* static void ggp_action_buddylist_delete(GaimPluginAction *action) {{{ */ +static void ggp_action_buddylist_delete(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *)action->context; + GGPInfo *info = gc->proto_data; + + gaim_debug_info("gg", "Deleting...\n"); + + gg_userlist_request(info->session, GG_USERLIST_PUT, NULL); +} +/* }}} */ + +/* + */ +/* static void ggp_callback_buddylist_save_ok(GaimConnection *gc, gchar *file) {{{ */ +static void ggp_callback_buddylist_save_ok(GaimConnection *gc, gchar *file) +{ + GaimAccount *account = gaim_connection_get_account(gc); + + FILE *fh; + char *buddylist = ggp_buddylist_dump(account); + gchar *msg; + + gaim_debug_info("gg", "Saving...\n"); + gaim_debug_info("gg", "file = %s\n", file); + + if (buddylist == NULL) { + gaim_notify_info(account, _("Save Buddylist..."), + _("Your buddylist is empty, nothing was written to the file."), + NULL); return; } -} -static void delete_buddies_server(GaimConnection *gc) -{ - struct agg_http *he = g_new0(struct agg_http, 1); - gchar *u = gg_urlencode(gaim_account_get_username(gc->account)); - gchar *p = gg_urlencode(gaim_connection_get_password(gc)); + if ((fh = g_fopen(file, "wb")) == NULL) { + msg = g_strconcat(_("Couldn't open file"), ": ", file, "\n", NULL); + gaim_debug_error("gg", "Could not open file: %s\n", file); + gaim_notify_error(account, _("Couldn't open file"), msg, NULL); + g_free(msg); + g_free(file); + return; + } + + fwrite(buddylist, sizeof(char), g_utf8_strlen(buddylist, -1), fh); + fclose(fh); + g_free(buddylist); + + gaim_notify_info(account, _("Save Buddylist..."), + _("Buddylist saved successfully!"), NULL); +} +/* }}} */ - he->gc = gc; - he->type = AGG_HTTP_USERLIST_DELETE; - he->form = AGG_PUBDIR_USERLIST_EXPORT_FORM; - he->host = GG_PUBDIR_HOST; - he->request = g_strdup_printf("FmNum=%s&Pass=%s&Delete=1", u, p); +/* + */ +/* static void ggp_callback_buddylist_load_ok(GaimConnection *gc, gchar *file) {{{ */ +static void ggp_callback_buddylist_load_ok(GaimConnection *gc, gchar *file) +{ + GaimAccount *account = gaim_connection_get_account(gc); + char *buddylist, *tmp, *ptr; + FILE *fh; + gchar *msg; - if (gaim_proxy_connect(gc->account, GG_PUBDIR_HOST, GG_PUBDIR_PORT, http_req_callback, he) < 0) { - gaim_notify_error(gc, NULL, - _("Unable to delete Gadu-Gadu buddy list"), - _("Gaim was unable to connect to the buddy " - "list server. Please try again later.")); - g_free(he->request); - g_free(he); + buddylist = g_strdup(""); + tmp = g_new0(gchar, 50); + + gaim_debug_info("gg", "file_name = %s\n", file); + + if ((fh = g_fopen(file, "rb")) == NULL) { + msg = g_strconcat(_("Couldn't open file"), ": ", file, "\n", NULL); + gaim_debug_error("gg", "Could not open file: %s\n", file); + gaim_notify_error(account, _("Could't open file"), msg, NULL); + g_free(msg); + g_free(file); return; } + + while (fread(tmp, sizeof(gchar), 49, fh) == 49) { + tmp[49] = '\0'; + /* gaim_debug_info("gg", "read: %s\n", tmp); */ + ptr = g_strconcat(buddylist, tmp, NULL); + memset(tmp, '\0', 50); + g_free(buddylist); + buddylist = ptr; + } + fclose(fh); + g_free(tmp); + + ggp_buddylist_load(gc, buddylist); + g_free(buddylist); + + gaim_notify_info(account, + _("Load Buddylist..."), + _("Buddylist loaded successfully!"), NULL); } -#endif +/* }}} */ + +/* + */ +/* static void ggp_action_buddylist_save(GaimPluginAction *action) {{{ */ +static void ggp_action_buddylist_save(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *)action->context; + + gaim_request_file(action, _("Save buddylist..."), NULL, TRUE, + G_CALLBACK(ggp_callback_buddylist_save_ok), NULL, gc); +} +/* }}} */ -#if 0 -static void agg_dir_search(GaimConnection *gc, const char *first, const char *middle, - const char *last, const char *maiden, const char *city, const char *state, - const char *country, const char *email) +/* + */ +/* static void ggp_action_buddylist_load(GaimPluginAction *action) {{{ */ +static void ggp_action_buddylist_load(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *)action->context; + + gaim_request_file(action, "Load buddylist from file...", NULL, FALSE, + G_CALLBACK(ggp_callback_buddylist_load_ok), NULL, gc); +} +/* }}} */ + +/* + */ +/* static void ggp_callback_change_passwd_ok(GaimConnection *gc, GaimRequestFields *fields) {{{ */ +static void ggp_callback_change_passwd_ok(GaimConnection *gc, GaimRequestFields *fields) { - struct agg_http *srch = g_new0(struct agg_http, 1); - srch->gc = gc; - srch->type = AGG_HTTP_SEARCH; - srch->form = AGG_PUBDIR_SEARCH_FORM; - srch->host = GG_PUBDIR_HOST; + GaimAccount *account; + GGPInfo *info = gc->proto_data; + struct gg_http *h; + gchar *cur, *p1, *p2, *t; + + cur = charset_convert(gaim_request_fields_get_string(fields, "password_cur"), + "UTF-8", "CP1250"); + p1 = charset_convert(gaim_request_fields_get_string(fields, "password1"), + "UTF-8", "CP1250"); + p2 = charset_convert(gaim_request_fields_get_string(fields, "password2"), + "UTF-8", "CP1250"); + t = charset_convert(gaim_request_fields_get_string(fields, "token"), + "UTF-8", "CP1250"); + + account = gaim_connection_get_account(gc); + + if (cur == NULL || p1 == NULL || p2 == NULL || t == NULL || + *cur == '\0' || *p1 == '\0' || *p2 == '\0' || *t == '\0') { + gaim_notify_error(account, NULL, _("Fill in the fields."), NULL); + goto exit_err; + } + + if (g_utf8_collate(p1, p2) != 0) { + gaim_notify_error(account, NULL, _("New passwords do not match."), NULL); + goto exit_err; + } - if (email && strlen(email)) { - gchar *eemail = gg_urlencode(email); - srch->request = g_strdup_printf("Mode=1&Email=%s", eemail); - g_free(eemail); - } else { - gchar *new_first = charset_convert(first, "UTF-8", "CP1250"); - gchar *new_last = charset_convert(last, "UTF-8", "CP1250"); - gchar *new_city = charset_convert(city, "UTF-8", "CP1250"); + if (g_utf8_collate(cur, gaim_account_get_password(account)) != 0) { + gaim_notify_error(account, NULL, + _("Your current password is different from the one that you specified."), + NULL); + goto exit_err; + } + + gaim_debug_info("gg", "change_passwd: old=%s; p1=%s; token=%s\n", + cur, p1, info->chpasswd_token->token_id); + + /* XXX: this e-mail should be a pref... */ + h = gg_change_passwd4(ggp_get_uin(account), + "user@example.net", gaim_account_get_password(account), + p1, info->chpasswd_token->token_id, t, 0); - gchar *enew_first = gg_urlencode(new_first); - gchar *enew_last = gg_urlencode(new_last); - gchar *enew_city = gg_urlencode(new_city); + if (h == NULL) { + gaim_notify_error(account, NULL, + _("Unable to change password. Error occured.\n"), + NULL); + goto exit_err; + } + + gaim_account_set_password(account, p1); + + gg_change_passwd_free(h); + + gaim_notify_info(account, _("Change password for the Gadu-Gadu account"), + _("Password was changed successfully!"), NULL); + +exit_err: + g_free(cur); + g_free(p1); + g_free(p2); + g_free(t); + g_free(info->chpasswd_token->token_id); + g_free(info->chpasswd_token); +} +/* }}} */ - g_free(new_first); - g_free(new_last); - g_free(new_city); +/* + */ +/* static void ggp_callback_register_account_ok(GaimConnection *gc, GaimRequestFields *fields) {{{ */ +static void ggp_callback_register_account_ok(GaimConnection *gc, GaimRequestFields *fields) +{ + GaimAccount *account; + GGPInfo *info = gc->proto_data; + struct gg_http *h = NULL; + struct gg_pubdir *s; + uin_t uin; + gchar *email, *p1, *p2, *t; - /* For active only add &ActiveOnly= */ - srch->request = g_strdup_printf("Mode=0&FirstName=%s&LastName=%s&Gender=%d" - "&NickName=%s&City=%s&MinBirth=%d&MaxBirth=%d", - enew_first, enew_last, AGG_GENDER_NONE, - "", enew_city, 0, 0); + email = charset_convert(gaim_request_fields_get_string(fields, "email"), + "UTF-8", "CP1250"); + p1 = charset_convert(gaim_request_fields_get_string(fields, "password1"), + "UTF-8", "CP1250"); + p2 = charset_convert(gaim_request_fields_get_string(fields, "password2"), + "UTF-8", "CP1250"); + t = charset_convert(gaim_request_fields_get_string(fields, "token"), + "UTF-8", "CP1250"); + + account = gaim_connection_get_account(gc); - g_free(enew_first); - g_free(enew_last); - g_free(enew_city); + if (email == NULL || p1 == NULL || p2 == NULL || t == NULL || + *email == '\0' || *p1 == '\0' || *p2 == '\0' || *t == '\0') { + gaim_notify_error(account, NULL, _("Fill in the fields."), NULL); + goto exit_err; + } + + if (g_utf8_collate(p1, p2) != 0) { + gaim_notify_error(account, NULL, _("Passwords do not match."), NULL); + goto exit_err; + } + + h = gg_register3(email, p1, info->register_token->token_id, t, 0); + if (h == NULL || !(s = h->data) || !s->success) { + gaim_notify_error(account, NULL, + _("Unable to register new account. Error occured.\n"), + NULL); + goto exit_err; } - if (gaim_proxy_connect(gc->account, GG_PUBDIR_HOST, GG_PUBDIR_PORT, http_req_callback, srch) < 0) { - gaim_notify_error(gc, NULL, - _("Unable to access directory"), - _("Gaim was unable to search the Directory " - "because it was unable to connect to the " - "directory server. Please try again later.")); - g_free(srch->request); - g_free(srch); + uin = s->uin; + gaim_debug_info("gg", "registered uin: %d\n", uin); + + gaim_notify_info(NULL, _("New Gadu-Gadu Account Registered"), + _("Registration completed successfully!"), NULL); + +exit_err: + gg_register_free(h); + g_free(email); + g_free(p1); + g_free(p2); + g_free(t); + g_free(info->register_token->token_id); + g_free(info->register_token); +} +/* }}} */ + +/* + */ +/* static void ggp_callback_find_buddies(GaimConnection *gc, GaimRequestFields *fields) {{{ */ +static void ggp_callback_find_buddies(GaimConnection *gc, GaimRequestFields *fields) +{ + GGPInfo *info = gc->proto_data; + GGPSearchForm *form; + + form = ggp_searchform_new(); + /* + * TODO: Fail if we have already a form attached. Only one search + * at a time will be allowed for now. + */ + info->search_form = form; + + form->lastname = charset_convert(gaim_request_fields_get_string(fields, "lastname"), + "UTF-8", "CP1250"); + form->firstname = charset_convert(gaim_request_fields_get_string(fields, "firstname"), + "UTF-8", "CP1250"); + form->nickname = charset_convert(gaim_request_fields_get_string(fields, "nickname"), + "UTF-8", "CP1250"); + form->city = charset_convert(gaim_request_fields_get_string(fields, "city"), + "UTF-8", "CP1250"); + form->birthyear = charset_convert(gaim_request_fields_get_string(fields, "year"), + "UTF-8", "CP1250"); + + switch (gaim_request_fields_get_choice(fields, "gender")) { + case 1: + form->gender = g_strdup(GG_PUBDIR50_GENDER_MALE); + break; + case 2: + form->gender = g_strdup(GG_PUBDIR50_GENDER_FEMALE); + break; + default: + form->gender = NULL; + break; + } + + form->active = gaim_request_fields_get_bool(fields, "active") + ? g_strdup(GG_PUBDIR50_ACTIVE_TRUE) : NULL; + + form->offset = g_strdup("0"); + + ggp_pubdir_start_search(gc, form); +} +/* }}} */ + +/* + */ +/* static void ggp_find_buddies(GaimPluginAction *action) {{{ */ +static void ggp_find_buddies(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *)action->context; + + GaimRequestFields *fields; + GaimRequestFieldGroup *group; + GaimRequestField *field; + + fields = gaim_request_fields_new(); + group = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, group); + + field = gaim_request_field_string_new("lastname", _("Last name"), NULL, FALSE); + gaim_request_field_string_set_masked(field, FALSE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("firstname", _("First name"), NULL, FALSE); + gaim_request_field_string_set_masked(field, FALSE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("nickname", _("Nickname"), NULL, FALSE); + gaim_request_field_string_set_masked(field, FALSE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("city", _("City"), NULL, FALSE); + gaim_request_field_string_set_masked(field, FALSE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("year", _("Year of birth"), NULL, FALSE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_choice_new("gender", "Gender", 0); + gaim_request_field_choice_add(field, "Male or female"); + gaim_request_field_choice_add(field, "Male"); + gaim_request_field_choice_add(field, "Female"); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_bool_new("active", _("Only online"), FALSE); + gaim_request_field_group_add_field(group, field); + + gaim_request_fields(gc, + _("Find buddies"), + _("Find buddies"), + _("Please, enter your search criteria below"), + fields, + _("OK"), G_CALLBACK(ggp_callback_find_buddies), + _("Cancel"), NULL, + gc); +} +/* }}} */ + +/* + */ +/* static void ggp_change_passwd(GaimPluginAction *action) {{{ */ +static void ggp_change_passwd(GaimPluginAction *action) +{ + GaimConnection *gc = (GaimConnection *)action->context; + GGPInfo *info = gc->proto_data; + GGPToken *token; + + GaimRequestFields *fields; + GaimRequestFieldGroup *group; + GaimRequestField *field; + + struct gg_http *req; + struct gg_token *t; + gchar *msg; + + gaim_debug_info("gg", "token: requested.\n"); + + /* TODO: This should be async. */ + if ((req = gg_token(0)) == NULL) { + gaim_notify_error(gaim_connection_get_account(gc), + _("Token Error"), + _("Unable to fetch the token.\n"), NULL); return; } + + t = req->data; + + token = g_new0(GGPToken, 1); + token->token_id = g_strdup(t->tokenid); + info->chpasswd_token = token; + + + fields = gaim_request_fields_new(); + group = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, group); + + field = gaim_request_field_string_new("password_cur", _("Current password"), "", FALSE); + gaim_request_field_string_set_masked(field, TRUE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("password1", _("Password"), "", FALSE); + gaim_request_field_string_set_masked(field, TRUE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("password2", _("Password (retype)"), "", FALSE); + gaim_request_field_string_set_masked(field, TRUE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("token", _("Enter current token"), "", FALSE); + gaim_request_field_string_set_masked(field, FALSE); + gaim_request_field_group_add_field(group, field); + + /* original size: 60x24 */ + field = gaim_request_field_image_new("token_img", _("Current token"), req->body, req->body_size); + gaim_request_field_group_add_field(group, field); + + gg_token_free(req); + + msg = g_strdup_printf("%s %d", + _("Please, enter your current password and your new password for UIN: "), + ggp_get_uin(gaim_connection_get_account(gc))); + + gaim_request_fields(gc, + _("Change Gadu-Gadu Password"), + _("Change Gadu-Gadu Password"), + msg, + fields, _("OK"), G_CALLBACK(ggp_callback_change_passwd_ok), + _("Cancel"), NULL, gc); + + g_free(msg); +} +/* }}} */ + +/* ---------------------------------------------------------------------- */ +/* ----- GaimPluginProtocolInfo ----------------------------------------- */ +/* ---------------------------------------------------------------------- */ + +/* + */ +/* static const char *ggp_list_icon(GaimAccount *account, GaimBuddy *buddy) {{{ */ +static const char *ggp_list_icon(GaimAccount *account, GaimBuddy *buddy) +{ + return "gadu-gadu"; +} +/* }}} */ + +/* + */ +/* static void ggp_list_emblems(GaimBuddy *b, const char **se, const char **sw, const char **nw, const char **ne) {{{ */ +static void ggp_list_emblems(GaimBuddy *b, const char **se, const char **sw, const char **nw, const char **ne) +{ + GaimPresence *presence = gaim_buddy_get_presence(b); + + /* + * Note to myself: + * The only valid status types are those defined + * in prpl_info->status_types. + * + * Usable icons: away, blocked, dnd, extendedaway, + * freeforchat, ignored, invisible, na, offline. + */ + + if (!GAIM_BUDDY_IS_ONLINE(b)) { + *se = "offline"; + } else if (gaim_presence_is_status_active(presence, "away")) { + *se = "away"; + } else if (gaim_presence_is_status_active(presence, "online")) { + *se = "online"; + } else if (gaim_presence_is_status_active(presence, "offline")) { + *se = "offline"; + } else if (gaim_presence_is_status_active(presence, "blocked")) { + *se = "blocked"; + } else { + *se = "offline"; + gaim_debug_info("gg", "ggp_list_emblems: unknown status\n"); + } } -#endif +/* }}} */ -static void agg_change_passwd(GaimConnection *gc, const char *old, const char *new) +/* + */ +/* static char *ggp_status_text(GaimBuddy *b) {{{ */ +static char *ggp_status_text(GaimBuddy *b) { - struct agg_http *hpass = g_new0(struct agg_http, 1); - gchar *u = gg_urlencode(gaim_account_get_username(gc->account)); - gchar *p = gg_urlencode(gaim_connection_get_password(gc)); - gchar *enew = gg_urlencode(new); - gchar *eold = gg_urlencode(old); + GaimStatus *status; + const char *msg; + char *text; + char *tmp; - hpass->gc = gc; - hpass->type = AGG_HTTP_PASSWORD_CHANGE; - hpass->form = AGG_REGISTER_DATA_FORM; - hpass->host = GG_REGISTER_HOST; + status = gaim_presence_get_active_status(gaim_buddy_get_presence(b)); - /* We are using old password as place for email - it's ugly */ - hpass->request = g_strdup_printf("fmnumber=%s&fmpwd=%s&pwd=%s&email=%s&code=%u", - u, p, enew, eold, gg_http_hash(old, new)); + msg = gaim_status_get_attr_string(status, "message"); - g_free(u); - g_free(p); - g_free(enew); - g_free(eold); + if (msg != NULL) { + tmp = gaim_markup_strip_html(msg); + text = g_markup_escape_text(tmp, -1); + g_free(tmp); - if (gaim_proxy_connect(gc->account, GG_REGISTER_HOST, GG_REGISTER_PORT, http_req_callback, hpass) < 0) { - gaim_notify_error(gc, NULL, - _("Unable to change Gadu-Gadu password"), - _("Gaim was unable to change your password " - "due to an error connecting to the " - "Gadu-Gadu server. Please try again " - "later.")); - g_free(hpass->request); - g_free(hpass); - return; + return text; + } else { + tmp = g_strdup(gaim_status_get_name(status)); + text = g_markup_escape_text(tmp, -1); + g_free(tmp); + + return text; } } +/* }}} */ -static GList *agg_actions(GaimPlugin *plugin, gpointer context) +/* + */ +/* static char *ggp_tooltip_text(GaimBuddy *b) {{{ */ +static char *ggp_tooltip_text(GaimBuddy *b) +{ + GaimStatus *status; + char *text; + gchar *ret; + const char *msg, *name; + + status = gaim_presence_get_active_status(gaim_buddy_get_presence(b)); + msg = gaim_status_get_attr_string(status, "message"); + name = gaim_status_get_name(status); + + if (msg != NULL) { + char *tmp = gaim_markup_strip_html(msg); + text = g_markup_escape_text(tmp, -1); + g_free(tmp); + + ret = g_strdup_printf("\n%s: %s: %s", + _("Status"), name, text); + + g_free(text); + } else { + ret = g_strdup_printf("\n%s: %s", + _("Status"), name); + } + + return ret; +} +/* }}} */ + +/* + */ +/* static GList *ggp_status_types(GaimAccount *account) {{{ */ +static GList *ggp_status_types(GaimAccount *account) +{ + GaimStatusType *type; + GList *types = NULL; + + type = gaim_status_type_new_with_attrs(GAIM_STATUS_OFFLINE, "offline", _("Offline"), + TRUE, TRUE, FALSE, "message", _("Message"), + gaim_value_new(GAIM_TYPE_STRING), NULL); + types = g_list_append(types, type); + + type = gaim_status_type_new_with_attrs(GAIM_STATUS_ONLINE, "online", _("Online"), + TRUE, TRUE, FALSE, "message", _("Message"), + gaim_value_new(GAIM_TYPE_STRING), NULL); + types = g_list_append(types, type); + + /* + * Without this selecting Available or Invisible as own status doesn't + * work. It's not used and not needed to show status of buddies. + */ + type = gaim_status_type_new_with_attrs(GAIM_STATUS_AVAILABLE, "available", _("Available"), + TRUE, TRUE, FALSE, "message", _("Message"), + gaim_value_new(GAIM_TYPE_STRING), NULL); + types = g_list_append(types, type); + + type = gaim_status_type_new_with_attrs(GAIM_STATUS_HIDDEN, "invisible", _("Invisible"), + TRUE, TRUE, FALSE, "message", _("Message"), + gaim_value_new(GAIM_TYPE_STRING), NULL); + types = g_list_append(types, type); + + /* type = gaim_status_type_new_with_attrs(GAIM_STATUS_UNAVAILABLE, "not-available", "Not Available", */ + /* TRUE, TRUE, FALSE, "message", _("Message"), */ + /* gaim_value_new(GAIM_TYPE_STRING), NULL); */ + /* types = g_list_append(types, type); */ + + type = gaim_status_type_new_with_attrs(GAIM_STATUS_AWAY, "away", _("Busy"), + TRUE, TRUE, FALSE, "message", _("Message"), + gaim_value_new(GAIM_TYPE_STRING), NULL); + types = g_list_append(types, type); + + type = gaim_status_type_new_with_attrs(GAIM_STATUS_HIDDEN, "blocked", _("Blocked"), + TRUE, TRUE, FALSE, "message", _("Message"), + gaim_value_new(GAIM_TYPE_STRING), NULL); + types = g_list_append(types, type); + + return types; +} +/* }}} */ + +/* + */ +/* static GList *ggp_blist_node_menu(GaimBlistNode *node) {{{ */ +static GList *ggp_blist_node_menu(GaimBlistNode *node) { GList *m = NULL; - GaimPluginAction *act = NULL; - -#if 0 - act = gaim_plugin_action_new(_("Directory Search"), show_find_info); - m = g_list_append(m, act); - m = g_list_append(m, NULL); -#endif - - act = gaim_plugin_action_new(_("Change Password"), change_pass); - m = g_list_append(m, act); -#if 0 - act = gaim_plugin_action_new(_("Import Buddy List from Server"), - import_buddies_server); - m = g_list_append(m, act); + if (!GAIM_BLIST_NODE_IS_BUDDY(node)) + return NULL; - act = gaim_plugin_action_new(_("Export Buddy List to Server"), - export_buddies_server); - m = g_list_append(m, act); - - act = gaim_plugin_action_new(_("Delete Buddy List from Server"), - delete_buddies_server); - m = g_list_append(m, act); -#endif + /* act = gaim_blist_node_action_new("Change Password", ggp_bmenu_change_passwd, NULL, NULL); */ + /* m = g_list_append(m, act); */ return m; } +/* }}} */ -static void agg_get_info(GaimConnection *gc, const char *who) +/* + */ +/* static void ggp_login(GaimAccount *account, GaimStatus *status) {{{ */ +static void ggp_login(GaimAccount *account, GaimStatus *status) { - struct agg_http *srch = g_new0(struct agg_http, 1); - srch->gc = gc; - srch->type = AGG_HTTP_SEARCH; - srch->form = AGG_PUBDIR_SEARCH_FORM; - srch->host = GG_PUBDIR_HOST; + GaimConnection *gc = gaim_account_get_connection(account); + struct gg_login_params *glp = g_new0(struct gg_login_params, 1); + GGPInfo *info = g_new0(GGPInfo, 1); + + info->session = NULL; + info->searchresults_window = NULL; + + gc->proto_data = info; + + glp->uin = ggp_get_uin(account); + glp->password = (char *)gaim_account_get_password(account); + + glp->async = 0; + glp->status = GG_STATUS_AVAIL; + glp->tls = 0; + + info->session = gg_login(glp); + if (info->session == NULL) { + gaim_connection_error(gc, _("Connection failed.")); + g_free(glp); + return; + } + gaim_debug_info("gg", "ggp_login: so far so good.\n"); + + gc->inpa = gaim_input_add(info->session->fd, GAIM_INPUT_READ, ggp_callback_recv, gc); - /* If it's invalid uin then maybe it's nickname? */ - if (invalid_uin(who)) { - gchar *new_who = charset_convert(who, "UTF-8", "CP1250"); - gchar *enew_who = gg_urlencode(new_who); + gg_change_status(info->session, GG_STATUS_AVAIL); + gaim_connection_set_state(gc, GAIM_CONNECTED); + ggp_buddylist_send(gc); +} +/* }}} */ + +/* + */ +/* static void ggp_close(GaimConnection *gc) {{{ */ +static void ggp_close(GaimConnection *gc) +{ + GGPInfo *info; - g_free(new_who); + if (gc == NULL) { + gaim_debug_info("gg", "gc == NULL\n"); + return; + } + + info = gc->proto_data; + + /* XXX: Any way to pass description here? */ + if (info->session != NULL) + gg_change_status(info->session, GG_STATUS_NOT_AVAIL); + + if (gc->inpa > 0) + gaim_input_remove(gc->inpa); + + gg_logoff(info->session); + gg_free_session(info->session); + ggp_buddylist_offline(gc); - srch->request = g_strdup_printf("Mode=0&FirstName=%s&LastName=%s&Gender=%d" - "&NickName=%s&City=%s&MinBirth=%d&MaxBirth=%d", - "", "", AGG_GENDER_NONE, enew_who, "", 0, 0); + gaim_debug_info("gg", "Connection closed.\n"); +} +/* }}} */ + +/* + */ +/* static int ggp_send_im(GaimConnection *gc, const char *who, const char *msg, GaimConvImFlags flags) {{{ */ +static int ggp_send_im(GaimConnection *gc, const char *who, const char *msg, GaimConvImFlags flags) +{ + GGPInfo *info = gc->proto_data; + const char *tmp; + + if (strlen(msg) == 0) + return 1; + + tmp = charset_convert(msg, "UTF-8", "CP1250"); - g_free(enew_who); - } else - srch->request = g_strdup_printf("Mode=3&UserId=%s", who); + if (tmp != NULL && strlen(tmp) > 0) { + if (gg_send_message(info->session, GG_CLASS_CHAT, ggp_str_to_uin(who), tmp) < 0) { + return -1; + } + } + + return 1; +} +/* }}} */ + +/* + */ +/* static void ggp_get_info(GaimConnection *gc, const char *name) { {{{ */ +static void ggp_get_info(GaimConnection *gc, const char *name) +{ + GGPInfo *info = gc->proto_data; + GGPSearchForm *form; - if (gaim_proxy_connect(gc->account, GG_PUBDIR_HOST, GG_PUBDIR_PORT, http_req_callback, srch) < 0) { - gaim_notify_error(gc, NULL, - _("Unable to access user profile."), - _("Gaim was unable to access this user's " - "profile due to an error connecting to the " - "directory server. Please try again later.")); - g_free(srch->request); - g_free(srch); + form = ggp_searchform_new(); + info->search_form = form; + + form->uin = g_strdup(name); + form->offset = g_strdup("0"); + form->last_uin = g_strdup("0"); + + ggp_pubdir_start_search(gc, form); +} +/* }}} */ + +/* + */ +/* static void ggp_set_status(GaimAccount *account, GaimStatus *status) {{{ */ +static void ggp_set_status(GaimAccount *account, GaimStatus *status) +{ + GaimStatusPrimitive prim; + GaimConnection *gc; + GGPInfo *info; + const char *status_id; + int new_status, new_status_descr; + + prim = gaim_status_type_get_primitive(gaim_status_get_type(status)); + + if (!gaim_status_is_active(status)) + return; + + if (prim == GAIM_STATUS_OFFLINE) { + gaim_account_disconnect(account); + return; + } + + if (!gaim_account_is_connected(account)) { + gaim_account_connect(account); return; } -} + + gc = gaim_account_get_connection(account); + info = gc->proto_data; + + status_id = gaim_status_get_id(status); + + gaim_debug_info("gg", "ggp_set_status: Requested status = %s\n", status_id); -static const char *agg_list_icon(GaimAccount *a, GaimBuddy *b) -{ - return "gadu-gadu"; -} + if (strcmp(status_id, "available") == 0) { + new_status = GG_STATUS_AVAIL; + new_status_descr = GG_STATUS_AVAIL_DESCR; + } else if (strcmp(status_id, "away") == 0) { + new_status = GG_STATUS_BUSY; + new_status_descr = GG_STATUS_BUSY_DESCR; + } else if (strcmp(status_id, "invisible") == 0) { + new_status = GG_STATUS_INVISIBLE; + new_status_descr = GG_STATUS_INVISIBLE_DESCR; + } else { + new_status = GG_STATUS_AVAIL; + new_status_descr = GG_STATUS_AVAIL_DESCR; + gaim_debug_info("gg", "ggp_set_status: uknown status requested (status_id=%s)\n", status_id); + } -static void agg_list_emblems(GaimBuddy *b, const char **se, const char **sw, - const char **nw, const char **ne) -{ - GaimPresence *presence = gaim_buddy_get_presence(b); + const char *msg = gaim_status_get_attr_string(status, "message"); + + if (msg == NULL) { + gaim_debug_info("gg", "ggp_set_status: msg == NULL\n"); + gg_change_status(info->session, new_status); + } else { + char *tmp = charset_convert(msg, "UTF-8", "CP1250"); + gaim_debug_info("gg", "ggp_set_status: msg != NULL. msg = %s\n", tmp); + gaim_debug_info("gg", "ggp_set_status: gg_change_status_descr() = %d\n", + gg_change_status_descr(info->session, new_status_descr, tmp)); + g_free(tmp); + } - if (!GAIM_BUDDY_IS_ONLINE(b)) - *se = "offline"; - else if (gaim_presence_is_status_active(presence, "away") || - gaim_presence_is_status_active(presence, "away-friends")) - { - *se = "away"; - } - else if (gaim_presence_is_status_active(presence, "invisible") || - gaim_presence_is_status_active(presence, "invisible-friends")) - { - *se = "invisible"; +} +/* }}} */ + +/* + */ +/* static void ggp_add_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group) {{{ */ +static void ggp_add_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group) +{ + GGPInfo *info = gc->proto_data; + + gg_add_notify(info->session, ggp_str_to_uin(buddy->name)); +} +/* }}} */ + +/* + */ +/* static void ggp_remove_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group) {{{ */ +static void ggp_remove_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group) +{ + GGPInfo *info = gc->proto_data; + + gg_remove_notify(info->session, ggp_str_to_uin(buddy->name)); +} +/* }}} */ + +/* + */ +/* static void ggp_keepalive(GaimConnection *gc) {{{ */ +static void ggp_keepalive(GaimConnection *gc) +{ + GGPInfo *info = gc->proto_data; + + /* gaim_debug_info("gg", "Keeping connection alive....\n"); */ + + if (gg_ping(info->session) < 0) { + gaim_debug_info("gg", "Not connected to the server " + "or gg_session is not correct\n"); + gaim_connection_error(gc, _("Not connected to the server.")); } } +/* }}} */ + +/* + */ +/* static void ggp_register_user(GaimAccount *account) {{{ */ +static void ggp_register_user(GaimAccount *account) +{ + GaimConnection *gc; + GaimRequestFields *fields; + GaimRequestFieldGroup *group; + GaimRequestField *field; + GGPInfo *info; + GGPToken *token; + + struct gg_http *req; + struct gg_token *t; + + gaim_debug_info("gg", "token: requested.\n"); + + if ((req = gg_token(0)) == NULL) { + gaim_notify_error(account, _("Token Error"), + _("Unable to fetch the token.\n"), NULL); + return; + } + t = req->data; + + gc = gaim_account_get_connection(account); + + info = g_new0(GGPInfo, 1); + gc->proto_data = info; + + token = g_new0(GGPToken, 1); + token->token_id = g_strdup(t->tokenid); + info->register_token = token; + + fields = gaim_request_fields_new(); + group = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, group); + + field = gaim_request_field_string_new("email", _("e-Mail"), "", FALSE); + gaim_request_field_string_set_masked(field, FALSE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("password1", _("Password"), "", FALSE); + gaim_request_field_string_set_masked(field, TRUE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("password2", _("Password (retype)"), "", FALSE); + gaim_request_field_string_set_masked(field, TRUE); + gaim_request_field_group_add_field(group, field); + + field = gaim_request_field_string_new("token", _("Enter current token"), "", FALSE); + gaim_request_field_string_set_masked(field, FALSE); + gaim_request_field_group_add_field(group, field); + + /* original size: 60x24 */ + field = gaim_request_field_image_new("token_img", _("Current token"), req->body, req->body_size); + gaim_request_field_group_add_field(group, field); + + gg_token_free(req); -static void agg_set_permit_deny_dummy(GaimConnection *gc) -{ - /* It's implemented on client side because GG server doesn't support this */ + gaim_request_fields(account, + _("Register New Gadu-Gadu Account"), + _("Register New Gadu-Gadu Account"), + _("Please, fill in the following fields"), + fields, _("OK"), G_CALLBACK(ggp_callback_register_account_ok), + _("Cancel"), NULL, gc); } +/* }}} */ -static void agg_permit_deny_dummy(GaimConnection *gc, const char *who) -{ - /* It's implemented on client side because GG server doesn't support this */ -} - -static void agg_group_buddy (GaimConnection *gc, const char *who, - const char *old_group, const char *new_group) +/* + */ +/* static GList *ggp_actions(GaimPlugin *plugin, gpointer context) {{{ */ +static GList *ggp_actions(GaimPlugin *plugin, gpointer context) { - GaimBuddy *buddy = gaim_find_buddy(gaim_connection_get_account(gc), who); - gchar *newdata; - /* GG Number */ - gchar *name = buddy->name; - /* GG Pseudo */ - gchar *show = buddy->alias ? buddy->alias : buddy->name; - /* Group Name */ - const gchar *gname = new_group; + GList *m = NULL; + GaimPluginAction *act; + + act = gaim_plugin_action_new(_("Find buddies"), ggp_find_buddies); + m = g_list_append(m, act); + + m = g_list_append(m, NULL); + + act = gaim_plugin_action_new(_("Change password"), ggp_change_passwd); + m = g_list_append(m, act); + + m = g_list_append(m, NULL); + + act = gaim_plugin_action_new(_("Upload buddylist to Server"), ggp_action_buddylist_put); + m = g_list_append(m, act); - newdata = g_strdup_printf("%s;%s;%s;%s;%s;%s;%s;%s%s\r\n", - show, show, show, show, "", gname, name, "", ""); - agg_save_buddy_list(gc, newdata); - g_free(newdata); -} + act = gaim_plugin_action_new(_("Download buddylist from Server"), ggp_action_buddylist_get); + m = g_list_append(m, act); + + act = gaim_plugin_action_new(_("Delete buddylist from Server"), ggp_action_buddylist_delete); + m = g_list_append(m, act); + + act = gaim_plugin_action_new(_("Save buddylist to file"), ggp_action_buddylist_save); + m = g_list_append(m, act); -static void agg_rename_group (GaimConnection *gc, const char *old_name, - GaimGroup *group, GList *moved_buddies) -{ - agg_save_buddy_list(gc, NULL); + act = gaim_plugin_action_new(_("Load buddylist from file"), ggp_action_buddylist_load); + m = g_list_append(m, act); + + return m; } +/* }}} */ -static GaimPlugin *my_protocol = NULL; - +/* prpl_info setup {{{ */ static GaimPluginProtocolInfo prpl_info = { - 0, - NULL, /* user_splits */ - NULL, /* protocol_options */ + OPT_PROTO_REGISTER_NOSCREENNAME, + NULL, /* user_splits */ + NULL, /* protocol_options */ NO_BUDDY_ICONS, /* icon_spec */ - agg_list_icon, /* list_icon */ - agg_list_emblems, /* list_emblems */ - NULL, /* status_text */ - NULL, /* tooltip_text */ - agg_status_types, /* status_types */ - agg_blist_node_menu, /* blist_node_menu */ - NULL, /* chat_info */ - NULL, /* chat_info_defaults */ - agg_login, /* login */ - agg_close, /* close */ - agg_send_im, /* send_im */ - NULL, /* set_info */ - NULL, /* send_typing */ - agg_get_info, /* get_info */ - agg_set_status, /* set_away */ - NULL, /* set_idle */ - agg_change_passwd, /* change_passwd */ - agg_add_buddy, /* add_buddy */ - NULL, /* add_buddies */ - agg_rem_buddy, /* remove_buddy */ - NULL, /* remove_buddies */ - agg_permit_deny_dummy, /* add_permit */ - agg_permit_deny_dummy, /* add_deny */ - agg_permit_deny_dummy, /* rem_permit */ - agg_permit_deny_dummy, /* rem_deny */ - agg_set_permit_deny_dummy, /* set_permit_deny */ - NULL, /* join_chat */ - NULL, /* reject_chat */ - NULL, /* get_chat_name */ - NULL, /* chat_invite */ - NULL, /* chat_leave */ - NULL, /* chat_whisper */ - NULL, /* chat_send */ - agg_keepalive, /* keepalive */ - NULL, /* register_user */ - NULL, /* get_cb_info */ - NULL, /* get_cb_away */ - NULL, /* alias_buddy */ - agg_group_buddy, /* group_buddy */ - agg_rename_group, /* rename_group */ - agg_buddy_free, /* buddy_free */ - NULL, /* convo_closed */ - NULL, /* normalize */ - NULL, /* set_buddy_icon */ - NULL, /* remove_group */ - NULL, /* get_cb_real_name */ - NULL, /* set_chat_topic */ - NULL, /* find_blist_chat */ - NULL, /* roomlist_get_list */ - NULL, /* roomlist_cancel */ - NULL, /* roomlist_expand_category */ - NULL, /* can_receive_file */ - NULL /* send_file */ + ggp_list_icon, /* list_icon */ + ggp_list_emblems, /* list_emblems */ + ggp_status_text, /* status_text */ + ggp_tooltip_text, /* tooltip_text */ + ggp_status_types, /* status_types */ + ggp_blist_node_menu, /* blist_node_menu */ + NULL, /* chat_info */ + NULL, /* chat_info_defaults */ + ggp_login, /* login */ + ggp_close, /* close */ + ggp_send_im, /* send_im */ + NULL, /* set_info */ + NULL, /* send_typing */ + ggp_get_info, /* get_info */ + ggp_set_status, /* set_away */ + NULL, /* set_idle */ + NULL, /* change_passwd */ + ggp_add_buddy, /* add_buddy */ + NULL, /* add_buddies */ + ggp_remove_buddy, /* remove_buddy */ + NULL, /* remove_buddies */ + NULL, /* add_permit */ + NULL, /* add_deny */ + NULL, /* rem_permit */ + NULL, /* rem_deny */ + NULL, /* set_permit_deny */ + NULL, /* join_chat */ + NULL, /* reject_chat */ + NULL, /* get_chat_name */ + NULL, /* chat_invite */ + NULL, /* chat_leave */ + NULL, /* chat_whisper */ + NULL, /* chat_send */ + ggp_keepalive, /* keepalive */ + ggp_register_user, /* register_user */ + NULL, /* get_cb_info */ + NULL, /* get_cb_away */ + NULL, /* alias_buddy */ + NULL, /* group_buddy */ + NULL, /* rename_group */ + NULL, /* buddy_free */ + NULL, /* convo_closed */ + NULL, /* normalize */ + NULL, /* set_buddy_icon */ + NULL, /* remove_group */ + NULL, /* get_cb_real_name */ + NULL, /* set_chat_topic */ + NULL, /* find_blist_chat */ + NULL, /* roomlist_get_list */ + NULL, /* roomlist_cancel */ + NULL, /* roomlist_expand_category */ + NULL, /* can_receive_file */ + NULL /* send_file */ }; +/* }}} */ -static GaimPluginInfo info = -{ - GAIM_PLUGIN_MAGIC, - GAIM_MAJOR_VERSION, - GAIM_MINOR_VERSION, - GAIM_PLUGIN_PROTOCOL, /**< type */ - NULL, /**< ui_requirement */ - 0, /**< flags */ - NULL, /**< dependencies */ - GAIM_PRIORITY_DEFAULT, /**< priority */ +/* GaimPluginInfo setup {{{ */ +static GaimPluginInfo info = { + GAIM_PLUGIN_MAGIC, /* magic */ + GAIM_MAJOR_VERSION, /* major_version */ + GAIM_MINOR_VERSION, /* minor_version */ + GAIM_PLUGIN_PROTOCOL, /* plugin type */ + NULL, /* ui_requirement */ + 0, /* flags */ + NULL, /* dependencies */ + GAIM_PRIORITY_DEFAULT, /* priority */ + + "prpl-gg", /* id */ + "Gadu-Gadu", /* name */ + VERSION, /* version */ - "prpl-gg", /**< id */ - "Gadu-Gadu", /**< name */ - VERSION, /**< version */ - /** summary */ - N_("Gadu-Gadu Protocol Plugin"), - /** description */ - N_("Gadu-Gadu Protocol Plugin"), - "Arkadiusz Miśkiewicz ", /**< author */ - GAIM_WEBSITE, /**< homepage */ + N_("Gadu-Gadu Protocol Plugin"), /* summary */ + N_("Polish popular IM"), /* description */ + "boler@sourceforge.net", /* author */ + GAIM_WEBSITE, /* homepage */ + + NULL, /* load */ + NULL, /* unload */ + NULL, /* destroy */ - NULL, /**< load */ - NULL, /**< unload */ - NULL, /**< destroy */ + NULL, /* ui_info */ + &prpl_info, /* extra_info */ + NULL, /* prefs_info */ + ggp_actions /* actions */ +}; +/* }}} */ - NULL, /**< ui_info */ - &prpl_info, /**< extra_info */ - NULL, - agg_actions -}; - -static void -init_plugin(GaimPlugin *plugin) +/* + */ +/* static void init_plugin(GaimPlugin *plugin) {{{ */ +static void init_plugin(GaimPlugin *plugin) { GaimAccountOption *option; - option = gaim_account_option_string_new(_("Nick"), "nick", - "Gadu-Gadu User"); - prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, - option); + option = gaim_account_option_string_new(_("Nickname"), "nick", _("Gadu-Gadu User")); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); my_protocol = plugin; } +/* }}} */ -GAIM_INIT_PLUGIN(gg, init_plugin, info) +GAIM_INIT_PLUGIN(gadu-gadu, init_plugin, info); + +/* vim: set ts=4 sts=0 sw=4 noet: */ diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/lib/COPYING --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/lib/COPYING Sun Aug 28 22:46:01 2005 +0000 @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/lib/common.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/lib/common.c Sun Aug 28 22:46:01 2005 +0000 @@ -0,0 +1,822 @@ +/* $Id: common.c 13582 2005-08-28 22:46:01Z boler $ */ + +/* + * (C) Copyright 2001-2002 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 Lesser General Public License Version + * 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#include +#include +#include +#ifdef sun +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libgadu.h" + +FILE *gg_debug_file = NULL; + +#ifndef GG_DEBUG_DISABLE + +/* + * gg_debug() // funkcja wewnętrzna + * + * wyświetla komunikat o danym poziomie, o ile użytkownik sobie tego życzy. + * + * - level - poziom wiadomości + * - format... - treść wiadomości (kompatybilna z printf()) + */ +void gg_debug(int level, const char *format, ...) +{ + va_list ap; + int old_errno = errno; + + if (gg_debug_handler) { + va_start(ap, format); + (*gg_debug_handler)(level, format, ap); + va_end(ap); + + goto cleanup; + } + + if ((gg_debug_level & level)) { + va_start(ap, format); + vfprintf((gg_debug_file) ? gg_debug_file : stderr, format, ap); + va_end(ap); + } + +cleanup: + errno = old_errno; +} + +#endif + +/* + * gg_vsaprintf() // funkcja pomocnicza + * + * robi dokładnie to samo, co vsprintf(), tyle że alokuje sobie wcześniej + * miejsce na dane. powinno działać na tych maszynach, które mają funkcję + * vsnprintf() zgodną z C99, jak i na wcześniejszych. + * + * - format - opis wyświetlanego tekstu jak dla printf() + * - ap - lista argumentów dla printf() + * + * zaalokowany bufor, który należy później zwolnić, lub NULL + * jeśli nie udało się wykonać zadania. + */ +char *gg_vsaprintf(const char *format, va_list ap) +{ + int size = 0; + const char *start; + char *buf = NULL; + +#ifdef __GG_LIBGADU_HAVE_VA_COPY + va_list aq; + + va_copy(aq, ap); +#else +# ifdef __GG_LIBGADU_HAVE___VA_COPY + va_list aq; + + __va_copy(aq, ap); +# endif +#endif + + start = format; + +#ifndef __GG_LIBGADU_HAVE_C99_VSNPRINTF + { + int res; + char *tmp; + + size = 128; + do { + size *= 2; + if (!(tmp = realloc(buf, size))) { + free(buf); + return NULL; + } + buf = tmp; + res = vsnprintf(buf, size, format, ap); + } while (res == size - 1 || res == -1); + } +#else + { + char tmp[2]; + + /* libce Solarisa przy buforze NULL zawsze zwracają -1, więc + * musimy podać coś istniejącego jako cel printf()owania. */ + size = vsnprintf(tmp, sizeof(tmp), format, ap); + if (!(buf = malloc(size + 1))) + return NULL; + } +#endif + + format = start; + +#ifdef __GG_LIBGADU_HAVE_VA_COPY + vsnprintf(buf, size + 1, format, aq); + va_end(aq); +#else +# ifdef __GG_LIBGADU_HAVE___VA_COPY + vsnprintf(buf, size + 1, format, aq); + va_end(aq); +# else + vsnprintf(buf, size + 1, format, ap); +# endif +#endif + + return buf; +} + +/* + * gg_saprintf() // funkcja pomocnicza + * + * robi dokładnie to samo, co sprintf(), tyle że alokuje sobie wcześniej + * miejsce na dane. powinno działać na tych maszynach, które mają funkcję + * vsnprintf() zgodną z C99, jak i na wcześniejszych. + * + * - format... - treść taka sama jak w funkcji printf() + * + * zaalokowany bufor, który należy później zwolnić, lub NULL + * jeśli nie udało się wykonać zadania. + */ +char *gg_saprintf(const char *format, ...) +{ + va_list ap; + char *res; + + va_start(ap, format); + res = gg_vsaprintf(format, ap); + va_end(ap); + + return res; +} + +/* + * gg_get_line() // funkcja pomocnicza + * + * podaje kolejną linię z bufora tekstowego. niszczy go bezpowrotnie, dzieląc + * na kolejne stringi. zdarza się, nie ma potrzeby pisania funkcji dublującej + * bufor żeby tylko mieć nieruszone dane wejściowe, skoro i tak nie będą nam + * poźniej potrzebne. obcina `\r\n'. + * + * - ptr - wskaźnik do zmiennej, która przechowuje aktualną pozycję + * w przemiatanym buforze + * + * wskaźnik do kolejnej linii tekstu lub NULL, jeśli to już koniec bufora. + */ +char *gg_get_line(char **ptr) +{ + char *foo, *res; + + if (!ptr || !*ptr || !strcmp(*ptr, "")) + return NULL; + + res = *ptr; + + if (!(foo = strchr(*ptr, '\n'))) + *ptr += strlen(*ptr); + else { + *ptr = foo + 1; + *foo = 0; + if (strlen(res) > 1 && res[strlen(res) - 1] == '\r') + res[strlen(res) - 1] = 0; + } + + return res; +} + +/* + * gg_connect() // funkcja pomocnicza + * + * łączy się z serwerem. pierwszy argument jest typu (void *), żeby nie + * musieć niczego inkludować w libgadu.h i nie psuć jakiś głupich zależności + * na dziwnych systemach. + * + * - addr - adres serwera (struct in_addr *) + * - port - port serwera + * - async - asynchroniczne połączenie + * + * deskryptor gniazda lub -1 w przypadku błędu (kod błędu w zmiennej errno). + */ +int gg_connect(void *addr, int port, int async) +{ + int sock, one = 1, errno2; + struct sockaddr_in sin; + struct in_addr *a = addr; + struct sockaddr_in myaddr; + + 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, "// gg_connect() socket() failed (errno=%d, %s)\n", errno, strerror(errno)); + return -1; + } + + memset(&myaddr, 0, sizeof(myaddr)); + myaddr.sin_family = AF_INET; + + myaddr.sin_addr.s_addr = gg_local_ip; + + if (bind(sock, (struct sockaddr *) &myaddr, sizeof(myaddr)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_connect() bind() failed (errno=%d, %s)\n", errno, strerror(errno)); + return -1; + } + +#ifdef ASSIGN_SOCKETS_TO_THREADS + gg_win32_thread_socket(0, sock); +#endif + + if (async) { +#ifdef FIONBIO + if (ioctl(sock, FIONBIO, &one) == -1) { +#else + if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_connect() ioctl() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + close(sock); + errno = errno2; + 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) { + if (errno && (!async || errno != EINPROGRESS)) { + gg_debug(GG_DEBUG_MISC, "// gg_connect() connect() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + close(sock); + errno = errno2; + return -1; + } + gg_debug(GG_DEBUG_MISC, "// gg_connect() connect() in progress\n"); + } + + return sock; +} + +/* + * gg_read_line() // funkcja pomocnicza + * + * czyta jedną linię tekstu z gniazda. + * + * - sock - deskryptor gniazda + * - buf - wskaźnik do bufora + * - length - długość bufora + * + * jeśli trafi na błąd odczytu lub podano nieprawidłowe parametry, zwraca NULL. + * inaczej zwraca buf. + */ +char *gg_read_line(int sock, char *buf, int length) +{ + int ret; + + if (!buf || length < 0) + return NULL; + + for (; length > 1; buf++, length--) { + do { + if ((ret = read(sock, buf, 1)) == -1 && errno != EINTR) { + gg_debug(GG_DEBUG_MISC, "// gg_read_line() error on read (errno=%d, %s)\n", errno, strerror(errno)); + *buf = 0; + return NULL; + } else if (ret == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_read_line() eof reached\n"); + *buf = 0; + return NULL; + } + } while (ret == -1 && errno == EINTR); + + if (*buf == '\n') { + buf++; + break; + } + } + + *buf = 0; + return buf; +} + +/* + * gg_chomp() // funkcja pomocnicza + * + * ucina "\r\n" lub "\n" z końca linii. + * + * - line - linia do przycięcia + */ +void gg_chomp(char *line) +{ + int len; + + if (!line) + return; + + len = strlen(line); + + if (len > 0 && line[len - 1] == '\n') + line[--len] = 0; + if (len > 0 && line[len - 1] == '\r') + line[--len] = 0; +} + +/* + * gg_urlencode() // funkcja wewnętrzna + * + * zamienia podany tekst na ciąg znaków do formularza http. przydaje się + * przy różnych usługach katalogu publicznego. + * + * - str - ciąg znaków do zakodowania + * + * zaalokowany bufor, który należy później zwolnić albo NULL + * w przypadku błędu. + */ +char *gg_urlencode(const char *str) +{ + char *q, *buf, hex[] = "0123456789abcdef"; + const char *p; + unsigned int size = 0; + + if (!str) + str = ""; + + for (p = str; *p; p++, size++) { + if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == ' ') || (*p == '@') || (*p == '.') || (*p == '-')) + size += 2; + } + + if (!(buf = malloc(size + 1))) + return NULL; + + for (p = str, q = buf; *p; p++, q++) { + if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || (*p == '@') || (*p == '.') || (*p == '-')) + *q = *p; + else { + if (*p == ' ') + *q = '+'; + else { + *q++ = '%'; + *q++ = hex[*p >> 4 & 15]; + *q = hex[*p & 15]; + } + } + } + + *q = 0; + + return buf; +} + +/* + * gg_http_hash() // funkcja wewnętrzna + * + * funkcja licząca hash dla adresu e-mail, hasła i paru innych. + * + * - format... - format kolejnych parametrów ('s' jeśli dany parametr jest + * ciągiem znaków lub 'u' jeśli numerem GG) + * + * hash wykorzystywany przy rejestracji i wszelkich manipulacjach własnego + * wpisu w katalogu publicznym. + */ +int gg_http_hash(const char *format, ...) +{ + unsigned int a, c, i, j; + va_list ap; + int b = -1; + + va_start(ap, format); + + for (j = 0; j < strlen(format); j++) { + char *arg, buf[16]; + + if (format[j] == 'u') { + snprintf(buf, sizeof(buf), "%d", va_arg(ap, uin_t)); + arg = buf; + } else { + if (!(arg = va_arg(ap, char*))) + arg = ""; + } + + i = 0; + while ((c = (unsigned char) arg[i++]) != 0) { + a = (c ^ b) + (c << 8); + b = (a >> 24) | (a << 8); + } + } + + va_end(ap); + + return (b < 0 ? -b : b); +} + +/* + * gg_gethostbyname() // funkcja pomocnicza + * + * odpowiednik gethostbyname() troszczący się o współbieżność, gdy mamy do + * dyspozycji funkcję gethostbyname_r(). + * + * - hostname - nazwa serwera + * + * zwraca wskaźnik na strukturę in_addr, którą należy zwolnić. + */ +struct in_addr *gg_gethostbyname(const char *hostname) +{ + struct in_addr *addr = NULL; + +#ifdef HAVE_GETHOSTBYNAME_R + char *tmpbuf = NULL, *buf = NULL; + struct hostent *hp = NULL, *hp2 = NULL; + int h_errnop, ret; + size_t buflen = 1024; + int new_errno; + + new_errno = ENOMEM; + + if (!(addr = malloc(sizeof(struct in_addr)))) + goto cleanup; + + if (!(hp = calloc(1, sizeof(*hp)))) + goto cleanup; + + if (!(buf = malloc(buflen))) + goto cleanup; + + tmpbuf = buf; + + while ((ret = gethostbyname_r(hostname, hp, buf, buflen, &hp2, &h_errnop)) == ERANGE) { + buflen *= 2; + + if (!(tmpbuf = realloc(buf, buflen))) + break; + + buf = tmpbuf; + } + + if (ret) + new_errno = h_errnop; + + if (ret || !hp2 || !tmpbuf) + goto cleanup; + + memcpy(addr, hp->h_addr, sizeof(struct in_addr)); + + free(buf); + free(hp); + + return addr; + +cleanup: + errno = new_errno; + + if (addr) + free(addr); + if (hp) + free(hp); + if (buf) + free(buf); + + return NULL; +#else + struct hostent *hp; + + if (!(addr = malloc(sizeof(struct in_addr)))) { + goto cleanup; + } + + if (!(hp = gethostbyname(hostname))) + goto cleanup; + + memcpy(addr, hp->h_addr, sizeof(struct in_addr)); + + return addr; + +cleanup: + if (addr) + free(addr); + + return NULL; +#endif +} + +#ifdef ASSIGN_SOCKETS_TO_THREADS + +typedef struct gg_win32_thread { + int id; + int socket; + struct gg_win32_thread *next; +} gg_win32_thread; + +struct gg_win32_thread *gg_win32_threads = 0; + +/* + * gg_win32_thread_socket() // funkcja pomocnicza, tylko dla win32 + * + * zwraca deskryptor gniazda, które było ostatnio tworzone dla wątku + * o podanym identyfikatorze. + * + * jeśli na win32 przy połączeniach synchronicznych zapamiętamy w jakim + * wątku uruchomiliśmy funkcję, która się z czymkolwiek łączy, to z osobnego + * wątku możemy anulować połączenie poprzez gg_win32_thread_socket(watek, -1); + * + * - thread_id - id wątku. jeśli jest równe 0, brany jest aktualny wątek, + * jeśli równe -1, usuwa wpis o podanym sockecie. + * - socket - deskryptor gniazda. jeśli równe 0, zwraca deskryptor gniazda + * dla podanego wątku, jeśli równe -1, usuwa wpis, jeśli coś + * innego, ustawia dla podanego wątku dany numer deskryptora. + * + * jeśli socket jest równe 0, zwraca deskryptor gniazda dla podanego wątku. + */ +int gg_win32_thread_socket(int thread_id, int socket) +{ + char close = (thread_id == -1) || socket == -1; + gg_win32_thread *wsk = gg_win32_threads; + gg_win32_thread **p_wsk = &gg_win32_threads; + + if (!thread_id) + thread_id = GetCurrentThreadId(); + + while (wsk) { + if ((thread_id == -1 && wsk->socket == socket) || wsk->id == thread_id) { + if (close) { + /* socket zostaje usuniety */ + closesocket(wsk->socket); + *p_wsk = wsk->next; + free(wsk); + return 1; + } else if (!socket) { + /* socket zostaje zwrocony */ + return wsk->socket; + } else { + /* socket zostaje ustawiony */ + wsk->socket = socket; + return socket; + } + } + p_wsk = &(wsk->next); + wsk = wsk->next; + } + + if (close && socket != -1) + closesocket(socket); + if (close || !socket) + return 0; + + /* Dodaje nowy element */ + wsk = malloc(sizeof(gg_win32_thread)); + wsk->id = thread_id; + wsk->socket = socket; + wsk->next = 0; + *p_wsk = wsk; + + return socket; +} + +#endif /* ASSIGN_SOCKETS_TO_THREADS */ + +static char gg_base64_charset[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* + * gg_base64_encode() + * + * zapisuje ciąg znaków w base64. + * + * - buf - ciąg znaków. + * + * zaalokowany bufor. + */ +char *gg_base64_encode(const char *buf) +{ + char *out, *res; + unsigned int i = 0, j = 0, k = 0, len = strlen(buf); + + res = out = malloc((len / 3 + 1) * 4 + 2); + + if (!res) + return NULL; + + while (j <= len) { + switch (i % 4) { + case 0: + k = (buf[j] & 252) >> 2; + break; + case 1: + if (j < len) + k = ((buf[j] & 3) << 4) | ((buf[j + 1] & 240) >> 4); + else + k = (buf[j] & 3) << 4; + + j++; + break; + case 2: + if (j < len) + k = ((buf[j] & 15) << 2) | ((buf[j + 1] & 192) >> 6); + else + k = (buf[j] & 15) << 2; + + j++; + break; + case 3: + k = buf[j++] & 63; + break; + } + *out++ = gg_base64_charset[k]; + i++; + } + + if (i % 4) + for (j = 0; j < 4 - (i % 4); j++, out++) + *out = '='; + + *out = 0; + + return res; +} + +/* + * gg_base64_decode() + * + * dekoduje ciąg znaków z base64. + * + * - buf - ciąg znaków. + * + * zaalokowany bufor. + */ +char *gg_base64_decode(const char *buf) +{ + char *res, *save, *foo, val; + const char *end; + unsigned int index = 0; + + if (!buf) + return NULL; + + save = res = calloc(1, (strlen(buf) / 4 + 1) * 3 + 2); + + if (!save) + return NULL; + + end = buf + strlen(buf); + + while (*buf && buf < end) { + if (*buf == '\r' || *buf == '\n') { + buf++; + continue; + } + if (!(foo = strchr(gg_base64_charset, *buf))) + foo = gg_base64_charset; + val = (int)(foo - gg_base64_charset); + buf++; + switch (index) { + case 0: + *res |= val << 2; + break; + case 1: + *res++ |= val >> 4; + *res |= val << 4; + break; + case 2: + *res++ |= val >> 2; + *res |= val << 6; + break; + case 3: + *res++ |= val; + break; + } + index++; + index %= 4; + } + *res = 0; + + return save; +} + +/* + * gg_proxy_auth() // funkcja wewnętrzna + * + * tworzy nagłówek autoryzacji dla proxy. + * + * zaalokowany tekst lub NULL, jeśli proxy nie jest włączone lub nie wymaga + * autoryzacji. + */ +char *gg_proxy_auth() +{ + char *tmp, *enc, *out; + unsigned int tmp_size; + + if (!gg_proxy_enabled || !gg_proxy_username || !gg_proxy_password) + return NULL; + + if (!(tmp = malloc((tmp_size = strlen(gg_proxy_username) + strlen(gg_proxy_password) + 2)))) + return NULL; + + snprintf(tmp, tmp_size, "%s:%s", gg_proxy_username, gg_proxy_password); + + if (!(enc = gg_base64_encode(tmp))) { + free(tmp); + return NULL; + } + + free(tmp); + + if (!(out = malloc(strlen(enc) + 40))) { + free(enc); + return NULL; + } + + snprintf(out, strlen(enc) + 40, "Proxy-Authorization: Basic %s\r\n", enc); + + free(enc); + + return out; +} + +static uint32_t gg_crc32_table[256]; +static int gg_crc32_initialized = 0; + +/* + * gg_crc32_make_table() // funkcja wewnętrzna + */ +static void gg_crc32_make_table() +{ + uint32_t h = 1; + unsigned int i, j; + + memset(gg_crc32_table, 0, sizeof(gg_crc32_table)); + + for (i = 128; i; i >>= 1) { + h = (h >> 1) ^ ((h & 1) ? 0xedb88320L : 0); + + for (j = 0; j < 256; j += 2 * i) + gg_crc32_table[i + j] = gg_crc32_table[j] ^ h; + } + + gg_crc32_initialized = 1; +} + +/* + * gg_crc32() + * + * wyznacza sumę kontrolną CRC32 danego bloku danych. + * + * - crc - suma kontrola poprzedniego bloku danych lub 0 jeśli pierwszy + * - buf - bufor danych + * - size - ilość danych + * + * suma kontrolna CRC32. + */ +uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len) +{ + if (!gg_crc32_initialized) + gg_crc32_make_table(); + + if (!buf || len < 0) + return crc; + + crc ^= 0xffffffffL; + + while (len--) + crc = (crc >> 8) ^ gg_crc32_table[(crc ^ *buf++) & 0xff]; + + return crc ^ 0xffffffffL; +} + + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/lib/compat.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/lib/compat.h Sun Aug 28 22:46:01 2005 +0000 @@ -0,0 +1,29 @@ +/* $Id: compat.h 13582 2005-08-28 22:46:01Z boler $ */ + +/* + * (C) Copyright 2001-2002 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 Lesser General Public License Version + * 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef __COMPAT_H +#define __COMPAT_H + +#ifdef sun +# define INADDR_NONE ((in_addr_t) 0xffffffff) +#endif + +#endif diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/lib/dcc.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/lib/dcc.c Sun Aug 28 22:46:01 2005 +0000 @@ -0,0 +1,1296 @@ +/* $Id: dcc.c 13582 2005-08-28 22:46:01Z boler $ */ + +/* + * (C) Copyright 2001-2002 Wojtek Kaniewski + * Tomasz Chiliński + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#include +#include +#include +#include +#ifdef sun +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include "libgadu.h" + +#ifndef GG_DEBUG_DISABLE +/* + * gg_dcc_debug_data() // funkcja wewnętrzna + * + * wyświetla zrzut pakietu w hexie. + * + * - prefix - prefiks zrzutu pakietu + * - fd - deskryptor gniazda + * - buf - bufor z danymi + * - size - rozmiar danych + */ +static void gg_dcc_debug_data(const char *prefix, int fd, const void *buf, unsigned int size) +{ + unsigned int i; + + gg_debug(GG_DEBUG_MISC, "++ gg_dcc %s (fd=%d,len=%d)", prefix, fd, size); + + for (i = 0; i < size; i++) + gg_debug(GG_DEBUG_MISC, " %.2x", ((unsigned char*) buf)[i]); + + gg_debug(GG_DEBUG_MISC, "\n"); +} +#else +#define gg_dcc_debug_data(a,b,c,d) do { } while (0) +#endif + +/* + * gg_dcc_request() + * + * wysyła informację o tym, że dany klient powinien się z nami połączyć. + * wykorzystywane, kiedy druga strona, której chcemy coś wysłać jest za + * maskaradą. + * + * - sess - struktura opisująca sesję GG + * - uin - numerek odbiorcy + * + * patrz gg_send_message_ctcp(). + */ +int gg_dcc_request(struct gg_session *sess, uin_t uin) +{ + return gg_send_message_ctcp(sess, GG_CLASS_CTCP, uin, "\002", 1); +} + +/* + * gg_dcc_fill_filetime() // funkcja wewnętrzna + * + * zamienia czas w postaci unixowej na windowsowy. + * + * - unix - czas w postaci unixowej + * - filetime - czas w postaci windowsowej + */ +void gg_dcc_fill_filetime(uint32_t ut, uint32_t *ft) +{ +#ifdef __GG_LIBGADU_HAVE_LONG_LONG + unsigned long long tmp; + + tmp = ut; + tmp += 11644473600LL; + tmp *= 10000000LL; + +#ifndef __GG_LIBGADU_BIGENDIAN + ft[0] = (uint32_t) tmp; + ft[1] = (uint32_t) (tmp >> 32); +#else + ft[0] = gg_fix32((uint32_t) (tmp >> 32)); + ft[1] = gg_fix32((uint32_t) tmp); +#endif + +#endif +} + +/* + * gg_dcc_fill_file_info() + * + * wypełnia pola struct gg_dcc niezbędne do wysłania pliku. + * + * - d - struktura opisująca połączenie DCC + * - filename - nazwa pliku + * + * 0, -1. + */ +int gg_dcc_fill_file_info(struct gg_dcc *d, const char *filename) +{ + return gg_dcc_fill_file_info2(d, filename, filename); +} + +/* + * gg_dcc_fill_file_info2() + * + * wypełnia pola struct gg_dcc niezbędne do wysłania pliku. + * + * - d - struktura opisująca połączenie DCC + * - filename - nazwa pliku + * - local_filename - nazwa na lokalnym systemie plików + * + * 0, -1. + */ +int gg_dcc_fill_file_info2(struct gg_dcc *d, const char *filename, const char *local_filename) +{ + struct stat st; + const char *name, *ext, *p; + unsigned char *q; + int i, j; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_fill_file_info2(%p, \"%s\", \"%s\");\n", d, filename, local_filename); + + if (!d || d->type != GG_SESSION_DCC_SEND) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() invalid arguments\n"); + errno = EINVAL; + return -1; + } + + if (stat(local_filename, &st) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() stat() failed (%s)\n", strerror(errno)); + return -1; + } + + if ((st.st_mode & S_IFDIR)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() that's a directory\n"); + errno = EINVAL; + return -1; + } + + if ((d->file_fd = open(local_filename, O_RDONLY)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() open() failed (%s)\n", strerror(errno)); + return -1; + } + + memset(&d->file_info, 0, sizeof(d->file_info)); + + if (!(st.st_mode & S_IWUSR)) + d->file_info.mode |= gg_fix32(GG_DCC_FILEATTR_READONLY); + + gg_dcc_fill_filetime(st.st_atime, d->file_info.atime); + gg_dcc_fill_filetime(st.st_mtime, d->file_info.mtime); + gg_dcc_fill_filetime(st.st_ctime, d->file_info.ctime); + + d->file_info.size = gg_fix32(st.st_size); + d->file_info.mode = gg_fix32(0x20); /* FILE_ATTRIBUTE_ARCHIVE */ + + if (!(name = strrchr(filename, '/'))) + name = filename; + else + name++; + + if (!(ext = strrchr(name, '.'))) + ext = name + strlen(name); + + for (i = 0, p = name; i < 8 && p < ext; i++, p++) + d->file_info.short_filename[i] = toupper(name[i]); + + if (i == 8 && p < ext) { + d->file_info.short_filename[6] = '~'; + d->file_info.short_filename[7] = '1'; + } + + if (strlen(ext) > 0) { + for (j = 0; *ext && j < 4; j++, p++) + d->file_info.short_filename[i + j] = toupper(ext[j]); + } + + for (q = d->file_info.short_filename; *q; q++) { + if (*q == 185) { + *q = 165; + } else if (*q == 230) { + *q = 198; + } else if (*q == 234) { + *q = 202; + } else if (*q == 179) { + *q = 163; + } else if (*q == 241) { + *q = 209; + } else if (*q == 243) { + *q = 211; + } else if (*q == 156) { + *q = 140; + } else if (*q == 159) { + *q = 143; + } else if (*q == 191) { + *q = 175; + } + } + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() short name \"%s\", dos name \"%s\"\n", name, d->file_info.short_filename); + strncpy(d->file_info.filename, name, sizeof(d->file_info.filename) - 1); + + return 0; +} + +/* + * gg_dcc_transfer() // funkcja wewnętrzna + * + * inicjuje proces wymiany pliku z danym klientem. + * + * - ip - adres ip odbiorcy + * - port - port odbiorcy + * - my_uin - własny numer + * - peer_uin - numer obiorcy + * - type - rodzaj wymiany (GG_SESSION_DCC_SEND lub GG_SESSION_DCC_GET) + * + * zaalokowana struct gg_dcc lub NULL jeśli wystąpił błąd. + */ +static struct gg_dcc *gg_dcc_transfer(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin, int type) +{ + struct gg_dcc *d = NULL; + struct in_addr addr; + + addr.s_addr = ip; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_transfer(%s, %d, %ld, %ld, %s);\n", inet_ntoa(addr), port, my_uin, peer_uin, (type == GG_SESSION_DCC_SEND) ? "SEND" : "GET"); + + if (!ip || ip == INADDR_NONE || !port || !my_uin || !peer_uin) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() invalid arguments\n"); + errno = EINVAL; + return NULL; + } + + if (!(d = (void*) calloc(1, sizeof(*d)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() not enough memory\n"); + return NULL; + } + + d->check = GG_CHECK_WRITE; + d->state = GG_STATE_CONNECTING; + d->type = type; + d->timeout = GG_DEFAULT_TIMEOUT; + d->file_fd = -1; + d->active = 1; + d->fd = -1; + d->uin = my_uin; + d->peer_uin = peer_uin; + + if ((d->fd = gg_connect(&addr, port, 1)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() connection failed\n"); + free(d); + return NULL; + } + + return d; +} + +/* + * gg_dcc_get_file() + * + * inicjuje proces odbierania pliku od danego klienta, gdy ten wysłał do + * nas żądanie połączenia. + * + * - ip - adres ip odbiorcy + * - port - port odbiorcy + * - my_uin - własny numer + * - peer_uin - numer obiorcy + * + * zaalokowana struct gg_dcc lub NULL jeśli wystąpił błąd. + */ +struct gg_dcc *gg_dcc_get_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin) +{ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_get_file() handing over to gg_dcc_transfer()\n"); + + return gg_dcc_transfer(ip, port, my_uin, peer_uin, GG_SESSION_DCC_GET); +} + +/* + * gg_dcc_send_file() + * + * inicjuje proces wysyłania pliku do danego klienta. + * + * - ip - adres ip odbiorcy + * - port - port odbiorcy + * - my_uin - własny numer + * - peer_uin - numer obiorcy + * + * zaalokowana struct gg_dcc lub NULL jeśli wystąpił błąd. + */ +struct gg_dcc *gg_dcc_send_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin) +{ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_send_file() handing over to gg_dcc_transfer()\n"); + + return gg_dcc_transfer(ip, port, my_uin, peer_uin, GG_SESSION_DCC_SEND); +} + +/* + * gg_dcc_voice_chat() + * + * próbuje nawiązać połączenie głosowe. + * + * - ip - adres ip odbiorcy + * - port - port odbiorcy + * - my_uin - własny numer + * - peer_uin - numer obiorcy + * + * zaalokowana struct gg_dcc lub NULL jeśli wystąpił błąd. + */ +struct gg_dcc *gg_dcc_voice_chat(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin) +{ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_chat() handing over to gg_dcc_transfer()\n"); + + return gg_dcc_transfer(ip, port, my_uin, peer_uin, GG_SESSION_DCC_VOICE); +} + +/* + * gg_dcc_set_type() + * + * po zdarzeniu GG_EVENT_DCC_CALLBACK należy ustawić typ połączenia za + * pomocą tej funkcji. + * + * - d - struktura opisująca połączenie + * - type - typ połączenia (GG_SESSION_DCC_SEND lub GG_SESSION_DCC_VOICE) + */ +void gg_dcc_set_type(struct gg_dcc *d, int type) +{ + d->type = type; + d->state = (type == GG_SESSION_DCC_SEND) ? GG_STATE_SENDING_FILE_INFO : GG_STATE_SENDING_VOICE_REQUEST; +} + +/* + * gg_dcc_callback() // funkcja wewnętrzna + * + * wywoływana z struct gg_dcc->callback, odpala gg_dcc_watch_fd i umieszcza + * rezultat w struct gg_dcc->event. + * + * - d - structura opisująca połączenie + * + * 0, -1. + */ +static int gg_dcc_callback(struct gg_dcc *d) +{ + struct gg_event *e = gg_dcc_watch_fd(d); + + d->event = e; + + return (e != NULL) ? 0 : -1; +} + +/* + * gg_dcc_socket_create() + * + * tworzy gniazdo dla bezpośredniej komunikacji między klientami. + * + * - uin - własny numer + * - port - preferowany port, jeśli równy 0 lub -1, próbuje domyślnego + * + * zaalokowana struct gg_dcc, którą poźniej należy zwolnić funkcją + * gg_dcc_free(), albo NULL jeśli wystąpił błąd. + */ +struct gg_dcc *gg_dcc_socket_create(uin_t uin, uint16_t port) +{ + struct gg_dcc *c; + struct sockaddr_in sin; + int sock, bound = 0, errno2; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_create_dcc_socket(%d, %d);\n", uin, port); + + if (!uin) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() invalid arguments\n"); + errno = EINVAL; + return NULL; + } + + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() can't create socket (%s)\n", strerror(errno)); + return NULL; + } + + if (!port) + port = GG_DEFAULT_DCC_PORT; + + while (!bound) { + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = htons(port); + + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() trying port %d\n", port); + if (!bind(sock, (struct sockaddr*) &sin, sizeof(sin))) + bound = 1; + else { + if (++port == 65535) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() no free port found\n"); + close(sock); + return NULL; + } + } + } + + if (listen(sock, 10)) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() unable to listen (%s)\n", strerror(errno)); + errno2 = errno; + close(sock); + errno = errno2; + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() bound to port %d\n", port); + + if (!(c = malloc(sizeof(*c)))) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() not enough memory for struct\n"); + close(sock); + return NULL; + } + memset(c, 0, sizeof(*c)); + + c->port = c->id = port; + c->fd = sock; + c->type = GG_SESSION_DCC_SOCKET; + c->uin = uin; + c->timeout = -1; + c->state = GG_STATE_LISTENING; + c->check = GG_CHECK_READ; + c->callback = gg_dcc_callback; + c->destroy = gg_dcc_free; + + return c; +} + +/* + * gg_dcc_voice_send() + * + * wysyła ramkę danych dla rozmowy głosowej. + * + * - d - struktura opisująca połączenie dcc + * - buf - bufor z danymi + * - length - rozmiar ramki + * + * 0, -1. + */ +int gg_dcc_voice_send(struct gg_dcc *d, char *buf, int length) +{ + struct packet_s { + uint8_t type; + uint32_t length; + } GG_PACKED; + struct packet_s packet; + + gg_debug(GG_DEBUG_FUNCTION, "++ gg_dcc_voice_send(%p, %p, %d);\n", d, buf, length); + if (!d || !buf || length < 0 || d->type != GG_SESSION_DCC_VOICE) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() invalid argument\n"); + errno = EINVAL; + return -1; + } + + packet.type = 0x03; /* XXX */ + packet.length = gg_fix32(length); + + if (write(d->fd, &packet, sizeof(packet)) < (signed)sizeof(packet)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() write() failed\n"); + return -1; + } + gg_dcc_debug_data("write", d->fd, &packet, sizeof(packet)); + + if (write(d->fd, buf, length) < length) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() write() failed\n"); + return -1; + } + gg_dcc_debug_data("write", d->fd, buf, length); + + return 0; +} + +#define gg_read(fd, buf, size) \ +{ \ + int tmp = read(fd, buf, size); \ + \ + if (tmp < (int) size) { \ + if (tmp == -1) { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno)); \ + } else if (tmp == 0) { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed, connection broken\n"); \ + } else { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (%d bytes, %d needed)\n", tmp, size); \ + } \ + e->type = GG_EVENT_DCC_ERROR; \ + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; \ + return e; \ + } \ + gg_dcc_debug_data("read", fd, buf, size); \ +} + +#define gg_write(fd, buf, size) \ +{ \ + int tmp; \ + gg_dcc_debug_data("write", fd, buf, size); \ + tmp = write(fd, buf, size); \ + if (tmp < (int) size) { \ + if (tmp == -1) { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (errno=%d, %s)\n", errno, strerror(errno)); \ + } else { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%d needed, %d done)\n", size, tmp); \ + } \ + e->type = GG_EVENT_DCC_ERROR; \ + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; \ + return e; \ + } \ +} + +/* + * gg_dcc_watch_fd() + * + * funkcja, którą należy wywołać, gdy coś się zmieni na gg_dcc->fd. + * + * - h - struktura zwrócona przez gg_create_dcc_socket() + * + * zaalokowana struct gg_event lub NULL, jeśli zabrakło pamięci na nią. + */ +struct gg_event *gg_dcc_watch_fd(struct gg_dcc *h) +{ + struct gg_event *e; + int foo; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_watch_fd(%p);\n", h); + + if (!h || (h->type != GG_SESSION_DCC && h->type != GG_SESSION_DCC_SOCKET && h->type != GG_SESSION_DCC_SEND && h->type != GG_SESSION_DCC_GET && h->type != GG_SESSION_DCC_VOICE)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() invalid argument\n"); + errno = EINVAL; + return NULL; + } + + if (!(e = (void*) calloc(1, sizeof(*e)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() not enough memory\n"); + return NULL; + } + + e->type = GG_EVENT_NONE; + + if (h->type == GG_SESSION_DCC_SOCKET) { + struct sockaddr_in sin; + struct gg_dcc *c; + int fd, sin_len = sizeof(sin), one = 1; + + if ((fd = accept(h->fd, (struct sockaddr*) &sin, &sin_len)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() can't accept() new connection (errno=%d, %s)\n", errno, strerror(errno)); + return e; + } + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() new direct connection from %s:%d\n", inet_ntoa(sin.sin_addr), htons(sin.sin_port)); + +#ifdef FIONBIO + if (ioctl(fd, FIONBIO, &one) == -1) { +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() can't set nonblocking (errno=%d, %s)\n", errno, strerror(errno)); + close(fd); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + return e; + } + + if (!(c = (void*) calloc(1, sizeof(*c)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() not enough memory for client data\n"); + + free(e); + close(fd); + return NULL; + } + + c->fd = fd; + c->check = GG_CHECK_READ; + c->state = GG_STATE_READING_UIN_1; + c->type = GG_SESSION_DCC; + c->timeout = GG_DEFAULT_TIMEOUT; + c->file_fd = -1; + c->remote_addr = sin.sin_addr.s_addr; + c->remote_port = ntohs(sin.sin_port); + + e->type = GG_EVENT_DCC_NEW; + e->event.dcc_new = c; + + return e; + } else { + struct gg_dcc_tiny_packet tiny; + struct gg_dcc_small_packet small; + struct gg_dcc_big_packet big; + int size, tmp, res, res_size = sizeof(res); + unsigned int utmp; + char buf[1024], ack[] = "UDAG"; + + struct gg_dcc_file_info_packet { + struct gg_dcc_big_packet big; + struct gg_file_info file_info; + } GG_PACKED; + struct gg_dcc_file_info_packet file_info_packet; + + switch (h->state) { + case GG_STATE_READING_UIN_1: + case GG_STATE_READING_UIN_2: + { + uin_t uin; + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_READING_UIN_%d\n", (h->state == GG_STATE_READING_UIN_1) ? 1 : 2); + + gg_read(h->fd, &uin, sizeof(uin)); + + if (h->state == GG_STATE_READING_UIN_1) { + h->state = GG_STATE_READING_UIN_2; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->peer_uin = gg_fix32(uin); + } else { + h->state = GG_STATE_SENDING_ACK; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + h->uin = gg_fix32(uin); + e->type = GG_EVENT_DCC_CLIENT_ACCEPT; + } + + return e; + } + + case GG_STATE_SENDING_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_SENDING_ACK\n"); + + gg_write(h->fd, ack, 4); + + h->state = GG_STATE_READING_TYPE; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_READING_TYPE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_TYPE\n"); + + gg_read(h->fd, &small, sizeof(small)); + + small.type = gg_fix32(small.type); + + switch (small.type) { + case 0x0003: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() callback\n"); + h->type = GG_SESSION_DCC_SEND; + h->state = GG_STATE_SENDING_FILE_INFO; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + e->type = GG_EVENT_DCC_CALLBACK; + + break; + + case 0x0002: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() dialin\n"); + h->type = GG_SESSION_DCC_GET; + h->state = GG_STATE_READING_REQUEST; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->incoming = 1; + + break; + + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown dcc type (%.4x) from %ld\n", small.type, h->peer_uin); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + } + + return e; + + case GG_STATE_READING_REQUEST: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_REQUEST\n"); + + gg_read(h->fd, &small, sizeof(small)); + + small.type = gg_fix32(small.type); + + switch (small.type) { + case 0x0001: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() file transfer request\n"); + h->state = GG_STATE_READING_FILE_INFO; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + break; + + case 0x0003: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() voice chat request\n"); + h->state = GG_STATE_SENDING_VOICE_ACK; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DCC_TIMEOUT_VOICE_ACK; + h->type = GG_SESSION_DCC_VOICE; + e->type = GG_EVENT_DCC_NEED_VOICE_ACK; + + break; + + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown dcc request (%.4x) from %ld\n", small.type, h->peer_uin); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + } + + return e; + + case GG_STATE_READING_FILE_INFO: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_INFO\n"); + + gg_read(h->fd, &file_info_packet, sizeof(file_info_packet)); + + memcpy(&h->file_info, &file_info_packet.file_info, sizeof(h->file_info)); + + h->file_info.mode = gg_fix32(h->file_info.mode); + h->file_info.size = gg_fix32(h->file_info.size); + + h->state = GG_STATE_SENDING_FILE_ACK; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DCC_TIMEOUT_FILE_ACK; + + e->type = GG_EVENT_DCC_NEED_FILE_ACK; + + return e; + + case GG_STATE_SENDING_FILE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_ACK\n"); + + big.type = gg_fix32(0x0006); /* XXX */ + big.dunno1 = gg_fix32(h->offset); + big.dunno2 = 0; + + gg_write(h->fd, &big, sizeof(big)); + + h->state = GG_STATE_READING_FILE_HEADER; + h->chunk_size = sizeof(big); + h->chunk_offset = 0; + if (!(h->chunk_buf = malloc(sizeof(big)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory\n"); + free(e); + return NULL; + } + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_SENDING_VOICE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_VOICE_ACK\n"); + + tiny.type = 0x01; /* XXX */ + + gg_write(h->fd, &tiny, sizeof(tiny)); + + h->state = GG_STATE_READING_VOICE_HEADER; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + h->offset = 0; + + return e; + + case GG_STATE_READING_FILE_HEADER: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_HEADER\n"); + + tmp = read(h->fd, h->chunk_buf + h->chunk_offset, h->chunk_size - h->chunk_offset); + + if (tmp == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + gg_dcc_debug_data("read", h->fd, h->chunk_buf + h->chunk_offset, h->chunk_size - h->chunk_offset); + + h->chunk_offset += tmp; + + if (h->chunk_offset < h->chunk_size) + return e; + + memcpy(&big, h->chunk_buf, sizeof(big)); + free(h->chunk_buf); + h->chunk_buf = NULL; + + big.type = gg_fix32(big.type); + h->chunk_size = gg_fix32(big.dunno1); + h->chunk_offset = 0; + + if (big.type == 0x0005) { /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() transfer refused\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_REFUSED; + return e; + } + + if (h->chunk_size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() empty chunk, EOF\n"); + e->type = GG_EVENT_DCC_DONE; + return e; + } + + h->state = GG_STATE_GETTING_FILE; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->established = 1; + + return e; + + case GG_STATE_READING_VOICE_HEADER: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_HEADER\n"); + + gg_read(h->fd, &tiny, sizeof(tiny)); + + switch (tiny.type) { + case 0x03: /* XXX */ + h->state = GG_STATE_READING_VOICE_SIZE; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->established = 1; + break; + case 0x04: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() peer breaking connection\n"); + /* XXX zwracać odpowiedni event */ + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown request (%.2x)\n", tiny.type); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + } + + return e; + + case GG_STATE_READING_VOICE_SIZE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_SIZE\n"); + + gg_read(h->fd, &small, sizeof(small)); + + small.type = gg_fix32(small.type); + + if (small.type < 16 || small.type > sizeof(buf)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() invalid voice frame size (%d)\n", small.type); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + + return e; + } + + h->chunk_size = small.type; + h->chunk_offset = 0; + + if (!(h->voice_buf = malloc(h->chunk_size))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory for voice frame\n"); + return NULL; + } + + h->state = GG_STATE_READING_VOICE_DATA; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_READING_VOICE_DATA: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_DATA\n"); + + tmp = read(h->fd, h->voice_buf + h->chunk_offset, h->chunk_size - h->chunk_offset); + if (tmp < 1) { + if (tmp == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno)); + } else { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed, connection broken\n"); + } + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + gg_dcc_debug_data("read", h->fd, h->voice_buf + h->chunk_offset, tmp); + + h->chunk_offset += tmp; + + if (h->chunk_offset >= h->chunk_size) { + e->type = GG_EVENT_DCC_VOICE_DATA; + e->event.dcc_voice_data.data = h->voice_buf; + e->event.dcc_voice_data.length = h->chunk_size; + h->state = GG_STATE_READING_VOICE_HEADER; + h->voice_buf = NULL; + } + + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_CONNECTING: + { + uin_t uins[2]; + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_CONNECTING\n"); + + res = 0; + if ((foo = getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &res, &res_size)) || res) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() connection failed (fd=%d,errno=%d(%s),foo=%d,res=%d(%s))\n", h->fd, errno, strerror(errno), foo, res, strerror(res)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + return e; + } + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() connected, sending uins\n"); + + uins[0] = gg_fix32(h->uin); + uins[1] = gg_fix32(h->peer_uin); + + gg_write(h->fd, uins, sizeof(uins)); + + h->state = GG_STATE_READING_ACK; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + } + + case GG_STATE_READING_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_ACK\n"); + + gg_read(h->fd, buf, 4); + + if (strncmp(buf, ack, 4)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() did't get ack\n"); + + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + return e; + } + + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + h->state = GG_STATE_SENDING_REQUEST; + + return e; + + case GG_STATE_SENDING_VOICE_REQUEST: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_VOICE_REQUEST\n"); + + small.type = gg_fix32(0x0003); + + gg_write(h->fd, &small, sizeof(small)); + + h->state = GG_STATE_READING_VOICE_ACK; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_SENDING_REQUEST: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_REQUEST\n"); + + small.type = (h->type == GG_SESSION_DCC_GET) ? gg_fix32(0x0003) : gg_fix32(0x0002); /* XXX */ + + gg_write(h->fd, &small, sizeof(small)); + + switch (h->type) { + case GG_SESSION_DCC_GET: + h->state = GG_STATE_READING_REQUEST; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + break; + + case GG_SESSION_DCC_SEND: + h->state = GG_STATE_SENDING_FILE_INFO; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + if (h->file_fd == -1) + e->type = GG_EVENT_DCC_NEED_FILE_INFO; + break; + + case GG_SESSION_DCC_VOICE: + h->state = GG_STATE_SENDING_VOICE_REQUEST; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + return e; + + case GG_STATE_SENDING_FILE_INFO: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_INFO\n"); + + if (h->file_fd == -1) { + e->type = GG_EVENT_DCC_NEED_FILE_INFO; + return e; + } + + small.type = gg_fix32(0x0001); /* XXX */ + + gg_write(h->fd, &small, sizeof(small)); + + file_info_packet.big.type = gg_fix32(0x0003); /* XXX */ + file_info_packet.big.dunno1 = 0; + file_info_packet.big.dunno2 = 0; + + memcpy(&file_info_packet.file_info, &h->file_info, sizeof(h->file_info)); + + /* zostają teraz u nas, więc odwracamy z powrotem */ + h->file_info.size = gg_fix32(h->file_info.size); + h->file_info.mode = gg_fix32(h->file_info.mode); + + gg_write(h->fd, &file_info_packet, sizeof(file_info_packet)); + + h->state = GG_STATE_READING_FILE_ACK; + h->check = GG_CHECK_READ; + h->timeout = GG_DCC_TIMEOUT_FILE_ACK; + + return e; + + case GG_STATE_READING_FILE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_ACK\n"); + + gg_read(h->fd, &big, sizeof(big)); + + /* XXX sprawdzać wynik */ + h->offset = gg_fix32(big.dunno1); + + h->state = GG_STATE_SENDING_FILE_HEADER; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + e->type = GG_EVENT_DCC_ACK; + + return e; + + case GG_STATE_READING_VOICE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_ACK\n"); + + gg_read(h->fd, &tiny, sizeof(tiny)); + + if (tiny.type != 0x01) { + gg_debug(GG_DEBUG_MISC, "// invalid reply (%.2x), connection refused\n", tiny.type); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_REFUSED; + return e; + } + + h->state = GG_STATE_READING_VOICE_HEADER; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + e->type = GG_EVENT_DCC_ACK; + + return e; + + case GG_STATE_SENDING_FILE_HEADER: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_HEADER\n"); + + h->chunk_offset = 0; + + if ((h->chunk_size = h->file_info.size - h->offset) > 4096) { + h->chunk_size = 4096; + big.type = gg_fix32(0x0003); /* XXX */ + } else + big.type = gg_fix32(0x0002); /* XXX */ + + big.dunno1 = gg_fix32(h->chunk_size); + big.dunno2 = 0; + + gg_write(h->fd, &big, sizeof(big)); + + h->state = GG_STATE_SENDING_FILE; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + h->established = 1; + + return e; + + case GG_STATE_SENDING_FILE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE\n"); + + if ((utmp = h->chunk_size - h->chunk_offset) > sizeof(buf)) + utmp = sizeof(buf); + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() offset=%d, size=%d\n", h->offset, h->file_info.size); + + /* koniec pliku? */ + if (h->file_info.size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof on empty file\n"); + e->type = GG_EVENT_DCC_DONE; + + return e; + } + + lseek(h->file_fd, h->offset, SEEK_SET); + + size = read(h->file_fd, buf, utmp); + + /* błąd */ + if (size == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed. (errno=%d, %s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_FILE; + + return e; + } + + /* koniec pliku? */ + if (size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_EOF; + + return e; + } + + /* jeśli wczytaliśmy więcej, utnijmy. */ + if (h->offset + size > h->file_info.size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() too much (read=%d, ofs=%d, size=%d)\n", size, h->offset, h->file_info.size); + size = h->file_info.size - h->offset; + + if (size < 1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() reached EOF after cutting\n"); + e->type = GG_EVENT_DCC_DONE; + return e; + } + } + + tmp = write(h->fd, buf, size); + + if (tmp == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%s)\n", strerror(errno)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + h->offset += size; + + if (h->offset >= h->file_info.size) { + e->type = GG_EVENT_DCC_DONE; + return e; + } + + h->chunk_offset += size; + + if (h->chunk_offset >= h->chunk_size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() chunk finished\n"); + h->state = GG_STATE_SENDING_FILE_HEADER; + h->timeout = GG_DEFAULT_TIMEOUT; + } else { + h->state = GG_STATE_SENDING_FILE; + h->timeout = GG_DCC_TIMEOUT_SEND; + } + + h->check = GG_CHECK_WRITE; + + return e; + + case GG_STATE_GETTING_FILE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_GETTING_FILE\n"); + + if ((utmp = h->chunk_size - h->chunk_offset) > sizeof(buf)) + utmp = sizeof(buf); + + size = read(h->fd, buf, utmp); + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() ofs=%d, size=%d, read()=%d\n", h->offset, h->file_info.size, size); + + /* błąd */ + if (size == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed. (errno=%d, %s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + + return e; + } + + /* koniec? */ + if (size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_EOF; + + return e; + } + + tmp = write(h->file_fd, buf, size); + + if (tmp == -1 || tmp < size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%d:fd=%d:res=%d:%s)\n", tmp, h->file_fd, size, strerror(errno)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + h->offset += size; + + if (h->offset >= h->file_info.size) { + e->type = GG_EVENT_DCC_DONE; + return e; + } + + h->chunk_offset += size; + + if (h->chunk_offset >= h->chunk_size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() chunk finished\n"); + h->state = GG_STATE_READING_FILE_HEADER; + h->timeout = GG_DEFAULT_TIMEOUT; + h->chunk_offset = 0; + h->chunk_size = sizeof(big); + if (!(h->chunk_buf = malloc(sizeof(big)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory\n"); + free(e); + return NULL; + } + } else { + h->state = GG_STATE_GETTING_FILE; + h->timeout = GG_DCC_TIMEOUT_GET; + } + + h->check = GG_CHECK_READ; + + return e; + + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_???\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + + return e; + } + } + + return e; +} + +#undef gg_read +#undef gg_write + +/* + * gg_dcc_free() + * + * zwalnia pamięć po strukturze połączenia dcc. + * + * - d - zwalniana struktura + */ +void gg_dcc_free(struct gg_dcc *d) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_free(%p);\n", d); + + if (!d) + return; + + if (d->fd != -1) + close(d->fd); + + if (d->chunk_buf) { + free(d->chunk_buf); + d->chunk_buf = NULL; + } + + free(d); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/lib/events.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/lib/events.c Sun Aug 28 22:46:01 2005 +0000 @@ -0,0 +1,1566 @@ +/* $Id: events.c 13582 2005-08-28 22:46:01Z boler $ */ + +/* + * (C) Copyright 2001-2003 Wojtek Kaniewski + * Robert J. Woźny + * Arkadiusz Miśkiewicz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "libgadu-config.h" + +#include +#ifdef __GG_LIBGADU_HAVE_PTHREAD +# include +#endif +#include +#include +#include +#include +#include +#ifdef __GG_LIBGADU_HAVE_OPENSSL +# include +# include +#endif + +#include "compat.h" +#include "libgadu.h" + +/* + * gg_event_free() + * + * zwalnia pamięć zajmowaną przez informację o zdarzeniu. + * + * - e - wskaźnik do informacji o zdarzeniu + */ +void gg_event_free(struct gg_event *e) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_event_free(%p);\n", e); + + if (!e) + return; + + switch (e->type) { + case GG_EVENT_MSG: + free(e->event.msg.message); + free(e->event.msg.formats); + free(e->event.msg.recipients); + break; + + case GG_EVENT_NOTIFY: + free(e->event.notify); + break; + + case GG_EVENT_NOTIFY60: + { + int i; + + for (i = 0; e->event.notify60[i].uin; i++) + free(e->event.notify60[i].descr); + + free(e->event.notify60); + + break; + } + + case GG_EVENT_STATUS60: + free(e->event.status60.descr); + break; + + case GG_EVENT_STATUS: + free(e->event.status.descr); + break; + + case GG_EVENT_NOTIFY_DESCR: + free(e->event.notify_descr.notify); + free(e->event.notify_descr.descr); + break; + + case GG_EVENT_DCC_VOICE_DATA: + free(e->event.dcc_voice_data.data); + break; + + case GG_EVENT_PUBDIR50_SEARCH_REPLY: + case GG_EVENT_PUBDIR50_READ: + case GG_EVENT_PUBDIR50_WRITE: + gg_pubdir50_free(e->event.pubdir50); + break; + + case GG_EVENT_USERLIST: + free(e->event.userlist.reply); + break; + + case GG_EVENT_IMAGE_REPLY: + free(e->event.image_reply.filename); + free(e->event.image_reply.image); + break; + } + + free(e); +} + +/* + * gg_image_queue_remove() + * + * usuwa z kolejki dany wpis. + * + * - s - sesja + * - q - kolejka + * - freeq - czy zwolnić kolejkę + * + * 0/-1 + */ +int gg_image_queue_remove(struct gg_session *s, struct gg_image_queue *q, int freeq) +{ + if (!s || !q) { + errno = EFAULT; + return -1; + } + + if (s->images == q) + s->images = q->next; + else { + struct gg_image_queue *qq; + + for (qq = s->images; qq; qq = qq->next) { + if (qq->next == q) { + qq->next = q->next; + break; + } + } + } + + if (freeq) { + free(q->image); + free(q->filename); + free(q); + } + + return 0; +} + +/* + * gg_image_queue_parse() // funkcja wewnętrzna + * + * parsuje przychodzący pakiet z obrazkiem. + * + * - e - opis zdarzenia + * - + */ +static void gg_image_queue_parse(struct gg_event *e, char *p, unsigned int len, struct gg_session *sess, uin_t sender) +{ + struct gg_msg_image_reply *i = (void*) p; + struct gg_image_queue *q, *qq; + + if (!p || !sess || !e) { + errno = EFAULT; + return; + } + + /* znajdź dany obrazek w kolejce danej sesji */ + + for (qq = sess->images, q = NULL; qq; qq = qq->next) { + if (sender == qq->sender && i->size == qq->size && i->crc32 == qq->crc32) { + q = qq; + break; + } + } + + if (!q) { + gg_debug(GG_DEBUG_MISC, "// gg_image_queue_parse() unknown image from %d, size=%d, crc32=%.8x\n", sender, i->size, i->crc32); + return; + } + + if (p[0] == 0x05) { + int i, ok = 0; + + q->done = 0; + + len -= sizeof(struct gg_msg_image_reply); + p += sizeof(struct gg_msg_image_reply); + + /* sprawdź, czy mamy tekst zakończony \0 */ + + for (i = 0; i < len; i++) { + if (!p[i]) { + ok = 1; + break; + } + } + + if (!ok) { + gg_debug(GG_DEBUG_MISC, "// gg_image_queue_parse() malformed packet from %d, unlimited filename\n", sender); + return; + } + + if (!(q->filename = strdup(p))) { + gg_debug(GG_DEBUG_MISC, "// gg_image_queue_parse() not enough memory for filename\n"); + return; + } + + len -= strlen(p) + 1; + p += strlen(p) + 1; + } else { + len -= sizeof(struct gg_msg_image_reply); + p += sizeof(struct gg_msg_image_reply); + } + + if (q->done + len > q->size) + len = q->size - q->done; + + memcpy(q->image + q->done, p, len); + q->done += len; + + /* jeśli skończono odbierać obrazek, wygeneruj zdarzenie */ + + if (q->done >= q->size) { + e->type = GG_EVENT_IMAGE_REPLY; + e->event.image_reply.sender = sender; + e->event.image_reply.size = q->size; + e->event.image_reply.crc32 = q->crc32; + e->event.image_reply.filename = q->filename; + e->event.image_reply.image = q->image; + + gg_image_queue_remove(sess, q, 0); + + free(q); + } +} + +/* + * gg_handle_recv_msg() // funkcja wewnętrzna + * + * obsługuje pakiet z przychodzącą wiadomością, rozbijając go na dodatkowe + * struktury (konferencje, kolorki) w razie potrzeby. + * + * - h - nagłówek pakietu + * - e - opis zdarzenia + * + * 0, -1. + */ +static int gg_handle_recv_msg(struct gg_header *h, struct gg_event *e, struct gg_session *sess) +{ + struct gg_recv_msg *r = (struct gg_recv_msg*) ((char*) h + sizeof(struct gg_header)); + char *p, *packet_end = (char*) r + h->length; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_handle_recv_msg(%p, %p);\n", h, e); + + if (!r->seq && !r->msgclass) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() oops, silently ignoring the bait\n"); + e->type = GG_EVENT_NONE; + return 0; + } + + for (p = (char*) r + sizeof(*r); *p; p++) { + if (*p == 0x02 && p == packet_end - 1) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() received ctcp packet\n"); + break; + } + if (p >= packet_end) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() malformed packet, message out of bounds (0)\n"); + goto malformed; + } + } + + p++; + + /* przeanalizuj dodatkowe opcje */ + while (p < packet_end) { + switch (*p) { + case 0x01: /* konferencja */ + { + struct gg_msg_recipients *m = (void*) p; + uint32_t i, count; + + p += sizeof(*m); + + if (p > packet_end) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (1)\n"); + goto malformed; + } + + count = gg_fix32(m->count); + + if (p + count * sizeof(uin_t) > packet_end || p + count * sizeof(uin_t) < p || count > 0xffff) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (1.5)\n"); + goto malformed; + } + + if (!(e->event.msg.recipients = (void*) malloc(count * sizeof(uin_t)))) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() not enough memory for recipients data\n"); + goto fail; + } + + for (i = 0; i < count; i++, p += sizeof(uint32_t)) { + uint32_t u; + memcpy(&u, p, sizeof(uint32_t)); + e->event.msg.recipients[i] = gg_fix32(u); + } + + e->event.msg.recipients_count = count; + + break; + } + + case 0x02: /* richtext */ + { + uint16_t len; + char *buf; + + if (p + 3 > packet_end) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (2)\n"); + goto malformed; + } + + memcpy(&len, p + 1, sizeof(uint16_t)); + len = gg_fix16(len); + + if (!(buf = malloc(len))) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() not enough memory for richtext data\n"); + goto fail; + } + + p += 3; + + if (p + len > packet_end) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (3)\n"); + free(buf); + goto malformed; + } + + memcpy(buf, p, len); + + e->event.msg.formats = buf; + e->event.msg.formats_length = len; + + p += len; + + break; + } + + case 0x04: /* image_request */ + { + struct gg_msg_image_request *i = (void*) p; + + if (p + sizeof(*i) > packet_end) { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (3)\n"); + goto malformed; + } + + e->event.image_request.sender = gg_fix32(r->sender); + e->event.image_request.size = gg_fix32(i->size); + e->event.image_request.crc32 = gg_fix32(i->crc32); + + e->type = GG_EVENT_IMAGE_REQUEST; + + return 0; + } + + case 0x05: /* image_reply */ + case 0x06: + { + struct gg_msg_image_reply *rep = (void*) p; + + if (p + sizeof(struct gg_msg_image_reply) == packet_end) { + + /* pusta odpowiedź - klient po drugiej stronie nie ma żądanego obrazka */ + + e->type = GG_EVENT_IMAGE_REPLY; + e->event.image_reply.sender = gg_fix32(r->sender); + e->event.image_reply.size = 0; + e->event.image_reply.crc32 = gg_fix32(rep->crc32); + e->event.image_reply.filename = NULL; + e->event.image_reply.image = NULL; + return 0; + + } else if (p + sizeof(struct gg_msg_image_reply) + 1 > packet_end) { + + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (4)\n"); + goto malformed; + } + + rep->size = gg_fix32(rep->size); + rep->crc32 = gg_fix32(rep->crc32); + gg_image_queue_parse(e, p, (unsigned int)(packet_end - p), sess, gg_fix32(r->sender)); + + return 0; + } + + default: + { + gg_debug(GG_DEBUG_MISC, "// gg_handle_recv_msg() unknown payload 0x%.2x\n", *p); + p = packet_end; + } + } + } + + e->type = GG_EVENT_MSG; + e->event.msg.msgclass = gg_fix32(r->msgclass); + e->event.msg.sender = gg_fix32(r->sender); + e->event.msg.time = gg_fix32(r->time); + e->event.msg.message = strdup((char*) r + sizeof(*r)); + + return 0; + +malformed: + e->type = GG_EVENT_NONE; + + free(e->event.msg.recipients); + free(e->event.msg.formats); + + return 0; + +fail: + free(e->event.msg.recipients); + free(e->event.msg.formats); + return -1; +} + +/* + * gg_watch_fd_connected() // funkcja wewnętrzna + * + * patrzy na gniazdo, odbiera pakiet i wypełnia strukturę zdarzenia. + * + * - sess - struktura opisująca sesję + * - e - opis zdarzenia + * + * 0, -1. + */ +static int gg_watch_fd_connected(struct gg_session *sess, struct gg_event *e) +{ + struct gg_header *h = NULL; + char *p; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_watch_fd_connected(%p, %p);\n", sess, e); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (!(h = gg_recv_packet(sess))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() gg_recv_packet failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + + p = (char*) h + sizeof(struct gg_header); + + switch (h->type) { + case GG_RECV_MSG: + { + if (h->length >= sizeof(struct gg_recv_msg)) + if (gg_handle_recv_msg(h, e, sess)) + goto fail; + + break; + } + + case GG_NOTIFY_REPLY: + { + struct gg_notify_reply *n = (void*) p; + unsigned int count, i; + char *tmp; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); + + if (h->length < sizeof(*n)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() incomplete packet\n"); + errno = EINVAL; + goto fail; + } + + if (gg_fix32(n->status) == GG_STATUS_BUSY_DESCR || gg_fix32(n->status) == GG_STATUS_NOT_AVAIL_DESCR || gg_fix32(n->status) == GG_STATUS_AVAIL_DESCR) { + e->type = GG_EVENT_NOTIFY_DESCR; + + if (!(e->event.notify_descr.notify = (void*) malloc(sizeof(*n) * 2))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + e->event.notify_descr.notify[1].uin = 0; + memcpy(e->event.notify_descr.notify, p, sizeof(*n)); + e->event.notify_descr.notify[0].uin = gg_fix32(e->event.notify_descr.notify[0].uin); + e->event.notify_descr.notify[0].status = gg_fix32(e->event.notify_descr.notify[0].status); + e->event.notify_descr.notify[0].remote_ip = e->event.notify_descr.notify[0].remote_ip; + e->event.notify_descr.notify[0].remote_port = gg_fix16(e->event.notify_descr.notify[0].remote_port); + + count = h->length - sizeof(*n); + if (!(tmp = malloc(count + 1))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + memcpy(tmp, p + sizeof(*n), count); + tmp[count] = 0; + e->event.notify_descr.descr = tmp; + + } else { + e->type = GG_EVENT_NOTIFY; + + if (!(e->event.notify = (void*) malloc(h->length + 2 * sizeof(*n)))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + memcpy(e->event.notify, p, h->length); + count = h->length / sizeof(*n); + e->event.notify[count].uin = 0; + + for (i = 0; i < count; i++) { + e->event.notify[i].uin = gg_fix32(e->event.notify[i].uin); + e->event.notify[i].status = gg_fix32(e->event.notify[i].status); + e->event.notify[i].remote_ip = e->event.notify[i].remote_ip; + e->event.notify[i].remote_port = gg_fix16(e->event.notify[i].remote_port); + } + } + + break; + } + + case GG_STATUS: + { + struct gg_status *s = (void*) p; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); + + if (h->length >= sizeof(*s)) { + e->type = GG_EVENT_STATUS; + memcpy(&e->event.status, p, sizeof(*s)); + e->event.status.uin = gg_fix32(e->event.status.uin); + e->event.status.status = gg_fix32(e->event.status.status); + if (h->length > sizeof(*s)) { + int len = h->length - sizeof(*s); + char *buf = malloc(len + 1); + if (buf) { + memcpy(buf, p + sizeof(*s), len); + buf[len] = 0; + } + e->event.status.descr = buf; + } else + e->event.status.descr = NULL; + } + + 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 = gg_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 = gg_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 (uin & 0x40000000) + e->event.notify60[i].version |= GG_HAS_AUDIO_MASK; + if (uin & 0x08000000) + e->event.notify60[i].version |= GG_ERA_OMNIX_MASK; + + 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_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 = gg_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 = gg_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 (uin & 0x08000000) + e->event.status60.version |= GG_ERA_OMNIX_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) { + uint32_t t; + memcpy(&t, p + h->length - 4, sizeof(uint32_t)); + e->event.status60.time = gg_fix32(t); + } + } + + break; + } + + case GG_SEND_MSG_ACK: + { + struct gg_send_msg_ack *s = (void*) p; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a message ack\n"); + + if (h->length < sizeof(*s)) + break; + + e->type = GG_EVENT_ACK; + e->event.ack.status = gg_fix32(s->status); + e->event.ack.recipient = gg_fix32(s->recipient); + e->event.ack.seq = gg_fix32(s->seq); + + break; + } + + case GG_PONG: + { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received a pong\n"); + + e->type = GG_EVENT_PONG; + sess->last_pong = time(NULL); + + break; + } + + case GG_DISCONNECTING: + { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received disconnection warning\n"); + e->type = GG_EVENT_DISCONNECT; + break; + } + + case GG_PUBDIR50_REPLY: + { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd_connected() received pubdir/search reply\n"); + if (gg_pubdir50_handle_reply(e, p, h->length) == -1) + goto fail; + 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; + unsigned 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, którą należy wywołać, gdy coś się stanie z obserwowanym + * deskryptorem. zwraca klientowi informację o tym, co się dzieje. + * + * - sess - opis sesji + * + * wskaźnik do struktury gg_event, którą trzeba zwolnić później + * za pomocą gg_event_free(). jesli rodzaj zdarzenia jest równy + * GG_EVENT_NONE, należy je zignorować. jeśli zwróciło NULL, + * stało się coś niedobrego -- albo zabrakło pamięci albo zerwało + * połączenie. + */ +struct gg_event *gg_watch_fd(struct gg_session *sess) +{ + struct gg_event *e; + int res = 0; + int port = 0; + int errno2 = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_watch_fd(%p);\n", sess); + + if (!sess) { + errno = EFAULT; + return NULL; + } + + if (!(e = (void*) calloc(1, sizeof(*e)))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for event data\n"); + return NULL; + } + + e->type = GG_EVENT_NONE; + + switch (sess->state) { + case GG_STATE_RESOLVING: + { + struct in_addr addr; + int failed = 0; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_RESOLVING\n"); + + if (read(sess->fd, &addr, sizeof(addr)) < (signed)sizeof(addr) || addr.s_addr == INADDR_NONE) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() resolving failed\n"); + failed = 1; + errno2 = errno; + } + + close(sess->fd); + sess->fd = -1; + +#ifndef __GG_LIBGADU_HAVE_PTHREAD + waitpid(sess->pid, NULL, 0); + sess->pid = -1; +#else + if (sess->resolver) { + pthread_cancel(*((pthread_t*) sess->resolver)); + free(sess->resolver); + sess->resolver = NULL; + } +#endif + + if (failed) { + errno = errno2; + goto fail_resolving; + } + + /* jeśli jesteśmy w resolverze i mamy ustawiony port + * proxy, znaczy, że resolvowaliśmy proxy. zatem + * wpiszmy jego adres. */ + if (sess->proxy_port) + sess->proxy_addr = addr.s_addr; + + /* zapiszmy sobie adres huba i adres serwera (do + * bezpośredniego połączenia, jeśli hub leży) + * z resolvera. */ + if (sess->proxy_addr && sess->proxy_port) + port = sess->proxy_port; + else { + sess->server_addr = sess->hub_addr = addr.s_addr; + port = GG_APPMSG_PORT; + } + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() resolved, connecting to %s:%d\n", inet_ntoa(addr), port); + + /* łączymy się albo z hubem, albo z proxy, zależnie + * od tego, co resolvowaliśmy. */ + if ((sess->fd = gg_connect(&addr, port, sess->async)) == -1) { + /* jeśli w trybie asynchronicznym gg_connect() + * zwróci błąd, nie ma sensu próbować dalej. */ + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), critical\n", errno, strerror(errno)); + goto fail_connecting; + } + + /* jeśli podano serwer i łączmy się przez proxy, + * jest to bezpośrednie połączenie, inaczej jest + * do huba. */ + sess->state = (sess->proxy_addr && sess->proxy_port && sess->server_addr) ? GG_STATE_CONNECTING_GG : GG_STATE_CONNECTING_HUB; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + + case GG_STATE_CONNECTING_HUB: + { + char buf[1024], *client, *auth; + int res = 0, res_size = sizeof(res); + const char *host, *appmsg; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_HUB\n"); + + /* jeśli asynchroniczne, sprawdzamy, czy nie wystąpił + * przypadkiem jakiś błąd. */ + if (sess->async && (getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + /* no tak, nie udało się połączyć z proxy. nawet + * nie próbujemy dalej. */ + if (sess->proxy_addr && sess->proxy_port) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", res, strerror(res)); + goto fail_connecting; + } + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to hub failed (errno=%d, %s), trying direct connection\n", res, strerror(res)); + close(sess->fd); + + if ((sess->fd = gg_connect(&sess->hub_addr, GG_DEFAULT_PORT, sess->async)) == -1) { + /* przy asynchronicznych, gg_connect() + * zwraca -1 przy błędach socket(), + * ioctl(), braku routingu itd. dlatego + * nawet nie próbujemy dalej. */ + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() direct connection failed (errno=%d, %s), critical\n", errno, strerror(errno)); + goto fail_connecting; + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connected to hub, sending query\n"); + + if (!(client = gg_urlencode((sess->client_version) ? sess->client_version : GG_DEFAULT_CLIENT_VERSION))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() out of memory for client version\n"); + goto fail_connecting; + } + + if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port) + host = "http://" GG_APPMSG_HOST; + else + host = ""; + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) + appmsg = "appmsg3.asp"; + else +#endif + appmsg = "appmsg2.asp"; + + auth = gg_proxy_auth(); + + snprintf(buf, sizeof(buf) - 1, + "GET %s/appsvc/%s?fmnumber=%u&version=%s&lastmsg=%d HTTP/1.0\r\n" + "Host: " GG_APPMSG_HOST "\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Pragma: no-cache\r\n" + "%s" + "\r\n", host, appmsg, sess->uin, client, sess->last_sysmsg, (auth) ? auth : ""); + + if (auth) + free(auth); + + free(client); + + /* zwolnij pamięć po wersji klienta. */ + if (sess->client_version) { + free(sess->client_version); + sess->client_version = NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", buf); + + /* zapytanie jest krótkie, więc zawsze zmieści się + * do bufora gniazda. jeśli write() zwróci mniej, + * stało się coś złego. */ + if (write(sess->fd, buf, strlen(buf)) < (signed)strlen(buf)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() sending query failed\n"); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_WRITING; + sess->state = GG_STATE_IDLE; + close(sess->fd); + sess->fd = -1; + break; + } + + sess->state = GG_STATE_READING_DATA; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + + case GG_STATE_READING_DATA: + { + char buf[1024], *tmp, *host; + int port = GG_DEFAULT_PORT; + struct in_addr addr; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_DATA\n"); + + /* czytamy linię z gniazda i obcinamy \r\n. */ + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + gg_debug(GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http header (%s)\n", buf); + + /* sprawdzamy, czy wszystko w porządku. */ + if (strncmp(buf, "HTTP/1.", 7) || strncmp(buf + 9, "200", 3)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() that's not what we've expected, trying direct connection\n"); + + close(sess->fd); + + /* jeśli otrzymaliśmy jakieś dziwne informacje, + * próbujemy się łączyć z pominięciem huba. */ + if (sess->proxy_addr && sess->proxy_port) { + if ((sess->fd = gg_connect(&sess->proxy_addr, sess->proxy_port, sess->async)) == -1) { + /* trudno. nie wyszło. */ + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + sess->port = GG_DEFAULT_PORT; + + /* łączymy się na port 8074 huba. */ + if ((sess->fd = gg_connect(&sess->hub_addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno)); + + sess->port = GG_HTTPS_PORT; + + /* łączymy się na port 443. */ + if ((sess->fd = gg_connect(&sess->hub_addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + /* ignorujemy resztę nagłówka. */ + while (strcmp(buf, "\r\n") && strcmp(buf, "")) + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + + /* czytamy pierwszą linię danych. */ + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + + /* jeśli pierwsza liczba w linii nie jest równa zeru, + * oznacza to, że mamy wiadomość systemową. */ + if (atoi(buf)) { + char tmp[1024], *foo, *sysmsg_buf = NULL; + int len = 0; + + while (gg_read_line(sess->fd, tmp, sizeof(tmp) - 1)) { + if (!(foo = realloc(sysmsg_buf, len + strlen(tmp) + 2))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() out of memory for system message, ignoring\n"); + break; + } + + sysmsg_buf = foo; + + if (!len) + strcpy(sysmsg_buf, tmp); + else + strcat(sysmsg_buf, tmp); + + len += strlen(tmp); + } + + e->type = GG_EVENT_MSG; + e->event.msg.msgclass = atoi(buf); + e->event.msg.sender = 0; + e->event.msg.message = sysmsg_buf; + } + + close(sess->fd); + + gg_debug(GG_DEBUG_TRAFFIC, "// gg_watch_fd() 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); + } + + addr.s_addr = inet_addr(host); + sess->server_addr = addr.s_addr; + + if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port) { + /* jeśli mamy proxy, łączymy się z nim. */ + if ((sess->fd = gg_connect(&sess->proxy_addr, sess->proxy_port, sess->async)) == -1) { + /* nie wyszło? trudno. */ + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + sess->port = port; + + /* łączymy się z właściwym serwerem. */ + if ((sess->fd = gg_connect(&addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno)); + + sess->port = GG_HTTPS_PORT; + + /* nie wyszło? próbujemy portu 443. */ + if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, sess->async)) == -1) { + /* ostatnia deska ratunku zawiodła? + * w takim razie zwijamy manatki. */ + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + + case GG_STATE_CONNECTING_GG: + { + int res = 0, res_size = sizeof(res); + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_GG\n"); + + /* jeśli wystąpił błąd podczas łączenia się... */ + if (sess->async && (sess->timeout == 0 || getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + /* jeśli nie udało się połączenie z proxy, + * nie mamy czego próbować więcej. */ + if (sess->proxy_addr && sess->proxy_port) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", res, strerror(res)); + goto fail_connecting; + } + + close(sess->fd); + sess->fd = -1; + +#ifdef ETIMEDOUT + if (sess->timeout == 0) + errno = ETIMEDOUT; +#endif + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + /* jeśli logujemy się po TLS, nie próbujemy + * się łączyć już z niczym innym w przypadku + * błędu. nie dość, że nie ma sensu, to i + * trzeba by się bawić w tworzenie na nowo + * SSL i SSL_CTX. */ + + if (sess->ssl) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res)); + goto fail_connecting; + } +#endif + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", res, strerror(res)); + + sess->port = GG_HTTPS_PORT; + + /* próbujemy na port 443. */ + if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + } + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() connected\n"); + + if (gg_proxy_http_only) + sess->proxy_port = 0; + + /* jeśli mamy proxy, wyślijmy zapytanie. */ + if (sess->proxy_addr && sess->proxy_port) { + char buf[100], *auth = gg_proxy_auth(); + struct in_addr addr; + + if (sess->server_addr) + addr.s_addr = sess->server_addr; + else + addr.s_addr = sess->hub_addr; + + snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\r\n", inet_ntoa(addr), sess->port); + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() proxy request:\n// %s", buf); + + /* wysyłamy zapytanie. jest ono na tyle krótkie, + * że musi się zmieścić w buforze gniazda. jeśli + * write() zawiedzie, stało się coś złego. */ + if (write(sess->fd, buf, strlen(buf)) < (signed)strlen(buf)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); + goto fail_connecting; + } + + if (auth) { + gg_debug(GG_DEBUG_MISC, "// %s", auth); + if (write(sess->fd, auth, strlen(auth)) < (signed)strlen(auth)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); + goto fail_connecting; + } + + free(auth); + } + + if (write(sess->fd, "\r\n", 2) < 2) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); + goto fail_connecting; + } + } + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) { + SSL_set_fd(sess->ssl, sess->fd); + + sess->state = GG_STATE_TLS_NEGOTIATION; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } +#endif + + sess->state = GG_STATE_READING_KEY; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + case GG_STATE_TLS_NEGOTIATION: + { + int res; + X509 *peer; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n"); + + if ((res = SSL_connect(sess->ssl)) <= 0) { + int err = SSL_get_error(sess->ssl, res); + + if (res == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() disconnected during TLS negotiation\n"); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_TLS; + sess->state = GG_STATE_IDLE; + close(sess->fd); + sess->fd = -1; + break; + } + + if (err == SSL_ERROR_WANT_READ) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to read\n"); + + sess->state = GG_STATE_TLS_NEGOTIATION; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } else if (err == SSL_ERROR_WANT_WRITE) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to write\n"); + + sess->state = GG_STATE_TLS_NEGOTIATION; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } else { + char buf[1024]; + + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() bailed out: %s\n", buf); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_TLS; + sess->state = GG_STATE_IDLE; + close(sess->fd); + sess->fd = -1; + break; + } + } + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded:\n// cipher: %s\n", SSL_get_cipher_name(sess->ssl)); + + peer = SSL_get_peer_certificate(sess->ssl); + + if (!peer) + gg_debug(GG_DEBUG_MISC, "// WARNING! unable to get peer certificate!\n"); + else { + char buf[1024]; + + X509_NAME_oneline(X509_get_subject_name(peer), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// cert subject: %s\n", buf); + + X509_NAME_oneline(X509_get_issuer_name(peer), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// cert issuer: %s\n", buf); + } + + sess->state = GG_STATE_READING_KEY; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } +#endif + + case GG_STATE_READING_KEY: + { + struct gg_header *h; + struct gg_welcome *w; + struct gg_login60 l; + unsigned int hash; + unsigned char *password = sess->password; + int ret; + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_KEY\n"); + + memset(&l, 0, sizeof(l)); + l.dunno2 = 0xbe; + + /* XXX bardzo, bardzo, bardzo głupi pomysł na pozbycie + * się tekstu wrzucanego przez proxy. */ + if (sess->proxy_addr && sess->proxy_port) { + char buf[100]; + + strcpy(buf, ""); + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() proxy response:\n// %s\n", buf); + + while (strcmp(buf, "")) { + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + if (strcmp(buf, "")) + gg_debug(GG_DEBUG_MISC, "// %s\n", buf); + } + + /* XXX niech czeka jeszcze raz w tej samej + * fazie. głupio, ale działa. */ + sess->proxy_port = 0; + + break; + } + + /* czytaj pierwszy pakiet. */ + if (!(h = gg_recv_packet(sess))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_READING; + sess->state = GG_STATE_IDLE; + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + break; + } + + if (h->type != GG_WELCOME) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() invalid packet received\n"); + free(h); + close(sess->fd); + sess->fd = -1; + errno = EINVAL; + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_INVALID; + sess->state = GG_STATE_IDLE; + break; + } + + w = (struct gg_welcome*) ((char*) h + sizeof(struct gg_header)); + w->key = gg_fix32(w->key); + + hash = gg_login_hash(password, w->key); + + gg_debug(GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> hash %.8x\n", w->key, hash); + + free(h); + + free(sess->password); + sess->password = NULL; + + { + struct in_addr dcc_ip; + dcc_ip.s_addr = gg_dcc_ip; + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() gg_dcc_ip = %s\n", inet_ntoa(dcc_ip)); + } + + if (gg_dcc_ip == (unsigned long) inet_addr("255.255.255.255")) { + struct sockaddr_in sin; + int sin_len = sizeof(sin); + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() detecting address\n"); + + if (!getsockname(sess->fd, (struct sockaddr*) &sin, &sin_len)) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() detected address to %s\n", inet_ntoa(sin.sin_addr)); + l.local_ip = sin.sin_addr.s_addr; + } else { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() unable to detect address\n"); + l.local_ip = 0; + } + } else + l.local_ip = gg_dcc_ip; + + l.uin = gg_fix32(sess->uin); + l.hash = gg_fix32(hash); + l.status = gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL); + l.version = gg_fix32(sess->protocol_version); + l.local_port = gg_fix16(gg_dcc_port); + l.image_size = sess->image_size; + + if (sess->external_addr && sess->external_port > 1023) { + l.external_ip = sess->external_addr; + l.external_port = gg_fix16(sess->external_port); + } + + gg_debug(GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN60 packet\n"); + ret = gg_send_packet(sess, GG_LOGIN60, &l, sizeof(l), sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0, NULL); + + free(sess->initial_descr); + sess->initial_descr = NULL; + + if (ret == -1) { + gg_debug(GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending packet failed. (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + 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_watch_fd() GG_STATE_READING_REPLY\n"); + + if (!(h = gg_recv_packet(sess))) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno)); + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_READING; + sess->state = GG_STATE_IDLE; + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + break; + } + + if (h->type == GG_LOGIN_OK) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() login succeded\n"); + e->type = GG_EVENT_CONN_SUCCESS; + sess->state = GG_STATE_CONNECTED; + sess->timeout = -1; + sess->status = (sess->initial_status) ? sess->initial_status : GG_STATUS_AVAIL; + free(h); + break; + } + + if (h->type == GG_LOGIN_FAILED) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() login failed\n"); + e->event.failure = GG_FAILURE_PASSWORD; + errno = EACCES; + } else if (h->type == GG_NEED_EMAIL) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() email change needed\n"); + e->event.failure = GG_FAILURE_NEED_EMAIL; + errno = EACCES; + } else { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() invalid packet\n"); + e->event.failure = GG_FAILURE_INVALID; + errno = EINVAL; + } + + e->type = GG_EVENT_CONN_FAILED; + sess->state = GG_STATE_IDLE; + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + free(h); + + break; + } + + case GG_STATE_CONNECTED: + { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTED\n"); + + sess->last_event = time(NULL); + + if ((res = gg_watch_fd_connected(sess, e)) == -1) { + + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() 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; + } + } + +done: + if (res == -1) { + free(e); + e = NULL; + } + + return e; + +fail_connecting: + if (sess->fd != -1) { + errno2 = errno; + close(sess->fd); + errno = errno2; + sess->fd = -1; + } + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_CONNECTING; + sess->state = GG_STATE_IDLE; + goto done; + +fail_resolving: + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_RESOLVING; + sess->state = GG_STATE_IDLE; + goto done; +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/lib/http.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/lib/http.c Sun Aug 28 22:46:01 2005 +0000 @@ -0,0 +1,522 @@ +/* $Id: http.c 13582 2005-08-28 22:46:01Z boler $ */ + +/* + * (C) Copyright 2001-2002 Wojtek Kaniewski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#include +#include +#include + +#include "libgadu-config.h" + +#include +#include +#include +#ifdef __GG_LIBGADU_HAVE_PTHREAD +# include +#endif +#include +#include +#include +#include +#include + +#include "compat.h" +#include "libgadu.h" + +/* + * gg_http_connect() // funkcja pomocnicza + * + * rozpoczyna połączenie po http. + * + * - hostname - adres serwera + * - port - port serwera + * - async - asynchroniczne połączenie + * - method - metoda http (GET, POST, cokolwiek) + * - path - ścieżka do zasobu (musi być poprzedzona ,,/'') + * - header - nagłówek zapytania plus ewentualne dane dla POST + * + * zaalokowana struct gg_http, którą poźniej należy + * zwolnić funkcją gg_http_free(), albo NULL jeśli wystąpił błąd. + */ +struct gg_http *gg_http_connect(const char *hostname, int port, int async, const char *method, const char *path, const char *header) +{ + struct gg_http *h; + + if (!hostname || !port || !method || !path || !header) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() invalid arguments\n"); + errno = EFAULT; + return NULL; + } + + if (!(h = malloc(sizeof(*h)))) + return NULL; + memset(h, 0, sizeof(*h)); + + h->async = async; + h->port = port; + h->fd = -1; + h->type = GG_SESSION_HTTP; + + if (gg_proxy_enabled) { + char *auth = gg_proxy_auth(); + + h->query = gg_saprintf("%s http://%s:%d%s HTTP/1.0\r\n%s%s", + method, hostname, port, path, (auth) ? auth : + "", header); + hostname = gg_proxy_host; + h->port = port = gg_proxy_port; + + if (auth) + free(auth); + } else { + h->query = gg_saprintf("%s %s HTTP/1.0\r\n%s", + method, path, header); + } + + if (!h->query) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() not enough memory for query\n"); + free(h); + errno = ENOMEM; + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", h->query); + + if (async) { +#ifndef __GG_LIBGADU_HAVE_PTHREAD + if (gg_resolve(&h->fd, &h->pid, hostname)) { +#else + if (gg_resolve_pthread(&h->fd, &h->resolver, hostname)) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() resolver failed\n"); + gg_http_free(h); + errno = ENOENT; + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() resolver = %p\n", h->resolver); + + h->state = GG_STATE_RESOLVING; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + } else { + struct in_addr *hn, a; + + if (!(hn = gg_gethostbyname(hostname))) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() host not found\n"); + gg_http_free(h); + errno = ENOENT; + return NULL; + } else { + a.s_addr = hn->s_addr; + free(hn); + } + + if (!(h->fd = gg_connect(&a, port, 0)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + gg_http_free(h); + return NULL; + } + + h->state = GG_STATE_CONNECTING; + + while (h->state != GG_STATE_ERROR && h->state != GG_STATE_PARSING) { + if (gg_http_watch_fd(h) == -1) + break; + } + + if (h->state != GG_STATE_PARSING) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() some strange error\n"); + gg_http_free(h); + return NULL; + } + } + + h->callback = gg_http_watch_fd; + h->destroy = gg_http_free; + + return h; +} + +#define gg_http_error(x) \ + close(h->fd); \ + h->fd = -1; \ + h->state = GG_STATE_ERROR; \ + h->error = x; \ + return 0; + +/* + * gg_http_watch_fd() + * + * przy asynchronicznej obsłudze HTTP funkcję tą należy wywołać, jeśli + * zmieniło się coś na obserwowanym deskryptorze. + * + * - h - struktura opisująca połączenie + * + * jeśli wszystko poszło dobrze to 0, inaczej -1. połączenie będzie + * zakończone, jeśli h->state == GG_STATE_PARSING. jeśli wystąpi jakiś + * błąd, to będzie tam GG_STATE_ERROR i odpowiedni kod błędu w h->error. + */ +int gg_http_watch_fd(struct gg_http *h) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_http_watch_fd(%p);\n", h); + + if (!h) { + gg_debug(GG_DEBUG_MISC, "// gg_http_watch_fd() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + if (h->state == GG_STATE_RESOLVING) { + struct in_addr a; + + gg_debug(GG_DEBUG_MISC, "=> http, resolving done\n"); + + if (read(h->fd, &a, sizeof(a)) < (signed)sizeof(a) || a.s_addr == INADDR_NONE) { + gg_debug(GG_DEBUG_MISC, "=> http, resolver thread failed\n"); + gg_http_error(GG_ERROR_RESOLVING); + } + + close(h->fd); + h->fd = -1; + +#ifndef __GG_LIBGADU_HAVE_PTHREAD + waitpid(h->pid, NULL, 0); +#else + if (h->resolver) { + pthread_cancel(*((pthread_t *) h->resolver)); + free(h->resolver); + h->resolver = NULL; + } +#endif + + gg_debug(GG_DEBUG_MISC, "=> http, connecting to %s:%d\n", inet_ntoa(a), h->port); + + if ((h->fd = gg_connect(&a, h->port, h->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "=> http, connection failed (errno=%d, %s)\n", errno, strerror(errno)); + gg_http_error(GG_ERROR_CONNECTING); + } + + h->state = GG_STATE_CONNECTING; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + return 0; + } + + if (h->state == GG_STATE_CONNECTING) { + int res = 0; + unsigned int res_size = sizeof(res); + + if (h->async && (getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + gg_debug(GG_DEBUG_MISC, "=> http, async connection failed (errno=%d, %s)\n", (res) ? res : errno , strerror((res) ? res : errno)); + close(h->fd); + h->fd = -1; + h->state = GG_STATE_ERROR; + h->error = GG_ERROR_CONNECTING; + if (res) + errno = res; + return 0; + } + + gg_debug(GG_DEBUG_MISC, "=> http, connected, sending request\n"); + + h->state = GG_STATE_SENDING_QUERY; + } + + if (h->state == GG_STATE_SENDING_QUERY) { + int res; + + if ((res = write(h->fd, h->query, strlen(h->query))) < 1) { + gg_debug(GG_DEBUG_MISC, "=> http, write() failed (len=%d, res=%d, errno=%d)\n", strlen(h->query), res, errno); + gg_http_error(GG_ERROR_WRITING); + } + + if (res < strlen(h->query)) { + gg_debug(GG_DEBUG_MISC, "=> http, partial header sent (led=%d, sent=%d)\n", strlen(h->query), res); + + memmove(h->query, h->query + res, strlen(h->query) - res + 1); + h->state = GG_STATE_SENDING_QUERY; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + } else { + gg_debug(GG_DEBUG_MISC, "=> http, request sent (len=%d)\n", strlen(h->query)); + free(h->query); + h->query = NULL; + + h->state = GG_STATE_READING_HEADER; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + } + + return 0; + } + + if (h->state == GG_STATE_READING_HEADER) { + char buf[1024], *tmp; + int res; + + if ((res = read(h->fd, buf, sizeof(buf))) == -1) { + gg_debug(GG_DEBUG_MISC, "=> http, reading header failed (errno=%d)\n", errno); + if (h->header) { + free(h->header); + h->header = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + if (!res) { + gg_debug(GG_DEBUG_MISC, "=> http, connection reset by peer\n"); + if (h->header) { + free(h->header); + h->header = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + gg_debug(GG_DEBUG_MISC, "=> http, read %d bytes of header\n", res); + + if (!(tmp = realloc(h->header, h->header_size + res + 1))) { + gg_debug(GG_DEBUG_MISC, "=> http, not enough memory for header\n"); + free(h->header); + h->header = NULL; + gg_http_error(GG_ERROR_READING); + } + + h->header = tmp; + + memcpy(h->header + h->header_size, buf, res); + h->header_size += res; + + gg_debug(GG_DEBUG_MISC, "=> http, header_buf=%p, header_size=%d\n", h->header, h->header_size); + + h->header[h->header_size] = 0; + + if ((tmp = strstr(h->header, "\r\n\r\n")) || (tmp = strstr(h->header, "\n\n"))) { + int sep_len = (*tmp == '\r') ? 4 : 2; + unsigned int left; + char *line; + + left = h->header_size - ((long)(tmp) - (long)(h->header) + sep_len); + + gg_debug(GG_DEBUG_MISC, "=> http, got all header (%d bytes, %d left)\n", h->header_size - left, left); + + /* HTTP/1.1 200 OK */ + if (strlen(h->header) < 16 || strncmp(h->header + 9, "200", 3)) { + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header); + + gg_debug(GG_DEBUG_MISC, "=> http, didn't get 200 OK -- no results\n"); + free(h->header); + h->header = NULL; + gg_http_error(GG_ERROR_CONNECTING); + } + + h->body_size = 0; + line = h->header; + *tmp = 0; + + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header); + + while (line) { + if (!strncasecmp(line, "Content-length: ", 16)) { + h->body_size = atoi(line + 16); + } + line = strchr(line, '\n'); + if (line) + line++; + } + + if (h->body_size <= 0) { + gg_debug(GG_DEBUG_MISC, "=> http, content-length not found\n"); + h->body_size = left; + } + + if (left > h->body_size) { + gg_debug(GG_DEBUG_MISC, "=> http, oversized reply (%d bytes needed, %d bytes left)\n", h->body_size, left); + h->body_size = left; + } + + gg_debug(GG_DEBUG_MISC, "=> http, body_size=%d\n", h->body_size); + + if (!(h->body = malloc(h->body_size + 1))) { + gg_debug(GG_DEBUG_MISC, "=> http, not enough memory (%d bytes for body_buf)\n", h->body_size + 1); + free(h->header); + h->header = NULL; + gg_http_error(GG_ERROR_READING); + } + + if (left) { + memcpy(h->body, tmp + sep_len, left); + h->body_done = left; + } + + h->body[left] = 0; + + h->state = GG_STATE_READING_DATA; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + } + + return 0; + } + + if (h->state == GG_STATE_READING_DATA) { + char buf[1024]; + int res; + + if ((res = read(h->fd, buf, sizeof(buf))) == -1) { + gg_debug(GG_DEBUG_MISC, "=> http, reading body failed (errno=%d)\n", errno); + if (h->body) { + free(h->body); + h->body = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + if (!res) { + if (h->body_done >= h->body_size) { + gg_debug(GG_DEBUG_MISC, "=> http, we're done, closing socket\n"); + h->state = GG_STATE_PARSING; + close(h->fd); + h->fd = -1; + } else { + gg_debug(GG_DEBUG_MISC, "=> http, connection closed while reading (have %d, need %d)\n", h->body_done, h->body_size); + if (h->body) { + free(h->body); + h->body = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + return 0; + } + + gg_debug(GG_DEBUG_MISC, "=> http, read %d bytes of body\n", res); + + if (h->body_done + res > h->body_size) { + char *tmp; + + gg_debug(GG_DEBUG_MISC, "=> http, too much data (%d bytes, %d needed), enlarging buffer\n", h->body_done + res, h->body_size); + + if (!(tmp = realloc(h->body, h->body_done + res + 1))) { + gg_debug(GG_DEBUG_MISC, "=> http, not enough memory for data (%d needed)\n", h->body_done + res + 1); + free(h->body); + h->body = NULL; + gg_http_error(GG_ERROR_READING); + } + + h->body = tmp; + h->body_size = h->body_done + res; + } + + h->body[h->body_done + res] = 0; + memcpy(h->body + h->body_done, buf, res); + h->body_done += res; + + gg_debug(GG_DEBUG_MISC, "=> body_done=%d, body_size=%d\n", h->body_done, h->body_size); + + return 0; + } + + if (h->fd != -1) + close(h->fd); + + h->fd = -1; + h->state = GG_STATE_ERROR; + h->error = 0; + + return -1; +} + +#undef gg_http_error + +/* + * gg_http_stop() + * + * jeśli połączenie jest w trakcie, przerywa je. nie zwalnia h->data. + * + * - h - struktura opisująca połączenie + */ +void gg_http_stop(struct gg_http *h) +{ + if (!h) + return; + + if (h->state == GG_STATE_ERROR || h->state == GG_STATE_DONE) + return; + + if (h->fd != -1) + close(h->fd); + h->fd = -1; +} + +/* + * gg_http_free_fields() // funkcja wewnętrzna + * + * zwalnia pola struct gg_http, ale nie zwalnia samej struktury. + */ +void gg_http_free_fields(struct gg_http *h) +{ + if (!h) + return; + + if (h->body) { + free(h->body); + h->body = NULL; + } + + if (h->query) { + free(h->query); + h->query = NULL; + } + + if (h->header) { + free(h->header); + h->header = NULL; + } +} + +/* + * gg_http_free() + * + * próbuje zamknąć połączenie i zwalnia pamięć po nim. + * + * - h - struktura, którą należy zlikwidować + */ +void gg_http_free(struct gg_http *h) +{ + if (!h) + return; + + gg_http_stop(h); + gg_http_free_fields(h); + free(h); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/lib/libgadu-config.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/lib/libgadu-config.h Sun Aug 28 22:46:01 2005 +0000 @@ -0,0 +1,42 @@ +/* Local libgadu configuration. */ + +#include "../../../config.h" + +#ifndef __GG_LIBGADU_CONFIG_H +#define __GG_LIBGADU_CONFIG_H + +/* Defined if libgadu was compiled for bigendian machine. */ +#undef __GG_LIBGADU_BIGENDIAN +#ifdef WORDS_BIGENDIAN +#define __GG_LIBGADU_BIGENDIAN +#endif /* WORDS_BIGENDIAN */ + +/* Defined if this machine has va_copy(). */ +#define __GG_LIBGADU_HAVE_VA_COPY + +/* Defined if this machine has __va_copy(). */ +#define __GG_LIBGADU_HAVE___VA_COPY + +/* Defined if this machine supports long long. */ +#undef __GG_LIBGADU_HAVE_LONG_LONG +#ifdef HAVE_LONG_LONG +#define __GG_LIBGADU_HAVE_LONG_LONG +#endif /* HAVE_LONG_LONG */ + +/* Defined if libgadu was compiled and linked with pthread support. */ +/* We don't like pthreads. */ +#undef __GG_LIBGADU_HAVE_PTHREAD + +/* Defined if libgadu was compiled and linked with TLS support. */ +/* Always undefined in Gaim. */ +#undef __GG_LIBGADU_HAVE_OPENSSL + +/* Include file containing uintXX_t declarations. */ +#include + +/* Defined if this machine has C99-compiliant vsnprintf(). */ +#define __GG_LIBGADU_HAVE_C99_VSNPRINTF + +#define vnsprintf g_vnsprintf + +#endif /* __GG_LIBGADU_CONFIG_H */ diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/lib/libgadu.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/lib/libgadu.c Sun Aug 28 22:46:01 2005 +0000 @@ -0,0 +1,1811 @@ +/* $Id: libgadu.c 13582 2005-08-28 22:46:01Z boler $ */ + +/* + * (C) Copyright 2001-2003 Wojtek Kaniewski + * Robert J. Woźny + * Arkadiusz Miśkiewicz + * Tomasz Chiliński + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#include +#include +#include +#ifdef sun +# include +#endif + +#include "libgadu-config.h" + +#include +#include +#ifdef __GG_LIBGADU_HAVE_PTHREAD +# include +#endif +#include +#include +#include +#include +#include +#ifdef __GG_LIBGADU_HAVE_OPENSSL +# include +# include +#endif + +#include "compat.h" +#include "libgadu.h" + +int gg_debug_level = 0; +void (*gg_debug_handler)(int level, const char *format, va_list ap) = NULL; + +int gg_dcc_port = 0; +unsigned long gg_dcc_ip = 0; + +unsigned long gg_local_ip = 0; +/* + * zmienne opisujące parametry proxy http. + */ +char *gg_proxy_host = NULL; +int gg_proxy_port = 0; +int gg_proxy_enabled = 0; +int gg_proxy_http_only = 0; +char *gg_proxy_username = NULL; +char *gg_proxy_password = NULL; + +#ifndef lint +static char rcsid[] +#ifdef __GNUC__ +__attribute__ ((unused)) +#endif += "$Id: libgadu.c 13582 2005-08-28 22:46:01Z boler $"; +#endif + +/* + * gg_libgadu_version() + * + * zwraca wersję libgadu. + * + * - brak + * + * wersja libgadu. + */ +const char *gg_libgadu_version() +{ + return GG_LIBGADU_VERSION; +} + +/* + * gg_fix32() + * + * zamienia kolejność bajtów w liczbie 32-bitowej tak, by odpowiadała + * kolejności bajtów w protokole GG. ze względu na LE-owość serwera, + * zamienia tylko na maszynach BE-wych. + * + * - x - liczba do zamiany + * + * liczba z odpowiednią kolejnością bajtów. + */ +uint32_t gg_fix32(uint32_t x) +{ +#ifndef __GG_LIBGADU_BIGENDIAN + return x; +#else + return (uint32_t) + (((x & (uint32_t) 0x000000ffU) << 24) | + ((x & (uint32_t) 0x0000ff00U) << 8) | + ((x & (uint32_t) 0x00ff0000U) >> 8) | + ((x & (uint32_t) 0xff000000U) >> 24)); +#endif +} + +/* + * gg_fix16() + * + * zamienia kolejność bajtów w liczbie 16-bitowej tak, by odpowiadała + * kolejności bajtów w protokole GG. ze względu na LE-owość serwera, + * zamienia tylko na maszynach BE-wych. + * + * - x - liczba do zamiany + * + * liczba z odpowiednią kolejnością bajtów. + */ +uint16_t gg_fix16(uint16_t x) +{ +#ifndef __GG_LIBGADU_BIGENDIAN + return x; +#else + return (uint16_t) + (((x & (uint16_t) 0x00ffU) << 8) | + ((x & (uint16_t) 0xff00U) >> 8)); +#endif +} + +/* + * 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_resolve() // funkcja wewnętrzna + * + * tworzy potok, forkuje się i w drugim procesie zaczyna resolvować + * podanego hosta. zapisuje w sesji deskryptor potoku. 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 procesu potomnego + * - hostname - nazwa hosta do zresolvowania + * + * 0, -1. + */ +int gg_resolve(int *fd, int *pid, const char *hostname) +{ + int pipes[2], res; + struct in_addr a; + int errno2; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_resolve(%p, %p, \"%s\");\n", fd, pid, hostname); + + if (!fd || !pid) { + errno = EFAULT; + return -1; + } + + if (pipe(pipes) == -1) + return -1; + + if ((res = fork()) == -1) { + errno2 = errno; + close(pipes[0]); + close(pipes[1]); + errno = errno2; + return -1; + } + + if (!res) { + if ((a.s_addr = inet_addr(hostname)) == INADDR_NONE) { + struct in_addr *hn; + + if (!(hn = gg_gethostbyname(hostname))) + a.s_addr = INADDR_NONE; + else { + a.s_addr = hn->s_addr; + free(hn); + } + } + + write(pipes[1], &a, sizeof(a)); + + exit(0); + } + + close(pipes[1]); + + *fd = pipes[0]; + *pid = res; + + return 0; +} + +#ifdef __GG_LIBGADU_HAVE_PTHREAD + +struct gg_resolve_pthread_data { + char *hostname; + int fd; +}; + +static void *gg_resolve_pthread_thread(void *arg) +{ + struct gg_resolve_pthread_data *d = arg; + struct in_addr a; + + pthread_detach(pthread_self()); + + if ((a.s_addr = inet_addr(d->hostname)) == INADDR_NONE) { + struct in_addr *hn; + + if (!(hn = gg_gethostbyname(d->hostname))) + a.s_addr = INADDR_NONE; + else { + a.s_addr = hn->s_addr; + free(hn); + } + } + + write(d->fd, &a, sizeof(a)); + close(d->fd); + + free(d->hostname); + d->hostname = NULL; + + free(d); + + pthread_exit(NULL); + + return NULL; /* żeby kompilator nie marudził */ +} + +/* + * gg_resolve_pthread() // funkcja wewnętrzna + * + * tworzy potok, nowy wątek i w nim zaczyna resolvować podanego hosta. + * zapisuje w sesji deskryptor potoku. 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 do zmiennej przechowującej desktyptor resolvera + * - resolver - wskaźnik do wskaźnika resolvera + * - hostname - nazwa hosta do zresolvowania + * + * 0, -1. + */ +int gg_resolve_pthread(int *fd, void **resolver, const char *hostname) +{ + struct gg_resolve_pthread_data *d = NULL; + pthread_t *tmp; + int pipes[2], new_errno; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_resolve_pthread(%p, %p, \"%s\");\n", fd, resolver, hostname); + + if (!resolver || !fd || !hostname) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + if (!(tmp = malloc(sizeof(pthread_t)))) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory for pthread id\n"); + return -1; + } + + if (pipe(pipes) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); + free(tmp); + return -1; + } + + if (!(d = malloc(sizeof(*d)))) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory\n"); + new_errno = errno; + goto cleanup; + } + + d->hostname = NULL; + + if (!(d->hostname = strdup(hostname))) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory\n"); + new_errno = errno; + goto cleanup; + } + + d->fd = pipes[1]; + + if (pthread_create(tmp, NULL, gg_resolve_pthread_thread, d)) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_phread() unable to create thread\n"); + new_errno = errno; + goto cleanup; + } + + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() %p\n", tmp); + + *resolver = tmp; + + *fd = pipes[0]; + + return 0; + +cleanup: + if (d) { + free(d->hostname); + free(d); + } + + close(pipes[0]); + close(pipes[1]); + + free(tmp); + + errno = new_errno; + + return -1; +} + +#endif + +/* + * gg_read() // funkcja pomocnicza + * + * czyta z 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 read(). + */ +int gg_read(struct gg_session *sess, char *buf, int length) +{ + int res; + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) { + int err; + + res = SSL_read(sess->ssl, buf, length); + + if (res < 0) { + err = SSL_get_error(sess->ssl, res); + + if (err == SSL_ERROR_WANT_READ) + errno = EAGAIN; + + return -1; + } + } else +#endif + res = read(sess->fd, buf, length); + + return res; +} + +/* + * 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 = 0; + +#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 + { + int written = 0; + + while (written < length) { + res = write(sess->fd, buf + written, length - written); + + if (res == -1) { + if (errno == EAGAIN) + continue; + else + break; + } else { + written += res; + res = written; + } + } + } + + return res; +} + +/* + * gg_recv_packet() // funkcja wewnętrzna + * + * odbiera jeden pakiet i zwraca wskaźnik do niego. pamięć po nim + * należy zwolnić za pomocą free(). + * + * - sess - opis sesji + * + * w przypadku błędu NULL, kod błędu w errno. należy zwrócić uwagę, że gdy + * połączenie jest nieblokujące, a kod błędu wynosi EAGAIN, nie udało się + * odczytać całego pakietu i nie należy tego traktować jako błąd. + */ +void *gg_recv_packet(struct gg_session *sess) +{ + struct gg_header h; + char *buf = NULL; + int ret = 0; + unsigned int offset, size = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_recv_packet(%p);\n", sess); + + if (!sess) { + errno = EFAULT; + return NULL; + } + + if (sess->recv_left < 1) { + if (sess->header_buf) { + memcpy(&h, sess->header_buf, sess->header_done); + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv: resuming last read (%d bytes left)\n", sizeof(h) - sess->header_done); + free(sess->header_buf); + sess->header_buf = NULL; + } else + sess->header_done = 0; + + while (sess->header_done < sizeof(h)) { + ret = gg_read(sess, (char*) &h + sess->header_done, sizeof(h) - sess->header_done); + + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv(%d,%p,%d) = %d\n", sess->fd, &h + sess->header_done, sizeof(h) - sess->header_done, ret); + + if (!ret) { + errno = ECONNRESET; + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: connection broken\n"); + return NULL; + } + + if (ret == -1) { + if (errno == EINTR) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() interrupted system call, resuming\n"); + continue; + } + + if (errno == EAGAIN) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() incomplete header received\n"); + + if (!(sess->header_buf = malloc(sess->header_done))) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() not enough memory\n"); + return NULL; + } + + memcpy(sess->header_buf, &h, sess->header_done); + + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: errno=%d, %s\n", errno, strerror(errno)); + + return NULL; + } + + sess->header_done += ret; + + } + + h.type = gg_fix32(h.type); + h.length = gg_fix32(h.length); + } else + memcpy(&h, sess->recv_buf, sizeof(h)); + + /* jakieś sensowne limity na rozmiar pakietu */ + if (h.length > 65535) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() invalid packet length (%d)\n", h.length); + errno = ERANGE; + return NULL; + } + + if (sess->recv_left > 0) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() 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, "// gg_recv_packet() not enough memory for packet data\n"); + return NULL; + } + + memcpy(buf, &h, sizeof(h)); + + offset = 0; + size = h.length; + } + + while (size > 0) { + ret = gg_read(sess, buf + sizeof(h) + offset, size); + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() body recv(%d,%p,%d) = %d\n", sess->fd, buf + sizeof(h) + offset, size, ret); + if (!ret) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed: connection broken\n"); + errno = ECONNRESET; + return NULL; + } + if (ret > -1 && ret <= size) { + offset += ret; + size -= ret; + } else if (ret == -1) { + int errno2 = errno; + + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno = errno2; + + if (errno == EAGAIN) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() %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) { + free(buf); + return NULL; + } + } + } + + sess->recv_left = 0; + + if ((gg_debug_level & GG_DEBUG_DUMP)) { + unsigned int i; + + gg_debug(GG_DEBUG_DUMP, "// gg_recv_packet(%.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"); + } + + 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; + unsigned int tmp_length; + void *payload; + unsigned int payload_length; + va_list ap; + int res; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_packet(%p, 0x%.2x, ...)\n", sess, type); + + tmp_length = sizeof(struct gg_header); + + if (!(tmp = malloc(tmp_length))) { + gg_debug(GG_DEBUG_MISC, "// gg_send_packet() not enough memory for packet header\n"); + return -1; + } + + va_start(ap, type); + + payload = va_arg(ap, void *); + + while (payload) { + char *tmp2; + + payload_length = va_arg(ap, unsigned int); + + if (!(tmp2 = realloc(tmp, 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 + tmp_length, payload, payload_length); + tmp_length += payload_length; + + payload = va_arg(ap, void *); + } + + va_end(ap); + + h = (struct gg_header*) tmp; + h->type = gg_fix32(type); + h->length = gg_fix32(tmp_length - sizeof(struct gg_header)); + + if ((gg_debug_level & GG_DEBUG_DUMP)) { + unsigned int i; + + gg_debug(GG_DEBUG_DUMP, "// gg_send_packet(0x%.2x)", gg_fix32(h->type)); + for (i = 0; i < tmp_length; ++i) + gg_debug(GG_DEBUG_DUMP, " %.2x", (unsigned char) tmp[i]); + gg_debug(GG_DEBUG_DUMP, "\n"); + } + + 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_session_callback() // funkcja wewnętrzna + * + * wywoływany z gg_session->callback, wykonuje gg_watch_fd() i pakuje + * do gg_session->event jego wynik. + */ +static int gg_session_callback(struct gg_session *s) +{ + if (!s) { + errno = EFAULT; + return -1; + } + + return ((s->event = gg_watch_fd(s)) != NULL) ? 0 : -1; +} + +/* + * gg_login() + * + * rozpoczyna procedurę łączenia się z serwerem. resztę obsługuje się przez + * gg_watch_fd(). + * + * UWAGA! program musi obsłużyć SIGCHLD, jeśli łączy się asynchronicznie, + * żeby poprawnie zamknąć proces resolvera. + * + * - p - struktura opisująca początkowy stan. wymagane pola: uin, + * password + * + * w przypadku błędu NULL, jeśli idzie dobrze (async) albo poszło + * dobrze (sync), zwróci wskaźnik do zaalokowanej struct gg_session. + */ +struct gg_session *gg_login(const struct gg_login_params *p) +{ + struct gg_session *sess = NULL; + char *hostname; + int port; + + if (!p) { + gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p);\n", p); + errno = EFAULT; + return NULL; + } + + gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p: [uin=%u, async=%d, ...]);\n", p, p->uin, p->async); + + if (!(sess = malloc(sizeof(struct gg_session)))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for session data\n"); + goto fail; + } + + memset(sess, 0, sizeof(struct gg_session)); + + if (!p->password || !p->uin) { + gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. uin and password needed\n"); + errno = EFAULT; + goto fail; + } + + if (!(sess->password = strdup(p->password))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for password\n"); + goto fail; + } + + if (p->status_descr && !(sess->initial_descr = strdup(p->status_descr))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for status\n"); + goto fail; + } + + sess->uin = p->uin; + sess->state = GG_STATE_RESOLVING; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + sess->async = p->async; + sess->type = GG_SESSION_GG; + sess->initial_status = p->status; + sess->callback = gg_session_callback; + sess->destroy = gg_free_session; + sess->port = (p->server_port) ? p->server_port : ((gg_proxy_enabled) ? GG_HTTPS_PORT : GG_DEFAULT_PORT); + sess->server_addr = p->server_addr; + sess->external_port = p->external_port; + sess->external_addr = p->external_addr; + sess->protocol_version = (p->protocol_version) ? p->protocol_version : GG_DEFAULT_PROTOCOL_VERSION; + if (p->era_omnix) + sess->protocol_version |= GG_ERA_OMNIX_MASK; + if (p->has_audio) + sess->protocol_version |= GG_HAS_AUDIO_MASK; + sess->client_version = (p->client_version) ? strdup(p->client_version) : NULL; + sess->last_sysmsg = p->last_sysmsg; + sess->image_size = p->image_size; + sess->pid = -1; + + if (p->tls == 1) { +#ifdef __GG_LIBGADU_HAVE_OPENSSL + char buf[1024]; + + OpenSSL_add_ssl_algorithms(); + + if (!RAND_status()) { + char rdata[1024]; + struct { + time_t time; + void *ptr; + } rstruct; + + time(&rstruct.time); + rstruct.ptr = (void *) &rstruct; + + RAND_seed((void *) rdata, sizeof(rdata)); + RAND_seed((void *) &rstruct, sizeof(rstruct)); + } + + sess->ssl_ctx = SSL_CTX_new(TLSv1_client_method()); + + if (!sess->ssl_ctx) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_CTX_new() failed: %s\n", buf); + goto fail; + } + + SSL_CTX_set_verify(sess->ssl_ctx, SSL_VERIFY_NONE, NULL); + + sess->ssl = SSL_new(sess->ssl_ctx); + + if (!sess->ssl) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_new() failed: %s\n", buf); + goto fail; + } +#else + gg_debug(GG_DEBUG_MISC, "// gg_login() client requested TLS but no support compiled in\n"); +#endif + } + + if (gg_proxy_enabled) { + hostname = gg_proxy_host; + sess->proxy_port = port = gg_proxy_port; + } else { + hostname = GG_APPMSG_HOST; + port = GG_APPMSG_PORT; + } + + if (!p->async) { + struct in_addr a; + + if (!p->server_addr || !p->server_port) { + if ((a.s_addr = inet_addr(hostname)) == INADDR_NONE) { + struct in_addr *hn; + + if (!(hn = gg_gethostbyname(hostname))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() host \"%s\" not found\n", hostname); + goto fail; + } else { + a.s_addr = hn->s_addr; + free(hn); + } + } + } else { + a.s_addr = p->server_addr; + port = p->server_port; + } + + sess->hub_addr = a.s_addr; + + if (gg_proxy_enabled) + sess->proxy_addr = a.s_addr; + + if ((sess->fd = gg_connect(&a, port, 0)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + + if (p->server_addr && p->server_port) + sess->state = GG_STATE_CONNECTING_GG; + else + sess->state = GG_STATE_CONNECTING_HUB; + + while (sess->state != GG_STATE_CONNECTED) { + struct gg_event *e; + + if (!(e = gg_watch_fd(sess))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() critical error in gg_watch_fd()\n"); + goto fail; + } + + if (e->type == GG_EVENT_CONN_FAILED) { + errno = EACCES; + gg_debug(GG_DEBUG_MISC, "// gg_login() could not login\n"); + gg_event_free(e); + goto fail; + } + + gg_event_free(e); + } + + return sess; + } + + if (!sess->server_addr || gg_proxy_enabled) { +#ifndef __GG_LIBGADU_HAVE_PTHREAD + if (gg_resolve(&sess->fd, &sess->pid, hostname)) { +#else + if (gg_resolve_pthread(&sess->fd, &sess->resolver, hostname)) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_login() resolving failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + } else { + if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() direct connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + } + + return sess; + +fail: + if (sess) { + if (sess->password) + free(sess->password); + if (sess->initial_descr) + free(sess->initial_descr); + free(sess); + } + + return NULL; +} + +/* + * gg_free_session() + * + * próbuje zamknąć połączenia i zwalnia pamięć zajmowaną przez sesję. + * + * - sess - opis sesji + */ +void gg_free_session(struct gg_session *sess) +{ + if (!sess) + return; + + /* XXX dopisać zwalnianie i zamykanie wszystkiego, co mogło zostać */ + + if (sess->password) + free(sess->password); + + if (sess->initial_descr) + free(sess->initial_descr); + + if (sess->client_version) + free(sess->client_version); + + if (sess->header_buf) + free(sess->header_buf); + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) + SSL_free(sess->ssl); + + if (sess->ssl_ctx) + SSL_CTX_free(sess->ssl_ctx); +#endif + +#ifdef __GG_LIBGADU_HAVE_PTHREAD + if (sess->resolver) { + pthread_cancel(*((pthread_t*) sess->resolver)); + free(sess->resolver); + sess->resolver = NULL; + } +#else + if (sess->pid != -1) + waitpid(sess->pid, NULL, WNOHANG); +#endif + + if (sess->fd != -1) + close(sess->fd); + + while (sess->images) + gg_image_queue_remove(sess, sess->images, 1); + + free(sess); +} + +/* + * gg_change_status() + * + * zmienia status użytkownika. przydatne do /away i /busy oraz /quit. + * + * - sess - opis sesji + * - status - nowy status użytkownika + * + * 0, -1. + */ +int gg_change_status(struct gg_session *sess, int status) +{ + struct gg_new_status p; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status(%p, %d);\n", sess, status); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + p.status = gg_fix32(status); + + sess->status = status; + + return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), NULL); +} + +/* + * 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 = gg_fix32(status); + + sess->status = 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_change_status_descr_time() + * + * zmienia status użytkownika na opisowy z godziną powrotu. + * + * - sess - opis sesji + * - status - nowy status użytkownika + * - descr - opis statusu + * - time - czas w formacie uniksowym + * + * 0, -1. + */ +int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time) +{ + struct gg_new_status p; + uint32_t newtime; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status_descr_time(%p, %d, \"%s\", %d);\n", sess, status, descr, time); + + if (!sess || !descr || !time) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + p.status = gg_fix32(status); + + sess->status = status; + + newtime = gg_fix32(time); + + return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), descr, (strlen(descr) > GG_STATUS_DESCR_MAXSIZE) ? GG_STATUS_DESCR_MAXSIZE : strlen(descr), &newtime, sizeof(newtime), NULL); +} + +/* + * gg_logoff() + * + * wylogowuje użytkownika i zamyka połączenie, ale nie zwalnia pamięci. + * + * - sess - opis sesji + */ +void gg_logoff(struct gg_session *sess) +{ + if (!sess) + return; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_logoff(%p);\n", sess); + + if (GG_S_NA(sess->status & ~GG_STATUS_FRIENDS_MASK)) + gg_change_status(sess, GG_STATUS_NOT_AVAIL); + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) + SSL_shutdown(sess->ssl); +#endif + +#ifdef __GG_LIBGADU_HAVE_PTHREAD + if (sess->resolver) { + pthread_cancel(*((pthread_t*) sess->resolver)); + free(sess->resolver); + sess->resolver = NULL; + } +#else + if (sess->pid != -1) { + waitpid(sess->pid, NULL, WNOHANG); + sess->pid = -1; + } +#endif + + if (sess->fd != -1) { + shutdown(sess->fd, SHUT_RDWR); + close(sess->fd); + sess->fd = -1; + } +} + +/* + * gg_image_request() + * + * wysyła żądanie wysłania obrazka o podanych parametrach. + * + * - sess - opis sesji + * - recipient - numer adresata + * - size - rozmiar obrazka + * - crc32 - suma kontrolna obrazka + * + * 0/-1 + */ +int gg_image_request(struct gg_session *sess, uin_t recipient, int size, uint32_t crc32) +{ + struct gg_send_msg s; + struct gg_msg_image_request r; + char dummy = 0; + int res; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_image_request(%p, %d, %u, 0x%.4x);\n", sess, recipient, size, crc32); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (size < 0) { + errno = EINVAL; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(GG_CLASS_MSG); + + r.flag = 0x04; + r.size = gg_fix32(size); + r.crc32 = gg_fix32(crc32); + + res = gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), &dummy, 1, &r, sizeof(r), NULL); + + if (!res) { + struct gg_image_queue *q = malloc(sizeof(*q)); + char *buf; + + if (!q) { + gg_debug(GG_DEBUG_MISC, "// gg_image_request() not enough memory for image queue\n"); + return -1; + } + + buf = malloc(size); + if (size && !buf) + { + gg_debug(GG_DEBUG_MISC, "// gg_image_request() not enough memory for image\n"); + free(q); + return -1; + } + + memset(q, 0, sizeof(*q)); + + q->sender = recipient; + q->size = size; + q->crc32 = crc32; + q->image = buf; + + if (!sess->images) + sess->images = q; + else { + struct gg_image_queue *qq; + + for (qq = sess->images; qq->next; qq = qq->next) + ; + + qq->next = q; + } + } + + return res; +} + +/* + * gg_image_reply() + * + * wysyła żądany obrazek. + * + * - sess - opis sesji + * - recipient - numer adresata + * - filename - nazwa pliku + * - image - bufor z obrazkiem + * - size - rozmiar obrazka + * + * 0/-1 + */ +int gg_image_reply(struct gg_session *sess, uin_t recipient, const char *filename, const char *image, int size) +{ + struct gg_msg_image_reply *r; + struct gg_send_msg s; + const char *tmp; + char buf[1910]; + int res = -1; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_image_reply(%p, %d, \"%s\", %p, %d);\n", sess, recipient, filename, image, size); + + if (!sess || !filename || !image) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (size < 0) { + errno = EINVAL; + return -1; + } + + /* wytnij ścieżki, zostaw tylko nazwę pliku */ + while ((tmp = strrchr(filename, '/')) || (tmp = strrchr(filename, '\\'))) + filename = tmp + 1; + + if (strlen(filename) < 1 || strlen(filename) > 1024) { + errno = EINVAL; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(GG_CLASS_MSG); + + buf[0] = 0; + r = (void*) &buf[1]; + + r->flag = 0x05; + r->size = gg_fix32(size); + r->crc32 = gg_fix32(gg_crc32(0, image, size)); + + while (size > 0) { + int buflen, chunklen; + + /* \0 + struct gg_msg_image_reply */ + buflen = sizeof(struct gg_msg_image_reply) + 1; + + /* w pierwszym kawałku jest nazwa pliku */ + if (r->flag == 0x05) { + strcpy(buf + buflen, filename); + buflen += strlen(filename) + 1; + } + + chunklen = (size >= sizeof(buf) - buflen) ? (sizeof(buf) - buflen) : size; + + memcpy(buf + buflen, image, chunklen); + size -= chunklen; + image += chunklen; + + res = gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), buf, buflen + chunklen, NULL); + + if (res == -1) + break; + + r->flag = 0x06; + } + + return res; +} + +/* + * gg_send_message_ctcp() + * + * 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 + * - message_len - długość + * + * numer sekwencyjny wiadomości lub -1 w przypadku błędu. + */ +int gg_send_message_ctcp(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, int message_len) +{ + struct gg_send_msg s; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message_ctcp(%p, %d, %u, ...);\n", sess, msgclass, recipient); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(msgclass); + + return gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, message_len, NULL); +} + +/* + * 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; + } + + if (!message) { + errno = EFAULT; + return -1; + } + + s.recipient = gg_fix32(recipient); + if (!sess->seq) + sess->seq = 0x01740000 | (rand() & 0xffff); + s.seq = gg_fix32(sess->seq); + s.msgclass = gg_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 gg_fix32(s.seq); +} + +/* + * gg_send_message_confer() + * + * wysyła wiadomość do kilku użytkownikow (konferencja). zwraca losowy numer + * sekwencyjny, który można zignorować albo wykorzystać do potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomości + * - recipients_count - ilość adresatów + * - recipients - numerki adresatów + * - message - treść wiadomości + * + * numer sekwencyjny wiadomości lub -1 w przypadku błędu. + */ +int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message_confer(%p, %d, %d, %p, %p);\n", sess, msgclass, recipients_count, recipients, message); + + return gg_send_message_confer_richtext(sess, msgclass, recipients_count, recipients, message, NULL, 0); +} + +/* + * gg_send_message_confer_richtext() + * + * wysyła kolorową wiadomość do kilku użytkownikow (konferencja). zwraca + * losowy numer sekwencyjny, który można zignorować albo wykorzystać do + * potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomości + * - recipients_count - ilość adresatów + * - recipients - numerki adresatów + * - 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_confer_richtext(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen) +{ + struct gg_send_msg s; + struct gg_msg_recipients r; + int i, j, k; + uin_t *recps; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message_confer_richtext(%p, %d, %d, %p, %p, %p, %d);\n", sess, msgclass, recipients_count, recipients, message, format, formatlen); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!message || recipients_count <= 0 || recipients_count > 0xffff || !recipients) { + errno = EINVAL; + return -1; + } + + r.flag = 0x01; + r.count = gg_fix32(recipients_count - 1); + + if (!sess->seq) + sess->seq = 0x01740000 | (rand() & 0xffff); + s.seq = gg_fix32(sess->seq); + s.msgclass = gg_fix32(msgclass); + + recps = malloc(sizeof(uin_t) * recipients_count); + if (!recps) + return -1; + + for (i = 0; i < recipients_count; i++) { + + s.recipient = gg_fix32(recipients[i]); + + for (j = 0, k = 0; j < recipients_count; j++) + if (recipients[j] != recipients[i]) { + recps[k] = gg_fix32(recipients[j]); + k++; + } + + if (!i) + sess->seq += (rand() % 0x300) + 0x300; + + if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, strlen(message) + 1, &r, sizeof(r), recps, (recipients_count - 1) * sizeof(uin_t), format, formatlen, NULL) == -1) { + free(recps); + return -1; + } + } + + free(recps); + + return gg_fix32(s.seq); +} + +/* + * gg_ping() + * + * wysyła do serwera pakiet ping. + * + * - sess - opis sesji + * + * 0, -1. + */ +int gg_ping(struct gg_session *sess) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_ping(%p);\n", sess); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + return gg_send_packet(sess, GG_PING, NULL); +} + +/* + * gg_notify_ex() + * + * wysyła serwerowi listę kontaktów (wraz z odpowiadającymi im typami userów), + * dzięki czemu wie, czyj stan nas interesuje. + * + * - sess - opis sesji + * - userlist - wskaźnik do tablicy numerów + * - types - wskaźnik do tablicy typów użytkowników + * - count - ilość numerków + * + * 0, -1. + */ +int gg_notify_ex(struct gg_session *sess, uin_t *userlist, char *types, int count) +{ + struct gg_notify *n; + uin_t *u; + char *t; + int i, res = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_notify_ex(%p, %p, %p, %d);\n", sess, userlist, types, count); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!userlist || !count) + return gg_send_packet(sess, GG_LIST_EMPTY, NULL); + + while (count > 0) { + int part_count, packet_type; + + if (count > 400) { + part_count = 400; + packet_type = GG_NOTIFY_FIRST; + } else { + part_count = count; + packet_type = GG_NOTIFY_LAST; + } + + if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count))) + return -1; + + for (u = userlist, t = types, i = 0; i < part_count; u++, t++, i++) { + n[i].uin = gg_fix32(*u); + n[i].dunno1 = *t; + } + + if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) { + free(n); + res = -1; + break; + } + + count -= part_count; + userlist += part_count; + types += part_count; + + free(n); + } + + return res; +} + +/* + * gg_notify() + * + * wysyła serwerowi listę kontaktów, dzięki czemu wie, czyj stan nas + * interesuje. + * + * - sess - opis sesji + * - userlist - wskaźnik do tablicy numerów + * - count - ilość numerków + * + * 0, -1. + */ +int gg_notify(struct gg_session *sess, uin_t *userlist, int count) +{ + struct gg_notify *n; + uin_t *u; + int i, res = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_notify(%p, %p, %d);\n", sess, userlist, count); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!userlist || !count) + return gg_send_packet(sess, GG_LIST_EMPTY, NULL); + + while (count > 0) { + int part_count, packet_type; + + if (count > 400) { + part_count = 400; + packet_type = GG_NOTIFY_FIRST; + } else { + part_count = count; + packet_type = GG_NOTIFY_LAST; + } + + if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count))) + return -1; + + for (u = userlist, i = 0; i < part_count; u++, i++) { + n[i].uin = gg_fix32(*u); + n[i].dunno1 = GG_USER_NORMAL; + } + + if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) { + res = -1; + free(n); + break; + } + + free(n); + + userlist += part_count; + count -= part_count; + } + + return res; +} + +/* + * gg_add_notify_ex() + * + * dodaje do listy kontaktów dany numer w trakcie połączenia. + * dodawanemu użytkownikowi określamy jego typ (patrz protocol.html) + * + * - sess - opis sesji + * - uin - numer + * - type - typ + * + * 0, -1. + */ +int gg_add_notify_ex(struct gg_session *sess, uin_t uin, char type) +{ + struct gg_add_remove a; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_add_notify_ex(%p, %u, %d);\n", sess, uin, type); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + a.uin = gg_fix32(uin); + a.dunno1 = type; + + return gg_send_packet(sess, GG_ADD_NOTIFY, &a, sizeof(a), NULL); +} + +/* + * gg_add_notify() + * + * dodaje do listy kontaktów dany numer w trakcie połączenia. + * + * - sess - opis sesji + * - uin - numer + * + * 0, -1. + */ +int gg_add_notify(struct gg_session *sess, uin_t uin) +{ + return gg_add_notify_ex(sess, uin, GG_USER_NORMAL); +} + +/* + * gg_remove_notify_ex() + * + * usuwa z listy kontaktów w trakcie połączenia. + * usuwanemu użytkownikowi określamy jego typ (patrz protocol.html) + * + * - sess - opis sesji + * - uin - numer + * - type - typ + * + * 0, -1. + */ +int gg_remove_notify_ex(struct gg_session *sess, uin_t uin, char type) +{ + struct gg_add_remove a; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_remove_notify_ex(%p, %u, %d);\n", sess, uin, type); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + a.uin = gg_fix32(uin); + a.dunno1 = type; + + return gg_send_packet(sess, GG_REMOVE_NOTIFY, &a, sizeof(a), NULL); +} + +/* + * gg_remove_notify() + * + * usuwa z listy kontaktów w trakcie połączenia. + * + * - sess - opis sesji + * - uin - numer + * + * 0, -1. + */ +int gg_remove_notify(struct gg_session *sess, uin_t uin) +{ + return gg_remove_notify_ex(sess, uin, GG_USER_NORMAL); +} + +/* + * 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 = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + 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); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/lib/libgadu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/lib/libgadu.h Sun Aug 28 22:46:01 2005 +0000 @@ -0,0 +1,1308 @@ +/* $Id: libgadu.h 13582 2005-08-28 22:46:01Z boler $ */ + +/* + * (C) Copyright 2001-2003 Wojtek Kaniewski + * Robert J. Woźny + * Arkadiusz Miśkiewicz + * Tomasz Chiliński + * Piotr Wysocki + * Dawid Jarosz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef __GG_LIBGADU_H +#define __GG_LIBGADU_H + +#ifdef __cplusplus +#ifdef _WIN32 +#pragma pack(push, 1) +#endif +extern "C" { +#endif + +#include +#include +#include +#include + +#ifdef __GG_LIBGADU_HAVE_OPENSSL +#include +#endif + +/* + * typedef uin_t + * + * typ reprezentujący numer osoby. + */ +typedef uint32_t uin_t; + +/* + * ogólna struktura opisująca różne sesje. przydatna w klientach. + */ +#define gg_common_head(x) \ + int fd; /* podglądany deskryptor */ \ + int check; /* sprawdzamy zapis czy odczyt */ \ + int state; /* aktualny stan maszynki */ \ + int error; /* kod błędu dla GG_STATE_ERROR */ \ + int type; /* rodzaj sesji */ \ + int id; /* identyfikator */ \ + int timeout; /* sugerowany timeout w sekundach */ \ + int (*callback)(x*); /* callback przy zmianach */ \ + void (*destroy)(x*); /* funkcja niszczenia */ + +struct gg_common { + gg_common_head(struct gg_common) +}; + +struct gg_image_queue; + +/* + * struct gg_session + * + * struktura opisująca daną sesję. tworzona przez gg_login(), zwalniana + * przez gg_free_session(). + */ +struct gg_session { + gg_common_head(struct gg_session) + + int async; /* czy połączenie jest asynchroniczne */ + int pid; /* pid procesu resolvera */ + int port; /* port, z którym się łączymy */ + int seq; /* numer sekwencyjny ostatniej wiadomości */ + int last_pong; /* czas otrzymania ostatniego ping/pong */ + int last_event; /* czas otrzymania ostatniego pakietu */ + + struct gg_event *event; /* zdarzenie po ->callback() */ + + uint32_t proxy_addr; /* adres proxy, keszowany */ + uint16_t proxy_port; /* port proxy */ + + uint32_t hub_addr; /* adres huba po resolvnięciu */ + uint32_t server_addr; /* adres serwera, od huba */ + + uint32_t client_addr; /* adres klienta */ + uint16_t client_port; /* port, na którym klient słucha */ + + uint32_t external_addr; /* adres zewnetrzny klienta */ + uint16_t external_port; /* port zewnetrzny klienta */ + + uin_t uin; /* numerek klienta */ + char *password; /* i jego hasło. zwalniane automagicznie */ + + int initial_status; /* początkowy stan klienta */ + int status; /* aktualny stan klienta */ + + char *recv_buf; /* bufor na otrzymywane pakiety */ + int recv_done; /* ile już wczytano do bufora */ + int recv_left; /* i ile jeszcze trzeba wczytać */ + + int protocol_version; /* wersja używanego protokołu */ + char *client_version; /* wersja używanego klienta */ + int last_sysmsg; /* ostatnia wiadomość systemowa */ + + char *initial_descr; /* początkowy opis stanu klienta */ + + void *resolver; /* wskaźnik na informacje resolvera */ + + char *header_buf; /* bufor na początek nagłówka */ + unsigned int header_done;/* ile już mamy */ + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + SSL *ssl; /* sesja TLS */ + SSL_CTX *ssl_ctx; /* kontekst sesji? */ +#else + void *ssl; /* zachowujemy ABI */ + void *ssl_ctx; +#endif + + int image_size; /* maksymalny rozmiar obrazków w KiB */ + + char *userlist_reply; /* fragment odpowiedzi listy kontaktów */ + + int userlist_blocks; /* na ile kawałków podzielono listę kontaktów */ + + struct gg_image_queue *images; /* aktualnie wczytywane obrazki */ +}; + +/* + * struct gg_http + * + * ogólna struktura opisująca stan wszystkich operacji HTTP. tworzona + * przez gg_http_connect(), zwalniana przez gg_http_free(). + */ +struct gg_http { + gg_common_head(struct gg_http) + + int async; /* czy połączenie asynchroniczne */ + int pid; /* pid procesu resolvera */ + int port; /* port, z którym się łączymy */ + + char *query; /* bufor zapytania http */ + char *header; /* bufor nagłówka */ + int header_size; /* rozmiar wczytanego nagłówka */ + char *body; /* bufor otrzymanych informacji */ + unsigned int body_size; /* oczekiwana ilość informacji */ + + void *data; /* dane danej operacji http */ + + char *user_data; /* dane użytkownika, nie są zwalniane przez gg_http_free() */ + + void *resolver; /* wskaźnik na informacje resolvera */ + + unsigned int body_done; /* ile już treści odebrano? */ +}; + +#ifdef __GNUC__ +#define GG_PACKED __attribute__ ((packed)) +#else +#define GG_PACKED +#endif + +#define GG_MAX_PATH 276 + +/* + * struct gg_file_info + * + * odpowiednik windowsowej struktury WIN32_FIND_DATA niezbędnej przy + * wysyłaniu plików. + */ +struct gg_file_info { + uint32_t mode; /* dwFileAttributes */ + uint32_t ctime[2]; /* ftCreationTime */ + uint32_t atime[2]; /* ftLastAccessTime */ + uint32_t mtime[2]; /* ftLastWriteTime */ + uint32_t size_hi; /* nFileSizeHigh */ + uint32_t size; /* nFileSizeLow */ + uint32_t reserved0; /* dwReserved0 */ + uint32_t reserved1; /* dwReserved1 */ + unsigned char filename[GG_MAX_PATH - 14]; /* cFileName */ + unsigned char short_filename[14]; /* cAlternateFileName */ +} GG_PACKED; + +/* + * struct gg_dcc + * + * struktura opisująca nasłuchujące gniazdo połączeń między klientami. + * tworzona przez gg_dcc_socket_create(), zwalniana przez gg_dcc_free(). + */ +struct gg_dcc { + gg_common_head(struct gg_dcc) + + struct gg_event *event; /* opis zdarzenia */ + + int active; /* czy to my się łączymy? */ + int port; /* port, na którym siedzi */ + uin_t uin; /* uin klienta */ + uin_t peer_uin; /* uin drugiej strony */ + int file_fd; /* deskryptor pliku */ + unsigned int offset; /* offset w pliku */ + unsigned int chunk_size;/* rozmiar kawałka */ + unsigned int chunk_offset;/* offset w aktualnym kawałku */ + struct gg_file_info file_info; + /* informacje o pliku */ + int established; /* połączenie ustanowione */ + char *voice_buf; /* bufor na pakiet połączenia głosowego */ + int incoming; /* połączenie przychodzące */ + char *chunk_buf; /* bufor na kawałek danych */ + uint32_t remote_addr; /* adres drugiej strony */ + uint16_t remote_port; /* port drugiej strony */ +}; + +/* + * enum gg_session_t + * + * rodzaje sesji. + */ +enum gg_session_t { + GG_SESSION_GG = 1, /* połączenie z serwerem gg */ + GG_SESSION_HTTP, /* ogólna sesja http */ + GG_SESSION_SEARCH, /* szukanie */ + GG_SESSION_REGISTER, /* rejestrowanie */ + GG_SESSION_REMIND, /* przypominanie hasła */ + GG_SESSION_PASSWD, /* zmiana hasła */ + GG_SESSION_CHANGE, /* zmiana informacji o sobie */ + GG_SESSION_DCC, /* ogólne połączenie DCC */ + GG_SESSION_DCC_SOCKET, /* nasłuchujący socket */ + GG_SESSION_DCC_SEND, /* wysyłanie pliku */ + GG_SESSION_DCC_GET, /* odbieranie pliku */ + GG_SESSION_DCC_VOICE, /* rozmowa głosowa */ + GG_SESSION_USERLIST_GET, /* pobieranie userlisty */ + GG_SESSION_USERLIST_PUT, /* wysyłanie userlisty */ + GG_SESSION_UNREGISTER, /* usuwanie konta */ + GG_SESSION_USERLIST_REMOVE, /* usuwanie userlisty */ + GG_SESSION_TOKEN, /* pobieranie tokenu */ + + GG_SESSION_USER0 = 256, /* zdefiniowana dla użytkownika */ + GG_SESSION_USER1, /* j.w. */ + GG_SESSION_USER2, /* j.w. */ + GG_SESSION_USER3, /* j.w. */ + GG_SESSION_USER4, /* j.w. */ + GG_SESSION_USER5, /* j.w. */ + GG_SESSION_USER6, /* j.w. */ + GG_SESSION_USER7 /* j.w. */ +}; + +/* + * enum gg_state_t + * + * opisuje stan asynchronicznej maszyny. + */ +enum gg_state_t { + /* wspólne */ + GG_STATE_IDLE = 0, /* nie powinno wystąpić. */ + GG_STATE_RESOLVING, /* wywołał gethostbyname() */ + GG_STATE_CONNECTING, /* wywołał connect() */ + GG_STATE_READING_DATA, /* czeka na dane http */ + GG_STATE_ERROR, /* wystąpił błąd. kod w x->error */ + + /* gg_session */ + GG_STATE_CONNECTING_HUB, /* wywołał connect() na huba */ + GG_STATE_CONNECTING_GG, /* wywołał connect() na serwer */ + GG_STATE_READING_KEY, /* czeka na klucz */ + GG_STATE_READING_REPLY, /* czeka na odpowiedź */ + GG_STATE_CONNECTED, /* połączył się */ + + /* gg_http */ + GG_STATE_SENDING_QUERY, /* wysyła zapytanie http */ + GG_STATE_READING_HEADER, /* czeka na nagłówek http */ + GG_STATE_PARSING, /* przetwarza dane */ + GG_STATE_DONE, /* skończył */ + + /* gg_dcc */ + GG_STATE_LISTENING, /* czeka na połączenia */ + GG_STATE_READING_UIN_1, /* czeka na uin peera */ + GG_STATE_READING_UIN_2, /* czeka na swój uin */ + GG_STATE_SENDING_ACK, /* wysyła potwierdzenie dcc */ + GG_STATE_READING_ACK, /* czeka na potwierdzenie dcc */ + GG_STATE_READING_REQUEST, /* czeka na komendę */ + GG_STATE_SENDING_REQUEST, /* wysyła komendę */ + GG_STATE_SENDING_FILE_INFO, /* wysyła informacje o pliku */ + GG_STATE_READING_PRE_FILE_INFO, /* czeka na pakiet przed file_info */ + GG_STATE_READING_FILE_INFO, /* czeka na informacje o pliku */ + GG_STATE_SENDING_FILE_ACK, /* wysyła potwierdzenie pliku */ + GG_STATE_READING_FILE_ACK, /* czeka na potwierdzenie pliku */ + GG_STATE_SENDING_FILE_HEADER, /* wysyła nagłówek pliku */ + GG_STATE_READING_FILE_HEADER, /* czeka na nagłówek */ + GG_STATE_GETTING_FILE, /* odbiera plik */ + GG_STATE_SENDING_FILE, /* wysyła plik */ + GG_STATE_READING_VOICE_ACK, /* czeka na potwierdzenie voip */ + GG_STATE_READING_VOICE_HEADER, /* czeka na rodzaj bloku voip */ + GG_STATE_READING_VOICE_SIZE, /* czeka na rozmiar bloku voip */ + GG_STATE_READING_VOICE_DATA, /* czeka na dane voip */ + GG_STATE_SENDING_VOICE_ACK, /* wysyła potwierdzenie voip */ + GG_STATE_SENDING_VOICE_REQUEST, /* wysyła żądanie voip */ + GG_STATE_READING_TYPE, /* czeka na typ połączenia */ + + /* nowe. bez sensu jest to API. */ + GG_STATE_TLS_NEGOTIATION /* negocjuje połączenie TLS */ +}; + +/* + * enum gg_check_t + * + * informuje, co proces klienta powinien sprawdzić na deskryptorze danego + * połączenia. + */ +enum gg_check_t { + GG_CHECK_NONE = 0, /* nic. nie powinno wystąpić */ + GG_CHECK_WRITE = 1, /* sprawdzamy możliwość zapisu */ + GG_CHECK_READ = 2 /* sprawdzamy możliwość odczytu */ +}; + +/* + * struct gg_login_params + * + * parametry gg_login(). przeniesiono do struktury, żeby uniknąć problemów + * z ciągłymi zmianami API, gdy dodano coś nowego do protokołu. + */ +struct gg_login_params { + uin_t uin; /* numerek */ + char *password; /* hasło */ + int async; /* asynchroniczne sockety? */ + int status; /* początkowy status klienta */ + char *status_descr; /* opis statusu */ + uint32_t server_addr; /* adres serwera gg */ + uint16_t server_port; /* port serwera gg */ + uint32_t client_addr; /* adres dcc klienta */ + uint16_t client_port; /* port dcc klienta */ + int protocol_version; /* wersja protokołu */ + char *client_version; /* wersja klienta */ + int has_audio; /* czy ma dźwięk? */ + int last_sysmsg; /* ostatnia wiadomość systemowa */ + uint32_t external_addr; /* adres widziany na zewnatrz */ + uint16_t external_port; /* port widziany na zewnatrz */ + int tls; /* czy łączymy po TLS? */ + int image_size; /* maksymalny rozmiar obrazka w KiB */ + int era_omnix; /* czy udawać klienta era omnix? */ + + char dummy[6 * sizeof(int)]; /* miejsce na kolejnych 6 zmiennych, + * żeby z dodaniem parametru nie + * zmieniał się rozmiar struktury */ +}; + +struct gg_session *gg_login(const struct gg_login_params *p); +void gg_free_session(struct gg_session *sess); +void gg_logoff(struct gg_session *sess); +int gg_change_status(struct gg_session *sess, int status); +int gg_change_status_descr(struct gg_session *sess, int status, const char *descr); +int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time); +int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message); +int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen); +int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message); +int gg_send_message_confer_richtext(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen); +int gg_send_message_ctcp(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, int message_len); +int gg_ping(struct gg_session *sess); +int gg_userlist_request(struct gg_session *sess, char type, const char *request); +int gg_image_request(struct gg_session *sess, uin_t recipient, int size, uint32_t crc32); +int gg_image_reply(struct gg_session *sess, uin_t recipient, const char *filename, const char *image, int size); + +uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len); + +struct gg_image_queue { + uin_t sender; /* nadawca obrazka */ + uint32_t size; /* rozmiar */ + uint32_t crc32; /* suma kontrolna */ + char *filename; /* nazwa pliku */ + char *image; /* bufor z obrazem */ + uint32_t done; /* ile już wczytano */ + + struct gg_image_queue *next; /* następny na liście */ +}; + +/* + * enum gg_event_t + * + * rodzaje zdarzeń. + */ +enum gg_event_t { + GG_EVENT_NONE = 0, /* nic się nie wydarzyło */ + GG_EVENT_MSG, /* otrzymano wiadomość */ + GG_EVENT_NOTIFY, /* ktoś się pojawił */ + GG_EVENT_NOTIFY_DESCR, /* ktoś się pojawił z opisem */ + GG_EVENT_STATUS, /* ktoś zmienił stan */ + GG_EVENT_ACK, /* potwierdzenie wysłania wiadomości */ + GG_EVENT_PONG, /* pakiet pong */ + GG_EVENT_CONN_FAILED, /* połączenie się nie udało */ + GG_EVENT_CONN_SUCCESS, /* połączenie się powiodło */ + GG_EVENT_DISCONNECT, /* serwer zrywa połączenie */ + + GG_EVENT_DCC_NEW, /* nowe połączenie między klientami */ + GG_EVENT_DCC_ERROR, /* błąd połączenia między klientami */ + GG_EVENT_DCC_DONE, /* zakończono połączenie */ + GG_EVENT_DCC_CLIENT_ACCEPT, /* moment akceptacji klienta */ + GG_EVENT_DCC_CALLBACK, /* klient się połączył na żądanie */ + GG_EVENT_DCC_NEED_FILE_INFO, /* należy wypełnić file_info */ + GG_EVENT_DCC_NEED_FILE_ACK, /* czeka na potwierdzenie pliku */ + GG_EVENT_DCC_NEED_VOICE_ACK, /* czeka na potwierdzenie rozmowy */ + GG_EVENT_DCC_VOICE_DATA, /* ramka danych rozmowy głosowej */ + + GG_EVENT_PUBDIR50_SEARCH_REPLY, /* odpowiedz wyszukiwania */ + GG_EVENT_PUBDIR50_READ, /* odczytano własne dane z katalogu */ + GG_EVENT_PUBDIR50_WRITE, /* wpisano własne dane do katalogu */ + + GG_EVENT_STATUS60, /* ktoś zmienił stan w GG 6.0 */ + GG_EVENT_NOTIFY60, /* ktoś się pojawił w GG 6.0 */ + GG_EVENT_USERLIST, /* odpowiedź listy kontaktów w GG 6.0 */ + GG_EVENT_IMAGE_REQUEST, /* prośba o wysłanie obrazka GG 6.0 */ + GG_EVENT_IMAGE_REPLY, /* podesłany obrazek GG 6.0 */ + GG_EVENT_DCC_ACK /* potwierdzenie transmisji */ +}; + +#define GG_EVENT_SEARCH50_REPLY GG_EVENT_PUBDIR50_SEARCH_REPLY + +/* + * enum gg_failure_t + * + * określa powód nieudanego połączenia. + */ +enum gg_failure_t { + GG_FAILURE_RESOLVING = 1, /* nie znaleziono serwera */ + GG_FAILURE_CONNECTING, /* nie można się połączyć */ + GG_FAILURE_INVALID, /* serwer zwrócił nieprawidłowe dane */ + GG_FAILURE_READING, /* zerwano połączenie podczas odczytu */ + GG_FAILURE_WRITING, /* zerwano połączenie podczas zapisu */ + GG_FAILURE_PASSWORD, /* nieprawidłowe hasło */ + GG_FAILURE_404, /* XXX nieużywane */ + GG_FAILURE_TLS, /* błąd negocjacji TLS */ + GG_FAILURE_NEED_EMAIL /* serwer rozłączył nas z prośbą o zmianę emaila */ +}; + +/* + * enum gg_error_t + * + * określa rodzaj błędu wywołanego przez daną operację. nie zawiera + * przesadnie szczegółowych informacji o powodzie błędu, by nie komplikować + * obsługi błędów. jeśli wymagana jest większa dokładność, należy sprawdzić + * zawartość zmiennej errno. + */ +enum gg_error_t { + GG_ERROR_RESOLVING = 1, /* błąd znajdowania hosta */ + GG_ERROR_CONNECTING, /* błąd łaczenia się */ + GG_ERROR_READING, /* błąd odczytu */ + GG_ERROR_WRITING, /* błąd wysyłania */ + + GG_ERROR_DCC_HANDSHAKE, /* błąd negocjacji */ + GG_ERROR_DCC_FILE, /* błąd odczytu/zapisu pliku */ + GG_ERROR_DCC_EOF, /* plik się skończył? */ + GG_ERROR_DCC_NET, /* błąd wysyłania/odbierania */ + GG_ERROR_DCC_REFUSED /* połączenie odrzucone przez usera */ +}; + +/* + * struktury dotyczące wyszukiwania w GG 5.0. NIE NALEŻY SIĘ DO NICH + * ODWOŁYWAĆ BEZPOŚREDNIO! do dostępu do nich służą funkcje gg_pubdir50_*() + */ +struct gg_pubdir50_entry { + int num; + char *field; + char *value; +}; + +struct gg_pubdir50_s { + int count; + uin_t next; + int type; + uint32_t seq; + struct gg_pubdir50_entry *entries; + int entries_count; +}; + +/* + * typedef gg_pubdir_50_t + * + * typ opisujący zapytanie lub wynik zapytania katalogu publicznego + * z protokołu GG 5.0. nie należy się odwoływać bezpośrednio do jego + * pól -- służą do tego funkcje gg_pubdir50_*() + */ +typedef struct gg_pubdir50_s *gg_pubdir50_t; + +/* + * struct gg_event + * + * struktura opisująca rodzaj zdarzenia. wychodzi z gg_watch_fd() lub + * z gg_dcc_watch_fd() + */ +struct gg_event { + int type; /* rodzaj zdarzenia -- gg_event_t */ + union { /* @event */ + struct gg_notify_reply *notify; /* informacje o liście kontaktów -- GG_EVENT_NOTIFY */ + + enum gg_failure_t failure; /* błąd połączenia -- GG_EVENT_FAILURE */ + + struct gg_dcc *dcc_new; /* nowe połączenie bezpośrednie -- GG_EVENT_DCC_NEW */ + + int dcc_error; /* błąd połączenia bezpośredniego -- GG_EVENT_DCC_ERROR */ + + gg_pubdir50_t pubdir50; /* wynik operacji związanej z katalogiem publicznym -- GG_EVENT_PUBDIR50_* */ + + struct { /* @msg odebrano wiadomość -- GG_EVENT_MSG */ + uin_t sender; /* numer nadawcy */ + int msgclass; /* klasa wiadomości */ + time_t time; /* czas nadania */ + unsigned char *message; /* treść wiadomości */ + + int recipients_count; /* ilość odbiorców konferencji */ + uin_t *recipients; /* odbiorcy konferencji */ + + int formats_length; /* długość informacji o formatowaniu tekstu */ + void *formats; /* informacje o formatowaniu tekstu */ + } msg; + + struct { /* @notify_descr informacje o liście kontaktów z opisami stanu -- GG_EVENT_NOTIFY_DESCR */ + struct gg_notify_reply *notify; /* informacje o liście kontaktów */ + char *descr; /* opis stanu */ + } notify_descr; + + struct { /* @status zmiana stanu -- GG_EVENT_STATUS */ + uin_t uin; /* numer */ + uint32_t status; /* nowy stan */ + char *descr; /* opis stanu */ + } status; + + struct { /* @status60 zmiana stanu -- GG_EVENT_STATUS60 */ + uin_t uin; /* numer */ + int status; /* nowy stan */ + uint32_t remote_ip; /* adres ip */ + uint16_t remote_port; /* port */ + int version; /* wersja klienta */ + int image_size; /* maksymalny rozmiar grafiki w KiB */ + char *descr; /* opis stanu */ + time_t time; /* czas powrotu */ + } status60; + + struct { /* @notify60 informacja o liście kontaktów -- GG_EVENT_NOTIFY60 */ + uin_t uin; /* numer */ + int status; /* stan */ + uint32_t remote_ip; /* adres ip */ + uint16_t remote_port; /* port */ + int version; /* wersja klienta */ + int image_size; /* maksymalny rozmiar grafiki w KiB */ + char *descr; /* opis stanu */ + time_t time; /* czas powrotu */ + } *notify60; + + struct { /* @ack potwierdzenie wiadomości -- GG_EVENT_ACK */ + uin_t recipient; /* numer odbiorcy */ + int status; /* stan doręczenia wiadomości */ + int seq; /* numer sekwencyjny wiadomości */ + } ack; + + struct { /* @dcc_voice_data otrzymano dane dźwiękowe -- GG_EVENT_DCC_VOICE_DATA */ + uint8_t *data; /* dane dźwiękowe */ + int length; /* ilość danych dźwiękowych */ + } dcc_voice_data; + + struct { /* @userlist odpowiedź listy kontaktów serwera */ + char type; /* rodzaj odpowiedzi */ + char *reply; /* treść odpowiedzi */ + } userlist; + + struct { /* @image_request prośba o obrazek */ + uin_t sender; /* nadawca prośby */ + uint32_t size; /* rozmiar obrazka */ + uint32_t crc32; /* suma kontrolna */ + } image_request; + + struct { /* @image_reply odpowiedź z obrazkiem */ + uin_t sender; /* nadawca odpowiedzi */ + uint32_t size; /* rozmiar obrazka */ + uint32_t crc32; /* suma kontrolna */ + char *filename; /* nazwa pliku */ + char *image; /* bufor z obrazkiem */ + } image_reply; + } event; +}; + +struct gg_event *gg_watch_fd(struct gg_session *sess); +void gg_event_free(struct gg_event *e); +#define gg_free_event gg_event_free + +/* + * funkcje obsługi listy kontaktów. + */ +int gg_notify_ex(struct gg_session *sess, uin_t *userlist, char *types, int count); +int gg_notify(struct gg_session *sess, uin_t *userlist, int count); +int gg_add_notify_ex(struct gg_session *sess, uin_t uin, char type); +int gg_add_notify(struct gg_session *sess, uin_t uin); +int gg_remove_notify_ex(struct gg_session *sess, uin_t uin, char type); +int gg_remove_notify(struct gg_session *sess, uin_t uin); + +/* + * funkcje obsługi http. + */ +struct gg_http *gg_http_connect(const char *hostname, int port, int async, const char *method, const char *path, const char *header); +int gg_http_watch_fd(struct gg_http *h); +void gg_http_stop(struct gg_http *h); +void gg_http_free(struct gg_http *h); +void gg_http_free_fields(struct gg_http *h); +#define gg_free_http gg_http_free + +/* + * struktury opisująca kryteria wyszukiwania dla gg_search(). nieaktualne, + * zastąpione przez gg_pubdir50_t. pozostawiono je dla zachowania ABI. + */ +struct gg_search_request { + int active; + unsigned int start; + char *nickname; + char *first_name; + char *last_name; + char *city; + int gender; + int min_birth; + int max_birth; + char *email; + char *phone; + uin_t uin; +}; + +struct gg_search { + int count; + struct gg_search_result *results; +}; + +struct gg_search_result { + uin_t uin; + char *first_name; + char *last_name; + char *nickname; + int born; + int gender; + char *city; + int active; +}; + +#define GG_GENDER_NONE 0 +#define GG_GENDER_FEMALE 1 +#define GG_GENDER_MALE 2 + +/* + * funkcje wyszukiwania. + */ +struct gg_http *gg_search(const struct gg_search_request *r, int async); +int gg_search_watch_fd(struct gg_http *f); +void gg_free_search(struct gg_http *f); +#define gg_search_free gg_free_search + +const struct gg_search_request *gg_search_request_mode_0(char *nickname, char *first_name, char *last_name, char *city, int gender, int min_birth, int max_birth, int active, int start); +const struct gg_search_request *gg_search_request_mode_1(char *email, int active, int start); +const struct gg_search_request *gg_search_request_mode_2(char *phone, int active, int start); +const struct gg_search_request *gg_search_request_mode_3(uin_t uin, int active, int start); +void gg_search_request_free(struct gg_search_request *r); + +/* + * funkcje obsługi katalogu publicznego zgodne z GG 5.0. tym razem funkcje + * zachowują pewien poziom abstrakcji, żeby uniknąć zmian ABI przy zmianach + * w protokole. + * + * NIE NALEŻY SIĘ ODWOŁYWAĆ DO PÓL gg_pubdir50_t BEZPOŚREDNIO! + */ +uint32_t gg_pubdir50(struct gg_session *sess, gg_pubdir50_t req); +gg_pubdir50_t gg_pubdir50_new(int type); +int gg_pubdir50_add(gg_pubdir50_t req, const char *field, const char *value); +int gg_pubdir50_seq_set(gg_pubdir50_t req, uint32_t seq); +const char *gg_pubdir50_get(gg_pubdir50_t res, int num, const char *field); +int gg_pubdir50_type(gg_pubdir50_t res); +int gg_pubdir50_count(gg_pubdir50_t res); +uin_t gg_pubdir50_next(gg_pubdir50_t res); +uint32_t gg_pubdir50_seq(gg_pubdir50_t res); +void gg_pubdir50_free(gg_pubdir50_t res); + +#define GG_PUBDIR50_UIN "FmNumber" +#define GG_PUBDIR50_STATUS "FmStatus" +#define GG_PUBDIR50_FIRSTNAME "firstname" +#define GG_PUBDIR50_LASTNAME "lastname" +#define GG_PUBDIR50_NICKNAME "nickname" +#define GG_PUBDIR50_BIRTHYEAR "birthyear" +#define GG_PUBDIR50_CITY "city" +#define GG_PUBDIR50_GENDER "gender" +#define GG_PUBDIR50_GENDER_FEMALE "1" +#define GG_PUBDIR50_GENDER_MALE "2" +#define GG_PUBDIR50_GENDER_SET_FEMALE "2" +#define GG_PUBDIR50_GENDER_SET_MALE "1" +#define GG_PUBDIR50_ACTIVE "ActiveOnly" +#define GG_PUBDIR50_ACTIVE_TRUE "1" +#define GG_PUBDIR50_START "fmstart" +#define GG_PUBDIR50_FAMILYNAME "familyname" +#define GG_PUBDIR50_FAMILYCITY "familycity" + +int gg_pubdir50_handle_reply(struct gg_event *e, const char *packet, int length); + +/* + * struct gg_pubdir + * + * operacje na katalogu publicznym. + */ +struct gg_pubdir { + int success; /* czy się udało */ + uin_t uin; /* otrzymany numerek. 0 jeśli błąd */ +}; + +/* ogólne funkcje, nie powinny być używane */ +int gg_pubdir_watch_fd(struct gg_http *f); +void gg_pubdir_free(struct gg_http *f); +#define gg_free_pubdir gg_pubdir_free + +struct gg_token { + int width; /* szerokość obrazka */ + int height; /* wysokość obrazka */ + int length; /* ilość znaków w tokenie */ + char *tokenid; /* id tokenu */ +}; + +/* funkcje dotyczące tokenów */ +struct gg_http *gg_token(int async); +int gg_token_watch_fd(struct gg_http *h); +void gg_token_free(struct gg_http *h); + +/* rejestracja nowego numerka */ +struct gg_http *gg_register(const char *email, const char *password, int async); +struct gg_http *gg_register2(const char *email, const char *password, const char *qa, int async); +struct gg_http *gg_register3(const char *email, const char *password, const char *tokenid, const char *tokenval, int async); +#define gg_register_watch_fd gg_pubdir_watch_fd +#define gg_register_free gg_pubdir_free +#define gg_free_register gg_pubdir_free + +struct gg_http *gg_unregister(uin_t uin, const char *password, const char *email, int async); +struct gg_http *gg_unregister2(uin_t uin, const char *password, const char *qa, int async); +struct gg_http *gg_unregister3(uin_t uin, const char *password, const char *tokenid, const char *tokenval, int async); +#define gg_unregister_watch_fd gg_pubdir_watch_fd +#define gg_unregister_free gg_pubdir_free + +/* przypomnienie hasła e-mailem */ +struct gg_http *gg_remind_passwd(uin_t uin, int async); +struct gg_http *gg_remind_passwd2(uin_t uin, const char *tokenid, const char *tokenval, int async); +struct gg_http *gg_remind_passwd3(uin_t uin, const char *email, const char *tokenid, const char *tokenval, int async); +#define gg_remind_passwd_watch_fd gg_pubdir_watch_fd +#define gg_remind_passwd_free gg_pubdir_free +#define gg_free_remind_passwd gg_pubdir_free + +/* zmiana hasła */ +struct gg_http *gg_change_passwd(uin_t uin, const char *passwd, const char *newpasswd, const char *newemail, int async); +struct gg_http *gg_change_passwd2(uin_t uin, const char *passwd, const char *newpasswd, const char *email, const char *newemail, int async); +struct gg_http *gg_change_passwd3(uin_t uin, const char *passwd, const char *newpasswd, const char *qa, int async); +struct gg_http *gg_change_passwd4(uin_t uin, const char *email, const char *passwd, const char *newpasswd, const char *tokenid, const char *tokenval, int async); +#define gg_change_passwd_free gg_pubdir_free +#define gg_free_change_passwd gg_pubdir_free + +/* + * struct gg_change_info_request + * + * opis żądania zmiany informacji w katalogu publicznym. + */ +struct gg_change_info_request { + char *first_name; /* imię */ + char *last_name; /* nazwisko */ + char *nickname; /* pseudonim */ + char *email; /* email */ + int born; /* rok urodzenia */ + int gender; /* płeć */ + char *city; /* miasto */ +}; + +struct gg_change_info_request *gg_change_info_request_new(const char *first_name, const char *last_name, const char *nickname, const char *email, int born, int gender, const char *city); +void gg_change_info_request_free(struct gg_change_info_request *r); + +struct gg_http *gg_change_info(uin_t uin, const char *passwd, const struct gg_change_info_request *request, int async); +#define gg_change_pubdir_watch_fd gg_pubdir_watch_fd +#define gg_change_pubdir_free gg_pubdir_free +#define gg_free_change_pubdir gg_pubdir_free + +/* + * funkcje dotyczące listy kontaktów na serwerze. + */ +struct gg_http *gg_userlist_get(uin_t uin, const char *password, int async); +int gg_userlist_get_watch_fd(struct gg_http *f); +void gg_userlist_get_free(struct gg_http *f); + +struct gg_http *gg_userlist_put(uin_t uin, const char *password, const char *contacts, int async); +int gg_userlist_put_watch_fd(struct gg_http *f); +void gg_userlist_put_free(struct gg_http *f); + +struct gg_http *gg_userlist_remove(uin_t uin, const char *password, int async); +int gg_userlist_remove_watch_fd(struct gg_http *f); +void gg_userlist_remove_free(struct gg_http *f); + + + +/* + * funkcje dotyczące komunikacji między klientami. + */ +extern int gg_dcc_port; /* port, na którym nasłuchuje klient */ +extern unsigned long gg_dcc_ip; /* adres, na którym nasłuchuje klient */ + +int gg_dcc_request(struct gg_session *sess, uin_t uin); + +struct gg_dcc *gg_dcc_send_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin); +struct gg_dcc *gg_dcc_get_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin); +struct gg_dcc *gg_dcc_voice_chat(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin); +void gg_dcc_set_type(struct gg_dcc *d, int type); +int gg_dcc_fill_file_info(struct gg_dcc *d, const char *filename); +int gg_dcc_fill_file_info2(struct gg_dcc *d, const char *filename, const char *local_filename); +int gg_dcc_voice_send(struct gg_dcc *d, char *buf, int length); + +#define GG_DCC_VOICE_FRAME_LENGTH 195 +#define GG_DCC_VOICE_FRAME_LENGTH_505 326 + +struct gg_dcc *gg_dcc_socket_create(uin_t uin, uint16_t port); +#define gg_dcc_socket_free gg_free_dcc +#define gg_dcc_socket_watch_fd gg_dcc_watch_fd + +struct gg_event *gg_dcc_watch_fd(struct gg_dcc *d); + +void gg_dcc_free(struct gg_dcc *c); +#define gg_free_dcc gg_dcc_free + +/* + * jeśli chcemy sobie podebugować, wystarczy ustawić `gg_debug_level'. + * niestety w miarę przybywania wpisów `gg_debug(...)' nie chciało mi + * się ustawiać odpowiednich leveli, więc większość szła do _MISC. + */ +extern int gg_debug_level; /* poziom debugowania. mapa bitowa stałych GG_DEBUG_* */ + +/* + * można podać wskaźnik do funkcji obsługującej wywołania gg_debug(). + * nieoficjalne, nieudokumentowane, może się zmienić. jeśli ktoś jest + * zainteresowany, niech da znać na ekg-devel. + */ +extern void (*gg_debug_handler)(int level, const char *format, va_list ap); + +/* + * można podać plik, do którego będą zapisywane teksty z gg_debug(). + */ +extern FILE *gg_debug_file; + +#define GG_DEBUG_NET 1 +#define GG_DEBUG_TRAFFIC 2 +#define GG_DEBUG_DUMP 4 +#define GG_DEBUG_FUNCTION 8 +#define GG_DEBUG_MISC 16 + +#ifdef GG_DEBUG_DISABLE +#define gg_debug(x, y...) do { } while(0) +#else +void gg_debug(int level, const char *format, ...); +#endif + +const char *gg_libgadu_version(void); + +/* + * konfiguracja http proxy. + */ +extern int gg_proxy_enabled; /* włącza obsługę proxy */ +extern char *gg_proxy_host; /* określa adres serwera proxy */ +extern int gg_proxy_port; /* określa port serwera proxy */ +extern char *gg_proxy_username; /* określa nazwę użytkownika przy autoryzacji serwera proxy */ +extern char *gg_proxy_password; /* określa hasło użytkownika przy autoryzacji serwera proxy */ +extern int gg_proxy_http_only; /* włącza obsługę proxy wyłącznie dla usług HTTP */ + + +/* + * adres, z którego ślemy pakiety (np łączymy się z serwerem) + * używany przy gg_connect() + */ +extern unsigned long gg_local_ip; +/* + * ------------------------------------------------------------------------- + * poniżej znajdują się wewnętrzne sprawy biblioteki. zwykły klient nie + * powinien ich w ogóle ruszać, bo i nie ma po co. wszystko można załatwić + * procedurami wyższego poziomu, których definicje znajdują się na początku + * tego pliku. + * ------------------------------------------------------------------------- + */ + +#ifdef __GG_LIBGADU_HAVE_PTHREAD +int gg_resolve_pthread(int *fd, void **resolver, const char *hostname); +#endif + +#ifdef _WIN32 +int gg_thread_socket(int thread_id, int socket); +#endif + +int gg_resolve(int *fd, int *pid, const char *hostname); + +#ifdef __GNUC__ +char *gg_saprintf(const char *format, ...) __attribute__ ((format (printf, 1, 2))); +#else +char *gg_saprintf(const char *format, ...); +#endif + +char *gg_vsaprintf(const char *format, va_list ap); + +#define gg_alloc_sprintf gg_saprintf + +char *gg_get_line(char **ptr); + +int gg_connect(void *addr, int port, int async); +struct in_addr *gg_gethostbyname(const char *hostname); +char *gg_read_line(int sock, char *buf, int length); +void gg_chomp(char *line); +char *gg_urlencode(const char *str); +int gg_http_hash(const char *format, ...); +int gg_read(struct gg_session *sess, char *buf, int length); +int gg_write(struct gg_session *sess, const char *buf, int length); +void *gg_recv_packet(struct gg_session *sess); +int gg_send_packet(struct gg_session *sess, int type, ...); +unsigned int gg_login_hash(const unsigned char *password, unsigned int seed); +uint32_t gg_fix32(uint32_t x); +uint16_t gg_fix16(uint16_t x); +#define fix16 gg_fix16 +#define fix32 gg_fix32 +char *gg_proxy_auth(void); +char *gg_base64_encode(const char *buf); +char *gg_base64_decode(const char *buf); +int gg_image_queue_remove(struct gg_session *s, struct gg_image_queue *q, int freeq); + +#define GG_APPMSG_HOST "appmsg.gadu-gadu.pl" +#define GG_APPMSG_PORT 80 +#define GG_PUBDIR_HOST "pubdir.gadu-gadu.pl" +#define GG_PUBDIR_PORT 80 +#define GG_REGISTER_HOST "register.gadu-gadu.pl" +#define GG_REGISTER_PORT 80 +#define GG_REMIND_HOST "retr.gadu-gadu.pl" +#define GG_REMIND_PORT 80 + +#define GG_DEFAULT_PORT 8074 +#define GG_HTTPS_PORT 443 +#define GG_HTTP_USERAGENT "Mozilla/4.7 [en] (Win98; I)" + +#define GG_DEFAULT_CLIENT_VERSION "6, 1, 0, 158" +#define GG_DEFAULT_PROTOCOL_VERSION 0x24 +#define GG_DEFAULT_TIMEOUT 30 +#define GG_HAS_AUDIO_MASK 0x40000000 +#define GG_ERA_OMNIX_MASK 0x04000000 +#define GG_LIBGADU_VERSION "1.5.20050718" + +#define GG_DEFAULT_DCC_PORT 1550 + +struct gg_header { + uint32_t type; /* typ pakietu */ + uint32_t length; /* długość reszty pakietu */ +} GG_PACKED; + +#define GG_WELCOME 0x0001 +#define GG_NEED_EMAIL 0x0014 + +struct gg_welcome { + uint32_t key; /* klucz szyfrowania hasła */ +} GG_PACKED; + +#define GG_LOGIN 0x000c + +struct gg_login { + uint32_t uin; /* mój numerek */ + uint32_t hash; /* hash hasła */ + uint32_t status; /* status na dzień dobry */ + uint32_t version; /* moja wersja klienta */ + uint32_t local_ip; /* mój adres ip */ + uint16_t local_port; /* port, na którym słucham */ +} GG_PACKED; + +#define GG_LOGIN_EXT 0x0013 + +struct gg_login_ext { + uint32_t uin; /* mój numerek */ + uint32_t hash; /* hash hasła */ + uint32_t status; /* status na dzień dobry */ + uint32_t version; /* moja wersja klienta */ + uint32_t local_ip; /* mój adres ip */ + uint16_t local_port; /* port, na którym słucham */ + uint32_t external_ip; /* zewnętrzny adres ip */ + uint16_t external_port; /* zewnętrzny port */ +} GG_PACKED; + +#define GG_LOGIN60 0x0015 + +struct gg_login60 { + uint32_t uin; /* mój numerek */ + uint32_t hash; /* hash hasła */ + uint32_t status; /* status na dzień dobry */ + uint32_t version; /* moja wersja klienta */ + uint8_t dunno1; /* 0x00 */ + uint32_t local_ip; /* mój adres ip */ + uint16_t local_port; /* port, na którym słucham */ + uint32_t external_ip; /* zewnętrzny adres ip */ + uint16_t external_port; /* zewnętrzny port */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno2; /* 0xbe */ +} GG_PACKED; + +#define GG_LOGIN_OK 0x0003 + +#define GG_LOGIN_FAILED 0x0009 + +#define GG_PUBDIR50_REQUEST 0x0014 + +#define GG_PUBDIR50_WRITE 0x01 +#define GG_PUBDIR50_READ 0x02 +#define GG_PUBDIR50_SEARCH 0x03 +#define GG_PUBDIR50_SEARCH_REQUEST GG_PUBDIR50_SEARCH +#define GG_PUBDIR50_SEARCH_REPLY 0x05 + +struct gg_pubdir50_request { + uint8_t type; /* GG_PUBDIR50_* */ + uint32_t seq; /* czas wysłania zapytania */ +} GG_PACKED; + +#define GG_PUBDIR50_REPLY 0x000e + +struct gg_pubdir50_reply { + uint8_t type; /* GG_PUBDIR50_* */ + uint32_t seq; /* czas wysłania zapytania */ +} GG_PACKED; + +#define GG_NEW_STATUS 0x0002 + +#define GG_STATUS_NOT_AVAIL 0x0001 /* niedostępny */ +#define GG_STATUS_NOT_AVAIL_DESCR 0x0015 /* niedostępny z opisem (4.8) */ +#define GG_STATUS_AVAIL 0x0002 /* dostępny */ +#define GG_STATUS_AVAIL_DESCR 0x0004 /* dostępny z opisem (4.9) */ +#define GG_STATUS_BUSY 0x0003 /* zajęty */ +#define GG_STATUS_BUSY_DESCR 0x0005 /* zajęty z opisem (4.8) */ +#define GG_STATUS_INVISIBLE 0x0014 /* niewidoczny (4.6) */ +#define GG_STATUS_INVISIBLE_DESCR 0x0016 /* niewidoczny z opisem (4.9) */ +#define GG_STATUS_BLOCKED 0x0006 /* zablokowany */ + +#define GG_STATUS_FRIENDS_MASK 0x8000 /* tylko dla znajomych (4.6) */ + +#define GG_STATUS_DESCR_MAXSIZE 70 + +/* + * makra do łatwego i szybkiego sprawdzania stanu. + */ + +/* GG_S_F() tryb tylko dla znajomych */ +#define GG_S_F(x) (((x) & GG_STATUS_FRIENDS_MASK) != 0) + +/* GG_S() stan bez uwzględnienia trybu tylko dla znajomych */ +#define GG_S(x) ((x) & ~GG_STATUS_FRIENDS_MASK) + +/* GG_S_A() dostępny */ +#define GG_S_A(x) (GG_S(x) == GG_STATUS_AVAIL || GG_S(x) == GG_STATUS_AVAIL_DESCR) + +/* GG_S_NA() niedostępny */ +#define GG_S_NA(x) (GG_S(x) == GG_STATUS_NOT_AVAIL || GG_S(x) == GG_STATUS_NOT_AVAIL_DESCR) + +/* GG_S_B() zajęty */ +#define GG_S_B(x) (GG_S(x) == GG_STATUS_BUSY || GG_S(x) == GG_STATUS_BUSY_DESCR) + +/* GG_S_I() niewidoczny */ +#define GG_S_I(x) (GG_S(x) == GG_STATUS_INVISIBLE || GG_S(x) == GG_STATUS_INVISIBLE_DESCR) + +/* GG_S_D() stan opisowy */ +#define GG_S_D(x) (GG_S(x) == GG_STATUS_NOT_AVAIL_DESCR || GG_S(x) == GG_STATUS_AVAIL_DESCR || GG_S(x) == GG_STATUS_BUSY_DESCR || GG_S(x) == GG_STATUS_INVISIBLE_DESCR) + +/* GG_S_BL() blokowany lub blokujący */ +#define GG_S_BL(x) (GG_S(x) == GG_STATUS_BLOCKED) + +struct gg_new_status { + uint32_t status; /* na jaki zmienić? */ +} GG_PACKED; + +#define GG_NOTIFY_FIRST 0x000f +#define GG_NOTIFY_LAST 0x0010 + +#define GG_NOTIFY 0x0010 + +struct gg_notify { + uint32_t uin; /* numerek danej osoby */ + uint8_t dunno1; /* rodzaj wpisu w liście */ +} GG_PACKED; + +#define GG_USER_OFFLINE 0x01 /* będziemy niewidoczni dla użytkownika */ +#define GG_USER_NORMAL 0x03 /* zwykły użytkownik */ +#define GG_USER_BLOCKED 0x04 /* zablokowany użytkownik */ + +#define GG_LIST_EMPTY 0x0012 + +#define GG_NOTIFY_REPLY 0x000c /* tak, to samo co GG_LOGIN */ + +struct gg_notify_reply { + uint32_t uin; /* numerek */ + uint32_t status; /* status danej osoby */ + uint32_t remote_ip; /* adres ip delikwenta */ + uint16_t remote_port; /* port, na którym słucha klient */ + uint32_t version; /* wersja klienta */ + uint16_t dunno2; /* znowu port? */ +} GG_PACKED; + +#define GG_NOTIFY_REPLY60 0x0011 + +struct gg_notify_reply60 { + uint32_t uin; /* numerek plus flagi w MSB */ + uint8_t status; /* status danej osoby */ + uint32_t remote_ip; /* adres ip delikwenta */ + uint16_t remote_port; /* port, na którym słucha klient */ + uint8_t version; /* wersja klienta */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno1; /* 0x00 */ +} GG_PACKED; + +#define GG_STATUS60 0x000f + +struct gg_status60 { + uint32_t uin; /* numerek plus flagi w MSB */ + uint8_t status; /* status danej osoby */ + uint32_t remote_ip; /* adres ip delikwenta */ + uint16_t remote_port; /* port, na którym słucha klient */ + uint8_t version; /* wersja klienta */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno1; /* 0x00 */ +} GG_PACKED; + +#define GG_ADD_NOTIFY 0x000d +#define GG_REMOVE_NOTIFY 0x000e + +struct gg_add_remove { + uint32_t uin; /* numerek */ + uint8_t dunno1; /* bitmapa */ +} GG_PACKED; + +#define GG_STATUS 0x0002 + +struct gg_status { + uint32_t uin; /* numerek */ + uint32_t status; /* nowy stan */ +} GG_PACKED; + +#define GG_SEND_MSG 0x000b + +#define GG_CLASS_QUEUED 0x0001 +#define GG_CLASS_OFFLINE GG_CLASS_QUEUED +#define GG_CLASS_MSG 0x0004 +#define GG_CLASS_CHAT 0x0008 +#define GG_CLASS_CTCP 0x0010 +#define GG_CLASS_ACK 0x0020 +#define GG_CLASS_EXT GG_CLASS_ACK /* kompatybilność wstecz */ + +#define GG_MSG_MAXSIZE 2000 + +struct gg_send_msg { + uint32_t recipient; + uint32_t seq; + uint32_t msgclass; +} GG_PACKED; + +struct gg_msg_richtext { + uint8_t flag; + uint16_t length; +} GG_PACKED; + +struct gg_msg_richtext_format { + uint16_t position; + uint8_t font; +} GG_PACKED; + +struct gg_msg_richtext_image { + uint16_t unknown1; + uint32_t size; + uint32_t crc32; +} GG_PACKED; + +#define GG_FONT_BOLD 0x01 +#define GG_FONT_ITALIC 0x02 +#define GG_FONT_UNDERLINE 0x04 +#define GG_FONT_COLOR 0x08 +#define GG_FONT_IMAGE 0x80 + +struct gg_msg_richtext_color { + uint8_t red; + uint8_t green; + uint8_t blue; +} GG_PACKED; + +struct gg_msg_recipients { + uint8_t flag; + uint32_t count; +} GG_PACKED; + +struct gg_msg_image_request { + uint8_t flag; + uint32_t size; + uint32_t crc32; +} GG_PACKED; + +struct gg_msg_image_reply { + uint8_t flag; + uint32_t size; + uint32_t crc32; + /* char filename[]; */ + /* char image[]; */ +} GG_PACKED; + +#define GG_SEND_MSG_ACK 0x0005 + +#define GG_ACK_BLOCKED 0x0001 +#define GG_ACK_DELIVERED 0x0002 +#define GG_ACK_QUEUED 0x0003 +#define GG_ACK_MBOXFULL 0x0004 +#define GG_ACK_NOT_DELIVERED 0x0006 + +struct gg_send_msg_ack { + uint32_t status; + uint32_t recipient; + uint32_t seq; +} GG_PACKED; + +#define GG_RECV_MSG 0x000a + +struct gg_recv_msg { + uint32_t sender; + uint32_t seq; + uint32_t time; + uint32_t msgclass; +} GG_PACKED; + +#define GG_PING 0x0008 + +#define GG_PONG 0x0007 + +#define GG_DISCONNECTING 0x000b + +#define GG_USERLIST_REQUEST 0x0016 + +#define GG_USERLIST_PUT 0x00 +#define GG_USERLIST_PUT_MORE 0x01 +#define GG_USERLIST_GET 0x02 + +struct gg_userlist_request { + uint8_t type; +} GG_PACKED; + +#define GG_USERLIST_REPLY 0x0010 + +#define GG_USERLIST_PUT_REPLY 0x00 +#define GG_USERLIST_PUT_MORE_REPLY 0x02 +#define GG_USERLIST_GET_REPLY 0x06 +#define GG_USERLIST_GET_MORE_REPLY 0x04 + +struct gg_userlist_reply { + uint8_t type; +} GG_PACKED; + +/* + * pakiety, stałe, struktury dla DCC + */ + +struct gg_dcc_tiny_packet { + uint8_t type; /* rodzaj pakietu */ +} GG_PACKED; + +struct gg_dcc_small_packet { + uint32_t type; /* rodzaj pakietu */ +} GG_PACKED; + +struct gg_dcc_big_packet { + uint32_t type; /* rodzaj pakietu */ + uint32_t dunno1; /* niewiadoma */ + uint32_t dunno2; /* niewiadoma */ +} GG_PACKED; + +/* + * póki co, nie znamy dokładnie protokołu. nie wiemy, co czemu odpowiada. + * nazwy są niepoważne i tymczasowe. + */ +#define GG_DCC_WANT_FILE 0x0003 /* peer chce plik */ +#define GG_DCC_HAVE_FILE 0x0001 /* więc mu damy */ +#define GG_DCC_HAVE_FILEINFO 0x0003 /* niech ma informacje o pliku */ +#define GG_DCC_GIMME_FILE 0x0006 /* peer jest pewny */ +#define GG_DCC_CATCH_FILE 0x0002 /* wysyłamy plik */ + +#define GG_DCC_FILEATTR_READONLY 0x0020 + +#define GG_DCC_TIMEOUT_SEND 1800 /* 30 minut */ +#define GG_DCC_TIMEOUT_GET 1800 /* 30 minut */ +#define GG_DCC_TIMEOUT_FILE_ACK 300 /* 5 minut */ +#define GG_DCC_TIMEOUT_VOICE_ACK 300 /* 5 minut */ + +#ifdef __cplusplus +} +#ifdef _WIN32 +#pragma pack(pop) +#endif +#endif + +#endif /* __GG_LIBGADU_H */ + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/lib/obsolete.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/lib/obsolete.c Sun Aug 28 22:46:01 2005 +0000 @@ -0,0 +1,207 @@ +/* $Id: obsolete.c 13582 2005-08-28 22:46:01Z boler $ */ + +/* + * (C) Copyright 2001-2003 Wojtek Kaniewski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/* + * Plik zawiera deklaracje funkcji, które są już nieaktualne ze względu + * na zmiany w protokole, ale są wymagane przez aplikacje linkowane ze + * starszymi wersjami bibliotek. + */ + +#include + +#include "libgadu.h" + +struct gg_http *gg_userlist_get(uin_t uin, const char *passwd, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_userlist_get() is obsolete. use gg_userlist_request() instead!\n"); + errno = EINVAL; + return NULL; +} + +int gg_userlist_get_watch_fd(struct gg_http *h) +{ + errno = EINVAL; + return -1; +} + +void gg_userlist_get_free(struct gg_http *h) +{ + +} + +struct gg_http *gg_userlist_put(uin_t uin, const char *password, const char *contacts, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_userlist_put() is obsolete. use gg_userlist_request() instead!\n"); + errno = EINVAL; + return NULL; +} + +int gg_userlist_put_watch_fd(struct gg_http *h) +{ + errno = EINVAL; + return -1; +} + +void gg_userlist_put_free(struct gg_http *h) +{ + +} + +struct gg_http *gg_userlist_remove(uin_t uin, const char *passwd, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_userlist_remove() is obsolete. use gg_userlist_request() instead!\n"); + errno = EINVAL; + return NULL; +} + +int gg_userlist_remove_watch_fd(struct gg_http *h) +{ + errno = EINVAL; + return -1; +} + +void gg_userlist_remove_free(struct gg_http *h) +{ + +} + +struct gg_http *gg_search(const struct gg_search_request *r, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_search() is obsolete. use gg_search50() instead!\n"); + errno = EINVAL; + return NULL; +} + +int gg_search_watch_fd(struct gg_http *h) +{ + errno = EINVAL; + return -1; +} + +void gg_search_free(struct gg_http *h) +{ + +} + +const struct gg_search_request *gg_search_request_mode_0(char *nickname, char *first_name, char *last_name, char *city, int gender, int min_birth, int max_birth, int active, int start) +{ + return NULL; +} + +const struct gg_search_request *gg_search_request_mode_1(char *email, int active, int start) +{ + return NULL; +} + +const struct gg_search_request *gg_search_request_mode_2(char *phone, int active, int start) +{ + return NULL; +} + +const struct gg_search_request *gg_search_request_mode_3(uin_t uin, int active, int start) +{ + return NULL; +} + +void gg_search_request_free(struct gg_search_request *r) +{ + +} + +struct gg_http *gg_register(const char *email, const char *password, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_register() is obsolete. use gg_register3() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_register2(const char *email, const char *password, const char *qa, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_register2() is obsolete. use gg_register3() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_unregister(uin_t uin, const char *password, const char *email, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_unregister() is obsolete. use gg_unregister3() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_unregister2(uin_t uin, const char *password, const char *qa, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_unregister2() is obsolete. use gg_unregister3() instead!\n"); + errno = EINVAL; + return NULL; +} + + +struct gg_http *gg_change_passwd(uin_t uin, const char *passwd, const char *newpasswd, const char *newemail, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_change_passwd() is obsolete. use gg_change_passwd4() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_change_passwd2(uin_t uin, const char *passwd, const char *newpasswd, const char *email, const char *newemail, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_change_passwd2() is obsolete. use gg_change_passwd4() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_change_passwd3(uin_t uin, const char *passwd, const char *newpasswd, const char *qa, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_change_passwd3() is obsolete. use gg_change_passwd4() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_remind_passwd(uin_t uin, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_remind_passwd() is obsolete. use gg_remind_passwd3() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_remind_passwd2(uin_t uin, const char *tokenid, const char *tokenval, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_remind_passwd2() is obsolete. use gg_remind_passwd3() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_change_info(uin_t uin, const char *passwd, const struct gg_change_info_request *request, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_change_info() is obsolete. use gg_pubdir50() instead\n"); + errno = EINVAL; + return NULL; +} + +struct gg_change_info_request *gg_change_info_request_new(const char *first_name, const char *last_name, const char *nickname, const char *email, int born, int gender, const char *city) +{ + return NULL; +} + +void gg_change_info_request_free(struct gg_change_info_request *r) +{ + +} diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/lib/pubdir.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/lib/pubdir.c Sun Aug 28 22:46:01 2005 +0000 @@ -0,0 +1,684 @@ +/* $Id: pubdir.c 13582 2005-08-28 22:46:01Z boler $ */ + +/* + * (C) Copyright 2001-2002 Wojtek Kaniewski + * Dawid Jarosz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "libgadu.h" + +/* + * gg_register3() + * + * rozpoczyna rejestrację użytkownika protokołem GG 6.0. wymaga wcześniejszego + * pobrania tokenu za pomocą funkcji gg_token(). + * + * - email - adres e-mail klienta + * - password - hasło klienta + * - tokenid - identyfikator tokenu + * - tokenval - wartość tokenu + * - async - połączenie asynchroniczne + * + * zaalokowana struct gg_http, którą poźniej należy zwolnić + * funkcją gg_register_free(), albo NULL jeśli wystąpił błąd. + */ +struct gg_http *gg_register3(const char *email, const char *password, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *__pwd, *__email, *__tokenid, *__tokenval, *form, *query; + + if (!email || !password || !tokenid || !tokenval) { + gg_debug(GG_DEBUG_MISC, "=> register, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __pwd = gg_urlencode(password); + __email = gg_urlencode(email); + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + + if (!__pwd || !__email || !__tokenid || !__tokenval) { + gg_debug(GG_DEBUG_MISC, "=> register, not enough memory for form fields\n"); + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + return NULL; + } + + form = gg_saprintf("pwd=%s&email=%s&tokenid=%s&tokenval=%s&code=%u", + __pwd, __email, __tokenid, __tokenval, + gg_http_hash("ss", email, password)); + + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + + if (!form) { + gg_debug(GG_DEBUG_MISC, "=> register, not enough memory for form query\n"); + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> register, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> register, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/fmregister3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> register, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_REGISTER; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +/* + * gg_unregister3() + * + * usuwa konto użytkownika z serwera protokołem GG 6.0 + * + * - uin - numerek GG + * - password - hasło klienta + * - tokenid - identyfikator tokenu + * - tokenval - wartość tokenu + * - async - połączenie asynchroniczne + * + * zaalokowana struct gg_http, którą poźniej należy zwolnić + * funkcją gg_unregister_free(), albo NULL jeśli wystąpił błąd. + */ +struct gg_http *gg_unregister3(uin_t uin, const char *password, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *__fmpwd, *__pwd, *__tokenid, *__tokenval, *form, *query; + + if (!password || !tokenid || !tokenval) { + gg_debug(GG_DEBUG_MISC, "=> unregister, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __pwd = gg_saprintf("%ld", random()); + __fmpwd = gg_urlencode(password); + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + + if (!__fmpwd || !__pwd || !__tokenid || !__tokenval) { + gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for form fields\n"); + free(__pwd); + free(__fmpwd); + free(__tokenid); + free(__tokenval); + return NULL; + } + + form = gg_saprintf("fmnumber=%d&fmpwd=%s&delete=1&pwd=%s&email=deletedaccount@gadu-gadu.pl&tokenid=%s&tokenval=%s&code=%u", uin, __fmpwd, __pwd, __tokenid, __tokenval, gg_http_hash("ss", "deletedaccount@gadu-gadu.pl", __pwd)); + + free(__fmpwd); + free(__pwd); + free(__tokenid); + free(__tokenval); + + if (!form) { + gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for form query\n"); + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> unregister, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/fmregister3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> unregister, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_UNREGISTER; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +/* + * gg_change_passwd4() + * + * wysyła żądanie zmiany hasła zgodnie z protokołem GG 6.0. wymaga + * wcześniejszego pobrania tokenu za pomocą funkcji gg_token(). + * + * - uin - numer + * - email - adres e-mail + * - passwd - stare hasło + * - newpasswd - nowe hasło + * - tokenid - identyfikator tokenu + * - tokenval - wartość tokenu + * - async - połączenie asynchroniczne + * + * zaalokowana struct gg_http, którą poźniej należy zwolnić + * funkcją gg_change_passwd_free(), albo NULL jeśli wystąpił błąd. + */ +struct gg_http *gg_change_passwd4(uin_t uin, const char *email, const char *passwd, const char *newpasswd, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *form, *query, *__email, *__fmpwd, *__pwd, *__tokenid, *__tokenval; + + if (!uin || !email || !passwd || !newpasswd || !tokenid || !tokenval) { + gg_debug(GG_DEBUG_MISC, "=> change, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __fmpwd = gg_urlencode(passwd); + __pwd = gg_urlencode(newpasswd); + __email = gg_urlencode(email); + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + + if (!__fmpwd || !__pwd || !__email || !__tokenid || !__tokenval) { + gg_debug(GG_DEBUG_MISC, "=> change, not enough memory for form fields\n"); + free(__fmpwd); + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + return NULL; + } + + if (!(form = gg_saprintf("fmnumber=%d&fmpwd=%s&pwd=%s&email=%s&tokenid=%s&tokenval=%s&code=%u", uin, __fmpwd, __pwd, __email, __tokenid, __tokenval, gg_http_hash("ss", email, newpasswd)))) { + gg_debug(GG_DEBUG_MISC, "=> change, not enough memory for form fields\n"); + free(__fmpwd); + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + + return NULL; + } + + free(__fmpwd); + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + + gg_debug(GG_DEBUG_MISC, "=> change, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> change, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/fmregister3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> change, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_PASSWD; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +/* + * gg_remind_passwd3() + * + * wysyła żądanie przypomnienia hasła e-mailem. + * + * - uin - numer + * - email - adres e-mail taki, jak ten zapisany na serwerze + * - async - połączenie asynchroniczne + * - tokenid - identyfikator tokenu + * - tokenval - wartość tokenu + * + * zaalokowana struct gg_http, którą poźniej należy zwolnić + * funkcją gg_remind_passwd_free(), albo NULL jeśli wystąpił błąd. + */ +struct gg_http *gg_remind_passwd3(uin_t uin, const char *email, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *form, *query, *__tokenid, *__tokenval, *__email; + + if (!tokenid || !tokenval || !email) { + gg_debug(GG_DEBUG_MISC, "=> remind, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + __email = gg_urlencode(email); + + if (!__tokenid || !__tokenval || !__email) { + gg_debug(GG_DEBUG_MISC, "=> remind, not enough memory for form fields\n"); + free(__tokenid); + free(__tokenval); + free(__email); + return NULL; + } + + if (!(form = gg_saprintf("userid=%d&code=%u&tokenid=%s&tokenval=%s&email=%s", uin, gg_http_hash("u", uin), __tokenid, __tokenval, __email))) { + gg_debug(GG_DEBUG_MISC, "=> remind, not enough memory for form fields\n"); + free(__tokenid); + free(__tokenval); + free(__email); + return NULL; + } + + free(__tokenid); + free(__tokenval); + free(__email); + + gg_debug(GG_DEBUG_MISC, "=> remind, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REMIND_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> remind, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REMIND_HOST, GG_REMIND_PORT, async, "POST", "/appsvc/fmsendpwd3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> remind, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_REMIND; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +/* + * gg_pubdir_watch_fd() + * + * przy asynchronicznych operacjach na katalogu publicznym należy wywoływać + * tę funkcję przy zmianach na obserwowanym deskryptorze. + * + * - h - struktura opisująca połączenie + * + * jeśli wszystko poszło dobrze to 0, inaczej -1. operacja będzie + * zakończona, jeśli h->state == GG_STATE_DONE. jeśli wystąpi jakiś + * błąd, to będzie tam GG_STATE_ERROR i odpowiedni kod błędu w h->error. + */ +int gg_pubdir_watch_fd(struct gg_http *h) +{ + struct gg_pubdir *p; + char *tmp; + + if (!h) { + errno = EFAULT; + return -1; + } + + if (h->state == GG_STATE_ERROR) { + gg_debug(GG_DEBUG_MISC, "=> pubdir, watch_fd issued on failed session\n"); + errno = EINVAL; + return -1; + } + + if (h->state != GG_STATE_PARSING) { + if (gg_http_watch_fd(h) == -1) { + gg_debug(GG_DEBUG_MISC, "=> pubdir, http failure\n"); + errno = EINVAL; + return -1; + } + } + + if (h->state != GG_STATE_PARSING) + return 0; + + h->state = GG_STATE_DONE; + + if (!(h->data = p = malloc(sizeof(struct gg_pubdir)))) { + gg_debug(GG_DEBUG_MISC, "=> pubdir, not enough memory for results\n"); + return -1; + } + + p->success = 0; + p->uin = 0; + + gg_debug(GG_DEBUG_MISC, "=> pubdir, let's parse \"%s\"\n", h->body); + + if ((tmp = strstr(h->body, "success")) || (tmp = strstr(h->body, "results"))) { + p->success = 1; + if (tmp[7] == ':') + p->uin = strtol(tmp + 8, NULL, 0); + gg_debug(GG_DEBUG_MISC, "=> pubdir, success (uin=%d)\n", p->uin); + } else + gg_debug(GG_DEBUG_MISC, "=> pubdir, error.\n"); + + return 0; +} + +/* + * gg_pubdir_free() + * + * zwalnia pamięć po efektach operacji na katalogu publicznym. + * + * - h - zwalniana struktura + */ +void gg_pubdir_free(struct gg_http *h) +{ + if (!h) + return; + + free(h->data); + gg_http_free(h); +} + +/* + * gg_token() + * + * pobiera z serwera token do autoryzacji zakładania konta, usuwania + * konta i zmiany hasła. + * + * zaalokowana struct gg_http, którą poźniej należy zwolnić + * funkcją gg_token_free(), albo NULL jeśli wystąpił błąd. + */ +struct gg_http *gg_token(int async) +{ + struct gg_http *h; + const char *query; + + query = "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: 0\r\n" + "Pragma: no-cache\r\n" + "\r\n"; + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/regtoken.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> token, gg_http_connect() failed mysteriously\n"); + return NULL; + } + + h->type = GG_SESSION_TOKEN; + + h->callback = gg_token_watch_fd; + h->destroy = gg_token_free; + + if (!async) + gg_token_watch_fd(h); + + return h; +} + +/* + * gg_token_watch_fd() + * + * przy asynchronicznych operacjach związanych z tokenem należy wywoływać + * tę funkcję przy zmianach na obserwowanym deskryptorze. + * + * - h - struktura opisująca połączenie + * + * jeśli wszystko poszło dobrze to 0, inaczej -1. operacja będzie + * zakończona, jeśli h->state == GG_STATE_DONE. jeśli wystąpi jakiś + * błąd, to będzie tam GG_STATE_ERROR i odpowiedni kod błędu w h->error. + */ +int gg_token_watch_fd(struct gg_http *h) +{ + if (!h) { + errno = EFAULT; + return -1; + } + + if (h->state == GG_STATE_ERROR) { + gg_debug(GG_DEBUG_MISC, "=> token, watch_fd issued on failed session\n"); + errno = EINVAL; + return -1; + } + + if (h->state != GG_STATE_PARSING) { + if (gg_http_watch_fd(h) == -1) { + gg_debug(GG_DEBUG_MISC, "=> token, http failure\n"); + errno = EINVAL; + return -1; + } + } + + if (h->state != GG_STATE_PARSING) + return 0; + + /* jeśli h->data jest puste, to ściągaliśmy tokenid i url do niego, + * ale jeśli coś tam jest, to znaczy, że mamy drugi etap polegający + * na pobieraniu tokenu. */ + if (!h->data) { + int width, height, length; + char *url = NULL, *tokenid = NULL, *path, *headers; + const char *host; + struct gg_http *h2; + struct gg_token *t; + + gg_debug(GG_DEBUG_MISC, "=> token body \"%s\"\n", h->body); + + if (h->body && (!(url = malloc(strlen(h->body))) || !(tokenid = malloc(strlen(h->body))))) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for results\n"); + free(url); + return -1; + } + + if (!h->body || sscanf(h->body, "%d %d %d\r\n%s\r\n%s", &width, &height, &length, tokenid, url) != 5) { + gg_debug(GG_DEBUG_MISC, "=> token, parsing failed\n"); + free(url); + free(tokenid); + errno = EINVAL; + return -1; + } + + /* dostaliśmy tokenid i wszystkie niezbędne informacje, + * więc pobierzmy obrazek z tokenem */ + + if (strncmp(url, "http://", 7)) { + path = gg_saprintf("%s?tokenid=%s", url, tokenid); + host = GG_REGISTER_HOST; + } else { + char *slash = strchr(url + 7, '/'); + + if (slash) { + path = gg_saprintf("%s?tokenid=%s", slash, tokenid); + *slash = 0; + host = url + 7; + } else { + gg_debug(GG_DEBUG_MISC, "=> token, url parsing failed\n"); + free(url); + free(tokenid); + errno = EINVAL; + return -1; + } + } + + if (!path) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token url\n"); + free(url); + free(tokenid); + return -1; + } + + if (!(headers = gg_saprintf("Host: %s\r\nUser-Agent: " GG_HTTP_USERAGENT "\r\n\r\n", host))) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token url\n"); + free(path); + free(url); + free(tokenid); + return -1; + } + + if (!(h2 = gg_http_connect(host, GG_REGISTER_PORT, h->async, "GET", path, headers))) { + gg_debug(GG_DEBUG_MISC, "=> token, gg_http_connect() failed mysteriously\n"); + free(headers); + free(url); + free(path); + free(tokenid); + return -1; + } + + free(headers); + free(path); + free(url); + + memcpy(h, h2, sizeof(struct gg_http)); + free(h2); + + h->type = GG_SESSION_TOKEN; + + h->callback = gg_token_watch_fd; + h->destroy = gg_token_free; + + if (!h->async) + gg_token_watch_fd(h); + + if (!(h->data = t = malloc(sizeof(struct gg_token)))) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token data\n"); + free(tokenid); + return -1; + } + + t->width = width; + t->height = height; + t->length = length; + t->tokenid = tokenid; + } else { + /* obrazek mamy w h->body */ + h->state = GG_STATE_DONE; + } + + return 0; +} + +/* + * gg_token_free() + * + * zwalnia pamięć po efektach pobierania tokenu. + * + * - h - zwalniana struktura + */ +void gg_token_free(struct gg_http *h) +{ + struct gg_token *t; + + if (!h) + return; + + if ((t = h->data)) + free(t->tokenid); + + free(h->data); + gg_http_free(h); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/lib/pubdir50.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/lib/pubdir50.c Sun Aug 28 22:46:01 2005 +0000 @@ -0,0 +1,467 @@ +/* $Id: pubdir50.c 13582 2005-08-28 22:46:01Z boler $ */ + +/* + * (C) Copyright 2003 Wojtek Kaniewski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#include +#include + +#include "libgadu.h" + +/* + * gg_pubdir50_new() + * + * tworzy nową zmienną typu gg_pubdir50_t. + * + * zaalokowana zmienna lub NULL w przypadku braku pamięci. + */ +gg_pubdir50_t gg_pubdir50_new(int type) +{ + gg_pubdir50_t res = malloc(sizeof(struct gg_pubdir50_s)); + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_new(%d);\n", type); + + if (!res) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_new() out of memory\n"); + return NULL; + } + + memset(res, 0, sizeof(struct gg_pubdir50_s)); + + res->type = type; + + return res; +} + +/* + * gg_pubdir50_add_n() // funkcja wewnętrzna + * + * funkcja dodaje lub zastępuje istniejące pole do zapytania lub odpowiedzi. + * + * - req - wskaźnik opisu zapytania, + * - num - numer wyniku (0 dla zapytania), + * - field - nazwa pola, + * - value - wartość pola, + * + * 0/-1 + */ +int gg_pubdir50_add_n(gg_pubdir50_t req, int num, const char *field, const char *value) +{ + struct gg_pubdir50_entry *tmp = NULL, *entry; + char *dupfield, *dupvalue; + int i; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_add_n(%p, %d, \"%s\", \"%s\");\n", req, num, field, value); + + if (!(dupvalue = strdup(value))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n"); + return -1; + } + + for (i = 0; i < req->entries_count; i++) { + if (req->entries[i].num != num || strcmp(req->entries[i].field, field)) + continue; + + free(req->entries[i].value); + req->entries[i].value = dupvalue; + + return 0; + } + + if (!(dupfield = strdup(field))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n"); + free(dupvalue); + return -1; + } + + if (!(tmp = realloc(req->entries, sizeof(struct gg_pubdir50_entry) * (req->entries_count + 1)))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n"); + free(dupfield); + free(dupvalue); + return -1; + } + + req->entries = tmp; + + entry = &req->entries[req->entries_count]; + entry->num = num; + entry->field = dupfield; + entry->value = dupvalue; + + req->entries_count++; + + return 0; +} + +/* + * gg_pubdir50_add() + * + * funkcja dodaje pole do zapytania. + * + * - req - wskaźnik opisu zapytania, + * - field - nazwa pola, + * - value - wartość pola, + * + * 0/-1 + */ +int gg_pubdir50_add(gg_pubdir50_t req, const char *field, const char *value) +{ + return gg_pubdir50_add_n(req, 0, field, value); +} + +/* + * gg_pubdir50_seq_set() + * + * ustawia numer sekwencyjny zapytania. + * + * - req - zapytanie, + * - seq - nowy numer sekwencyjny. + * + * 0/-1. + */ +int gg_pubdir50_seq_set(gg_pubdir50_t req, uint32_t seq) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_seq_set(%p, %d);\n", req, seq); + + if (!req) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_seq_set() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + req->seq = seq; + + return 0; +} + +/* + * gg_pubdir50_free() + * + * zwalnia pamięć po zapytaniu lub rezultacie szukania użytkownika. + * + * - s - zwalniana zmienna, + */ +void gg_pubdir50_free(gg_pubdir50_t s) +{ + int i; + + if (!s) + return; + + for (i = 0; i < s->entries_count; i++) { + free(s->entries[i].field); + free(s->entries[i].value); + } + + free(s->entries); + free(s); +} + +/* + * gg_pubdir50() + * + * wysyła zapytanie katalogu publicznego do serwera. + * + * - sess - sesja, + * - req - zapytanie. + * + * numer sekwencyjny wyszukiwania lub 0 w przypadku błędu. + */ +uint32_t gg_pubdir50(struct gg_session *sess, gg_pubdir50_t req) +{ + int i, size = 5; + uint32_t res; + char *buf, *p; + struct gg_pubdir50_request *r; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50(%p, %p);\n", sess, req); + + if (!sess || !req) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50() invalid arguments\n"); + errno = EFAULT; + return 0; + } + + if (sess->state != GG_STATE_CONNECTED) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50() not connected\n"); + errno = ENOTCONN; + return 0; + } + + for (i = 0; i < req->entries_count; i++) { + /* wyszukiwanie bierze tylko pierwszy wpis */ + if (req->entries[i].num) + continue; + + size += strlen(req->entries[i].field) + 1; + size += strlen(req->entries[i].value) + 1; + } + + if (!(buf = malloc(size))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50() out of memory (%d bytes)\n", size); + return 0; + } + + r = (struct gg_pubdir50_request*) buf; + res = time(NULL); + r->type = req->type; + r->seq = (req->seq) ? gg_fix32(req->seq) : gg_fix32(time(NULL)); + req->seq = gg_fix32(r->seq); + + for (i = 0, p = buf + 5; i < req->entries_count; i++) { + if (req->entries[i].num) + continue; + + strcpy(p, req->entries[i].field); + p += strlen(p) + 1; + + strcpy(p, req->entries[i].value); + p += strlen(p) + 1; + } + + if (gg_send_packet(sess, GG_PUBDIR50_REQUEST, buf, size, NULL, 0) == -1) + res = 0; + + free(buf); + + return res; +} + +/* + * gg_pubdir50_handle_reply() // funkcja wewnętrzna + * + * analizuje przychodzący pakiet odpowiedzi i zapisuje wynik w struct gg_event. + * + * - e - opis zdarzenia + * - packet - zawartość pakietu odpowiedzi + * - length - długość pakietu odpowiedzi + * + * 0/-1 + */ +int gg_pubdir50_handle_reply(struct gg_event *e, const char *packet, int length) +{ + const char *end = packet + length, *p; + struct gg_pubdir50_reply *r = (struct gg_pubdir50_reply*) packet; + gg_pubdir50_t res; + int num = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_handle_reply(%p, %p, %d);\n", e, packet, length); + + if (!e || !packet) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + if (length < 5) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() packet too short\n"); + errno = EINVAL; + return -1; + } + + if (!(res = gg_pubdir50_new(r->type))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() unable to allocate reply\n"); + return -1; + } + + e->event.pubdir50 = res; + + res->seq = gg_fix32(r->seq); + + switch (res->type) { + case GG_PUBDIR50_READ: + e->type = GG_EVENT_PUBDIR50_READ; + break; + + case GG_PUBDIR50_WRITE: + e->type = GG_EVENT_PUBDIR50_WRITE; + break; + + default: + e->type = GG_EVENT_PUBDIR50_SEARCH_REPLY; + break; + } + + /* brak wyników? */ + if (length == 5) + return 0; + + /* pomiń początek odpowiedzi */ + p = packet + 5; + + while (p < end) { + const char *field, *value; + + field = p; + + /* sprawdź, czy nie mamy podziału na kolejne pole */ + if (!*field) { + num++; + field++; + } + + value = NULL; + + for (p = field; p < end; p++) { + /* jeśli mamy koniec tekstu... */ + if (!*p) { + /* ...i jeszcze nie mieliśmy wartości pola to + * wiemy, że po tym zerze jest wartość... */ + if (!value) + value = p + 1; + else + /* ...w przeciwym wypadku koniec + * wartości i możemy wychodzić + * grzecznie z pętli */ + break; + } + } + + /* sprawdźmy, czy pole nie wychodzi poza pakiet, żeby nie + * mieć segfaultów, jeśli serwer przestanie zakańczać pakietów + * przez \0 */ + + if (p == end) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() premature end of packet\n"); + goto failure; + } + + p++; + + /* jeśli dostaliśmy namier na następne wyniki, to znaczy że + * mamy koniec wyników i nie jest to kolejna osoba. */ + if (!strcasecmp(field, "nextstart")) { + res->next = atoi(value); + num--; + } else { + if (gg_pubdir50_add_n(res, num, field, value) == -1) + goto failure; + } + } + + res->count = num + 1; + + return 0; + +failure: + gg_pubdir50_free(res); + return -1; +} + +/* + * gg_pubdir50_get() + * + * pobiera informację z rezultatu wyszukiwania. + * + * - res - rezultat wyszukiwania, + * - num - numer odpowiedzi, + * - field - nazwa pola (wielkość liter nie ma znaczenia). + * + * wartość pola lub NULL, jeśli nie znaleziono. + */ +const char *gg_pubdir50_get(gg_pubdir50_t res, int num, const char *field) +{ + char *value = NULL; + int i; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_get(%p, %d, \"%s\");\n", res, num, field); + + if (!res || num < 0 || !field) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_get() invalid arguments\n"); + errno = EINVAL; + return NULL; + } + + for (i = 0; i < res->entries_count; i++) { + if (res->entries[i].num == num && !strcasecmp(res->entries[i].field, field)) { + value = res->entries[i].value; + break; + } + } + + return value; +} + +/* + * gg_pubdir50_count() + * + * zwraca ilość wyników danego zapytania. + * + * - res - odpowiedź + * + * ilość lub -1 w przypadku błędu. + */ +int gg_pubdir50_count(gg_pubdir50_t res) +{ + return (!res) ? -1 : res->count; +} + +/* + * gg_pubdir50_type() + * + * zwraca rodzaj zapytania lub odpowiedzi. + * + * - res - zapytanie lub odpowiedź + * + * ilość lub -1 w przypadku błędu. + */ +int gg_pubdir50_type(gg_pubdir50_t res) +{ + return (!res) ? -1 : res->type; +} + +/* + * gg_pubdir50_next() + * + * zwraca numer, od którego należy rozpocząć kolejne wyszukiwanie, jeśli + * zależy nam na kolejnych wynikach. + * + * - res - odpowiedź + * + * numer lub -1 w przypadku błędu. + */ +uin_t gg_pubdir50_next(gg_pubdir50_t res) +{ + return (!res) ? (unsigned) -1 : res->next; +} + +/* + * gg_pubdir50_seq() + * + * zwraca numer sekwencyjny zapytania lub odpowiedzi. + * + * - res - zapytanie lub odpowiedź + * + * numer lub -1 w przypadku błędu. + */ +uint32_t gg_pubdir50_seq(gg_pubdir50_t res) +{ + return (!res) ? (unsigned) -1 : res->seq; +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/libgg.c --- a/src/protocols/gg/libgg.c Sun Aug 28 22:21:24 2005 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1591 +0,0 @@ -/* - * (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; - -/* - * 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 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 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 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, sizeof(*s)); - 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) { - uint32_t t; - memcpy(&t, p + h->length - 4, sizeof(uint32_t)); - e->event.status60.time = t; - } - } - - 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"); - 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: - */ diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/libgg.h --- a/src/protocols/gg/libgg.h Sun Aug 28 22:21:24 2005 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,683 +0,0 @@ -/* - * (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. - */ - -#ifndef __GG_LIBGG_H -#define __GG_LIBGG_H - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(sun) && !defined(INADDR_NONE) - #define INADDR_NONE 0xffffffff -#endif - -#ifdef HAVE_STDINT_H -# include -#endif -#include - -/* - * typ zmiennej określającej numerek danej osoby. - */ -typedef unsigned long uin_t; - -/* - * struktura opisująca daną sesję. tworzona przez gg_login(). - */ -struct gg_session { - int fd; /* podglądany deskryptor */ - int check; /* sprawdzamy zapis czy odczyt */ - int state; /* aktualny stan maszynki */ - int error; /* kod błędu dla GG_STATE_ERROR */ - int type; /* rodzaj sesji. == GG_SESSION_GG */ - - int async; /* czy połączenie jest asynchroniczne */ - int pid; /* pid procesu resolvera */ - int port; /* port, z którym się łączymy */ - int seq; /* numer sekwencyjny ostatniej wiadomości */ - int last_pong; /* czas otrzymania ostatniego ping/pong */ - - unsigned int server_ip; /* adres serwera */ - unsigned int client_ip; /* adres klienta */ - int client_port; /* port, na którym klient słucha */ - - uin_t uin; /* numerek klienta */ - char *password; /* i jego hasło. zwalniane automagicznie */ - - int initial_status; /* początkowy stan klienta */ - - char *recv_buf; /* bufor na otrzymywane pakiety */ - int recv_done; /* ile już wczytano do bufora */ - int recv_left; /* i ile jeszcze trzeba wczytać */ - - char *userlist_reply; /* fragment odpowiedzi listy kontaktów */ - - int userlist_blocks; /* na ile kawałków podzielono listę kontaktów */ -}; - -/* - * ogólna struktura opisująca stan wszystkich operacji http. - */ -struct gg_http { - int fd; /* podglądany deskryptor */ - int check; /* sprawdzamy zapis czy odczyt */ - int state; /* aktualny stan maszynki */ - int error; /* kod błędu dla GG_STATE_ERROR */ - int type; /* rodzaj sesji. == GG_SESSION_HTTP */ - - int async; /* czy połączenie asynchroniczne */ - int pid; /* pid procesu resolvera */ - int port; /* port, z którym się łączymy */ - - char *query; /* bufor zapytania http */ - char *header; /* bufor nagłówka */ - int header_size; /* rozmiar wczytanego nagłówka */ - char *body; /* bufor otrzymanych informacji */ - int body_size; /* ilość informacji */ - - void *data; /* dane danej operacji http */ -}; - -/* - * ogólna struktura opisująca różne sesje. przydatna w klientach. - */ -struct gg_common { - int fd; /* podglądany deskryptor */ - int check; /* sprawdzamy zapis czy odczyt */ - int state; /* aktualny stan maszynki */ - int error; /* kod błędu dla GG_STATE_ERROR */ - int type; /* rodzaj sesji */ -}; - -/* - * rodzaje sesji. - */ -enum { - GG_SESSION_GG = 1, /* połączenie z serwerem gg */ - GG_SESSION_HTTP, /* ogólna sesja http */ - GG_SESSION_SEARCH, /* szukanie */ - GG_SESSION_REGISTER /* rejestrowanie */ -}; - -/* - * różne stany asynchronicznej maszynki. - */ -enum { - /* wspólne */ - GG_STATE_IDLE = 0, /* nie powinno wystąpić. */ - GG_STATE_RESOLVING, /* wywołał gethostbyname() */ - GG_STATE_CONNECTING, /* wywołał connect() */ - GG_STATE_READING_DATA, /* czeka na dane http */ - GG_STATE_ERROR, /* wystąpił błąd. kod w x->error */ - - /* gg_session */ - GG_STATE_CONNECTING_GG, /* wywołał connect() */ - GG_STATE_READING_KEY, /* czeka na klucz */ - GG_STATE_READING_REPLY, /* czeka na odpowiedź */ - GG_STATE_CONNECTED, /* połączył się */ - - /* gg_http */ - GG_STATE_READING_HEADER, /* czeka na nagłówek http */ - GG_STATE_PARSING, /* przetwarza dane */ - GG_STATE_DONE /* skończył */ -}; - -/* - * dla zachowania kompatybilności wstecz. w wersji 1.0 będzie usunięte. oby. - */ -#define GG_STATE_WRITING_HTTP GG_STATE_READING_DATA -#define GG_STATE_WAITING_FOR_KEY GG_STATE_READING_KEY -#define GG_STATE_SENDING_KEY GG_STATE_READING_REPLY -#define GG_STATE_FINISHED GG_STATE_DONE - -/* - * co proces klienta powinien sprawdzać w deskryptorach? - */ -enum { - GG_CHECK_NONE = 0, /* nic. nie powinno wystąpić */ - GG_CHECK_WRITE = 1, /* sprawdzamy możliwość zapisu */ - GG_CHECK_READ = 2 /* sprawdzamy możliwość odczytu */ -}; - -struct gg_session *gg_login(uin_t uin, char *password, int async); -void gg_free_session(struct gg_session *sess); -void gg_logoff(struct gg_session *sess); -int gg_write(struct gg_session *sess, const char *buf, int length); -int gg_change_status(struct gg_session *sess, int status); -int gg_change_status_descr(struct gg_session *sess, int status, const char *descr); -int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const char *message); -int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const char *message, const unsigned char *format, int formatlen); -int gg_ping(struct gg_session *sess); -int gg_userlist_request(struct gg_session *sess, char type, const char *request); - -struct gg_notify_reply { - uin_t uin; /* numerek */ - unsigned long status; /* status danej osoby */ - unsigned long remote_ip; /* adres ip delikwenta */ - unsigned short remote_port; /* port, na którym słucha klient */ - unsigned long version; /* == 0x0b */ - unsigned short dunno2; /* znowu port? */ -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -; - -#define GG_NOTIFY_REPLY60 0x0011 - -struct gg_notify_reply60 { - uint32_t uin; /* numerek plus flagi w MSB */ - uint8_t status; /* status danej osoby */ - uint32_t remote_ip; /* adres ip delikwenta */ - uint16_t remote_port; /* port, na którym słucha klient */ - uint8_t version; /* wersja klienta */ - uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ - uint8_t dunno1; /* 0x00 */ -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -; - -#define GG_STATUS60 0x000f - -struct gg_status60 { - uint32_t uin; /* numerek plus flagi w MSB */ - uint8_t status; /* status danej osoby */ - uint32_t remote_ip; /* adres ip delikwenta */ - uint16_t remote_port; /* port, na którym słucha klient */ - uint8_t version; /* wersja klienta */ - uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ - uint8_t dunno1; /* 0x00 */ -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -; - - -struct gg_status { - uin_t uin; /* numerek */ - unsigned long status; /* nowy stan */ -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -; - -enum { - GG_EVENT_NONE = 0, - GG_EVENT_MSG, - GG_EVENT_NOTIFY, - GG_EVENT_STATUS, - GG_EVENT_ACK, - GG_EVENT_CONN_FAILED, - GG_EVENT_CONN_SUCCESS, - GG_EVENT_STATUS60, /* ktoś zmienił stan w GG 6.0 */ - GG_EVENT_NOTIFY60, /* ktoś się pojawił w GG 6.0 */ - GG_EVENT_USERLIST /* odpowiedź listy kontaktów w GG 6.0 */ -}; - -/* - * niedługo się tego pozbędę na rzecz sensownej obsługi błędów. --w - */ -enum { - GG_FAILURE_RESOLVING = 1, - GG_FAILURE_CONNECTING, - GG_FAILURE_INVALID, - GG_FAILURE_READING, - GG_FAILURE_WRITING, - GG_FAILURE_PASSWORD, - GG_FAILURE_404 -}; - -/* - * rodzaje błędów, na razie używane przez http. bez rozczulania się nad - * powodami. klient powie, że albo nie znalazł hosta, albo nie mógł się - * połączyć, albo nie mógł wysłać, albo nie mógł odebrac. i tyle. jak - * ktoś będzie chciał, to będzie mógł sprawdzić errno. ale po co? - */ -enum { - GG_ERROR_RESOLVING = 1, - GG_ERROR_CONNECTING, - GG_ERROR_READING, - GG_ERROR_WRITING -}; - -/* - * struktura opisująca rodzaj zdarzenia. wychodzi z gg_watch_fd() - */ -struct gg_event { - int type; - union { - struct { - uin_t sender; - int msgclass; - time_t time; - char *message; - } msg; - struct gg_notify_reply *notify; - struct { /* @notify60 informacja o liście kontaktów -- GG_EVENT_NOTIFY60 */ - uin_t uin; /* numer */ - int status; /* stan */ - uint32_t remote_ip; /* adres ip */ - uint16_t remote_port; /* port */ - int version; /* wersja klienta */ - int image_size; /* maksymalny rozmiar grafiki w KiB */ - char *descr; /* opis stanu */ - time_t time; /* czas powrotu */ - } *notify60; - struct gg_status status; - struct { /* @status60 zmiana stanu -- GG_EVENT_STATUS60 */ - uin_t uin; /* numer */ - int status; /* nowy stan */ - uint32_t remote_ip; /* adres ip */ - uint16_t remote_port; /* port */ - int version; /* wersja klienta */ - int image_size; /* maksymalny rozmiar grafiki w KiB */ - char *descr; /* opis stanu */ - time_t time; /* czas powrotu */ - } status60; - struct { /* @userlist odpowiedź listy kontaktów serwera */ - char type; /* rodzaj odpowiedzi */ - char *reply; /* treść odpowiedzi */ - } userlist; - struct { - uin_t recipient; - int status; - int seq; - } ack; - int failure; - } event; -}; - -struct gg_event *gg_watch_fd(struct gg_session *sess); -void gg_free_event(struct gg_event *e); - -int gg_notify(struct gg_session *sess, uin_t *userlist, int count); -int gg_add_notify(struct gg_session *sess, uin_t uin); -int gg_remove_notify(struct gg_session *sess, uin_t uin); - - -/* - * OBSŁUGA HTTP - */ - -struct gg_http *gg_http_connect(char *hostname, int port, int async, char *method, char *path, char *header); -int gg_http_watch_fd(struct gg_http *h); -void gg_http_stop(struct gg_http *h); -void gg_free_http(struct gg_http *h); - -/* - * SZUKANIE UŻYTKOWNIKÓW - */ - -/* - * struktura opisująca kryteria wyszukiwania. argument gg_search(). - */ -struct gg_search_request { - int active; /* czy ma szukać tylko aktywnych? */ - - /* mode 0 */ - char *nickname; /* pseudonim */ - char *first_name; /* imię */ - char *last_name; /* nazwisko */ - char *city; /* miasto */ - int gender; /* płeć */ - int min_birth; /* urodzony od roku... */ - int max_birth; /* urodzony do roku... */ - - /* mode 1 */ - char *email; /* adres e-mail */ - - /* mode 2 */ - char *phone; /* numer telefonu */ - - /* mode 3 */ - uin_t uin; /* numerek */ -}; - -/* - * struktura opisująca rezultat wyszukiwania. pole gg_http. - */ -struct gg_search { - int count; /* ilość znalezionych */ - struct gg_search_result *results; /* tabelka z nimi */ -}; - -/* - * pojedynczy rezultat wyszukiwania. - */ -struct gg_search_result { - uin_t uin; /* numerek */ - char *first_name; /* imię */ - char *last_name; /* nazwisko */ - char *nickname; /* pseudonim */ - int born; /* rok urodzenia */ - int gender; /* płeć */ - char *city; /* miasto */ - int active; /* czy jest aktywny */ -}; - -#define GG_GENDER_NONE 0 /* nie podano lub bez znaczenia */ -#define GG_GENDER_FEMALE 1 /* kobieta */ -#define GG_GENDER_MALE 2 /* mężczyzna */ - -struct gg_http *gg_search(struct gg_search_request *r, int async); -int gg_search_watch_fd(struct gg_http *f); -void gg_free_search(struct gg_http *f); - -struct gg_search_request *gg_search_request_mode_0(char *nickname, char *first_name, char *last_name, char *city, int gender, int min_birth, int max_birth, int active); -struct gg_search_request *gg_search_request_mode_1(char *email, int active); -struct gg_search_request *gg_search_request_mode_2(char *phone, int active); -struct gg_search_request *gg_search_request_mode_3(uin_t uin, int active); - -/* - * OPERACJE NA KATALOGU PUBLICZNYM - */ - -struct gg_pubdir { - int success; /* czy się udało */ - uin_t uin; /* otrzymany numerek. 0 jeśli błąd */ -}; - -struct gg_http *gg_register(char *email, char *password, int async); -void gg_free_register(struct gg_http *f); - -int gg_pubdir_watch_fd(struct gg_http *f); -#define gg_register_watch_fd gg_pubdir_watch_fd - -/* - * jeśli chcemy sobie podebugować, wystarczy ustawić `gg_debug_level'. - * niestety w miarę przybywania wpisów `gg_debug(...)' nie chciało mi - * się ustawiać odpowiednich leveli, więc większość szła do _MISC. - */ - -extern int gg_debug_level; - -#define GG_DEBUG_NET 1 -#define GG_DEBUG_TRAFFIC 2 -#define GG_DEBUG_DUMP 4 -#define GG_DEBUG_FUNCTION 8 -#define GG_DEBUG_MISC 16 - -void gg_debug(int level, char *format, ...); - -/* - * Pare małych zmiennych do obsługi "http proxy" - * - */ - -extern int gg_http_use_proxy; -extern char *gg_http_proxy_host; -extern int gg_http_proxy_port; - -/* - * ------------------------------------------------------------------------- - * poniżej znajdują się wewnętrzne sprawy biblioteki. zwykły klient nie - * powinien ich w ogóle ruszać, bo i nie ma po co. wszystko można załatwić - * procedurami wyższego poziomu, których definicje znajdują się na początku - * tego pliku. - * ------------------------------------------------------------------------- - */ - -int gg_resolve(int *fd, int *pid, char *hostname); -void gg_debug(int level, char *format, ...); -char *gg_alloc_sprintf(char *format, ...); -char *gg_get_line(char **ptr); -int gg_connect(void *addr, int port, int async); -void gg_read_line(int sock, char *buf, int length); -void gg_chomp(char *line); -char *gg_urlencode(const char *str); -int gg_http_hash(const char *email, const char *password); - -#define GG_APPMSG_HOST "appmsg.gadu-gadu.pl" -#define GG_APPMSG_PORT 80 -#define GG_PUBDIR_HOST "pubdir.gadu-gadu.pl" -#define GG_PUBDIR_PORT 80 -#define GG_REGISTER_HOST "register.gadu-gadu.pl" -#define GG_REGISTER_PORT 80 -#define GG_DEFAULT_PORT 8074 -#define GG_HTTPS_PORT 443 -#define GG_HTTP_USERAGENT "Mozilla/4.0 (compatible MSIE 5.0; Windows 98; I)" -#define GG_HAS_AUDIO_MASK 0x40000000 -#define GG_DEFAULT_CLIENT_VERSION "6, 0, 0, 132" - -struct gg_header { - unsigned long type; /* typ pakietu */ - unsigned long length; /* długość reszty pakietu */ -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -; - -#define GG_WELCOME 0x0001 - -struct gg_welcome { - unsigned long key; /* klucz szyfrowania hasła */ -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -; - -#define GG_LOGIN 0x000c - -struct gg_login { - uin_t uin; /* twój numerek */ - unsigned long hash; /* hash hasła */ - unsigned long status; /* status na dzień dobry */ - unsigned long version; /* == 0x20 */ - unsigned long local_ip; /* mój adres ip */ - unsigned short local_port; /* port, na którym słucham */ -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -; - -#define GG_LOGIN60 0x0015 - -struct gg_login60 { - uint32_t uin; /* mój numerek */ - uint32_t hash; /* hash hasła */ - uint32_t status; /* status na dzień dobry */ - uint32_t version; /* moja wersja klienta */ - uint8_t dunno1; /* 0x00 */ - uint32_t local_ip; /* mój adres ip */ - uint16_t local_port; /* port, na którym słucham */ - uint32_t external_ip; /* zewnętrzny adres ip */ - uint16_t external_port; /* zewnętrzny port */ - uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ - uint8_t dunno2; /* 0xbe */ -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -; - -#define GG_LOGIN_OK 0x0003 - -#define GG_LOGIN_FAILED 0x0009 - -#define GG_NEW_STATUS 0x0002 - -#define GG_STATUS_NOT_AVAIL 0x0001 /* niedostępny */ -#define GG_STATUS_NOT_AVAIL_DESCR 0x0015 /* niedostępny z opisem (4.8) */ -#define GG_STATUS_AVAIL 0x0002 /* dostępny */ -#define GG_STATUS_AVAIL_DESCR 0x0004 /* dostępny z opisem (4.9) */ -#define GG_STATUS_BUSY 0x0003 /* zajęty */ -#define GG_STATUS_BUSY_DESCR 0x0005 /* zajęty z opisem (4.8) */ -#define GG_STATUS_INVISIBLE 0x0014 /* niewidoczny (4.6) */ -#define GG_STATUS_INVISIBLE_DESCR 0x0016 /* niewidoczny z opisem (4.9) */ -#define GG_STATUS_BLOCKED 0x0006 /* zablokowany */ - -#define GG_STATUS_FRIENDS_MASK 0x8000 /* tylko dla znajomych (4.6) */ - -#define GG_STATUS_DESCR_MAXSIZE 70 - -/* GG_S() stan bez uwzględnienia trybu tylko dla znajomych */ -#define GG_S(x) ((x) & ~GG_STATUS_FRIENDS_MASK) - -/* GG_S_D() stan opisowy */ -#define GG_S_D(x) (GG_S(x) == GG_STATUS_NOT_AVAIL_DESCR || GG_S(x) == GG_STATUS_AVAIL_DESCR || GG_S(x) == GG_STATUS_BUSY_DESCR || GG_S(x) == GG_STATUS_INVISIBLE_DESCR) - -struct gg_new_status { - unsigned long status; /* na jaki zmienić? */ -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -; - -#define GG_NOTIFY 0x0010 - -struct gg_notify { - uin_t uin; /* numerek danej osoby */ - char dunno1; /* == 3 */ -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -; - -#define GG_NOTIFY_REPLY 0x000c /* tak, to samo co GG_LOGIN */ - -/* struct gg_notify_reply zadeklarowane wyżej */ - -#define GG_ADD_NOTIFY 0x000d -#define GG_REMOVE_NOTIFY 0x000e - -struct gg_add_remove { - uin_t uin; /* numerek */ - char dunno1; /* == 3 */ -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -; - -#define GG_STATUS 0x0002 - -/* struct gg_status zadeklarowane wcześniej */ - -#define GG_SEND_MSG 0x000b - -#define GG_CLASS_QUEUED 0x0001 -#define GG_CLASS_OFFLINE GG_CLASS_QUEUED -#define GG_CLASS_MSG 0x0004 -#define GG_CLASS_CHAT 0x0008 - -struct gg_send_msg { - unsigned long recipient; - unsigned long seq; - unsigned long msgclass; -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -; - -#define GG_SEND_MSG_ACK 0x0005 - -#define GG_ACK_DELIVERED 0x0002 -#define GG_ACK_QUEUED 0x0003 - -struct gg_send_msg_ack { - unsigned long status; - unsigned long recipient; - unsigned long seq; -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -; - -#define GG_RECV_MSG 0x000a - -struct gg_recv_msg { - unsigned long sender; - unsigned long seq; - unsigned long time; - unsigned long msgclass; -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -; - -#define GG_PING 0x0008 - -#define GG_PONG 0x0007 - -#define GG_USERLIST_REQUEST 0x0016 - -#define GG_USERLIST_PUT 0x00 -#define GG_USERLIST_PUT_MORE 0x01 -#define GG_USERLIST_GET 0x02 - -struct gg_userlist_request { - uint8_t type; -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -; - -#define GG_USERLIST_REPLY 0x0010 - -#define GG_USERLIST_PUT_REPLY 0x00 -#define GG_USERLIST_PUT_MORE_REPLY 0x02 -#define GG_USERLIST_GET_REPLY 0x06 -#define GG_USERLIST_GET_MORE_REPLY 0x04 - -struct gg_userlist_reply { - uint8_t type; -} -#ifdef __GNUC__ -__attribute__ ((packed)) -#endif -; - -/* listy */ - -struct list { - void *data; - struct list *next; -}; - -typedef struct list * list_t; - - -#ifdef __cplusplus -} -#endif - -#endif /* __GG_LIBGG_H */ - -/* - * Local variables: - * c-indentation-style: k&r - * c-basic-offset: 8 - * indent-tabs-mode: notnil - * End: - * - * vim: shiftwidth=8: - */ diff -r 9480e0d0f563 -r cf15c1cdcfbd src/protocols/gg/protocol.txt --- a/src/protocols/gg/protocol.txt Sun Aug 28 22:21:24 2005 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,316 +0,0 @@ ---------------------------------------------------------------------------- - - Protokół G*du-G*du 4.x - - (C) Copyright 2001 by Wojtek Kaniewski , - Robert J. Woźny , - Tomasz Jarzynka , - Adam Ludwikowski , - Marek Kozina , - Rafał Florek , - Igor Popik - ---- 0) disclaimer --------------------------------------------------------- - -opis protokołu bazują na doświadczeniach przeprowadzonych na moim -domowym komputerze oraz informacjach przysłanych do mnie przez różnych -ludzi. żaden klient g*du-g*du nie został skrzywdzony podczas -przeprowadzania badań, blabla. - ---- 1) transmisja, format wszystkich pakietów ----------------------------- - -w przeciwieństwie do zabawek typu icq, g*du-g*du korzysta z protokołu tcp. -każdy pakiet zawiera dwa stałe pola: - - struct gg_header { - int type; /* typ pakietu */ - int length; /* długość reszty pakietu */ - }; - -dla ułatwienia przyjmuję następujące długości zmiennych: sizeof(char) = 1, -sizeof(short) = 2, sizeof(int) = 4. oczywiście wszystkie liczby są zgodnie -z intelowym endianem. zakładam też, że wszystkie zmienne są bez znaku. nie -chce mi się wszędzie pisać `unsigned'. - -pola, co do których znaczenia nie mam pewności, lub w ogóle nie mam pojęcia, -skąd się tam wzięły, oznaczam `dunno'. - ---- 2) zanim się połączymy ------------------------------------------------- - -żeby wiedzieć, z jakim serwerem mamy się połączyć, należy poudawać przez -chwilę Internet Explorera, połączyć się z hostem `appmsg.gadu-gadu.pl'. - - GET /appsvc/appmsg.asp?fmnumber= HTTP/1.0 - Host: appmsg.gadu-gadu.pl - User-Agent: Mozilla/4.7 [en] (Win98; I) - Pragma: no-cache - -oryginalny klient może wysłać jeden z podanych identyfikatorów przeglądarki: - - Mozilla/4.04 [en] (Win95; I ;Nav) - Mozilla/4.7 [en] (Win98; I) - Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt) - Mozilla/4.0 (compatible; MSIE 5.0; Windows NT) - Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt) - Mozilla/4.0 (compatible; MSIE 5.0; Windows 98) - -nowsze wersje klienta do zapytania dodają również `version=...' opisujące, -z jakim klientem serwer ma do czynienia. jednak ze względu na możliwe -różnice w protokole, lepiej pomijać ten parametr i uwagać GG 4.0. w każdym -razie na to zapytanie serwer powinien odpowiedzieć: - - HTTP/1.0 200 OK - - 0 1 0 217.17.33.21:8074 217.17.33.21 217.17.33.21 - -co to oznacza? nie mam pojęcia ;) wygląda na to, że cały g*du-g*du jest -przemyślany i w przyszłości będzie można używać różnych serwerów do różnych -rzeczy, typu szukanie, obsługa klientów itd. póki co, łączyć się trzeba na -pierwszy adres (tak, ten z portem). -Jeżeli połączenie z portem 8074 nie wyjdzie z jakiś specyficznych powodów - -można się łączyć na port 443. - ---- 3) logowanie się ------------------------------------------------------- - -po połączeniu się portem serwera g*du-g*du, dostajemy pakiet typu 0x0001, -który na potrzeby tego dokumentu nazwiemy: - - #define GG_WELCOME 0x0001 - -reszta pakietu zawiera liczbę, na podstawie której liczony jest hash z hasła -klienta: - - struct gg_welcome { - int key; /* klucz szyfrowania hasła */ - }; - -kiedy mamy już tą wartość możemy odesłać pakiet logowania - - #define GG_LOGIN 0x000c - -musimy podać kilka informacji: - - struct gg_login { - int uin; /* twój numerek */ - int hash; /* hash hasła */ - int status; /* status na dzień dobry */ - int version; /* wersja klienta */ - int local_ip; /* mój adres ip */ - short local_port; /* port, na którym słucham */ - }; - -jak obliczyć hash hasła? hmm... nic prostszego. do każdej literki hasła -dodaje się jedynkę, mnoży wszystko razem, a potem przez liczbę podaną przez -serwer. - - for (hash = 1; *passwd; passwd++) - hash *= (*passwd) + 1; - -zrozumiałe, racja? liczba oznaczająca wersję może być jedną z poniższych: - - 0x11 - 4.6.1 - 0x10 - 4.5.22, 4.5.21, 4.5.19, 4.5.17, 4.5.15 - 0x0f - 4.5.12 - 0x0b - 4.0.30, 4.0.29, 4.0.28, 4.0.25 - -oczywiście nie są to wszystkie możliwe wersje klientów, lecz te, które -udało się sprawdzić. najbezpieczniej będzie przedstawiać się jako ta -wersja, której ficzerów używamy. wiadomo, że 4.0.x nie obsługiwały trybu -ukrytego, ani tylko dla znajomych itd. - -jeśli wszystko się powiedzie, dostaniemy w odpowiedzi pakiet typu - - #define GG_LOGIN_OK 0x0003 - -z polem header->length = 0, lub pakiet - - #define GG_LOGIN_FAILED 0x0009 - ---- 4) zmiana statusu ----------------------------------------------------- - -g*du-g*du przewiduje trzy stany klienta, które zmieniamy pakietem - - #define GG_NEW_STATUS 0x0002 - - #define GG_STATUS_NOT_AVAIL 0x0001 /* rozłączony */ - #define GG_STATUS_AVAIL 0x0002 /* dostępny */ - #define GG_STATUS_BUSY 0x0003 /* zajęty */ - #define GG_STATUS_INVISIBLE 0x0014 /* niewidoczny */ - - #define GG_STATUS_FRIENDS_MASK 0x8000 /* tylko dla przyjaciół */ - - struct gg_new_status { - int status; /* na jaki zmienić? */ - } - -należy pamiętać, żeby przed rozłączeniem się z serwerem należy zmienić -stan na GG_STATUS_NOT_AVAIL. jeśli ma być widoczny tylko dla przyjaciół, -należy dodać GG_STATUS_FRIENDS do normalnej wartości stanu. - ---- 5) ludzie przychodzą, ludzie odchodzą --------------------------------- - -zaraz po zalogowaniu możemy wysłać serwerowi listę ludzików w naszej liście -kontaktów, żeby dowiedzieć się, czy są w tej chwili dostępni. pakiet zawiera -dowolną ilość struktur gg_notify: - - #define GG_NOTIFY 0x0010 - - struct gg_notify { - int uin; /* numerek danej osoby */ - char dunno1; /* == 3 */ - }; - -jeśli ktoś jest, serwer odpowie pakietem zawierającym jedną lub więcej -struktur gg_notify_reply: - - #define GG_NOTIFY_REPLY 0x000c /* tak, to samo co GG_LOGIN */ - - struct gg_notify_reply { - int uin; /* numerek */ - int status; /* status danej osoby */ - int remote_ip; /* adres ip delikwenta */ - short remote_port; /* port, na którym słucha klient */ - int version; /* wersja klienta */ - short dunno1; /* znowu port? */ - }; - -jeśli klient nie obsługuje połączeń między klientami (np. g*du-g*du 3.x) -zamiast adresu ip jest 0, zamiast portu może być 0, 1, 2... nieważne ;) -port może przyjmować wartość 1, jeśli klient znajduje się za jakimś -firewallem lub innym urządzeniem robiącym NAT. w każdym razie, jeśli ktoś -się pojawi w trakcie pracy, również zostanie przysłany ten pakiet. -proste? proste :) - -żeby dodać kogoś do listy w trakcie pracy, trzeba wysłać niżej opisany -pakiet. jego format jest identyczny jak przy GG_NOTIFY. - - #define GG_ADD 0x000d - - struct gg_add { - int uin; /* numerek */ - char dunno1; /* == 3 */ - }; - -jeśli ktoś opuści g*du-g*du lub zmieni stan, otrzymamy pakiet - - #define GG_STATUS 0x0002 - - struct gg_status { - int uin; /* numerek */ - int status; /* nowy stan */ - }; - ---- 6) wysyłanie wiadomości ------------------------------------------------ - -przejdźmy do sedna sprawy ;) - - #define GG_SEND_MSG 0x000b - - #define GG_CLASS_QUEUED 0x0001 /* tylko przy odbieraniu */ - #define GG_CLASS_MSG 0x0004 - #define GG_CLASS_CHAT 0x0008 - #define GG_CLASS_UNKNOWN_1 0x0020 - - struct gg_send_msg { - int recipient; - int seq; - int class; - char message[]; - }; - -wiadomo, odbiorca. numer sekwencyjny, który wykorzystujemy potem do -potwierdzenia. nie wykluczone, że w jakis sposób odróżnia się różne -rozmowy za pomocą części bajtów, ale raczej nie ma znaczenia. klasa -wiadomości pozwala odróżnić, czy wiadomość ma się pokazać w osobym -okienku czy jako kolejna linijka w okienku rozmowy. wygląda na to, -że to jakaś bitmapa, więc najlepiej olać inne bity niż 0x0e. (czasem -klienty wysyłają 0x04, czasem 0x24 -- widocznie 0x20 to też jakaś -flaga). jeśli odbiorca był niedostępny podczas wysyłania wiadomości, -zostanie zaznaczony bit 0x01. - -oryginalny klient wysyłając wiadomość do kilku użytkowników, wysyła po -prostu kilka takich samych pakietów z różnymi numerkami odbiorców. nie -ma osobnego pakietu do tego. natomiast jeśli chodzi o ,,konferencyjnę'' -do pakietu doklejana jest za ,,char message[];'' następująca struktura: - - struct gg_send_recipients { - char flag; /* == 1 */ - int count; /* ilość odbiorców */ - int recipients[]; /* tablica odbiorców */ - }; - -na przykład, by wysłać do trzech ludzi, należy wysłać pakiet: - - - -- --- --+--+--+--+--+--+--+-----------+-----------+ - treść |\0|\1| 0x02 | uin1 | uin2 | - - -- -- ---+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - -serwer po otrzymaniu wiadomości odsyła informację o tym. przy okazji -mówi, czy wiadomość dotarła do odbiorcy (status == GG_ACK_DELIVERED), -czy może jest offline i została zakolejkowana (GG_ACK_QUEUED): - - #define GG_SEND_MSG_ACK 0x0005 - - #define GG_ACK_DELIVERED 0x0002 - #define GG_ACK_QUEUED 0x0003 - - struct gg_send_msg_ack { - int status; - int recipient; - int seq; - }; - -numer sekwencyjny i adresat ten sam, co przy wysyłaniu. - ---- 7) otrzymywanie wiadomości --------------------------------------------- - -zbyt wiele wyjaśnień chyba nie trzeba. wiadomo od kogo. drugie pole to -najprawdopodobniej jakiś numerek sekwencyjny. trzecie oznacza czas nadania -wiadomości. klasa wiadomości taka sama jak przy wysyłaniu: - - #define GG_RECV_MSG 0x000a - - struct gg_recv_msg { - int sender; - int seq; - int time; - int class; - char message[]; - }; - -w przypadku pakietów ,,konferencyjnych'' na koncu pakietu doklejona jest -struktura identyczna ze struct gg_send_recipients zawierająca pozostałych -rozmówców. - ---- 8) ping/pong ----------------------------------------------------------- - -od czasu do czasu klient wysyła pakiet a'la ping do serwera i dostaje pustą -odpowiedź. o ile dobrze pamiętam, serwer rozłącza się po upływie 5 minut od -otrzymania ostatniej informacji. - - #define GG_PING 0x0008 - - /* nie ma niczego */ - - #define GG_PONG 0x0007 - - /* nie ma niczego */ - ---- 9) podziękowania ------------------------------------------------------- - -swój wkład w poznanie protokołu mieli: - - Robert J. Woźny : - opis nowości w protokole GG 4.6, - - Tomasz Jarzynka : - badanie timeoutów, - - Adam Ludwikowski : - wiele różnych poprawek do tekstu, badanie wersji, - - Marek Kozina : - czas otrzymania wiadomości, - - Rafał Florek : - konferencje, - - Igor Popik : - klasy wiadomości przy odbieraniu zakolejkowanej. - ----------------------------------------------------------------------------- -