# HG changeset patch # User Eric Warmenhoven # Date 1001804790 0 # Node ID a7ecfd3f7714bf61ea4f6286c85cc913ffe04ea9 # Parent 9965c0bbdb7cb65bf03771dcb9049d6903d14c13 [gaim-migrate @ 2406] Arkadiusz Miskiewicz\'s Gadu-Gadu plugin. I was able to figure out enough polish to be able to download Gadu-Gadu, create an account, and test the plugin. Imagine my shock when I got my info and it said I was a woman. Whoops. Also splitting plugins.c so that non-gtk stuff is in modules.c. gaim-core is almost ready for protocol implantaion. Also fixing an IRC bug. Also patiently waiting for anoncvs_gaim's lock in /cvsroot/gaim/gaim/pixmaps committer: Tailor Script diff -r 9965c0bbdb7c -r a7ecfd3f7714 ChangeLog --- a/ChangeLog Sat Sep 29 02:08:00 2001 +0000 +++ b/ChangeLog Sat Sep 29 23:06:30 2001 +0000 @@ -11,6 +11,7 @@ * Nick Highlighting in chat * Tab-completion for nicks in chat (thanks to Sean Egan) * Large internal reworkings + * New Protocol: Gadu-Gadu, written by Arkadiusz Miskiewicz version 0.44 (09/20/2001): * More sane scaling of buddy icons (intelligently scale to diff -r 9965c0bbdb7c -r a7ecfd3f7714 acconfig.h --- a/acconfig.h Sat Sep 29 02:08:00 2001 +0000 +++ b/acconfig.h Sat Sep 29 23:06:30 2001 +0000 @@ -17,7 +17,6 @@ #undef ARTSC_SOUND #undef _REENTRANT #undef NEED_GNOMESUPPORT_H -#undef NEED_SOCKLEN_T #undef ZEPHYR_INT32 #undef ZEPHYR_USES_KERBEROS #ifndef STATIC_PROTO_INIT diff -r 9965c0bbdb7c -r a7ecfd3f7714 configure.ac --- a/configure.ac Sat Sep 29 02:08:00 2001 +0000 +++ b/configure.ac Sat Sep 29 23:06:30 2001 +0000 @@ -32,8 +32,6 @@ AC_TYPE_SIGNAL AC_FUNC_STRFTIME AC_CHECK_FUNCS(socket strdup strstr atexit getaddrinfo) -AC_TRY_COMPILE([#include -#include ], [socklen_t slen;],,[AC_DEFINE(NEED_SOCKLEN_T)]) dnl Checks for getopt in standard library AC_CHECK_FUNCS(getopt_long , , [LIBOBJS="$LIBOBJS getopt.o getopt1.o"] ) @@ -48,7 +46,7 @@ AC_ARG_ENABLE(prpls, [ --disable-prpls don't build dynamic protocol plugins],,enable_prpls=yes) AC_ARG_WITH(static-prpls, [ --with-static-prpls link in certain protocols statically],[STATIC_PRPLS=`echo $withval | $sedpath 's/,/ /g'`],STATIC_PRPLS="oscar toc") if test "x$STATIC_PRPLS" = "xall" ; then - STATIC_PRPLS="icq irc jabber msn napster oscar toc yahoo zephyr" + STATIC_PRPLS="gg icq irc jabber msn napster oscar toc yahoo zephyr" fi AC_SUBST(STATIC_PRPLS) STATIC_LINK_LIBS= @@ -59,6 +57,7 @@ extern_init="$extern_init extern void ${i}_init(struct prpl *);" load_proto="$load_proto load_protocol(${i}_init, sizeof(struct prpl));" case $i in + gg) static_gg=yes ;; icq) static_icq=yes ;; irc) static_irc=yes ;; jabber) static_jabber=yes ;; @@ -71,6 +70,7 @@ *) echo "Invalid static protocol $i!!" ; exit ;; esac done +AM_CONDITIONAL(STATIC_GG, test "x$static_gg" = "xyes") AM_CONDITIONAL(STATIC_ICQ, test "x$static_icq" = "xyes") AM_CONDITIONAL(STATIC_IRC, test "x$static_irc" = "xyes") AM_CONDITIONAL(STATIC_JABBER, test "x$static_jabber" = "xyes") @@ -358,6 +358,7 @@ sounds/Makefile src/Makefile src/protocols/Makefile + src/protocols/gg/Makefile src/protocols/icq/Makefile src/protocols/irc/Makefile src/protocols/jabber/Makefile diff -r 9965c0bbdb7c -r a7ecfd3f7714 doc/CREDITS --- a/doc/CREDITS Sat Sep 29 02:08:00 2001 +0000 +++ b/doc/CREDITS Sat Sep 29 23:06:30 2001 +0000 @@ -36,6 +36,8 @@ A healthy amount of patches for the Jabber plugin Neil Sanchala Wrote most of the Zephyr plugin +Arkadiusz Miskiewicz + Wrote the Gadu-Gadu plugin David Prater draven@tcsx.net Log and Colour Button Images diff -r 9965c0bbdb7c -r a7ecfd3f7714 pixmaps/Makefile.am --- a/pixmaps/Makefile.am Sat Sep 29 02:08:00 2001 +0000 +++ b/pixmaps/Makefile.am Sat Sep 29 23:06:30 2001 +0000 @@ -34,6 +34,10 @@ fontface2.xpm \ free_icon.xpm \ gaim.xpm \ + gg_suncloud.xpm \ + gg_sunred.xpm \ + gg_sunwhitered.xpm \ + gg_sunyellow.xpm \ gnome_add.xpm \ gnome_preferences.xpm \ gnome_remove.xpm \ diff -r 9965c0bbdb7c -r a7ecfd3f7714 pixmaps/gg_suncloud.xpm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pixmaps/gg_suncloud.xpm Sat Sep 29 23:06:30 2001 +0000 @@ -0,0 +1,28 @@ +/* XPM */ +static char * gg_suncloud_xpm[] = { +"18 18 7 1", +" c None", +". c #FF0000", +"+ c #7F7F7F", +"@ c #FFFF00", +"# c #000000", +"$ c #00007F", +"% c #00FFFF", +" ", +" . ", +" .+ ", +" . .+ . ", +" . ..@.. .+ ", +" .@@@@@.+ ", +" .@@#@#@@.+ ", +" .@@@@@@@.+ ", +" ...@$@@@@@@@... ", +" $$%$@@@#@$$$++ ", +" $%%%%$##@$%%%$ ", +" $%%%%%%$$$%%%%$ ", +" $%%%%%%%%%%%%%%$ ", +" $%%%%%%%%%%%%%$ ", +" $%%%%%%%%%%%%$ ", +" $%%%$$$%%%$$$ ", +" $$$ $$$ ", +" "}; diff -r 9965c0bbdb7c -r a7ecfd3f7714 pixmaps/gg_sunred.xpm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pixmaps/gg_sunred.xpm Sat Sep 29 23:06:30 2001 +0000 @@ -0,0 +1,25 @@ +/* XPM */ +static char * gg_sunred_xpm[] = { +"19 18 4 1", +" c None", +". c #FF0000", +"+ c #7F7F7F", +"@ c #000000", +" ", +" . ", +" .+ ", +" . .+ . ", +" . ..... .+ ", +" .......+ ", +" ...@.@...+ ", +" .........+ ", +" ............... ", +" ..@...@..++++ ", +" ...@@@...+ ", +" .......+ ", +" .+..... .+ ", +" .+ .++ .+ ", +" + .+ ", +" .+ ", +" + ", +" "}; diff -r 9965c0bbdb7c -r a7ecfd3f7714 pixmaps/gg_sunwhitered.xpm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pixmaps/gg_sunwhitered.xpm Sat Sep 29 23:06:30 2001 +0000 @@ -0,0 +1,27 @@ +/* XPM */ +static char * gg_sunwhitered_xpm[] = { +"19 18 6 1", +" c None", +". c #7F0000", +"+ c #FF0000", +"@ c #7F7F7F", +"# c #FFFFFF", +"$ c #000000", +" ", +" . ", +" +@ ", +" + .@ + ", +" . .+#+. .@ ", +" +#####+@ ", +" .##$#$##.@ ", +" +#######+@ ", +" .+.#########.+. ", +" +#$###$#+@@@@ ", +" .##$$$##.@ ", +" +#####+@ ", +" .@.+#+. .@ ", +" +@ .@@ +@ ", +" @ +@ ", +" .@ ", +" @ ", +" "}; diff -r 9965c0bbdb7c -r a7ecfd3f7714 pixmaps/gg_sunyellow.xpm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pixmaps/gg_sunyellow.xpm Sat Sep 29 23:06:30 2001 +0000 @@ -0,0 +1,26 @@ +/* XPM */ +static char * gg_sunyellow_xpm[] = { +"18 18 5 1", +" c None", +". c #FF0000", +"+ c #7F7F7F", +"@ c #FFFF00", +"# c #000000", +" ", +" . ", +" .+ ", +" . .+ . ", +" . ..@.. .+ ", +" .@@@@@.+ ", +" .@@#@#@@.+ ", +" .@@@@@@@.+ ", +" ...@@@@@@@@@... ", +" .@#@@@#@.++++ ", +" .@@###@@.+ ", +" .@@@@@.+ ", +" .+..@.. .+ ", +" .+ .++ .+ ", +" + .+ ", +" .+ ", +" + ", +" "}; diff -r 9965c0bbdb7c -r a7ecfd3f7714 po/POTFILES.in --- a/po/POTFILES.in Sat Sep 29 02:08:00 2001 +0000 +++ b/po/POTFILES.in Sat Sep 29 23:06:30 2001 +0000 @@ -1,3 +1,4 @@ +src/protocols/gg/gg.c src/protocols/icq/gaim_icq.c src/protocols/irc/irc.c src/protocols/jabber/jabber.c diff -r 9965c0bbdb7c -r a7ecfd3f7714 src/Makefile.am --- a/src/Makefile.am Sat Sep 29 02:08:00 2001 +0000 +++ b/src/Makefile.am Sat Sep 29 23:06:30 2001 +0000 @@ -18,6 +18,7 @@ html.c \ idle.c \ list.c \ + module.c \ multi.c \ perl.c \ plugins.c \ @@ -49,6 +50,7 @@ html.c \ idle.c \ list.c \ + module.c \ multi.c \ perl.c \ plugins.c \ diff -r 9965c0bbdb7c -r a7ecfd3f7714 src/buddy.c --- a/src/buddy.c Sat Sep 29 02:08:00 2001 +0000 +++ b/src/buddy.c Sat Sep 29 23:06:30 2001 +0000 @@ -2389,7 +2389,8 @@ gtk_widget_show(menuitem); menuitem = gtk_menu_item_new_with_label(_("by Dir Info")); gtk_menu_append(GTK_MENU(findmenu), menuitem); - gtk_signal_connect(GTK_OBJECT(menuitem), "activate", GTK_SIGNAL_FUNC(show_find_info), NULL); + gtk_signal_connect(GTK_OBJECT(menuitem), "activate", GTK_SIGNAL_FUNC(show_find_info), + connections->data); gtk_widget_show(menuitem); setmenu = gtk_menu_new(); diff -r 9965c0bbdb7c -r a7ecfd3f7714 src/dialogs.c --- a/src/dialogs.c Sat Sep 29 02:08:00 2001 +0000 +++ b/src/dialogs.c Sat Sep 29 23:06:30 2001 +0000 @@ -150,6 +150,7 @@ }; struct findbyinfo { + struct gaim_connection *gc; GtkWidget *window; GtkWidget *firstentry; GtkWidget *middleentry; @@ -2058,10 +2059,7 @@ state = gtk_entry_get_text(GTK_ENTRY(b->stateentry)); country = gtk_entry_get_text(GTK_ENTRY(b->countryentry)); - /* FIXME : dir search. not sure if even works; not important */ - if (connections) - serv_dir_search(connections->data, first, middle, last, maiden, city, state, country, - ""); + serv_dir_search(b->gc, first, middle, last, maiden, city, state, country, ""); destroy_dialog(NULL, b->window); } @@ -2079,7 +2077,7 @@ destroy_dialog(NULL, b->window); } -void show_find_info() +void show_find_info(struct gaim_connection *gc) { GtkWidget *cancel; GtkWidget *ok; @@ -2091,6 +2089,7 @@ GtkWidget *frame; struct findbyinfo *b = g_new0(struct findbyinfo, 1); + b->gc = gc; b->window = gtk_window_new(GTK_WINDOW_DIALOG); gtk_window_set_policy(GTK_WINDOW(b->window), FALSE, TRUE, TRUE); gtk_window_set_wmclass(GTK_WINDOW(b->window), "find_info", "Gaim"); diff -r 9965c0bbdb7c -r a7ecfd3f7714 src/gaim.h --- a/src/gaim.h Sat Sep 29 02:08:00 2001 +0000 +++ b/src/gaim.h Sat Sep 29 23:06:30 2001 +0000 @@ -807,7 +807,9 @@ /* Functions in plugins.c */ #ifdef GAIM_PLUGINS extern void show_plugins(GtkWidget *, gpointer); -extern void load_plugin (char *); +extern struct gaim_plugin *load_plugin(char *); +extern void unload_plugin(struct gaim_plugin *); +extern struct gaim_plugin *reload_plugin(struct gaim_plugin *); extern void gaim_signal_connect(GModule *, enum gaim_event, void *, void *); extern void gaim_signal_disconnect(GModule *, enum gaim_event, void *); extern void gaim_plugin_unload(GModule *); @@ -857,8 +859,8 @@ extern void show_new_bp(); extern void show_log(char *); extern void show_log_dialog(struct conversation *); -extern void show_find_email(struct gaim_connection *gc); -extern void show_find_info(); +extern void show_find_email(struct gaim_connection *); +extern void show_find_info(struct gaim_connection *); extern void g_show_info_text(char *, ...); extern void show_set_info(struct gaim_connection *); extern void show_set_dir(); diff -r 9965c0bbdb7c -r a7ecfd3f7714 src/module.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/module.c Sat Sep 29 23:06:30 2001 +0000 @@ -0,0 +1,516 @@ +/* + * gaim + * + * Copyright (C) 1998-1999, Mark Spencer + * + * 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. + * + * 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 + * + * ---------------- + * The Plug-in plug + * + * Plugin support is currently being maintained by Mike Saraf + * msaraf@dwc.edu + * + * Well, I didn't see any work done on it for a while, so I'm going to try + * my hand at it. - Eric warmenhoven@yahoo.com + * + * Mike is my roomate. I can assure you that he's lazy :-P -- Rob rob@marko.net + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef GAIM_PLUGINS + +#include +#include + +#include +#include + +#include +#include +#include +#include "gaim.h" + +/* ------------------ Global Variables ----------------------- */ + +GList *plugins = NULL; +GList *callbacks = NULL; + +/* --------------- Function Declarations --------------------- */ + +struct gaim_plugin * load_plugin(char *); + void unload_plugin(struct gaim_plugin *p); +struct gaim_plugin *reload_plugin(struct gaim_plugin *p); + +void gaim_signal_connect(GModule *, enum gaim_event, void *, void *); +void gaim_signal_disconnect(GModule *, enum gaim_event, void *); +void gaim_plugin_unload(GModule *); + +/* --------------- Static Function Declarations ------------- */ + +static void plugin_remove_callbacks(GModule *); + +/* ------------------ Code Below ---------------------------- */ + +struct gaim_plugin *load_plugin(char *filename) +{ + struct gaim_plugin *plug; + GList *c = plugins; + char *(*gaim_plugin_init)(GModule *); + char *(*cfunc)(); + char *error; + char *retval; + + if (!g_module_supported()) + return NULL; + if (filename && !strlen(filename)) + return NULL; + + while (filename && c) { + plug = (struct gaim_plugin *)c->data; + if (!strcmp(filename, g_module_name(plug->handle))) { + /* just need to reload plugin */ + return reload_plugin(plug); + } else + c = g_list_next(c); + } + plug = g_malloc(sizeof *plug); + + debug_printf("Loading %s\n", filename); + plug->handle = g_module_open(filename, 0); + if (!plug->handle) { + error = (char *)g_module_error(); + do_error_dialog(error, _("Plugin Error")); + g_free(plug); + return NULL; + } + + if (!g_module_symbol(plug->handle, "gaim_plugin_init", (gpointer *)&gaim_plugin_init)) { + do_error_dialog(g_module_error(), _("Plugin Error")); + g_module_close(plug->handle); + g_free(plug); + return NULL; + } + + retval = (*gaim_plugin_init)(plug->handle); + debug_printf("loaded plugin returned %s\n", retval ? retval : "NULL"); + if (retval) { + plugin_remove_callbacks(plug->handle); + do_error_dialog(retval, _("Plugin Error")); + g_module_close(plug->handle); + g_free(plug); + return NULL; + } + + plugins = g_list_append(plugins, plug); + + if (g_module_symbol(plug->handle, "name", (gpointer *)&cfunc)) { + plug->name = (*cfunc)(); + } else { + plug->name = NULL; + } + + if (g_module_symbol(plug->handle, "description", (gpointer *)&cfunc)) + plug->description = (*cfunc)(); + else + plug->description = NULL; + + save_prefs(); + return plug; +} + +static void unload_gaim_plugin(struct gaim_plugin *p) +{ + void (*gaim_plugin_remove)(); + + debug_printf("Unloading %s\n", g_module_name(p->handle)); + + /* Attempt to call the plugin's remove function (if there) */ + if (g_module_symbol(p->handle, "gaim_plugin_remove", (gpointer *)&gaim_plugin_remove)) + (*gaim_plugin_remove)(); + + plugin_remove_callbacks(p->handle); + + plugins = g_list_remove(plugins, p); + g_free(p); + save_prefs(); +} + +void unload_plugin(struct gaim_plugin *p) +{ + GModule *handle = p->handle; + unload_gaim_plugin(p); + g_module_close(handle); +} + +static gboolean unload_timeout(gpointer handle) +{ + g_module_close(handle); + return FALSE; +} + +void gaim_plugin_unload(GModule *handle) +{ + g_timeout_add(5000, unload_timeout, handle); +} + +/* Do unload/load cycle of plugin. */ +struct gaim_plugin *reload_plugin(struct gaim_plugin *p) +{ + char file[1024]; + GModule *handle = p->handle; + + strncpy(file, g_module_name(handle), sizeof(file)); + file[sizeof(file) - 1] = '\0'; + + debug_printf("Reloading %s\n", file); + + /* Unload */ + unload_plugin(p); + + /* Load */ + return load_plugin(file); +} + +/* Remove all callbacks associated with plugin handle */ +static void plugin_remove_callbacks(GModule *handle) +{ + GList *c = callbacks; + struct gaim_callback *g; + + debug_printf("%d callbacks to search\n", g_list_length(callbacks)); + + while (c) { + g = (struct gaim_callback *)c->data; + if (g->handle == handle) { + c = g_list_next(c); + callbacks = g_list_remove(callbacks, (gpointer)g); + debug_printf("Removing callback, %d remain\n", g_list_length(callbacks)); + } else + c = g_list_next(c); + } +} + +void gaim_signal_connect(GModule *handle, enum gaim_event which, void *func, void *data) +{ + struct gaim_callback *call = g_new0(struct gaim_callback, 1); + call->handle = handle; + call->event = which; + call->function = func; + call->data = data; + + callbacks = g_list_append(callbacks, call); + debug_printf("Adding callback %d\n", g_list_length(callbacks)); +} + +void gaim_signal_disconnect(GModule *handle, enum gaim_event which, void *func) +{ + GList *c = callbacks; + struct gaim_callback *g = NULL; + + while (c) { + g = (struct gaim_callback *)c->data; + if (handle == g->handle && func == g->function) { + callbacks = g_list_remove(callbacks, c->data); + g_free(g); + c = callbacks; + if (c == NULL) + break; + } + c = g_list_next(c); + } +} + +#endif /* GAIM_PLUGINS */ + +static char *event_name(enum gaim_event event) +{ + static char buf[128]; + switch (event) { + case event_signon: + sprintf(buf, "event_signon"); + break; + case event_signoff: + sprintf(buf, "event_signoff"); + break; + case event_away: + sprintf(buf, "event_away"); + break; + case event_back: + sprintf(buf, "event_back"); + break; + case event_im_recv: + sprintf(buf, "event_im_recv"); + break; + case event_im_send: + sprintf(buf, "event_im_send"); + break; + case event_buddy_signon: + sprintf(buf, "event_buddy_signon"); + break; + case event_buddy_signoff: + sprintf(buf, "event_buddy_signoff"); + break; + case event_buddy_away: + sprintf(buf, "event_buddy_away"); + break; + case event_buddy_back: + sprintf(buf, "event_buddy_back"); + break; + case event_buddy_idle: + sprintf(buf, "event_buddy_idle"); + break; + case event_buddy_unidle: + sprintf(buf, "event_buddy_unidle"); + break; + case event_blist_update: + sprintf(buf, "event_blist_update"); + break; + case event_chat_invited: + sprintf(buf, "event_chat_invited"); + break; + case event_chat_join: + sprintf(buf, "event_chat_join"); + break; + case event_chat_leave: + sprintf(buf, "event_chat_leave"); + break; + case event_chat_buddy_join: + sprintf(buf, "event_chat_buddy_join"); + break; + case event_chat_buddy_leave: + sprintf(buf, "event_chat_buddy_leave"); + break; + case event_chat_recv: + sprintf(buf, "event_chat_recv"); + break; + case event_chat_send: + sprintf(buf, "event_chat_send"); + break; + case event_warned: + sprintf(buf, "event_warned"); + break; + case event_error: + sprintf(buf, "event_error"); + break; + case event_quit: + sprintf(buf, "event_quit"); + break; + case event_new_conversation: + sprintf(buf, "event_new_conversation"); + break; + case event_set_info: + sprintf(buf, "event_set_info"); + break; + case event_draw_menu: + sprintf(buf, "event_draw_menu"); + break; + case event_im_displayed_sent: + sprintf(buf, "event_im_displayed_sent"); + break; + case event_im_displayed_rcvd: + sprintf(buf, "event_im_displayed_rcvd"); + break; + case event_chat_send_invite: + sprintf(buf, "event_chat_send_invite"); + break; + default: + sprintf(buf, "event_unknown"); + break; + } + return buf; +} + +int plugin_event(enum gaim_event event, void *arg1, void *arg2, void *arg3, void *arg4) +{ +#ifdef USE_PERL + char buf[BUF_LONG]; +#endif +#ifdef GAIM_PLUGINS + GList *c = callbacks; + struct gaim_callback *g; + + while (c) { + void (*zero)(void *); + void (*one)(void *, void *); + void (*two)(void *, void *, void *); + void (*three)(void *, void *, void *, void *); + void (*four)(void *, void *, void *, void *, void *); + + g = (struct gaim_callback *)c->data; + if (g->event == event && g->function !=NULL) { + switch (event) { + + /* no args */ + case event_blist_update: + case event_quit: + zero = g->function; + (*zero)(g->data); + break; + + /* one arg */ + case event_signon: + case event_signoff: + case event_new_conversation: + case event_error: + one = g->function; + (*one)(arg1, g->data); + break; + + /* two args */ + case event_buddy_signon: + case event_buddy_signoff: + case event_buddy_away: + case event_buddy_back: + case event_buddy_idle: + case event_buddy_unidle: + case event_chat_leave: + case event_set_info: + case event_draw_menu: + two = g->function; + (*two)(arg1, arg2, g->data); + break; + + /* three args */ + case event_im_send: + case event_im_displayed_sent: + case event_chat_join: + case event_chat_buddy_join: + case event_chat_buddy_leave: + case event_chat_send: + case event_away: + case event_back: + case event_warned: + three = g->function; + (*three)(arg1, arg2, arg3, g->data); + break; + + /* four args */ + case event_im_recv: + case event_chat_recv: + case event_im_displayed_rcvd: + case event_chat_send_invite: + case event_chat_invited: + four = g->function; + (*four)(arg1, arg2, arg3, arg4, g->data); + break; + + default: + debug_printf("unknown event %d\n", event); + break; + } + } + c = c->next; + } +#endif /* GAIM_PLUGINS */ +#ifdef USE_PERL + switch (event) { + case event_signon: + case event_signoff: + case event_away: + case event_back: + g_snprintf(buf, sizeof buf, "%lu", (unsigned long)arg1); + break; + case event_im_recv: + g_snprintf(buf, sizeof buf, "%lu \"%s\" %s", (unsigned long)arg1, + *(char **)arg2 ? *(char **)arg2 : "(null)", + *(char **)arg3 ? *(char **)arg3 : "(null)"); + break; + case event_im_send: + g_snprintf(buf, sizeof buf, "%lu \"%s\" %s", (unsigned long)arg1, + (char *)arg2, *(char **)arg3 ? *(char **)arg3 : "(null)"); + break; + case event_buddy_signon: + case event_buddy_signoff: + case event_set_info: + case event_buddy_away: + case event_buddy_back: + case event_buddy_idle: + case event_buddy_unidle: + g_snprintf(buf, sizeof buf, "%lu \"%s\"", (unsigned long)arg1, (char *)arg2); + break; + case event_chat_invited: + g_snprintf(buf, sizeof buf, "%lu \"%s\" \"%s\" %s", (unsigned long)arg1, + (char *)arg2, (char *)arg3, arg4 ? (char *)arg4 : ""); + break; + case event_chat_join: + case event_chat_buddy_join: + case event_chat_buddy_leave: + g_snprintf(buf, sizeof buf, "%lu %d \"%s\"", (unsigned long)arg1, + (int)arg2, (char *)arg3); + break; + case event_chat_leave: + g_snprintf(buf, sizeof buf, "%lu %d", (unsigned long)arg1, (int)arg2); + break; + case event_chat_recv: + g_snprintf(buf, sizeof buf, "%lu %d \"%s\" %s", (unsigned long)arg1, + (int)arg2, (char *)arg3, (char *)arg4 ? (char *)arg4 : "(null)"); + break; + case event_chat_send_invite: + g_snprintf(buf, sizeof buf, "%lu %d \"%s\" %s", (unsigned long)arg1, + (int)arg2, (char *)arg3, *(char **)arg4 ? *(char **)arg4 : "(null)"); + break; + case event_chat_send: + g_snprintf(buf, sizeof buf, "%lu %d %s", (unsigned long)arg1, (int)arg2, + *(char **)arg3 ? *(char **)arg3 : "(null)"); + break; + case event_warned: + g_snprintf(buf, sizeof buf, "%lu \"%s\" %d", (unsigned long)arg1, + arg2 ? (char *)arg2 : "", (int)arg3); + break; + case event_quit: + buf[0] = 0; + break; + case event_new_conversation: + g_snprintf(buf, sizeof buf, "\"%s\"", (char *)arg1); + break; + case event_im_displayed_sent: + g_snprintf(buf, sizeof buf, "%lu \"%s\" %s", (unsigned long)arg1, + (char *)arg2, *(char **)arg3 ? *(char **)arg3 : "(null)"); + break; + case event_im_displayed_rcvd: + g_snprintf(buf, sizeof buf, "%lu \"%s\" %s", (unsigned long)arg1, + (char *)arg2, (char *)arg3 ? (char *)arg3 : "(null)"); + break; + default: + return 0; + } + return perl_event(event_name(event), buf); +#else + return 0; +#endif +} + +/* Calls the gaim_plugin_remove function in any loaded plugin that has one */ +#ifdef GAIM_PLUGINS +void remove_all_plugins() +{ + GList *c = plugins; + struct gaim_plugin *p; + void (*gaim_plugin_remove)(); + + while (c) { + p = (struct gaim_plugin *)c->data; + if (g_module_symbol(p->handle, "gaim_plugin_remove", (gpointer *)&gaim_plugin_remove)) + (*gaim_plugin_remove)(); + g_free(p); + c = c->next; + } +} +#endif diff -r 9965c0bbdb7c -r a7ecfd3f7714 src/plugins.c --- a/src/plugins.c Sat Sep 29 02:08:00 2001 +0000 +++ b/src/plugins.c Sat Sep 29 23:06:30 2001 +0000 @@ -34,6 +34,8 @@ #include #endif +#ifdef GAIM_PLUGINS + #include #include @@ -46,10 +48,6 @@ #include #include "gaim.h" -#ifdef GAIM_PLUGINS - -#include - #include "pixmaps/gnome_add.xpm" #include "pixmaps/gnome_remove.xpm" #include "pixmaps/gnome_preferences.xpm" @@ -59,11 +57,6 @@ #define PATHSIZE 1024 /* XXX: stolen from dialogs.c */ -/* ------------------ Global Variables ----------------------- */ - -GList *plugins = NULL; -GList *callbacks = NULL; - /* ------------------ Local Variables ------------------------ */ static GtkWidget *plugin_dialog = NULL; @@ -84,25 +77,16 @@ /* --------------- Function Declarations --------------------- */ void show_plugins(GtkWidget *, gpointer); -void load_plugin(char *); - -void gaim_signal_connect(GModule *, enum gaim_event, void *, void *); -void gaim_signal_disconnect(GModule *, enum gaim_event, void *); -void gaim_plugin_unload(GModule *); /* UI button callbacks */ +static void unload_plugin_cb(GtkWidget *, gpointer); static void plugin_reload_cb(GtkWidget *, gpointer); static const gchar *plugin_makelistname(GModule *); -static void plugin_remove_callbacks(GModule *); - -static void plugin_reload(struct gaim_plugin *p); static void destroy_plugins(GtkWidget *, gpointer); static void load_file(GtkWidget *, gpointer); static void load_which_plugin(GtkWidget *, gpointer); -static void unload_plugin(GtkWidget *, gpointer); -static void unload_immediate(GModule *); static void list_clicked(GtkWidget *, struct gaim_plugin *); static void update_show_plugins(); static void hide_plugins(GtkWidget *, gpointer); @@ -166,80 +150,8 @@ if (plugin_dialog) gtk_widget_destroy(plugin_dialog); plugin_dialog = NULL; -} - -void load_plugin(char *filename) -{ - struct gaim_plugin *plug; - GList *c = plugins; - char *(*gaim_plugin_init)(GModule *); - char *(*cfunc)(); - char *error; - char *retval; - - if (!g_module_supported()) - return; - if (filename && !strlen(filename)) - return; - - while (filename && c) { - plug = (struct gaim_plugin *)c->data; - if (!strcmp(filename, g_module_name(plug->handle))) { - /* just need to reload plugin */ - plugin_reload(plug); - return; - } else - c = g_list_next(c); - } - plug = g_malloc(sizeof *plug); - - if (filename) { - if (last_dir) - g_free(last_dir); - last_dir = g_dirname(filename); - } - - debug_printf("Loading %s\n", filename); - plug->handle = g_module_open(filename, 0); - if (!plug->handle) { - error = (char *)g_module_error(); - do_error_dialog(error, _("Plugin Error")); - g_free(plug); - return; - } - - if (!g_module_symbol(plug->handle, "gaim_plugin_init", (gpointer *)&gaim_plugin_init)) { - do_error_dialog(g_module_error(), _("Plugin Error")); - g_module_close(plug->handle); - g_free(plug); - return; - } - - retval = (*gaim_plugin_init)(plug->handle); - debug_printf("loaded plugin returned %s\n", retval ? retval : "NULL"); - if (retval) { - plugin_remove_callbacks(plug->handle); - do_error_dialog(retval, _("Plugin Error")); - g_module_close(plug->handle); - g_free(plug); - return; - } - - plugins = g_list_append(plugins, plug); - - if (g_module_symbol(plug->handle, "name", (gpointer *)&cfunc)) { - plug->name = (*cfunc)(); - } else { - plug->name = NULL; - } - - if (g_module_symbol(plug->handle, "description", (gpointer *)&cfunc)) - plug->description = (*cfunc)(); - else - plug->description = NULL; update_show_plugins(); - save_prefs(); } void show_plugins(GtkWidget *w, gpointer data) @@ -358,7 +270,7 @@ gtk_tooltips_set_tip(tooltips, reload, _("Reload the selected plugin"), ""); unload = picture_button(plugwindow, _("Unload"), gnome_remove_xpm); - gtk_signal_connect(GTK_OBJECT(unload), "clicked", GTK_SIGNAL_FUNC(unload_plugin), pluglist); + gtk_signal_connect(GTK_OBJECT(unload), "clicked", GTK_SIGNAL_FUNC(unload_plugin_cb), pluglist); gtk_box_pack_start(GTK_BOX(bothbox), unload, TRUE, TRUE, 0); gtk_tooltips_set_tip(tooltips, unload, _("Unload the selected plugin"), ""); @@ -415,11 +327,10 @@ } } -static void unload_plugin(GtkWidget *w, gpointer data) +static void unload_plugin_cb(GtkWidget *w, gpointer data) { GList *i; struct gaim_plugin *p; - void (*gaim_plugin_remove)(); i = GTK_LIST(pluglist)->selection; @@ -428,105 +339,28 @@ p = gtk_object_get_user_data(GTK_OBJECT(i->data)); - /* Attempt to call the plugin's remove function (if there) */ - if (g_module_symbol(p->handle, "gaim_plugin_remove", (gpointer *)&gaim_plugin_remove)) - (*gaim_plugin_remove)(); - - unload_immediate(p->handle); + unload_plugin(p); update_show_plugins(); } -static void unload_for_real(void *handle) -{ - GList *i; - struct gaim_plugin *p = NULL; - - i = plugins; - while (i) { - p = (struct gaim_plugin *)i->data; - if (handle == p->handle) - break; - p = NULL; - i = g_list_next(i); - } - - if (!p) - return; - - debug_printf("Unloading %s\n", g_module_name(p->handle)); - - plugin_remove_callbacks(p->handle); - - plugins = g_list_remove(plugins, p); - g_free(p); - update_show_plugins(); - save_prefs(); -} - -static void unload_immediate(GModule *handle) -{ - unload_for_real(handle); - g_module_close(handle); -} - -static gboolean unload_timeout(gpointer handle) -{ - g_module_close(handle); - return FALSE; -} - -void gaim_plugin_unload(GModule *handle) -{ - unload_for_real(handle); - g_timeout_add(5000, unload_timeout, handle); -} - static void plugin_reload_cb(GtkWidget *w, gpointer data) { GList *i; + struct gaim_plugin *p; i = GTK_LIST(pluglist)->selection; if (i == NULL) return; /* Just pass off plugin to the actual function */ - plugin_reload(gtk_object_get_user_data(GTK_OBJECT(i->data))); -} - -/* Do unload/load cycle of plugin. */ -static void plugin_reload(struct gaim_plugin *p) -{ - char file[PATHSIZE]; - void (*gaim_plugin_remove)(); - GModule *handle = p->handle; - struct gaim_plugin *plug; - GList *plugs; + p = reload_plugin(gtk_object_get_user_data(GTK_OBJECT(i->data))); - strncpy(file, g_module_name(handle), sizeof(file)); - file[sizeof(file) - 1] = '\0'; - - debug_printf("Reloading %s\n", file); - - /* Unload */ - if (g_module_symbol(handle, "gaim_plugin_remove", (gpointer *)&gaim_plugin_remove)) - (*gaim_plugin_remove)(); - unload_immediate(handle); - - /* Load */ - load_plugin(file); + update_show_plugins(); /* Try and reselect the plugin in list */ if (!pluglist) return; - plugs = plugins; - while (plugs) { - plug = plugs->data; - if (!strcmp(file, g_module_name(plug->handle))) { - gtk_list_select_item(GTK_LIST(pluglist), g_list_index(plugins, plug)); - return; - } - plugs = plugs->next; - } + gtk_list_select_item(GTK_LIST(pluglist), g_list_index(plugins, p)); } static void list_clicked(GtkWidget *w, struct gaim_plugin *p) @@ -594,330 +428,5 @@ return filename; } - -/* Remove all callbacks associated with plugin handle */ -static void plugin_remove_callbacks(GModule *handle) -{ - GList *c = callbacks; - struct gaim_callback *g; - debug_printf("%d callbacks to search\n", g_list_length(callbacks)); - - while (c) { - g = (struct gaim_callback *)c->data; - if (g->handle == handle) { - c = g_list_next(c); - callbacks = g_list_remove(callbacks, (gpointer)g); - debug_printf("Removing callback, %d remain\n", g_list_length(callbacks)); - } else - c = g_list_next(c); - } -} - -void gaim_signal_connect(GModule *handle, enum gaim_event which, void *func, void *data) -{ - struct gaim_callback *call = g_new0(struct gaim_callback, 1); - call->handle = handle; - call->event = which; - call->function = func; - call->data = data; - - callbacks = g_list_append(callbacks, call); - debug_printf("Adding callback %d\n", g_list_length(callbacks)); -} - -void gaim_signal_disconnect(GModule *handle, enum gaim_event which, void *func) -{ - GList *c = callbacks; - struct gaim_callback *g = NULL; - - while (c) { - g = (struct gaim_callback *)c->data; - if (handle == g->handle && func == g->function) { - callbacks = g_list_remove(callbacks, c->data); - g_free(g); - c = callbacks; - if (c == NULL) - break; - } - c = g_list_next(c); - } -} - -#endif /* GAIM_PLUGINS */ - -static char *event_name(enum gaim_event event) -{ - static char buf[128]; - switch (event) { - case event_signon: - sprintf(buf, "event_signon"); - break; - case event_signoff: - sprintf(buf, "event_signoff"); - break; - case event_away: - sprintf(buf, "event_away"); - break; - case event_back: - sprintf(buf, "event_back"); - break; - case event_im_recv: - sprintf(buf, "event_im_recv"); - break; - case event_im_send: - sprintf(buf, "event_im_send"); - break; - case event_buddy_signon: - sprintf(buf, "event_buddy_signon"); - break; - case event_buddy_signoff: - sprintf(buf, "event_buddy_signoff"); - break; - case event_buddy_away: - sprintf(buf, "event_buddy_away"); - break; - case event_buddy_back: - sprintf(buf, "event_buddy_back"); - break; - case event_buddy_idle: - sprintf(buf, "event_buddy_idle"); - break; - case event_buddy_unidle: - sprintf(buf, "event_buddy_unidle"); - break; - case event_blist_update: - sprintf(buf, "event_blist_update"); - break; - case event_chat_invited: - sprintf(buf, "event_chat_invited"); - break; - case event_chat_join: - sprintf(buf, "event_chat_join"); - break; - case event_chat_leave: - sprintf(buf, "event_chat_leave"); - break; - case event_chat_buddy_join: - sprintf(buf, "event_chat_buddy_join"); - break; - case event_chat_buddy_leave: - sprintf(buf, "event_chat_buddy_leave"); - break; - case event_chat_recv: - sprintf(buf, "event_chat_recv"); - break; - case event_chat_send: - sprintf(buf, "event_chat_send"); - break; - case event_warned: - sprintf(buf, "event_warned"); - break; - case event_error: - sprintf(buf, "event_error"); - break; - case event_quit: - sprintf(buf, "event_quit"); - break; - case event_new_conversation: - sprintf(buf, "event_new_conversation"); - break; - case event_set_info: - sprintf(buf, "event_set_info"); - break; - case event_draw_menu: - sprintf(buf, "event_draw_menu"); - break; - case event_im_displayed_sent: - sprintf(buf, "event_im_displayed_sent"); - break; - case event_im_displayed_rcvd: - sprintf(buf, "event_im_displayed_rcvd"); - break; - case event_chat_send_invite: - sprintf(buf, "event_chat_send_invite"); - break; - default: - sprintf(buf, "event_unknown"); - break; - } - return buf; -} - -int plugin_event(enum gaim_event event, void *arg1, void *arg2, void *arg3, void *arg4) -{ -#ifdef USE_PERL - char buf[BUF_LONG]; #endif -#ifdef GAIM_PLUGINS - GList *c = callbacks; - struct gaim_callback *g; - - while (c) { - void (*zero)(void *); - void (*one)(void *, void *); - void (*two)(void *, void *, void *); - void (*three)(void *, void *, void *, void *); - void (*four)(void *, void *, void *, void *, void *); - - g = (struct gaim_callback *)c->data; - if (g->event == event && g->function !=NULL) { - switch (event) { - - /* no args */ - case event_blist_update: - case event_quit: - zero = g->function; - (*zero)(g->data); - break; - - /* one arg */ - case event_signon: - case event_signoff: - case event_new_conversation: - case event_error: - one = g->function; - (*one)(arg1, g->data); - break; - - /* two args */ - case event_buddy_signon: - case event_buddy_signoff: - case event_buddy_away: - case event_buddy_back: - case event_buddy_idle: - case event_buddy_unidle: - case event_chat_leave: - case event_set_info: - case event_draw_menu: - two = g->function; - (*two)(arg1, arg2, g->data); - break; - - /* three args */ - case event_im_send: - case event_im_displayed_sent: - case event_chat_join: - case event_chat_buddy_join: - case event_chat_buddy_leave: - case event_chat_send: - case event_away: - case event_back: - case event_warned: - three = g->function; - (*three)(arg1, arg2, arg3, g->data); - break; - - /* four args */ - case event_im_recv: - case event_chat_recv: - case event_im_displayed_rcvd: - case event_chat_send_invite: - case event_chat_invited: - four = g->function; - (*four)(arg1, arg2, arg3, arg4, g->data); - break; - - default: - debug_printf("unknown event %d\n", event); - break; - } - } - c = c->next; - } -#endif /* GAIM_PLUGINS */ -#ifdef USE_PERL - switch (event) { - case event_signon: - case event_signoff: - case event_away: - case event_back: - g_snprintf(buf, sizeof buf, "%lu", (unsigned long)arg1); - break; - case event_im_recv: - g_snprintf(buf, sizeof buf, "%lu \"%s\" %s", (unsigned long)arg1, - *(char **)arg2 ? *(char **)arg2 : "(null)", - *(char **)arg3 ? *(char **)arg3 : "(null)"); - break; - case event_im_send: - g_snprintf(buf, sizeof buf, "%lu \"%s\" %s", (unsigned long)arg1, - (char *)arg2, *(char **)arg3 ? *(char **)arg3 : "(null)"); - break; - case event_buddy_signon: - case event_buddy_signoff: - case event_set_info: - case event_buddy_away: - case event_buddy_back: - case event_buddy_idle: - case event_buddy_unidle: - g_snprintf(buf, sizeof buf, "%lu \"%s\"", (unsigned long)arg1, (char *)arg2); - break; - case event_chat_invited: - g_snprintf(buf, sizeof buf, "%lu \"%s\" \"%s\" %s", (unsigned long)arg1, - (char *)arg2, (char *)arg3, arg4 ? (char *)arg4 : ""); - break; - case event_chat_join: - case event_chat_buddy_join: - case event_chat_buddy_leave: - g_snprintf(buf, sizeof buf, "%lu %d \"%s\"", (unsigned long)arg1, - (int)arg2, (char *)arg3); - break; - case event_chat_leave: - g_snprintf(buf, sizeof buf, "%lu %d", (unsigned long)arg1, (int)arg2); - break; - case event_chat_recv: - g_snprintf(buf, sizeof buf, "%lu %d \"%s\" %s", (unsigned long)arg1, - (int)arg2, (char *)arg3, (char *)arg4 ? (char *)arg4 : "(null)"); - break; - case event_chat_send_invite: - g_snprintf(buf, sizeof buf, "%lu %d \"%s\" %s", (unsigned long)arg1, - (int)arg2, (char *)arg3, *(char **)arg4 ? *(char **)arg4 : "(null)"); - break; - case event_chat_send: - g_snprintf(buf, sizeof buf, "%lu %d %s", (unsigned long)arg1, (int)arg2, - *(char **)arg3 ? *(char **)arg3 : "(null)"); - break; - case event_warned: - g_snprintf(buf, sizeof buf, "%lu \"%s\" %d", (unsigned long)arg1, - arg2 ? (char *)arg2 : "", (int)arg3); - break; - case event_quit: - buf[0] = 0; - break; - case event_new_conversation: - g_snprintf(buf, sizeof buf, "\"%s\"", (char *)arg1); - break; - case event_im_displayed_sent: - g_snprintf(buf, sizeof buf, "%lu \"%s\" %s", (unsigned long)arg1, - (char *)arg2, *(char **)arg3 ? *(char **)arg3 : "(null)"); - break; - case event_im_displayed_rcvd: - g_snprintf(buf, sizeof buf, "%lu \"%s\" %s", (unsigned long)arg1, - (char *)arg2, (char *)arg3 ? (char *)arg3 : "(null)"); - break; - default: - return 0; - } - return perl_event(event_name(event), buf); -#else - return 0; -#endif -} - -/* Calls the gaim_plugin_remove function in any loaded plugin that has one */ -#ifdef GAIM_PLUGINS -void remove_all_plugins() -{ - GList *c = plugins; - struct gaim_plugin *p; - void (*gaim_plugin_remove)(); - - while (c) { - p = (struct gaim_plugin *)c->data; - if (g_module_symbol(p->handle, "gaim_plugin_remove", (gpointer *)&gaim_plugin_remove)) - (*gaim_plugin_remove)(); - g_free(p); - c = c->next; - } -} -#endif diff -r 9965c0bbdb7c -r a7ecfd3f7714 src/protocols/Makefile.am --- a/src/protocols/Makefile.am Sat Sep 29 02:08:00 2001 +0000 +++ b/src/protocols/Makefile.am Sat Sep 29 23:06:30 2001 +0000 @@ -1,6 +1,6 @@ if PRPLS -SUBDIRS = icq irc jabber msn napster oscar toc yahoo zephyr +SUBDIRS = gg icq irc jabber msn napster oscar toc yahoo zephyr else diff -r 9965c0bbdb7c -r a7ecfd3f7714 src/protocols/gg/.cvsignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/.cvsignore Sat Sep 29 23:06:30 2001 +0000 @@ -0,0 +1,7 @@ +.deps +.libs +Makefile +Makefile.in +gg.lo +libgg.la +libgg.lo diff -r 9965c0bbdb7c -r a7ecfd3f7714 src/protocols/gg/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/Makefile.am Sat Sep 29 23:06:30 2001 +0000 @@ -0,0 +1,28 @@ +EXTRA_DIST = protocol.txt + +pkgdir = $(libdir)/gaim + +CFLAGS += -I\$(top_srcdir)/src $(st) $(DEBUG_CFLAGS) +libgg_la_LDFLAGS = -avoid-version + +if STATIC_GG + +st = -DSTATIC +pkg_LTLIBRARIES = +noinst_LIBRARIES = libgg.a + +libgg_a_SOURCES = libgg.c \ + libgg.h \ + gg.c + +else + +st = +pkg_LTLIBRARIES = libgg.la +noinst_LIBRARIES = + +libgg_la_SOURCES = libgg.c \ + libgg.h \ + gg.c + +endif diff -r 9965c0bbdb7c -r a7ecfd3f7714 src/protocols/gg/gg.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/gg.c Sat Sep 29 23:06:30 2001 +0000 @@ -0,0 +1,1040 @@ +/* + * gaim - Gadu-Gadu Protocol Plugin + * $Id: gg.c 2406 2001-09-29 23:06:30Z warmenhoven $ + * + * 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. + * + * 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 + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LANGINFO_CODESET +#include +#endif +#ifdef HAVE_ICONV +#include +#endif +/* Library from EKG (Eksperymentalny Klient Gadu-Gadu) */ +#include "libgg.h" +#include "multi.h" +#include "prpl.h" +#include "gaim.h" +#include "proxy.h" + +#include "pixmaps/gg_suncloud.xpm" +#include "pixmaps/gg_sunred.xpm" +#include "pixmaps/gg_sunwhitered.xpm" +#include "pixmaps/gg_sunyellow.xpm" + +#define USEROPT_NICK 0 + +#define AGG_BUF_LEN 1024 + +#define AGG_GENDER_NONE -1 + +#define AGG_PUBDIR_FORM "/appsvc/fmpubquery2.asp" +#define AGG_PUBDIR_MAX_ENTRIES 200 + +#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") + +struct agg_data { + struct gg_session *sess; +}; + +struct agg_search { + struct gaim_connection *gc; + gchar *search_data; + int inpa; +}; + +static char *agg_name() +{ + return "Gadu-Gadu"; +} + +static gchar *charset_convert(const gchar *locstr, char *encsrc, char *encdst) +{ +#ifdef HAVE_ICONV + gchar *dststr; + size_t loclen, dstlen; + gchar *fsave, *tsave; + size_t count; + static iconv_t cd = (iconv_t)(-1); + + if (cd == (iconv_t)(-1)) { + cd = iconv_open(encdst, encsrc); + if (cd == (iconv_t)(-1)) { + return g_strdup(locstr); + } + } + + loclen = strlen(locstr); + /* we are ready for multibyte conversions */ + dstlen = MB_LEN_MAX * loclen; + dststr = g_new0(gchar, dstlen + 1); + fsave = (gchar *)locstr; + tsave = dststr; + count = iconv(cd, &fsave, &loclen, &tsave, &dstlen); + if (count == -1) { + g_free(dststr); + return g_strdup(locstr); + } + return dststr; +#else + return g_strdup(locstr); +#endif +} + +static gboolean invalid_uin(char *uin) +{ + unsigned long int res = strtol(uin, (char **)NULL, 10); + if (res == LONG_MIN || res == LONG_MAX || res == 0) + return TRUE; + return FALSE; +} + +static gint args_compare(gconstpointer a, gconstpointer b) +{ + gchar *arg_a = (gchar *)a; + gchar *arg_b = (gchar *)b; + + return g_strcasecmp(arg_a, arg_b); +} + +static gboolean allowed_uin(struct gaim_connection *gc, char *uin) +{ + switch (gc->permdeny) { + 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->permit, uin, args_compare)) + return TRUE; + return FALSE; + break; + case 4: + /* deny some. */ + if (g_slist_find_custom(gc->deny, uin, args_compare)) + return FALSE; + return TRUE; + break; + default: + return TRUE; + break; + } +} + +static gchar *find_local_charset(void) +{ + gchar *gg_localenc = g_getenv("GG_CHARSET"); + + if (gg_localenc == NULL) { +#ifdef HAVE_LANGINFO_CODESET + gg_localenc = nl_langinfo(CODESET); +#else + gg_localenc = "US-ASCII"; +#endif + } + return gg_localenc; +} + +static char *handle_errcode(int errcode, gboolean show) +{ + 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 writting to socket.")); + break; + case GG_FAILURE_PASSWORD: + g_snprintf(msg, sizeof(msg), _("Authentification failed.")); + break; + default: + g_snprintf(msg, sizeof(msg), _("Unknown Error Code.")); + break; + } + + if (show) + do_error_dialog(msg, _("Gadu-Gadu Error")); + + return msg; +} + +static gchar *encode_postdata(const gchar *data) +{ + gchar *p = NULL; + int i, j = 0; + for (i = 0; i < strlen(data); i++) { + /* locale insensitive, doesn't reflect RFC (1738 section 2.2, 1866 section 8.2.1) */ + if ((data[i] >= 'a' && data[i] <= 'z') + || (data[i] >= 'A' && data[i] <= 'Z') + || (data[i] >= '0' && data[i] <= '9') + || data[i] == '=' || data[i] == '&' + || data[i] == '\n' || data[i] == '\r' || data[i] == '\t' || data[i] == '\014') { + p = g_realloc(p, j + 1); + p[j] = data[i]; + j++; + } else { + p = g_realloc(p, j + 4); /* remember, sprintf appends a '\0' */ + sprintf(p + j, "%%%02x", (unsigned char)data[i]); + j += 3; + } + } + p = g_realloc(p, j + 1); + p[j] = '\0'; + + if (p && strlen(p)) + return p; + else + return g_strdup(data); +} + +static void agg_set_away(struct gaim_connection *gc, char *state, char *msg) +{ + struct agg_data *gd = (struct agg_data *)gc->proto_data; + + if (gc->away) + gc->away = NULL; + + if (!g_strcasecmp(state, AGG_STATUS_AVAIL)) + gg_change_status(gd->sess, GG_STATUS_AVAIL); + else if (!g_strcasecmp(state, AGG_STATUS_AVAIL_FRIENDS)) + gg_change_status(gd->sess, GG_STATUS_AVAIL | GG_STATUS_FRIENDS_MASK); + else if (!g_strcasecmp(state, AGG_STATUS_BUSY)) { + gg_change_status(gd->sess, GG_STATUS_BUSY); + gc->away = ""; + } else if (!g_strcasecmp(state, AGG_STATUS_BUSY_FRIENDS)) { + gg_change_status(gd->sess, GG_STATUS_BUSY | GG_STATUS_FRIENDS_MASK); + gc->away = ""; + } else if (!g_strcasecmp(state, AGG_STATUS_INVISIBLE)) { + gg_change_status(gd->sess, GG_STATUS_INVISIBLE); + gc->away = ""; + } else if (!g_strcasecmp(state, AGG_STATUS_INVISIBLE_FRIENDS)) { + gg_change_status(gd->sess, GG_STATUS_INVISIBLE | GG_STATUS_FRIENDS_MASK); + gc->away = ""; + } else if (!g_strcasecmp(state, AGG_STATUS_NOT_AVAIL)) { + gg_change_status(gd->sess, GG_STATUS_NOT_AVAIL); + gc->away = ""; + } else if (!g_strcasecmp(state, GAIM_AWAY_CUSTOM)) { + if (msg) { + gg_change_status(gd->sess, GG_STATUS_BUSY); + gc->away = ""; + } else + gg_change_status(gd->sess, GG_STATUS_AVAIL); + } +} + +static gchar *get_away_text(int uc) +{ + if (uc == UC_UNAVAILABLE) + return AGG_STATUS_NOT_AVAIL; + uc = uc >> 5; + switch (uc) { + case GG_STATUS_AVAIL: + default: + return AGG_STATUS_AVAIL; + case GG_STATUS_AVAIL | GG_STATUS_FRIENDS_MASK: + return AGG_STATUS_AVAIL_FRIENDS; + case GG_STATUS_BUSY: + return AGG_STATUS_BUSY; + case GG_STATUS_BUSY | GG_STATUS_FRIENDS_MASK: + return AGG_STATUS_BUSY_FRIENDS; + case GG_STATUS_INVISIBLE: + return AGG_STATUS_INVISIBLE; + case GG_STATUS_INVISIBLE | GG_STATUS_FRIENDS_MASK: + return AGG_STATUS_INVISIBLE_FRIENDS; + case GG_STATUS_NOT_AVAIL: + return AGG_STATUS_NOT_AVAIL; + } +} + +static GList *agg_away_states() +{ + GList *m = NULL; + + m = g_list_append(m, AGG_STATUS_AVAIL); + m = g_list_append(m, AGG_STATUS_BUSY); + m = g_list_append(m, AGG_STATUS_INVISIBLE); + m = g_list_append(m, AGG_STATUS_AVAIL_FRIENDS); + m = g_list_append(m, AGG_STATUS_BUSY_FRIENDS); + m = g_list_append(m, AGG_STATUS_INVISIBLE_FRIENDS); + m = g_list_append(m, AGG_STATUS_NOT_AVAIL); + return m; +} + +/* Enhance these functions, more options and such stuff */ +static GList *agg_buddy_menu(struct gaim_connection *gc, char *who) +{ + GList *m = NULL; + struct proto_buddy_menu *pbm; + struct buddy *b = find_buddy(gc, who); + static char buf[AGG_BUF_LEN]; + + if (!b) { + return m; + } + + pbm = g_new0(struct proto_buddy_menu, 1); + g_snprintf(buf, sizeof(buf), _("Status: %s"), get_away_text(b->uc)); + pbm->label = buf; + pbm->callback = NULL; + pbm->gc = gc; + m = g_list_append(m, pbm); + + return m; +} + +static GList *agg_user_opts() +{ + GList *m = NULL; + struct proto_user_opt *puo; + + puo = g_new0(struct proto_user_opt, 1); + puo->label = _("Nick:"); + puo->def = _("Gadu-Gadu User"); + puo->pos = USEROPT_NICK; + m = g_list_append(m, puo); + + return m; +} + +static void main_callback(gpointer data, gint source, GaimInputCondition cond) +{ + struct gaim_connection *gc = data; + struct agg_data *gd = gc->proto_data; + struct gg_event *e; + + debug_printf("main_callback enter: begin\n"); + + if (gd->sess->fd != source) + gd->sess->fd = source; + + if (source == -1) { + signoff(gc); + return; + } + + if (!(e = gg_watch_fd(gd->sess))) { + debug_printf("main_callback: gg_watch_fd failed - CRITICAL!\n"); + signoff(gc); + return; + } + + switch (e->type) { + case GG_EVENT_NONE: + /* do nothing */ + break; + case GG_EVENT_CONN_SUCCESS: + debug_printf("main_callback: CONNECTED AGAIN!?\n"); + break; + case GG_EVENT_CONN_FAILED: + if (gc->inpa) + gaim_input_remove(gc->inpa); + handle_errcode(e->event.failure, TRUE); + signoff(gc); + break; + case GG_EVENT_MSG: + { + gchar *imsg; + gchar user[20]; + + g_snprintf(user, sizeof(user), "%u", e->event.msg.sender); + if (!allowed_uin(gc, user)) + break; + imsg = charset_convert(e->event.msg.message, "CP1250", find_local_charset()); + serv_got_im(gc, user, imsg, 0, time((time_t) NULL)); + g_free(imsg); + } + break; + case GG_EVENT_NOTIFY: + { + gchar user[20]; + struct gg_notify_reply *n = e->event.notify; + guint status; + + while (n->uin) { + switch (n->status) { + case GG_STATUS_NOT_AVAIL: + status = UC_UNAVAILABLE; + break; + case GG_STATUS_AVAIL: + case GG_STATUS_BUSY: + case GG_STATUS_INVISIBLE: + case GG_STATUS_FRIENDS_MASK: + status = UC_NORMAL | (e->event.status.status << 5); + break; + default: + status = UC_NORMAL; + break; + } + + g_snprintf(user, sizeof(user), "%u", n->uin); + serv_got_update(gc, user, (status == UC_UNAVAILABLE) ? 0 : 1, 0, 0, 0, + status, 0); + n++; + } + } + break; + case GG_EVENT_STATUS: + { + gchar user[20]; + guint status; + + switch (e->event.status.status) { + case GG_STATUS_NOT_AVAIL: + status = UC_UNAVAILABLE; + break; + case GG_STATUS_AVAIL: + case GG_STATUS_BUSY: + case GG_STATUS_INVISIBLE: + case GG_STATUS_FRIENDS_MASK: + status = UC_NORMAL | (e->event.status.status << 5); + break; + default: + status = UC_NORMAL; + break; + } + + g_snprintf(user, sizeof(user), "%u", e->event.status.uin); + serv_got_update(gc, user, (status == UC_UNAVAILABLE) ? 0 : 1, 0, 0, 0, status, + 0); + } + break; + case GG_EVENT_ACK: + debug_printf("main_callback: message %d to %u sent with status %d\n", + e->event.ack.seq, e->event.ack.recipient, e->event.ack.status); + break; + default: + debug_printf("main_callback: unsupported event %d\n", e->type); + break; + } + gg_free_event(e); +} + +static void login_callback(gpointer data, gint source, GaimInputCondition cond) +{ + struct gaim_connection *gc = data; + struct agg_data *gd = gc->proto_data; + struct gg_event *e; + + if (!g_slist_find(connections, data)) { + close(source); + return; + } + + if (gd->sess->fd != source) + gd->sess->fd = source; + + if (source == -1) { + hide_login_progress(gc, _("Unable to connect.")); + signoff(gc); + return; + } + + if (gc->inpa == 0) + gc->inpa = gaim_input_add(gd->sess->fd, GAIM_INPUT_READ, login_callback, gc); + + switch (gd->sess->state) { + case GG_STATE_CONNECTING_HTTP: + case GG_STATE_WRITING_HTTP: + set_login_progress(gc, 2, _("Handshake")); + case GG_STATE_CONNECTING_GG: + set_login_progress(gc, 3, _("Connecting to GG server")); + break; + case GG_STATE_WAITING_FOR_KEY: + set_login_progress(gc, 4, _("Waiting for server key")); + case GG_STATE_SENDING_KEY: + set_login_progress(gc, 5, _("Sending key")); + break; + default: + break; + } + + if (!(e = gg_watch_fd(gd->sess))) { + debug_printf("login_callback: gg_watch_fd failed - CRITICAL!\n"); + signoff(gc); + return; + } + + 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 */ + account_online(gc); + serv_finish_login(gc); + + do_import(gc, NULL); + break; + case GG_EVENT_CONN_FAILED: + gaim_input_remove(gc->inpa); + gc->inpa = 0; + handle_errcode(e->event.failure, TRUE); + signoff(gc); + break; + default: + break; + } + + gg_free_event(e); +} + +static void agg_keepalive(struct gaim_connection *gc) +{ + struct agg_data *gd = (struct agg_data *)gc->proto_data; + if (gg_ping(gd->sess) < 0) { + signoff(gc); + return; + } +} + +static void agg_login(struct aim_user *user) +{ + struct gaim_connection *gc = new_gaim_conn(user); + struct agg_data *gd = gc->proto_data = g_new0(struct agg_data, 1); + char buf[80]; + + gd->sess = g_new0(struct gg_session, 1); + + if (user->proto_opt[USEROPT_NICK][0]) + g_snprintf(gc->displayname, sizeof(gc->displayname), "%s", + user->proto_opt[USEROPT_NICK]); + + set_login_progress(gc, 1, _("Looking up GG server")); + + if (invalid_uin(user->username)) { + hide_login_progress(gc, _("Invalid Gadu-Gadu UIN specified")); + signoff(gc); + return; + } + + gc->inpa = 0; + + /* + if (gg_login(gd->sess, strtol(user->username, (char **)NULL, 10), user->password, 1) < 0) { + debug_printf("uin=%u, pass=%s", strtol(user->username, (char **)NULL, 10), user->password); + hide_login_progress(gc, "Unable to connect."); + signoff(gc); + return; + } + + gg_login() sucks for me, so I'm using proxy_connect() + */ + + gd->sess->uin = (uin_t) strtol(user->username, (char **)NULL, 10); + gd->sess->password = g_strdup(user->password); + gd->sess->state = GG_STATE_CONNECTING_HTTP; + gd->sess->check = GG_CHECK_WRITE; + gd->sess->async = 1; + gd->sess->recv_left = 0; + gd->sess->fd = proxy_connect(GG_APPMSG_HOST, GG_APPMSG_PORT, login_callback, gc); + + if (gd->sess->fd < 0) { + g_snprintf(buf, sizeof(buf), _("Connect to %s failed"), GG_APPMSG_HOST); + hide_login_progress(gc, buf); + signoff(gc); + return; + } +} + +static void agg_close(struct gaim_connection *gc) +{ + struct agg_data *gd = (struct agg_data *)gc->proto_data; + if (gc->inpa) + gaim_input_remove(gc->inpa); + gg_logoff(gd->sess); + gg_free_session(gd->sess); + g_free(gc->proto_data); +} + +static int agg_send_im(struct gaim_connection *gc, char *who, char *msg, int flags) +{ + struct agg_data *gd = (struct agg_data *)gc->proto_data; + gchar *imsg; + + if (invalid_uin(who)) { + do_error_dialog(_("You are trying to send message to invalid Gadu-Gadu UIN!"), + _("Gadu-Gadu Error")); + return -1; + } + + if (strlen(msg) > 0) { + imsg = charset_convert(msg, find_local_charset(), "CP1250"); + if (gg_send_message(gd->sess, (flags & IM_FLAG_CHECKBOX) ? GG_CLASS_MSG : GG_CLASS_CHAT, + strtol(who, (char **)NULL, 10), imsg) < 0) + return -1; + g_free(imsg); + } + return 1; +} + +static void agg_add_buddy(struct gaim_connection *gc, char *who) +{ + struct agg_data *gd = (struct agg_data *)gc->proto_data; + if (invalid_uin(who)) + return; + gg_add_notify(gd->sess, strtol(who, (char **)NULL, 10)); +} + +static void agg_rem_buddy(struct gaim_connection *gc, char *who) +{ + struct agg_data *gd = (struct agg_data *)gc->proto_data; + if (invalid_uin(who)) + return; + gg_remove_notify(gd->sess, strtol(who, (char **)NULL, 10)); +} + +static void agg_add_buddies(struct gaim_connection *gc, GList *whos) +{ + struct agg_data *gd = (struct agg_data *)gc->proto_data; + uin_t *userlist = NULL; + int userlist_size = 0; + + while (whos) { + if (!invalid_uin(whos->data)) { + userlist_size++; + userlist = g_renew(uin_t, userlist, userlist_size); + userlist[userlist_size - 1] = + (uin_t) strtol((char *)whos->data, (char **)NULL, 10); + } + whos = g_list_next(whos); + } + + if (userlist) { + gg_notify(gd->sess, userlist, userlist_size); + g_free(userlist); + } +} + +static void search_results(gpointer data, gint source, GaimInputCondition cond) +{ + struct agg_search *srch = data; + struct gaim_connection *gc = srch->gc; + gchar *buf; + char *ptr; + char *webdata; + int len; + char read_data; + gchar **webdata_tbl; + int i, j; + + if (!g_slist_find(connections, gc)) { + debug_printf("search_callback: g_slist_find error\n"); + gaim_input_remove(srch->inpa); + g_free(srch); + close(source); + return; + } + + webdata = NULL; + len = 0; + + while (read(source, &read_data, 1) > 0 || errno == EWOULDBLOCK) { + if (errno == EWOULDBLOCK) { + errno = 0; + continue; + } + + if (!read_data) + continue; + + len++; + webdata = g_realloc(webdata, len); + webdata[len - 1] = read_data; + } + + webdata = g_realloc(webdata, len + 1); + webdata[len] = 0; + + gaim_input_remove(srch->inpa); + g_free(srch); + close(source); + + if ((ptr = strstr(webdata, "query_results:")) == NULL || (ptr = strchr(ptr, '\n')) == NULL) { + debug_printf("search_callback: pubdir result [%s]\n", webdata); + g_free(webdata); + do_error_dialog(_("Couldn't get search results"), _("Gadu-Gadu Error")); + return; + } + ptr++; + + buf = g_strconcat("", _("Gadu-Gadu Search Engine"), "
\n", NULL); + + webdata_tbl = g_strsplit(ptr, "\n", AGG_PUBDIR_MAX_ENTRIES); + + g_free(webdata); + + j = 0; + + /* Parse array */ + 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", find_local_charset()); + + 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, "", _("Second 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); + + g_show_info_text(buf, NULL); + + g_free(buf); +} + +static void search_callback(gpointer data, gint source, GaimInputCondition cond) +{ + struct agg_search *srch = data; + struct gaim_connection *gc = srch->gc; + gchar *search_data = srch->search_data; + gchar *buf; + char *ptr; + + debug_printf("search_callback enter: begin\n"); + + if (!g_slist_find(connections, gc)) { + debug_printf("search_callback: g_slist_find error\n"); + g_free(search_data); + g_free(srch); + close(source); + return; + } + + if (source == -1) { + g_free(search_data); + g_free(srch); + return; + } + + ptr = encode_postdata(search_data); + g_free(search_data); + + debug_printf("search_callback: pubdir request [%s]\n", ptr); + + buf = g_strdup_printf("POST " AGG_PUBDIR_FORM " HTTP/1.0\r\n" + "Host: " GG_PUBDIR_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: Mozilla/4.7 [en] (Win98; I)\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" "\r\n" "%s\r\n", strlen(ptr), ptr); + + g_free(ptr); + + if (write(source, buf, strlen(buf)) < strlen(buf)) { + g_free(buf); + g_free(srch); + close(source); + do_error_dialog(_("Couldn't send search request"), _("Gadu-Gadu Error")); + return; + } + + g_free(buf); + + srch->inpa = gaim_input_add(source, GAIM_INPUT_READ, search_results, srch); +} + +static void agg_dir_search(struct gaim_connection *gc, char *first, char *middle, + char *last, char *maiden, char *city, char *state, char *country, char *email) +{ + struct agg_search *srch = g_new0(struct agg_search, 1); + static char msg[AGG_BUF_LEN]; + + srch->gc = gc; + + if (email && strlen(email)) { + srch->search_data = g_strdup_printf("Mode=1&Email=%s", email); + } else { + gchar *new_first = charset_convert(first, find_local_charset(), "CP1250"); + gchar *new_last = charset_convert(last, find_local_charset(), "CP1250"); + gchar *new_city = charset_convert(city, find_local_charset(), "CP1250"); + + /* For active only add &ActiveOnly= */ + srch->search_data = g_strdup_printf("Mode=0&FirstName=%s&LastName=%s&Gender=%d" + "&NickName=%s&City=%s&MinBirth=%d&MaxBirth=%d", + new_first, new_last, AGG_GENDER_NONE, + "", new_city, 0, 0); + + g_free(new_first); + g_free(new_last); + g_free(new_city); + } + + if (proxy_connect(GG_PUBDIR_HOST, GG_PUBDIR_PORT, search_callback, srch) < 0) { + g_snprintf(msg, sizeof(msg), _("Connect to search service failed (%s)"), GG_PUBDIR_HOST); + do_error_dialog(msg, _("Gadu-Gadu Error")); + g_free(srch->search_data); + g_free(srch); + return; + } +} + +static void agg_do_action(struct gaim_connection *gc, char *action) +{ + if (!strcmp(action, _("Directory Search"))) { + show_find_info(gc); + } +} + +static GList *agg_actions() +{ + GList *m = NULL; + + m = g_list_append(m, _("Directory Search")); + + return m; +} + +static void agg_get_info(struct gaim_connection *gc, char *who) +{ + struct agg_search *srch = g_new0(struct agg_search, 1); + static char msg[AGG_BUF_LEN]; + + srch->gc = gc; + + /* If it's invalid uin then maybe it's nickname? */ + if (invalid_uin(who)) { + gchar *new_who = charset_convert(who, find_local_charset(), "CP1250"); + + srch->search_data = g_strdup_printf("Mode=0&FirstName=%s&LastName=%s&Gender=%d" + "&NickName=%s&City=%s&MinBirth=%d&MaxBirth=%d", + "", "", AGG_GENDER_NONE, new_who, "", 0, 0); + + g_free(new_who); + } else + srch->search_data = g_strdup_printf("Mode=3&UserId=%s", who); + + if (proxy_connect(GG_PUBDIR_HOST, GG_PUBDIR_PORT, search_callback, srch) < 0) { + g_snprintf(msg, sizeof(msg), _("Connect to search service failed (%s)"), GG_PUBDIR_HOST); + do_error_dialog(msg, _("Gadu-Gadu Error")); + g_free(srch->search_data); + g_free(srch); + return; + } +} + +static char **agg_list_icon(int uc) +{ + guint status; + if (uc == UC_UNAVAILABLE) + return (char **)gg_sunred_xpm; + status = uc >> 5; + /* Drop all masks */ + status &= ~(GG_STATUS_FRIENDS_MASK); + if (status == GG_STATUS_AVAIL) + return (char **)gg_sunyellow_xpm; + if (status == GG_STATUS_BUSY) + return (char **)gg_suncloud_xpm; + if (status == GG_STATUS_INVISIBLE) + return (char **)gg_sunwhitered_xpm; + return (char **)gg_sunyellow_xpm; +} + +static void agg_set_permit_deny_dummy(struct gaim_connection *gc) +{ + /* It's implemented on client side because GG server doesn't support this */ +} + +static void agg_permit_deny_dummy(struct gaim_connection *gc, char *who) +{ + /* It's implemented on client side because GG server doesn't support this */ +} + +static struct prpl *my_protocol = NULL; + +void agg_init(struct prpl *ret) +{ + ret->protocol = PROTO_GADUGADU; + ret->options = 0; + ret->name = agg_name; + ret->checkbox = _("Send as message"); + ret->list_icon = agg_list_icon; + ret->away_states = agg_away_states; + ret->actions = agg_actions; + ret->do_action = agg_do_action; + ret->user_opts = agg_user_opts; + ret->buddy_menu = agg_buddy_menu; + ret->chat_info = NULL; + ret->login = agg_login; + ret->close = agg_close; + ret->send_im = agg_send_im; + ret->set_info = NULL; + ret->get_info = agg_get_info; + ret->set_away = agg_set_away; + ret->set_dir = NULL; + ret->get_dir = agg_get_info; + ret->dir_search = agg_dir_search; + ret->set_idle = NULL; + ret->change_passwd = NULL; + ret->add_buddy = agg_add_buddy; + ret->add_buddies = agg_add_buddies; + ret->remove_buddy = agg_rem_buddy; + ret->add_permit = agg_permit_deny_dummy; + ret->add_deny = agg_permit_deny_dummy; + ret->rem_permit = agg_permit_deny_dummy; + ret->rem_deny = agg_permit_deny_dummy; + ret->set_permit_deny = agg_set_permit_deny_dummy; + ret->warn = NULL; + ret->join_chat = NULL; + ret->chat_invite = NULL; + ret->chat_leave = NULL; + ret->chat_whisper = NULL; + ret->chat_send = NULL; + ret->keepalive = agg_keepalive; + ret->normalize = NULL; + my_protocol = ret; +} + +#ifndef STATIC + +char *gaim_plugin_init(GModule *handle) +{ + load_protocol(agg_init, sizeof(struct prpl)); + return NULL; +} + +void gaim_plugin_remove() +{ + struct prpl *p = find_prpl(PROTO_GADUGADU); + if (p == my_protocol) + unload_protocol(p); +} + +char *name() +{ + return "Gadu-Gadu"; +} + +char *description() +{ + return PRPL_DESC("Gadu-Gadu"); +} + +#endif diff -r 9965c0bbdb7c -r a7ecfd3f7714 src/protocols/gg/libgg.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/libgg.c Sat Sep 29 23:06:30 2001 +0000 @@ -0,0 +1,1247 @@ +/* $Id: libgg.c 2406 2001-09-29 23:06:30Z warmenhoven $ */ + +/* + * (C) Copyright 2001 Wojtek Kaniewski + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "endian.h" +#include "libgg.h" + +int gg_debug_level = 0; + +#ifdef GG_DEBUG + +/* + * gg_debug_real() + * + * wyrzuca komunikat o danym poziomie, o ile użytkownik sobie tego życzy. + * + * - level - poziom wiadomości, + * - format... - treść wiadomości (printf-alike.) + * + * niczego nie zwraca. + */ +void gg_debug_real(int level, char *format, ...) +{ + va_list ap; + + if ((gg_debug_level & level)) { + va_start(ap, format); + vprintf(format, ap); + va_end(ap); + } +} + +#endif /* GG_DEBUG */ + +/* + * fix32() // funkcja wewnętrzna + * + * dla maszyn big-endianowych zamienia kolejność bajtów w ,,long''ach. + */ +static inline unsigned long fix32(unsigned long x) +{ +#if __BYTE_ORDER == __LITTLE_ENDIAN + return x; +#else + char *y = &x; + + return (y[0] << 24 + y[1] << 16 + y[2] << 8 + y[3]); +#endif +} + +/* + * fix16() // funkcja wewnętrzna + * + * dla maszyn big-endianowych zamienia kolejność bajtów w ,,short''ach. + */ +static inline unsigned short fix16(unsigned short x) +{ +#if __BYTE_ORDER == __LITTLE_ENDIAN + return x; +#else + char *y = &x; + + return (y[0] << 8 + y[1]); +#endif +} + +/* + * gg_alloc_sprintf() // funkcja wewnętrzna + * + * robi dokładnie to samo, co sprintf(), tyle że alokuje sobie wcześniej + * miejsce na dane. jak znam życie, ze względu na różnice między funkcjami + * vsnprintf() na różnych platformach, nie będzie działało ;) + * + * - format, ... - parametry takie same jak w innych funkcjach *printf() + * + * zwraca zaalokowany buforek, który wypadałoby później zwolnić, lub NULL + * jeśli nie udało się wykonać zadania. + */ +char *gg_alloc_sprintf(char *format, ...) +{ + va_list ap; + char *buf = NULL; + int size; + + va_start(ap, format); + + if ((size = vsnprintf(buf, 0, format, ap)) < 0) + return NULL; + + if (!(buf = malloc(size + 1))) + return NULL; + + vsnprintf(buf, size + 1, format, ap); + + va_end(ap); + + return buf; +} + +/* + * gg_resolve() // funkcja wewnętrzna + * + * tworzy pipe'y, forkuje się i w drugim procesie zaczyna resolvować + * podanego hosta. zapisuje w sesji deskryptor pipe'u. jeśli coś tam + * będzie gotowego, znaczy, że można wczytać ,,struct in_addr''. jeśli + * nie znajdzie, zwraca INADDR_NONE. + * + * - fd - wskaźnik gdzie wrzucić deskryptor, + * - pid - gdzie wrzucić pid dzieciaka, + * - hostname - nazwa hosta do zresolvowania. + * + * zwraca 0 jeśli udało się odpalić proces lub -1 w przypadku błędu. + */ +int gg_resolve(int *fd, int *pid, char *hostname) +{ + int pipes[2], res; + struct in_addr a; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_resolve(..., \"%s\");\n", hostname); + + if (!fd | !pid) { + errno = EFAULT; + return -1; + } + + if (pipe(pipes) == -1) + return -1; + + if ((res = fork()) == -1) + return -1; + + if (!res) { + if ((a.s_addr = inet_addr(hostname)) == INADDR_NONE) { + struct hostent *he; + + if (!(he = gethostbyname(hostname))) + a.s_addr = INADDR_NONE; + else + memcpy((char*) &a, he->h_addr, sizeof(a)); + } + + write(pipes[1], &a, sizeof(a)); + + exit(0); + } + + close(pipes[1]); + + *fd = pipes[0]; + *pid = res; + + return 0; +} + +/* + * gg_connect() // funkcja wewnętrzna + * + * łączy się z serwerem. pierwszy argument jest typu (void *), żeby nie + * musieć niczego inkludować w libgg.h i nie psuć jakiś głupich zależności + * na dziwnych systemach. + * + * - addr - adres serwera (struct in_addr *), + * - port - port serwera, + * - async - ma być asynchroniczne połączenie? + * + * zwraca połączonego socketa lub -1 w przypadku błędu. zobacz errno. + */ +int gg_connect(void *addr, int port, int async) +{ + int sock, one = 1; + struct sockaddr_in sin; + struct in_addr *a = addr; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_connect(%s, %d, %d);\n", inet_ntoa(*a), port, async); + + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + gg_debug(GG_DEBUG_MISC, "-- socket() failed. errno = %d (%s)\n", errno, strerror(errno)); + return -1; + } + + if (async) { + if (ioctl(sock, FIONBIO, &one) == -1) { + gg_debug(GG_DEBUG_MISC, "-- ioctl() failed. errno = %d (%s)\n", errno, strerror(errno)); + return -1; + } + } + + sin.sin_port = htons(port); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = a->s_addr; + + if (connect(sock, (struct sockaddr*) &sin, sizeof(sin)) == -1) { + gg_debug(GG_DEBUG_MISC, "-- connect() failed. errno = %d (%s)\n", errno, strerror(errno)); + if (errno && (!async || errno != EINPROGRESS)) + return -1; + } + + return sock; +} + +/* + * gg_read_line() // funkcja wewnętrzna + * + * czyta jedną linię tekstu z socketa. + * + * - sock - socket, + * - buf - wskaźnik bufora, + * - length - długość bufora. + * + * olewa błędy. jeśli na jakiś trafi, potraktuje go jako koniec linii. + */ +static void gg_read_line(int sock, char *buf, int length) +{ + int ret; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_read_line(...);\n"); + + for (; length > 1; buf++, length--) { + do { + if ((ret = read(sock, buf, 1)) == -1 && errno != EINTR) { + *buf = 0; + return; + } + } while (ret == -1 && errno == EINTR); + + if (*buf == '\n') { + buf++; + break; + } + } + + *buf = 0; + return; +} + +/* + * gg_recv_packet() // funkcja wewnętrzna + * + * odbiera jeden pakiet gg i zwraca wskaźnik do niego. pamięć po nim + * wypadałoby uwolnić. + * + * - sock - połączony socket. + * + * jeśli wystąpił błąd, zwraca NULL. reszta w errno. + */ +static void *gg_recv_packet(struct gg_session *sess) +{ + struct gg_header h; + char *buf = NULL; + int ret = 0, offset, size = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_recv_packet(...);\n"); + + if (!sess) { + errno = EFAULT; + return NULL; + } + + if (sess->recv_left < 1) { + while (ret != sizeof(h)) { + ret = read(sess->fd, &h, sizeof(h)); + gg_debug(GG_DEBUG_MISC, "-- header recv(..., %d) = %d\n", sizeof(h), ret); + if (ret < sizeof(h)) { + if (errno != EINTR) { + gg_debug(GG_DEBUG_MISC, "-- errno = %d (%s)\n", errno, strerror(errno)); + return NULL; + } + } + } + + h.type = fix32(h.type); + h.length = fix32(h.length); + } else { + memcpy(&h, sess->recv_buf, sizeof(h)); + } + + /* jakieś sensowne limity na rozmiar pakietu */ + if (h.length < 0 || h.length > 65535) { + gg_debug(GG_DEBUG_MISC, "-- invalid packet length (%d)\n", h.length); + errno = ERANGE; + return NULL; + } + + if (sess->recv_left > 0) { + gg_debug(GG_DEBUG_MISC, "-- resuming last gg_recv_packet()\n"); + size = sess->recv_left; + offset = sess->recv_done; + buf = sess->recv_buf; + } else { + if (!(buf = malloc(sizeof(h) + h.length + 1))) { + gg_debug(GG_DEBUG_MISC, "-- not enough memory\n"); + return NULL; + } + + memcpy(buf, &h, sizeof(h)); + + offset = 0; + size = h.length; + } + + while (size > 0) { + ret = read(sess->fd, buf + sizeof(h) + offset, size); + gg_debug(GG_DEBUG_MISC, "-- body recv(..., %d) = %d\n", size, ret); + if (ret > -1 && ret <= size) { + offset += ret; + size -= ret; + } else if (ret == -1) { + gg_debug(GG_DEBUG_MISC, "-- errno = %d (%s)\n", errno, strerror(errno)); + if (errno == EAGAIN) { + gg_debug(GG_DEBUG_MISC, "-- %d bytes received, %d left\n", offset, size); + sess->recv_buf = buf; + sess->recv_left = size; + sess->recv_done = offset; + return NULL; + } + if (errno != EINTR) { + /* errno = EINVAL; */ + free(buf); + return NULL; + } + } + } + + sess->recv_left = 0; + +#ifdef GG_DEBUG + if ((gg_debug_level & GG_DEBUG_DUMP)) { + int i; + + gg_debug(GG_DEBUG_DUMP, ">> received packet (type=%.2x):", h.type); + for (i = 0; i < sizeof(h) + h.length; i++) + gg_debug(GG_DEBUG_DUMP, " %.2x", (unsigned char) buf[i]); + gg_debug(GG_DEBUG_DUMP, "\n"); + } +#endif + + return buf; +} + +/* + * gg_send_packet() // funkcja wewnętrzna + * + * konstruuje pakiet i wysyła go w do serwera. + * + * - sock - połączony socket, + * - type - typ pakietu, + * - packet - wskaźnik do struktury pakietu, + * - length - długość struktury pakietu, + * - payload - dodatkowy tekst doklejany do pakietu (np. wiadomość), + * - payload_length - długość dodatkowego tekstu. + * + * jeśli poszło dobrze, zwraca 0. w przypadku błędu -1. jeśli errno=ENOMEM, + * zabrakło pamięci. inaczej był błąd przy wysyłaniu pakietu. dla errno=0 + * nie wysłano całego pakietu. + */ +static int gg_send_packet(int sock, int type, void *packet, int length, void *payload, int payload_length) +{ + struct gg_header *h; + int res, plen; + char *tmp; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_packet(0x%.2x, %d, %d);\n", type, length, payload_length); + + if (length < 0 || payload_length < 0) { + gg_debug(GG_DEBUG_MISC, "-- invalid packet/payload length\n"); + errno = ERANGE; + return -1; + } + + if (!(tmp = malloc(sizeof(struct gg_header) + length + payload_length))) { + gg_debug(GG_DEBUG_MISC, "-- not enough memory\n"); + return -1; + } + + h = (struct gg_header*) tmp; + h->type = fix32(type); + h->length = fix32(length + payload_length); + + if (packet) + memcpy(tmp + sizeof(struct gg_header), packet, length); + if (payload) + memcpy(tmp + sizeof(struct gg_header) + length, payload, payload_length); + +#ifdef GG_DEBUG + if ((gg_debug_level & GG_DEBUG_DUMP)) { + int i; + + gg_debug(GG_DEBUG_DUMP, "%%%% sending packet (type=%.2x):", fix32(h->type)); + for (i = 0; i < sizeof(struct gg_header) + fix32(h->length); i++) + gg_debug(GG_DEBUG_DUMP, " %.2x", (unsigned char) tmp[i]); + gg_debug(GG_DEBUG_DUMP, "\n"); + } +#endif + + plen = sizeof(struct gg_header) + length + payload_length; + + if ((res = write(sock, tmp, plen)) < plen) { + gg_debug(GG_DEBUG_MISC, "-- write() failed. res = %d, errno = %d (%s)\n", res, errno, strerror(errno)); + free(tmp); + return -1; + } + + free(tmp); + return 0; +} + + +/* + * gg_login() + * + * rozpoczyna procedurę łączenia się z serwerem. resztę obsłguje się przez + * gg_watch_event. + * + * - uin - numerek usera, + * - password - jego hasełko, + * - async - ma być asynchronicznie? + * + * UWAGA! program musi obsłużyć SIGCHLD, jeśli łączy się asynchronicznie, + * żeby zrobić pogrzeb zmarłemu procesowi resolvera. + * + * w przypadku błędu zwraca NULL, jeśli idzie dobrze (async) albo poszło + * dobrze (sync), zwróci wskaźnik do zaalokowanej struktury `gg_session'. + */ +struct gg_session *gg_login(uin_t uin, char *password, int async) +{ + struct gg_session *sess; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%u, \"...\", %d);\n", uin, async); + + if (!(sess = malloc(sizeof(*sess)))) + return NULL; + + sess->uin = uin; + if (!(sess->password = strdup(password))) { + free(sess); + return NULL; + } + sess->state = GG_STATE_RESOLVING; + sess->check = GG_CHECK_READ; + sess->async = async; + sess->seq = 0; + sess->recv_left = 0; + + if (async) { + if (gg_resolve(&sess->fd, &sess->pid, GG_APPMSG_HOST)) { + gg_debug(GG_DEBUG_MISC, "-- resolving failed\n"); + free(sess); + return NULL; + } + } else { + struct in_addr a; + + if ((a.s_addr = inet_addr(GG_APPMSG_HOST)) == INADDR_NONE) { + struct hostent *he; + + if (!(he = gethostbyname(GG_APPMSG_HOST))) { + gg_debug(GG_DEBUG_MISC, "-- host %s not found\n", GG_APPMSG_HOST); + free(sess); + return NULL; + } else + memcpy((char*) &a, he->h_addr, sizeof(a)); + } + + if (!(sess->fd = gg_connect(&a, GG_APPMSG_PORT, 0)) == -1) { + gg_debug(GG_DEBUG_MISC, "-- connection failed\n"); + free(sess); + return NULL; + } + + sess->state = GG_STATE_CONNECTING_HTTP; + + while (sess->state != GG_STATE_CONNECTED) { + struct gg_event *e; + + if (!(e = gg_watch_fd(sess))) { + gg_debug(GG_DEBUG_MISC, "-- some nasty error in gg_watch_fd()\n"); + free(sess); + return NULL; + } + + if (e->type == GG_EVENT_CONN_FAILED) { + gg_debug(GG_DEBUG_MISC, "-- could not login\n"); + gg_free_event(e); + free(sess); + return NULL; + } + + gg_free_event(e); + } + } + + return sess; +} + +/* + * gg_free_session() + * + * zwalnia pamięć zajmowaną przez opis sesji. + * + * - sess - opis sesji. + * + * nie zwraca niczego, bo i po co? + */ +void gg_free_session(struct gg_session *sess) +{ + if (!sess) + return; + + free(sess->password); + free(sess); +} + +/* + * gg_change_status() + * + * zmienia status użytkownika. przydatne do /away i /busy oraz /quit. + * + * - sess - opis sesji, + * - status - nowy status użytkownika. + * + * jeśli wysłał pakiet zwraca 0, jeśli nie udało się, zwraca -1. + */ +int gg_change_status(struct gg_session *sess, int status) +{ + struct gg_new_status p; + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status(..., %d);\n", status); + + p.status = fix32(status); + + return gg_send_packet(sess->fd, GG_NEW_STATUS, &p, sizeof(p), NULL, 0); +} + +/* + * gg_logoff() + * + * wylogowuje użytkownika i zamyka połączenie. + * + * - sock - deskryptor socketu. + * + * nie zwraca błędów. skoro się żegnamy, to olewamy wszystko. + */ +void gg_logoff(struct gg_session *sess) +{ + if (!sess) + return; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_logoff(...);\n"); + + if (sess->state == GG_STATE_CONNECTED) + gg_change_status(sess, GG_STATUS_NOT_AVAIL); + + if (sess->fd) { + shutdown(sess->fd, 2); + close(sess->fd); + } +} + +/* + * gg_send_message() + * + * wysyła wiadomość do innego użytkownika. zwraca losowy numer + * sekwencyjny, który można olać albo wykorzystać do potwierdzenia. + * + * - sess - opis sesji, + * - msgclass - rodzaj wiadomości, + * - recipient - numer adresata, + * - message - treść wiadomości. + * + * w przypadku błędu zwraca -1, inaczej numer sekwencyjny. + */ +int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, unsigned char *message) +{ + struct gg_send_msg s; + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message(..., %d, %u, \"...\");\n", msgclass, recipient); + + s.recipient = fix32(recipient); + if (!sess->seq) + sess->seq = 0x01740000 | (rand() & 0xffff); + s.seq = fix32(sess->seq); + s.msgclass = fix32(msgclass); + sess->seq += (rand() % 0x300) + 0x300; + + if (gg_send_packet(sess->fd, GG_SEND_MSG, &s, sizeof(s), message, strlen(message) + 1) == -1) + return -1; + + return fix32(s.seq); +} + +/* + * gg_ping() + * + * wysyła do serwera pakiet typu yeah-i'm-still-alive. + * + * - sess - zgadnij. + * + * jeśli nie powiodło się wysłanie pakietu, zwraca -1. otherwise 0. + */ +int gg_ping(struct gg_session *sess) +{ + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + gg_debug(GG_DEBUG_FUNCTION, "** gg_ping(...);\n"); + + return gg_send_packet(sess->fd, GG_PING, NULL, 0, NULL, 0); +} + +/* + * gg_free_event() + * + * zwalnia pamięć zajmowaną przez informację o zdarzeniu + * + * - event - wskaźnik do informacji o zdarzeniu + * + * nie ma czego zwracać. + */ +void gg_free_event(struct gg_event *e) +{ + if (!e) + return; + if (e->type == GG_EVENT_MSG) + free(e->event.msg.message); + if (e->type == GG_EVENT_NOTIFY) + free(e->event.notify); + free(e); +} + +/* + * gg_notify() + * + * wysyła serwerowi listę ludków, za którymi tęsknimy. + * + * - sess - identyfikator sesji, + * - userlist - wskaźnik do tablicy numerów, + * - count - ilość numerków. + * + * jeśli udało się, zwraca 0. jeśli błąd, dostajemy -1. + */ +int gg_notify(struct gg_session *sess, uin_t *userlist, int count) +{ + struct gg_notify *n; + uin_t *u; + int i, res = 0; + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + gg_debug(GG_DEBUG_FUNCTION, "** gg_notify(..., %d);\n", count); + + if (!userlist || !count) + return 0; + + if (!(n = (struct gg_notify*) malloc(sizeof(*n) * count))) + return -1; + + for (u = userlist, i = 0; i < count; u++, i++) { + n[i].uin = fix32(*u); + n[i].dunno1 = 3; + } + + if (gg_send_packet(sess->fd, GG_NOTIFY, n, sizeof(*n) * count, NULL, 0) == -1) + res = -1; + + free(n); + + return res; +} + +/* + * gg_add_notify() + * + * dodaje w locie do listy ukochanych dany numerek. + * + * - sess - identyfikator sesji, + * - uin - numerek ukochanej. + * + * jeśli udało się wysłać, daje 0. inaczej -1. + */ +int gg_add_notify(struct gg_session *sess, uin_t uin) +{ + struct gg_add_remove a; + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + gg_debug(GG_DEBUG_FUNCTION, "** gg_add_notify(..., %u);\n", uin); + + a.uin = fix32(uin); + a.dunno1 = 3; + + return gg_send_packet(sess->fd, GG_ADD_NOTIFY, &a, sizeof(a), NULL, 0); +} + +/* + * gg_remove_notify() + * + * w locie usuwa z listy zainteresowanych. + * + * - sess - id sesji, + * - uin - numerek. + * + * zwraca -1 jeśli był błąd, 0 jeśli się udało wysłać pakiet. + */ +int gg_remove_notify(struct gg_session *sess, uin_t uin) +{ + struct gg_add_remove a; + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + gg_debug(GG_DEBUG_FUNCTION, "** gg_remove_notify(..., %u);\n", uin); + + a.uin = fix32(uin); + a.dunno1 = 3; + + return gg_send_packet(sess->fd, GG_REMOVE_NOTIFY, &a, sizeof(a), NULL, 0); +} + +/* + * gg_watch_fd_connected() // funkcja wewnętrzna + * + * patrzy na socketa, odbiera pakiet i wypełnia strukturę zdarzenia. + * + * - sock - lalala, trudno zgadnąć. + * + * jeśli błąd -1, jeśli dobrze 0. + */ +static int gg_watch_fd_connected(struct gg_session *sess, struct gg_event *e) +{ + struct gg_header *h; + void *p; + + if (!sess) { + errno = EFAULT; + return -1; + } + + gg_debug(GG_DEBUG_FUNCTION, "** gg_watch_fd_connected(...);\n"); + + if (!(h = gg_recv_packet(sess))) { + gg_debug(GG_DEBUG_MISC, "-- gg_recv_packet failed. errno = %d (%d)\n", errno, strerror(errno)); + return -1; + } + + p = (void*) h + sizeof(struct gg_header); + + if (h->type == GG_RECV_MSG) { + struct gg_recv_msg *r = p; + + gg_debug(GG_DEBUG_MISC, "-- received a message\n"); + + if (h->length >= sizeof(*r)) { + e->type = GG_EVENT_MSG; + e->event.msg.msgclass = fix32(r->msgclass); + e->event.msg.sender = fix32(r->sender); + e->event.msg.message = strdup((char*) r + sizeof(*r)); + } + } + + if (h->type == GG_NOTIFY_REPLY) { + struct gg_notify_reply *n = p; + + gg_debug(GG_DEBUG_MISC, "-- received a notify reply\n"); + + e->type = GG_EVENT_NOTIFY; + if (!(e->event.notify = (void*) malloc(h->length + 2 * sizeof(*n)))) { + gg_debug(GG_DEBUG_MISC, "-- not enough memory\n"); + free(h); + return -1; + } + memcpy(e->event.notify, p, h->length); + e->event.notify[h->length / sizeof(*n)].uin = 0; + } + + if (h->type == GG_STATUS) { + struct gg_status *s = p; + + gg_debug(GG_DEBUG_MISC, "-- received a status change\n"); + + if (h->length >= sizeof(*s)) { + e->type = GG_EVENT_STATUS; + memcpy(&e->event.status, p, h->length); + } + } + + if (h->type == GG_SEND_MSG_ACK) { + struct gg_send_msg_ack *s = p; + + gg_debug(GG_DEBUG_MISC, "-- received a message ack\n"); + + if (h->length >= sizeof(*s)) { + e->type = GG_EVENT_ACK; + e->event.ack.status = fix32(s->status); + e->event.ack.recipient = fix32(s->recipient); + e->event.ack.seq = fix32(s->seq); + } + } + + free(h); + + return 0; +} + +/* + * gg_chomp() // funkcja wewnętrzna + * + * ucina "\r\n" lub "\n" z końca linii. + * + * - line - ofiara operacji plastycznej. + * + * niczego nie zwraca. + */ +static void gg_chomp(char *line) +{ + if (!line || strlen(line) < 1) + return; + + if (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = 0; + if (line[strlen(line) - 1] == '\r') + line[strlen(line) - 1] = 0; +} + +/* + * gg_watch_fd() + * + * funkcja wywoływana, gdy coś się stanie na obserwowanym deskryptorze. + * zwraca klientowi informację o tym, co się dzieje. + * + * - sess - identyfikator sesji. + * + * zwraca wskaźnik do struktury gg_event, którą trzeba zwolnić później + * za pomocą gg_free_event(). jesli rodzaj zdarzenia jest równy + * GG_EVENT_NONE, należy je olać kompletnie. jeśli zwróciło NULL, + * stało się coś niedobrego -- albo brakło pamięci albo zerwało + * połączenie albo coś takiego. + */ +struct gg_event *gg_watch_fd(struct gg_session *sess) +{ + struct gg_event *e; + int res = 0; + + if (!sess) { + errno = EFAULT; + return NULL; + } + + gg_debug(GG_DEBUG_FUNCTION, "** gg_watch_fd(...);\n"); + + if (!(e = (void*) malloc(sizeof(*e)))) { + gg_debug(GG_DEBUG_MISC, "-- not enough memory\n"); + return NULL; + } + + e->type = GG_EVENT_NONE; + + switch (sess->state) { + case GG_STATE_RESOLVING: + { + struct in_addr a; + + gg_debug(GG_DEBUG_MISC, "== GG_STATE_RESOLVING\n"); + + if (read(sess->fd, &a, sizeof(a)) < sizeof(a) || a.s_addr == INADDR_NONE) { + gg_debug(GG_DEBUG_MISC, "-- resolving failed\n"); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_RESOLVING; + sess->state = GG_STATE_IDLE; + + close(sess->fd); + + break; + } + + close(sess->fd); + + waitpid(sess->pid, NULL, 0); + + gg_debug(GG_DEBUG_MISC, "-- resolved, now connecting\n"); + + if ((sess->fd = gg_connect(&a, GG_APPMSG_PORT, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "-- connection failed\n"); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_CONNECTING; + sess->state = GG_STATE_IDLE; + break; + } + + sess->state = GG_STATE_CONNECTING_HTTP; + sess->check = GG_CHECK_WRITE; + + break; + } + + case GG_STATE_CONNECTING_HTTP: + { + char buf[1024]; + int res, res_size = sizeof(res); + + gg_debug(GG_DEBUG_MISC, "== GG_STATE_CONNECTING_HTTP\n"); + + if (sess->async && (getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + gg_debug(GG_DEBUG_MISC, "-- http connection failed, errno = %d (%s)\n", res, strerror(res)); + + errno = res; + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_CONNECTING; + sess->state = GG_STATE_IDLE; + break; + } + + gg_debug(GG_DEBUG_MISC, "-- http connection succeded, sending query\n"); + + + snprintf(buf, sizeof(buf) - 1, + "GET /appsvc/appmsg.asp?fmnumber=%u HTTP/1.0\r\n" + "Host: " GG_APPMSG_HOST "\r\n" + "User-Agent: Mozilla/4.7 [en] (Win98; I)\r\n" + "Pragma: no-cache\r\n" + "\r\n", sess->uin); + + if (write(sess->fd, buf, strlen(buf)) < strlen(buf)) { + gg_debug(GG_DEBUG_MISC, "-- sending query failed\n"); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_WRITING; + sess->state = GG_STATE_IDLE; + break; + } + + sess->state = GG_STATE_WRITING_HTTP; + sess->check = GG_CHECK_READ; + + break; + } + + case GG_STATE_WRITING_HTTP: + { + char buf[1024], *tmp, *host; + int port = GG_DEFAULT_PORT; + struct in_addr a; + + gg_debug(GG_DEBUG_MISC, "== GG_STATE_WRITING_HTTP\n"); + + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + + gg_debug(GG_DEBUG_TRAFFIC, "-- got http response (%s)\n", buf); + + if (strncmp(buf, "HTTP/1.", 7) || strncmp(buf + 9, "200", 3)) { + gg_debug(GG_DEBUG_MISC, "-- but that's not what we've expected\n"); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_INVALID; + sess->state = GG_STATE_IDLE; + break; + } + + while (strcmp(buf, "\r\n") && strcmp(buf, "")) + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + + close(sess->fd); + + gg_debug(GG_DEBUG_TRAFFIC, "-- received http data (%s)\n", buf); + + tmp = buf; + while (*tmp && *tmp != ' ') + tmp++; + while (*tmp && *tmp == ' ') + tmp++; + while (*tmp && *tmp != ' ') + tmp++; + while (*tmp && *tmp == ' ') + tmp++; + while (*tmp && *tmp != ' ') + tmp++; + while (*tmp && *tmp == ' ') + tmp++; + host = tmp; + while (*tmp && *tmp != ' ') + tmp++; + *tmp = 0; + + if ((tmp = strchr(host, ':'))) { + *tmp = 0; + port = atoi(tmp+1); + } + + a.s_addr = inet_addr(host); + + if ((sess->fd = gg_connect(&a, port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "-- connect() failed. errno = %d (%s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_CONNECTING; + sess->state = GG_STATE_IDLE; + break; + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + + break; + } + + case GG_STATE_CONNECTING_GG: + { + int res, res_size = sizeof(res); + + gg_debug(GG_DEBUG_MISC, "== GG_STATE_CONNECTING_GG\n"); + + if (sess->async && (getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + gg_debug(GG_DEBUG_MISC, "-- connection failed, errno = %d (%s)\n", errno, strerror(errno)); + + errno = res; + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_CONNECTING; + sess->state = GG_STATE_IDLE; + break; + } + + gg_debug(GG_DEBUG_MISC, "-- connected\n"); + + sess->state = GG_STATE_WAITING_FOR_KEY; + sess->check = GG_CHECK_READ; + + break; + } + + case GG_STATE_WAITING_FOR_KEY: + { + struct gg_header *h; + struct gg_welcome *w; + struct gg_login l; + unsigned int hash; + char *password = sess->password; + + gg_debug(GG_DEBUG_MISC, "== GG_STATE_WAITING_FOR_KEY\n"); + + if (!(h = gg_recv_packet(sess))) { + gg_debug(GG_DEBUG_MISC, "-- gg_recv_packet() failed. errno = %d (%s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_READING; + sess->state = GG_STATE_IDLE; + close(sess->fd); + break; + } + + if (h->type != GG_WELCOME) { + gg_debug(GG_DEBUG_MISC, "-- invalid packet received\n"); + + free(h); + close(sess->fd); + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_INVALID; + sess->state = GG_STATE_IDLE; + break; + } + + w = (void*) h + sizeof(struct gg_header); + w->key = fix32(w->key); + + for (hash = 1; *password; password++) + hash *= (*password) + 1; + hash *= w->key; + + gg_debug(GG_DEBUG_DUMP, "%%%% klucz serwera %.4x, hash hasła %.8x\n", w->key, hash); + + free(h); + + free(sess->password); + sess->password = NULL; + + l.uin = fix32(sess->uin); + l.hash = fix32(hash); + l.status = fix32(GG_STATUS_AVAIL); + l.dunno = fix32(0x0b); + l.local_ip = 0; + l.local_port = 0; + + gg_debug(GG_DEBUG_TRAFFIC, "-- sending GG_LOGIN packet\n"); + + if (gg_send_packet(sess->fd, GG_LOGIN, &l, sizeof(l), NULL, 0) == -1) { + gg_debug(GG_DEBUG_TRAFFIC, "-- oops, failed. errno = %d (%s)\n", errno, strerror(errno)); + + close(sess->fd); + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_WRITING; + sess->state = GG_STATE_IDLE; + break; + } + + sess->state = GG_STATE_SENDING_KEY; + + break; + } + + case GG_STATE_SENDING_KEY: + { + struct gg_header *h; + + gg_debug(GG_DEBUG_MISC, "== GG_STATE_SENDING_KEY\n"); + + if (!(h = gg_recv_packet(sess))) { + gg_debug(GG_DEBUG_MISC, "-- recv_packet failed\n"); + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_READING; + sess->state = GG_STATE_IDLE; + close(sess->fd); + break; + } + + if (h->type == GG_LOGIN_OK) { + gg_debug(GG_DEBUG_MISC, "-- login succeded\n"); + e->type = GG_EVENT_CONN_SUCCESS; + sess->state = GG_STATE_CONNECTED; + break; + } + + if (h->type == GG_LOGIN_FAILED) { + gg_debug(GG_DEBUG_MISC, "-- login failed\n"); + e->event.failure = GG_FAILURE_PASSWORD; + errno = EACCES; + } else { + gg_debug(GG_DEBUG_MISC, "-- invalid packet\n"); + e->event.failure = GG_FAILURE_INVALID; + } + + e->type = GG_EVENT_CONN_FAILED; + sess->state = GG_STATE_IDLE; + close(sess->fd); + + break; + } + + case GG_STATE_CONNECTED: + { + gg_debug(GG_DEBUG_MISC, "== GG_STATE_CONNECTED\n"); + + if ((res = gg_watch_fd_connected(sess, e)) == -1) { + + gg_debug(GG_DEBUG_MISC, "-- watch_fd_connected failed. errno = %d (%s)\n", errno, strerror(errno)); + + if (errno == EAGAIN) { + e->type = GG_EVENT_NONE; + res = 0; + } else + res = -1; + } + break; + } + } + + if (res == -1) { + free(e); + e = NULL; + } + + return e; +} + + diff -r 9965c0bbdb7c -r a7ecfd3f7714 src/protocols/gg/libgg.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/libgg.h Sat Sep 29 23:06:30 2001 +0000 @@ -0,0 +1,339 @@ +/* $Id: libgg.h 2406 2001-09-29 23:06:30Z warmenhoven $ */ + +/* + * (C) Copyright 2001 Wojtek Kaniewski + * + * 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 __LIBGG_H +#define __LIBGG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define GG_DEBUG 1 + +/* + * typ zmiennej określającej numerek danej osoby. + */ +typedef unsigned int uin_t; + +/* + * cośtam. + */ +struct gg_session { + int state, check; + int fd, pid; + int port; + int seq, async; + + uin_t uin; + char *password; + + char *recv_buf; + int recv_done, recv_left; +}; + +/* + * różne stany asynchronicznej maszynki. + */ +enum { + GG_STATE_IDLE = 0, /* wspólne */ + GG_STATE_RESOLVING, + GG_STATE_CONNECTING_HTTP, + + GG_STATE_WRITING_HTTP, /* gg_login */ + GG_STATE_CONNECTING_GG, + GG_STATE_WAITING_FOR_KEY, + GG_STATE_SENDING_KEY, + GG_STATE_CONNECTED, + + GG_STATE_READING_HEADER, /* gg_search */ + GG_STATE_READING_DATA, + GG_STATE_PARSING, + GG_STATE_FINISHED, +}; + +/* + * co proces klienta powinien sprawdzać w deskryptorach? + */ +enum { + GG_CHECK_NONE = 0, + GG_CHECK_WRITE = 1, + GG_CHECK_READ = 2, +}; + +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_change_status(struct gg_session *sess, int status); +int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, unsigned char *message); +int gg_ping(struct gg_session *sess); + +struct gg_notify_reply { + uin_t uin; /* numerek */ + int status; /* status danej osoby */ + int remote_ip; /* adres ip delikwenta */ + short remote_port; /* port, na którym słucha klient */ + int dunno1; /* == 0x0b */ + short dunno2; /* znowu port? */ +} __attribute__ ((packed)); + +struct gg_status { + uin_t uin; /* numerek */ + int status; /* nowy stan */ +} __attribute__ ((packed)); + +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, +}; + +enum { + GG_FAILURE_RESOLVING = 1, + GG_FAILURE_CONNECTING, + GG_FAILURE_INVALID, + GG_FAILURE_READING, + GG_FAILURE_WRITING, + GG_FAILURE_PASSWORD, +}; + +struct gg_event { + int type; + union { + struct { + uin_t sender; + int msgclass; + unsigned char *message; + } msg; + struct gg_notify_reply *notify; + struct gg_status status; + 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); + +/* + * jakieśtam bzdurki dotyczące szukania userów. + */ + +struct gg_search_result { + uin_t uin; + char *first_name; + char *last_name; + char *nickname; + int born; + int gender; + char *city; + int active; +}; + +struct gg_search_request { + /* czy ma szukać tylko aktywnych? */ + int active; + /* mode 0 */ + char *nickname, *first_name, *last_name, *city; + int gender, min_birth, max_birth; + /* mode 1 */ + char *email; + /* mode 2 */ + char *phone; + /* mode 3 */ + uin_t uin; +}; + +struct gg_search { + struct gg_search_request request; + + /* bzdurki */ + int mode, fd, async, state, check, error, pid; + char *header_buf, *data_buf; + int header_size, data_size; + + /* wyniki */ + int count; + struct gg_search_result *results; +}; + +#define GG_GENDER_NONE 0 +#define GG_GENDER_FEMALE 1 +#define GG_GENDER_MALE 2 + +struct gg_search *gg_search(struct gg_search_request *r, int async); +int gg_search_watch_fd(struct gg_search *f); +void gg_free_search(struct gg_search *f); +void gg_search_cancel(struct gg_search *f); + +/* + * jeśli chcemy sobie podebugować, wystarczy zdefiniować GG_DEBUG. + */ + +int gg_debug_level; + +#ifdef GG_DEBUG + +# 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_real(int level, char *format, ...); +# define gg_debug(x, y...) gg_debug_real(x, y) + +#else + +# define gg_debug(x, y...) while(0) { }; + +#endif /* GG_DEBUG */ + +/* + * ------------------------------------------------------------------------- + * 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); +int gg_connect(void *addr, int port, int async); +char *gg_alloc_sprintf(char *format, ...); + +#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_DEFAULT_PORT 8074 + +struct gg_header { + int type; /* typ pakietu */ + int length; /* długość reszty pakietu */ +} __attribute__ ((packed)); + +#define GG_WELCOME 0x0001 + +struct gg_welcome { + int key; /* klucz szyfrowania hasła */ +} __attribute__ ((packed)); + +#define GG_LOGIN 0x000c + +struct gg_login { + uin_t uin; /* twój numerek */ + int hash; /* hash hasła */ + int status; /* status na dzień dobry */ + int dunno; /* == 0x0b */ + int local_ip; /* mój adres ip */ + short local_port; /* port, na którym słucham */ +} __attribute__ ((packed)); + +#define GG_LOGIN_OK 0x0003 + +#define GG_LOGIN_FAILED 0x0009 + +#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 (GG 4.6) */ + +#define GG_STATUS_FRIENDS_MASK 0x8000 /* tylko dla znajomych (GG 4.6) */ + +struct gg_new_status { + int status; /* na jaki zmienić? */ +} __attribute__ ((packed)); + +#define GG_NOTIFY 0x0010 + +struct gg_notify { + uin_t uin; /* numerek danej osoby */ + char dunno1; /* == 3 */ +} __attribute__ ((packed)); + +#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 */ +} __attribute__ ((packed)); + +#define GG_STATUS 0x0002 + +/* struct gg_status zadeklarowane wcześniej */ + +#define GG_SEND_MSG 0x000b + +#define GG_CLASS_MSG 0x0004 +#define GG_CLASS_CHAT 0x0008 + +struct gg_send_msg { + int recipient; + int seq; + int msgclass; +} __attribute__ ((packed)); + +#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; +} __attribute__ ((packed)); + +#define GG_RECV_MSG 0x000a + +struct gg_recv_msg { + int sender; + int dunno1; + int dunno2; + int msgclass; +} __attribute__ ((packed)); + +#define GG_PING 0x0008 + +#define GG_PONG 0x0007 + +#ifdef __cplusplus +} +#endif + +#endif diff -r 9965c0bbdb7c -r a7ecfd3f7714 src/protocols/gg/protocol.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/gg/protocol.txt Sat Sep 29 23:06:30 2001 +0000 @@ -0,0 +1,248 @@ +--------------------------------------------------------------------------- + + protokół g*du-g*du 4.x + (c) copyright 2001 by wojtek kaniewski + +--- 0) disclaimer --------------------------------------------------------- + +wszystkie informacje bazują na doświadczeniach przeprowadzonych na moim +domowym komputerze. ż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 + +na co powinniśmy dostać odpowiedź w stylu: + + 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). + +--- 3) logowanie się ------------------------------------------------------- + +po połączeniu się portem 8074 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 dunno1; /* == 0x0b */ + 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? 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 dunno1; /* == 0x0b */ + short dunno2; /* 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 jest 2. nieważne ;) 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_MSG 0x0004 + #define GG_CLASS_CHAT 0x0008 + + 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ż 0x0f. (czasem +klienty wysyłają 0x04, czasem 0x24) + +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. nieznane pola to +coś a'la numer sekwencyjny albo identyfikator okienka z rozmową albo nowe +dane dla setiathome. klasa wiadomości taka sama jak przy wysyłaniu: + + #define GG_RECV_MSG 0x000a + + struct gg_recv_msg { + int sender; + int dunno1; + int dunno2; + int class; + char message[]; + }; + +--- 8) otrzymywanie wiadomości --------------------------------------------- + +od czasu do czasu klient wysyła pakiet a'la ping do serwera i dostaje pustą +odpowiedź. ciężko stwierdzić, czy serwer wywala, jeśli nie dostanie pinga +przez jakiś czas, czy klient się rozłącza, jeśli serwer mu nie odpowie. +jakoś nie chce mi się sprawdzać ;) + + #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 miał Robert Woźny, który opisał nowości +w GG 4.6. + +---------------------------------------------------------------------------- + +$Id: protocol.txt 2406 2001-09-29 23:06:30Z warmenhoven $ + diff -r 9965c0bbdb7c -r a7ecfd3f7714 src/protocols/irc/irc.c --- a/src/protocols/irc/irc.c Sat Sep 29 02:08:00 2001 +0000 +++ b/src/protocols/irc/irc.c Sat Sep 29 23:06:30 2001 +0000 @@ -1319,9 +1319,14 @@ struct irc_data *idata = gc->proto_data; char buf[IRC_BUF_LEN]; - if (msg) + if (gc->away) + g_free(gc->away); + gc->away = NULL; + + if (msg) { g_snprintf(buf, sizeof(buf), "AWAY :%s\r\n", msg); - else + gc->away = g_strdup(msg); + } else g_snprintf(buf, sizeof(buf), "AWAY\r\n"); irc_write(idata->fd, buf, strlen(buf)); }