Mon, 21 Dec 2020 23:17:45 -0600
Delete some now-unused networking bits
* Remove several unnecessary networking headers and types.
* Remove some unused Windows networking wrappers.
Testing Done:
Compile only
Reviewed at https://reviews.imfreedom.org/r/306/
/* * 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 <config.h> #include <sys/types.h> #include <nice.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; PurpleBuddy *pb; }; G_DEFINE_DYNAMIC_TYPE(XepXfer, xep_xfer, PURPLE_TYPE_XFER); 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(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) { PurpleBuddy *buddy; BonjourBuddy *bb; XepXfer *xf; xf = XEP_XFER(xfer); purple_debug_info("bonjour", "Bonjour-xfer-init.\n"); buddy = purple_blist_find_buddy(purple_xfer_get_account(xfer), purple_xfer_get_remote_user(xfer)); /* this buddy is offline. */ if (buddy == NULL || (bb = purple_buddy_get_protocol_data(buddy)) == NULL) 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"); } } void xep_si_parse(PurpleConnection *pc, PurpleXmlNode *packet, PurpleBuddy *pb) { 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(pb != NULL); bd = purple_connection_get_protocol_data(pc); if(bd == NULL) return; purple_debug_info("bonjour", "xep-si-parse.\n"); name = purple_buddy_get_name(pb); 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 possiblity 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, PurpleBuddy *pb) { 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 = purple_buddy_get_protocol_data(pb); 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(PurpleBuddy *pb, 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, pb)) 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->pb = pb; 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, PurpleBuddy *pb) { 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(pb != NULL); 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_buddy_get_name(pb); 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(pb, 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, 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(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); local_ips = nice_interfaces_get_local_ips(FALSE); 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->pb, 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; PurpleBuddy *pb; 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; } pb = xf->pb; hash_input = g_strdup_printf("%s%s%s", xf->sid, purple_buddy_get_name(pb), bonjour_get_jid(purple_buddy_get_account(pb))); 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_buddy_get_account(xf->pb); 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(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); }