view libpurple/protocols/bonjour/jabber.c @ 21696:fded60f269bc

Don't advertise our presence in avahi on IPv6 or listen for sevices since we don't support receiving connections from or connecting to IPv6 buddies. If someone needs to do that, they can submit a patch. Fixes #4188.
author Daniel Atallah <daniel.atallah@gmail.com>
date Fri, 30 Nov 2007 21:29:18 +0000
parents 919338d399ae
children d4c01ceb50a1
line wrap: on
line source

/*
 * purple - Bonjour Protocol Plugin
 *
 * Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 */

#include "internal.h"

#ifndef _WIN32
#include <net/if.h>
#include <sys/ioctl.h>
#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 <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 "parser.h"
#include "bonjour.h"
#include "buddy.h"
#include "bonjour_ft.h"

#ifdef _SIZEOF_ADDR_IFREQ
#  define HX_SIZE_OF_IFREQ(a) _SIZEOF_ADDR_IFREQ(a)
#else
#  define HX_SIZE_OF_IFREQ(a) sizeof(a)
#endif

#define STREAM_END "</stream:stream>"
/* TODO: specify version='1.0' and send stream features */
#define DOCTYPE "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" \
		"<stream:stream xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\" from=\"%s\" to=\"%s\">"

static void
xep_iq_parse(xmlnode *packet, PurpleConnection *connection, PurpleBuddy *pb);

#if 0 /* this isn't used anywhere... */
static const char *
_font_size_purple_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 BonjourJabberConversation *
bonjour_jabber_conv_new(PurpleBuddy *pb) {

	BonjourJabberConversation *bconv = g_new0(BonjourJabberConversation, 1);
	bconv->socket = -1;
	bconv->tx_buf = purple_circ_buffer_new(512);
	bconv->tx_handler = 0;
	bconv->rx_handler = 0;
	bconv->pb = pb;

	return bconv;
}


static const char *
_font_size_ichat_to_purple(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(xmlnode *message_node, PurpleBuddy *pb)
{
	xmlnode *body_node, *html_node, *events_node;
	PurpleConnection *gc = pb->account->gc;
	char *body, *html_body = NULL;
	const char *ichat_balloon_color = NULL;
	const char *ichat_text_color = NULL;
	const char *font_face = NULL;
	const char *font_size = NULL;
	const char *font_color = NULL;
	gboolean composing_event = FALSE;

	body_node = xmlnode_get_child(message_node, "body");
	if (body_node == NULL)
		return;
	body = xmlnode_get_data(body_node);

	html_node = xmlnode_get_child(message_node, "html");
	if (html_node != NULL)
	{
		xmlnode *html_body_node;

		html_body_node = xmlnode_get_child(html_node, "body");
		if (html_body_node != NULL)
		{
			xmlnode *html_body_font_node;

			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_purple(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 Purple creates */
					html_body = xmlnode_to_str(html_body_font_node, NULL);
			}
		}
	}

	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 */
			/* TODO: Deal with typing notification */
			g_free(body);
			g_free(html_body);
			return;
		}
	}

	/* Compose the message */
	if (html_body != NULL)
	{
		g_free(body);

		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_strdup_printf("<font face='%s' size='%s' color='%s' back='%s'>%s</font>",
				       font_face, font_size, ichat_text_color, ichat_balloon_color,
				       html_body);
	}

	/* TODO: Should we do something with "composing_event" here? */

	/* Send the message to the UI */
	serv_got_im(gc, pb->name, body, 0, time(NULL));

	g_free(body);
	g_free(html_body);
}

struct _check_buddy_by_address_t {
	const char *address;
	PurpleBuddy **pb;
	BonjourJabber *bj;
};

static void
_check_buddy_by_address(gpointer key, gpointer value, gpointer data)
{
	PurpleBuddy *pb = value;
	BonjourBuddy *bb;
	struct _check_buddy_by_address_t *cbba = data;

	/*
	 * If the current PurpleBuddy's data is not null and the PurpleBuddy'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 == pb->account)
	{
		bb = pb->proto_data;
		if ((bb != NULL) && (g_ascii_strcasecmp(bb->ip, cbba->address) == 0))
			*(cbba->pb) = pb;
	}
}

static void
_send_data_write_cb(gpointer data, gint source, PurpleInputCondition cond)
{
	PurpleBuddy *pb = data;
	BonjourBuddy *bb = pb->proto_data;
	BonjourJabberConversation *bconv = bb->conversation;
	int ret, writelen;

	/* TODO: Make sure that the stream has been established before sending */

	writelen = purple_circ_buffer_get_max_read(bconv->tx_buf);

	if (writelen == 0) {
		purple_input_remove(bconv->tx_handler);
		bconv->tx_handler = 0;
		return;
	}

	ret = send(bconv->socket, bconv->tx_buf->outptr, writelen, 0);

	if (ret < 0 && errno == EAGAIN)
		return;
	else if (ret <= 0) {
		PurpleConversation *conv;
		const char *error = g_strerror(errno);

		purple_debug_error("bonjour", "Error sending message to buddy %s error: %s\n",
				   purple_buddy_get_name(pb), error ? error : "(null)");

		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
		if (conv != NULL)
			purple_conversation_write(conv, NULL,
				  _("Unable to send message."),
				  PURPLE_MESSAGE_SYSTEM, time(NULL));

		bonjour_jabber_close_conversation(bb->conversation);
		bb->conversation = NULL;
		return;
	}

	purple_circ_buffer_mark_read(bconv->tx_buf, ret);
}

static gint
_send_data(PurpleBuddy *pb, char *message)
{
	gint ret;
	int len = strlen(message);
	BonjourBuddy *bb = pb->proto_data;
	BonjourJabberConversation *bconv = bb->conversation;

	/* If we're not ready to actually send, append it to the buffer */
	if (bconv->tx_handler != 0
			|| bconv->connect_data != NULL
			|| !bconv->sent_stream_start
			|| !bconv->recv_stream_start
			|| purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) {
		ret = -1;
		errno = EAGAIN;
	} else {
		ret = send(bconv->socket, message, len, 0);
	}

	if (ret == -1 && errno == EAGAIN)
		ret = 0;
	else if (ret <= 0) {
		PurpleConversation *conv;
		const char *error = g_strerror(errno);

		purple_debug_error("bonjour", "Error sending message to buddy %s error: %s\n",
				   purple_buddy_get_name(pb), error ? error : "(null)");

		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
		if (conv != NULL)
			purple_conversation_write(conv, NULL,
				  _("Unable to send message."),
				  PURPLE_MESSAGE_SYSTEM, time(NULL));

		bonjour_jabber_close_conversation(bb->conversation);
		bb->conversation = NULL;
		return -1;
	}

	if (ret < len) {
		if (bconv->tx_handler == 0)
			bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE,
				_send_data_write_cb, pb);
		purple_circ_buffer_append(bconv->tx_buf, message + ret, len - ret);
	}

	return ret;
}

void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet) {

	g_return_if_fail(packet != NULL);

	if (!strcmp(packet->name, "message"))
		_jabber_parse_and_write_message_to_ui(packet, pb);
	else if(!strcmp(packet->name, "iq"))
		xep_iq_parse(packet, NULL, pb);
	else
		purple_debug_warning("bonjour", "Unknown packet: %s\n", packet->name ? packet->name : "(null)");
}


static void
_client_socket_handler(gpointer data, gint socket, PurpleInputCondition condition)
{
	PurpleBuddy *pb = data;
	gint len, message_length;
	static char message[4096];

	/*TODO: use a static buffer */

	/* Read the data from the socket */
	if ((len = recv(socket, message, sizeof(message) - 1, 0)) == -1) {
		/* There have been an error reading from the socket */
		if (errno != EAGAIN) {
			BonjourBuddy *bb = pb->proto_data;
			const char *err = g_strerror(errno);

			purple_debug_warning("bonjour", "receive error: %s\n", err ? err : "(null)");

			bonjour_jabber_close_conversation(bb->conversation);
			bb->conversation = NULL;

			/* I guess we really don't need to notify the user.
			 * If they try to send another message it'll reconnect */
		}
		return;
	} else if (len == 0) { /* The other end has closed the socket */
		purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", pb->name ? pb->name : "(null)");
		bonjour_jabber_stream_ended(pb);
		return;
	} else {
		message_length = len;
		message[message_length] = '\0';

		while (message_length > 0 && g_ascii_iscntrl(message[message_length - 1])) {
			message[message_length - 1] = '\0';
			message_length--;
		}
	}

	purple_debug_info("bonjour", "Receive: -%s- %d bytes\n", message, len);

	bonjour_parser_process(pb, message, message_length);
}

void bonjour_jabber_stream_ended(PurpleBuddy *pb) {
	BonjourBuddy *bb = pb->proto_data;

	purple_debug_info("bonjour", "Recieved conversation close notification from %s.\n", pb->name);

	g_return_if_fail(bb != NULL);

	/* Inform the user that the conversation has been closed */
	if (bb->conversation != NULL) {
#if 0
		PurpleConversation *conv;
		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, pb->account);
		if (conv != NULL) {
			char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name);
			purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
			g_free(tmp);
		}
#endif
		/* Close the socket, clear the watcher and free memory */
		bonjour_jabber_close_conversation(bb->conversation);
		bb->conversation = NULL;
	}
}

void bonjour_jabber_stream_started(PurpleBuddy *pb) {
	BonjourBuddy *bb = pb->proto_data;
	BonjourJabberConversation *bconv = bb->conversation;

	/* If the stream has been completely started, we can start doing stuff */
	if (bconv->sent_stream_start && bconv->recv_stream_start && purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) {
		/* Watch for when we can write the buffered messages */
		bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE,
			_send_data_write_cb, pb);
		/* We can probably write the data right now. */
		_send_data_write_cb(pb, bconv->socket, PURPLE_INPUT_WRITE);
	}

}

struct _stream_start_data {
	char *msg;
};


static void
_start_stream(gpointer data, gint source, PurpleInputCondition condition)
{
	PurpleBuddy *pb = data;
	BonjourBuddy *bb = pb->proto_data;
	BonjourJabberConversation *bconv = bb->conversation;
	struct _stream_start_data *ss = bconv->stream_data;
	int len, ret;

	len = strlen(ss->msg);

	/* Start Stream */
	ret = send(source, ss->msg, len, 0);

	if (ret == -1 && errno == EAGAIN)
		return;
	else if (ret <= 0) {
		const char *err = g_strerror(errno);
		PurpleConversation *conv;

		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");

		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
		if (conv != NULL)
			purple_conversation_write(conv, NULL,
				  _("Unable to send the message, the conversation couldn't be started."),
				  PURPLE_MESSAGE_SYSTEM, time(NULL));

		bonjour_jabber_close_conversation(bconv);
		bb->conversation = NULL;

		return;
	}

	/* This is EXTREMELY unlikely to happen */
	if (ret < len) {
		char *tmp = g_strdup(ss->msg + ret);
		g_free(ss->msg);
		ss->msg = tmp;
		return;
	}

	g_free(ss->msg);
	g_free(ss);
	bconv->stream_data = NULL;

	/* Stream started; process the send buffer if there is one */
	purple_input_remove(bconv->tx_handler);
	bconv->tx_handler= 0;
	bconv->sent_stream_start = TRUE;

	bonjour_jabber_stream_started(pb);

}

static gboolean bonjour_jabber_stream_init(PurpleBuddy *pb, int client_socket)
{
	int ret, len;
	char *stream_start;
	BonjourBuddy *bb = pb->proto_data;

	stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account),
								   purple_buddy_get_name(pb));
	len = strlen(stream_start);

	/* Start the stream */
	ret = send(client_socket, stream_start, len, 0);

	if (ret == -1 && errno == EAGAIN)
		ret = 0;
	else if (ret <= 0) {
		const char *err = g_strerror(errno);

		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");

		close(client_socket);
		g_free(stream_start);

		return FALSE;
	}

	/* This is unlikely to happen */
	if (ret < len) {
		struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
		ss->msg = g_strdup(stream_start + ret);
		bb->conversation->stream_data = ss;
		/* Finish sending the stream start */
		bb->conversation->tx_handler = purple_input_add(client_socket,
			PURPLE_INPUT_WRITE, _start_stream, pb);
	} else
		bb->conversation->sent_stream_start = TRUE;

	g_free(stream_start);

	/* setup the parser fresh for each stream */
	bonjour_parser_setup(bb->conversation);

	bb->conversation->socket = client_socket;
	bb->conversation->rx_handler = purple_input_add(client_socket,
		PURPLE_INPUT_READ, _client_socket_handler, pb);

	return TRUE;
}

static void
_server_socket_handler(gpointer data, int server_socket, PurpleInputCondition condition)
{
	PurpleBuddy *pb = NULL;
	struct sockaddr_in their_addr; /* connector's address information */
	socklen_t sin_size = sizeof(struct sockaddr);
	int client_socket;
	int flags;
	BonjourBuddy *bb;
	char *address_text = NULL;
	PurpleBuddyList *bl = purple_get_blist();
	struct _check_buddy_by_address_t *cbba;

	/* Check that it is a read condition */
	if (condition != PURPLE_INPUT_READ)
		return;

	if ((client_socket = accept(server_socket, (struct sockaddr *)&their_addr, &sin_size)) == -1)
		return;

	flags = fcntl(client_socket, F_GETFL);
	fcntl(client_socket, F_SETFL, flags | O_NONBLOCK);

	/* Look for the buddy that has opened the conversation and fill information */
	address_text = inet_ntoa(their_addr.sin_addr);
	purple_debug_info("bonjour", "Received incoming connection from %s.\n", address_text);
	cbba = g_new0(struct _check_buddy_by_address_t, 1);
	cbba->address = address_text;
	cbba->pb = &pb;
	cbba->bj = data;
	g_hash_table_foreach(bl->buddies, _check_buddy_by_address, cbba);
	g_free(cbba);
	if (pb == NULL)
	{
		purple_debug_info("bonjour", "We don't like invisible buddies, this is not a superheros comic\n");
		close(client_socket);
		return;
	}
	bb = pb->proto_data;

	/* Check if the conversation has been previously started */
	if (bb->conversation == NULL)
	{
		bb->conversation = bonjour_jabber_conv_new(pb);

		if (!bonjour_jabber_stream_init(pb, client_socket)) {
			close(client_socket);
			return;
		}

	} else {
		purple_debug_warning("bonjour", "Ignoring incoming connection because an existing connection exists.\n");
		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)
	{
		purple_debug_error("bonjour", "Cannot open socket: %s\n", g_strerror(errno));
		purple_connection_error_reason (data->account->gc,
			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
			_("Cannot open socket"));
		return -1;
	}

	/* Make the socket reusable */
	if (setsockopt(data->socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != 0)
	{
		purple_debug_error("bonjour", "Error setting socket options: %s\n", g_strerror(errno));
		purple_connection_error_reason (data->account->gc,
			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
			_("Error setting socket options"));
		return -1;
	}

	memset(&my_addr, 0, sizeof(struct sockaddr_in));
	my_addr.sin_family = AF_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)
	{
		purple_debug_error("bonjour", "Cannot bind socket: %s\n", g_strerror(errno));
		purple_connection_error_reason (data->account->gc,
			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
			_("Could not bind socket to port"));
		return -1;
	}

	/* Attempt to listen on the bound socket */
	if (listen(data->socket, 10) != 0)
	{
		purple_debug_error("bonjour", "Cannot listen on socket: %s\n", g_strerror(errno));
		purple_connection_error_reason (data->account->gc,
			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
			_("Could not listen on socket"));
		return -1;
	}

#if 0
	/* TODO: Why isn't this being used? */
	data->socket = purple_network_listen(data->port, SOCK_STREAM);

	if (data->socket == -1)
	{
		purple_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 = purple_input_add(data->socket, PURPLE_INPUT_READ, _server_socket_handler, data);

	return data->port;
}

static void
_connected_to_buddy(gpointer data, gint source, const gchar *error)
{
	PurpleBuddy *pb = data;
	BonjourBuddy *bb = pb->proto_data;

	bb->conversation->connect_data = NULL;

	if (source < 0) {
		PurpleConversation *conv;

		purple_debug_error("bonjour", "Error connecting to buddy %s at %s:%d error: %s\n",
				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, error ? error : "(null)");

		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
		if (conv != NULL)
			purple_conversation_write(conv, NULL,
				  _("Unable to send the message, the conversation couldn't be started."),
				  PURPLE_MESSAGE_SYSTEM, time(NULL));

		bonjour_jabber_close_conversation(bb->conversation);
		bb->conversation = NULL;
		return;
	}

	if (!bonjour_jabber_stream_init(pb, source)) {
		const char *err = g_strerror(errno);
		PurpleConversation *conv;

		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");

		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
		if (conv != NULL)
			purple_conversation_write(conv, NULL,
				  _("Unable to send the message, the conversation couldn't be started."),
				  PURPLE_MESSAGE_SYSTEM, time(NULL));

		close(source);
		bonjour_jabber_close_conversation(bb->conversation);
		bb->conversation = NULL;
		return;
	}
}

static PurpleBuddy *
_find_or_start_conversation(BonjourJabber *data, const gchar *to)
{
	PurpleBuddy *pb = NULL;
	BonjourBuddy *bb = NULL;

	g_return_val_if_fail(data != NULL, NULL);
	g_return_val_if_fail(to != NULL, NULL);

	pb = purple_find_buddy(data->account, to);
	if (pb == NULL || pb->proto_data == NULL)
		/* You can not send a message to an offline buddy */
		return NULL;

	bb = (BonjourBuddy *) pb->proto_data;

	/* Check if there is a previously open conversation */
	if (bb->conversation == NULL)
	{
		PurpleProxyConnectData *connect_data;
		PurpleProxyInfo *proxy_info;

		purple_debug_info("bonjour", "Starting conversation with %s\n", to);

		/* Make sure that the account always has a proxy of "none".
		 * This is kind of dirty, but proxy_connect_none() isn't exposed. */
		proxy_info = purple_account_get_proxy_info(data->account);
		if (proxy_info == NULL) {
			proxy_info = purple_proxy_info_new();
			purple_account_set_proxy_info(data->account, proxy_info);
		}
		purple_proxy_info_set_type(proxy_info, PURPLE_PROXY_NONE);

		connect_data = purple_proxy_connect(data->account->gc, data->account,
						    bb->ip, bb->port_p2pj, _connected_to_buddy, pb);

		if (connect_data == NULL) {
			purple_debug_error("bonjour", "Unable to connect to buddy (%s).\n", to);
			return NULL;
		}

		bb->conversation = bonjour_jabber_conv_new(pb);
		bb->conversation->connect_data = connect_data;
		/* We don't want _send_data() to register the tx_handler;
		 * that neeeds to wait until we're actually connected. */
		bb->conversation->tx_handler = 0;
	}
	return pb;
}

int
bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body)
{
	xmlnode *message_node, *node, *node2;
	gchar *message;
	PurpleBuddy *pb;
	BonjourBuddy *bb;
	int ret;

	pb = _find_or_start_conversation(data, to);
	if (pb == NULL) {
		purple_debug_info("bonjour", "Can't send a message to an offline buddy (%s).\n", to);
		/* You can not send a message to an offline buddy */
		return -10000;
	}

	bb = pb->proto_data;

	message_node = xmlnode_new("message");
	xmlnode_set_attrib(message_node, "to", bb->name);
	xmlnode_set_attrib(message_node, "from", purple_account_get_username(data->account));
	xmlnode_set_attrib(message_node, "type", "chat");

	/* Enclose the message from the UI within a "font" node */
	node = xmlnode_new_child(message_node, "body");
	message = purple_markup_strip_html(body);
	xmlnode_insert_data(node, message, strlen(message));
	g_free(message);

	node = xmlnode_new_child(message_node, "html");
	xmlnode_set_namespace(node, "http://www.w3.org/1999/xhtml");

	node = xmlnode_new_child(node, "body");
	message = g_strdup_printf("<font>%s</font>", body);
	node2 = xmlnode_from_str(message, strlen(message));
	g_free(message);
	xmlnode_insert_child(node, node2);

	node = xmlnode_new_child(message_node, "x");
	xmlnode_set_namespace(node, "jabber:x:event");
	xmlnode_insert_child(node, xmlnode_new("composing"));

	message = xmlnode_to_str(message_node, NULL);
	xmlnode_free(message_node);

	ret = _send_data(pb, message) >= 0;

	g_free(message);

	return ret;
}

void
bonjour_jabber_close_conversation(BonjourJabberConversation *bconv)
{
	if (bconv != NULL) {
		GList *xfers, *tmp_next;
		BonjourData *bd = bconv->pb->account->gc->proto_data;

		/* Cancel any file transfers that are waiting to begin */
		xfers = bd->xfer_lists;
		while(xfers != NULL) {
			PurpleXfer *xfer = xfers->data;
			tmp_next = xfers->next;
			/* We only need to cancel this if it hasn't actually started transferring. */
			/* This will change if we ever support IBB transfers. */
			if (strcmp(xfer->who, bconv->pb->name) == 0
					&& (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_NOT_STARTED
					    || purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_UNKNOWN)) {
				purple_xfer_cancel_remote(xfer);
			}
			xfers = tmp_next;
		}

		/* Close the socket and remove the watcher */
		if (bconv->socket >= 0) {
			/* Send the end of the stream to the other end of the conversation */
			if (bconv->sent_stream_start)
				send(bconv->socket, STREAM_END, strlen(STREAM_END), 0);
			/* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */
			close(bconv->socket);
		}
		if (bconv->rx_handler != 0)
			purple_input_remove(bconv->rx_handler);
		if (bconv->tx_handler > 0)
			purple_input_remove(bconv->tx_handler);

		/* Free all the data related to the conversation */
		purple_circ_buffer_destroy(bconv->tx_buf);
		if (bconv->connect_data != NULL)
			purple_proxy_connect_cancel(bconv->connect_data);
		if (bconv->stream_data != NULL) {
			struct _stream_start_data *ss = bconv->stream_data;
			g_free(ss->msg);
			g_free(ss);
		}

		if (bconv->context != NULL)
			bonjour_parser_setup(bconv);

		g_free(bconv);
	}
}

void
bonjour_jabber_stop(BonjourJabber *data)
{
	/* Close the server socket and remove the watcher */
	if (data->socket >= 0)
		close(data->socket);
	if (data->watcher_id > 0)
		purple_input_remove(data->watcher_id);

	/* Close all the conversation sockets and remove all the watchers after sending end streams */
	if (data->account->gc != NULL)
	{
		GSList *buddies, *l;

		buddies = purple_find_buddies(data->account, purple_account_get_username(data->account));
		for (l = buddies; l; l = l->next) {
			BonjourBuddy *bb = ((PurpleBuddy*) l->data)->proto_data;
			bonjour_jabber_close_conversation(bb->conversation);
			bb->conversation = NULL;
		}

		g_slist_free(buddies);
	}
}

XepIq *
xep_iq_new(void *data, XepIqType type, const char *to, const char *from, const char *id)
{
	xmlnode *iq_node = NULL;
	XepIq *iq = NULL;

	g_return_val_if_fail(data != NULL, NULL);
	g_return_val_if_fail(to != NULL, NULL);
	g_return_val_if_fail(id != NULL, NULL);

	iq_node = xmlnode_new("iq");

	xmlnode_set_attrib(iq_node, "to", to);
	xmlnode_set_attrib(iq_node, "from", from);
	xmlnode_set_attrib(iq_node, "id", id);
	switch (type) {
		case XEP_IQ_SET:
			xmlnode_set_attrib(iq_node, "type", "set");
			break;
		case XEP_IQ_GET:
			xmlnode_set_attrib(iq_node, "type", "get");
			break;
		case XEP_IQ_RESULT:
			xmlnode_set_attrib(iq_node, "type", "result");
			break;
		case XEP_IQ_ERROR:
			xmlnode_set_attrib(iq_node, "type", "error");
			break;
		case XEP_IQ_NONE:
		default:
			xmlnode_set_attrib(iq_node, "type", "none");
			break;
	}

	iq = g_new0(XepIq, 1);
	iq->node = iq_node;
	iq->type = type;
	iq->data = ((BonjourData*)data)->jabber_data;
	iq->to = (char*)to;

	return iq;
}

static gboolean
check_if_blocked(PurpleBuddy *pb)
{
	gboolean blocked = FALSE;
	GSList *l = NULL;
	PurpleAccount *acc = NULL;

	if(pb == NULL)
		return FALSE;

	acc = pb->account;

	for(l = acc->deny; l != NULL; l = l->next) {
		if(!purple_utf8_strcasecmp(pb->name, (char *)l->data)) {
			purple_debug_info("bonjour", "%s has been blocked.\n", pb->name, acc->username);
			blocked = TRUE;
			break;
		}
	}
	return blocked;
}

static void
xep_iq_parse(xmlnode *packet, PurpleConnection *connection, PurpleBuddy *pb)
{
	xmlnode *child = NULL;

	if(packet == NULL || pb == NULL)
		return;

	if(connection == NULL) {
		if(pb->account != NULL)
			connection = (pb->account)->gc;
	}

	if(check_if_blocked(pb))
		return;

	if ((child = xmlnode_get_child(packet, "si")) || (child = xmlnode_get_child(packet, "error")))
		xep_si_parse(connection, packet, pb);
	else
		xep_bytestreams_parse(connection, packet, pb);
}

int
xep_iq_send_and_free(XepIq *iq)
{
	int ret = -1;
	PurpleBuddy *pb = NULL;

	/* start the talk, reuse the message socket  */
	pb = _find_or_start_conversation ((BonjourJabber*)iq->data, iq->to);
	/* Send the message */
	if (pb != NULL) {
		/* Convert xml node into stream */
		gchar *msg = xmlnode_to_str(iq->node, NULL);
		ret = _send_data(pb, msg);
		g_free(msg);
	}

	xmlnode_free(iq->node);
	iq->node = NULL;
	g_free(iq);

	return (ret >= 0) ? 0 : -1;
}

/* This returns a ';' delimited string containing all non-localhost IPs */
const char *
purple_network_get_my_ip_ext2(int fd)
{
	char buffer[1024];
	static char ip_ext[17 * 10];
	char *tmp;
	char *tip;
	struct ifconf ifc;
	struct ifreq *ifr;
	struct sockaddr_in *sinptr;
	guint32 lhost = htonl(127 * 256 * 256 * 256 + 1);
	long unsigned int add;
	int source = fd;
	int len, count = 0;

	if (fd < 0)
		source = socket(PF_INET, SOCK_STREAM, 0);

	ifc.ifc_len = sizeof(buffer);
	ifc.ifc_req = (struct ifreq *)buffer;
	ioctl(source, SIOCGIFCONF, &ifc);

	if (fd < 0)
		close(source);

	memset(ip_ext, 0, sizeof(ip_ext));
	memcpy(ip_ext, "0.0.0.0", 7);
	tmp = buffer;
	tip = ip_ext;
	while (tmp < buffer + ifc.ifc_len && count < 10)
	{
		ifr = (struct ifreq *)tmp;
		tmp += HX_SIZE_OF_IFREQ(*ifr);

		if (ifr->ifr_addr.sa_family == AF_INET)
		{
			sinptr = (struct sockaddr_in *)&ifr->ifr_addr;
			if (sinptr->sin_addr.s_addr != lhost)
			{
				add = ntohl(sinptr->sin_addr.s_addr);
				len = g_snprintf(tip, 17, "%lu.%lu.%lu.%lu;",
					((add >> 24) & 255),
					((add >> 16) & 255),
					((add >> 8) & 255),
					add & 255);
				tip = &tip[len];
				count++;
				continue;
			}
		}
	}

	return ip_ext;
}