summaryrefslogtreecommitdiff
path: root/libpurple/protocols/bonjour
diff options
context:
space:
mode:
Diffstat (limited to 'libpurple/protocols/bonjour')
-rw-r--r--libpurple/protocols/bonjour/Makefile.am42
-rw-r--r--libpurple/protocols/bonjour/Makefile.mingw86
-rw-r--r--libpurple/protocols/bonjour/bonjour.c600
-rw-r--r--libpurple/protocols/bonjour/bonjour.h51
-rw-r--r--libpurple/protocols/bonjour/buddy.c167
-rw-r--r--libpurple/protocols/bonjour/buddy.h66
-rw-r--r--libpurple/protocols/bonjour/dns_sd.c387
-rw-r--r--libpurple/protocols/bonjour/dns_sd.h81
-rw-r--r--libpurple/protocols/bonjour/issues.txt18
-rw-r--r--libpurple/protocols/bonjour/jabber.c667
-rw-r--r--libpurple/protocols/bonjour/jabber.h65
-rw-r--r--libpurple/protocols/bonjour/messages.txt4
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>