Sat, 26 Oct 2019 16:53:30 +0000
Merged in bonjour-rename-xmpp (pull request #610)
Rename Jabber to XMPP in Bonjour
Approved-by: Elliott Sales de Andrade
Approved-by: Gary Kramlich
Approved-by: Eion Robb
| libpurple/protocols/bonjour/jabber.c | file | annotate | diff | comparison | revisions | |
| libpurple/protocols/bonjour/jabber.h | file | annotate | diff | comparison | revisions | |
| po/POTFILES.in | file | annotate | diff | comparison | revisions |
--- a/libpurple/protocols/bonjour/bonjour.c Fri Oct 25 05:52:30 2019 -0400 +++ b/libpurple/protocols/bonjour/bonjour.c Sat Oct 26 16:53:30 2019 +0000 @@ -35,9 +35,9 @@ #include "bonjour.h" #include "mdns_common.h" -#include "jabber.h" #include "buddy.h" #include "bonjour_ft.h" +#include "xmpp.h" static PurpleProtocol *my_protocol = NULL; @@ -104,12 +104,12 @@ bd = g_new0(BonjourData, 1); purple_connection_set_protocol_data(gc, bd); - /* Start waiting for jabber connections (iChat style) */ - bd->jabber_data = g_new0(BonjourJabber, 1); - bd->jabber_data->port = purple_account_get_int(account, "port", BONJOUR_DEFAULT_PORT); - bd->jabber_data->account = account; + /* Start waiting for xmpp connections (iChat style) */ + bd->xmpp_data = g_new0(BonjourXMPP, 1); + bd->xmpp_data->port = purple_account_get_int(account, "port", BONJOUR_DEFAULT_PORT); + bd->xmpp_data->account = account; - if (bonjour_jabber_start(bd->jabber_data) == -1) { + if (bonjour_xmpp_start(bd->xmpp_data) == -1) { /* Send a message about the connection error */ purple_connection_error (gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, @@ -121,7 +121,7 @@ bd->dns_sd_data = bonjour_dns_sd_new(); bd->dns_sd_data->first = g_strdup(purple_account_get_string(account, "first", default_firstname)); bd->dns_sd_data->last = g_strdup(purple_account_get_string(account, "last", default_lastname)); - bd->dns_sd_data->port_p2pj = bd->jabber_data->port; + bd->dns_sd_data->port_p2pj = bd->xmpp_data->port; /* Not engaged in AV conference */ bd->dns_sd_data->vc = g_strdup("!"); @@ -168,11 +168,11 @@ bonjour_dns_sd_free(bd->dns_sd_data); } - if (bd != NULL && bd->jabber_data != NULL) + if (bd != NULL && bd->xmpp_data != NULL) { /* Stop waiting for conversations */ - bonjour_jabber_stop(bd->jabber_data); - g_free(bd->jabber_data); + bonjour_xmpp_stop(bd->xmpp_data); + g_free(bd->xmpp_data); } /* Delete the bonjour group @@ -206,7 +206,7 @@ if (purple_message_is_empty(msg) || !purple_message_get_recipient(msg)) return 0; - return bonjour_jabber_send_message(bd->jabber_data, + return bonjour_xmpp_send_message(bd->xmpp_data, purple_message_get_recipient(msg), purple_message_get_contents(msg)); } @@ -319,7 +319,7 @@ return; } - bonjour_jabber_close_conversation(bb->conversation); + bonjour_xmpp_close_conversation(bb->conversation); bb->conversation = NULL; } @@ -448,7 +448,7 @@ new_group = purple_group_get_name(group); - g_list_foreach(moved_buddies, (GFunc)bonjour_do_group_change, new_group); + g_list_foreach(moved_buddies, (GFunc)bonjour_do_group_change, (gpointer)new_group); } static gboolean
--- a/libpurple/protocols/bonjour/bonjour.h Fri Oct 25 05:52:30 2019 -0400 +++ b/libpurple/protocols/bonjour/bonjour.h Sat Oct 26 16:53:30 2019 +0000 @@ -1,5 +1,5 @@ /** - * @file bonjour.h The Purple interface to mDNS and peer to peer Jabber. + * @file bonjour.h The Purple interface to mDNS and peer to peer XMPP. * * purple * @@ -32,7 +32,7 @@ #include <purple.h> #include "mdns_common.h" -#include "jabber.h" +#include "xmpp.h" #define BONJOUR_GROUP_NAME _("Bonjour") #define BONJOUR_PROTOCOL_NAME "bonjour" @@ -64,7 +64,7 @@ typedef struct { BonjourDnsSd *dns_sd_data; - BonjourJabber *jabber_data; + BonjourXMPP *xmpp_data; GSList *xfer_lists; gchar *jid; } BonjourData;
--- a/libpurple/protocols/bonjour/bonjour_ft.c Fri Oct 25 05:52:30 2019 -0400 +++ b/libpurple/protocols/bonjour/bonjour_ft.c Sat Oct 26 16:53:30 2019 +0000 @@ -80,7 +80,7 @@ return; } - iq = xep_iq_new(bd, XEP_IQ_ERROR, to, bonjour_get_jid(bd->jabber_data->account), id); + iq = xep_iq_new(bd, XEP_IQ_ERROR, to, bonjour_get_jid(bd->xmpp_data->account), id); if(iq == NULL) return; @@ -213,7 +213,7 @@ /* Assign stream id. */ g_free(xf->iq_id); xf->iq_id = g_strdup_printf("%u", next_id++); - iq = xep_iq_new(xf->data, XEP_IQ_SET, to, bonjour_get_jid(bd->jabber_data->account), xf->iq_id); + iq = xep_iq_new(xf->data, XEP_IQ_SET, to, bonjour_get_jid(bd->xmpp_data->account), xf->iq_id); if(iq == NULL) return; @@ -271,7 +271,7 @@ bd = xf->data; purple_debug_info("bonjour", "xep file transfer stream initialization result.\n"); - iq = xep_iq_new(bd, XEP_IQ_RESULT, to, bonjour_get_jid(bd->jabber_data->account), xf->iq_id); + iq = xep_iq_new(bd, XEP_IQ_RESULT, to, bonjour_get_jid(bd->xmpp_data->account), xf->iq_id); if(iq == NULL) return; @@ -904,7 +904,7 @@ bd = xf->data; - iq = xep_iq_new(bd, XEP_IQ_SET, purple_xfer_get_remote_user(xfer), bonjour_get_jid(bd->jabber_data->account), xf->sid); + iq = xep_iq_new(bd, XEP_IQ_SET, purple_xfer_get_remote_user(xfer), bonjour_get_jid(bd->xmpp_data->account), xf->sid); query = purple_xmlnode_new_child(iq->node, "query"); purple_xmlnode_set_namespace(query, "http://jabber.org/protocol/bytestreams"); @@ -913,7 +913,7 @@ purple_xfer_set_local_port(xfer, purple_network_get_port_from_fd(sock)); - local_ips = bonjour_jabber_get_local_ips(sock); + local_ips = bonjour_xmpp_get_local_ips(sock); port = g_strdup_printf("%hu", purple_xfer_get_local_port(xfer)); while(local_ips) { @@ -981,7 +981,7 @@ /* Here, start the file transfer.*/ /* Notify Initiator of Connection */ - iq = xep_iq_new(bd, XEP_IQ_RESULT, purple_xfer_get_remote_user(xfer), bonjour_get_jid(bd->jabber_data->account), xf->iq_id); + iq = xep_iq_new(bd, XEP_IQ_RESULT, purple_xfer_get_remote_user(xfer), bonjour_get_jid(bd->xmpp_data->account), xf->iq_id); q_node = purple_xmlnode_new_child(iq->node, "query"); purple_xmlnode_set_namespace(q_node, "http://jabber.org/protocol/bytestreams"); tmp_node = purple_xmlnode_new_child(q_node, "streamhost-used");
--- a/libpurple/protocols/bonjour/buddy.c Fri Oct 25 05:52:30 2019 -0400 +++ b/libpurple/protocols/bonjour/buddy.c Sat Oct 26 16:53:30 2019 +0000 @@ -258,7 +258,7 @@ g_free(buddy->node); g_free(buddy->ver); - bonjour_jabber_close_conversation(buddy->conversation); + bonjour_xmpp_close_conversation(buddy->conversation); buddy->conversation = NULL; /* Clean up any mdns implementation data */
--- a/libpurple/protocols/bonjour/buddy.h Fri Oct 25 05:52:30 2019 -0400 +++ b/libpurple/protocols/bonjour/buddy.h Sat Oct 26 16:53:30 2019 +0000 @@ -21,7 +21,7 @@ #include <purple.h> -#include "jabber.h" +#include "xmpp.h" typedef struct { @@ -45,7 +45,7 @@ gchar *node; gchar *ver; - BonjourJabberConversation *conversation; + BonjourXMPPConversation *conversation; gpointer mdns_impl_data; } BonjourBuddy;
--- a/libpurple/protocols/bonjour/jabber.c Fri Oct 25 05:52:30 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1472 +0,0 @@ -/* - * 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" -#include <purple.h> - -#ifndef _WIN32 -#include <net/if.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#endif -#include <sys/types.h> - -/* Solaris */ -#if defined (__SVR4) && defined (__sun) -#include <sys/sockio.h> -#endif - -#include <glib.h> -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <fcntl.h> - -#ifdef HAVE_GETIFADDRS -#include <ifaddrs.h> -#endif - -#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\">" - -enum sent_stream_start_types { - NOT_SENT = 0, - PARTIALLY_SENT = 1, - FULLY_SENT = 2 -}; - -static void -xep_iq_parse(PurpleXmlNode *packet, PurpleBuddy *pb); - -static BonjourJabberConversation * -bonjour_jabber_conv_new(PurpleBuddy *pb, PurpleAccount *account, const char *ip) { - - BonjourJabberConversation *bconv = g_new0(BonjourJabberConversation, 1); - bconv->cancellable = g_cancellable_new(); - bconv->tx_buf = purple_circular_buffer_new(512); - bconv->tx_handler = 0; - bconv->rx_handler = 0; - bconv->pb = pb; - bconv->account = account; - bconv->ip = g_strdup(ip); - - bonjour_parser_setup(bconv); - - 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 gchar * -get_xmlnode_contents(PurpleXmlNode *node) -{ - gchar *contents; - - contents = purple_xmlnode_to_str(node, NULL); - - /* we just want the stuff inside <font></font> - * There isn't stuff exposed in PurpleXmlNode.c to do this more cleanly. */ - - if (contents) { - char *bodystart = strchr(contents, '>'); - char *bodyend = bodystart ? strrchr(bodystart, '<') : NULL; - if (bodystart && bodyend && (bodystart + 1) != bodyend) { - *bodyend = '\0'; - memmove(contents, bodystart + 1, (bodyend - bodystart)); - } - } - - return contents; -} - -static void -_jabber_parse_and_write_message_to_ui(PurpleXmlNode *message_node, PurpleBuddy *pb) -{ - PurpleXmlNode *body_node, *html_node, *events_node; - PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(pb)); - gchar *body = NULL; - - body_node = purple_xmlnode_get_child(message_node, "body"); - html_node = purple_xmlnode_get_child(message_node, "html"); - - if (body_node == NULL && html_node == NULL) { - purple_debug_error("bonjour", "No body or html node found, discarding message.\n"); - return; - } - - events_node = purple_xmlnode_get_child_with_namespace(message_node, "x", "jabber:x:event"); - if (events_node != NULL) { - if (purple_xmlnode_get_child(events_node, "id") != NULL) { - /* The user is just typing */ - /* TODO: Deal with typing notification */ - return; - } - } - - if (html_node != NULL) { - PurpleXmlNode *html_body_node; - - html_body_node = purple_xmlnode_get_child(html_node, "body"); - if (html_body_node != NULL) { - PurpleXmlNode *html_body_font_node; - - html_body_font_node = purple_xmlnode_get_child(html_body_node, "font"); - /* Types of messages sent by iChat */ - if (html_body_font_node != NULL) { - gchar *html_body; - const char *font_face, *font_size, *font_color, - *ichat_balloon_color, *ichat_text_color; - - font_face = purple_xmlnode_get_attrib(html_body_font_node, "face"); - /* The absolute iChat font sizes should be converted to 1..7 range */ - font_size = purple_xmlnode_get_attrib(html_body_font_node, "ABSZ"); - if (font_size != NULL) - font_size = _font_size_ichat_to_purple(atoi(font_size)); - font_color = purple_xmlnode_get_attrib(html_body_font_node, "color"); - ichat_balloon_color = purple_xmlnode_get_attrib(html_body_node, "ichatballooncolor"); - ichat_text_color = purple_xmlnode_get_attrib(html_body_node, "ichattextcolor"); - - html_body = get_xmlnode_contents(html_body_font_node); - - if (html_body == NULL) - /* This is the kind of formatted messages that Purple creates */ - html_body = purple_xmlnode_to_str(html_body_font_node, NULL); - - if (html_body != NULL) { - GString *str = g_string_new("<font"); - - if (font_face) - g_string_append_printf(str, " face='%s'", font_face); - if (font_size) - g_string_append_printf(str, " size='%s'", font_size); - if (font_color) - g_string_append_printf(str, " color='%s'", font_color); - else if (ichat_text_color) - g_string_append_printf(str, " color='%s'", ichat_text_color); - if (ichat_balloon_color) - g_string_append_printf(str, " back='%s'", ichat_balloon_color); - g_string_append_printf(str, ">%s</font>", html_body); - - body = g_string_free(str, FALSE); - - g_free(html_body); - } - } - } - } - - /* Compose the message */ - if (body == NULL && body_node != NULL) - body = purple_xmlnode_get_data(body_node); - - if (body == NULL) { - purple_debug_error("bonjour", "No html body or regular body found.\n"); - return; - } - - /* Send the message to the UI */ - purple_serv_got_im(gc, purple_buddy_get_name(pb), body, 0, time(NULL)); - - g_free(body); -} - -struct _match_buddies_by_address { - const char *address; - GSList *matched_buddies; -}; - -static void -_match_buddies_by_address(gpointer value, gpointer data) -{ - PurpleBuddy *pb = value; - BonjourBuddy *bb = NULL; - struct _match_buddies_by_address *mbba = data; - - bb = purple_buddy_get_protocol_data(pb); - - /* - * If the current PurpleBuddy's data is not null, then continue to determine - * whether one of the buddies IPs matches the target IP. - */ - if (bb != NULL) - { - const char *ip; - GSList *tmp = bb->ips; - - while(tmp) { - ip = tmp->data; - if (ip != NULL && g_ascii_strcasecmp(ip, mbba->address) == 0) { - mbba->matched_buddies = g_slist_prepend(mbba->matched_buddies, pb); - break; - } - tmp = tmp->next; - } - } -} - -static void -_send_data_write_cb(GObject *stream, gpointer data) -{ - PurpleBuddy *pb = data; - BonjourBuddy *bb = purple_buddy_get_protocol_data(pb); - BonjourJabberConversation *bconv = bb->conversation; - gsize writelen; - gssize ret; - GError *error = NULL; - - writelen = purple_circular_buffer_get_max_read(bconv->tx_buf); - - if (writelen == 0) { - g_source_remove(bconv->tx_handler); - bconv->tx_handler = 0; - return; - } - - ret = g_pollable_output_stream_write_nonblocking( - G_POLLABLE_OUTPUT_STREAM(stream), - purple_circular_buffer_get_output(bconv->tx_buf), writelen, - bconv->cancellable, &error); - - if (ret < 0 && error->code == G_IO_ERROR_WOULD_BLOCK) { - g_clear_error(&error); - return; - } else if (ret <= 0) { - PurpleConversation *conv = NULL; - PurpleAccount *account = NULL; - - purple_debug_error( - "bonjour", - "Error sending message to buddy %s error: %s", - purple_buddy_get_name(pb), - error ? error->message : "(null)"); - - account = purple_buddy_get_account(pb); - - conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(bb->name, account)); - if (conv != NULL) - purple_conversation_write_system_message(conv, - _("Unable to send message."), - PURPLE_MESSAGE_ERROR); - - bonjour_jabber_close_conversation(bb->conversation); - bb->conversation = NULL; - g_clear_error(&error); - return; - } - - purple_circular_buffer_mark_read(bconv->tx_buf, ret); -} - -static gint -_send_data(PurpleBuddy *pb, char *message) -{ - BonjourBuddy *bb = purple_buddy_get_protocol_data(pb); - BonjourJabberConversation *bconv = bb->conversation; - gsize len = strlen(message); - gssize ret; - GError *error = NULL; - - /* If we're not ready to actually send, append it to the buffer */ - if (bconv->tx_handler != 0 - || bconv->sent_stream_start != FULLY_SENT - || !bconv->recv_stream_start - || purple_circular_buffer_get_max_read(bconv->tx_buf) > 0) { - ret = -1; - g_set_error_literal(&error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, - "Not yet ready to send."); - } else { - ret = g_pollable_output_stream_write_nonblocking( - G_POLLABLE_OUTPUT_STREAM(bconv->output), message, len, - bconv->cancellable, &error); - } - - if (ret == -1 && error->code == G_IO_ERROR_WOULD_BLOCK) { - ret = 0; - g_clear_error(&error); - } else if (ret <= 0) { - PurpleConversation *conv; - PurpleAccount *account; - - purple_debug_error( - "bonjour", - "Error sending message to buddy %s error: %s", - purple_buddy_get_name(pb), - error ? error->message : "(null)"); - - account = purple_buddy_get_account(pb); - - conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(bb->name, account)); - if (conv != NULL) - purple_conversation_write_system_message(conv, - _("Unable to send message."), - PURPLE_MESSAGE_ERROR); - - bonjour_jabber_close_conversation(bb->conversation); - bb->conversation = NULL; - g_clear_error(&error); - return -1; - } - - if (ret < len) { - /* Don't interfere with the stream starting */ - if (bconv->sent_stream_start == FULLY_SENT && - bconv->recv_stream_start && bconv->tx_handler == 0) { - GSource *source = - g_pollable_output_stream_create_source( - G_POLLABLE_OUTPUT_STREAM(bconv->output), - bconv->cancellable); - g_source_set_callback(source, - (GSourceFunc)_send_data_write_cb, - pb, NULL); - bconv->tx_handler = g_source_attach(source, NULL); - } - purple_circular_buffer_append(bconv->tx_buf, message + ret, len - ret); - } - - return ret; -} - -void bonjour_jabber_process_packet(PurpleBuddy *pb, PurpleXmlNode *packet) { - - g_return_if_fail(packet != NULL); - g_return_if_fail(pb != NULL); - - if (purple_strequal(packet->name, "message")) - _jabber_parse_and_write_message_to_ui(packet, pb); - else if (purple_strequal(packet->name, "iq")) - xep_iq_parse(packet, pb); - else { - purple_debug_warning("bonjour", "Unknown packet: %s\n", - packet->name ? packet->name : "(null)"); - } -} - -static void bonjour_jabber_stream_ended(BonjourJabberConversation *bconv) { - - /* Inform the user that the conversation has been closed */ - BonjourBuddy *bb = NULL; - const gchar *name = bconv->pb ? purple_buddy_get_name(bconv->pb) : "(unknown)"; - - purple_debug_info("bonjour", "Received conversation close notification from %s.\n", name); - - if(bconv->pb != NULL) - bb = purple_buddy_get_protocol_data(bconv->pb); - - /* Close the socket, clear the watcher and free memory */ - bonjour_jabber_close_conversation(bconv); - if(bb) - bb->conversation = NULL; -} - -static gboolean -_client_socket_handler(GObject *stream, gpointer data) -{ - BonjourJabberConversation *bconv = data; - GError *error = NULL; - gssize len; - static char message[4096]; - - /* Read the data from the socket */ - len = g_pollable_input_stream_read_nonblocking( - G_POLLABLE_INPUT_STREAM(stream), message, sizeof(message) - 1, - bconv->cancellable, &error); - if (len == -1) { - /* There has been an error reading from the socket */ - if (error == NULL || (error->code != G_IO_ERROR_WOULD_BLOCK && - error->code != G_IO_ERROR_CANCELLED)) { - purple_debug_warning( - "bonjour", - "receive of %" G_GSSIZE_FORMAT " error: %s", - len, error ? error->message : "(null)"); - - bonjour_jabber_close_conversation(bconv); - if (bconv->pb != NULL) { - BonjourBuddy *bb = purple_buddy_get_protocol_data(bconv->pb); - - if(bb != NULL) - bb->conversation = NULL; - } - - /* I guess we really don't need to notify the user. - * If they try to send another message it'll reconnect */ - } - g_clear_error(&error); - return FALSE; - } else if (len == 0) { /* The other end has closed the socket */ - const gchar *name = purple_buddy_get_name(bconv->pb); - purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", (name) ? name : "(unknown)"); - bonjour_jabber_stream_ended(bconv); - return FALSE; - } - - message[len] = '\0'; - - purple_debug_info("bonjour", "Receive: -%s- %" G_GSSIZE_FORMAT " bytes\n", message, len); - bonjour_parser_process(bconv, message, len); - - return TRUE; -} - -struct _stream_start_data { - char *msg; -}; - -static void -_start_stream(GObject *stream, gpointer data) -{ - BonjourJabberConversation *bconv = data; - struct _stream_start_data *ss = bconv->stream_data; - GError *error = NULL; - gsize len; - gssize ret; - - len = strlen(ss->msg); - - /* Start Stream */ - ret = g_pollable_output_stream_write_nonblocking( - G_POLLABLE_OUTPUT_STREAM(stream), ss->msg, len, - bconv->cancellable, &error); - - if (ret == -1 && error->code == G_IO_ERROR_WOULD_BLOCK) { - g_clear_error(&error); - return; - } else if (ret <= 0) { - PurpleConversation *conv; - const char *bname = bconv->buddy_name; - BonjourBuddy *bb = NULL; - - if(bconv->pb) { - bb = purple_buddy_get_protocol_data(bconv->pb); - bname = purple_buddy_get_name(bconv->pb); - } - - purple_debug_error( - "bonjour", - "Error starting stream with buddy %s at %s error: %s", - bname ? bname : "(unknown)", bconv->ip, - error ? error->message : "(null)"); - - conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(bname, bconv->account)); - if (conv != NULL) - purple_conversation_write_system_message(conv, - _("Unable to send the message, the conversation couldn't be started."), - PURPLE_MESSAGE_ERROR); - - bonjour_jabber_close_conversation(bconv); - if(bb != NULL) - bb->conversation = NULL; - - g_clear_error(&error); - 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 */ - g_source_remove(bconv->tx_handler); - bconv->tx_handler = 0; - bconv->sent_stream_start = FULLY_SENT; - - bonjour_jabber_stream_started(bconv); -} - -static gboolean -bonjour_jabber_send_stream_init(BonjourJabberConversation *bconv, - GError **error) -{ - gchar *stream_start; - gsize len; - gssize ret; - const char *bname = bconv->buddy_name; - - g_return_val_if_fail(error != NULL, FALSE); - - if (bconv->pb != NULL) - bname = purple_buddy_get_name(bconv->pb); - - /* If we have no idea who "to" is, use an empty string. - * If we don't know now, it is because the other side isn't playing nice, so they can't complain. */ - if (bname == NULL) - bname = ""; - - stream_start = g_strdup_printf(DOCTYPE, bonjour_get_jid(bconv->account), bname); - len = strlen(stream_start); - - bconv->sent_stream_start = PARTIALLY_SENT; - - /* Start the stream */ - ret = g_pollable_output_stream_write_nonblocking( - G_POLLABLE_OUTPUT_STREAM(bconv->output), stream_start, len, - bconv->cancellable, error); - if (ret == -1 && (*error)->code == G_IO_ERROR_WOULD_BLOCK) { - ret = 0; - g_clear_error(error); - } else if (ret <= 0) { - purple_debug_error( - "bonjour", - "Error starting stream with buddy %s at %s error: %s", - (*bname) ? bname : "(unknown)", bconv->ip, - *error ? (*error)->message : "(null)"); - - if (bconv->pb) { - PurpleConversation *conv; - conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(bname, bconv->account)); - if (conv != NULL) - purple_conversation_write_system_message(conv, - _("Unable to send the message, the conversation couldn't be started."), - PURPLE_MESSAGE_ERROR); - } - - purple_gio_graceful_close(G_IO_STREAM(bconv->socket), - G_INPUT_STREAM(bconv->input), - G_OUTPUT_STREAM(bconv->output)); - g_clear_object(&bconv->socket); - bconv->input = NULL; - bconv->output = NULL; - g_free(stream_start); - - return FALSE; - } - - /* This is unlikely to happen */ - if (ret < len) { - GSource *source; - struct _stream_start_data *ss = g_new(struct _stream_start_data, 1); - ss->msg = g_strdup(stream_start + ret); - bconv->stream_data = ss; - /* Finish sending the stream start */ - source = g_pollable_output_stream_create_source( - G_POLLABLE_OUTPUT_STREAM(bconv->output), - bconv->cancellable); - g_source_set_callback(source, (GSourceFunc)_start_stream, bconv, - NULL); - bconv->tx_handler = g_source_attach(source, NULL); - } else { - bconv->sent_stream_start = FULLY_SENT; - } - - g_free(stream_start); - - return TRUE; -} - -/* This gets called when we've successfully sent our <stream:stream /> - * AND when we've received a <stream:stream /> */ -void -bonjour_jabber_stream_started(BonjourJabberConversation *bconv) -{ - GError *error = NULL; - - if (bconv->sent_stream_start == NOT_SENT && - !bonjour_jabber_send_stream_init(bconv, &error)) { - const char *bname = bconv->buddy_name; - - if (bconv->pb) - bname = purple_buddy_get_name(bconv->pb); - - purple_debug_error( - "bonjour", - "Error starting stream with buddy %s at %s error: %s", - bname ? bname : "(unknown)", bconv->ip, - error ? error->message : "(null)"); - - if (bconv->pb) { - PurpleConversation *conv; - conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(bname, bconv->account)); - if (conv != NULL) - purple_conversation_write_system_message(conv, - _("Unable to send the message, the conversation couldn't be started."), - PURPLE_MESSAGE_ERROR); - } - - /* We don't want to recieve anything else */ - purple_gio_graceful_close(G_IO_STREAM(bconv->socket), - G_INPUT_STREAM(bconv->input), - G_OUTPUT_STREAM(bconv->output)); - g_clear_object(&bconv->socket); - bconv->input = NULL; - bconv->output = NULL; - - /* This must be asynchronous because it destroys the parser and we - * may be in the middle of parsing. - */ - async_bonjour_jabber_close_conversation(bconv); - g_clear_error(&error); - return; - } - - /* If the stream has been completely started and we know who we're talking to, we can start doing stuff. */ - /* I don't think the circ_buffer can actually contain anything without a buddy being associated, but lets be explicit. */ - if (bconv->sent_stream_start == FULLY_SENT && bconv->recv_stream_start - && bconv->pb && purple_circular_buffer_get_max_read(bconv->tx_buf) > 0) { - /* Watch for when we can write the buffered messages */ - GSource *source = g_pollable_output_stream_create_source( - G_POLLABLE_OUTPUT_STREAM(bconv->output), - bconv->cancellable); - g_source_set_callback(source, (GSourceFunc)_send_data_write_cb, - bconv->pb, NULL); - bconv->tx_handler = g_source_attach(source, NULL); - /* We can probably write the data right now. */ - _send_data_write_cb(G_OBJECT(bconv->output), bconv->pb); - } -} - -#ifndef INET6_ADDRSTRLEN -#define INET6_ADDRSTRLEN 46 -#endif - -static void -_server_socket_handler(GSocketService *service, GSocketConnection *connection, - GObject *source_object, gpointer data) -{ - BonjourJabber *jdata = data; - GSocketAddress *their_addr; /* connector's address information */ - GInetAddress *their_inet_addr; - gchar *address_text; - struct _match_buddies_by_address *mbba; - BonjourJabberConversation *bconv; - GSList *buddies; - GSource *source; - - their_addr = g_socket_connection_get_remote_address(connection, NULL); - if (their_addr == NULL) { - return; - } - their_inet_addr = g_inet_socket_address_get_address( - G_INET_SOCKET_ADDRESS(their_addr)); - - /* Look for the buddy that has opened the conversation and fill information */ - address_text = g_inet_address_to_string(their_inet_addr); - if (g_inet_address_get_family(their_inet_addr) == - G_SOCKET_FAMILY_IPV6 && - g_inet_address_get_is_link_local(their_inet_addr)) { - gchar *tmp = g_strdup_printf( - "%s%%%d", address_text, - g_inet_socket_address_get_scope_id( - G_INET_SOCKET_ADDRESS(their_addr))); - g_free(address_text); - address_text = tmp; - } - g_object_unref(their_addr); - - purple_debug_info("bonjour", "Received incoming connection from %s.\n", address_text); - mbba = g_new0(struct _match_buddies_by_address, 1); - mbba->address = address_text; - - buddies = purple_blist_find_buddies(jdata->account, NULL); - g_slist_foreach(buddies, _match_buddies_by_address, mbba); - g_slist_free(buddies); - - if (mbba->matched_buddies == NULL) { - purple_debug_info("bonjour", "We don't like invisible buddies, this is not a superheroes comic\n"); - g_free(address_text); - g_free(mbba); - return; - } - - g_slist_free(mbba->matched_buddies); - g_free(mbba); - - /* We've established that this *could* be from one of our buddies. - * Wait for the stream open to see if that matches too before assigning it. - */ - bconv = bonjour_jabber_conv_new(NULL, jdata->account, address_text); - - /* We wait for the stream start before doing anything else */ - bconv->socket = g_object_ref(connection); - bconv->input = g_io_stream_get_input_stream(G_IO_STREAM(bconv->socket)); - bconv->output = - g_io_stream_get_output_stream(G_IO_STREAM(bconv->socket)); - source = g_pollable_input_stream_create_source( - G_POLLABLE_INPUT_STREAM(bconv->input), bconv->cancellable); - g_source_set_callback(source, (GSourceFunc)_client_socket_handler, - bconv, NULL); - bconv->rx_handler = g_source_attach(source, NULL); - g_free(address_text); -} - -gint -bonjour_jabber_start(BonjourJabber *jdata) -{ - GError *error = NULL; - guint16 port; - - purple_debug_info("bonjour", "Attempting to bind IP socket to port %d.", - jdata->port); - - /* Open a listening server for incoming conversations */ - jdata->service = g_socket_service_new(); - g_socket_listener_set_backlog(G_SOCKET_LISTENER(jdata->service), 10); - port = jdata->port; - if (!g_socket_listener_add_inet_port(G_SOCKET_LISTENER(jdata->service), - port, NULL, &error)) { - purple_debug_info("bonjour", - "Unable to bind to specified port %i: %s", - port, error ? error->message : "(unknown)"); - g_clear_error(&error); - port = g_socket_listener_add_any_inet_port( - G_SOCKET_LISTENER(jdata->service), NULL, &error); - if (port == 0) { - purple_debug_error( - "bonjour", "Unable to create socket: %s", - error ? error->message : "(unknown)"); - g_clear_error(&error); - return -1; - } - } - purple_debug_info("bonjour", "Bound IP socket to port %u.", port); - jdata->port = port; - - g_signal_connect(G_OBJECT(jdata->service), "incoming", - G_CALLBACK(_server_socket_handler), jdata); - - return jdata->port; -} - -static void -_connected_to_buddy(GObject *source, GAsyncResult *res, gpointer user_data) -{ - PurpleBuddy *pb = user_data; - BonjourBuddy *bb = purple_buddy_get_protocol_data(pb); - GSocketConnection *conn; - GSource *rx_source; - GError *error = NULL; - - conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source), - res, &error); - - if (conn == NULL) { - PurpleConversation *conv = NULL; - PurpleAccount *account = NULL; - GSList *tmp; - - if (error && error->code == G_IO_ERROR_CANCELLED) { - /* This conversation was closed before it started. */ - g_error_free(error); - return; - } - - purple_debug_error("bonjour", - "Error connecting to buddy %s at %s:%d " - "(%s); Trying next IP address", - purple_buddy_get_name(pb), - bb->conversation->ip, bb->port_p2pj, - error ? error->message : "(unknown)"); - g_clear_error(&error); - - /* There may be multiple entries for the same IP - one per - * presence recieved (e.g. multiple interfaces). - * We need to make sure that we find the previously used entry. - */ - tmp = g_slist_find(bb->ips, bb->conversation->ip_link); - if (tmp) - tmp = g_slist_next(tmp); - - account = purple_buddy_get_account(pb); - - if (tmp != NULL) { - const gchar *ip; - GSocketClient *client; - - bb->conversation->ip_link = ip = tmp->data; - - purple_debug_info("bonjour", "Starting conversation with %s at %s:%d\n", - purple_buddy_get_name(pb), ip, bb->port_p2pj); - - /* Make sure to connect without a proxy. */ - client = g_socket_client_new(); - if (client != NULL) { - g_free(bb->conversation->ip); - bb->conversation->ip = g_strdup(ip); - g_socket_client_connect_to_host_async( - client, ip, bb->port_p2pj, - bb->conversation->cancellable, - _connected_to_buddy, pb); - g_object_unref(client); - return; - } - } - - purple_debug_error("bonjour", "No more addresses for buddy %s. Aborting", purple_buddy_get_name(pb)); - - conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(bb->name, account)); - if (conv != NULL) - purple_conversation_write_system_message(conv, - _("Unable to send the message, the conversation couldn't be started."), - PURPLE_MESSAGE_ERROR); - - bonjour_jabber_close_conversation(bb->conversation); - bb->conversation = NULL; - return; - } - - bb->conversation->socket = conn; - bb->conversation->input = - g_io_stream_get_input_stream(G_IO_STREAM(conn)); - bb->conversation->output = - g_io_stream_get_output_stream(G_IO_STREAM(conn)); - - if (!bonjour_jabber_send_stream_init(bb->conversation, &error)) { - PurpleConversation *conv = NULL; - PurpleAccount *account = NULL; - - purple_debug_error("bonjour", - "Error starting stream with buddy %s at " - "%s:%d error: %s", - purple_buddy_get_name(pb), - bb->conversation->ip, bb->port_p2pj, - error ? error->message : "(null)"); - - account = purple_buddy_get_account(pb); - - conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(bb->name, account)); - if (conv != NULL) - purple_conversation_write_system_message(conv, - _("Unable to send the message, the conversation couldn't be started."), - PURPLE_MESSAGE_ERROR); - - bonjour_jabber_close_conversation(bb->conversation); - bb->conversation = NULL; - g_clear_error(&error); - return; - } - - /* Start listening for the stream acknowledgement */ - rx_source = g_pollable_input_stream_create_source( - G_POLLABLE_INPUT_STREAM(bb->conversation->input), - bb->conversation->cancellable); - g_source_set_callback(rx_source, (GSourceFunc)_client_socket_handler, - bb->conversation, NULL); - bb->conversation->rx_handler = g_source_attach(rx_source, NULL); -} - -void -bonjour_jabber_conv_match_by_name(BonjourJabberConversation *bconv) { - PurpleBuddy *pb = NULL; - BonjourBuddy *bb = NULL; - - g_return_if_fail(bconv->ip != NULL); - g_return_if_fail(bconv->pb == NULL); - - pb = purple_blist_find_buddy(bconv->account, bconv->buddy_name); - if (pb && (bb = purple_buddy_get_protocol_data(pb))) { - const char *ip; - GSList *tmp = bb->ips; - - purple_debug_info("bonjour", "Found buddy %s for incoming conversation \"from\" attrib.\n", - purple_buddy_get_name(pb)); - - /* Check that one of the buddy's IPs matches */ - while(tmp) { - ip = tmp->data; - if (ip != NULL && g_ascii_strcasecmp(ip, bconv->ip) == 0) { - PurpleConnection *pc = purple_account_get_connection(bconv->account); - BonjourData *bd = purple_connection_get_protocol_data(pc); - BonjourJabber *jdata = bd->jabber_data; - - purple_debug_info("bonjour", "Matched buddy %s to incoming conversation \"from\" attrib and IP (%s)\n", - purple_buddy_get_name(pb), bconv->ip); - - /* Attach conv. to buddy and remove from pending list */ - jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv); - - /* Check if the buddy already has a conversation and, if so, replace it */ - if(bb->conversation != NULL && bb->conversation != bconv) - bonjour_jabber_close_conversation(bb->conversation); - - bconv->pb = pb; - bb->conversation = bconv; - - break; - } - tmp = tmp->next; - } - } - - /* We've failed to match a buddy - give up */ - if (bconv->pb == NULL) { - /* This must be asynchronous because it destroys the parser and we - * may be in the middle of parsing. - */ - async_bonjour_jabber_close_conversation(bconv); - } -} - - -void -bonjour_jabber_conv_match_by_ip(BonjourJabberConversation *bconv) { - PurpleConnection *pc = purple_account_get_connection(bconv->account); - BonjourData *bd = purple_connection_get_protocol_data(pc); - BonjourJabber *jdata = bd->jabber_data; - struct _match_buddies_by_address *mbba; - GSList *buddies; - - mbba = g_new0(struct _match_buddies_by_address, 1); - mbba->address = bconv->ip; - - buddies = purple_blist_find_buddies(jdata->account, NULL); - g_slist_foreach(buddies, _match_buddies_by_address, mbba); - g_slist_free(buddies); - - /* If there is exactly one match, use it */ - if(mbba->matched_buddies != NULL) { - if(mbba->matched_buddies->next != NULL) - purple_debug_error("bonjour", "More than one buddy matched for ip %s.\n", bconv->ip); - else { - PurpleBuddy *pb = mbba->matched_buddies->data; - BonjourBuddy *bb = purple_buddy_get_protocol_data(pb); - - purple_debug_info("bonjour", "Matched buddy %s to incoming conversation using IP (%s)\n", - purple_buddy_get_name(pb), bconv->ip); - - /* Attach conv. to buddy and remove from pending list */ - jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv); - - /* Check if the buddy already has a conversation and, if so, replace it */ - if (bb->conversation != NULL && bb->conversation != bconv) - bonjour_jabber_close_conversation(bb->conversation); - - bconv->pb = pb; - bb->conversation = bconv; - } - } else - purple_debug_error("bonjour", "No buddies matched for ip %s.\n", bconv->ip); - - /* We've failed to match a buddy - give up */ - if (bconv->pb == NULL) { - /* This must be asynchronous because it destroys the parser and we - * may be in the middle of parsing. - */ - async_bonjour_jabber_close_conversation(bconv); - } - - g_slist_free(mbba->matched_buddies); - g_free(mbba); -} - -static PurpleBuddy * -_find_or_start_conversation(BonjourJabber *jdata, const gchar *to) -{ - PurpleBuddy *pb = NULL; - BonjourBuddy *bb = NULL; - - g_return_val_if_fail(jdata != NULL, NULL); - g_return_val_if_fail(to != NULL, NULL); - - pb = purple_blist_find_buddy(jdata->account, to); - if (pb == NULL || (bb = purple_buddy_get_protocol_data(pb)) == NULL) - /* You can not send a message to an offline buddy */ - return NULL; - - /* Check if there is a previously open conversation */ - if (bb->conversation == NULL) { - GSocketClient *client; - /* Start with the first IP address. */ - const gchar *ip = bb->ips->data; - - purple_debug_info("bonjour", - "Starting conversation with %s at %s:%d", to, - ip, bb->port_p2pj); - - /* Make sure to connect without a proxy. */ - client = g_socket_client_new(); - if (client == NULL) { - purple_debug_error("bonjour", - "Unable to connect to buddy (%s).", - to); - return NULL; - } - - bb->conversation = bonjour_jabber_conv_new(pb, jdata->account, ip); - bb->conversation->ip_link = ip; - - g_socket_client_connect_to_host_async( - client, ip, bb->port_p2pj, - bb->conversation->cancellable, _connected_to_buddy, pb); - g_object_unref(client); - } - return pb; -} - -int -bonjour_jabber_send_message(BonjourJabber *jdata, const gchar *to, const gchar *body) -{ - PurpleXmlNode *message_node, *node, *node2; - gchar *message, *xhtml; - PurpleBuddy *pb; - BonjourBuddy *bb; - int ret; - - pb = _find_or_start_conversation(jdata, to); - if (pb == NULL || (bb = purple_buddy_get_protocol_data(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; - } - - purple_markup_html_to_xhtml(body, &xhtml, &message); - - message_node = purple_xmlnode_new("message"); - purple_xmlnode_set_attrib(message_node, "to", bb->name); - purple_xmlnode_set_attrib(message_node, "from", bonjour_get_jid(jdata->account)); - purple_xmlnode_set_attrib(message_node, "type", "chat"); - - /* Enclose the message from the UI within a "font" node */ - node = purple_xmlnode_new_child(message_node, "body"); - purple_xmlnode_insert_data(node, message, strlen(message)); - g_free(message); - - node = purple_xmlnode_new_child(message_node, "html"); - purple_xmlnode_set_namespace(node, "http://www.w3.org/1999/xhtml"); - - node = purple_xmlnode_new_child(node, "body"); - message = g_strdup_printf("<font>%s</font>", xhtml); - node2 = purple_xmlnode_from_str(message, strlen(message)); - g_free(xhtml); - g_free(message); - purple_xmlnode_insert_child(node, node2); - - node = purple_xmlnode_new_child(message_node, "x"); - purple_xmlnode_set_namespace(node, "jabber:x:event"); - purple_xmlnode_insert_child(node, purple_xmlnode_new("composing")); - - message = purple_xmlnode_to_str(message_node, NULL); - purple_xmlnode_free(message_node); - - ret = _send_data(pb, message) >= 0; - - g_free(message); - - return ret; -} - -static gboolean -_async_bonjour_jabber_close_conversation_cb(gpointer data) { - BonjourJabberConversation *bconv = data; - bonjour_jabber_close_conversation(bconv); - return FALSE; -} - -void -async_bonjour_jabber_close_conversation(BonjourJabberConversation *bconv) { - PurpleConnection *pc = purple_account_get_connection(bconv->account); - BonjourData *bd = purple_connection_get_protocol_data(pc); - BonjourJabber *jdata = bd->jabber_data; - - jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv); - - /* Disconnect this conv. from the buddy here so it can't be disposed of twice.*/ - if(bconv->pb != NULL) { - BonjourBuddy *bb = purple_buddy_get_protocol_data(bconv->pb); - if (bb->conversation == bconv) - bb->conversation = NULL; - } - - bconv->close_timeout = g_timeout_add(0, _async_bonjour_jabber_close_conversation_cb, bconv); -} - -void -bonjour_jabber_close_conversation(BonjourJabberConversation *bconv) -{ - BonjourData *bd = NULL; - PurpleConnection *pc = NULL; - - if (bconv == NULL) { - return; - } - - pc = purple_account_get_connection(bconv->account); - PURPLE_ASSERT_CONNECTION_IS_VALID(pc); - - bd = purple_connection_get_protocol_data(pc); - if (bd) { - bd->jabber_data->pending_conversations = g_slist_remove( - bd->jabber_data->pending_conversations, bconv); - } - - /* Cancel any file transfers that are waiting to begin */ - /* There wont be any transfers if it hasn't been attached to a buddy */ - if (bconv->pb != NULL && bd != NULL) { - GSList *xfers, *tmp_next; - 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 (purple_strequal(purple_xfer_get_remote_user(xfer), purple_buddy_get_name(bconv->pb)) - && (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 != NULL) { - /* Send the end of the stream to the other end of the conversation */ - if (bconv->sent_stream_start == FULLY_SENT) { - size_t len = strlen(STREAM_END); - if (g_pollable_output_stream_write_nonblocking( - G_POLLABLE_OUTPUT_STREAM(bconv->output), - STREAM_END, len, bconv->cancellable, - NULL) != (gssize)len) { - purple_debug_error("bonjour", - "bonjour_jabber_close_conversation: " - "couldn't send data\n"); - } - } - /* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */ - purple_gio_graceful_close(G_IO_STREAM(bconv->socket), - G_INPUT_STREAM(bconv->input), - G_OUTPUT_STREAM(bconv->output)); - } - if (bconv->rx_handler != 0) { - g_source_remove(bconv->rx_handler); - bconv->rx_handler = 0; - } - if (bconv->tx_handler != 0) { - g_source_remove(bconv->tx_handler); - bconv->tx_handler = 0; - } - - /* Cancel any pending operations. */ - if (bconv->cancellable != NULL) { - g_cancellable_cancel(bconv->cancellable); - g_clear_object(&bconv->cancellable); - } - - /* Free all the data related to the conversation */ - g_clear_object(&bconv->socket); - bconv->input = NULL; - bconv->output = NULL; - - g_object_unref(G_OBJECT(bconv->tx_buf)); - 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); - } - - if (bconv->close_timeout != 0) { - g_source_remove(bconv->close_timeout); - } - - g_free(bconv->buddy_name); - g_free(bconv->ip); - g_free(bconv); -} - -void -bonjour_jabber_stop(BonjourJabber *jdata) -{ - /* Close the server socket and remove the watcher */ - if (jdata->service) { - g_socket_service_stop(jdata->service); - g_socket_listener_close(G_SOCKET_LISTENER(jdata->service)); - g_clear_object(&jdata->service); - } - - /* Close all the conversation sockets and remove all the watchers after sending end streams */ - if (!purple_account_is_disconnected(jdata->account)) { - GSList *buddies, *l; - - buddies = purple_blist_find_buddies(jdata->account, NULL); - for (l = buddies; l; l = l->next) { - BonjourBuddy *bb = purple_buddy_get_protocol_data((PurpleBuddy*) l->data); - if (bb && bb->conversation) { - /* Any ongoing connection attempt is cancelled - * when a connection is destroyed */ - bonjour_jabber_close_conversation(bb->conversation); - bb->conversation = NULL; - } - } - - g_slist_free(buddies); - } - - g_slist_free_full(jdata->pending_conversations, (GDestroyNotify)bonjour_jabber_close_conversation); -} - -XepIq * -xep_iq_new(void *data, XepIqType type, const char *to, const char *from, const char *id) -{ - PurpleXmlNode *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 = purple_xmlnode_new("iq"); - - purple_xmlnode_set_attrib(iq_node, "to", to); - purple_xmlnode_set_attrib(iq_node, "from", from); - purple_xmlnode_set_attrib(iq_node, "id", id); - switch (type) { - case XEP_IQ_SET: - purple_xmlnode_set_attrib(iq_node, "type", "set"); - break; - case XEP_IQ_GET: - purple_xmlnode_set_attrib(iq_node, "type", "get"); - break; - case XEP_IQ_RESULT: - purple_xmlnode_set_attrib(iq_node, "type", "result"); - break; - case XEP_IQ_ERROR: - purple_xmlnode_set_attrib(iq_node, "type", "error"); - break; - case XEP_IQ_NONE: - default: - purple_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 = purple_buddy_get_account(pb); - const gchar *name; - - if(acc == NULL) - return FALSE; - - l = purple_account_privacy_get_denied(acc); - name = purple_buddy_get_name(pb); - - if(g_slist_find_custom(l, name, (GCompareFunc)purple_utf8_strcasecmp) != NULL) { - const gchar *username = bonjour_get_jid(acc); - - purple_debug_info("bonjour", "%s has been blocked by %s.\n", name, username); - blocked = TRUE; - } - return blocked; -} - -static void -xep_iq_parse(PurpleXmlNode *packet, PurpleBuddy *pb) -{ - PurpleAccount *account; - PurpleConnection *gc; - - if(check_if_blocked(pb)) - return; - - account = purple_buddy_get_account(pb); - gc = purple_account_get_connection(account); - - if (purple_xmlnode_get_child(packet, "si") != NULL || purple_xmlnode_get_child(packet, "error") != NULL) - xep_si_parse(gc, packet, pb); - else - xep_bytestreams_parse(gc, 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 = purple_xmlnode_to_str(iq->node, NULL); - ret = _send_data(pb, msg); - g_free(msg); - } - - purple_xmlnode_free(iq->node); - iq->node = NULL; - g_free(iq); - - return (ret >= 0) ? 0 : -1; -} - -/* This returns a list containing all non-localhost IPs */ -GSList * -bonjour_jabber_get_local_ips(int fd) -{ - GSList *ips = NULL; - const char *address_text; - int ret; - -#ifdef HAVE_GETIFADDRS /* This is required for IPv6 */ - struct ifaddrs *ifap, *ifa; - common_sockaddr_t addr; - char addrstr[INET6_ADDRSTRLEN]; - - ret = getifaddrs(&ifap); - if (ret != 0) { - const char *error = g_strerror(errno); - purple_debug_error("bonjour", "getifaddrs() error: %s\n", error ? error : "(null)"); - return NULL; - } - - for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { - if (!(ifa->ifa_flags & IFF_RUNNING) || (ifa->ifa_flags & IFF_LOOPBACK) || ifa->ifa_addr == NULL) - continue; - - memcpy(&addr, ifa->ifa_addr, sizeof(addr)); - address_text = NULL; - switch (addr.sa.sa_family) { - case AF_INET: - address_text = inet_ntop(addr.sa.sa_family, - &addr.in.sin_addr, - addrstr, sizeof(addrstr)); - break; -#ifdef PF_INET6 - case AF_INET6: - address_text = inet_ntop(addr.sa.sa_family, - &addr.in6.sin6_addr, - addrstr, sizeof(addrstr)); - break; -#endif - } - - if (address_text != NULL) { - if (addr.sa.sa_family == AF_INET) - ips = g_slist_append(ips, g_strdup(address_text)); - else - ips = g_slist_prepend(ips, g_strdup(address_text)); - } - } - - freeifaddrs(ifap); -#else - char *tmp; - struct ifconf ifc; - struct ifreq *ifr; - char buffer[1024]; - struct sockaddr_in *sinptr; - int source = fd; - - if (fd < 0) - source = socket(PF_INET, SOCK_STREAM, 0); - - ifc.ifc_len = sizeof(buffer); - ifc.ifc_req = (struct ifreq *)buffer; - ret = ioctl(source, SIOCGIFCONF, &ifc); - - if (fd < 0) - close(source); - - if (ret < 0) { - const char *error = g_strerror(errno); - purple_debug_error("bonjour", "ioctl(SIOCGIFCONF) error: %s\n", error ? error : "(null)"); - return NULL; - } - - tmp = buffer; - while (tmp < buffer + ifc.ifc_len) { - 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 ((ntohl(sinptr->sin_addr.s_addr) >> 24) != 127) { - address_text = inet_ntoa(sinptr->sin_addr); - ips = g_slist_prepend(ips, g_strdup(address_text)); - } - } - } -#endif - - return ips; -} - -void -append_iface_if_linklocal(char *ip, guint32 interface_param) { - struct in6_addr in6_addr; - int len_remain = INET6_ADDRSTRLEN - strlen(ip); - - if (len_remain <= 1) - return; - - if (inet_pton(AF_INET6, ip, &in6_addr) != 1 || - !IN6_IS_ADDR_LINKLOCAL(&in6_addr)) - return; - - snprintf(ip + strlen(ip), len_remain, "%%%d", - interface_param); -}
--- a/libpurple/protocols/bonjour/jabber.h Fri Oct 25 05:52:30 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,112 +0,0 @@ -/** - * @file jabber.h The Purple interface to mDNS and peer to peer Jabber. - * - * purple - * - * 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 - * - */ - -#ifndef PURPLE_BONJOUR_JABBER_H -#define PURPLE_BONJOUR_JABBER_H - -#include <libxml/parser.h> - -#include <purple.h> - -typedef struct -{ - GSocketService *service; - guint16 port; - PurpleAccount *account; - GSList *pending_conversations; -} BonjourJabber; - -typedef struct -{ - GCancellable *cancellable; - GSocketConnection *socket; - GInputStream *input; - GOutputStream *output; - guint rx_handler; - guint tx_handler; - guint close_timeout; - PurpleCircularBuffer *tx_buf; - int sent_stream_start; /* 0 = Unsent, 1 = Partial, 2 = Complete */ - gboolean recv_stream_start; - gpointer stream_data; - xmlParserCtxt *context; - PurpleXmlNode *current; - PurpleBuddy *pb; - PurpleAccount *account; - - /* The following are only needed before attaching to a PurpleBuddy */ - gchar *buddy_name; - gchar *ip; - /* This points to a data entry in BonjourBuddy->ips */ - const gchar *ip_link; -} 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 char *to, const char *body); - -void bonjour_jabber_close_conversation(BonjourJabberConversation *bconv); - -void async_bonjour_jabber_close_conversation(BonjourJabberConversation *bconv); - -void bonjour_jabber_stream_started(BonjourJabberConversation *bconv); - -void bonjour_jabber_process_packet(PurpleBuddy *pb, PurpleXmlNode *packet); - -void bonjour_jabber_stop(BonjourJabber *data); - -void bonjour_jabber_conv_match_by_ip(BonjourJabberConversation *bconv); - -void bonjour_jabber_conv_match_by_name(BonjourJabberConversation *bconv); - -typedef enum { - XEP_IQ_SET, - XEP_IQ_GET, - XEP_IQ_RESULT, - XEP_IQ_ERROR, - XEP_IQ_NONE -} XepIqType; - -typedef struct { - XepIqType type; - char *id; - PurpleXmlNode *node; - char *to; - void *data; -} XepIq; - -XepIq *xep_iq_new(void *data, XepIqType type, const char *to, const char *from, const char *id); -int xep_iq_send_and_free(XepIq *iq); -GSList * bonjour_jabber_get_local_ips(int fd); - -void append_iface_if_linklocal(char *ip, guint32 interface_param); - -#endif /* PURPLE_BONJOUR_JABBER_H */
--- a/libpurple/protocols/bonjour/meson.build Fri Oct 25 05:52:30 2019 -0400 +++ b/libpurple/protocols/bonjour/meson.build Sat Oct 26 16:53:30 2019 +0000 @@ -3,8 +3,8 @@ 'bonjour.h', 'buddy.c', 'buddy.h', - 'jabber.c', - 'jabber.h', + 'xmpp.c', + 'xmpp.h', 'mdns_common.c', 'mdns_common.h', 'mdns_interface.h',
--- a/libpurple/protocols/bonjour/parser.c Fri Oct 25 05:52:30 2019 -0400 +++ b/libpurple/protocols/bonjour/parser.c Sat Oct 26 16:53:30 2019 +0000 @@ -1,5 +1,5 @@ /* - * purple - Bonjour Jabber XML parser stuff + * purple - Bonjour XMPP XML parser stuff * * 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 @@ -25,11 +25,11 @@ #include <libxml/parser.h> -#include "jabber.h" #include "parser.h" +#include "xmpp.h" static gboolean -parse_from_attrib_and_find_buddy(BonjourJabberConversation *bconv, int nb_attributes, const xmlChar **attributes) { +parse_from_attrib_and_find_buddy(BonjourXMPPConversation *bconv, int nb_attributes, const xmlChar **attributes) { int i; /* If the "from" attribute is specified, attach it to the conversation. */ @@ -37,7 +37,7 @@ if(!xmlStrcmp(attributes[i], (xmlChar*) "from")) { int len = attributes[i+4] - attributes[i+3]; bconv->buddy_name = g_strndup((char *)attributes[i+3], len); - bonjour_jabber_conv_match_by_name(bconv); + bonjour_xmpp_conv_match_by_name(bconv); return (bconv->pb != NULL); } @@ -52,7 +52,7 @@ int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes) { - BonjourJabberConversation *bconv = user_data; + BonjourXMPPConversation *bconv = user_data; PurpleXmlNode *node; int i; @@ -66,7 +66,7 @@ if (bconv->pb == NULL) parse_from_attrib_and_find_buddy(bconv, nb_attributes, attributes); - bonjour_jabber_stream_started(bconv); + bonjour_xmpp_stream_started(bconv); } } else { @@ -79,7 +79,7 @@ /* We've run out of options for finding who the conversation is from using explicitly specified stuff; see if we can make a good match by using the IP */ - bonjour_jabber_conv_match_by_ip(bconv); + bonjour_xmpp_conv_match_by_ip(bconv); if(bconv->current) node = purple_xmlnode_new_child(bconv->current, (const char*) element_name); @@ -113,7 +113,7 @@ bonjour_parser_element_end_libxml(void *user_data, const xmlChar *element_name, const xmlChar *prefix, const xmlChar *namespace) { - BonjourJabberConversation *bconv = user_data; + BonjourXMPPConversation *bconv = user_data; if(!bconv->current) { /* We don't keep a reference to the start stream PurpleXmlNode, @@ -121,7 +121,7 @@ if(!xmlStrcmp(element_name, (xmlChar*) "stream")) /* Asynchronously close the conversation to prevent bonjour_parser_setup() * being called from within this context */ - async_bonjour_jabber_close_conversation(bconv); + async_bonjour_xmpp_close_conversation(bconv); return; } @@ -131,7 +131,7 @@ } else { PurpleXmlNode *packet = bconv->current; bconv->current = NULL; - bonjour_jabber_process_packet(bconv->pb, packet); + bonjour_xmpp_process_packet(bconv->pb, packet); purple_xmlnode_free(packet); } } @@ -139,7 +139,7 @@ static void bonjour_parser_element_text_libxml(void *user_data, const xmlChar *text, int text_len) { - BonjourJabberConversation *bconv = user_data; + BonjourXMPPConversation *bconv = user_data; if(!bconv->current) return; @@ -153,9 +153,9 @@ static void bonjour_parser_structured_error_handler(void *user_data, xmlErrorPtr error) { - BonjourJabberConversation *bconv = user_data; + BonjourXMPPConversation *bconv = user_data; - purple_debug_error("jabber", "XML parser error for BonjourJabberConversation %p: " + purple_debug_error("bonjour", "XML parser error for BonjourXMPPConversation %p: " "Domain %i, code %i, level %i: %s", bconv, error->domain, error->code, error->level, @@ -198,7 +198,7 @@ }; void -bonjour_parser_setup(BonjourJabberConversation *bconv) +bonjour_parser_setup(BonjourXMPPConversation *bconv) { /* This seems backwards, but it makes sense. The libxml code creates @@ -213,7 +213,7 @@ } -void bonjour_parser_process(BonjourJabberConversation *bconv, const char *buf, int len) +void bonjour_parser_process(BonjourXMPPConversation *bconv, const char *buf, int len) { if (bconv->context == NULL) {
--- a/libpurple/protocols/bonjour/parser.h Fri Oct 25 05:52:30 2019 -0400 +++ b/libpurple/protocols/bonjour/parser.h Sat Oct 26 16:53:30 2019 +0000 @@ -1,5 +1,5 @@ /** - * @file parser.h Bonjour Jabber XML parser functions + * @file parser.h Bonjour XMPP XML parser functions * * purple * @@ -26,9 +26,9 @@ #define PURPLE_BONJOUR_PARSER_H #include "buddy.h" -#include "jabber.h" +#include "xmpp.h" -void bonjour_parser_setup(BonjourJabberConversation *bconv); -void bonjour_parser_process(BonjourJabberConversation *bconv, const char *buf, int len); +void bonjour_parser_setup(BonjourXMPPConversation *bconv); +void bonjour_parser_process(BonjourXMPPConversation *bconv, const char *buf, int len); #endif /* PURPLE_BONJOUR_PARSER_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/bonjour/xmpp.c Sat Oct 26 16:53:30 2019 +0000 @@ -0,0 +1,1472 @@ +/* + * 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" +#include <purple.h> + +#ifndef _WIN32 +#include <net/if.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#endif +#include <sys/types.h> + +/* Solaris */ +#if defined (__SVR4) && defined (__sun) +#include <sys/sockio.h> +#endif + +#include <glib.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <fcntl.h> + +#ifdef HAVE_GETIFADDRS +#include <ifaddrs.h> +#endif + +#include "xmpp.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\">" + +enum sent_stream_start_types { + NOT_SENT = 0, + PARTIALLY_SENT = 1, + FULLY_SENT = 2 +}; + +static void +xep_iq_parse(PurpleXmlNode *packet, PurpleBuddy *pb); + +static BonjourXMPPConversation * +bonjour_xmpp_conv_new(PurpleBuddy *pb, PurpleAccount *account, const char *ip) { + + BonjourXMPPConversation *bconv = g_new0(BonjourXMPPConversation, 1); + bconv->cancellable = g_cancellable_new(); + bconv->tx_buf = purple_circular_buffer_new(512); + bconv->tx_handler = 0; + bconv->rx_handler = 0; + bconv->pb = pb; + bconv->account = account; + bconv->ip = g_strdup(ip); + + bonjour_parser_setup(bconv); + + 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 gchar * +get_xmlnode_contents(PurpleXmlNode *node) +{ + gchar *contents; + + contents = purple_xmlnode_to_str(node, NULL); + + /* we just want the stuff inside <font></font> + * There isn't stuff exposed in PurpleXmlNode.c to do this more cleanly. */ + + if (contents) { + char *bodystart = strchr(contents, '>'); + char *bodyend = bodystart ? strrchr(bodystart, '<') : NULL; + if (bodystart && bodyend && (bodystart + 1) != bodyend) { + *bodyend = '\0'; + memmove(contents, bodystart + 1, (bodyend - bodystart)); + } + } + + return contents; +} + +static void +_xmpp_parse_and_write_message_to_ui(PurpleXmlNode *message_node, PurpleBuddy *pb) +{ + PurpleXmlNode *body_node, *html_node, *events_node; + PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(pb)); + gchar *body = NULL; + + body_node = purple_xmlnode_get_child(message_node, "body"); + html_node = purple_xmlnode_get_child(message_node, "html"); + + if (body_node == NULL && html_node == NULL) { + purple_debug_error("bonjour", "No body or html node found, discarding message.\n"); + return; + } + + events_node = purple_xmlnode_get_child_with_namespace(message_node, "x", "jabber:x:event"); + if (events_node != NULL) { + if (purple_xmlnode_get_child(events_node, "id") != NULL) { + /* The user is just typing */ + /* TODO: Deal with typing notification */ + return; + } + } + + if (html_node != NULL) { + PurpleXmlNode *html_body_node; + + html_body_node = purple_xmlnode_get_child(html_node, "body"); + if (html_body_node != NULL) { + PurpleXmlNode *html_body_font_node; + + html_body_font_node = purple_xmlnode_get_child(html_body_node, "font"); + /* Types of messages sent by iChat */ + if (html_body_font_node != NULL) { + gchar *html_body; + const char *font_face, *font_size, *font_color, + *ichat_balloon_color, *ichat_text_color; + + font_face = purple_xmlnode_get_attrib(html_body_font_node, "face"); + /* The absolute iChat font sizes should be converted to 1..7 range */ + font_size = purple_xmlnode_get_attrib(html_body_font_node, "ABSZ"); + if (font_size != NULL) + font_size = _font_size_ichat_to_purple(atoi(font_size)); + font_color = purple_xmlnode_get_attrib(html_body_font_node, "color"); + ichat_balloon_color = purple_xmlnode_get_attrib(html_body_node, "ichatballooncolor"); + ichat_text_color = purple_xmlnode_get_attrib(html_body_node, "ichattextcolor"); + + html_body = get_xmlnode_contents(html_body_font_node); + + if (html_body == NULL) + /* This is the kind of formatted messages that Purple creates */ + html_body = purple_xmlnode_to_str(html_body_font_node, NULL); + + if (html_body != NULL) { + GString *str = g_string_new("<font"); + + if (font_face) + g_string_append_printf(str, " face='%s'", font_face); + if (font_size) + g_string_append_printf(str, " size='%s'", font_size); + if (font_color) + g_string_append_printf(str, " color='%s'", font_color); + else if (ichat_text_color) + g_string_append_printf(str, " color='%s'", ichat_text_color); + if (ichat_balloon_color) + g_string_append_printf(str, " back='%s'", ichat_balloon_color); + g_string_append_printf(str, ">%s</font>", html_body); + + body = g_string_free(str, FALSE); + + g_free(html_body); + } + } + } + } + + /* Compose the message */ + if (body == NULL && body_node != NULL) + body = purple_xmlnode_get_data(body_node); + + if (body == NULL) { + purple_debug_error("bonjour", "No html body or regular body found.\n"); + return; + } + + /* Send the message to the UI */ + purple_serv_got_im(gc, purple_buddy_get_name(pb), body, 0, time(NULL)); + + g_free(body); +} + +struct _match_buddies_by_address { + const char *address; + GSList *matched_buddies; +}; + +static void +_match_buddies_by_address(gpointer value, gpointer data) +{ + PurpleBuddy *pb = value; + BonjourBuddy *bb = NULL; + struct _match_buddies_by_address *mbba = data; + + bb = purple_buddy_get_protocol_data(pb); + + /* + * If the current PurpleBuddy's data is not null, then continue to determine + * whether one of the buddies IPs matches the target IP. + */ + if (bb != NULL) + { + const char *ip; + GSList *tmp = bb->ips; + + while(tmp) { + ip = tmp->data; + if (ip != NULL && g_ascii_strcasecmp(ip, mbba->address) == 0) { + mbba->matched_buddies = g_slist_prepend(mbba->matched_buddies, pb); + break; + } + tmp = tmp->next; + } + } +} + +static void +_send_data_write_cb(GObject *stream, gpointer data) +{ + PurpleBuddy *pb = data; + BonjourBuddy *bb = purple_buddy_get_protocol_data(pb); + BonjourXMPPConversation *bconv = bb->conversation; + gsize writelen; + gssize ret; + GError *error = NULL; + + writelen = purple_circular_buffer_get_max_read(bconv->tx_buf); + + if (writelen == 0) { + g_source_remove(bconv->tx_handler); + bconv->tx_handler = 0; + return; + } + + ret = g_pollable_output_stream_write_nonblocking( + G_POLLABLE_OUTPUT_STREAM(stream), + purple_circular_buffer_get_output(bconv->tx_buf), writelen, + bconv->cancellable, &error); + + if (ret < 0 && error->code == G_IO_ERROR_WOULD_BLOCK) { + g_clear_error(&error); + return; + } else if (ret <= 0) { + PurpleConversation *conv = NULL; + PurpleAccount *account = NULL; + + purple_debug_error( + "bonjour", + "Error sending message to buddy %s error: %s", + purple_buddy_get_name(pb), + error ? error->message : "(null)"); + + account = purple_buddy_get_account(pb); + + conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(bb->name, account)); + if (conv != NULL) + purple_conversation_write_system_message(conv, + _("Unable to send message."), + PURPLE_MESSAGE_ERROR); + + bonjour_xmpp_close_conversation(bb->conversation); + bb->conversation = NULL; + g_clear_error(&error); + return; + } + + purple_circular_buffer_mark_read(bconv->tx_buf, ret); +} + +static gint +_send_data(PurpleBuddy *pb, char *message) +{ + BonjourBuddy *bb = purple_buddy_get_protocol_data(pb); + BonjourXMPPConversation *bconv = bb->conversation; + gsize len = strlen(message); + gssize ret; + GError *error = NULL; + + /* If we're not ready to actually send, append it to the buffer */ + if (bconv->tx_handler != 0 + || bconv->sent_stream_start != FULLY_SENT + || !bconv->recv_stream_start + || purple_circular_buffer_get_max_read(bconv->tx_buf) > 0) { + ret = -1; + g_set_error_literal(&error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, + "Not yet ready to send."); + } else { + ret = g_pollable_output_stream_write_nonblocking( + G_POLLABLE_OUTPUT_STREAM(bconv->output), message, len, + bconv->cancellable, &error); + } + + if (ret == -1 && error->code == G_IO_ERROR_WOULD_BLOCK) { + ret = 0; + g_clear_error(&error); + } else if (ret <= 0) { + PurpleConversation *conv; + PurpleAccount *account; + + purple_debug_error( + "bonjour", + "Error sending message to buddy %s error: %s", + purple_buddy_get_name(pb), + error ? error->message : "(null)"); + + account = purple_buddy_get_account(pb); + + conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(bb->name, account)); + if (conv != NULL) + purple_conversation_write_system_message(conv, + _("Unable to send message."), + PURPLE_MESSAGE_ERROR); + + bonjour_xmpp_close_conversation(bb->conversation); + bb->conversation = NULL; + g_clear_error(&error); + return -1; + } + + if (ret < len) { + /* Don't interfere with the stream starting */ + if (bconv->sent_stream_start == FULLY_SENT && + bconv->recv_stream_start && bconv->tx_handler == 0) { + GSource *source = + g_pollable_output_stream_create_source( + G_POLLABLE_OUTPUT_STREAM(bconv->output), + bconv->cancellable); + g_source_set_callback(source, + (GSourceFunc)_send_data_write_cb, + pb, NULL); + bconv->tx_handler = g_source_attach(source, NULL); + } + purple_circular_buffer_append(bconv->tx_buf, message + ret, len - ret); + } + + return ret; +} + +void bonjour_xmpp_process_packet(PurpleBuddy *pb, PurpleXmlNode *packet) { + + g_return_if_fail(packet != NULL); + g_return_if_fail(pb != NULL); + + if (purple_strequal(packet->name, "message")) + _xmpp_parse_and_write_message_to_ui(packet, pb); + else if (purple_strequal(packet->name, "iq")) + xep_iq_parse(packet, pb); + else { + purple_debug_warning("bonjour", "Unknown packet: %s\n", + packet->name ? packet->name : "(null)"); + } +} + +static void bonjour_xmpp_stream_ended(BonjourXMPPConversation *bconv) { + + /* Inform the user that the conversation has been closed */ + BonjourBuddy *bb = NULL; + const gchar *name = bconv->pb ? purple_buddy_get_name(bconv->pb) : "(unknown)"; + + purple_debug_info("bonjour", "Received conversation close notification from %s.\n", name); + + if(bconv->pb != NULL) + bb = purple_buddy_get_protocol_data(bconv->pb); + + /* Close the socket, clear the watcher and free memory */ + bonjour_xmpp_close_conversation(bconv); + if(bb) + bb->conversation = NULL; +} + +static gboolean +_client_socket_handler(GObject *stream, gpointer data) +{ + BonjourXMPPConversation *bconv = data; + GError *error = NULL; + gssize len; + static char message[4096]; + + /* Read the data from the socket */ + len = g_pollable_input_stream_read_nonblocking( + G_POLLABLE_INPUT_STREAM(stream), message, sizeof(message) - 1, + bconv->cancellable, &error); + if (len == -1) { + /* There has been an error reading from the socket */ + if (error == NULL || (error->code != G_IO_ERROR_WOULD_BLOCK && + error->code != G_IO_ERROR_CANCELLED)) { + purple_debug_warning( + "bonjour", + "receive of %" G_GSSIZE_FORMAT " error: %s", + len, error ? error->message : "(null)"); + + bonjour_xmpp_close_conversation(bconv); + if (bconv->pb != NULL) { + BonjourBuddy *bb = purple_buddy_get_protocol_data(bconv->pb); + + if(bb != NULL) + bb->conversation = NULL; + } + + /* I guess we really don't need to notify the user. + * If they try to send another message it'll reconnect */ + } + g_clear_error(&error); + return FALSE; + } else if (len == 0) { /* The other end has closed the socket */ + const gchar *name = purple_buddy_get_name(bconv->pb); + purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", (name) ? name : "(unknown)"); + bonjour_xmpp_stream_ended(bconv); + return FALSE; + } + + message[len] = '\0'; + + purple_debug_info("bonjour", "Receive: -%s- %" G_GSSIZE_FORMAT " bytes\n", message, len); + bonjour_parser_process(bconv, message, len); + + return TRUE; +} + +struct _stream_start_data { + char *msg; +}; + +static void +_start_stream(GObject *stream, gpointer data) +{ + BonjourXMPPConversation *bconv = data; + struct _stream_start_data *ss = bconv->stream_data; + GError *error = NULL; + gsize len; + gssize ret; + + len = strlen(ss->msg); + + /* Start Stream */ + ret = g_pollable_output_stream_write_nonblocking( + G_POLLABLE_OUTPUT_STREAM(stream), ss->msg, len, + bconv->cancellable, &error); + + if (ret == -1 && error->code == G_IO_ERROR_WOULD_BLOCK) { + g_clear_error(&error); + return; + } else if (ret <= 0) { + PurpleConversation *conv; + const char *bname = bconv->buddy_name; + BonjourBuddy *bb = NULL; + + if(bconv->pb) { + bb = purple_buddy_get_protocol_data(bconv->pb); + bname = purple_buddy_get_name(bconv->pb); + } + + purple_debug_error( + "bonjour", + "Error starting stream with buddy %s at %s error: %s", + bname ? bname : "(unknown)", bconv->ip, + error ? error->message : "(null)"); + + conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(bname, bconv->account)); + if (conv != NULL) + purple_conversation_write_system_message(conv, + _("Unable to send the message, the conversation couldn't be started."), + PURPLE_MESSAGE_ERROR); + + bonjour_xmpp_close_conversation(bconv); + if(bb != NULL) + bb->conversation = NULL; + + g_clear_error(&error); + return; + } + + /* This is EXTREMELY unlikely to happen */ + if (G_UNLIKELY(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 */ + g_source_remove(bconv->tx_handler); + bconv->tx_handler = 0; + bconv->sent_stream_start = FULLY_SENT; + + bonjour_xmpp_stream_started(bconv); +} + +static gboolean +bonjour_xmpp_send_stream_init(BonjourXMPPConversation *bconv, + GError **error) +{ + gchar *stream_start; + gsize len; + gssize ret; + const char *bname = bconv->buddy_name; + + g_return_val_if_fail(error != NULL, FALSE); + + if (bconv->pb != NULL) + bname = purple_buddy_get_name(bconv->pb); + + /* If we have no idea who "to" is, use an empty string. + * If we don't know now, it is because the other side isn't playing nice, so they can't complain. */ + if (bname == NULL) + bname = ""; + + stream_start = g_strdup_printf(DOCTYPE, bonjour_get_jid(bconv->account), bname); + len = strlen(stream_start); + + bconv->sent_stream_start = PARTIALLY_SENT; + + /* Start the stream */ + ret = g_pollable_output_stream_write_nonblocking( + G_POLLABLE_OUTPUT_STREAM(bconv->output), stream_start, len, + bconv->cancellable, error); + if (ret == -1 && (*error)->code == G_IO_ERROR_WOULD_BLOCK) { + ret = 0; + g_clear_error(error); + } else if (ret <= 0) { + purple_debug_error( + "bonjour", + "Error starting stream with buddy %s at %s error: %s", + (*bname) ? bname : "(unknown)", bconv->ip, + *error ? (*error)->message : "(null)"); + + if (bconv->pb) { + PurpleConversation *conv; + conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(bname, bconv->account)); + if (conv != NULL) + purple_conversation_write_system_message(conv, + _("Unable to send the message, the conversation couldn't be started."), + PURPLE_MESSAGE_ERROR); + } + + purple_gio_graceful_close(G_IO_STREAM(bconv->socket), + G_INPUT_STREAM(bconv->input), + G_OUTPUT_STREAM(bconv->output)); + g_clear_object(&bconv->socket); + bconv->input = NULL; + bconv->output = NULL; + g_free(stream_start); + + return FALSE; + } + + /* This is unlikely to happen */ + if (ret < len) { + GSource *source; + struct _stream_start_data *ss = g_new(struct _stream_start_data, 1); + ss->msg = g_strdup(stream_start + ret); + bconv->stream_data = ss; + /* Finish sending the stream start */ + source = g_pollable_output_stream_create_source( + G_POLLABLE_OUTPUT_STREAM(bconv->output), + bconv->cancellable); + g_source_set_callback(source, (GSourceFunc)_start_stream, bconv, + NULL); + bconv->tx_handler = g_source_attach(source, NULL); + } else { + bconv->sent_stream_start = FULLY_SENT; + } + + g_free(stream_start); + + return TRUE; +} + +/* This gets called when we've successfully sent our <stream:stream /> + * AND when we've received a <stream:stream /> */ +void +bonjour_xmpp_stream_started(BonjourXMPPConversation *bconv) +{ + GError *error = NULL; + + if (bconv->sent_stream_start == NOT_SENT && + !bonjour_xmpp_send_stream_init(bconv, &error)) { + const char *bname = bconv->buddy_name; + + if (bconv->pb) + bname = purple_buddy_get_name(bconv->pb); + + purple_debug_error( + "bonjour", + "Error starting stream with buddy %s at %s error: %s", + bname ? bname : "(unknown)", bconv->ip, + error ? error->message : "(null)"); + + if (bconv->pb) { + PurpleConversation *conv; + conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(bname, bconv->account)); + if (conv != NULL) + purple_conversation_write_system_message(conv, + _("Unable to send the message, the conversation couldn't be started."), + PURPLE_MESSAGE_ERROR); + } + + /* We don't want to recieve anything else */ + purple_gio_graceful_close(G_IO_STREAM(bconv->socket), + G_INPUT_STREAM(bconv->input), + G_OUTPUT_STREAM(bconv->output)); + g_clear_object(&bconv->socket); + bconv->input = NULL; + bconv->output = NULL; + + /* This must be asynchronous because it destroys the parser and we + * may be in the middle of parsing. + */ + async_bonjour_xmpp_close_conversation(bconv); + g_clear_error(&error); + return; + } + + /* If the stream has been completely started and we know who we're talking to, we can start doing stuff. */ + /* I don't think the circ_buffer can actually contain anything without a buddy being associated, but lets be explicit. */ + if (bconv->sent_stream_start == FULLY_SENT && bconv->recv_stream_start + && bconv->pb && purple_circular_buffer_get_max_read(bconv->tx_buf) > 0) { + /* Watch for when we can write the buffered messages */ + GSource *source = g_pollable_output_stream_create_source( + G_POLLABLE_OUTPUT_STREAM(bconv->output), + bconv->cancellable); + g_source_set_callback(source, (GSourceFunc)_send_data_write_cb, + bconv->pb, NULL); + bconv->tx_handler = g_source_attach(source, NULL); + /* We can probably write the data right now. */ + _send_data_write_cb(G_OBJECT(bconv->output), bconv->pb); + } +} + +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 46 +#endif + +static void +_server_socket_handler(GSocketService *service, GSocketConnection *connection, + GObject *source_object, gpointer data) +{ + BonjourXMPP *jdata = data; + GSocketAddress *their_addr; /* connector's address information */ + GInetAddress *their_inet_addr; + gchar *address_text; + struct _match_buddies_by_address *mbba; + BonjourXMPPConversation *bconv; + GSList *buddies; + GSource *source; + + their_addr = g_socket_connection_get_remote_address(connection, NULL); + if (their_addr == NULL) { + return; + } + their_inet_addr = g_inet_socket_address_get_address( + G_INET_SOCKET_ADDRESS(their_addr)); + + /* Look for the buddy that has opened the conversation and fill information */ + address_text = g_inet_address_to_string(their_inet_addr); + if (g_inet_address_get_family(their_inet_addr) == + G_SOCKET_FAMILY_IPV6 && + g_inet_address_get_is_link_local(their_inet_addr)) { + gchar *tmp = g_strdup_printf( + "%s%%%d", address_text, + g_inet_socket_address_get_scope_id( + G_INET_SOCKET_ADDRESS(their_addr))); + g_free(address_text); + address_text = tmp; + } + g_object_unref(their_addr); + + purple_debug_info("bonjour", "Received incoming connection from %s.\n", address_text); + mbba = g_new0(struct _match_buddies_by_address, 1); + mbba->address = address_text; + + buddies = purple_blist_find_buddies(jdata->account, NULL); + g_slist_foreach(buddies, _match_buddies_by_address, mbba); + g_slist_free(buddies); + + if (mbba->matched_buddies == NULL) { + purple_debug_info("bonjour", "We don't like invisible buddies, this is not a superheroes comic\n"); + g_free(address_text); + g_free(mbba); + return; + } + + g_slist_free(mbba->matched_buddies); + g_free(mbba); + + /* We've established that this *could* be from one of our buddies. + * Wait for the stream open to see if that matches too before assigning it. + */ + bconv = bonjour_xmpp_conv_new(NULL, jdata->account, address_text); + + /* We wait for the stream start before doing anything else */ + bconv->socket = g_object_ref(connection); + bconv->input = g_io_stream_get_input_stream(G_IO_STREAM(bconv->socket)); + bconv->output = + g_io_stream_get_output_stream(G_IO_STREAM(bconv->socket)); + source = g_pollable_input_stream_create_source( + G_POLLABLE_INPUT_STREAM(bconv->input), bconv->cancellable); + g_source_set_callback(source, (GSourceFunc)_client_socket_handler, + bconv, NULL); + bconv->rx_handler = g_source_attach(source, NULL); + g_free(address_text); +} + +gint +bonjour_xmpp_start(BonjourXMPP *jdata) +{ + GError *error = NULL; + guint16 port; + + purple_debug_info("bonjour", "Attempting to bind IP socket to port %d.", + jdata->port); + + /* Open a listening server for incoming conversations */ + jdata->service = g_socket_service_new(); + g_socket_listener_set_backlog(G_SOCKET_LISTENER(jdata->service), 10); + port = jdata->port; + if (!g_socket_listener_add_inet_port(G_SOCKET_LISTENER(jdata->service), + port, NULL, &error)) { + purple_debug_info("bonjour", + "Unable to bind to specified port %i: %s", + port, error ? error->message : "(unknown)"); + g_clear_error(&error); + port = g_socket_listener_add_any_inet_port( + G_SOCKET_LISTENER(jdata->service), NULL, &error); + if (port == 0) { + purple_debug_error( + "bonjour", "Unable to create socket: %s", + error ? error->message : "(unknown)"); + g_clear_error(&error); + return -1; + } + } + purple_debug_info("bonjour", "Bound IP socket to port %u.", port); + jdata->port = port; + + g_signal_connect(G_OBJECT(jdata->service), "incoming", + G_CALLBACK(_server_socket_handler), jdata); + + return jdata->port; +} + +static void +_connected_to_buddy(GObject *source, GAsyncResult *res, gpointer user_data) +{ + PurpleBuddy *pb = user_data; + BonjourBuddy *bb = purple_buddy_get_protocol_data(pb); + GSocketConnection *conn; + GSource *rx_source; + GError *error = NULL; + + conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source), + res, &error); + + if (conn == NULL) { + PurpleConversation *conv = NULL; + PurpleAccount *account = NULL; + GSList *tmp; + + if (error && error->code == G_IO_ERROR_CANCELLED) { + /* This conversation was closed before it started. */ + g_error_free(error); + return; + } + + purple_debug_error("bonjour", + "Error connecting to buddy %s at %s:%d " + "(%s); Trying next IP address", + purple_buddy_get_name(pb), + bb->conversation->ip, bb->port_p2pj, + error ? error->message : "(unknown)"); + g_clear_error(&error); + + /* There may be multiple entries for the same IP - one per + * presence recieved (e.g. multiple interfaces). + * We need to make sure that we find the previously used entry. + */ + tmp = g_slist_find(bb->ips, bb->conversation->ip_link); + if (tmp) + tmp = g_slist_next(tmp); + + account = purple_buddy_get_account(pb); + + if (tmp != NULL) { + const gchar *ip; + GSocketClient *client; + + bb->conversation->ip_link = ip = tmp->data; + + purple_debug_info("bonjour", "Starting conversation with %s at %s:%d\n", + purple_buddy_get_name(pb), ip, bb->port_p2pj); + + /* Make sure to connect without a proxy. */ + client = g_socket_client_new(); + if (client != NULL) { + g_free(bb->conversation->ip); + bb->conversation->ip = g_strdup(ip); + g_socket_client_connect_to_host_async( + client, ip, bb->port_p2pj, + bb->conversation->cancellable, + _connected_to_buddy, pb); + g_object_unref(client); + return; + } + } + + purple_debug_error("bonjour", "No more addresses for buddy %s. Aborting", purple_buddy_get_name(pb)); + + conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(bb->name, account)); + if (conv != NULL) + purple_conversation_write_system_message(conv, + _("Unable to send the message, the conversation couldn't be started."), + PURPLE_MESSAGE_ERROR); + + bonjour_xmpp_close_conversation(bb->conversation); + bb->conversation = NULL; + return; + } + + bb->conversation->socket = conn; + bb->conversation->input = + g_io_stream_get_input_stream(G_IO_STREAM(conn)); + bb->conversation->output = + g_io_stream_get_output_stream(G_IO_STREAM(conn)); + + if (!bonjour_xmpp_send_stream_init(bb->conversation, &error)) { + PurpleConversation *conv = NULL; + PurpleAccount *account = NULL; + + purple_debug_error("bonjour", + "Error starting stream with buddy %s at " + "%s:%d error: %s", + purple_buddy_get_name(pb), + bb->conversation->ip, bb->port_p2pj, + error ? error->message : "(null)"); + + account = purple_buddy_get_account(pb); + + conv = PURPLE_CONVERSATION(purple_conversations_find_im_with_account(bb->name, account)); + if (conv != NULL) + purple_conversation_write_system_message(conv, + _("Unable to send the message, the conversation couldn't be started."), + PURPLE_MESSAGE_ERROR); + + bonjour_xmpp_close_conversation(bb->conversation); + bb->conversation = NULL; + g_clear_error(&error); + return; + } + + /* Start listening for the stream acknowledgement */ + rx_source = g_pollable_input_stream_create_source( + G_POLLABLE_INPUT_STREAM(bb->conversation->input), + bb->conversation->cancellable); + g_source_set_callback(rx_source, (GSourceFunc)_client_socket_handler, + bb->conversation, NULL); + bb->conversation->rx_handler = g_source_attach(rx_source, NULL); +} + +void +bonjour_xmpp_conv_match_by_name(BonjourXMPPConversation *bconv) { + PurpleBuddy *pb = NULL; + BonjourBuddy *bb = NULL; + + g_return_if_fail(bconv->ip != NULL); + g_return_if_fail(bconv->pb == NULL); + + pb = purple_blist_find_buddy(bconv->account, bconv->buddy_name); + if (pb && (bb = purple_buddy_get_protocol_data(pb))) { + const char *ip; + GSList *tmp = bb->ips; + + purple_debug_info("bonjour", "Found buddy %s for incoming conversation \"from\" attrib.\n", + purple_buddy_get_name(pb)); + + /* Check that one of the buddy's IPs matches */ + while(tmp) { + ip = tmp->data; + if (ip != NULL && g_ascii_strcasecmp(ip, bconv->ip) == 0) { + PurpleConnection *pc = purple_account_get_connection(bconv->account); + BonjourData *bd = purple_connection_get_protocol_data(pc); + BonjourXMPP *jdata = bd->xmpp_data; + + purple_debug_info("bonjour", "Matched buddy %s to incoming conversation \"from\" attrib and IP (%s)\n", + purple_buddy_get_name(pb), bconv->ip); + + /* Attach conv. to buddy and remove from pending list */ + jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv); + + /* Check if the buddy already has a conversation and, if so, replace it */ + if(bb->conversation != NULL && bb->conversation != bconv) + bonjour_xmpp_close_conversation(bb->conversation); + + bconv->pb = pb; + bb->conversation = bconv; + + break; + } + tmp = tmp->next; + } + } + + /* We've failed to match a buddy - give up */ + if (bconv->pb == NULL) { + /* This must be asynchronous because it destroys the parser and we + * may be in the middle of parsing. + */ + async_bonjour_xmpp_close_conversation(bconv); + } +} + + +void +bonjour_xmpp_conv_match_by_ip(BonjourXMPPConversation *bconv) { + PurpleConnection *pc = purple_account_get_connection(bconv->account); + BonjourData *bd = purple_connection_get_protocol_data(pc); + BonjourXMPP *jdata = bd->xmpp_data; + struct _match_buddies_by_address *mbba; + GSList *buddies; + + mbba = g_new0(struct _match_buddies_by_address, 1); + mbba->address = bconv->ip; + + buddies = purple_blist_find_buddies(jdata->account, NULL); + g_slist_foreach(buddies, _match_buddies_by_address, mbba); + g_slist_free(buddies); + + /* If there is exactly one match, use it */ + if(mbba->matched_buddies != NULL) { + if(mbba->matched_buddies->next != NULL) + purple_debug_error("bonjour", "More than one buddy matched for ip %s.\n", bconv->ip); + else { + PurpleBuddy *pb = mbba->matched_buddies->data; + BonjourBuddy *bb = purple_buddy_get_protocol_data(pb); + + purple_debug_info("bonjour", "Matched buddy %s to incoming conversation using IP (%s)\n", + purple_buddy_get_name(pb), bconv->ip); + + /* Attach conv. to buddy and remove from pending list */ + jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv); + + /* Check if the buddy already has a conversation and, if so, replace it */ + if (bb->conversation != NULL && bb->conversation != bconv) + bonjour_xmpp_close_conversation(bb->conversation); + + bconv->pb = pb; + bb->conversation = bconv; + } + } else + purple_debug_error("bonjour", "No buddies matched for ip %s.\n", bconv->ip); + + /* We've failed to match a buddy - give up */ + if (bconv->pb == NULL) { + /* This must be asynchronous because it destroys the parser and we + * may be in the middle of parsing. + */ + async_bonjour_xmpp_close_conversation(bconv); + } + + g_slist_free(mbba->matched_buddies); + g_free(mbba); +} + +static PurpleBuddy * +_find_or_start_conversation(BonjourXMPP *jdata, const gchar *to) +{ + PurpleBuddy *pb = NULL; + BonjourBuddy *bb = NULL; + + g_return_val_if_fail(jdata != NULL, NULL); + g_return_val_if_fail(to != NULL, NULL); + + pb = purple_blist_find_buddy(jdata->account, to); + if (pb == NULL || (bb = purple_buddy_get_protocol_data(pb)) == NULL) + /* You can not send a message to an offline buddy */ + return NULL; + + /* Check if there is a previously open conversation */ + if (bb->conversation == NULL) { + GSocketClient *client; + /* Start with the first IP address. */ + const gchar *ip = bb->ips->data; + + purple_debug_info("bonjour", + "Starting conversation with %s at %s:%d", to, + ip, bb->port_p2pj); + + /* Make sure to connect without a proxy. */ + client = g_socket_client_new(); + if (client == NULL) { + purple_debug_error("bonjour", + "Unable to connect to buddy (%s).", + to); + return NULL; + } + + bb->conversation = bonjour_xmpp_conv_new(pb, jdata->account, ip); + bb->conversation->ip_link = ip; + + g_socket_client_connect_to_host_async( + client, ip, bb->port_p2pj, + bb->conversation->cancellable, _connected_to_buddy, pb); + g_object_unref(client); + } + return pb; +} + +int +bonjour_xmpp_send_message(BonjourXMPP *jdata, const gchar *to, const gchar *body) +{ + PurpleXmlNode *message_node, *node, *node2; + gchar *message, *xhtml; + PurpleBuddy *pb; + BonjourBuddy *bb; + int ret; + + pb = _find_or_start_conversation(jdata, to); + if (pb == NULL || (bb = purple_buddy_get_protocol_data(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; + } + + purple_markup_html_to_xhtml(body, &xhtml, &message); + + message_node = purple_xmlnode_new("message"); + purple_xmlnode_set_attrib(message_node, "to", bb->name); + purple_xmlnode_set_attrib(message_node, "from", bonjour_get_jid(jdata->account)); + purple_xmlnode_set_attrib(message_node, "type", "chat"); + + /* Enclose the message from the UI within a "font" node */ + node = purple_xmlnode_new_child(message_node, "body"); + purple_xmlnode_insert_data(node, message, strlen(message)); + g_free(message); + + node = purple_xmlnode_new_child(message_node, "html"); + purple_xmlnode_set_namespace(node, "http://www.w3.org/1999/xhtml"); + + node = purple_xmlnode_new_child(node, "body"); + message = g_strdup_printf("<font>%s</font>", xhtml); + node2 = purple_xmlnode_from_str(message, strlen(message)); + g_free(xhtml); + g_free(message); + purple_xmlnode_insert_child(node, node2); + + node = purple_xmlnode_new_child(message_node, "x"); + purple_xmlnode_set_namespace(node, "jabber:x:event"); + purple_xmlnode_insert_child(node, purple_xmlnode_new("composing")); + + message = purple_xmlnode_to_str(message_node, NULL); + purple_xmlnode_free(message_node); + + ret = _send_data(pb, message) >= 0; + + g_free(message); + + return ret; +} + +static gboolean +_async_bonjour_xmpp_close_conversation_cb(gpointer data) { + BonjourXMPPConversation *bconv = data; + bonjour_xmpp_close_conversation(bconv); + return FALSE; +} + +void +async_bonjour_xmpp_close_conversation(BonjourXMPPConversation *bconv) { + PurpleConnection *pc = purple_account_get_connection(bconv->account); + BonjourData *bd = purple_connection_get_protocol_data(pc); + BonjourXMPP *jdata = bd->xmpp_data; + + jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv); + + /* Disconnect this conv. from the buddy here so it can't be disposed of twice.*/ + if(bconv->pb != NULL) { + BonjourBuddy *bb = purple_buddy_get_protocol_data(bconv->pb); + if (bb->conversation == bconv) + bb->conversation = NULL; + } + + bconv->close_timeout = g_timeout_add(0, _async_bonjour_xmpp_close_conversation_cb, bconv); +} + +void +bonjour_xmpp_close_conversation(BonjourXMPPConversation *bconv) +{ + BonjourData *bd = NULL; + PurpleConnection *pc = NULL; + + if (bconv == NULL) { + return; + } + + pc = purple_account_get_connection(bconv->account); + PURPLE_ASSERT_CONNECTION_IS_VALID(pc); + + bd = purple_connection_get_protocol_data(pc); + if (bd) { + bd->xmpp_data->pending_conversations = g_slist_remove( + bd->xmpp_data->pending_conversations, bconv); + } + + /* Cancel any file transfers that are waiting to begin */ + /* There wont be any transfers if it hasn't been attached to a buddy */ + if (bconv->pb != NULL && bd != NULL) { + GSList *xfers, *tmp_next; + 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 (purple_strequal(purple_xfer_get_remote_user(xfer), purple_buddy_get_name(bconv->pb)) + && (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 != NULL) { + /* Send the end of the stream to the other end of the conversation */ + if (bconv->sent_stream_start == FULLY_SENT) { + size_t len = strlen(STREAM_END); + if (g_pollable_output_stream_write_nonblocking( + G_POLLABLE_OUTPUT_STREAM(bconv->output), + STREAM_END, len, bconv->cancellable, + NULL) != (gssize)len) { + purple_debug_error("bonjour", + "bonjour_xmpp_close_conversation: " + "couldn't send data\n"); + } + } + /* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */ + purple_gio_graceful_close(G_IO_STREAM(bconv->socket), + G_INPUT_STREAM(bconv->input), + G_OUTPUT_STREAM(bconv->output)); + } + if (bconv->rx_handler != 0) { + g_source_remove(bconv->rx_handler); + bconv->rx_handler = 0; + } + if (bconv->tx_handler != 0) { + g_source_remove(bconv->tx_handler); + bconv->tx_handler = 0; + } + + /* Cancel any pending operations. */ + if (bconv->cancellable != NULL) { + g_cancellable_cancel(bconv->cancellable); + g_clear_object(&bconv->cancellable); + } + + /* Free all the data related to the conversation */ + g_clear_object(&bconv->socket); + bconv->input = NULL; + bconv->output = NULL; + + g_object_unref(G_OBJECT(bconv->tx_buf)); + 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); + } + + if (bconv->close_timeout != 0) { + g_source_remove(bconv->close_timeout); + } + + g_free(bconv->buddy_name); + g_free(bconv->ip); + g_free(bconv); +} + +void +bonjour_xmpp_stop(BonjourXMPP *jdata) +{ + /* Close the server socket and remove the watcher */ + if (jdata->service) { + g_socket_service_stop(jdata->service); + g_socket_listener_close(G_SOCKET_LISTENER(jdata->service)); + g_clear_object(&jdata->service); + } + + /* Close all the conversation sockets and remove all the watchers after sending end streams */ + if (!purple_account_is_disconnected(jdata->account)) { + GSList *buddies, *l; + + buddies = purple_blist_find_buddies(jdata->account, NULL); + for (l = buddies; l; l = l->next) { + BonjourBuddy *bb = purple_buddy_get_protocol_data((PurpleBuddy*) l->data); + if (bb && bb->conversation) { + /* Any ongoing connection attempt is cancelled + * when a connection is destroyed */ + bonjour_xmpp_close_conversation(bb->conversation); + bb->conversation = NULL; + } + } + + g_slist_free(buddies); + } + + g_slist_free_full(jdata->pending_conversations, (GDestroyNotify)bonjour_xmpp_close_conversation); +} + +XepIq * +xep_iq_new(void *data, XepIqType type, const char *to, const char *from, const char *id) +{ + PurpleXmlNode *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 = purple_xmlnode_new("iq"); + + purple_xmlnode_set_attrib(iq_node, "to", to); + purple_xmlnode_set_attrib(iq_node, "from", from); + purple_xmlnode_set_attrib(iq_node, "id", id); + switch (type) { + case XEP_IQ_SET: + purple_xmlnode_set_attrib(iq_node, "type", "set"); + break; + case XEP_IQ_GET: + purple_xmlnode_set_attrib(iq_node, "type", "get"); + break; + case XEP_IQ_RESULT: + purple_xmlnode_set_attrib(iq_node, "type", "result"); + break; + case XEP_IQ_ERROR: + purple_xmlnode_set_attrib(iq_node, "type", "error"); + break; + case XEP_IQ_NONE: + default: + purple_xmlnode_set_attrib(iq_node, "type", "none"); + break; + } + + iq = g_new0(XepIq, 1); + iq->node = iq_node; + iq->type = type; + iq->data = ((BonjourData*)data)->xmpp_data; + iq->to = (char*)to; + + return iq; +} + +static gboolean +check_if_blocked(PurpleBuddy *pb) +{ + gboolean blocked = FALSE; + GSList *l = NULL; + PurpleAccount *acc = purple_buddy_get_account(pb); + const gchar *name; + + if(acc == NULL) + return FALSE; + + l = purple_account_privacy_get_denied(acc); + name = purple_buddy_get_name(pb); + + if(g_slist_find_custom(l, name, (GCompareFunc)purple_utf8_strcasecmp) != NULL) { + const gchar *username = bonjour_get_jid(acc); + + purple_debug_info("bonjour", "%s has been blocked by %s.\n", name, username); + blocked = TRUE; + } + return blocked; +} + +static void +xep_iq_parse(PurpleXmlNode *packet, PurpleBuddy *pb) +{ + PurpleAccount *account; + PurpleConnection *gc; + + if(check_if_blocked(pb)) + return; + + account = purple_buddy_get_account(pb); + gc = purple_account_get_connection(account); + + if (purple_xmlnode_get_child(packet, "si") != NULL || purple_xmlnode_get_child(packet, "error") != NULL) + xep_si_parse(gc, packet, pb); + else + xep_bytestreams_parse(gc, 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((BonjourXMPP*) iq->data, iq->to); + /* Send the message */ + if (pb != NULL) { + /* Convert xml node into stream */ + gchar *msg = purple_xmlnode_to_str(iq->node, NULL); + ret = _send_data(pb, msg); + g_free(msg); + } + + purple_xmlnode_free(iq->node); + iq->node = NULL; + g_free(iq); + + return (ret >= 0) ? 0 : -1; +} + +/* This returns a list containing all non-localhost IPs */ +GSList * +bonjour_xmpp_get_local_ips(int fd) +{ + GSList *ips = NULL; + const char *address_text; + int ret; + +#ifdef HAVE_GETIFADDRS /* This is required for IPv6 */ + struct ifaddrs *ifap, *ifa; + common_sockaddr_t addr; + char addrstr[INET6_ADDRSTRLEN]; + + ret = getifaddrs(&ifap); + if (ret != 0) { + const char *error = g_strerror(errno); + purple_debug_error("bonjour", "getifaddrs() error: %s\n", error ? error : "(null)"); + return NULL; + } + + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { + if (!(ifa->ifa_flags & IFF_RUNNING) || (ifa->ifa_flags & IFF_LOOPBACK) || ifa->ifa_addr == NULL) + continue; + + memcpy(&addr, ifa->ifa_addr, sizeof(addr)); + address_text = NULL; + switch (addr.sa.sa_family) { + case AF_INET: + address_text = inet_ntop(addr.sa.sa_family, + &addr.in.sin_addr, + addrstr, sizeof(addrstr)); + break; +#ifdef PF_INET6 + case AF_INET6: + address_text = inet_ntop(addr.sa.sa_family, + &addr.in6.sin6_addr, + addrstr, sizeof(addrstr)); + break; +#endif + } + + if (address_text != NULL) { + if (addr.sa.sa_family == AF_INET) + ips = g_slist_append(ips, g_strdup(address_text)); + else + ips = g_slist_prepend(ips, g_strdup(address_text)); + } + } + + freeifaddrs(ifap); +#else + char *tmp; + struct ifconf ifc; + struct ifreq *ifr; + char buffer[1024]; + struct sockaddr_in *sinptr; + int source = fd; + + if (fd < 0) + source = socket(PF_INET, SOCK_STREAM, 0); + + ifc.ifc_len = sizeof(buffer); + ifc.ifc_req = (struct ifreq *)buffer; + ret = ioctl(source, SIOCGIFCONF, &ifc); + + if (fd < 0) + close(source); + + if (ret < 0) { + const char *error = g_strerror(errno); + purple_debug_error("bonjour", "ioctl(SIOCGIFCONF) error: %s\n", error ? error : "(null)"); + return NULL; + } + + tmp = buffer; + while (tmp < buffer + ifc.ifc_len) { + 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 ((ntohl(sinptr->sin_addr.s_addr) >> 24) != 127) { + address_text = inet_ntoa(sinptr->sin_addr); + ips = g_slist_prepend(ips, g_strdup(address_text)); + } + } + } +#endif + + return ips; +} + +void +append_iface_if_linklocal(char *ip, guint32 interface_param) { + struct in6_addr in6_addr; + int len_remain = INET6_ADDRSTRLEN - strlen(ip); + + if (len_remain <= 1) + return; + + if (inet_pton(AF_INET6, ip, &in6_addr) != 1 || + !IN6_IS_ADDR_LINKLOCAL(&in6_addr)) + return; + + snprintf(ip + strlen(ip), len_remain, "%%%d", + interface_param); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/bonjour/xmpp.h Sat Oct 26 16:53:30 2019 +0000 @@ -0,0 +1,112 @@ +/** + * @file xmpp.h The Purple interface to mDNS and peer to peer XMPP. + * + * purple + * + * 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 + * + */ + +#ifndef PURPLE_BONJOUR_XMPP_H +#define PURPLE_BONJOUR_XMPP_H + +#include <libxml/parser.h> + +#include <purple.h> + +typedef struct +{ + GSocketService *service; + guint16 port; + PurpleAccount *account; + GSList *pending_conversations; +} BonjourXMPP; + +typedef struct +{ + GCancellable *cancellable; + GSocketConnection *socket; + GInputStream *input; + GOutputStream *output; + guint rx_handler; + guint tx_handler; + guint close_timeout; + PurpleCircularBuffer *tx_buf; + int sent_stream_start; /* 0 = Unsent, 1 = Partial, 2 = Complete */ + gboolean recv_stream_start; + gpointer stream_data; + xmlParserCtxt *context; + PurpleXmlNode *current; + PurpleBuddy *pb; + PurpleAccount *account; + + /* The following are only needed before attaching to a PurpleBuddy */ + gchar *buddy_name; + gchar *ip; + /* This points to a data entry in BonjourBuddy->ips */ + const gchar *ip_link; +} BonjourXMPPConversation; + +/** + * Start listening for xmpp connections. + * + * @return -1 if there was a problem, else returns the listening + * port number. + */ +gint bonjour_xmpp_start(BonjourXMPP *data); + +int bonjour_xmpp_send_message(BonjourXMPP *data, const char *to, const char *body); + +void bonjour_xmpp_close_conversation(BonjourXMPPConversation *bconv); + +void async_bonjour_xmpp_close_conversation(BonjourXMPPConversation *bconv); + +void bonjour_xmpp_stream_started(BonjourXMPPConversation *bconv); + +void bonjour_xmpp_process_packet(PurpleBuddy *pb, PurpleXmlNode *packet); + +void bonjour_xmpp_stop(BonjourXMPP *data); + +void bonjour_xmpp_conv_match_by_ip(BonjourXMPPConversation *bconv); + +void bonjour_xmpp_conv_match_by_name(BonjourXMPPConversation *bconv); + +typedef enum { + XEP_IQ_SET, + XEP_IQ_GET, + XEP_IQ_RESULT, + XEP_IQ_ERROR, + XEP_IQ_NONE +} XepIqType; + +typedef struct { + XepIqType type; + char *id; + PurpleXmlNode *node; + char *to; + void *data; +} XepIq; + +XepIq *xep_iq_new(void *data, XepIqType type, const char *to, const char *from, const char *id); +int xep_iq_send_and_free(XepIq *iq); +GSList * bonjour_xmpp_get_local_ips(int fd); + +void append_iface_if_linklocal(char *ip, guint32 interface_param); + +#endif /* PURPLE_BONJOUR_XMPP_H */
--- a/po/POTFILES.in Fri Oct 25 05:52:30 2019 -0400 +++ b/po/POTFILES.in Sat Oct 26 16:53:30 2019 +0000 @@ -98,11 +98,11 @@ libpurple/protocols/bonjour/bonjour_ft.c libpurple/protocols/bonjour/buddy.c libpurple/protocols/bonjour/dns_sd_proxy.c -libpurple/protocols/bonjour/jabber.c libpurple/protocols/bonjour/mdns_avahi.c libpurple/protocols/bonjour/mdns_common.c libpurple/protocols/bonjour/mdns_dns_sd.c libpurple/protocols/bonjour/parser.c +libpurple/protocols/bonjour/xmpp.c libpurple/protocols.c libpurple/protocols/facebook/api.c libpurple/protocols/facebook/data.c