From 5a680fb4ad98dbd6ca9ee7311bad454d22b32388 Mon Sep 17 00:00:00 2001 From: Jakub Adam Date: Wed, 17 Sep 2014 12:50:52 +0200 Subject: [PATCH] appshare: basic RDP viewer implementation Expects xfreerdp binary in $PATH. --- configure.ac | 8 + src/core/Makefile.am | 11 +- src/core/sipe-appshare-xfreerdp.c | 112 +++++++++ .../{sipe-appshare.h => sipe-appshare-xfreerdp.h} | 8 +- src/core/sipe-appshare.c | 264 ++++++++++++++++++++- src/core/sipe-appshare.h | 12 + src/core/sipe-incoming.c | 6 +- 7 files changed, 410 insertions(+), 11 deletions(-) create mode 100644 src/core/sipe-appshare-xfreerdp.c copy src/core/{sipe-appshare.h => sipe-appshare-xfreerdp.h} (82%) diff --git a/configure.ac b/configure.ac index 603c7f7a..f81f70d0 100644 --- a/configure.ac +++ b/configure.ac @@ -232,6 +232,14 @@ dnl check for glib PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.12.0]) PKG_CHECK_MODULES(GMODULE, [gmodule-2.0 >= 2.12.0]) +dnl check for gio-2.0 optional for application sharing support +PKG_CHECK_MODULES(GIO, [gio-2.0], + [ac_have_gio=yes], + [ac_have_gio=no]) +AM_CONDITIONAL(SIPE_HAVE_GIO, [test "x$ac_have_gio" == xyes]) +AS_IF([test "x$ac_have_gio" == xyes], + [AC_DEFINE(HAVE_GIO, 1, [Define if we have GIO library.])]) + dnl check for gmime dnl See also: https://bugzilla.gnome.org/show_bug.cgi?id=613653#c8 PKG_CHECK_MODULES(GMIME, [gmime-2.6 >= 2.5.2], diff --git a/src/core/Makefile.am b/src/core/Makefile.am index e17d81c8..438587f8 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -211,8 +211,15 @@ endif if SIPE_HAVE_XDATA libsipe_core_la_SOURCES += \ - sipe-ft-lync.h sipe-ft-lync.c \ - sipe-appshare.h sipe-appshare.c + sipe-ft-lync.h sipe-ft-lync.c + +if SIPE_HAVE_GIO +libsipe_core_la_SOURCES += \ + sipe-appshare.h sipe-appshare.c \ + sipe-appshare-xfreerdp.h sipe-appshare-xfreerdp.c + +libsipe_core_la_CFLAGS += $(GIO_CFLAGS) +endif endif check_PROGRAMS = diff --git a/src/core/sipe-appshare-xfreerdp.c b/src/core/sipe-appshare-xfreerdp.c new file mode 100644 index 00000000..f2e8e362 --- /dev/null +++ b/src/core/sipe-appshare-xfreerdp.c @@ -0,0 +1,112 @@ +/** + * @file sipe-appshare-xfreerdp.c + * + * pidgin-sipe + * + * Copyright (C) 2014-2016 SIPE Project + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +#include + +#include +#include + +#include "sipe-appshare.h" +#include "sipe-appshare-xfreerdp.h" +#include "sipe-backend.h" +#include "sipe-common.h" +#include "sipe-utils.h" + +struct xfreerdp_data { + gchar *socket_path; +}; + +static GSocketAddress * +xfreerdp_get_listen_address(struct sipe_rdp_client *client) +{ + struct xfreerdp_data *data = client->client_data; + struct sockaddr_un address; + + data->socket_path = g_strdup_printf("%s/sipe-appshare-%u-%p", + g_get_user_runtime_dir(), getpid(), + client); + + g_unlink(data->socket_path); + + address.sun_family = AF_LOCAL; + strncpy(address.sun_path, data->socket_path, sizeof (address.sun_path)); + + return g_socket_address_new_from_native(&address, sizeof (address)); +} + +static gboolean +xfreerdp_launch(struct sipe_rdp_client *client, + SIPE_UNUSED_PARAMETER GSocketAddress *listen_address, + SIPE_UNUSED_PARAMETER struct sipe_media_stream *stream) +{ + struct xfreerdp_data *client_data = client->client_data; + gchar *cmdline; + GError *error = NULL; + + cmdline = g_strdup_printf("xfreerdp /v:%s /sec:rdp", + client_data->socket_path); + + g_spawn_command_line_async(cmdline, &error); + g_free(cmdline); + if (error) { + SIPE_DEBUG_ERROR("Can't launch xfreerdp: %s", error->message); + g_error_free(error); + return FALSE; + } + + return TRUE; +} + +static void +xfreerdp_free(struct sipe_rdp_client *client) +{ + struct xfreerdp_data *client_data = client->client_data; + + if (client_data->socket_path) { + g_unlink(client_data->socket_path); + g_free(client_data->socket_path); + } + + g_free(client_data); +} + +void +sipe_appshare_xfreerdp_init(struct sipe_rdp_client *client) +{ + client->client_data = g_new0(struct xfreerdp_data, 1); + + client->get_listen_address_cb = xfreerdp_get_listen_address; + client->launch_cb = xfreerdp_launch; + client->free_cb = xfreerdp_free; +} + +/* + Local Variables: + mode: c + c-file-style: "bsd" + indent-tabs-mode: t + tab-width: 8 + End: +*/ diff --git a/src/core/sipe-appshare.h b/src/core/sipe-appshare-xfreerdp.h similarity index 82% copy from src/core/sipe-appshare.h copy to src/core/sipe-appshare-xfreerdp.h index 870e256b..3fe1b1d5 100644 --- a/src/core/sipe-appshare.h +++ b/src/core/sipe-appshare-xfreerdp.h @@ -1,5 +1,5 @@ /** - * @file sipe-appshare.h + * @file sipe-appshare-xfreerdp.h * * pidgin-sipe * @@ -21,8 +21,6 @@ */ /* Forward declarations */ -struct sipe_core_private; -struct sipmsg; +struct sipe_rdp_client; -void process_incoming_invite_appshare(struct sipe_core_private *sipe_private, - struct sipmsg *msg); +void sipe_appshare_xfreerdp_init(struct sipe_rdp_client *client); diff --git a/src/core/sipe-appshare.c b/src/core/sipe-appshare.c index 80d3ae07..0ba5e2e1 100644 --- a/src/core/sipe-appshare.c +++ b/src/core/sipe-appshare.c @@ -22,18 +22,263 @@ #include +#include + #include "sipe-appshare.h" +#include "sipe-appshare-xfreerdp.h" #include "sipe-backend.h" +#include "sipe-common.h" #include "sipe-core.h" #include "sipe-media.h" +struct sipe_appshare { + struct sipe_media_stream *stream; + GSocket *socket; + GIOChannel *channel; + guint rdp_channel_readable_watch_id; + + struct sipe_rdp_client client; +}; + +static void +sipe_appshare_free(struct sipe_appshare *appshare) +{ + GError *error = NULL; + + g_source_destroy(g_main_context_find_source_by_id(NULL, + appshare->rdp_channel_readable_watch_id)); + + g_io_channel_shutdown(appshare->channel, TRUE, &error); + if (error) { + SIPE_DEBUG_ERROR("Error shutting down RDP channel: %s", + error->message); + g_error_free(error); + } + g_io_channel_unref(appshare->channel); + + g_object_unref(appshare->socket); + + if (appshare->client.free_cb) { + appshare->client.free_cb(&appshare->client); + } + + g_free(appshare); +} + +static gboolean +rdp_channel_readable_cb(GIOChannel *channel, + GIOCondition condition, + gpointer data) +{ + struct sipe_appshare *appshare = data; + GError *error = NULL; + gchar *buffer; + gsize bytes_read; + + if (condition & G_IO_HUP) { + struct sipe_media_call *call = appshare->stream->call; + + sipe_backend_media_hangup(call->backend_private, TRUE); + return FALSE; + } + + buffer = g_malloc(2048); + while (sipe_media_stream_is_writable(appshare->stream)) { + g_io_channel_read_chars(channel, buffer, 2048, &bytes_read, &error); + if (error) { + struct sipe_media_call *call = appshare->stream->call; + + SIPE_DEBUG_ERROR("Error reading from RDP channel: %s", + error->message); + g_error_free(error); + sipe_backend_media_hangup(call->backend_private, TRUE); + g_free(buffer); + return FALSE; + } + + if (bytes_read == 0) { + break; + } + + sipe_media_stream_write(appshare->stream, (guint8 *)buffer, + bytes_read); + SIPE_DEBUG_INFO("Written: %" G_GSIZE_FORMAT "\n", bytes_read); + } + g_free(buffer); + + return TRUE; +} + +static gboolean +socket_connect_cb(SIPE_UNUSED_PARAMETER GIOChannel *channel, + SIPE_UNUSED_PARAMETER GIOCondition condition, + gpointer data) +{ + struct sipe_appshare *appshare = data; + GError *error = NULL; + GSocket *data_socket; + + data_socket = g_socket_accept(appshare->socket, NULL, &error); + if (error) { + struct sipe_media_call *call = appshare->stream->call; + + SIPE_DEBUG_ERROR("Error accepting RDP client connection: %s", + error->message); + g_error_free(error); + sipe_backend_media_hangup(call->backend_private, TRUE); + return FALSE; + } + + g_io_channel_shutdown(appshare->channel, TRUE, &error); + if (error) { + struct sipe_media_call *call = appshare->stream->call; + + SIPE_DEBUG_ERROR("Error shutting down RDP channel: %s", + error->message); + g_error_free(error); + g_object_unref(data_socket); + sipe_backend_media_hangup(call->backend_private, TRUE); + return FALSE; + } + g_io_channel_unref(appshare->channel); + + g_object_unref(appshare->socket); + appshare->socket = data_socket; + + appshare->channel = g_io_channel_unix_new( + g_socket_get_fd(appshare->socket)); + + // No encoding for binary data + g_io_channel_set_encoding(appshare->channel, NULL, &error); + if (error) { + struct sipe_media_call *call = appshare->stream->call; + + SIPE_DEBUG_ERROR("Error setting RDP channel encoding: %s", + error->message); + g_error_free(error); + sipe_backend_media_hangup(call->backend_private, TRUE); + return FALSE; + } + + appshare->rdp_channel_readable_watch_id = + g_io_add_watch(appshare->channel, G_IO_IN | G_IO_HUP, + rdp_channel_readable_cb, appshare); + + return FALSE; +} + +static void +launch_rdp_client(struct sipe_appshare *appshare) +{ + struct sipe_rdp_client *client = &appshare->client; + struct sipe_media_call *call = appshare->stream->call; + GSocketAddress *address; + GError *error = NULL; + + address = client->get_listen_address_cb(client); + if (!address) { + sipe_backend_media_hangup(call->backend_private, TRUE); + return; + } + + appshare->socket = g_socket_new(g_socket_address_get_family(address), + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + if (error) { + SIPE_DEBUG_ERROR("Can't create RDP client listen socket: %s", + error->message); + g_error_free(error); + sipe_backend_media_hangup(call->backend_private, TRUE); + return; + } + + g_socket_set_blocking(appshare->socket, FALSE); + + g_socket_bind(appshare->socket, address, TRUE, &error); + g_object_unref(address); + if (error) { + SIPE_DEBUG_ERROR("Can't bind to RDP client socket: %s", + error->message); + g_error_free(error); + sipe_backend_media_hangup(call->backend_private, TRUE); + return; + } + + g_socket_listen(appshare->socket, &error); + if (error) { + SIPE_DEBUG_ERROR("Can't listen on RDP client socket: %s", + error->message); + g_error_free(error); + sipe_backend_media_hangup(call->backend_private, TRUE); + return; + } + + appshare->channel = g_io_channel_unix_new( + g_socket_get_fd(appshare->socket)); + appshare->rdp_channel_readable_watch_id = + g_io_add_watch(appshare->channel, G_IO_IN, + socket_connect_cb, appshare); + + address = g_socket_get_local_address(appshare->socket, &error); + if (error) { + SIPE_DEBUG_ERROR("Couldn't get appshare socket address: %s", + error->message); + g_error_free(error); + sipe_backend_media_hangup(call->backend_private, TRUE); + return; + } + + if (!client->launch_cb(client, address, appshare->stream)) { + sipe_backend_media_hangup(call->backend_private, TRUE); + } + + g_object_unref(address); +} + static void read_cb(struct sipe_media_stream *stream) { + struct sipe_appshare *appshare = sipe_media_stream_get_data(stream); guint8 buffer[0x800]; + gint bytes_read; + gsize bytes_written; + GError *error = NULL; SIPE_DEBUG_INFO_NOFORMAT("Incoming appshare data"); - sipe_backend_media_stream_read(stream, buffer, sizeof (buffer)); + bytes_read = sipe_backend_media_stream_read(stream, buffer, + sizeof (buffer)); + + if (bytes_read == 0) { + return; + } + + g_io_channel_write_chars(appshare->channel, (gchar *)buffer, + bytes_read, &bytes_written, &error); + if (error) { + SIPE_DEBUG_ERROR("Error writing RDP data: %s", error->message); + g_error_free(error); + sipe_backend_media_hangup(stream->call->backend_private, TRUE); + return; + } + if (g_io_channel_flush(appshare->channel, &error) == G_IO_STATUS_ERROR && + g_error_matches(error, G_IO_CHANNEL_ERROR, G_IO_CHANNEL_ERROR_PIPE)) { + g_error_free(error); + return; + } + + // TODO: Buffer data not written to RDP socket. + g_assert(bytes_read == (gint)bytes_written); +} + +static void +writable_cb(struct sipe_media_stream *stream) +{ + struct sipe_appshare *appshare = sipe_media_stream_get_data(stream); + + if (!appshare->socket) { + launch_rdp_client(appshare); + } } void @@ -42,15 +287,25 @@ process_incoming_invite_appshare(struct sipe_core_private *sipe_private, { struct sipe_media_call *call; struct sipe_media_stream *stream; + struct sipe_appshare *appshare; call = process_incoming_invite_call(sipe_private, msg); if (!call) { return; } - sipe_backend_media_accept(call->backend_private, TRUE); - stream = sipe_core_media_get_stream_by_id(call, "applicationsharing"); + if (!stream) { + sipe_backend_media_hangup(call->backend_private, TRUE); + return; + } + + appshare = g_new0(struct sipe_appshare, 1); + appshare->stream = stream; + sipe_appshare_xfreerdp_init(&appshare->client); + + sipe_media_stream_set_data(stream, appshare, + (GDestroyNotify)sipe_appshare_free); sipe_media_stream_add_extra_attribute(stream, "x-applicationsharing-session-id", "1"); @@ -60,6 +315,9 @@ process_incoming_invite_appshare(struct sipe_core_private *sipe_private, "x-applicationsharing-media-type", "rdp"); stream->read_cb = read_cb; + stream->writable_cb = writable_cb; + + sipe_backend_media_accept(call->backend_private, TRUE); } /* diff --git a/src/core/sipe-appshare.h b/src/core/sipe-appshare.h index 870e256b..029250e0 100644 --- a/src/core/sipe-appshare.h +++ b/src/core/sipe-appshare.h @@ -22,7 +22,19 @@ /* Forward declarations */ struct sipe_core_private; +struct sipe_media_stream; +struct sipe_rdp_client; struct sipmsg; +struct sipe_rdp_client { + void *client_data; + + GSocketAddress *(*get_listen_address_cb)(struct sipe_rdp_client *client); + gboolean (*launch_cb)(struct sipe_rdp_client *client, + GSocketAddress *listen_address, + struct sipe_media_stream *stream); + void (*free_cb)(struct sipe_rdp_client *client); +}; + void process_incoming_invite_appshare(struct sipe_core_private *sipe_private, struct sipmsg *msg); diff --git a/src/core/sipe-incoming.c b/src/core/sipe-incoming.c index 629d52e0..149d0d49 100644 --- a/src/core/sipe-incoming.c +++ b/src/core/sipe-incoming.c @@ -29,6 +29,10 @@ #include +#ifdef HAVE_GIO +#include +#endif + #include "sipe-appshare.h" #include "sipmsg.h" #include "sip-csta.h" @@ -416,7 +420,7 @@ void process_incoming_invite(struct sipe_core_private *sipe_private, if (sipe_strcase_equal(content_type, "application/sdp") && msg->body && strstr(msg->body, "m=applicationsharing") && sipe_strequal(sipmsg_find_header(msg, "CSeq"), "1 INVITE")) { -#ifdef HAVE_XDATA +#if defined(HAVE_XDATA) && defined(HAVE_GIO) process_incoming_invite_appshare(sipe_private, msg); #else sip_transport_response(sipe_private, msg, -- 2.11.4.GIT