/* * si-bytestream-manager.c - Source for SalutSiBytestreamManager * 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 "si-bytestream-manager.h" #include #include #include #include #include #include "im-manager.h" #include "muc-manager.h" #include "tubes-manager.h" #define DEBUG_FLAG DEBUG_SI_BYTESTREAM_MGR #include "debug.h" G_DEFINE_TYPE (SalutSiBytestreamManager, salut_si_bytestream_manager, G_TYPE_OBJECT) /* properties */ enum { PROP_CONNECTION = 1, PROP_HOST_NAME_FQDN, LAST_PROPERTY }; /* private structure */ typedef struct _SalutSiBytestreamManagerPrivate SalutSiBytestreamManagerPrivate; struct _SalutSiBytestreamManagerPrivate { SalutConnection *connection; SalutImManager *im_manager; SalutMucManager *muc_manager; gchar *host_name_fqdn; guint si_request_id; gboolean dispose_has_run; }; #define SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE(obj) \ ((SalutSiBytestreamManagerPrivate *) ((SalutSiBytestreamManager *) obj)->priv) static void salut_si_bytestream_manager_init (SalutSiBytestreamManager *self) { SalutSiBytestreamManagerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, SALUT_TYPE_SI_BYTESTREAM_MANAGER, SalutSiBytestreamManagerPrivate); self->priv = priv; priv->dispose_has_run = FALSE; } static gboolean streaminit_parse_request (WockyStanza *stanza, const gchar **profile, const gchar **from, const gchar **stream_id, const gchar **stream_init_id, const gchar **mime_type, GSList **stream_methods) { WockyNode *iq, *si, *feature, *x; GSList *x_children, *field_children; iq = wocky_stanza_get_top_node (stanza); if (stream_init_id != NULL) *stream_init_id = wocky_node_get_attribute (iq, "id"); *from = wocky_node_get_attribute (iq, "from"); if (*from == NULL) { DEBUG ("got a message without a from field"); return FALSE; } /* Parse */ si = wocky_node_get_child_ns (iq, "si", WOCKY_XMPP_NS_SI); if (si == NULL) return FALSE; *stream_id = wocky_node_get_attribute (si, "id"); if (*stream_id == NULL) { DEBUG ("got a SI request without a stream id field"); return FALSE; } *mime_type = wocky_node_get_attribute (si, "mime-type"); /* if no mime_type is defined, XEP-0095 says to assume * "application/octet-stream" */ *profile = wocky_node_get_attribute (si, "profile"); if (*profile == NULL) { DEBUG ("got a SI request without a profile field"); return FALSE; } /* Parse */ feature = wocky_node_get_child_ns (si, "feature", WOCKY_XMPP_NS_FEATURENEG); if (feature == NULL) { DEBUG ("got a SI request without a feature field"); return FALSE; } x = wocky_node_get_child_ns (feature, "x", WOCKY_XMPP_NS_DATA); if (x == NULL) { DEBUG ("got a SI request without a X data field"); return FALSE; } for (x_children = x->children; x_children; x_children = g_slist_next (x_children)) { WockyNode *field = x_children->data; if (tp_strdiff (wocky_node_get_attribute (field, "var"), "stream-method")) /* some future field, ignore it */ continue; if (tp_strdiff (wocky_node_get_attribute (field, "type"), "list-single")) { DEBUG ( "SI request's stream-method field was " "not of type list-single"); return FALSE; } /* Get the stream methods offered */ *stream_methods = NULL; for (field_children = field->children; field_children; field_children = g_slist_next (field_children)) { WockyNode *stream_method, *value; const gchar *stream_method_str; stream_method = (WockyNode *) field_children->data; value = wocky_node_get_child (stream_method, "value"); if (value == NULL) continue; stream_method_str = value->content; if (!tp_strdiff (stream_method_str, "")) continue; DEBUG ("Got stream-method %s", stream_method_str); /* Append to the stream_methods list */ *stream_methods = g_slist_append (*stream_methods, (gchar *) stream_method_str); } /* no need to parse the rest of the fields, we've found the one we * wanted */ break; } if (*stream_methods == NULL) { DEBUG ("got a SI request without stream method proposed"); return FALSE; } return TRUE; } static void bytestream_state_changed (GibberBytestreamIface *bytestream, GibberBytestreamState state, WockyContact *contact) { if (state == GIBBER_BYTESTREAM_STATE_CLOSED) { WockyMetaPorter *porter; DEBUG ("bytestream closed, release the connection"); g_object_get (bytestream, "porter", &porter, NULL); wocky_meta_porter_unhold (porter, contact); g_object_unref (porter); g_object_unref (bytestream); } } static GibberBytestreamIface * choose_bytestream_method (SalutSiBytestreamManager *self, GSList *stream_methods, WockyPorter *porter, SalutContact *contact, const gchar *stream_id, WockyStanza *stream_init_iq) { SalutSiBytestreamManagerPrivate *priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE (self); GSList *l; /* We create the stream according the stream method chosen. * User has to accept it */ /* check OOB */ for (l = stream_methods; l != NULL; l = l->next) { if (!tp_strdiff (l->data, WOCKY_XMPP_NS_IQ_OOB)) { DEBUG ("choose OOB in methods list"); return g_object_new (GIBBER_TYPE_BYTESTREAM_OOB, "porter", porter, "stream-id", stream_id, "state", GIBBER_BYTESTREAM_STATE_LOCAL_PENDING, "self-id", priv->connection->name, "peer-id", contact->name, "contact", contact, "stream-init-iq", stream_init_iq, NULL); } } return NULL; } static gboolean si_request_cb (WockyPorter *porter, WockyStanza *stanza, gpointer user_data) { SalutSiBytestreamManager *self = SALUT_SI_BYTESTREAM_MANAGER (user_data); SalutSiBytestreamManagerPrivate *priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE (self); TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( (TpBaseConnection *) priv->connection, TP_HANDLE_TYPE_CONTACT); TpHandleRepoIface *room_repo = tp_base_connection_get_handles ( (TpBaseConnection *) priv->connection, TP_HANDLE_TYPE_ROOM); TpHandle peer_handle; GibberBytestreamIface *bytestream = NULL; WockyNode *top_node = wocky_stanza_get_top_node (stanza); WockyNode *si, *node; const gchar *profile, *from, *stream_id, *mime_type; GSList *stream_methods = NULL; WockyContact *contact = wocky_stanza_get_from_contact (stanza); /* after this point, the message is for us, so in all cases we either handle * it or send an error reply */ if (!streaminit_parse_request (stanza, &profile, &from, &stream_id, NULL, &mime_type, &stream_methods)) { GError err = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST, "failed to parse SI request" }; WockyStanza *reply; reply = wocky_stanza_build_iq_error (stanza, NULL); wocky_stanza_error_to_node (&err, wocky_stanza_get_top_node (reply)); wocky_porter_send (porter, reply); g_object_unref (reply); return TRUE; } si = wocky_node_get_child_ns (top_node, "si", WOCKY_XMPP_NS_SI); g_assert (si != NULL); DEBUG ("received a SI request"); peer_handle = tp_handle_lookup (contact_repo, from, NULL, NULL); if (peer_handle == 0) { goto out; } /* check stream method */ bytestream = choose_bytestream_method (self, stream_methods, porter, SALUT_CONTACT (contact), stream_id, stanza); if (bytestream == NULL) { GError err = { WOCKY_XMPP_ERROR, WOCKY_SI_ERROR_NO_VALID_STREAMS, NULL }; WockyStanza *reply; DEBUG ("SI request doesn't contain any supported stream method."); reply = wocky_stanza_build_iq_error (stanza, NULL); wocky_stanza_error_to_node (&err, wocky_stanza_get_top_node (reply)); wocky_porter_send (porter, reply); g_object_unref (reply); goto out; } /* Now that we have a bytestream, it's responsible for declining the IQ * if needed. */ /* As bytestreams are not porter aware, they can't take/release * the connection so we do it for them. * We'll release it when the bytestream will be closed */ wocky_meta_porter_hold (WOCKY_META_PORTER (porter), contact); g_signal_connect (bytestream, "state-changed", G_CALLBACK (bytestream_state_changed), contact); /* We inform the right manager we received a SI request */ if (tp_strdiff (profile, WOCKY_TELEPATHY_NS_TUBES)) { GError e = { WOCKY_SI_ERROR, WOCKY_SI_ERROR_BAD_PROFILE, "" }; DEBUG ("SI profile unsupported: %s", profile); gibber_bytestream_iface_close (bytestream, &e); goto out; } /* A Tubes SI request can only be a muc tube extra bytestream offer. * We don't use SI for 1-1 tubes */ if ((node = wocky_node_get_child_ns (si, "muc-stream", WOCKY_TELEPATHY_NS_TUBES))) { const gchar *muc; TpHandle room_handle; SalutMucManager *muc_mgr; muc = wocky_node_get_attribute (node, "muc"); if (muc == NULL) { DEBUG ("muc-stream SI doesn't contain muc attribute"); gibber_bytestream_iface_close (bytestream, NULL); goto out; } room_handle = tp_handle_lookup (room_repo, muc, NULL, NULL); if (room_handle == 0) { DEBUG ("Unknown room: %s\n", muc); gibber_bytestream_iface_close (bytestream, NULL); goto out; } g_object_get (priv->connection, "muc-manager", &muc_mgr, NULL); g_assert (muc_mgr != NULL); salut_muc_manager_handle_si_stream_request (muc_mgr, bytestream, room_handle, stream_id, stanza); g_object_unref (muc_mgr); } else { GError e = { WOCKY_XMPP_ERROR, WOCKY_XMPP_ERROR_BAD_REQUEST, "Invalid tube SI request: expected , or " "" }; DEBUG ("Invalid tube SI request"); gibber_bytestream_iface_close (bytestream, &e); goto out; } out: g_slist_free (stream_methods); return TRUE; } static void salut_si_bytestream_manager_dispose (GObject *object) { SalutSiBytestreamManager *self = SALUT_SI_BYTESTREAM_MANAGER (object); SalutSiBytestreamManagerPrivate *priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE (self); if (priv->dispose_has_run) return; priv->dispose_has_run = TRUE; if (priv->connection->porter != NULL) { wocky_porter_unregister_handler (priv->connection->porter, priv->si_request_id); priv->si_request_id = 0; } g_object_unref (priv->im_manager); g_object_unref (priv->muc_manager); if (G_OBJECT_CLASS (salut_si_bytestream_manager_parent_class)->dispose) G_OBJECT_CLASS (salut_si_bytestream_manager_parent_class)->dispose (object); } static void salut_si_bytestream_manager_finalize (GObject *object) { SalutSiBytestreamManager *self = SALUT_SI_BYTESTREAM_MANAGER (object); SalutSiBytestreamManagerPrivate *priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE ( self); g_free (priv->host_name_fqdn); if (G_OBJECT_CLASS (salut_si_bytestream_manager_parent_class)->finalize) G_OBJECT_CLASS (salut_si_bytestream_manager_parent_class)->finalize (object); } static void salut_si_bytestream_manager_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { SalutSiBytestreamManager *self = SALUT_SI_BYTESTREAM_MANAGER (object); SalutSiBytestreamManagerPrivate *priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE ( self); switch (property_id) { case PROP_CONNECTION: g_value_set_object (value, priv->connection); break; case PROP_HOST_NAME_FQDN: g_value_set_string (value, priv->host_name_fqdn); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void salut_si_bytestream_manager_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { SalutSiBytestreamManager *self = SALUT_SI_BYTESTREAM_MANAGER (object); SalutSiBytestreamManagerPrivate *priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE ( self); switch (property_id) { case PROP_CONNECTION: priv->connection = g_value_get_object (value); break; case PROP_HOST_NAME_FQDN: g_free (priv->host_name_fqdn); priv->host_name_fqdn = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static GObject * salut_si_bytestream_manager_constructor (GType type, guint n_props, GObjectConstructParam *props) { GObject *obj; SalutSiBytestreamManager *self; SalutSiBytestreamManagerPrivate *priv; obj = G_OBJECT_CLASS (salut_si_bytestream_manager_parent_class)-> constructor (type, n_props, props); self = SALUT_SI_BYTESTREAM_MANAGER (obj); priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE (self); g_assert (priv->connection != NULL); g_object_get (priv->connection, "im-manager", &(priv->im_manager), "muc-manager", &(priv->muc_manager), NULL); g_assert (priv->im_manager != NULL); g_assert (priv->muc_manager != NULL); g_assert (priv->host_name_fqdn != NULL); priv->si_request_id = wocky_porter_register_handler_from_anyone ( priv->connection->porter, WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET, WOCKY_PORTER_HANDLER_PRIORITY_NORMAL, si_request_cb, self, '(', "si", ':', WOCKY_XMPP_NS_SI, ')', NULL); return obj; } static void salut_si_bytestream_manager_class_init ( SalutSiBytestreamManagerClass *salut_si_bytestream_manager_class) { GObjectClass *object_class = G_OBJECT_CLASS (salut_si_bytestream_manager_class); GParamSpec *param_spec; g_type_class_add_private (salut_si_bytestream_manager_class, sizeof (SalutSiBytestreamManagerPrivate)); object_class->constructor = salut_si_bytestream_manager_constructor; object_class->dispose = salut_si_bytestream_manager_dispose; object_class->finalize = salut_si_bytestream_manager_finalize; object_class->get_property = salut_si_bytestream_manager_get_property; object_class->set_property = salut_si_bytestream_manager_set_property; param_spec = g_param_spec_object ( "connection", "SalutConnection object", "Salut Connection that owns the connection for this bytestream channel", SALUT_TYPE_CONNECTION, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); param_spec = g_param_spec_string ( "host-name-fqdn", "host name FQDN", "The FQDN host name that will be used by OOB bytestreams", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_HOST_NAME_FQDN, param_spec); } SalutSiBytestreamManager * salut_si_bytestream_manager_new (SalutConnection *conn, const gchar *host_name_fqdn) { g_return_val_if_fail (SALUT_IS_CONNECTION (conn), NULL); return g_object_new ( SALUT_TYPE_SI_BYTESTREAM_MANAGER, "connection", conn, "host-name-fqdn", host_name_fqdn, NULL); } /** * salut_si_bytestream_manager_make_stream_init_iq * * @from: your contact * @to: the contact to who you want to offer the stream * @stream_id: the stream ID of the new stream * @profile: the profile associated with the stream * * Create a SI request IQ as described in XEP-0095. * */ WockyStanza * salut_si_bytestream_manager_make_stream_init_iq (const gchar *from, const gchar *to, const gchar *stream_id, const gchar *profile) { return wocky_stanza_build ( WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET, from, to, '(', "si", ':', WOCKY_XMPP_NS_SI, '@', "id", stream_id, '@', "profile", profile, '@', "mime-type", "application/octet-stream", '(', "feature", ':', WOCKY_XMPP_NS_FEATURENEG, '(', "x", ':', WOCKY_XMPP_NS_DATA, '@', "type", "form", '(', "field", '@', "var", "stream-method", '@', "type", "list-single", '(', "option", '(', "value", '$', WOCKY_XMPP_NS_IQ_OOB, ')', ')', ')', ')', ')', ')', NULL); } struct streaminit_reply_cb_data { SalutSiBytestreamManager *self; gchar *stream_id; SalutSiBytestreamManagerNegotiateReplyFunc func; gpointer user_data; SalutContact *contact; WockyStanza *stanza; }; static struct streaminit_reply_cb_data * streaminit_reply_cb_data_new (void) { return g_slice_new0 (struct streaminit_reply_cb_data); } static void streaminit_reply_cb_data_free (struct streaminit_reply_cb_data *data) { g_free (data->stream_id); if (data->contact != NULL) g_object_unref (data->contact); if (data->stanza != NULL) g_object_unref (data->stanza); g_slice_free (struct streaminit_reply_cb_data, data); } static gboolean check_bytestream_oob_peer_addr (GibberBytestreamOOB *bytestream, struct sockaddr *addr, socklen_t addrlen, gpointer user_data) { SalutSiBytestreamManager *self = SALUT_SI_BYTESTREAM_MANAGER (user_data); SalutSiBytestreamManagerPrivate *priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE ( self); TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( (TpBaseConnection *) priv->connection, TP_HANDLE_TYPE_CONTACT); TpHandle handle; SalutContactManager *contact_mgr; SalutContact *contact; gchar *peer; gboolean result; g_object_get (bytestream, "peer-id", &peer, NULL); g_assert (peer != NULL); handle = tp_handle_lookup (contact_repo, peer, NULL, NULL); g_assert (handle != 0); g_free (peer); g_object_get (priv->connection, "contact-manager", &contact_mgr, NULL); g_assert (contact_mgr != NULL); contact = salut_contact_manager_get_contact (contact_mgr, handle); g_object_unref (contact_mgr); if (contact == NULL) return FALSE; result = salut_contact_has_address (contact, addr, addrlen); g_object_unref (contact); return result; } static void si_request_sent_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { WockyPorter *porter = WOCKY_PORTER (source_object); struct streaminit_reply_cb_data *data = (struct streaminit_reply_cb_data *) user_data; SalutSiBytestreamManagerPrivate *priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE (data->self); WockyStanza *stanza; GError *error = NULL; WockyStanzaSubType sub_type; WockyNode *si, *feature, *x; GibberBytestreamIface *bytestream = NULL; const gchar *from, *stream_method; GSList *x_children; WockyNode *node; stanza = wocky_porter_send_iq_finish (porter, result, &error); if (result == NULL) { DEBUG ("sending SI request failed: %s", error->message); g_clear_error (&error); goto END; } DEBUG ("received SI request response"); node = wocky_stanza_get_top_node (stanza); wocky_stanza_get_type_info (stanza, NULL, &sub_type); if (sub_type != WOCKY_STANZA_SUB_TYPE_RESULT) { DEBUG ("stream %s declined", data->stream_id); goto END; } /* stream accepted */ from = wocky_node_get_attribute (node, "from"); if (from == NULL) { DEBUG ("got a message without a from field"); goto END; } si = wocky_node_get_child_ns (node, "si", WOCKY_XMPP_NS_SI); if (si == NULL) { DEBUG ("got a SI reply without a si field"); goto END; } feature = wocky_node_get_child_ns (si, "feature", WOCKY_XMPP_NS_FEATURENEG); if (feature == NULL) { DEBUG ("got a SI reply without a feature field"); goto END; } x = wocky_node_get_child_ns (feature, "x", WOCKY_XMPP_NS_DATA); if (x == NULL) { DEBUG ("got a SI reply without a x field"); goto END; } for (x_children = x->children; x_children; x_children = g_slist_next (x_children)) { WockyNode *value, *field = x_children->data; if (tp_strdiff (wocky_node_get_attribute (field, "var"), "stream-method")) /* some future field, ignore it */ continue; value = wocky_node_get_child (field, "value"); if (value == NULL) { DEBUG ("SI reply's stream-method field " "doesn't contain stream-method value"); goto END; } stream_method = value->content; if (!tp_strdiff (stream_method, WOCKY_XMPP_NS_IQ_OOB)) { /* Remote user have accepted the stream */ DEBUG ("remote user chose a OOB bytestream"); bytestream = g_object_new (GIBBER_TYPE_BYTESTREAM_OOB, "porter", porter, "stream-id", data->stream_id, "state", GIBBER_BYTESTREAM_STATE_INITIATING, "self-id", priv->connection->name, "peer-id", from, "contact", wocky_stanza_get_from_contact (stanza), "stream-init-iq", NULL, "host", priv->host_name_fqdn, NULL); gibber_bytestream_oob_set_check_addr_func ( GIBBER_BYTESTREAM_OOB (bytestream), check_bytestream_oob_peer_addr, data->self); } else { DEBUG ("Remote user chose an unsupported stream method"); goto END; } /* no need to parse the rest of the fields, we've found the one we * wanted */ break; } if (bytestream == NULL) goto END; DEBUG ("stream %s accepted. Start to initiate it", data->stream_id); /* As bytestreams are not porter aware, they can't take/release * the connection so we do it for them. * We'll release it when the bytestream will be closed */ wocky_meta_porter_hold (WOCKY_META_PORTER (porter), WOCKY_CONTACT (data->contact)); g_signal_connect (bytestream, "state-changed", G_CALLBACK (bytestream_state_changed), data->contact); /* Let's start the initiation of the stream */ if (!gibber_bytestream_iface_initiate (bytestream)) { /* Initiation failed. */ gibber_bytestream_iface_close (bytestream, NULL); bytestream = NULL; } END: /* user callback */ data->func (bytestream, data->user_data); streaminit_reply_cb_data_free (data); if (stanza != NULL) g_object_unref (stanza); } /* * salut_si_bytestream_manager_negotiate_stream: * * @contact: the contact to who send the SI request * @stanza: the SI negotiation IQ (created using * salut_si_bytestream_manager_make_stream_init_iq) * @stream_id: the stream identifier * @func: the callback to call when we receive the answser of the request * @user_data: user data to pass to the callback * @error: pointer in which to return a GError in case of failure. * * Send a Stream Initiation (XEP-0095) request. */ gboolean salut_si_bytestream_manager_negotiate_stream (SalutSiBytestreamManager *self, SalutContact *contact, WockyStanza *stanza, const gchar *stream_id, SalutSiBytestreamManagerNegotiateReplyFunc func, gpointer user_data, GError **error) { SalutSiBytestreamManagerPrivate *priv; struct streaminit_reply_cb_data *data; g_assert (SALUT_IS_SI_BYTESTREAM_MANAGER (self)); g_assert (stream_id != NULL); g_assert (func != NULL); priv = SALUT_SI_BYTESTREAM_MANAGER_GET_PRIVATE (self); data = streaminit_reply_cb_data_new (); data->self = self; data->stream_id = g_strdup (stream_id); data->func = func; data->user_data = user_data; data->contact = g_object_ref (contact); data->stanza = g_object_ref (stanza); DEBUG ("send an SI request to %s", contact->name); wocky_porter_send_iq_async (priv->connection->porter, stanza, NULL, si_request_sent_cb, data); return TRUE; }