/* * gibber-bytestream-oob.c - Source for GibberBytestreamOOB * Copyright (C) 2007 Collabora Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include "gibber-bytestream-oob.h" #include #include #include #include #include #include #include #include #include "gibber-sockets.h" #include "gibber-bytestream-iface.h" #include "gibber-linklocal-transport.h" #include "gibber-util.h" #include "gibber-transport.h" #include "gibber-fd-transport.h" #include "gibber-listener.h" #define DEBUG_FLAG DEBUG_BYTESTREAM #include "gibber-debug.h" static void bytestream_iface_init (gpointer g_iface, gpointer iface_data); G_DEFINE_TYPE_WITH_CODE (GibberBytestreamOOB, gibber_bytestream_oob, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GIBBER_TYPE_BYTESTREAM_IFACE, bytestream_iface_init)); /* properties */ enum { PROP_PORTER = 1, PROP_CONTACT, PROP_SELF_ID, PROP_PEER_ID, PROP_STREAM_ID, PROP_STREAM_INIT_IQ, PROP_STATE, PROP_HOST, PROP_PROTOCOL, LAST_PROPERTY }; typedef struct _GibberBytestreamIBBPrivate GibberBytestreamOOBPrivate; struct _GibberBytestreamIBBPrivate { WockyPorter *porter; WockyContact *contact; gchar *self_id; gchar *peer_id; gchar *stream_id; WockyStanza *stream_init_iq; /* ID of the OOB opening stanza. We'll reply to * it when we the bytestream is closed */ gchar *stream_open_id; guint stanza_received_id; GibberBytestreamState state; gchar *host; gchar *url; /* Are we the recipient of this bytestream? * If not we are the sender */ gboolean recipient; GibberTransport *transport; gboolean write_blocked; gboolean read_blocked; GibberListener *listener; GibberBytestreamOOBCheckAddrFunc check_addr_func; gpointer check_addr_func_data; gboolean dispose_has_run; }; #define GIBBER_BYTESTREAM_OOB_GET_PRIVATE(obj) \ ((GibberBytestreamOOBPrivate *) (GibberBytestreamOOB *) obj->priv) static void gibber_bytestream_oob_do_close (GibberBytestreamOOB *self, GError *error, gboolean can_wait); static void bytestream_closed (GibberBytestreamOOB *self); static void gibber_bytestream_oob_init (GibberBytestreamOOB *self) { GibberBytestreamOOBPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GIBBER_TYPE_BYTESTREAM_OOB, GibberBytestreamOOBPrivate); self->priv = priv; priv->dispose_has_run = FALSE; } static WockyStanza * make_iq_oob_sucess_response (const gchar *from, const gchar *to, const gchar *id) { return wocky_stanza_build ( WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_RESULT, from, to, WOCKY_NODE_ATTRIBUTE, "id", id, NULL); } static void transport_handler (GibberTransport *transport, GibberBuffer *data, gpointer user_data) { GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (user_data); GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); GString *buffer; buffer = g_string_new_len ((const gchar *) data->data, data->length); g_signal_emit_by_name (G_OBJECT (self), "data-received", priv->peer_id, buffer); g_string_free (buffer, TRUE); } static void transport_connected_cb (GibberTransport *transport, GibberBytestreamOOB *self) { DEBUG ("transport connected. Bytestream is now open"); g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_OPEN, NULL); } static void transport_disconnected_cb (GibberTransport *transport, GibberBytestreamOOB *self) { GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); if (priv->state == GIBBER_BYTESTREAM_STATE_CLOSED) return; DEBUG ("transport disconnected. close the bytestream"); if (priv->state == GIBBER_BYTESTREAM_STATE_ACCEPTED) { /* Connection to host failed */ GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_ITEM_NOT_FOUND, "connection failed" }; gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), &e); } else { gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), NULL); } } static void change_write_blocked_state (GibberBytestreamOOB *self, gboolean blocked) { GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); if (priv->write_blocked == blocked) return; priv->write_blocked = blocked; g_signal_emit_by_name (self, "write-blocked", blocked); } static void transport_buffer_empty_cb (GibberTransport *transport, GibberBytestreamOOB *self) { GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); if (priv->state == GIBBER_BYTESTREAM_STATE_CLOSING) { DEBUG ("buffer is now empty. Bytestream can be closed"); bytestream_closed (self); } else if (priv->write_blocked) { DEBUG ("buffer is empty, unblock write to the bytestream"); change_write_blocked_state (self, FALSE); } } static void set_transport (GibberBytestreamOOB *self, GibberTransport *transport) { GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); g_assert (priv->transport == NULL); priv->transport = g_object_ref (transport); gibber_transport_set_handler (transport, transport_handler, self); /* The transport will already be connected if it is created from * GibberListener. In this case, set the bytestream to open, otherwise * it will be done in transport_connected_cb. */ if (gibber_transport_get_state (transport) == GIBBER_TRANSPORT_CONNECTED) { g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_OPEN, NULL); } g_signal_connect (transport, "connected", G_CALLBACK (transport_connected_cb), self); g_signal_connect (transport, "disconnected", G_CALLBACK (transport_disconnected_cb), self); g_signal_connect (transport, "buffer-empty", G_CALLBACK (transport_buffer_empty_cb), self); } static void connect_to_url (GibberBytestreamOOB *self) { GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); GibberLLTransport *ll_transport; GSocketConnection *conn; GSocketAddress *socket_address = NULL; gchar **tokens; union { struct sockaddr_storage storage; struct sockaddr_in in; } addr; const gchar *port; gint portnum = 0; const gchar *url; GError *error = NULL; /* TODO: if we want to support IPv6 literals, we have to remove * [] around the address */ url = priv->url + strlen ("x-tcp://"); tokens = g_strsplit (url, ":", 2); port = tokens[1]; if (port != NULL) portnum = atoi (port); if (portnum <= 0 || portnum > G_MAXUINT16) { /* I'm too lazy to create more specific errors for this as it should * never happen while using salut anyway.. */ GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_NOT_ACCEPTABLE, "Invalid port number" }; DEBUG ("Invalid port number: %s", port); gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), &e); goto out; } conn = wocky_meta_porter_borrow_connection (WOCKY_META_PORTER (priv->porter), WOCKY_LL_CONTACT (priv->contact)); if (conn != NULL) socket_address = g_socket_connection_get_remote_address (conn, NULL); if (conn == NULL || socket_address == NULL) { /* I'm too lazy to create more specific errors for this as it should * never happen while using salut anyway.. */ GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_ITEM_NOT_FOUND, "Unsable get socket address for the control connection" }; DEBUG ("Could not get socket address for the control connection" ); gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), &e); goto out; } if (!g_socket_address_to_native (G_SOCKET_ADDRESS (socket_address), &(addr.storage), sizeof (addr.storage), &error)) { GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_ITEM_NOT_FOUND, "Failed to turn socket address into bytes" }; DEBUG ("Failed to get native socket address: %s", error->message); gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), &e); g_clear_error (&error); goto out; } /* FIXME: this is a hack until we get the normalization of v6-in-v4 * addresses in GLib. See bgo#646082 */ gibber_normalize_address (&(addr.storage)); addr.in.sin_port = g_htons ((guint16) portnum); ll_transport = gibber_ll_transport_new (); set_transport (self, GIBBER_TRANSPORT (ll_transport)); gibber_ll_transport_open_sockaddr (ll_transport, &addr.storage, NULL); g_object_unref (ll_transport); out: if (socket_address != NULL) g_object_unref (socket_address); g_strfreev (tokens); } static void opened_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { WockyMetaPorter *porter = WOCKY_META_PORTER (source_object); GibberBytestreamOOB *self = user_data; GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); GError *error = NULL; if (!wocky_meta_porter_open_finish (porter, result, &error)) { DEBUG ("failed to open connection to contact"); g_clear_error (&error); } else { connect_to_url (self); } g_free (priv->url); wocky_meta_porter_unhold (porter, priv->contact); } static gboolean parse_oob_init_iq (GibberBytestreamOOB *self, WockyStanza *stanza) { GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); WockyNode *query_node, *url_node; WockyStanzaType type; WockyStanzaSubType sub_type; const gchar *stream_id, *url; WockyNode *node = wocky_stanza_get_top_node (stanza); wocky_stanza_get_type_info (stanza, &type, &sub_type); if (type != WOCKY_STANZA_TYPE_IQ || sub_type != WOCKY_STANZA_SUB_TYPE_SET) return FALSE; query_node = wocky_node_get_child_ns (node, "query", WOCKY_XMPP_NS_IQ_OOB); if (query_node == NULL) return FALSE; stream_id = wocky_node_get_attribute (query_node, "sid"); if (stream_id == NULL || strcmp (stream_id, priv->stream_id) != 0) return FALSE; url_node = wocky_node_get_child (query_node, "url"); if (url_node == NULL) return FALSE; url = url_node->content; priv->recipient = TRUE; priv->stream_open_id = g_strdup (wocky_node_get_attribute ( node, "id")); if (!g_str_has_prefix (url, "x-tcp://")) { GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_ITEM_NOT_FOUND, "URL is not a TCP URL" }; DEBUG ("URL is not a TCP URL: %s. Close the bytestream", priv->url); gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), &e); return TRUE; } priv->url = g_strdup (url); wocky_meta_porter_open_async (WOCKY_META_PORTER (priv->porter), WOCKY_LL_CONTACT (priv->contact), NULL, opened_cb, self); return TRUE; } static gboolean parse_oob_iq_result (GibberBytestreamOOB *self, WockyStanza *stanza) { GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); WockyStanzaType type; WockyStanzaSubType sub_type; const gchar *id; WockyNode *node = wocky_stanza_get_top_node (stanza); if (priv->recipient) /* Only the sender have to wait for the IQ reply */ return FALSE; wocky_stanza_get_type_info (stanza, &type, &sub_type); if (type != WOCKY_STANZA_TYPE_IQ || sub_type != WOCKY_STANZA_SUB_TYPE_RESULT) return FALSE; /* FIXME: we should check if it's the right sender */ id = wocky_node_get_attribute (node, "id"); if (id == NULL || strcmp (id, priv->stream_open_id) != 0) return FALSE; DEBUG ("received OOB close stanza - ignoring"); return TRUE; } static gboolean received_stanza_cb (WockyPorter *porter, WockyStanza *stanza, gpointer user_data) { GibberBytestreamOOB *self = (GibberBytestreamOOB *) user_data; WockyNode *node = wocky_stanza_get_top_node (stanza); const gchar *from; /* discard invalid stanza */ from = wocky_node_get_attribute (node, "from"); if (from == NULL) { DEBUG ("got a message without a from field"); return FALSE; } if (parse_oob_init_iq (self, stanza)) return TRUE; if (parse_oob_iq_result (self, stanza)) return TRUE; return FALSE; } static void gibber_bytestream_oob_dispose (GObject *object) { GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (object); GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); if (priv->dispose_has_run) return; priv->dispose_has_run = TRUE; if (priv->state != GIBBER_BYTESTREAM_STATE_CLOSED) { if (priv->state == GIBBER_BYTESTREAM_STATE_CLOSING) { bytestream_closed (self); } else { gibber_bytestream_oob_do_close (self, NULL, FALSE); } } if (priv->listener != NULL) { g_object_unref (priv->listener); priv->listener = NULL; } if (priv->porter != NULL) { wocky_porter_unregister_handler (priv->porter, priv->stanza_received_id); priv->stanza_received_id = 0; g_object_unref (priv->porter); priv->porter = NULL; } if (priv->contact != NULL) { g_object_unref (priv->contact); priv->contact = NULL; } if (priv->stream_init_iq != NULL) { g_object_unref (priv->stream_init_iq); priv->stream_init_iq = NULL; } G_OBJECT_CLASS (gibber_bytestream_oob_parent_class)->dispose (object); } static void gibber_bytestream_oob_finalize (GObject *object) { GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (object); GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); g_free (priv->stream_id); g_free (priv->stream_open_id); g_free (priv->host); g_free (priv->self_id); g_free (priv->peer_id); G_OBJECT_CLASS (gibber_bytestream_oob_parent_class)->finalize (object); } static void gibber_bytestream_oob_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (object); GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); switch (property_id) { case PROP_PORTER: g_value_set_object (value, priv->porter); break; case PROP_CONTACT: g_value_set_object (value, priv->contact); break; case PROP_SELF_ID: g_value_set_string (value, priv->self_id); break; case PROP_PEER_ID: g_value_set_string (value, priv->peer_id); break; case PROP_STREAM_ID: g_value_set_string (value, priv->stream_id); break; case PROP_STREAM_INIT_IQ: g_value_set_object (value, priv->stream_init_iq); break; case PROP_STATE: g_value_set_uint (value, priv->state); break; case PROP_HOST: g_value_set_string (value, priv->host); break; case PROP_PROTOCOL: g_value_set_string (value, WOCKY_XMPP_NS_IQ_OOB); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void make_porter_connections (GibberBytestreamOOB *self) { GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); gchar *jid; jid = wocky_contact_dup_jid (priv->contact); priv->stanza_received_id = wocky_porter_register_handler_from (priv->porter, WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_NONE, jid, WOCKY_PORTER_HANDLER_PRIORITY_NORMAL, received_stanza_cb, self, NULL); g_free (jid); } static void gibber_bytestream_oob_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (object); GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); switch (property_id) { case PROP_PORTER: priv->porter = g_value_dup_object (value); break; case PROP_CONTACT: priv->contact = g_value_dup_object (value); break; case PROP_SELF_ID: g_free (priv->self_id); priv->self_id = g_value_dup_string (value); break; case PROP_PEER_ID: g_free (priv->peer_id); priv->peer_id = g_value_dup_string (value); break; case PROP_STREAM_ID: g_free (priv->stream_id); priv->stream_id = g_value_dup_string (value); break; case PROP_STREAM_INIT_IQ: priv->stream_init_iq = g_value_dup_object (value); break; case PROP_STATE: if (priv->state != g_value_get_uint (value)) { priv->state = g_value_get_uint (value); g_signal_emit_by_name (object, "state-changed", priv->state); } break; case PROP_HOST: g_free (priv->host); priv->host = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static GObject * gibber_bytestream_oob_constructor (GType type, guint n_props, GObjectConstructParam *props) { GObject *obj; GibberBytestreamOOBPrivate *priv; obj = G_OBJECT_CLASS (gibber_bytestream_oob_parent_class)-> constructor (type, n_props, props); priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (GIBBER_BYTESTREAM_OOB (obj)); g_assert (priv->self_id != NULL); g_assert (priv->peer_id != NULL); g_assert (priv->stream_id != NULL); g_assert (priv->porter != NULL); g_assert (priv->contact != NULL); return obj; } static void gibber_bytestream_oob_constructed (GObject *obj) { GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (obj); GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); if (G_OBJECT_CLASS (gibber_bytestream_oob_parent_class)->constructed != NULL) G_OBJECT_CLASS (gibber_bytestream_oob_parent_class)->constructed (obj); if (priv->porter != NULL && priv->contact != NULL) make_porter_connections (self); } static void gibber_bytestream_oob_class_init ( GibberBytestreamOOBClass *gibber_bytestream_oob_class) { GObjectClass *object_class = G_OBJECT_CLASS (gibber_bytestream_oob_class); GParamSpec *param_spec; g_type_class_add_private (gibber_bytestream_oob_class, sizeof (GibberBytestreamOOBPrivate)); object_class->dispose = gibber_bytestream_oob_dispose; object_class->finalize = gibber_bytestream_oob_finalize; object_class->get_property = gibber_bytestream_oob_get_property; object_class->set_property = gibber_bytestream_oob_set_property; object_class->constructor = gibber_bytestream_oob_constructor; object_class->constructed = gibber_bytestream_oob_constructed; g_object_class_override_property (object_class, PROP_SELF_ID, "self-id"); g_object_class_override_property (object_class, PROP_PEER_ID, "peer-id"); g_object_class_override_property (object_class, PROP_STREAM_ID, "stream-id"); g_object_class_override_property (object_class, PROP_STATE, "state"); g_object_class_override_property (object_class, PROP_PROTOCOL, "protocol"); param_spec = g_param_spec_object ( "porter", "WockyPorter object", "Wocky porter object used for communication by this " "bytestream if it's a private one", WOCKY_TYPE_PORTER, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_PORTER, param_spec); param_spec = g_param_spec_object ( "contact", "WockyContact object", "Contact object used for communication by this " "bytestream if it's a private one", WOCKY_TYPE_CONTACT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_CONTACT, param_spec); param_spec = g_param_spec_object ( "stream-init-iq", "stream init IQ", "the iq of the SI request", WOCKY_TYPE_STANZA, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_STREAM_INIT_IQ, param_spec); param_spec = g_param_spec_string ( "host", "host", "The hostname to use in the OOB URL. Literal are not allowed", "", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_HOST, param_spec); } /* * gibber_bytestream_oob_send * * Implements gibber_bytestream_iface_send on GibberBytestreamIface */ static gboolean gibber_bytestream_oob_send (GibberBytestreamIface *bytestream, guint len, const gchar *str) { GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (bytestream); GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); GError *error = NULL; if (priv->state != GIBBER_BYTESTREAM_STATE_OPEN) { DEBUG ("can't send data through a not open bytestream (state: %d)", priv->state); return FALSE; } if (priv->write_blocked) { DEBUG ("sending data while the bytestream was blocked"); } DEBUG ("send %u bytes through bytestream", len); if (!gibber_transport_send (priv->transport, (const guint8 *) str, len, &error)) { DEBUG ("sending failed: %s", error->message); g_error_free (error); gibber_bytestream_iface_close (GIBBER_BYTESTREAM_IFACE (self), NULL); return FALSE; } if (!gibber_transport_buffer_is_empty (priv->transport)) { /* We >don't want to send more data while the buffer isn't empty */ DEBUG ("buffer isn't empty. Block write to the bytestream"); change_write_blocked_state (self, TRUE); } return TRUE; } static WockyStanza * create_si_accept_iq (GibberBytestreamOOB *self) { GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); return wocky_stanza_build_iq_result (priv->stream_init_iq, '(', "si", ':', WOCKY_XMPP_NS_SI, '(', "feature", ':', WOCKY_XMPP_NS_FEATURENEG, '(', "x", ':', WOCKY_XMPP_NS_DATA, '@', "type", "submit", '(', "field", '@', "var", "stream-method", '(', "value", '$', WOCKY_XMPP_NS_IQ_OOB, ')', ')', ')', ')', ')', NULL); } /* * gibber_bytestream_oob_accept * * Implements gibber_bytestream_iface_accept on GibberBytestreamIface */ static void gibber_bytestream_oob_accept (GibberBytestreamIface *bytestream, GibberBytestreamAugmentSiAcceptReply func, gpointer user_data) { GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (bytestream); GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); WockyStanza *stanza; WockyNode *node; WockyNode *si; if (priv->state != GIBBER_BYTESTREAM_STATE_LOCAL_PENDING) { /* The stream was previoulsy or automatically accepted */ DEBUG ("stream was already accepted"); return; } stanza = create_si_accept_iq (self); node = wocky_stanza_get_top_node (stanza); si = wocky_node_get_child_ns (node, "si", WOCKY_XMPP_NS_SI); g_assert (si != NULL); if (func != NULL) { /* let the caller add his profile specific data */ func (si, user_data); } wocky_porter_send (priv->porter, stanza); DEBUG ("stream is now accepted"); g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_ACCEPTED, NULL); g_object_unref (stanza); } static void bytestream_closed (GibberBytestreamOOB *self) { GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); if (priv->recipient) { WockyStanza *stanza; /* As described in the XEP, we send result IQ when we have * finished to use the OOB */ stanza = make_iq_oob_sucess_response (priv->self_id, priv->peer_id, priv->stream_open_id); wocky_stanza_set_to_contact (stanza, priv->contact); DEBUG ("send OOB close stanza"); wocky_porter_send (priv->porter, stanza); g_object_unref (stanza); } else { /* We are the sender. Don't have to send anything */ } if (priv->transport != NULL) { g_signal_handlers_disconnect_matched (priv->transport, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self); gibber_transport_disconnect (priv->transport); g_object_unref (priv->transport); priv->transport = NULL; } g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_CLOSED, NULL); } static void gibber_bytestream_oob_decline (GibberBytestreamOOB *self, GError *error) { GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); g_return_if_fail (priv->state == GIBBER_BYTESTREAM_STATE_LOCAL_PENDING); if (error != NULL && (error->domain == WOCKY_XMPP_ERROR || error->domain == WOCKY_SI_ERROR)) { wocky_porter_send_iq_gerror (priv->porter, priv->stream_init_iq, error); } else { wocky_porter_send_iq_error (priv->porter, priv->stream_init_iq, WOCKY_XMPP_ERROR_FORBIDDEN, "Offer declined"); } } static void gibber_bytestream_oob_do_close (GibberBytestreamOOB *self, GError *error, gboolean can_wait) { GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); if (priv->state == GIBBER_BYTESTREAM_STATE_CLOSED) /* bytestream already closed, do nothing */ return; if (priv->state == GIBBER_BYTESTREAM_STATE_LOCAL_PENDING) { /* Stream was created using SI so we decline the request */ gibber_bytestream_oob_decline (self, error); } g_object_set (self, "state", GIBBER_BYTESTREAM_STATE_CLOSING, NULL); if (can_wait && priv->transport != NULL && !gibber_transport_buffer_is_empty (priv->transport)) { DEBUG ("Wait transport buffer is empty before close the bytestream"); } else { DEBUG ("Transport buffer is empty, we can close the bytestream"); bytestream_closed (self); } } /* * gibber_bytestream_oob_close * * Implements gibber_bytestream_iface_close on GibberBytestreamIface */ static void gibber_bytestream_oob_close (GibberBytestreamIface *bytestream, GError *error) { gibber_bytestream_oob_do_close (GIBBER_BYTESTREAM_OOB (bytestream), error, TRUE); } static WockyStanza * make_oob_init_iq (const gchar *from, const gchar *to, const gchar *stream_id, const gchar *url) { return wocky_stanza_build ( WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET, from, to, '(', "query", ':', WOCKY_XMPP_NS_IQ_OOB, '@', "sid", stream_id, '(', "url", '$', url, ')', ')', NULL); } static void new_connection_cb (GibberListener *listener, GibberTransport *transport, struct sockaddr *addr, guint size, gpointer user_data) { GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (user_data); GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); if (priv->check_addr_func != NULL && !priv->check_addr_func (self, addr, size, priv->check_addr_func_data)) { DEBUG ("connection refused by the bytestream user"); return; } DEBUG("New connection.."); set_transport (self, transport); } /* * gibber_bytestream_oob_initiate * * Implements gibber_bytestream_iface_initiate on GibberBytestreamIface */ static gboolean gibber_bytestream_oob_initiate (GibberBytestreamIface *bytestream) { GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (bytestream); GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); WockyStanza *stanza; WockyNode *node; const gchar *id; int port; gchar *url; if (priv->state != GIBBER_BYTESTREAM_STATE_INITIATING) { DEBUG ("bytestream is not is the initiating state (state %d)", priv->state); return FALSE; } g_assert (priv->host != NULL); priv->recipient = FALSE; g_assert (priv->listener == NULL); priv->listener = gibber_listener_new (); g_signal_connect (priv->listener, "new-connection", G_CALLBACK (new_connection_cb), self); if (!gibber_listener_listen_tcp (priv->listener, 0, NULL)) { DEBUG ("can't listen for incoming connection"); return FALSE; } port = gibber_listener_get_port (priv->listener); url = g_strdup_printf ("x-tcp://%s:%d", priv->host, port); stanza = make_oob_init_iq (priv->self_id, priv->peer_id, priv->stream_id, url); g_free (url); wocky_stanza_set_to_contact (stanza, priv->contact); node = wocky_stanza_get_top_node (stanza); id = wocky_node_get_attribute (node, "id"); if (id == NULL) { /* let the porter generate the IQ id for us */ wocky_porter_send_iq_async (priv->porter, stanza, NULL, NULL, NULL); priv->stream_open_id = g_strdup ( wocky_node_get_attribute (node, "id")); } else { /* save the stream open ID */ priv->stream_open_id = g_strdup (id); wocky_porter_send (priv->porter, stanza); } g_object_unref (stanza); return TRUE; } void gibber_bytestream_oob_set_check_addr_func ( GibberBytestreamOOB *self, GibberBytestreamOOBCheckAddrFunc func, gpointer user_data) { GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); priv->check_addr_func = func; priv->check_addr_func_data = user_data; } static void gibber_bytestream_oob_block_reading (GibberBytestreamIface *bytestream, gboolean block) { GibberBytestreamOOB *self = GIBBER_BYTESTREAM_OOB (bytestream); GibberBytestreamOOBPrivate *priv = GIBBER_BYTESTREAM_OOB_GET_PRIVATE (self); if (priv->read_blocked == block) return; priv->read_blocked = block; DEBUG ("%s the transport bytestream", block ? "block": "unblock"); gibber_transport_block_receiving (priv->transport, block); } static void bytestream_iface_init (gpointer g_iface, gpointer iface_data) { GibberBytestreamIfaceClass *klass = (GibberBytestreamIfaceClass *) g_iface; klass->initiate = gibber_bytestream_oob_initiate; klass->send = gibber_bytestream_oob_send; klass->close = gibber_bytestream_oob_close; klass->accept = gibber_bytestream_oob_accept; klass->block_reading = gibber_bytestream_oob_block_reading; }