--- a/libpurple/protocols/bonjour/bonjour_ft.c Tue Apr 09 23:11:12 2024 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1228 +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 02110-1301, USA - */ - -#include <purpleconfig.h> - -#include <sys/types.h> - -#include <purple.h> - -#include "bonjour.h" -#include "bonjour_ft.h" - -static void -bonjour_bytestreams_init(PurpleXfer *xfer); -static void -bonjour_bytestreams_connect(PurpleXfer *xfer); -static void -bonjour_xfer_init(PurpleXfer *xfer); -static void -bonjour_xfer_receive(PurpleConnection *pc, const char *id, const char *sid, const char *from, - goffset filesize, const char *filename, int option); - -/* Look for specific xfer handle */ -static unsigned int next_id = 0; - -struct _XepXfer -{ - PurpleXfer parent; - - GCancellable *cancellable; - void *data; - char *filename; - int filesize; - char *iq_id; - char *sid; - char *recv_id; - char *buddy_ip; - int mode; - GSocketClient *client; - GSocketService *service; - GSocketConnection *conn; - char rx_buf[0x500]; - char tx_buf[0x500]; - char *jid; - char *proxy_host; - int proxy_port; - PurpleXmlNode *streamhost; - PurpleContact *contact; -}; - -G_DEFINE_DYNAMIC_TYPE_EXTENDED(XepXfer, xep_xfer, PURPLE_TYPE_XFER, - G_TYPE_FLAG_FINAL, {}) - -static void -xep_ft_si_reject(BonjourData *bd, const char *id, const char *to, const char *error_code, const char *error_type) -{ - PurpleXmlNode *error_node; - XepIq *iq; - - g_return_if_fail(error_code != NULL); - g_return_if_fail(error_type != NULL); - - if(!to || !id) { - purple_debug_info("bonjour", "xep file transfer stream initialization error.\n"); - return; - } - - iq = xep_iq_new(bd, XEP_IQ_ERROR, to, bonjour_get_jid(bd->xmpp_data->account), id); - if(iq == NULL) - return; - - error_node = purple_xmlnode_new_child(iq->node, "error"); - purple_xmlnode_set_attrib(error_node, "code", error_code); - purple_xmlnode_set_attrib(error_node, "type", error_type); - - /* TODO: Make this better */ - if (purple_strequal(error_code, "403")) { - PurpleXmlNode *tmp_node = purple_xmlnode_new_child(error_node, "forbidden"); - purple_xmlnode_set_namespace(tmp_node, "urn:ietf:params:xml:ns:xmpp-stanzas"); - - tmp_node = purple_xmlnode_new_child(error_node, "text"); - purple_xmlnode_set_namespace(tmp_node, "urn:ietf:params:xml:ns:xmpp-stanzas"); - purple_xmlnode_insert_data(tmp_node, "Offer Declined", -1); - } else if (purple_strequal(error_code, "404")) { - PurpleXmlNode *tmp_node = purple_xmlnode_new_child(error_node, "item-not-found"); - purple_xmlnode_set_namespace(tmp_node, "urn:ietf:params:xml:ns:xmpp-stanzas"); - } - - xep_iq_send_and_free(iq); -} - -static void bonjour_xfer_cancel_send(PurpleXfer *xfer) -{ - XepXfer *xf = XEP_XFER(xfer); - - purple_debug_info("bonjour", "Bonjour-xfer-cancel-send.\n"); - g_cancellable_cancel(xf->cancellable); -} - -static void bonjour_xfer_request_denied(PurpleXfer *xfer) -{ - XepXfer *xf = XEP_XFER(xfer); - - purple_debug_info("bonjour", "Bonjour-xfer-request-denied.\n"); - - if(xf) { - xep_ft_si_reject(xf->data, xf->sid, purple_xfer_get_remote_user(xfer), "403", "cancel"); - } -} - -static void bonjour_xfer_cancel_recv(PurpleXfer *xfer) -{ - XepXfer *xf = XEP_XFER(xfer); - - purple_debug_info("bonjour", "Bonjour-xfer-cancel-recv.\n"); - g_cancellable_cancel(xf->cancellable); -} - -static void bonjour_xfer_end(PurpleXfer *xfer) -{ - purple_debug_info("bonjour", "Bonjour-xfer-end for xfer %p", xfer); - - /* We can't allow the server side to close the connection until the client is complete, - * otherwise there is a RST resulting in an error on the client side */ - if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_SEND && purple_xfer_is_completed(xfer)) { - XepXfer *xf = XEP_XFER(xfer); - g_io_stream_close_async(G_IO_STREAM(xf->conn), G_PRIORITY_DEFAULT, xf->cancellable, NULL, NULL); - purple_xfer_set_fd(xfer, -1); - } -} - -static PurpleXfer* -bonjour_si_xfer_find(BonjourData *bd, const char *sid, const char *from) -{ - GSList *xfers; - PurpleXfer *xfer; - XepXfer *xf; - - if(!sid || !from || !bd) - return NULL; - - purple_debug_info("bonjour", "Look for sid=%s from=%s xferlists.\n", - sid, from); - - for(xfers = bd->xfer_lists; xfers; xfers = xfers->next) { - xfer = xfers->data; - if(xfer == NULL) - break; - xf = XEP_XFER(xfer); - if(xf == NULL) - break; - if(xf->sid && purple_xfer_get_remote_user(xfer) && purple_strequal(xf->sid, sid) && - purple_strequal(purple_xfer_get_remote_user(xfer), from)) - return xfer; - } - - purple_debug_info("bonjour", "Look for xfer list fail\n"); - - return NULL; -} - -static void -xep_ft_si_offer(PurpleXfer *xfer, const gchar *to) -{ - PurpleXmlNode *si_node, *feature, *field, *file, *x; - XepIq *iq; - XepXfer *xf = XEP_XFER(xfer); - BonjourData *bd = NULL; - char buf[32]; - - if(!xf) - return; - - bd = xf->data; - if(!bd) - return; - - purple_debug_info("bonjour", "xep file transfer stream initialization offer-id=%d.\n", next_id); - - /* 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->xmpp_data->account), xf->iq_id); - if(iq == NULL) - return; - - /*Construct Stream initialization offer message.*/ - si_node = purple_xmlnode_new_child(iq->node, "si"); - purple_xmlnode_set_namespace(si_node, "http://jabber.org/protocol/si"); - purple_xmlnode_set_attrib(si_node, "profile", "http://jabber.org/protocol/si/profile/file-transfer"); - g_free(xf->sid); - xf->sid = g_strdup(xf->iq_id); - purple_xmlnode_set_attrib(si_node, "id", xf->sid); - - file = purple_xmlnode_new_child(si_node, "file"); - purple_xmlnode_set_namespace(file, "http://jabber.org/protocol/si/profile/file-transfer"); - purple_xmlnode_set_attrib(file, "name", purple_xfer_get_filename(xfer)); - g_snprintf(buf, sizeof(buf), "%" G_GOFFSET_FORMAT, purple_xfer_get_size(xfer)); - purple_xmlnode_set_attrib(file, "size", buf); - - feature = purple_xmlnode_new_child(si_node, "feature"); - purple_xmlnode_set_namespace(feature, "http://jabber.org/protocol/feature-neg"); - - x = purple_xmlnode_new_child(feature, "x"); - purple_xmlnode_set_namespace(x, "jabber:x:data"); - purple_xmlnode_set_attrib(x, "type", "form"); - - field = purple_xmlnode_new_child(x, "field"); - purple_xmlnode_set_attrib(field, "var", "stream-method"); - purple_xmlnode_set_attrib(field, "type", "list-single"); - - if (xf->mode & XEP_BYTESTREAMS) { - PurpleXmlNode *option = purple_xmlnode_new_child(field, "option"); - PurpleXmlNode *value = purple_xmlnode_new_child(option, "value"); - purple_xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", -1); - } - if (xf->mode & XEP_IBB) { - PurpleXmlNode *option = purple_xmlnode_new_child(field, "option"); - PurpleXmlNode *value = purple_xmlnode_new_child(option, "value"); - purple_xmlnode_insert_data(value, "http://jabber.org/protocol/ibb", -1); - } - - xep_iq_send_and_free(iq); -} - -static void -xep_ft_si_result(PurpleXfer *xfer, const char *to) -{ - PurpleXmlNode *si_node, *feature, *field, *value, *x; - XepIq *iq; - XepXfer *xf; - BonjourData *bd; - - if(!to || !xfer) - return; - xf = XEP_XFER(xfer); - - 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->xmpp_data->account), xf->iq_id); - if(iq == NULL) - return; - - si_node = purple_xmlnode_new_child(iq->node, "si"); - purple_xmlnode_set_namespace(si_node, "http://jabber.org/protocol/si"); - /*purple_xmlnode_set_attrib(si_node, "profile", "http://jabber.org/protocol/si/profile/file-transfer");*/ - - feature = purple_xmlnode_new_child(si_node, "feature"); - purple_xmlnode_set_namespace(feature, "http://jabber.org/protocol/feature-neg"); - - x = purple_xmlnode_new_child(feature, "x"); - purple_xmlnode_set_namespace(x, "jabber:x:data"); - purple_xmlnode_set_attrib(x, "type", "submit"); - - field = purple_xmlnode_new_child(x, "field"); - purple_xmlnode_set_attrib(field, "var", "stream-method"); - - value = purple_xmlnode_new_child(field, "value"); - purple_xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", -1); - - xep_iq_send_and_free(iq); -} - -/** - * Frees the whole tree of an xml node - * - * First determines the root of the xml tree and then frees the whole tree - * from there. - * - * @param node The node to free the tree from - */ -static void -purple_xmlnode_free_tree(PurpleXmlNode *node) -{ - g_return_if_fail(node != NULL); - - while(purple_xmlnode_get_parent(node)) - node = purple_xmlnode_get_parent(node); - - purple_xmlnode_free(node); -} - -PurpleXfer * -bonjour_new_xfer(G_GNUC_UNUSED PurpleProtocolXfer *prplxfer, - PurpleConnection *gc, const char *who) -{ - PurpleXfer *xfer; - XepXfer *xep_xfer; - BonjourData *bd; - - if(who == NULL || gc == NULL) - return NULL; - - purple_debug_info("bonjour", "Bonjour-new-xfer to %s.\n", who); - bd = purple_connection_get_protocol_data(gc); - if(bd == NULL) - return NULL; - - /* Build the file transfer handle */ - xep_xfer = g_object_new( - XEP_TYPE_XFER, - "account", purple_connection_get_account(gc), - "type", PURPLE_XFER_TYPE_SEND, - "remote-user", who, - NULL - ); - xfer = PURPLE_XFER(xep_xfer); - xep_xfer->data = bd; - - purple_debug_info("bonjour", "Bonjour-new-xfer bd=%p data=%p.\n", bd, xep_xfer->data); - - /* We don't support IBB yet */ - /*xep_xfer->mode = XEP_BYTESTREAMS | XEP_IBB;*/ - xep_xfer->mode = XEP_BYTESTREAMS; - xep_xfer->sid = NULL; - - bd->xfer_lists = g_slist_append(bd->xfer_lists, xfer); - - return xfer; -} - -void -bonjour_send_file(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who, const char *file) -{ - PurpleXfer *xfer; - - g_return_if_fail(gc != NULL); - g_return_if_fail(who != NULL); - - purple_debug_info("bonjour", "Bonjour-send-file to=%s.\n", who); - - xfer = bonjour_new_xfer(prplxfer, gc, who); - - if (file) - purple_xfer_request_accepted(xfer, file); - else - purple_xfer_request(xfer); - -} - -static void -bonjour_xfer_init(PurpleXfer *xfer) -{ - PurpleContact *contact = NULL; - PurpleContactManager *manager = NULL; - BonjourBuddy *bb; - XepXfer *xf; - - xf = XEP_XFER(xfer); - - purple_debug_info("bonjour", "Bonjour-xfer-init.\n"); - - manager = purple_contact_manager_get_default(); - contact = purple_contact_manager_find_with_username(manager, - purple_xfer_get_account(xfer), - purple_xfer_get_remote_user(xfer)); - - /* this buddy is offline. */ - if(!PURPLE_IS_CONTACT(contact) || - (bb = g_object_get_data(G_OBJECT(contact), "bonjour-buddy")) == NULL) - { - g_clear_object(&contact); - - return; - } - - /* Assume it is the first IP. We could do something like keep track of which one is in use or something. */ - if (bb->ips) - xf->buddy_ip = g_strdup(bb->ips->data); - if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_SEND) { - /* initiate file transfer, send SI offer. */ - purple_debug_info("bonjour", "Bonjour xfer type is PURPLE_XFER_TYPE_SEND.\n"); - xep_ft_si_offer(xfer, purple_xfer_get_remote_user(xfer)); - } else { - /* accept file transfer request, send SI result. */ - xep_ft_si_result(xfer, purple_xfer_get_remote_user(xfer)); - purple_debug_info("bonjour", "Bonjour xfer type is PURPLE_XFER_TYPE_RECEIVE.\n"); - } - - g_clear_object(&contact); -} - -void -xep_si_parse(PurpleConnection *pc, PurpleXmlNode *packet, - PurpleContact *contact) -{ - const char *type, *id; - BonjourData *bd; - PurpleXfer *xfer; - const gchar *name = NULL; - - g_return_if_fail(pc != NULL); - g_return_if_fail(packet != NULL); - g_return_if_fail(PURPLE_IS_CONTACT(contact)); - - bd = purple_connection_get_protocol_data(pc); - if(bd == NULL) { - return; - } - - purple_debug_info("bonjour", "xep-si-parse.\n"); - - name = purple_contact_info_get_username(PURPLE_CONTACT_INFO(contact)); - - type = purple_xmlnode_get_attrib(packet, "type"); - id = purple_xmlnode_get_attrib(packet, "id"); - if(!type) - return; - - if(purple_strequal(type, "set")) { - PurpleXmlNode *si; - gboolean parsed_receive = FALSE; - - si = purple_xmlnode_get_child(packet, "si"); - - purple_debug_info("bonjour", "si offer Message type - SET.\n"); - if (si) { - const char *profile; - - profile = purple_xmlnode_get_attrib(si, "profile"); - - if (purple_strequal(profile, "http://jabber.org/protocol/si/profile/file-transfer")) { - const char *filename = NULL, *filesize_str = NULL; - goffset filesize = 0; - PurpleXmlNode *file; - - const char *sid = purple_xmlnode_get_attrib(si, "id"); - - if ((file = purple_xmlnode_get_child(si, "file"))) { - filename = purple_xmlnode_get_attrib(file, "name"); - if((filesize_str = purple_xmlnode_get_attrib(file, "size"))) - filesize = g_ascii_strtoll(filesize_str, NULL, 10); - } - - /* TODO: Make sure that it is advertising a bytestreams transfer */ - - if (filename) { - bonjour_xfer_receive(pc, id, sid, name, filesize, filename, XEP_BYTESTREAMS); - - parsed_receive = TRUE; - } - } - } - - if (!parsed_receive) { - BonjourData *bd = purple_connection_get_protocol_data(pc); - - purple_debug_info("bonjour", "rejecting unrecognized si SET offer.\n"); - xep_ft_si_reject(bd, id, name, "403", "cancel"); - /*TODO: Send Cancel (501) */ - } - } else if(purple_strequal(type, "result")) { - purple_debug_info("bonjour", "si offer Message type - RESULT.\n"); - - xfer = bonjour_si_xfer_find(bd, id, name); - - if(xfer == NULL) { - BonjourData *bd = purple_connection_get_protocol_data(pc); - purple_debug_info("bonjour", "xfer find fail.\n"); - xep_ft_si_reject(bd, id, name, "403", "cancel"); - } else - bonjour_bytestreams_init(xfer); - - } else if(purple_strequal(type, "error")) { - purple_debug_info("bonjour", "si offer Message type - ERROR.\n"); - - xfer = bonjour_si_xfer_find(bd, id, name); - - if(xfer == NULL) - purple_debug_info("bonjour", "xfer find fail.\n"); - else - purple_xfer_cancel_remote(xfer); - } else - purple_debug_info("bonjour", "si offer Message type - Unknown-%s.\n", type); -} - -/** - * Will compare a host with a buddy_ip. - * - * Additionally to a common 'purple_strequal(host, buddy_ip)', it will also return TRUE - * if 'host' is a link local IPv6 address without an appended interface - * identifier and 'buddy_ip' string is "host" + "%iface". - * - * Note: This may theoretically result in the attempt to connect to the wrong - * host, because we do not know for sure which interface the according link - * local IPv6 address might relate to and RFC4862 for instance only ensures the - * uniqueness of this address on a given link. So we could possibly have two - * distinct buddies with the same ipv6 link local address on two distinct - * interfaces. Unfortunately XEP-0065 does not seem to specify how to deal with - * link local ip addresses properly... - * However, in practice the possibility for such a conflict is relatively low - * (2011 - might be different in the future though?). - * - * @param host ipv4 or ipv6 address string - * @param buddy_ip ipv4 or ipv6 address string - * @return TRUE if they match, FALSE otherwise - */ -static gboolean -xep_cmp_addr(const char *host, const char *buddy_ip) -{ - GInetAddress *addr = NULL; - - addr = g_inet_address_new_from_string(host); - if (addr != NULL && - g_inet_address_get_family(addr) == G_SOCKET_FAMILY_IPV6 && - g_inet_address_get_is_link_local(addr)) { - g_clear_object(&addr); - - if (strlen(buddy_ip) <= strlen(host) || buddy_ip[strlen(host)] != '%') { - return FALSE; - } - - return !strncmp(host, buddy_ip, strlen(host)); - } else { - g_clear_object(&addr); - return purple_strequal(host, buddy_ip); - } -} - -static inline gint -xep_addr_differ(const char *buddy_ip, const char *host) -{ - return !xep_cmp_addr(host, buddy_ip); -} - -/** - * Create and insert an identical twin - * - * Creates a copy of the specified node and inserts it right after - * this original node. - * - * @param node The node to clone - * @return A pointer to the new, cloned twin if successful - * or NULL otherwise. - */ -static PurpleXmlNode * -purple_xmlnode_insert_twin_copy(PurpleXmlNode *node) { - PurpleXmlNode *copy; - - g_return_val_if_fail(node != NULL, NULL); - - copy = purple_xmlnode_copy(node); - g_return_val_if_fail(copy != NULL, NULL); - - copy->next = node->next; - node->next = copy; - - return copy; -} - -/** - * Tries to append an interface scope to an IPv6 link local address. - * - * If the given address is a link local IPv6 address (with no - * interface scope) then we try to determine all fitting interfaces - * from our Bonjour IP address list. - * - * For any such found matches we insert a copy of our current xml - * streamhost entry right after this streamhost entry and append - * the determined interface to the host address of this copy. - * - * @param cur_streamhost The XML streamhost node we examine - * @param host The host address to examine in text form - * @param pb Buddy to get the list of link local IPv6 addresses - * and their interface from - * @return Returns TRUE if the specified 'host' address is a - * link local IPv6 address with no interface scope. - * Otherwise returns FALSE. - */ -static gboolean -add_ipv6_link_local_ifaces(PurpleXmlNode *cur_streamhost, const char *host, - PurpleContact *contact) -{ - PurpleXmlNode *new_streamhost = NULL; - GInetAddress *addr; - BonjourBuddy *bb; - GSList *ip_elem; - - addr = g_inet_address_new_from_string(host); - if (addr == NULL || - g_inet_address_get_family(addr) != G_SOCKET_FAMILY_IPV6 || - !g_inet_address_get_is_link_local(addr) || - strchr(host, '%')) - { - g_clear_object(&addr); - return FALSE; - } - g_clear_object(&addr); - - bb = g_object_get_data(G_OBJECT(contact), "bonjour-buddy"); - - for (ip_elem = bb->ips; - (ip_elem = g_slist_find_custom(ip_elem, host, (GCompareFunc)&xep_addr_differ)); - ip_elem = ip_elem->next) { - purple_debug_info("bonjour", "Inserting an PurpleXmlNode twin copy for %s with new host address %s\n", - host, (char*)ip_elem->data); - new_streamhost = purple_xmlnode_insert_twin_copy(cur_streamhost); - purple_xmlnode_set_attrib(new_streamhost, "host", ip_elem->data); - } - - if (!new_streamhost) - purple_debug_info("bonjour", "No interface for this IPv6 link local address found: %s\n", - host); - - return TRUE; -} - -static gboolean -__xep_bytestreams_parse(PurpleContact *contact, PurpleXfer *xfer, - PurpleXmlNode *streamhost, const char *iq_id) -{ - char *tmp_iq_id; - const char *jid, *host, *port; - int portnum; - XepXfer *xf = XEP_XFER(xfer); - - for(; streamhost; streamhost = purple_xmlnode_get_next_twin(streamhost)) { - if(!(jid = purple_xmlnode_get_attrib(streamhost, "jid")) || - !(host = purple_xmlnode_get_attrib(streamhost, "host")) || - !(port = purple_xmlnode_get_attrib(streamhost, "port")) || - !(portnum = atoi(port))) { - purple_debug_info("bonjour", "bytestream offer Message parse error.\n"); - continue; - } - - /* skip IPv6 link local addresses with no interface scope - * (but try to add a new one with an interface scope then) */ - if(add_ipv6_link_local_ifaces(streamhost, host, contact)) - continue; - - tmp_iq_id = g_strdup(iq_id); - g_free(xf->iq_id); - g_free(xf->jid); - g_free(xf->proxy_host); - - xf->iq_id = tmp_iq_id; - xf->jid = g_strdup(jid); - xf->proxy_host = g_strdup(host); - xf->proxy_port = portnum; - xf->streamhost = streamhost; - xf->contact = contact; - purple_debug_info("bonjour", "bytestream offer parse" - "jid=%s host=%s port=%d.\n", jid, host, portnum); - bonjour_bytestreams_connect(xfer); - return TRUE; - } - - return FALSE; -} - -void -xep_bytestreams_parse(PurpleConnection *pc, PurpleXmlNode *packet, - PurpleContact *contact) -{ - const char *type, *from, *iq_id, *sid; - PurpleXmlNode *query, *streamhost; - BonjourData *bd; - PurpleXfer *xfer; - - g_return_if_fail(pc != NULL); - g_return_if_fail(packet != NULL); - g_return_if_fail(PURPLE_IS_CONTACT(contact)); - - bd = purple_connection_get_protocol_data(pc); - if(bd == NULL) { - return; - } - - purple_debug_info("bonjour", "xep-bytestreams-parse.\n"); - - type = purple_xmlnode_get_attrib(packet, "type"); - from = purple_contact_info_get_username(PURPLE_CONTACT_INFO(contact)); - query = purple_xmlnode_get_child(packet,"query"); - if(!type) - return; - - query = purple_xmlnode_copy(query); - if (!query) - return; - - if(!purple_strequal(type, "set")) { - purple_debug_info("bonjour", "bytestream offer Message type - Unknown-%s.\n", type); - return; - } - - purple_debug_info("bonjour", "bytestream offer Message type - SET.\n"); - - iq_id = purple_xmlnode_get_attrib(packet, "id"); - - sid = purple_xmlnode_get_attrib(query, "sid"); - xfer = bonjour_si_xfer_find(bd, sid, from); - streamhost = purple_xmlnode_get_child(query, "streamhost"); - - if(xfer && streamhost && __xep_bytestreams_parse(contact, xfer, streamhost, iq_id)) - return; /* success */ - - purple_debug_error("bonjour", "Didn't find an acceptable streamhost.\n"); - - if (iq_id && xfer != NULL) - xep_ft_si_reject(bd, iq_id, purple_xfer_get_remote_user(xfer), "404", "cancel"); -} - -static void -bonjour_xfer_receive(PurpleConnection *pc, const char *id, const char *sid, - const char *from, goffset filesize, const char *filename, - G_GNUC_UNUSED int option) -{ - PurpleXfer *xfer; - XepXfer *xf; - BonjourData *bd; - - if(pc == NULL || id == NULL || from == NULL) - return; - - bd = purple_connection_get_protocol_data(pc); - if(bd == NULL) - return; - - purple_debug_info("bonjour", "bonjour-xfer-receive.\n"); - - /* Build the file transfer handle */ - xf = g_object_new( - XEP_TYPE_XFER, - "account", purple_connection_get_account(pc), - "type", PURPLE_XFER_TYPE_RECEIVE, - "remote-user", from, - NULL - ); - - xfer = PURPLE_XFER(xf); - - xf->data = bd; - purple_xfer_set_filename(xfer, filename); - xf->iq_id = g_strdup(id); - xf->sid = g_strdup(sid); - - if(filesize > 0) - purple_xfer_set_size(xfer, filesize); - - bd->xfer_lists = g_slist_append(bd->xfer_lists, xfer); - - purple_xfer_request(xfer); -} - -static void -bonjour_sock5_request_cb(GObject *source, GAsyncResult *result, - gpointer user_data) -{ - PurpleXfer *xfer = PURPLE_XFER(user_data); - XepXfer *xf = XEP_XFER(xfer); - gsize bytes_written = 0; - GError *error = NULL; - GSocket *sock = NULL; - gint fd = -1; - - purple_debug_info("bonjour", "bonjour_sock5_request_cb"); - - if (!g_output_stream_write_all_finish(G_OUTPUT_STREAM(source), result, - &bytes_written, &error)) { - if (error->code != G_IO_ERROR_CANCELLED) { - purple_xfer_cancel_remote(xfer); - } - g_clear_error(&error); - return; - } - - sock = g_socket_connection_get_socket(xf->conn); - fd = g_socket_get_fd(sock); - purple_debug_info("bonjour", "Accepted SOCKS5 ft connection - fd=%d", fd); - - _purple_network_set_common_socket_flags(fd); - purple_xfer_start(xfer, fd, NULL, -1); -} - -static void -bonjour_sock5_read_connect_cb(GObject *source, GAsyncResult *result, - gpointer user_data) -{ - PurpleXfer *xfer = PURPLE_XFER(user_data); - XepXfer *xf = XEP_XFER(xfer); - GOutputStream *output = NULL; - gsize bytes_read = 0; - GError *error = NULL; - - purple_debug_info("bonjour", "bonjour_sock5_request_state4_cb"); - - if (!g_input_stream_read_all_finish(G_INPUT_STREAM(source), result, - &bytes_read, &error)) { - if (error->code != G_IO_ERROR_CANCELLED) { - purple_xfer_cancel_remote(xfer); - } - g_clear_error(&error); - return; - } - - xf->tx_buf[0] = 0x05; - xf->tx_buf[1] = 0x00; - xf->tx_buf[2] = 0x00; - xf->tx_buf[3] = 0x03; - xf->tx_buf[4] = strlen(xf->buddy_ip); - memcpy(xf->tx_buf + 5, xf->buddy_ip, strlen(xf->buddy_ip)); - xf->tx_buf[5 + strlen(xf->buddy_ip)] = 0x00; - xf->tx_buf[6 + strlen(xf->buddy_ip)] = 0x00; - output = g_io_stream_get_output_stream(G_IO_STREAM(xf->conn)); - g_output_stream_write_all_async( - output, xf->tx_buf, 7 + strlen(xf->buddy_ip), G_PRIORITY_DEFAULT, - xf->cancellable, bonjour_sock5_request_cb, xfer); -} - -static void -bonjour_sock5_write_server_method_cb(GObject *source, GAsyncResult *result, - gpointer user_data) -{ - PurpleXfer *xfer = PURPLE_XFER(user_data); - XepXfer *xf = XEP_XFER(xfer); - GInputStream *input = NULL; - gsize bytes_written = 0; - GError *error = NULL; - - purple_debug_info("bonjour", "bonjour_sock5_request_state3_cb"); - - if (!g_output_stream_write_all_finish(G_OUTPUT_STREAM(source), result, - &bytes_written, &error)) { - if (error->code != G_IO_ERROR_CANCELLED) { - purple_xfer_cancel_remote(xfer); - } - g_clear_error(&error); - return; - } - - input = g_io_stream_get_input_stream(G_IO_STREAM(xf->conn)); - g_input_stream_read_all_async(input, xf->rx_buf, 20, G_PRIORITY_DEFAULT, - xf->cancellable, - bonjour_sock5_read_connect_cb, xfer); -} - -static void -bonjour_sock5_read_client_version_cb(GObject *source, GAsyncResult *result, - gpointer user_data) -{ - PurpleXfer *xfer = PURPLE_XFER(user_data); - XepXfer *xf = XEP_XFER(xfer); - GOutputStream *output = NULL; - gsize bytes_read = 0; - GError *error = NULL; - - purple_debug_info("bonjour", "bonjour_sock5_read_client_version_cb"); - - if (!g_input_stream_read_all_finish(G_INPUT_STREAM(source), result, - &bytes_read, &error)) { - if (error->code != G_IO_ERROR_CANCELLED) { - purple_xfer_cancel_remote(xfer); - } - g_clear_error(&error); - return; - } - - xf->tx_buf[0] = 0x05; - xf->tx_buf[1] = 0x00; - output = g_io_stream_get_output_stream(G_IO_STREAM(xf->conn)); - g_output_stream_write_all_async(output, xf->tx_buf, 2, G_PRIORITY_DEFAULT, - xf->cancellable, - bonjour_sock5_write_server_method_cb, xfer); -} - -static void -bonjour_sock5_incoming_cb(G_GNUC_UNUSED GSocketService *service, - GSocketConnection *connection, GObject *source_object, - G_GNUC_UNUSED gpointer data) -{ - PurpleXfer *xfer = PURPLE_XFER(source_object); - XepXfer *xf = XEP_XFER(xfer); - GInputStream *input = NULL; - - if (xf == NULL) { - return; - } - - purple_debug_info("bonjour", "bonjour_sock5_incoming_cb"); - - xf->conn = g_object_ref(connection); - g_socket_service_stop(xf->service); - g_clear_object(&xf->service); - - purple_debug_info("bonjour", "Accepted SOCKS5 ft connection"); - input = g_io_stream_get_input_stream(G_IO_STREAM(xf->conn)); - - g_input_stream_read_all_async(input, xf->rx_buf, 3, G_PRIORITY_DEFAULT, - xf->cancellable, - bonjour_sock5_read_client_version_cb, xfer); -} - -static void -bonjour_bytestreams_init(PurpleXfer *xfer) -{ - XepXfer *xf; - XepIq *iq; - PurpleXmlNode *query, *streamhost; - guint16 port; - gchar *port_str; - GList *local_ips; - BonjourData *bd; - GError *error = NULL; - - if (xfer == NULL) { - return; - } - - purple_debug_info("bonjour", "Bonjour-bytestreams-init.\n"); - xf = XEP_XFER(xfer); - - xf->service = g_socket_service_new(); - port = purple_socket_listener_add_any_inet_port( - G_SOCKET_LISTENER(xf->service), G_OBJECT(xfer), &error); - if (port == 0) { - purple_debug_error("bonjour", - "Unable to open port for file transfer: %s", - error->message); - purple_xfer_cancel_local(xfer); - g_error_free(error); - return; - } - - g_signal_connect(xf->service, "incoming", - G_CALLBACK(bonjour_sock5_incoming_cb), NULL); - - bd = xf->data; - - 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"); - purple_xmlnode_set_attrib(query, "sid", xf->sid); - purple_xmlnode_set_attrib(query, "mode", "tcp"); - - purple_xfer_set_local_port(xfer, port); - - #pragma message("Need to figure out how to get the local ip addresses") - local_ips = NULL; - - port_str = g_strdup_printf("%hu", port); - while(local_ips) { - streamhost = purple_xmlnode_new_child(query, "streamhost"); - purple_xmlnode_set_attrib(streamhost, "jid", xf->sid); - purple_xmlnode_set_attrib(streamhost, "host", local_ips->data); - purple_xmlnode_set_attrib(streamhost, "port", port_str); - g_free(local_ips->data); - local_ips = g_list_delete_link(local_ips, local_ips); - } - g_free(port_str); - - xep_iq_send_and_free(iq); -} - -static void -bonjour_bytestreams_handle_failure(PurpleXfer *xfer, const gchar *error_message) -{ - XepXfer *xf = XEP_XFER(xfer); - PurpleXmlNode *tmp_node; - gboolean ret; - - purple_debug_error("bonjour", "Error connecting via SOCKS5 to %s - %s", - xf->proxy_host, error_message); - - tmp_node = purple_xmlnode_get_next_twin(xf->streamhost); - ret = __xep_bytestreams_parse(xf->contact, xfer, tmp_node, xf->iq_id); - - if (!ret) { - xep_ft_si_reject(xf->data, xf->iq_id, purple_xfer_get_remote_user(xfer), - "404", "cancel"); - /* Cancel the connection */ - purple_xfer_cancel_local(xfer); - } -} - -static void -bonjour_bytestreams_connect_cb(GObject *source, GAsyncResult *result, - gpointer user_data) -{ - PurpleXfer *xfer = PURPLE_XFER(user_data); - XepXfer *xf = XEP_XFER(xfer); - GIOStream *stream; - GError *error = NULL; - GSocket *socket; - XepIq *iq; - PurpleXmlNode *q_node, *tmp_node; - BonjourData *bd; - - stream = g_proxy_connect_finish(G_PROXY(source), result, &error); - if (stream == NULL) { - if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { - purple_debug_error("bonjour", - "Unable to connect to destination host: %s", - error->message); - bonjour_bytestreams_handle_failure( - xfer, "Unable to connect to destination host."); - } - - g_clear_error(&error); - return; - } - - if (!G_IS_SOCKET_CONNECTION(stream)) { - purple_debug_error("bonjour", - "GProxy didn't return a GSocketConnection."); - bonjour_bytestreams_handle_failure( - xfer, "GProxy didn't return a GSocketConnection."); - g_object_unref(stream); - return; - } - - purple_debug_info("bonjour", "Connected successfully via SOCKS5, starting transfer.\n"); - - bd = xf->data; - - /* 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->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"); - purple_xmlnode_set_attrib(tmp_node, "jid", xf->jid); - xep_iq_send_and_free(iq); - - xf->conn = G_SOCKET_CONNECTION(stream); - socket = g_socket_connection_get_socket(xf->conn); - purple_xfer_start(xfer, g_socket_get_fd(socket), NULL, -1); -} - -/* This is called when we connect to the SOCKS5 proxy server (through any - * relevant account proxy) - */ -static void -bonjour_bytestreams_socks5_connect_to_host_cb(GObject *source, - GAsyncResult *result, - gpointer user_data) -{ - PurpleXfer *xfer = PURPLE_XFER(user_data); - XepXfer *xf = XEP_XFER(xfer); - GSocketConnection *conn; - GProxy *proxy; - GSocketAddress *addr; - GInetSocketAddress *inet_addr; - GSocketAddress *proxy_addr; - PurpleContact *contact = NULL; - gchar *hash_input; - gchar *dstaddr; - GError *error = NULL; - - conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source), - result, &error); - if (conn == NULL) { - if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { - purple_debug_error("bonjour", - "Unable to connect to SOCKS5 host: %s", - error->message); - bonjour_bytestreams_handle_failure( - xfer, "Unable to connect to SOCKS5 host."); - } - - g_clear_error(&error); - return; - } - - proxy = g_proxy_get_default_for_protocol("socks5"); - if (proxy == NULL) { - purple_debug_error("bonjour", "SOCKS5 proxy backend missing."); - bonjour_bytestreams_handle_failure(xfer, - "SOCKS5 proxy backend missing."); - g_object_unref(conn); - return; - } - - addr = g_socket_connection_get_remote_address(conn, &error); - if (addr == NULL) { - purple_debug_error( - "bonjour", - "Unable to retrieve SOCKS5 host address from connection: %s", - error->message); - bonjour_bytestreams_handle_failure( - xfer, "Unable to retrieve SOCKS5 host address from connection"); - g_object_unref(conn); - g_object_unref(proxy); - g_clear_error(&error); - return; - } - - contact = xf->contact; - hash_input = g_strdup_printf("%s%s%s", xf->sid, - purple_contact_info_get_username(PURPLE_CONTACT_INFO(contact)), - bonjour_get_jid(purple_contact_get_account(contact))); - dstaddr = g_compute_checksum_for_string(G_CHECKSUM_SHA1, hash_input, -1); - g_free(hash_input); - - purple_debug_info("bonjour", "Connecting to %s using SOCKS5 proxy %s:%d", - dstaddr, xf->proxy_host, xf->proxy_port); - - inet_addr = G_INET_SOCKET_ADDRESS(addr); - proxy_addr = - g_proxy_address_new(g_inet_socket_address_get_address(inet_addr), - g_inet_socket_address_get_port(inet_addr), - "socks5", dstaddr, 0, NULL, NULL); - g_object_unref(inet_addr); - - g_proxy_connect_async(proxy, G_IO_STREAM(conn), G_PROXY_ADDRESS(proxy_addr), - xf->cancellable, bonjour_bytestreams_connect_cb, - xfer); - g_object_unref(proxy_addr); - g_object_unref(conn); - g_object_unref(proxy); - g_free(dstaddr); -} - -static void -bonjour_bytestreams_connect(PurpleXfer *xfer) -{ - PurpleAccount *account = NULL; - XepXfer *xf; - GError *error = NULL; - - if (xfer == NULL) { - return; - } - - purple_debug_info("bonjour", "bonjour-bytestreams-connect."); - - xf = XEP_XFER(xfer); - account = purple_contact_get_account(xf->contact); - - xf->client = purple_gio_socket_client_new(account, &error); - if (xf->client == NULL) { - /* Cancel the connection */ - purple_debug_error("bonjour", - "Failed to connect to SOCKS5 streamhost proxy: %s", - error->message); - g_clear_error(&error); - xep_ft_si_reject(xf->data, xf->iq_id, purple_xfer_get_remote_user(xfer), - "404", "cancel"); - purple_xfer_cancel_local(xfer); - return; - } - - purple_debug_info("bonjour", "Connecting to SOCKS5 proxy %s:%d", - xf->proxy_host, xf->proxy_port); - - g_socket_client_connect_to_host_async( - xf->client, xf->proxy_host, xf->proxy_port, xf->cancellable, - bonjour_bytestreams_socks5_connect_to_host_cb, xfer); -} - -static void -xep_xfer_init(XepXfer *xf) -{ - xf->cancellable = g_cancellable_new(); -} - -static void -xep_xfer_finalize(GObject *obj) { - XepXfer *xf = XEP_XFER(obj); - BonjourData *bd = (BonjourData*)xf->data; - - if(bd != NULL) { - bd->xfer_lists = g_slist_remove(bd->xfer_lists, PURPLE_XFER(xf)); - purple_debug_misc("bonjour", "B free xfer from lists(%p).\n", bd->xfer_lists); - } - g_cancellable_cancel(xf->cancellable); - g_clear_object(&xf->cancellable); - g_clear_object(&xf->client); - if (xf->service) { - g_socket_service_stop(xf->service); - } - g_clear_object(&xf->service); - g_clear_object(&xf->conn); - - g_free(xf->iq_id); - g_free(xf->jid); - g_free(xf->proxy_host); - g_free(xf->buddy_ip); - g_free(xf->sid); - - g_clear_pointer(&xf->streamhost, purple_xmlnode_free_tree); - - G_OBJECT_CLASS(xep_xfer_parent_class)->finalize(obj); -} - -static void -xep_xfer_class_finalize(G_GNUC_UNUSED XepXferClass *klass) { -} - -static void -xep_xfer_class_init(XepXferClass *klass) { - GObjectClass *obj_class = G_OBJECT_CLASS(klass); - PurpleXferClass *xfer_class = PURPLE_XFER_CLASS(klass); - - obj_class->finalize = xep_xfer_finalize; - - xfer_class->init = bonjour_xfer_init; - xfer_class->request_denied = bonjour_xfer_request_denied; - xfer_class->cancel_recv = bonjour_xfer_cancel_recv; - xfer_class->cancel_send = bonjour_xfer_cancel_send; - xfer_class->end = bonjour_xfer_end; -} - -void -xep_xfer_register(GTypeModule *module) { - xep_xfer_register_type(module); -}