diff options
Diffstat (limited to 'libpurple/protocols/bonjour')
-rw-r--r-- | libpurple/protocols/bonjour/Makefile.am | 42 | ||||
-rw-r--r-- | libpurple/protocols/bonjour/Makefile.mingw | 86 | ||||
-rw-r--r-- | libpurple/protocols/bonjour/bonjour.c | 600 | ||||
-rw-r--r-- | libpurple/protocols/bonjour/bonjour.h | 51 | ||||
-rw-r--r-- | libpurple/protocols/bonjour/buddy.c | 167 | ||||
-rw-r--r-- | libpurple/protocols/bonjour/buddy.h | 66 | ||||
-rw-r--r-- | libpurple/protocols/bonjour/dns_sd.c | 387 | ||||
-rw-r--r-- | libpurple/protocols/bonjour/dns_sd.h | 81 | ||||
-rw-r--r-- | libpurple/protocols/bonjour/issues.txt | 18 | ||||
-rw-r--r-- | libpurple/protocols/bonjour/jabber.c | 667 | ||||
-rw-r--r-- | libpurple/protocols/bonjour/jabber.h | 65 | ||||
-rw-r--r-- | libpurple/protocols/bonjour/messages.txt | 4 |
12 files changed, 2234 insertions, 0 deletions
diff --git a/libpurple/protocols/bonjour/Makefile.am b/libpurple/protocols/bonjour/Makefile.am new file mode 100644 index 0000000000..2f8a047af7 --- /dev/null +++ b/libpurple/protocols/bonjour/Makefile.am @@ -0,0 +1,42 @@ +EXTRA_DIST = \ + Makefile.mingw + +pkgdir = $(libdir)/gaim + +BONJOURSOURCES = \ + bonjour.c \ + bonjour.h \ + buddy.c \ + buddy.h \ + dns_sd.c \ + dns_sd.h \ + jabber.c \ + jabber.h + +AM_CFLAGS = $(st) + +libbonjour_la_LDFLAGS = -module -avoid-version + +if STATIC_BONJOUR + +st = -DGAIM_STATIC_PRPL +noinst_LIBRARIES = libbonjour.a +libbonjour_a_SOURCES = $(BONJOURSOURCES) +libbonjour_a_CFLAGS = $(AM_CFLAGS) +libbonjour_a_LIBADD = $(HOWL_LIBS) + +else + +st = +pkg_LTLIBRARIES = libbonjour.la +libbonjour_la_SOURCES = $(BONJOURSOURCES) +libbonjour_la_LIBADD = $(GLIB_LIBS) $(HOWL_LIBS) + +endif + + +AM_CPPFLAGS = \ + -I$(top_srcdir)/libpurple \ + $(GLIB_CFLAGS) \ + $(DEBUG_CFLAGS) \ + $(HOWL_CFLAGS) diff --git a/libpurple/protocols/bonjour/Makefile.mingw b/libpurple/protocols/bonjour/Makefile.mingw new file mode 100644 index 0000000000..26ff66442f --- /dev/null +++ b/libpurple/protocols/bonjour/Makefile.mingw @@ -0,0 +1,86 @@ +# +# Makefile.mingw +# +# Description: Makefile for win32 (mingw) version of libbonjour +# + +GAIM_TOP := ../../.. +include $(GAIM_TOP)/libgaim/win32/global.mak + +TARGET = libbonjour +NEEDED_DLLS = $(HOWL_TOP)/bin/libhowl-1.dll +TYPE = PLUGIN + +# Static or Plugin... +ifeq ($(TYPE),STATIC) + DEFINES += -DSTATIC + DLL_INSTALL_DIR = $(GAIM_INSTALL_DIR) +else +ifeq ($(TYPE),PLUGIN) + DLL_INSTALL_DIR = $(GAIM_INSTALL_PLUGINS_DIR) +endif +endif + +## +## INCLUDE PATHS +## +INCLUDE_PATHS += -I$(BONJOUR_ROOT) \ + -I$(GTK_TOP)/include \ + -I$(GTK_TOP)/include/glib-2.0 \ + -I$(GTK_TOP)/lib/glib-2.0/include \ + -I$(HOWL_TOP)/include \ + -I$(GAIM_LIB_TOP) \ + -I$(GAIM_LIB_TOP)/win32 \ + -I$(GAIM_TOP) + +LIB_PATHS = -L$(GTK_TOP)/lib \ + -L$(HOWL_TOP)/lib \ + -L$(GAIM_LIB_TOP) + +## +## SOURCES, OBJECTS +## +C_SRC = bonjour.c \ + buddy.c \ + dns_sd.c \ + jabber.c + +OBJECTS = $(C_SRC:%.c=%.o) + +## +## LIBRARIES +## +LIBS = \ + -lglib-2.0 \ + -lws2_32 \ + -lintl \ + -lhowl \ + -lgaim + +include $(GAIM_COMMON_RULES) + +## +## TARGET DEFINITIONS +## +.PHONY: all install clean + +all: $(TARGET).dll + +install: all $(DLL_INSTALL_DIR) + cp $(TARGET).dll $(DLL_INSTALL_DIR) + cp $(NEEDED_DLLS) $(GAIM_INSTALL_DIR) + +$(OBJECTS): $(GAIM_CONFIG_H) + +$(TARGET).dll: $(GAIM_LIBGAIM_DLL).a $(OBJECTS) + $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll + +## +## CLEAN RULES +## + +clean: + rm -f $(OBJECTS) + rm -f $(TARGET).dll + +include $(GAIM_COMMON_TARGETS) diff --git a/libpurple/protocols/bonjour/bonjour.c b/libpurple/protocols/bonjour/bonjour.c new file mode 100644 index 0000000000..fa5c69a895 --- /dev/null +++ b/libpurple/protocols/bonjour/bonjour.c @@ -0,0 +1,600 @@ +/* + * gaim - Bonjour Protocol Plugin + * + * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * 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 <glib.h> +#ifndef _WIN32 +#include <pwd.h> +#else +#define UNICODE +#include <windows.h> +#include <lm.h> +#endif + +#include "internal.h" + +#include "account.h" +#include "accountopt.h" +#include "debug.h" +#include "util.h" +#include "version.h" + +#include "bonjour.h" +#include "dns_sd.h" +#include "jabber.h" +#include "buddy.h" + +/* + * TODO: Should implement an add_buddy callback that removes the buddy + * from the local list. Bonjour manages buddies for you, and + * adding someone locally by hand is stupid. Or, maybe even better, + * if a PRPL does not have an add_buddy callback then do not allow + * users to add buddies. + */ + +static char *default_firstname; +static char *default_lastname; +static char *default_hostname; + +static void +bonjour_removeallfromlocal(GaimConnection *gc) +{ + GaimAccount *account = gaim_connection_get_account(gc); + GaimBuddyList *blist; + GaimBlistNode *gnode, *cnode, *bnode; + GaimBuddy *buddy; + + blist = gaim_get_blist(); + if (blist == NULL) + return; + + /* Go through and remove all buddies that belong to this account */ + for (gnode = blist->root; gnode; gnode = gnode->next) + { + if (!GAIM_BLIST_NODE_IS_GROUP(gnode)) + continue; + for (cnode = gnode->child; cnode; cnode = cnode->next) + { + if (!GAIM_BLIST_NODE_IS_CONTACT(cnode)) + continue; + for (bnode = cnode->child; bnode; bnode = bnode->next) + { + if (!GAIM_BLIST_NODE_IS_BUDDY(bnode)) + continue; + buddy = (GaimBuddy *)bnode; + if (buddy->account != account) + continue; + gaim_prpl_got_user_status(account, buddy->name, "offline", NULL); + gaim_blist_remove_buddy(buddy); + } + } + } +} + +static void +bonjour_login(GaimAccount *account) +{ + GaimConnection *gc = gaim_account_get_connection(account); + GaimGroup *bonjour_group = NULL; + BonjourData *bd = NULL; + GaimStatus *status; + GaimPresence *presence; + + gc->flags |= GAIM_CONNECTION_HTML; + gc->proto_data = g_new0(BonjourData, 1); + bd = gc->proto_data; + + /* Start waiting for jabber connections (iChat style) */ + bd->jabber_data = g_new(BonjourJabber, 1); + bd->jabber_data->port = BONJOUR_DEFAULT_PORT_INT; + bd->jabber_data->account = account; + + if (bonjour_jabber_start(bd->jabber_data) == -1) { + /* Send a message about the connection error */ + gaim_connection_error(gc, _("Unable to listen for incoming IM connections\n")); + + /* Free the data */ + g_free(bd->jabber_data); + bd->jabber_data = NULL; + return; + } + + /* Connect to the mDNS daemon looking for buddies in the LAN */ + bd->dns_sd_data = bonjour_dns_sd_new(); + bd->dns_sd_data->name = (sw_string)gaim_account_get_username(account); + bd->dns_sd_data->txtvers = g_strdup("1"); + bd->dns_sd_data->version = g_strdup("1"); + bd->dns_sd_data->first = g_strdup(gaim_account_get_string(account, "first", default_firstname)); + bd->dns_sd_data->last = g_strdup(gaim_account_get_string(account, "last", default_lastname)); + bd->dns_sd_data->port_p2pj = bd->jabber_data->port; + bd->dns_sd_data->phsh = g_strdup(""); + bd->dns_sd_data->email = g_strdup(gaim_account_get_string(account, "email", "")); + bd->dns_sd_data->vc = g_strdup(""); + bd->dns_sd_data->jid = g_strdup(gaim_account_get_string(account, "jid", "")); + bd->dns_sd_data->AIM = g_strdup(gaim_account_get_string(account, "AIM", "")); + + status = gaim_account_get_active_status(account); + presence = gaim_account_get_presence(account); + if (gaim_presence_is_available(presence)) + bd->dns_sd_data->status = g_strdup("avail"); + else if (gaim_presence_is_idle(presence)) + bd->dns_sd_data->status = g_strdup("away"); + else + bd->dns_sd_data->status = g_strdup("dnd"); + bd->dns_sd_data->msg = g_strdup(gaim_status_get_attr_string(status, "message")); + + bd->dns_sd_data->account = account; + if (!bonjour_dns_sd_start(bd->dns_sd_data)) + { + gaim_connection_error(gc, _("Unable to establish connection with the local mDNS server. Is it running?")); + return; + } + + /* Create a group for bonjour buddies */ + bonjour_group = gaim_group_new(BONJOUR_GROUP_NAME); + gaim_blist_add_group(bonjour_group, NULL); + + /* Show the buddy list by telling Gaim we have already connected */ + gaim_connection_set_state(gc, GAIM_CONNECTED); +} + +static void +bonjour_close(GaimConnection *connection) +{ + GaimGroup *bonjour_group; + BonjourData *bd = (BonjourData*)connection->proto_data; + + /* Stop looking for buddies in the LAN */ + if (bd->dns_sd_data != NULL) + { + bonjour_dns_sd_stop(bd->dns_sd_data); + bonjour_dns_sd_free(bd->dns_sd_data); + } + + if (bd->jabber_data != NULL) + { + /* Stop waiting for conversations */ + bonjour_jabber_stop(bd->jabber_data); + g_free(bd->jabber_data); + } + + /* Remove all the bonjour buddies */ + bonjour_removeallfromlocal(connection); + + /* Delete the bonjour group */ + bonjour_group = gaim_find_group(BONJOUR_GROUP_NAME); + if (bonjour_group != NULL) + gaim_blist_remove_group(bonjour_group); + +} + +static const char * +bonjour_list_icon(GaimAccount *account, GaimBuddy *buddy) +{ + return BONJOUR_ICON_NAME; +} + +static int +bonjour_send_im(GaimConnection *connection, const char *to, const char *msg, GaimMessageFlags flags) +{ + if(!to || !msg) + return 0; + + return bonjour_jabber_send_message(((BonjourData*)(connection->proto_data))->jabber_data, to, msg); +} + +static void +bonjour_set_status(GaimAccount *account, GaimStatus *status) +{ + GaimConnection *gc; + BonjourData *bd; + gboolean disconnected; + GaimStatusType *type; + int primitive; + GaimPresence *presence; + const char *message, *bonjour_status; + gchar *stripped; + + gc = gaim_account_get_connection(account); + bd = gc->proto_data; + disconnected = gaim_account_is_disconnected(account); + type = gaim_status_get_type(status); + primitive = gaim_status_type_get_primitive(type); + presence = gaim_account_get_presence(account); + + message = gaim_status_get_attr_string(status, "message"); + if (message == NULL) + message = ""; + stripped = gaim_markup_strip_html(message); + + /* + * The three possible status for Bonjour are + * -available ("avail") + * -idle ("away") + * -away ("dnd") + * Each of them can have an optional message. + */ + if (gaim_presence_is_available(presence)) + bonjour_status = "avail"; + else if (gaim_presence_is_idle(presence)) + bonjour_status = "away"; + else + bonjour_status = "dnd"; + + bonjour_dns_sd_send_status(bd->dns_sd_data, bonjour_status, stripped); + g_free(stripped); +} + +static GList * +bonjour_status_types(GaimAccount *account) +{ + GList *status_types = NULL; + GaimStatusType *type; + + g_return_val_if_fail(account != NULL, NULL); + + type = gaim_status_type_new_with_attrs(GAIM_STATUS_AVAILABLE, + BONJOUR_STATUS_ID_AVAILABLE, + NULL, TRUE, TRUE, FALSE, + "message", _("Message"), + gaim_value_new(GAIM_TYPE_STRING), NULL); + status_types = g_list_append(status_types, type); + + type = gaim_status_type_new_with_attrs(GAIM_STATUS_AWAY, + BONJOUR_STATUS_ID_AWAY, + NULL, TRUE, TRUE, FALSE, + "message", _("Message"), + gaim_value_new(GAIM_TYPE_STRING), NULL); + status_types = g_list_append(status_types, type); + + type = gaim_status_type_new_full(GAIM_STATUS_OFFLINE, + BONJOUR_STATUS_ID_OFFLINE, + NULL, TRUE, TRUE, FALSE); + status_types = g_list_append(status_types, type); + + return status_types; +} + +static void +bonjour_convo_closed(GaimConnection *connection, const char *who) +{ + GaimBuddy *buddy = gaim_find_buddy(connection->account, who); + + if (buddy == NULL) + { + /* + * This buddy is not in our buddy list, and therefore does not really + * exist, so we won't have any data about them. + */ + return; + } + + bonjour_jabber_close_conversation(((BonjourData*)(connection->proto_data))->jabber_data, buddy); +} + +static void +bonjour_list_emblems(GaimBuddy *buddy, + const char **se, const char **sw, + const char **nw, const char **ne) +{ + GaimPresence *presence; + + presence = gaim_buddy_get_presence(buddy); + + if (gaim_presence_is_online(presence) && !gaim_presence_is_available(presence)) + *se = "away"; +} + +static char * +bonjour_status_text(GaimBuddy *buddy) +{ + GaimPresence *presence; + + presence = gaim_buddy_get_presence(buddy); + + if (gaim_presence_is_online(presence) && !gaim_presence_is_available(presence)) + return g_strdup("Away"); + + return NULL; +} + +static void +bonjour_tooltip_text(GaimBuddy *buddy, GaimNotifyUserInfo *user_info, gboolean full) +{ + GaimPresence *presence; + GaimStatus *status; + const char *status_description; + const char *message; + + presence = gaim_buddy_get_presence(buddy); + status = gaim_presence_get_active_status(presence); + message = gaim_status_get_attr_string(status, "message"); + + if (gaim_presence_is_available(presence)) + status_description = gaim_status_get_name(status); + else if (gaim_presence_is_idle(presence)) + status_description = _("Idle"); + else + status_description = gaim_status_get_name(status); + + gaim_notify_user_info_add_pair(user_info, _("Status"), status_description); + if (message != NULL) + gaim_notify_user_info_add_pair(user_info, _("Message"), message); +} + +static gboolean +plugin_unload(GaimPlugin *plugin) +{ + g_free(default_firstname); + g_free(default_lastname); + g_free(default_hostname); + + return TRUE; +} + +static GaimPlugin *my_protocol = NULL; + +static GaimPluginProtocolInfo prpl_info = +{ + OPT_PROTO_NO_PASSWORD, + NULL, /* user_splits */ + NULL, /* protocol_options */ + /* {"png", 0, 0, 96, 96, 0, GAIM_ICON_SCALE_DISPLAY}, */ /* icon_spec */ + NO_BUDDY_ICONS, /* not yet */ /* icon_spec */ + bonjour_list_icon, /* list_icon */ + bonjour_list_emblems, /* list_emblems */ + bonjour_status_text, /* status_text */ + bonjour_tooltip_text, /* tooltip_text */ + bonjour_status_types, /* status_types */ + NULL, /* blist_node_menu */ + NULL, /* chat_info */ + NULL, /* chat_info_defaults */ + bonjour_login, /* login */ + bonjour_close, /* close */ + bonjour_send_im, /* send_im */ + NULL, /* set_info */ + NULL, /* send_typing */ + NULL, /* get_info */ + bonjour_set_status, /* set_status */ + NULL, /* set_idle */ + NULL, /* change_passwd */ + NULL, /* add_buddy */ + NULL, /* add_buddies */ + NULL, /* remove_buddy */ + NULL, /* remove_buddies */ + NULL, /* add_permit */ + NULL, /* add_deny */ + NULL, /* rem_permit */ + NULL, /* rem_deny */ + NULL, /* set_permit_deny */ + NULL, /* join_chat */ + NULL, /* reject_chat */ + NULL, /* get_chat_name */ + NULL, /* chat_invite */ + NULL, /* chat_leave */ + NULL, /* chat_whisper */ + NULL, /* chat_send */ + NULL, /* keepalive */ + NULL, /* register_user */ + NULL, /* get_cb_info */ + NULL, /* get_cb_away */ + NULL, /* alias_buddy */ + NULL, /* group_buddy */ + NULL, /* rename_group */ + NULL, /* buddy_free */ + bonjour_convo_closed, /* convo_closed */ + NULL, /* normalize */ + NULL, /* set_buddy_icon */ + NULL, /* remove_group */ + NULL, /* get_cb_real_name */ + NULL, /* set_chat_topic */ + NULL, /* find_blist_chat */ + NULL, /* roomlist_get_list */ + NULL, /* roomlist_cancel */ + NULL, /* roomlist_expand_category */ + NULL, /* can_receive_file */ + NULL, /* send_file */ + NULL, /* new_xfer */ + NULL, /* offline_message */ + NULL, /* whiteboard_prpl_ops */ + NULL, /* send_raw */ + NULL, /* roomlist_room_serialize */ +}; + +static GaimPluginInfo info = +{ + GAIM_PLUGIN_MAGIC, + GAIM_MAJOR_VERSION, + GAIM_MINOR_VERSION, + GAIM_PLUGIN_PROTOCOL, /**< type */ + NULL, /**< ui_requirement */ + 0, /**< flags */ + NULL, /**< dependencies */ + GAIM_PRIORITY_DEFAULT, /**< priority */ + + "prpl-bonjour", /**< id */ + "Bonjour", /**< name */ + VERSION, /**< version */ + /** summary */ + N_("Bonjour Protocol Plugin"), + /** description */ + N_("Bonjour Protocol Plugin"), + NULL, /**< author */ + GAIM_WEBSITE, /**< homepage */ + + NULL, /**< load */ + plugin_unload, /**< unload */ + NULL, /**< destroy */ + + NULL, /**< ui_info */ + &prpl_info, /**< extra_info */ + NULL, /**< prefs_info */ + NULL +}; + +static void +initialize_default_account_values() +{ +#ifdef _WIN32 + char *fullname = NULL; +#else + struct passwd *info; + const char *fullname = NULL; +#endif + char *splitpoint = NULL; + char hostname[255]; + +#ifndef _WIN32 + /* Try to figure out the user's real name */ + info = getpwuid(getuid()); + if ((info != NULL) && (info->pw_gecos != NULL) && (info->pw_gecos[0] != '\0')) + fullname = info->pw_gecos; + else if ((info != NULL) && (info->pw_name != NULL) && (info->pw_name[0] != '\0')) + fullname = info->pw_name; + else if (((fullname = getlogin()) != NULL) && (fullname[0] != '\0')) + ; + else + fullname = _("Gaim User"); + + /* Make sure fullname is valid UTF-8. If not, try to convert it. */ + if (!g_utf8_validate(fullname, -1, NULL)) + { + gchar *tmp; + tmp = g_locale_to_utf8(fullname, -1, NULL, NULL, NULL); + if ((tmp == NULL) || (*tmp == '\0')) + fullname = _("Gaim User"); + } + +#else + FARPROC myNetUserGetInfo = wgaim_find_and_loadproc("Netapi32.dll", + "NetUserGetInfo"); + + if (myNetUserGetInfo) { + LPUSER_INFO_10 user_info = NULL; + LPSERVER_INFO_100 server_info = NULL; + wchar_t *servername = NULL; + wchar_t username[UNLEN + 1] = {'\0'}; + DWORD dwLenUsername = sizeof(username); + FARPROC myNetServerEnum = wgaim_find_and_loadproc( + "Netapi32.dll", "NetServerEnum"); + FARPROC myNetApiBufferFree = wgaim_find_and_loadproc( + "Netapi32.dll", "NetApiBufferFree"); + + if (myNetServerEnum && myNetApiBufferFree) { + DWORD dwEntriesRead = 0; + DWORD dwTotalEntries = 0; + DWORD dwResumeHandle = 0; + + NET_API_STATUS nStatus = (myNetServerEnum)(NULL, 100, + &server_info, MAX_PREFERRED_LENGTH, + &dwEntriesRead, &dwTotalEntries, + SV_TYPE_DOMAIN_CTRL, NULL, &dwResumeHandle); + + if ((nStatus == NERR_Success + || nStatus == ERROR_MORE_DATA) + && dwEntriesRead > 0) { + servername = server_info->sv100_name; + } else { + gaim_debug_warning("bonjour", "Unable to look up domain controller. NET_API_STATUS = %d, Entries Read = %d, Total Entries = %d\n", nStatus, dwEntriesRead, dwTotalEntries); + } + } + + if (!GetUserNameW(&username, &dwLenUsername)) { + gaim_debug_warning("bonjour", + "Unable to look up username\n"); + } + + if (username != NULL && *username != '\0' + && (myNetUserGetInfo)(servername, username, 10, + &user_info) == NERR_Success) { + if (user_info != NULL) { + fullname = g_utf16_to_utf8( + user_info->usri10_full_name, + -1, NULL, NULL, NULL); + } + } + if (user_info != NULL) + (myNetApiBufferFree)(user_info); + if (server_info != NULL) + (myNetApiBufferFree)(server_info); + } + + if (!fullname) + fullname = g_strdup(_("Gaim User")); +#endif + + /* Split the real name into a first and last name */ + splitpoint = strchr(fullname, ' '); + if (splitpoint != NULL) + { + default_firstname = g_strndup(fullname, splitpoint - fullname); + default_lastname = g_strdup(&splitpoint[1]); + } + else + { + default_firstname = g_strdup(fullname); + default_lastname = g_strdup(""); + } + +#ifdef _WIN32 + g_free(fullname); +#endif + + /* Try to figure out a good host name to use */ + /* TODO: Avoid 'localhost,' if possible */ + if (gethostname(hostname, 255) != 0) { + gaim_debug_warning("bonjour", "Error %d when getting host name. Using \"localhost.\"\n", errno); + strcpy(hostname, "localhost"); + } + default_hostname = g_strdup(hostname); +} + +static void +init_plugin(GaimPlugin *plugin) +{ + GaimAccountUserSplit *split; + GaimAccountOption *option; + + initialize_default_account_values(); + + /* Creating the user splits */ + split = gaim_account_user_split_new(_("Hostname"), default_hostname, '@'); + prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); + + /* Creating the options for the protocol */ + option = gaim_account_option_string_new(_("First name"), "first", default_firstname); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + option = gaim_account_option_string_new(_("Last name"), "last", default_lastname); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + option = gaim_account_option_string_new(_("E-mail"), "email", ""); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + option = gaim_account_option_string_new(_("AIM Account"), "AIM", ""); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + option = gaim_account_option_string_new(_("Jabber Account"), "jid", ""); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + my_protocol = plugin; +} + +GAIM_INIT_PLUGIN(bonjour, init_plugin, info); diff --git a/libpurple/protocols/bonjour/bonjour.h b/libpurple/protocols/bonjour/bonjour.h new file mode 100644 index 0000000000..6686092fb9 --- /dev/null +++ b/libpurple/protocols/bonjour/bonjour.h @@ -0,0 +1,51 @@ +/** + * @file bonjour.h The Gaim interface to mDNS and peer to peer Jabber. + * + * gaim + * + * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * 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 + * + */ + +#ifndef _BONJOUR_H_ +#define _BONJOUR_H_ + +#include <howl.h> + +#include "dns_sd.h" +#include "internal.h" +#include "jabber.h" + +#define BONJOUR_GROUP_NAME _("Bonjour") +#define BONJOUR_PROTOCOL_NAME "bonjour" +#define BONJOUR_ICON_NAME "bonjour" + +#define BONJOUR_STATUS_ID_OFFLINE "offline" +#define BONJOUR_STATUS_ID_AVAILABLE "available" +#define BONJOUR_STATUS_ID_AWAY "away" + +#define BONJOUR_DEFAULT_PORT_INT 5298 + +typedef struct _BonjourData +{ + BonjourDnsSd *dns_sd_data; + BonjourJabber *jabber_data; +} BonjourData; + +#endif /* _BONJOUR_H_ */ diff --git a/libpurple/protocols/bonjour/buddy.c b/libpurple/protocols/bonjour/buddy.c new file mode 100644 index 0000000000..3e59035662 --- /dev/null +++ b/libpurple/protocols/bonjour/buddy.c @@ -0,0 +1,167 @@ +/* + * 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 Library 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 <glib.h> +#include <stdlib.h> + +#include "buddy.h" +#include "account.h" +#include "blist.h" +#include "bonjour.h" +#include "debug.h" + +/** + * Creates a new buddy. + */ +BonjourBuddy * +bonjour_buddy_new(const gchar *name, const gchar *first, gint port_p2pj, + const gchar *phsh, const gchar *status, const gchar *email, + const gchar *last, const gchar *jid, const gchar *AIM, + const gchar *vc, const gchar *ip, const gchar *msg) +{ + BonjourBuddy *buddy = malloc(sizeof(BonjourBuddy)); + + buddy->name = g_strdup(name); + buddy->first = g_strdup(first); + buddy->port_p2pj = port_p2pj; + buddy->phsh = g_strdup(phsh); + buddy->status = g_strdup(status); + buddy->email = g_strdup(email); + buddy->last = g_strdup(last); + buddy->jid = g_strdup(jid); + buddy->AIM = g_strdup(AIM); + buddy->vc = g_strdup(vc); + buddy->ip = g_strdup(ip); + buddy->msg = g_strdup(msg); + buddy->conversation = NULL; + + return buddy; +} + +/** + * Check if all the compulsory buddy data is present. + */ +gboolean +bonjour_buddy_check(BonjourBuddy *buddy) +{ + if (buddy->name == NULL) { + return FALSE; + } + + if (buddy->first == NULL) { + return FALSE; + } + + if (buddy->last == NULL) { + return FALSE; + } + + if (buddy->status == NULL) { + return FALSE; + } + + return TRUE; +} + +/** + * If the buddy does not yet exist, then create it and add it to + * our buddy list. In either case we set the correct status for + * the buddy. + */ +void +bonjour_buddy_add_to_gaim(GaimAccount *account, BonjourBuddy *bonjour_buddy) +{ + GaimBuddy *buddy; + GaimGroup *group; + const char *status_id, *first, *last; + char *alias; + + /* Translate between the Bonjour status and the Gaim status */ + if (g_ascii_strcasecmp("dnd", bonjour_buddy->status) == 0) + status_id = BONJOUR_STATUS_ID_AWAY; + else + status_id = BONJOUR_STATUS_ID_AVAILABLE; + + /* + * TODO: Figure out the idle time by getting the "away" + * field from the DNS SD. + */ + + /* Create the alias for the buddy using the first and the last name */ + first = bonjour_buddy->first; + last = bonjour_buddy->last; + alias = g_strdup_printf("%s%s%s", + (first && *first ? first : ""), + (first && *first && last && *last ? " " : ""), + (last && *last ? last : "")); + + /* Make sure the Bonjour group exists in our buddy list */ + group = gaim_find_group(BONJOUR_GROUP_NAME); /* Use the buddy's domain, instead? */ + if (group == NULL) + { + group = gaim_group_new(BONJOUR_GROUP_NAME); + gaim_blist_add_group(group, NULL); + } + + /* Make sure the buddy exists in our buddy list */ + buddy = gaim_find_buddy(account, bonjour_buddy->name); + if (buddy == NULL) + { + buddy = gaim_buddy_new(account, bonjour_buddy->name, alias); + buddy->proto_data = bonjour_buddy; + gaim_blist_node_set_flags((GaimBlistNode *)buddy, GAIM_BLIST_NODE_FLAG_NO_SAVE); + gaim_blist_add_buddy(buddy, NULL, group, NULL); + } + + /* Set the user's status */ + if (bonjour_buddy->msg != NULL) + gaim_prpl_got_user_status(account, buddy->name, status_id, + "message", bonjour_buddy->msg, + NULL); + else + gaim_prpl_got_user_status(account, buddy->name, status_id, + NULL); + gaim_prpl_got_user_idle(account, buddy->name, FALSE, 0); + + g_free(alias); +} + +/** + * Deletes a buddy from memory. + */ +void +bonjour_buddy_delete(BonjourBuddy *buddy) +{ + g_free(buddy->name); + g_free(buddy->first); + g_free(buddy->phsh); + g_free(buddy->status); + g_free(buddy->email); + g_free(buddy->last); + g_free(buddy->jid); + g_free(buddy->AIM); + g_free(buddy->vc); + g_free(buddy->ip); + g_free(buddy->msg); + + if (buddy->conversation != NULL) + { + g_free(buddy->conversation->buddy_name); + g_free(buddy->conversation); + } + + free(buddy); +} diff --git a/libpurple/protocols/bonjour/buddy.h b/libpurple/protocols/bonjour/buddy.h new file mode 100644 index 0000000000..f743b55575 --- /dev/null +++ b/libpurple/protocols/bonjour/buddy.h @@ -0,0 +1,66 @@ +/* + * 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 Library 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. + */ + +#ifndef _BONJOUR_BUDDY +#define _BONJOUR_BUDDY + +#include <howl.h> +#include <glib.h> + +#include "account.h" +#include "jabber.h" + +typedef struct _BonjourBuddy +{ + gchar *name; + gchar *first; + gint port_p2pj; + gchar *phsh; + gchar *status; + gchar *email; + gchar *last; + gchar *jid; + gchar *AIM; + gchar *vc; + gchar *ip; + gchar *msg; + BonjourJabberConversation *conversation; +} BonjourBuddy; + +/** + * Creates a new buddy. + */ +BonjourBuddy *bonjour_buddy_new(const gchar *name, const gchar *first, + gint port_p2pj, const gchar *phsh, const gchar *status, + const gchar *email, const gchar *last, const gchar *jid, + const gchar *AIM, const gchar *vc, const gchar *ip, const gchar *msg); + +/** + * Check if all the compulsory buddy data is present. + */ +gboolean bonjour_buddy_check(BonjourBuddy *buddy); + +/** + * If the buddy doesn't previoulsy exists, it is created. Else, its data is changed (???) + */ +void bonjour_buddy_add_to_gaim(GaimAccount *account, BonjourBuddy *buddy); + +/** + * Deletes a buddy from memory. + */ +void bonjour_buddy_delete(BonjourBuddy *buddy); + +#endif diff --git a/libpurple/protocols/bonjour/dns_sd.c b/libpurple/protocols/bonjour/dns_sd.c new file mode 100644 index 0000000000..25b01345ea --- /dev/null +++ b/libpurple/protocols/bonjour/dns_sd.c @@ -0,0 +1,387 @@ +/* + * 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 Library 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 <string.h> + +#include "dns_sd.h" +#include "bonjour.h" +#include "buddy.h" +#include "debug.h" + +/* Private functions */ + +static sw_result HOWL_API +_publish_reply(sw_discovery discovery, sw_discovery_oid oid, + sw_discovery_publish_status status, sw_opaque extra) +{ + gaim_debug_warning("bonjour", "_publish_reply --> Start\n"); + + /* Check the answer from the mDNS daemon */ + switch (status) + { + case SW_DISCOVERY_PUBLISH_STARTED : + gaim_debug_info("bonjour", "_publish_reply --> Service started\n"); + break; + case SW_DISCOVERY_PUBLISH_STOPPED : + gaim_debug_info("bonjour", "_publish_reply --> Service stopped\n"); + break; + case SW_DISCOVERY_PUBLISH_NAME_COLLISION : + gaim_debug_info("bonjour", "_publish_reply --> Name collision\n"); + break; + case SW_DISCOVERY_PUBLISH_INVALID : + gaim_debug_info("bonjour", "_publish_reply --> Service invalid\n"); + break; + } + + return SW_OKAY; +} + +static sw_result HOWL_API +_resolve_reply(sw_discovery discovery, sw_discovery_oid oid, + sw_uint32 interface_index, sw_const_string name, + sw_const_string type, sw_const_string domain, + sw_ipv4_address address, sw_port port, + sw_octets text_record, sw_ulong text_record_len, + sw_opaque extra) +{ + BonjourBuddy *buddy; + GaimAccount *account = (GaimAccount*)extra; + gchar *txtvers = NULL; + gchar *version = NULL; + gchar *first = NULL; + gchar *phsh = NULL; + gchar *status = NULL; + gchar *email = NULL; + gchar *last = NULL; + gchar *jid = NULL; + gchar *AIM = NULL; + gchar *vc = NULL; + gchar *msg = NULL; + gint address_length = 16; + gchar *ip = NULL; + sw_text_record_iterator iterator; + char key[SW_TEXT_RECORD_MAX_LEN]; + char value[SW_TEXT_RECORD_MAX_LEN]; + sw_uint32 value_length; + + sw_discovery_cancel(discovery, oid); + + /* Get the ip as a string */ + ip = malloc(address_length); + sw_ipv4_address_name(address, ip, address_length); + + /* Obtain the parameters from the text_record */ + if ((text_record_len > 0) && (text_record) && (*text_record != '\0')) + { + sw_text_record_iterator_init(&iterator, text_record, text_record_len); + while (sw_text_record_iterator_next(iterator, key, (sw_octet *)value, &value_length) == SW_OKAY) + { + /* Compare the keys with the possible ones and save them on */ + /* the appropiate place of the buddy_list */ + if (strcmp(key, "txtvers") == 0) { + txtvers = g_strdup(value); + } else if (strcmp(key, "version") == 0) { + version = g_strdup(value); + } else if (strcmp(key, "1st") == 0) { + first = g_strdup(value); + } else if (strcmp(key, "status") == 0) { + status = g_strdup(value); + } else if (strcmp(key, "email") == 0) { + email = g_strdup(value); + } else if (strcmp(key, "last") == 0) { + last = g_strdup(value); + } else if (strcmp(key, "jid") == 0) { + jid = g_strdup(value); + } else if (strcmp(key, "AIM") == 0) { + AIM = g_strdup(value); + } else if (strcmp(key, "vc") == 0) { + vc = g_strdup(value); + } else if (strcmp(key, "phsh") == 0) { + phsh = g_strdup(value); + } else if (strcmp(key, "msg") == 0) { + msg = g_strdup(value); + } + } + } + + /* Put the parameters of the text_record in a buddy and add the buddy to */ + /* the buddy list */ + buddy = bonjour_buddy_new(name, first, port, phsh, + status, email, last, jid, AIM, vc, ip, msg); + + if (bonjour_buddy_check(buddy) == FALSE) + { + bonjour_buddy_delete(buddy); + return SW_DISCOVERY_E_UNKNOWN; + } + + /* Add or update the buddy in our buddy list */ + bonjour_buddy_add_to_gaim(account, buddy); + + /* Free all the temporal strings */ + g_free(txtvers); + g_free(version); + g_free(first); + g_free(last); + g_free(status); + g_free(email); + g_free(jid); + g_free(AIM); + g_free(vc); + g_free(phsh); + g_free(msg); + + return SW_OKAY; +} + +static sw_result HOWL_API +_browser_reply(sw_discovery discovery, sw_discovery_oid oid, + sw_discovery_browse_status status, + sw_uint32 interface_index, sw_const_string name, + sw_const_string type, sw_const_string domain, + sw_opaque_t extra) +{ + sw_discovery_resolve_id rid; + GaimAccount *account = (GaimAccount*)extra; + GaimBuddy *gb = NULL; + + switch (status) + { + case SW_DISCOVERY_BROWSE_INVALID: + gaim_debug_warning("bonjour", "_browser_reply --> Invalid\n"); + break; + case SW_DISCOVERY_BROWSE_RELEASE: + gaim_debug_warning("bonjour", "_browser_reply --> Release\n"); + break; + case SW_DISCOVERY_BROWSE_ADD_DOMAIN: + gaim_debug_warning("bonjour", "_browser_reply --> Add domain\n"); + break; + case SW_DISCOVERY_BROWSE_ADD_DEFAULT_DOMAIN: + gaim_debug_warning("bonjour", "_browser_reply --> Add default domain\n"); + break; + case SW_DISCOVERY_BROWSE_REMOVE_DOMAIN: + gaim_debug_warning("bonjour", "_browser_reply --> Remove domain\n"); + break; + case SW_DISCOVERY_BROWSE_ADD_SERVICE: + /* A new peer has joined the network and uses iChat bonjour */ + gaim_debug_info("bonjour", "_browser_reply --> Add service\n"); + if (g_ascii_strcasecmp(name, account->username) != 0) + { + if (sw_discovery_resolve(discovery, interface_index, name, type, + domain, _resolve_reply, extra, &rid) != SW_OKAY) + { + gaim_debug_warning("bonjour", "_browser_reply --> Cannot send resolve\n"); + } + } + break; + case SW_DISCOVERY_BROWSE_REMOVE_SERVICE: + gaim_debug_info("bonjour", "_browser_reply --> Remove service\n"); + gb = gaim_find_buddy((GaimAccount*)extra, name); + if (gb != NULL) + { + bonjour_buddy_delete(gb->proto_data); + gaim_blist_remove_buddy(gb); + } + break; + case SW_DISCOVERY_BROWSE_RESOLVED: + gaim_debug_info("bonjour", "_browse_reply --> Resolved\n"); + break; + default: + break; + } + + return SW_OKAY; +} + +static int +_dns_sd_publish(BonjourDnsSd *data, PublishType type) +{ + sw_text_record dns_data; + sw_result publish_result = SW_OKAY; + char portstring[6]; + + /* Fill the data for the service */ + if (sw_text_record_init(&dns_data) != SW_OKAY) + { + gaim_debug_error("bonjour", "Unable to initialize the data for the mDNS.\n"); + return -1; + } + + /* Convert the port to a string */ + snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj); + + /* Publish standard records */ + sw_text_record_add_key_and_string_value(dns_data, "txtvers", data->txtvers); + sw_text_record_add_key_and_string_value(dns_data, "version", data->version); + sw_text_record_add_key_and_string_value(dns_data, "1st", data->first); + sw_text_record_add_key_and_string_value(dns_data, "last", data->last); + sw_text_record_add_key_and_string_value(dns_data, "port.p2pj", portstring); + sw_text_record_add_key_and_string_value(dns_data, "phsh", data->phsh); + sw_text_record_add_key_and_string_value(dns_data, "status", data->status); + sw_text_record_add_key_and_string_value(dns_data, "vc", data->vc); + + /* Publish extra records */ + if ((data->email != NULL) && (*data->email != '\0')) + sw_text_record_add_key_and_string_value(dns_data, "email", data->email); + + if ((data->jid != NULL) && (*data->jid != '\0')) + sw_text_record_add_key_and_string_value(dns_data, "jid", data->jid); + + if ((data->AIM != NULL) && (*data->AIM != '\0')) + sw_text_record_add_key_and_string_value(dns_data, "AIM", data->AIM); + + if ((data->msg != NULL) && (*data->msg != '\0')) + sw_text_record_add_key_and_string_value(dns_data, "msg", data->msg); + + /* Publish the service */ + switch (type) + { + case PUBLISH_START: + publish_result = sw_discovery_publish(data->session, 0, data->name, ICHAT_SERVICE, NULL, + NULL, data->port_p2pj, sw_text_record_bytes(dns_data), sw_text_record_len(dns_data), + _publish_reply, NULL, &data->session_id); + break; + case PUBLISH_UPDATE: + publish_result = sw_discovery_publish_update(data->session, data->session_id, + sw_text_record_bytes(dns_data), sw_text_record_len(dns_data)); + break; + } + if (publish_result != SW_OKAY) + { + gaim_debug_error("bonjour", "Unable to publish or change the status of the _presence._tcp service.\n"); + return -1; + } + + /* Free the memory used by temp data */ + sw_text_record_fina(dns_data); + + return 0; +} + +static void +_dns_sd_handle_packets(gpointer data, gint source, GaimInputCondition condition) +{ + sw_discovery_read_socket((sw_discovery)data); +} + +/* End private functions */ + +/** + * Allocate space for the dns-sd data. + */ +BonjourDnsSd * +bonjour_dns_sd_new() +{ + BonjourDnsSd *data = g_new0(BonjourDnsSd, 1); + + return data; +} + +/** + * Deallocate the space of the dns-sd data. + */ +void +bonjour_dns_sd_free(BonjourDnsSd *data) +{ + g_free(data->first); + g_free(data->last); + g_free(data->email); + g_free(data); +} + +/** + * Send a new dns-sd packet updating our status. + */ +void +bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message) +{ + g_free(data->status); + g_free(data->msg); + + data->status = g_strdup(status); + data->msg = g_strdup(status_message); + + /* Update our text record with the new status */ + _dns_sd_publish(data, PUBLISH_UPDATE); /* <--We must control the errors */ +} + +/** + * Advertise our presence within the dns-sd daemon and start browsing + * for other bonjour peers. + */ +gboolean +bonjour_dns_sd_start(BonjourDnsSd *data) +{ + GaimAccount *account; + GaimConnection *gc; + gint dns_sd_socket; + sw_discovery_oid session_id; + + account = data->account; + gc = gaim_account_get_connection(account); + + /* Initialize the dns-sd data and session */ + if (sw_discovery_init(&data->session) != SW_OKAY) + { + gaim_debug_error("bonjour", "Unable to initialize an mDNS session.\n"); + + /* In Avahi, sw_discovery_init frees data->session but doesn't clear it */ + data->session = NULL; + + return FALSE; + } + + /* Publish our bonjour IM client at the mDNS daemon */ + _dns_sd_publish(data, PUBLISH_START); /* <--We must control the errors */ + + /* Advise the daemon that we are waiting for connections */ + if (sw_discovery_browse(data->session, 0, ICHAT_SERVICE, NULL, _browser_reply, + data->account, &session_id) != SW_OKAY) + { + gaim_debug_error("bonjour", "Unable to get service."); + return FALSE; + } + + /* Get the socket that communicates with the mDNS daemon and bind it to a */ + /* callback that will handle the dns_sd packets */ + dns_sd_socket = sw_discovery_socket(data->session); + gc->inpa = gaim_input_add(dns_sd_socket, GAIM_INPUT_READ, + _dns_sd_handle_packets, data->session); + + return TRUE; +} + +/** + * Unregister the "_presence._tcp" service at the mDNS daemon. + */ +void +bonjour_dns_sd_stop(BonjourDnsSd *data) +{ + GaimAccount *account; + GaimConnection *gc; + + if (data->session == NULL) + return; + + sw_discovery_cancel(data->session, data->session_id); + + account = data->account; + gc = gaim_account_get_connection(account); + gaim_input_remove(gc->inpa); + + g_free(data->session); + data->session = NULL; +} diff --git a/libpurple/protocols/bonjour/dns_sd.h b/libpurple/protocols/bonjour/dns_sd.h new file mode 100644 index 0000000000..e556539186 --- /dev/null +++ b/libpurple/protocols/bonjour/dns_sd.h @@ -0,0 +1,81 @@ +/* + * 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 Library 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. + */ + +#ifndef _BONJOUR_DNS_SD +#define _BONJOUR_DNS_SD + +#include <howl.h> +#include <glib.h> +#include "account.h" + +#define ICHAT_SERVICE "_presence._tcp." + +/** + * Data to be used by the dns-sd connection. + */ +typedef struct _BonjourDnsSd +{ + sw_discovery session; + sw_discovery_oid session_id; + GaimAccount *account; + gchar *name; + gchar *txtvers; + gchar *version; + gchar *first; + gchar *last; + gint port_p2pj; + gchar *phsh; + gchar *status; + gchar *email; + gchar *vc; + gchar *jid; + gchar *AIM; + gchar *msg; + GHashTable *buddies; +} BonjourDnsSd; + +typedef enum _PublishType { + PUBLISH_START, + PUBLISH_UPDATE +} PublishType; + +/** + * Allocate space for the dns-sd data. + */ +BonjourDnsSd *bonjour_dns_sd_new(void); + +/** + * Deallocate the space of the dns-sd data. + */ +void bonjour_dns_sd_free(BonjourDnsSd *data); + +/** + * Send a new dns-sd packet updating our status. + */ +void bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message); + +/** + * Advertise our presence within the dns-sd daemon and start + * browsing for other bonjour peers. + */ +gboolean bonjour_dns_sd_start(BonjourDnsSd *data); + +/** + * Unregister the "_presence._tcp" service at the mDNS daemon. + */ +void bonjour_dns_sd_stop(BonjourDnsSd *data); + +#endif diff --git a/libpurple/protocols/bonjour/issues.txt b/libpurple/protocols/bonjour/issues.txt new file mode 100644 index 0000000000..62ea6e1f05 --- /dev/null +++ b/libpurple/protocols/bonjour/issues.txt @@ -0,0 +1,18 @@ +========================================== +============= Known issues =============== +========================================== + +(1) Messages are limited in length (5000 char) <-- FIXED +(2) Messages formated by Gaim didn't work <-- FIXED +(3) iChat sends the size in points, Gaim wants a 1..7 range <-- FIXED Gaim2iChat (iChat2Gaim left) +(4) When the other end closes the socket without sending the end of stream, Gaim crashes and coredump <-- FIXED +(5) I18n +(6) Status changes don't work +(7) When the conversation is closed in Gaim with the X button, we don't send the end of stream <-- FIXED +(8) The server socket is not reusable, after an error, you cannot connect for a while <-- FIXED +(9) Avatars +(10) File transfers +(11) Typing notifications +(12) Gaim HTML syntax is not shown properly <-- FIXED +(13) Strange messages creates coredump <-- FIXED +(14) Check if it works on win32 diff --git a/libpurple/protocols/bonjour/jabber.c b/libpurple/protocols/bonjour/jabber.c new file mode 100644 index 0000000000..c8da317632 --- /dev/null +++ b/libpurple/protocols/bonjour/jabber.c @@ -0,0 +1,667 @@ +/* + * gaim - Bonjour Protocol Plugin + * + * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * 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 + */ +#ifndef _WIN32 +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#else +#include "libc_interface.h" +#endif +#include <sys/types.h> +#include <glib.h> +#include <glib/gprintf.h> +#include <unistd.h> +#include <fcntl.h> + +#include "network.h" +#include "eventloop.h" +#include "connection.h" +#include "blist.h" +#include "xmlnode.h" +#include "debug.h" +#include "notify.h" +#include "util.h" + +#include "jabber.h" +#include "bonjour.h" +#include "buddy.h" + +static gint +_connect_to_buddy(GaimBuddy *gb) +{ + gint socket_fd; + gint retorno = 0; + struct sockaddr_in buddy_address; + + /* Create a socket and make it non-blocking */ + socket_fd = socket(PF_INET, SOCK_STREAM, 0); + + buddy_address.sin_family = PF_INET; + buddy_address.sin_port = htons(((BonjourBuddy*)(gb->proto_data))->port_p2pj); + inet_aton(((BonjourBuddy*)(gb->proto_data))->ip, &(buddy_address.sin_addr)); + memset(&(buddy_address.sin_zero), '\0', 8); + + retorno = connect(socket_fd, (struct sockaddr*)&buddy_address, sizeof(struct sockaddr)); + if (retorno == -1) { + gaim_debug_warning("bonjour", "connect error: %s\n", strerror(errno)); + } + fcntl(socket_fd, F_SETFL, O_NONBLOCK); + + return socket_fd; +} + +#if 0 /* this isn't used anywhere... */ +static const char * +_font_size_gaim_to_ichat(int size) +{ + switch (size) { + case 1: + return "8"; + case 2: + return "10"; + case 3: + return "12"; + case 4: + return "14"; + case 5: + return "17"; + case 6: + return "21"; + case 7: + return "24"; + } + + return "12"; +} +#endif + +static const char * +_font_size_ichat_to_gaim(int size) +{ + if (size > 24) { + return "7"; + } else if (size >= 21) { + return "6"; + } else if (size >= 17) { + return "5"; + } else if (size >= 14) { + return "4"; + } else if (size >= 12) { + return "3"; + } else if (size >= 10) { + return "2"; + } + + return "1"; +} +static void +_jabber_parse_and_write_message_to_ui(char *message, GaimConnection *connection, GaimBuddy *gb) +{ + xmlnode *body_node = NULL; + char *body = NULL; + xmlnode *html_node = NULL; + gboolean isHTML = FALSE; + xmlnode *html_body_node = NULL; + const char *ichat_balloon_color = NULL; + const char *ichat_text_color = NULL; + xmlnode *html_body_font_node = NULL; + const char *font_face = NULL; + const char *font_size = NULL; + const char *font_color = NULL; + char *html_body = NULL; + xmlnode *events_node = NULL; + gboolean composing_event = FALSE; + gint garbage = -1; + xmlnode *message_node = NULL; + + /* Parsing of the message */ + message_node = xmlnode_from_str(message, strlen(message)); + if (message_node == NULL) { + return; + } + + body_node = xmlnode_get_child(message_node, "body"); + if (body_node != NULL) { + body = xmlnode_get_data(body_node); + } else { + return; + } + + html_node = xmlnode_get_child(message_node, "html"); + if (html_node != NULL) + { + isHTML = TRUE; + html_body_node = xmlnode_get_child(html_node, "body"); + if (html_body_node != NULL) + { + ichat_balloon_color = xmlnode_get_attrib(html_body_node, "ichatballooncolor"); + ichat_text_color = xmlnode_get_attrib(html_body_node, "ichattextcolor"); + html_body_font_node = xmlnode_get_child(html_body_node, "font"); + if (html_body_font_node != NULL) + { /* Types of messages sent by iChat */ + font_face = xmlnode_get_attrib(html_body_font_node, "face"); + /* The absolute iChat font sizes should be converted to 1..7 range */ + font_size = xmlnode_get_attrib(html_body_font_node, "ABSZ"); + if (font_size != NULL) + { + font_size = _font_size_ichat_to_gaim(atoi(font_size)); + } + font_color = xmlnode_get_attrib(html_body_font_node, "color"); + html_body = xmlnode_get_data(html_body_font_node); + if (html_body == NULL) + { + /* This is the kind of formated messages that Gaim creates */ + html_body = xmlnode_to_str(html_body_font_node, &garbage); + } + } else { + isHTML = FALSE; + } + } else { + isHTML = FALSE; + } + + } + + events_node = xmlnode_get_child_with_namespace(message_node, "x", "jabber:x:event"); + if (events_node != NULL) + { + if (xmlnode_get_child(events_node, "composing") != NULL) + { + composing_event = TRUE; + } + if (xmlnode_get_child(events_node, "id") != NULL) + { + /* The user is just typing */ + xmlnode_free(message_node); + g_free(body); + g_free(html_body); + return; + } + } + + /* Compose the message */ + if (isHTML) + { + if (font_face == NULL) font_face = "Helvetica"; + if (font_size == NULL) font_size = "3"; + if (ichat_text_color == NULL) ichat_text_color = "#000000"; + if (ichat_balloon_color == NULL) ichat_balloon_color = "#FFFFFF"; + body = g_strconcat("<font face='", font_face, "' size='", font_size, "' color='", ichat_text_color, + "' back='", ichat_balloon_color, "'>", html_body, "</font>", NULL); + } + + /* Send the message to the UI */ + serv_got_im(connection, gb->name, body, 0, time(NULL)); + + /* Free all the strings and nodes (the attributes are freed with their nodes) */ + xmlnode_free(message_node); + g_free(body); + g_free(html_body); +} + +struct _check_buddy_by_address_t { + char *address; + GaimBuddy **gb; + BonjourJabber *bj; +}; + +static void +_check_buddy_by_address(gpointer key, gpointer value, gpointer data) +{ + GaimBuddy *gb = (GaimBuddy*)value; + BonjourBuddy *bb; + struct _check_buddy_by_address_t *cbba; + + gb = value; + cbba = data; + + /* + * If the current GaimBuddy's data is not null and the GaimBuddy's account + * is the same as the account requesting the check then continue to determine + * whether the buddies IP matches the target IP. + */ + if (cbba->bj->account == gb->account) + { + bb = gb->proto_data; + if ((bb != NULL) && (g_strcasecmp(bb->ip, cbba->address) == 0)) + *(cbba->gb) = gb; + } +} + +static gint +_read_data(gint socket, char **message) +{ + GString *data = g_string_new(""); + char partial_data[512]; + gint total_message_length = 0; + gint partial_message_length = 0; + + /* Read chunks of 512 bytes till the end of the data */ + while ((partial_message_length = recv(socket, partial_data, 512, 0)) > 0) + { + g_string_append_len(data, partial_data, partial_message_length); + total_message_length += partial_message_length; + } + + if (partial_message_length == -1) + { + gaim_debug_warning("bonjour", "receive error: %s\n", strerror(errno)); + if (total_message_length == 0) { + return -1; + } + } + + *message = data->str; + g_string_free(data, FALSE); + if (total_message_length != 0) + gaim_debug_info("bonjour", "Receive: -%s- %d bytes\n", *message, total_message_length); + + return total_message_length; +} + +static gint +_send_data(gint socket, char *message) +{ + gint message_len = strlen(message); + gint partial_sent = 0; + gchar *partial_message = message; + + while ((partial_sent = send(socket, partial_message, message_len, 0)) < message_len) + { + if (partial_sent != -1) { + partial_message += partial_sent; + message_len -= partial_sent; + } else { + return -1; + } + } + + return strlen(message); +} + +static void +_client_socket_handler(gpointer data, gint socket, GaimInputCondition condition) +{ + char *message = NULL; + gint message_length; + GaimBuddy *gb = (GaimBuddy*)data; + GaimAccount *account = gb->account; + GaimConversation *conversation; + char *closed_conv_message; + BonjourBuddy *bb = (BonjourBuddy*)gb->proto_data; + gboolean closed_conversation = FALSE; + xmlnode *message_node = NULL; + + /* Read the data from the socket */ + if ((message_length = _read_data(socket, &message)) == -1) { + /* There have been an error reading from the socket */ + return; + } else if (message_length == 0) { /* The other end has closed the socket */ + closed_conversation = TRUE; + } else { + message[message_length] = '\0'; + + while (g_ascii_iscntrl(message[message_length - 1])) { + message[message_length - 1] = '\0'; + message_length--; + } + } + + /* Parse the message into an XMLnode for analysis */ + message_node = xmlnode_from_str(message, strlen(message)); + + /* Check if the start of the stream has been received, if not check that the current */ + /* data is the start of the stream */ + if (!(bb->conversation->stream_started)) + { + /* Check if this is the start of the stream */ + if ((message_node != NULL) && + g_ascii_strcasecmp(xmlnode_get_attrib(message_node, "xmlns"), "jabber:client") && + (xmlnode_get_attrib(message_node,"xmlns:stream") != NULL)) + { + bb->conversation->stream_started = TRUE; + } + else + { + /* TODO: This needs to be nonblocking! */ + if (send(bb->conversation->socket, DOCTYPE, strlen(DOCTYPE), 0) == -1) + { + gaim_debug_error("bonjour", "Unable to start a conversation with %s\n", bb->name); + } + else + { + bb->conversation->stream_started = TRUE; + } + } + } + + /* + * Check that this is not the end of the conversation. This is + * using a magic string, but xmlnode won't play nice when just + * parsing an end tag + */ + if (gaim_str_has_prefix(message, STREAM_END) || (closed_conversation == TRUE)) { + /* Close the socket, clear the watcher and free memory */ + if (bb->conversation != NULL) { + close(bb->conversation->socket); + gaim_input_remove(bb->conversation->watcher_id); + g_free(bb->conversation->buddy_name); + g_free(bb->conversation); + bb->conversation = NULL; + } + + /* Inform the user that the conversation has been closed */ + conversation = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, gb->name, account); + closed_conv_message = g_strdup_printf(_("%s has closed the conversation."), gb->name); + gaim_conversation_write(conversation, NULL, closed_conv_message, GAIM_MESSAGE_SYSTEM, time(NULL)); + g_free(closed_conv_message); + } else { + /* Parse the message to get the data and send to the ui */ + _jabber_parse_and_write_message_to_ui(message, account->gc, gb); + } + + if (message_node != NULL) + xmlnode_free(message_node); +} + +static void +_server_socket_handler(gpointer data, int server_socket, GaimInputCondition condition) +{ + GaimBuddy *gb = NULL; + struct sockaddr_in their_addr; /* connector's address information */ + socklen_t sin_size = sizeof(struct sockaddr); + int client_socket; + BonjourBuddy *bb = NULL; + BonjourJabber *bj = data; + char *address_text = NULL; + GaimBuddyList *bl = gaim_get_blist(); + struct _check_buddy_by_address_t *cbba; + + /* Check that it is a read condition */ + if (condition != GAIM_INPUT_READ) { + return; + } + + if ((client_socket = accept(server_socket, (struct sockaddr *)&their_addr, &sin_size)) == -1) + { + return; + } + fcntl(client_socket, F_SETFL, O_NONBLOCK); + + /* Look for the buddy that has opened the conversation and fill information */ + address_text = inet_ntoa(their_addr.sin_addr); + cbba = g_new0(struct _check_buddy_by_address_t, 1); + cbba->address = address_text; + cbba->gb = &gb; + cbba->bj = bj; + g_hash_table_foreach(bl->buddies, _check_buddy_by_address, cbba); + g_free(cbba); + if (gb == NULL) + { + gaim_debug_info("bonjour", "We don't like invisible buddies, this is not a superheros comic\n"); + close(client_socket); + return; + } + bb = (BonjourBuddy*)gb->proto_data; + + /* Check if the conversation has been previously started */ + if (bb->conversation == NULL) + { + bb->conversation = g_new(BonjourJabberConversation, 1); + bb->conversation->socket = client_socket; + bb->conversation->stream_started = FALSE; + bb->conversation->buddy_name = g_strdup(gb->name); + bb->conversation->message_id = 1; + + if (bb->conversation->stream_started == FALSE) { + /* Start the stream */ + send(bb->conversation->socket, DOCTYPE, strlen(DOCTYPE), 0); + bb->conversation->stream_started = TRUE; + } + + /* Open a watcher for the client socket */ + bb->conversation->watcher_id = gaim_input_add(client_socket, GAIM_INPUT_READ, + _client_socket_handler, gb); + } else { + close(client_socket); + } +} + +gint +bonjour_jabber_start(BonjourJabber *data) +{ + struct sockaddr_in my_addr; + int yes = 1; + int i; + gboolean bind_successful; + + /* Open a listening socket for incoming conversations */ + if ((data->socket = socket(PF_INET, SOCK_STREAM, 0)) < 0) + { + gaim_debug_error("bonjour", "Cannot open socket: %s\n", strerror(errno)); + gaim_connection_error(data->account->gc, _("Cannot open socket")); + return -1; + } + + /* Make the socket reusable */ + if (setsockopt(data->socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != 0) + { + gaim_debug_error("bonjour", "Error setting socket options: %s\n", strerror(errno)); + gaim_connection_error(data->account->gc, _("Error setting socket options")); + return -1; + } + + memset(&my_addr, 0, sizeof(struct sockaddr_in)); + my_addr.sin_family = PF_INET; + + /* Attempt to find a free port */ + bind_successful = FALSE; + for (i = 0; i < 10; i++) + { + my_addr.sin_port = htons(data->port); + if (bind(data->socket, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == 0) + { + bind_successful = TRUE; + break; + } + data->port++; + } + + /* On no! We tried 10 ports and could not bind to ANY of them */ + if (!bind_successful) + { + gaim_debug_error("bonjour", "Cannot bind socket: %s\n", strerror(errno)); + gaim_connection_error(data->account->gc, _("Could not bind socket to port")); + return -1; + } + + /* Attempt to listen on the bound socket */ + if (listen(data->socket, 10) != 0) + { + gaim_debug_error("bonjour", "Cannot listen on socket: %s\n", strerror(errno)); + gaim_connection_error(data->account->gc, _("Could not listen on socket")); + return -1; + } + +#if 0 + /* TODO: Why isn't this being used? */ + data->socket = gaim_network_listen(data->port, SOCK_STREAM); + + if (data->socket == -1) + { + gaim_debug_error("bonjour", "No se ha podido crear el socket\n"); + } +#endif + + /* Open a watcher in the socket we have just opened */ + data->watcher_id = gaim_input_add(data->socket, GAIM_INPUT_READ, _server_socket_handler, data); + + return data->port; +} + +int +bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body) +{ + xmlnode *message_node = NULL; + gchar *message = NULL; + gint message_length = -1; + xmlnode *message_body_node = NULL; + xmlnode *message_html_node = NULL; + xmlnode *message_html_body_node = NULL; + xmlnode *message_html_body_font_node = NULL; + xmlnode *message_x_node = NULL; + GaimBuddy *gb = NULL; + BonjourBuddy *bb = NULL; + char *conv_message = NULL; + GaimConversation *conversation = NULL; + char *message_from_ui = NULL; + char *stripped_message = NULL; + + gb = gaim_find_buddy(data->account, to); + if (gb == NULL) + /* You can not send a message to an offline buddy */ + return -10000; + + bb = (BonjourBuddy *)gb->proto_data; + + /* Enclose the message from the UI within a "font" node */ + message_body_node = xmlnode_new("body"); + stripped_message = gaim_markup_strip_html(body); + xmlnode_insert_data(message_body_node, stripped_message, strlen(stripped_message)); + + message_from_ui = g_strconcat("<font>", body, "</font>", NULL); + message_html_body_font_node = xmlnode_from_str(message_from_ui, strlen(message_from_ui)); + + message_html_body_node = xmlnode_new("body"); + xmlnode_insert_child(message_html_body_node, message_html_body_font_node); + + message_html_node = xmlnode_new("html"); + xmlnode_set_attrib(message_html_node, "xmlns", "http://www.w3.org/1999/xhtml"); + xmlnode_insert_child(message_html_node, message_html_body_node); + + message_x_node = xmlnode_new("x"); + xmlnode_set_attrib(message_x_node, "xmlns", "jabber:x:event"); + xmlnode_insert_child(message_x_node, xmlnode_new("composing")); + + message_node = xmlnode_new("message"); + xmlnode_set_attrib(message_node, "to", ((BonjourBuddy*)(gb->proto_data))->name); + xmlnode_set_attrib(message_node, "from", data->account->username); + xmlnode_set_attrib(message_node, "type", "chat"); + xmlnode_insert_child(message_node, message_body_node); + xmlnode_insert_child(message_node, message_html_node); + xmlnode_insert_child(message_node, message_x_node); + + message = xmlnode_to_str(message_node, &message_length); + + /* Check if there is a previously open conversation */ + if (bb->conversation == NULL) + { + bb->conversation = g_new(BonjourJabberConversation, 1); + bb->conversation->socket = _connect_to_buddy(gb); + bb->conversation->stream_started = FALSE; + bb->conversation->buddy_name = g_strdup(gb->name); + bb->conversation->watcher_id = gaim_input_add(bb->conversation->socket, + GAIM_INPUT_READ, _client_socket_handler, gb); + } + + /* Check if the stream for the conversation has been started */ + if (bb->conversation->stream_started == FALSE) + { + /* Start the stream */ + if (send(bb->conversation->socket, DOCTYPE, strlen(DOCTYPE), 0) == -1) + { + gaim_debug_error("bonjour", "Unable to start a conversation\n"); + gaim_debug_warning("bonjour", "send error: %s\n", strerror(errno)); + conv_message = g_strdup(_("Unable to send the message, the conversation couldn't be started.")); + conversation = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, bb->name, data->account); + gaim_conversation_write(conversation, NULL, conv_message, GAIM_MESSAGE_SYSTEM, time(NULL)); + close(bb->conversation->socket); + gaim_input_remove(bb->conversation->watcher_id); + + /* Free all the data related to the conversation */ + g_free(bb->conversation->buddy_name); + g_free(bb->conversation); + bb->conversation = NULL; + return 0; + } + + bb->conversation->stream_started = TRUE; + } + + /* Send the message */ + if (_send_data(bb->conversation->socket, message) == -1) + return -10000; + + return 1; +} + +void +bonjour_jabber_close_conversation(BonjourJabber *data, GaimBuddy *gb) +{ + BonjourBuddy *bb = (BonjourBuddy*)gb->proto_data; + + if (bb->conversation != NULL) + { + /* Send the end of the stream to the other end of the conversation */ + send(bb->conversation->socket, STREAM_END, strlen(STREAM_END), 0); + + /* Close the socket and remove the watcher */ + close(bb->conversation->socket); + gaim_input_remove(bb->conversation->watcher_id); + + /* Free all the data related to the conversation */ + g_free(bb->conversation->buddy_name); + g_free(bb->conversation); + bb->conversation = NULL; + } +} + +void +bonjour_jabber_stop(BonjourJabber *data) +{ + GaimBuddy *gb = NULL; + BonjourBuddy *bb = NULL; + GSList *buddies; + GSList *l; + + /* Close the server socket and remove all the watcher */ + close(data->socket); + gaim_input_remove(data->watcher_id); + + /* Close all the sockets and remove all the watchers after sending end streams */ + if (data->account->gc != NULL) + { + buddies = gaim_find_buddies(data->account, data->account->username); + for (l = buddies; l; l = l->next) + { + gb = (GaimBuddy*)l->data; + bb = (BonjourBuddy*)gb->proto_data; + if (bb->conversation != NULL) + { + send(bb->conversation->socket, STREAM_END, strlen(STREAM_END), 0); + close(bb->conversation->socket); + gaim_input_remove(bb->conversation->watcher_id); + } + } + g_slist_free(buddies); + } +} diff --git a/libpurple/protocols/bonjour/jabber.h b/libpurple/protocols/bonjour/jabber.h new file mode 100644 index 0000000000..efdc9dcb5e --- /dev/null +++ b/libpurple/protocols/bonjour/jabber.h @@ -0,0 +1,65 @@ +/** + * @file jabber.h The Gaim interface to mDNS and peer to peer Jabber. + * + * gaim + * + * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * 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 + * + */ + +#ifndef _BONJOUR_JABBER_H_ +#define _BONJOUR_JABBER_H_ + +#include "account.h" + +#define STREAM_END "</stream:stream>" +#define DOCTYPE "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<stream:stream xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\">" + +typedef struct _BonjourJabber +{ + gint port; + gint socket; + gint watcher_id; + GaimAccount* account; +} BonjourJabber; + +typedef struct _BonjourJabberConversation +{ + gint socket; + gint watcher_id; + gchar* buddy_name; + gboolean stream_started; + gint message_id; +} BonjourJabberConversation; + +/** + * Start listening for jabber connections. + * + * @return -1 if there was a problem, else returns the listening + * port number. + */ +gint bonjour_jabber_start(BonjourJabber *data); + +int bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body); + +void bonjour_jabber_close_conversation(BonjourJabber *data, GaimBuddy *gb); + +void bonjour_jabber_stop(BonjourJabber *data); + +#endif /* _BONJOUR_JABBER_H_ */ diff --git a/libpurple/protocols/bonjour/messages.txt b/libpurple/protocols/bonjour/messages.txt new file mode 100644 index 0000000000..081d2f8989 --- /dev/null +++ b/libpurple/protocols/bonjour/messages.txt @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<stream:stream xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" > +<message to="yo@chloe" type="chat"><body>hola</body><html xmlns="html://www.w3.org/1999/xhtml"><body ichatballoncolor="#111111" ichattextcolor="#000000"><font face="Courier" ABSZ="3">hola</font></body></html><x xmlns="jabber:x:event"><composing /></x></message> +</stream:stream> |