From c6e99d55b9c879fd15db64b1c5e92bc622e99c9e Mon Sep 17 00:00:00 2001 From: Stefan Becker Date: Sun, 16 May 2010 20:12:57 +0300 Subject: [PATCH] core cleanup: move purple specific file transfer code to backend sipe-ft.c is now purple free. Another rip apart and put together change. I hope I didn't break anything... --- po/POTFILES.in | 1 + src/api/core-depurple.h | 21 - src/api/sipe-backend.h | 49 ++ src/api/sipe-core.h | 30 + src/core/Makefile.am | 6 +- src/core/sipe-core-private.h | 3 + src/core/sipe-ft.c | 1952 ++++++++++++++++++++---------------------- src/core/sipe-ft.h | 45 +- src/core/sipe.c | 16 +- src/core/sipe.h | 1 - src/purple/Makefile.am | 1 + src/purple/purple-ft.c | 402 +++++++++ src/purple/purple-private.h | 22 + 13 files changed, 1459 insertions(+), 1090 deletions(-) rewrite src/core/sipe-ft.c (65%) create mode 100644 src/purple/purple-ft.c diff --git a/po/POTFILES.in b/po/POTFILES.in index a1706e78..f26b13b8 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -9,5 +9,6 @@ src/core/sipe-conf.c src/core/sipe-core.c src/core/sipe-domino.c src/core/sipe-ft.c +src/purple/purple-ft.c src/purple/purple-plugin.c src/purple/purple-transport.c diff --git a/src/api/core-depurple.h b/src/api/core-depurple.h index 2316fc5f..8d5230d1 100644 --- a/src/api/core-depurple.h +++ b/src/api/core-depurple.h @@ -53,29 +53,8 @@ void sipe_rename_group(PurpleConnection *gc, const char *old_name, void sipe_convo_closed(PurpleConnection *gc, const char *who); void sipe_remove_group(PurpleConnection *gc, PurpleGroup *group); -/** - * Initiates outgoing file transfer, sending @c file to remote peer identified - * by @c who. - * - * @param gc a PurpleConnection - * @param who string identifying receiver of the file - * @param file local file system path of the file to send - */ -void sipe_ft_send_file(PurpleConnection *gc, const char *who, - const char *file); - -/** - * Creates new PurpleXfer structure representing a file transfer. - * - * @param gc a PurpleConnection - * @param who remote participant in the file transfer session - */ -PurpleXfer *sipe_ft_new_xfer(PurpleConnection *gc, - const char *who); - /* Convenience macros */ #define PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE ((struct sipe_core_private *)account->gc->proto_data) #define PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE ((struct sipe_core_private *)buddy->account->gc->proto_data) #define PURPLE_CHAT_TO_SIPE_CORE_PRIVATE ((struct sipe_core_private *)chat->account->gc->proto_data) #define PURPLE_GC_TO_SIPE_CORE_PRIVATE ((struct sipe_core_private *)gc->proto_data) -#define PURPLE_XFER_TO_SIPE_CORE_PRIVATE ((struct sipe_core_private *)xfer->account->gc->proto_data) diff --git a/src/api/sipe-backend.h b/src/api/sipe-backend.h index 49921512..6162bf01 100644 --- a/src/api/sipe-backend.h +++ b/src/api/sipe-backend.h @@ -44,6 +44,7 @@ extern "C" { /* Forward declarations */ struct sipe_core_public; struct sipe_transport_connection; +struct sipe_file_transfer; struct sipe_media_call; struct sipe_media; @@ -107,6 +108,54 @@ void sipe_backend_dns_query(struct sipe_core_public *sipe_public, const gchar *transport, const gchar *domain); +/** FILE TRANSFER ************************************************************/ +void sipe_backend_ft_error(struct sipe_file_transfer *ft, + const gchar *errmsg); +const gchar *sipe_backend_ft_get_error(struct sipe_file_transfer *ft); +void sipe_backend_ft_deallocate(struct sipe_file_transfer *ft); + +/** + * Try to read up to @c size bytes from file transfer connection + * + * @param backend_ft backend private file transfer data. + * @param data buffer to read data into. + * @param size buffer size in bytes. + * + * @return number of bytes read or negative on failure. + * EAGAIN should return 0 bytes read. + */ +gssize sipe_backend_ft_read(struct sipe_file_transfer *ft, + guchar *data, + gsize size); + +/** + * Try to write up to @c size bytes to file transfer connection + * + * @param backend_ft backend private file transfer data. + * @param data data to write + * @param size buffer size in bytes. + * + * @return number of bytes read or negative on failure. + * EAGAIN should return 0 bytes written. + */ +gssize sipe_backend_ft_write(struct sipe_file_transfer *ft, + const guchar *data, + gsize size); + + +void sipe_backend_ft_cancel_local(struct sipe_file_transfer *ft); +void sipe_backend_ft_cancel_remote(struct sipe_file_transfer *ft); + +void sipe_backend_ft_incoming(struct sipe_core_public *sipe_public, + struct sipe_file_transfer *ft, + const gchar *who, + const gchar *file_name, + gsize file_size); +gboolean sipe_backend_ft_incoming_accept(struct sipe_file_transfer *ft, + const gchar *ip, + unsigned short port_min, + unsigned short port_max); + /** MARKUP *******************************************************************/ gchar *sipe_backend_markup_css_property(const gchar *style, diff --git a/src/api/sipe-core.h b/src/api/sipe-core.h index 0103cc1d..5b7beca2 100644 --- a/src/api/sipe-core.h +++ b/src/api/sipe-core.h @@ -93,6 +93,13 @@ struct sipe_transport_connection { }; /** + * File transport (public part) + */ +struct sipe_file_transfer { + struct sipe_backend_file_transfer *backend_private; +}; + +/** * Opaque data type for backend private data. * The backend is responsible to allocate and free it. */ @@ -256,6 +263,29 @@ void sipe_core_chat_create(struct sipe_core_public *sipe_public, int id, void sipe_core_media_initiate_call(struct sipe_core_public *sipe_public, const char *participant); +/* file transfer */ +struct sipe_file_transfer *sipe_core_ft_allocate(struct sipe_core_public *sipe_public); +void sipe_core_ft_deallocate(struct sipe_file_transfer *ft); +void sipe_core_ft_cancel(struct sipe_file_transfer *ft); +void sipe_core_ft_incoming_init(struct sipe_file_transfer *ft); +void sipe_core_ft_incoming_accept(struct sipe_file_transfer *ft, + const gchar *who, + int fd, + unsigned short port); +void sipe_core_ft_incoming_start(struct sipe_file_transfer *ft, + gsize total_size); +gboolean sipe_core_ft_incoming_stop(struct sipe_file_transfer *ft); +void sipe_core_ft_outgoing_init(struct sipe_file_transfer *ft, + const gchar *filename, gsize size, + const gchar *who); +void sipe_core_ft_outgoing_start(struct sipe_file_transfer *ft, + gsize total_size); +gboolean sipe_core_ft_outgoing_stop(struct sipe_file_transfer *ft); +gssize sipe_core_ft_read(struct sipe_file_transfer *ft, guchar **buffer, + gsize bytes_remaining, gsize bytes_available); +gssize sipe_core_ft_write(struct sipe_file_transfer *ft, + const guchar *buffer, gsize size); + #ifdef __cplusplus } #endif diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 11d07036..4d55d909 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -38,6 +38,8 @@ libsipe_core_la_SOURCES = \ sipe-domino.c \ sipe-ews.h \ sipe-ews.c \ + sipe-ft.h \ + sipe-ft.c \ sipe-schedule.h \ sipe-schedule.c \ sipe-session.h \ @@ -58,9 +60,7 @@ libsipe_core_purple_la_SOURCES = \ sipe.h \ sipe.c \ sipe-conf.h \ - sipe-conf.c \ - sipe-ft.h \ - sipe-ft.c + sipe-conf.c AM_CFLAGS = $(st) diff --git a/src/core/sipe-core-private.h b/src/core/sipe-core-private.h index 8373fd9d..ee618d69 100644 --- a/src/core/sipe-core-private.h +++ b/src/core/sipe-core-private.h @@ -55,6 +55,9 @@ struct sipe_core_private { /* Buddies */ GHashTable *buddies; + /* File Transfer */ + GHashTable *filetransfers; + /* Scheduling system */ GSList *timeouts; diff --git a/src/core/sipe-ft.c b/src/core/sipe-ft.c dissimilarity index 65% index 3a0a0853..5bc604c4 100644 --- a/src/core/sipe-ft.c +++ b/src/core/sipe-ft.c @@ -1,1039 +1,913 @@ -/** - * @file sipe-ft.c - * - * pidgin-sipe - * - * Copyright (C) 2010 Jakub Adam - * Copyright (C) 2010 Tomáš Hrabčík - * - * 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 - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include -#include -#include -#include - -#include -#include - -#include "connection.h" -#include "eventloop.h" -#include "ft.h" -#include "network.h" -#include "request.h" - -#ifdef _WIN32 -/* for network */ -#include "libc_interface.h" -#include -#else -#include -#include -#include -#include -#include -#ifdef HAVE_SYS_SOCKIO_H -#include /* SIOCGIFCONF for Solaris */ -#endif -#endif - -#include "core-depurple.h" /* Temporary for the core de-purple transition */ - -#include "sipe-common.h" -#include "sipmsg.h" -#include "sip-transport.h" -#include "sipe-backend.h" -#include "sipe-core.h" -#include "sipe-core-private.h" -#include "sipe-crypt.h" -#include "sipe-dialog.h" -#include "sipe-digest.h" -#include "sipe-nls.h" -#include "sipe-ft.h" -#include "sipe-session.h" -#include "sipe-utils.h" -#include "sipe.h" - -#define SIPE_FT_KEY_LENGTH 24 -#define SIPE_FT_CHUNK_HEADER_LENGTH 3 - -/* - * DO NOT CHANGE THE FOLLOWING CONSTANTS!!! - * - * It seems that Microsoft Office Communicator client will accept - * file transfer invitations *only* within this port range! - * - * If a firewall is active on your system you need to open these ports if - * you want to *send* files to other users. Receiving files uses an ougoing - * connection and should therefore automatically penetrate your firewall. - */ -#define SIPE_FT_TCP_PORT_MIN 6891 -#define SIPE_FT_TCP_PORT_MAX 6901 - -struct _sipe_file_transfer { - guchar encryption_key[SIPE_FT_KEY_LENGTH]; - guchar hash_key[SIPE_FT_KEY_LENGTH]; - gchar *invitation_cookie; - unsigned auth_cookie; - struct sipe_core_private *sipe_private; - struct sip_dialog *dialog; - gpointer cipher_context; - gpointer hmac_context; - - PurpleNetworkListenData *listener; - int listenfd; - - gsize bytes_remaining_chunk; - guchar* encrypted_outbuf; - guchar* outbuf_ptr; - gsize outbuf_size; -}; -typedef struct _sipe_file_transfer sipe_file_transfer; - -static void send_filetransfer_accept(PurpleXfer* xfer); -static void send_filetransfer_cancel(PurpleXfer* xfer); -static ssize_t do_read(PurpleXfer *xfer, guchar *buf, size_t len); -static gboolean read_fully(PurpleXfer *xfer, guchar *buf, size_t len); -static gssize read_line(PurpleXfer *xfer, gchar *buffer, gssize size); -static gpointer sipe_cipher_context_init(const guchar *enc_key); -static gpointer sipe_hmac_context_init(const guchar *hash_key); -static gchar *sipe_hmac_finalize(gpointer hmac_context); -static void generate_key(guchar *buffer, gsize size); -static void set_socket_nonblock(int fd, gboolean state); -static void sipe_ft_listen_socket_created(int listenfd, gpointer data); - -//****************************************************************************** -// I/O operations for PurpleXfer structure -//****************************************************************************** - -static void -sipe_ft_incoming_init(PurpleXfer *xfer) -{ - send_filetransfer_accept(xfer); -} - -static void -sipe_ft_free_xfer_struct(PurpleXfer *xfer) -{ - sipe_file_transfer *ft = xfer->data; - if (ft) { - struct sipe_core_private *sipe_private = PURPLE_XFER_TO_SIPE_CORE_PRIVATE; - struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE; - - g_hash_table_remove(sip->filetransfers,ft->invitation_cookie); - - if (xfer->watcher) { - purple_input_remove(xfer->watcher); - xfer->watcher = 0; - } - if (ft->listenfd >= 0) { - SIPE_DEBUG_INFO("sipe_ft_free_xfer_struct: closing listening socket %d", ft->listenfd); - close(ft->listenfd); - } - if (ft->listener) - purple_network_listen_cancel(ft->listener); - if (ft->cipher_context) - sipe_crypt_ft_destroy(ft->cipher_context); - - if (ft->hmac_context) - sipe_digest_ft_destroy(ft->hmac_context); - - g_free(ft->encrypted_outbuf); - g_free(ft->invitation_cookie); - g_free(ft); - xfer->data = NULL; - } -} - -static void -sipe_ft_request_denied(PurpleXfer *xfer) -{ - if (xfer->type == PURPLE_XFER_RECEIVE) - send_filetransfer_cancel(xfer); - sipe_ft_free_xfer_struct(xfer); -} - -static -void raise_ft_error(PurpleXfer *xfer, const char *errmsg) -{ - purple_xfer_error(purple_xfer_get_type(xfer), - xfer->account, xfer->who, - errmsg); -} - -static -void raise_ft_strerror(PurpleXfer *xfer, const char *errmsg) -{ - gchar *tmp = g_strdup_printf("%s: %s", errmsg, strerror(errno)); - raise_ft_error(xfer, tmp); - g_free(tmp); -} - -static -void raise_ft_error_and_cancel(PurpleXfer *xfer, const char *errmsg) -{ - raise_ft_error(xfer, errmsg); - purple_xfer_cancel_local(xfer); -} - -static -void raise_ft_socket_read_error_and_cancel(PurpleXfer *xfer) -{ - raise_ft_error_and_cancel(xfer, _("Socket read failed")); -} - -static -void raise_ft_socket_write_error_and_cancel(PurpleXfer *xfer) -{ - raise_ft_error_and_cancel(xfer, _("Socket write failed")); -} - -static void -sipe_ft_incoming_start(PurpleXfer *xfer) -{ - sipe_file_transfer *ft; - static const gchar VER[] = "VER MSN_SECURE_FTP\r\n"; - static const gchar TFR[] = "TFR\r\n"; - const gsize BUFFER_SIZE = 50; - gchar buf[BUFFER_SIZE]; - struct sipe_core_private *sipe_private = PURPLE_XFER_TO_SIPE_CORE_PRIVATE; - gchar* request; - const gsize FILE_SIZE_OFFSET = 4; - gsize file_size; - - ft = xfer->data; - - if (write(xfer->fd,VER,strlen(VER)) == -1) { - raise_ft_socket_write_error_and_cancel(xfer); - return; - } - if (read_line(xfer, buf, BUFFER_SIZE) < 0) { - raise_ft_socket_read_error_and_cancel(xfer); - return; - } - - request = g_strdup_printf("USR %s %u\r\n", sipe_private->username, ft->auth_cookie); - if (write(xfer->fd,request,strlen(request)) == -1) { - raise_ft_socket_write_error_and_cancel(xfer); - g_free(request); - return; - } - g_free(request); - - if (read_line(xfer, buf, BUFFER_SIZE) < 0) { - raise_ft_socket_read_error_and_cancel(xfer); - return; - } - - file_size = g_ascii_strtoull(buf + FILE_SIZE_OFFSET,NULL,10); - if (file_size != xfer->size) { - raise_ft_error_and_cancel(xfer, - _("File size is different from the advertised value.")); - return; - } - - if (write(xfer->fd,TFR,strlen(TFR)) == -1) { - raise_ft_socket_write_error_and_cancel(xfer); - return; - } - - ft->bytes_remaining_chunk = 0; - - ft->cipher_context = sipe_cipher_context_init(ft->encryption_key); - ft->hmac_context = sipe_hmac_context_init(ft->hash_key); -} - -static void -sipe_ft_incoming_stop(PurpleXfer *xfer) -{ - static const gchar BYE[] = "BYE 16777989\r\n"; - gsize BUFFER_SIZE = 50; - char buffer[BUFFER_SIZE]; - const gssize MAC_OFFSET = 4; - const gssize CRLF_LEN = 2; - gssize macLen; - sipe_file_transfer *ft; - gchar *mac; - gchar *mac1; - - if (write(xfer->fd,BYE,strlen(BYE)) == -1) { - raise_ft_socket_write_error_and_cancel(xfer); - return; - } - - macLen = read_line(xfer, buffer, BUFFER_SIZE); - - if (macLen < 0) { - raise_ft_socket_read_error_and_cancel(xfer); - return; - } else if (macLen < (MAC_OFFSET + CRLF_LEN)) { - raise_ft_error_and_cancel(xfer, _("Received MAC is corrupted")); - return; - } - - // Check MAC - ft = xfer->data; - mac = g_strndup(buffer + MAC_OFFSET, macLen - MAC_OFFSET - CRLF_LEN); - mac1 = sipe_hmac_finalize(ft->hmac_context); - if (!sipe_strequal(mac, mac1)) { - unlink(xfer->local_filename); - raise_ft_error_and_cancel(xfer, - _("Received file is corrupted")); - } - g_free(mac1); - g_free(mac); - - sipe_ft_free_xfer_struct(xfer); -} - -static gssize -sipe_ft_read(guchar **buffer, PurpleXfer *xfer) -{ - gsize bytes_to_read; - ssize_t bytes_read; - - sipe_file_transfer *ft = xfer->data; - - if (ft->bytes_remaining_chunk == 0) { - guchar hdr_buf[SIPE_FT_CHUNK_HEADER_LENGTH]; - - /* read chunk header */ - if (!read_fully(xfer, hdr_buf, sizeof(hdr_buf))) { - raise_ft_strerror(xfer, _("Socket read failed")); - return -1; - } - - /* chunk header format: - * - * 0: 00 unknown (always zero?) - * 1: LL chunk size in bytes (low byte) - * 2: HH chunk size in bytes (high byte) - * - * Convert size from little endian to host order - */ - ft->bytes_remaining_chunk = hdr_buf[1] + (hdr_buf[2] << 8); - } - - bytes_to_read = MIN(purple_xfer_get_bytes_remaining(xfer), - xfer->current_buffer_size); - bytes_to_read = MIN(bytes_to_read, ft->bytes_remaining_chunk); - - *buffer = g_malloc(bytes_to_read); - if (!*buffer) { - raise_ft_error(xfer, _("Out of memory")); - SIPE_DEBUG_ERROR("sipe_ft_read: can't allocate %" G_GSIZE_FORMAT " bytes for receive buffer", - bytes_to_read); - return -1; - } - - bytes_read = do_read(xfer, *buffer, bytes_to_read); - if (bytes_read < 0) { - raise_ft_strerror(xfer, _("Socket read failed")); - return -1; - } - - if (bytes_read > 0) { - guchar *decrypted = g_malloc(bytes_read); - - if (!decrypted) { - raise_ft_error(xfer, _("Out of memory")); - SIPE_DEBUG_ERROR("sipe_ft_read: can't allocate %" G_GSIZE_FORMAT " bytes for decryption buffer", - (gsize)bytes_read); - g_free(*buffer); - *buffer = NULL; - return -1; - } - sipe_crypt_ft_stream(ft->cipher_context, *buffer, bytes_read, decrypted); - g_free(*buffer); - *buffer = decrypted; - - sipe_digest_ft_update(ft->hmac_context, decrypted, bytes_read); - - ft->bytes_remaining_chunk -= bytes_read; - } - - return bytes_read; -} - -static gssize -sipe_ft_write(const guchar *buffer, size_t size, PurpleXfer *xfer) -{ - ssize_t bytes_written; - sipe_file_transfer *ft = xfer->data; - - /* When sending data via server with ForeFront installed, block bigger than - * this default causes ending of transmission. Hard limit block to this value - * when libpurple sends us more data. */ - const gsize DEFAULT_BLOCK_SIZE = 2045; - if (size > DEFAULT_BLOCK_SIZE) - size = DEFAULT_BLOCK_SIZE; - - if (ft->bytes_remaining_chunk == 0) { - ssize_t bytes_read; - guchar local_buf[16]; - guchar hdr_buf[SIPE_FT_CHUNK_HEADER_LENGTH]; - - memset(local_buf, 0, sizeof local_buf); - - // Check if receiver did not cancel the transfer before it is finished - bytes_read = read(xfer->fd,local_buf,sizeof (local_buf)); - if (bytes_read == -1 && errno != EAGAIN) { - raise_ft_strerror(xfer, _("Socket read failed")); - return -1; - } else if (bytes_read > 0 - && (g_str_has_prefix((gchar*)local_buf,"CCL\r\n") - || g_str_has_prefix((gchar*)local_buf,"BYE 2164261682\r\n"))) { - return -1; - } - - if (ft->outbuf_size < size) { - g_free(ft->encrypted_outbuf); - ft->outbuf_size = size; - ft->encrypted_outbuf = g_malloc(ft->outbuf_size); - if (!ft->encrypted_outbuf) { - raise_ft_error(xfer, _("Out of memory")); - SIPE_DEBUG_ERROR("sipe_ft_write: can't allocate %" G_GSIZE_FORMAT " bytes for send buffer", - ft->outbuf_size); - return -1; - } - } - - ft->bytes_remaining_chunk = size; - ft->outbuf_ptr = ft->encrypted_outbuf; - sipe_crypt_ft_stream(ft->cipher_context, buffer, size, - ft->encrypted_outbuf); - sipe_digest_ft_update(ft->hmac_context, buffer, size); - - /* chunk header format: - * - * 0: 00 unknown (always zero?) - * 1: LL chunk size in bytes (low byte) - * 2: HH chunk size in bytes (high byte) - * - * Convert size from host order to little endian - */ - hdr_buf[0] = 0; - hdr_buf[1] = (ft->bytes_remaining_chunk & 0x00FF); - hdr_buf[2] = (ft->bytes_remaining_chunk & 0xFF00) >> 8; - - /* write chunk header */ - if (write(xfer->fd, hdr_buf, sizeof(hdr_buf)) == -1) { - raise_ft_strerror(xfer, _("Socket write failed")); - return -1; - } - } - - bytes_written = write(xfer->fd, ft->outbuf_ptr, ft->bytes_remaining_chunk); - if (bytes_written == -1) { - if (errno == EAGAIN) - bytes_written = 0; - else { - raise_ft_strerror(xfer, _("Socket write failed")); - } - } - - if (bytes_written > 0) { - ft->bytes_remaining_chunk -= bytes_written; - ft->outbuf_ptr += bytes_written; - } - - if ((xfer->bytes_remaining - bytes_written) == 0) - purple_xfer_set_completed(xfer, TRUE); - - return bytes_written; -} - -static void -sipe_ft_outgoing_init(PurpleXfer *xfer) -{ - struct sip_dialog *dialog; - sipe_file_transfer *ft = xfer->data; - - gchar *body = g_strdup_printf("Application-Name: File Transfer\r\n" - "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n" - "Invitation-Command: INVITE\r\n" - "Invitation-Cookie: %s\r\n" - "Application-File: %s\r\n" - "Application-FileSize: %lu\r\n" - //"Connectivity: N\r\n" TODO - "Encryption: R\r\n", // TODO: non encrypted file transfer support - ft->invitation_cookie, - purple_xfer_get_filename(xfer), - (long unsigned) purple_xfer_get_size(xfer)); - - struct sipe_core_private *sipe_private = PURPLE_XFER_TO_SIPE_CORE_PRIVATE; - struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE; - struct sip_session *session = sipe_session_find_or_add_im(sipe_private, xfer->who); - - g_hash_table_insert(sip->filetransfers,g_strdup(ft->invitation_cookie),xfer); - - // Queue the message - sipe_session_enqueue_message(session, body, "text/x-msmsgsinvite"); - - dialog = sipe_dialog_find(session, xfer->who); - if (dialog && !dialog->outgoing_invite) { - ft->dialog = dialog; - sipe_im_process_queue(sipe_private, session); - } else if (!dialog || !dialog->outgoing_invite) { - // Need to send the INVITE to get the outgoing dialog setup - sipe_invite(sipe_private, session, xfer->who, body, "text/x-msmsgsinvite", NULL, FALSE); - } - - g_free(body); -} - -static void -sipe_ft_outgoing_start(PurpleXfer *xfer) -{ - sipe_file_transfer *ft; - static const gchar VER[] = "VER MSN_SECURE_FTP\r\n"; - const gsize BUFFER_SIZE = 50; - gchar buf[BUFFER_SIZE]; - gchar** parts; - unsigned auth_cookie_received; - gboolean users_match; - gchar *tmp; - ssize_t bytes_written; - - set_socket_nonblock(xfer->fd, TRUE); - - ft = xfer->data; - - if (read_line(xfer, buf, BUFFER_SIZE) < 0) { - raise_ft_socket_read_error_and_cancel(xfer); - return; - } - - if (!sipe_strequal(buf,VER)) { - raise_ft_error_and_cancel(xfer,_("File transfer initialization failed.")); - SIPE_DEBUG_INFO("File transfer VER string incorrect, received: %s expected: %s", - buf, VER); - return; - } - - if (write(xfer->fd,VER,strlen(VER)) == -1) { - raise_ft_socket_write_error_and_cancel(xfer); - return; - } - - if (read_line(xfer, buf, BUFFER_SIZE) < 0) { - raise_ft_socket_read_error_and_cancel(xfer); - return; - } - - parts = g_strsplit(buf, " ", 3); - - auth_cookie_received = g_ascii_strtoull(parts[2],NULL,10); - - // xfer->who has 'sip:' prefix, skip these four characters - users_match = sipe_strcase_equal(parts[1], (xfer->who + 4)); - g_strfreev(parts); - - SIPE_DEBUG_INFO("File transfer authentication: %s Expected: USR %s %u", - buf, xfer->who + 4, ft->auth_cookie); - - if (!users_match || (ft->auth_cookie != auth_cookie_received)) { - raise_ft_error_and_cancel(xfer, - _("File transfer authentication failed.")); - return; - } - - tmp = g_strdup_printf("FIL %lu\r\n",(long unsigned) xfer->size); - bytes_written = write(xfer->fd, tmp, strlen(tmp)); - g_free(tmp); - - if (bytes_written == -1) { - raise_ft_socket_write_error_and_cancel(xfer); - return; - } - - // TFR - if (read_line(xfer,buf,BUFFER_SIZE) < 0) { - raise_ft_socket_read_error_and_cancel(xfer); - return; - } - - ft->bytes_remaining_chunk = 0; - - ft->cipher_context = sipe_cipher_context_init(ft->encryption_key); - ft->hmac_context = sipe_hmac_context_init(ft->hash_key); -} - -static void -sipe_ft_outgoing_stop(PurpleXfer *xfer) -{ - sipe_file_transfer *ft = xfer->data; - gsize BUFFER_SIZE = 50; - char buffer[BUFFER_SIZE]; - gchar *mac; - gsize mac_strlen; - - // BYE - if (read_line(xfer, buffer, BUFFER_SIZE) < 0) { - raise_ft_socket_read_error_and_cancel(xfer); - return; - } - - mac = sipe_hmac_finalize(ft->hmac_context); - g_sprintf(buffer, "MAC %s \r\n", mac); - g_free(mac); - - mac_strlen = strlen(buffer); - // There must be this zero byte between mac and \r\n - buffer[mac_strlen - 3] = 0; - - if (write(xfer->fd,buffer,mac_strlen) == -1) { - raise_ft_socket_write_error_and_cancel(xfer); - return; - } - - sipe_ft_free_xfer_struct(xfer); -} - -//****************************************************************************** - -void sipe_ft_incoming_transfer(PurpleAccount *account, struct sipmsg *msg, const GSList *body) -{ - PurpleXfer *xfer; - struct sipe_core_private *sipe_private = PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE; - struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE; - const gchar *callid = sipmsg_find_header(msg, "Call-ID"); - gchar *from = parse_from(sipmsg_find_header(msg, "From")); - struct sip_session *session = sipe_session_find_chat_or_im(sipe_private, - callid, - from); - g_free(from); - - if (!session) { - SIPE_DEBUG_ERROR_NOFORMAT("sipe_ft_incoming_transfer: can't find session for remote party"); - return; - } - - xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE, session->with); - - if (xfer) { - size_t file_size; - sipe_file_transfer *ft = g_new0(sipe_file_transfer, 1); - ft->invitation_cookie = g_strdup(sipe_utils_nameval_find(body, "Invitation-Cookie")); - ft->sipe_private = sipe_private; - ft->dialog = sipe_dialog_find(session, session->with); - ft->listenfd = -1; - generate_key(ft->encryption_key, SIPE_FT_KEY_LENGTH); - generate_key(ft->hash_key, SIPE_FT_KEY_LENGTH); - xfer->data = ft; - - purple_xfer_set_filename(xfer, sipe_utils_nameval_find(body, "Application-File")); - - file_size = g_ascii_strtoull(sipe_utils_nameval_find(body, "Application-FileSize"),NULL,10); - purple_xfer_set_size(xfer, file_size); - - purple_xfer_set_init_fnc(xfer, sipe_ft_incoming_init); - purple_xfer_set_start_fnc(xfer,sipe_ft_incoming_start); - purple_xfer_set_end_fnc(xfer,sipe_ft_incoming_stop); - purple_xfer_set_request_denied_fnc(xfer, sipe_ft_request_denied); - purple_xfer_set_read_fnc(xfer,sipe_ft_read); - purple_xfer_set_cancel_send_fnc(xfer,sipe_ft_free_xfer_struct); - purple_xfer_set_cancel_recv_fnc(xfer,sipe_ft_free_xfer_struct); - - g_hash_table_insert(sip->filetransfers,g_strdup(ft->invitation_cookie),xfer); - - purple_xfer_request(xfer); - } -} - -void sipe_ft_incoming_accept(PurpleAccount *account, const GSList *body) -{ - struct sipe_core_private *sipe_private = PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE; - struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE; - const gchar *inv_cookie = sipe_utils_nameval_find(body, "Invitation-Cookie"); - PurpleXfer *xfer = g_hash_table_lookup(sip->filetransfers,inv_cookie); - - if (xfer) { - const gchar *ip = sipe_utils_nameval_find(body, "IP-Address"); - const gchar *port_str = sipe_utils_nameval_find(body, "Port"); - const gchar *auth_cookie = sipe_utils_nameval_find(body, "AuthCookie"); - const gchar *enc_key_b64 = sipe_utils_nameval_find(body, "Encryption-Key"); - const gchar *hash_key_b64 = sipe_utils_nameval_find(body, "Hash-Key"); - - sipe_file_transfer *ft = xfer->data; - - if (auth_cookie) - ft->auth_cookie = g_ascii_strtoull(auth_cookie,NULL,10); - if (enc_key_b64) { - gsize ret_len; - guchar *enc_key = g_base64_decode(enc_key_b64, &ret_len); - if (ret_len == SIPE_FT_KEY_LENGTH) { - memcpy(ft->encryption_key,enc_key,SIPE_FT_KEY_LENGTH); - } else { - raise_ft_error_and_cancel(xfer, - _("Received encryption key has wrong size.")); - g_free(enc_key); - return; - } - g_free(enc_key); - } - if (hash_key_b64) { - gsize ret_len; - guchar *hash_key = g_base64_decode(hash_key_b64, &ret_len); - if (ret_len == SIPE_FT_KEY_LENGTH) { - memcpy(ft->hash_key,hash_key,SIPE_FT_KEY_LENGTH); - } else { - raise_ft_error_and_cancel(xfer, - _("Received hash key has wrong size.")); - g_free(hash_key); - return; - } - g_free(hash_key); - } - - if (ip && port_str) { - purple_xfer_start(xfer, -1, ip, g_ascii_strtoull(port_str,NULL,10)); - } else { - ft->listener = purple_network_listen_range(SIPE_FT_TCP_PORT_MIN, - SIPE_FT_TCP_PORT_MAX, - SOCK_STREAM, - sipe_ft_listen_socket_created, - xfer); - if (!ft->listener) { - raise_ft_error_and_cancel(xfer, - _("Could not create listen socket")); - return; - } - } - } -} - -void sipe_ft_incoming_cancel(PurpleAccount *account, GSList *body) -{ - gchar *inv_cookie = g_strdup(sipe_utils_nameval_find(body, "Invitation-Cookie")); - - struct sipe_core_private *sipe_private = PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE; - struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE; - PurpleXfer *xfer = g_hash_table_lookup(sip->filetransfers,inv_cookie); - - purple_xfer_cancel_remote(xfer); -} - -static void send_filetransfer_accept(PurpleXfer* xfer) -{ - sipe_file_transfer* ft = xfer->data; - struct sip_dialog *dialog = ft->dialog; - - gchar *b64_encryption_key = g_base64_encode(ft->encryption_key, 24); - gchar *b64_hash_key = g_base64_encode(ft->hash_key, 24); - - gchar *body = g_strdup_printf("Invitation-Command: ACCEPT\r\n" - "Request-Data: IP-Address:\r\n" - "Invitation-Cookie: %s\r\n" - "Encryption-Key: %s\r\n" - "Hash-Key: %s\r\n" - /*"IP-Address: %s\r\n" - "Port: 6900\r\n" - "PortX: 11178\r\n" - "Auth-Cookie: 11111111\r\n" - "Sender-Connect: TRUE\r\n"*/, - ft->invitation_cookie, - b64_encryption_key, - b64_hash_key - /*,sipe_backend_network_ip_address()*/ - ); - - send_sip_request(ft->sipe_private, "MESSAGE", dialog->with, dialog->with, - "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n", - body, dialog, NULL); - - g_free(body); - g_free(b64_encryption_key); - g_free(b64_hash_key); -} - -static void send_filetransfer_cancel(PurpleXfer* xfer) { - sipe_file_transfer* ft = xfer->data; - struct sip_dialog* dialog = ft->dialog; - - gchar *body = g_strdup_printf("Invitation-Command: CANCEL\r\n" - "Invitation-Cookie: %s\r\n" - "Cancel-Code: REJECT\r\n", - ft->invitation_cookie); - - send_sip_request(ft->sipe_private, "MESSAGE", dialog->with, dialog->with, - "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n", - body, dialog, NULL); - - g_free(body); -} - -static ssize_t -do_read(PurpleXfer *xfer, guchar *buf, size_t len) -{ - ssize_t bytes_read = read(xfer->fd, buf, len); - if (bytes_read == 0) { - // Sender canceled transfer before it was finished - return -2; - } else if (bytes_read == -1) { - if (errno == EAGAIN) - return 0; - else - return -1; - } - return bytes_read; -} - -static gboolean -read_fully(PurpleXfer *xfer, guchar *buf, size_t len) -{ - const gulong READ_TIMEOUT = 10000000; - gulong time_spent = 0; - - while (len) { - ssize_t bytes_read = do_read(xfer, buf, len); - if (bytes_read == 0) { - g_usleep(100000); - time_spent += 100000; - } else if (bytes_read < 0 || time_spent > READ_TIMEOUT) { - return FALSE; - } else { - len -= bytes_read; - buf += bytes_read; - time_spent = 0; - } - } - return TRUE; -} - -static gssize read_line(PurpleXfer *xfer, gchar *buffer, gssize size) -{ - gssize pos = 0; - - memset(buffer,0,size); - do { - if (!read_fully(xfer, (guchar*) buffer + pos, 1)) - return -1; - } while (buffer[pos] != '\n' && ++pos != (size - 1)); - - if (pos == (size - 1) && buffer[pos - 1] != '\n') { - // Buffer too short - return -2; - } - - return pos; -} - -static gpointer sipe_cipher_context_init(const guchar *enc_key) -{ - /* - * Decryption of file from SIPE file transfer - * - * Decryption: - * 1.) SHA1-Key = SHA1sum (Encryption-Key); Do SHA1 digest from Encryption-Key, return 20 bytes SHA1-Key. - * 2.) Decrypt-Data = RC4 (Encrypt-Data, substr(SHA1-Key, 0, 15)); Decryption of encrypted data, used 16 bytes SHA1-Key; - */ - - guchar k2[SIPE_DIGEST_SHA1_LENGTH]; - - /* 1.) SHA1 sum */ - sipe_digest_sha1(enc_key, SIPE_FT_KEY_LENGTH, k2); - - /* 2.) RC4 decryption */ - return sipe_crypt_ft_start(k2); -} - -static gpointer sipe_hmac_context_init(const guchar *hash_key) -{ - /* - * Count MAC digest - * - * HMAC digest: - * 1.) SHA1-Key = SHA1sum (Hash-Key); Do SHA1 digest from Hash-Key, return 20 bytes SHA1-Key. - * 2.) MAC = HMAC_SHA1 (Decrypt-Data, substr(HMAC-Key,0,15)); Digest of decrypted file and SHA1-Key (used again only 16 bytes) - */ - - guchar k2[SIPE_DIGEST_SHA1_LENGTH]; - - /* 1.) SHA1 sum */ - sipe_digest_sha1(hash_key, SIPE_FT_KEY_LENGTH, k2); - - /* 2.) HMAC (initialization only) */ - return sipe_digest_ft_start(k2); -} - -static gchar* sipe_hmac_finalize(gpointer hmac_context) -{ - guchar hmac_digest[SIPE_DIGEST_FILETRANSFER_LENGTH]; - - /* MAC = Digest of decrypted file and SHA1-Key (used again only 16 bytes) */ - sipe_digest_ft_end(hmac_context, hmac_digest); - - return g_base64_encode(hmac_digest, sizeof (hmac_digest)); -} - -static void generate_key(guchar *buffer, gsize size) -{ - gsize i; - for (i = 0; i != size; ++i) - buffer[i] = rand(); -} - -static void set_socket_nonblock(int fd, gboolean state) -{ - int flags = fcntl(fd, F_GETFL, 0); - if (flags == -1) - flags = 0; - - if (state == TRUE) - fcntl(fd, F_SETFL, flags | O_NONBLOCK); - else - fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); -} - -void sipe_ft_send_file(PurpleConnection *gc, const char *who, const char *file) -{ - PurpleXfer *xfer = sipe_ft_new_xfer(gc, who); - - if (xfer) { - if (file != NULL) - purple_xfer_request_accepted(xfer, file); - else - purple_xfer_request(xfer); - } -} - -PurpleXfer * sipe_ft_new_xfer(PurpleConnection *gc, const char *who) -{ - PurpleXfer *xfer = NULL; - - if (PURPLE_CONNECTION_IS_VALID(gc)) { - xfer = purple_xfer_new(purple_connection_get_account(gc), - PURPLE_XFER_SEND, who); - - if (xfer) { - struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE; - - sipe_file_transfer *ft = g_new0(sipe_file_transfer, 1); - ft->invitation_cookie = g_strdup_printf("%u", rand() % 1000000000); - ft->sipe_private = sipe_private; - - xfer->data = ft; - - purple_xfer_set_init_fnc(xfer, sipe_ft_outgoing_init); - purple_xfer_set_start_fnc(xfer, sipe_ft_outgoing_start); - purple_xfer_set_end_fnc(xfer, sipe_ft_outgoing_stop); - purple_xfer_set_request_denied_fnc(xfer, sipe_ft_request_denied); - purple_xfer_set_write_fnc(xfer, sipe_ft_write); - purple_xfer_set_cancel_send_fnc(xfer, sipe_ft_free_xfer_struct); - purple_xfer_set_cancel_recv_fnc(xfer, sipe_ft_free_xfer_struct); - } - } - - return xfer; -} - -static -void sipe_ft_client_connected(gpointer p_xfer, gint listenfd, - SIPE_UNUSED_PARAMETER PurpleInputCondition cond) -{ - struct sockaddr_in saddr; - socklen_t slen = sizeof (saddr); - - int fd = accept(listenfd, (struct sockaddr*)&saddr, &slen); - - PurpleXfer *xfer = p_xfer; - sipe_file_transfer *ft = xfer->data; - - purple_input_remove(xfer->watcher); - xfer->watcher = 0; - close(listenfd); - ft->listenfd = -1; - - if (fd < 0) { - raise_ft_socket_read_error_and_cancel(xfer); - } else { - purple_xfer_start(xfer, fd, NULL, 0); - } -} - -static -void sipe_ft_listen_socket_created(int listenfd, gpointer data) -{ - gchar *body; - PurpleXfer *xfer = data; - sipe_file_transfer *ft = xfer->data; - - struct sockaddr_in addr; - - socklen_t socklen = sizeof (addr); - - ft->listener = NULL; - ft->listenfd = listenfd; - - getsockname(listenfd, (struct sockaddr*)&addr, &socklen); - - xfer->watcher = purple_input_add(listenfd, PURPLE_INPUT_READ, - sipe_ft_client_connected, xfer); - - ft->auth_cookie = rand() % 1000000000; - - body = g_strdup_printf("Invitation-Command: ACCEPT\r\n" - "Invitation-Cookie: %s\r\n" - "IP-Address: %s\r\n" - "Port: %u\r\n" - "PortX: 11178\r\n" - "AuthCookie: %u\r\n" - "Request-Data: IP-Address:\r\n", - ft->invitation_cookie, - sipe_utils_get_suitable_local_ip(listenfd), - ntohs(addr.sin_port), - ft->auth_cookie); - - if (!ft->dialog) { - struct sipe_core_private *sipe_private = PURPLE_XFER_TO_SIPE_CORE_PRIVATE; - struct sip_session *session = sipe_session_find_or_add_im(sipe_private, - xfer->who); - ft->dialog = sipe_dialog_find(session, xfer->who); - } - - if (ft->dialog) { - send_sip_request(ft->sipe_private, "MESSAGE", ft->dialog->with, ft->dialog->with, - "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n", - body, ft->dialog, NULL); - } - g_free(body); -} - -GSList * sipe_ft_parse_msg_body(const gchar *body) -{ - GSList *list = NULL; - gchar **lines = g_strsplit(body, "\r\n", 0); - if (sipe_utils_parse_lines(&list, lines, ":") == FALSE) { - sipe_utils_nameval_free(list); - list = NULL; - } - g_strfreev(lines); - return list; -} - -/* - Local Variables: - mode: c - c-file-style: "bsd" - indent-tabs-mode: t - tab-width: 8 - End: -*/ +/** + * @file sipe-ft.c + * + * pidgin-sipe + * + * Copyright (C) 2010 SIPE Project + * Copyright (C) 2010 Jakub Adam + * Copyright (C) 2010 Tomáš Hrabčík + * + * 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include + +#include "sipmsg.h" +#include "sip-transport.h" +#include "sipe-backend.h" +#include "sipe-core.h" +#include "sipe-core-private.h" +#include "sipe-crypt.h" +#include "sipe-dialog.h" +#include "sipe-digest.h" +#include "sipe-ft.h" +#include "sipe-nls.h" +#include "sipe-session.h" +#include "sipe-utils.h" +#include "sipe.h" + +#define SIPE_FT_KEY_LENGTH 24 +#define SIPE_FT_CHUNK_HEADER_LENGTH 3 + +/* + * DO NOT CHANGE THE FOLLOWING CONSTANTS!!! + * + * It seems that Microsoft Office Communicator client will accept + * file transfer invitations *only* within this port range! + * + * If a firewall is active on your system you need to open these ports if + * you want to *send* files to other users. Receiving files uses an outgoing + * connection and should therefore automatically penetrate your firewall. + */ +#define SIPE_FT_TCP_PORT_MIN 6891 +#define SIPE_FT_TCP_PORT_MAX 6901 + +/** + * File transport (private part) + */ +struct sipe_file_transfer_private { + struct sipe_file_transfer public; + + struct sipe_core_private *sipe_private; + + guchar encryption_key[SIPE_FT_KEY_LENGTH]; + guchar hash_key[SIPE_FT_KEY_LENGTH]; + unsigned auth_cookie; + gchar *invitation_cookie; + + struct sip_dialog *dialog; + + gpointer cipher_context; + gpointer hmac_context; + + gsize bytes_remaining_chunk; + + guchar *encrypted_outbuf; + guchar *outbuf_ptr; + gsize outbuf_size; +}; +#define SIPE_FILE_TRANSFER_PUBLIC ((struct sipe_file_transfer *) ft_private) +#define SIPE_FILE_TRANSFER_PRIVATE ((struct sipe_file_transfer_private *) ft) + +static void raise_ft_error(struct sipe_file_transfer_private *ft_private, + const gchar *errmsg) +{ + gchar *tmp = g_strdup_printf("%s: %s", errmsg, + sipe_backend_ft_get_error(SIPE_FILE_TRANSFER_PUBLIC)); + sipe_backend_ft_error(SIPE_FILE_TRANSFER_PUBLIC, tmp); + g_free(tmp); +} + +static void raise_ft_error_and_cancel(struct sipe_file_transfer_private *ft_private, + const gchar *errmsg) +{ + sipe_backend_ft_error(SIPE_FILE_TRANSFER_PUBLIC, errmsg); + sipe_backend_ft_cancel_local(SIPE_FILE_TRANSFER_PUBLIC); +} + +static void raise_ft_socket_read_error_and_cancel(struct sipe_file_transfer_private *ft_private) +{ + raise_ft_error_and_cancel(ft_private, _("Socket read failed")); +} + +static void raise_ft_socket_write_error_and_cancel(struct sipe_file_transfer_private *ft_private) +{ + raise_ft_error_and_cancel(ft_private, _("Socket write failed")); +} + +static gboolean read_exact(struct sipe_file_transfer_private *ft_private, + guchar *data, + gsize size) +{ + const gulong READ_TIMEOUT = 10000000; + gulong time_spent = 0; + + while (size) { + gssize bytes_read = sipe_backend_ft_read(SIPE_FILE_TRANSFER_PUBLIC, + data, size); + if (bytes_read == 0) { + g_usleep(100000); + time_spent += 100000; + } else if (bytes_read < 0 || time_spent > READ_TIMEOUT) { + return FALSE; + } else { + size -= bytes_read; + data += bytes_read; + time_spent = 0; + } + } + return TRUE; +} + +static gboolean read_line(struct sipe_file_transfer_private *ft_private, + guchar *data, + gsize size) +{ + gsize pos = 0; + + if (size < 2) return FALSE; + + memset(data, 0, size--); + do { + if (!read_exact(ft_private, data + pos, 1)) + return FALSE; + } while ((data[pos] != '\n') && (++pos < size)); + + /* Buffer too short? */ + if ((pos == size) && (data[pos - 1] != '\n')) { + return FALSE; + } + + return TRUE; +} + +static gboolean write_exact(struct sipe_file_transfer_private *ft_private, + const guchar *data, + gsize size) +{ + gssize bytes_written = sipe_backend_ft_write(SIPE_FILE_TRANSFER_PUBLIC, + data, size); + if ((bytes_written < 0) || ((gsize) bytes_written != size)) + return FALSE; + return TRUE; +} + +static void generate_key(guchar *buffer, gsize size) +{ + gsize i = 0; + while (i < size) buffer[i++] = rand(); +} + +static gpointer sipe_cipher_context_init(const guchar *enc_key) +{ + /* + * Decryption of file from SIPE file transfer + * + * Decryption: + * 1.) SHA1-Key = SHA1sum (Encryption-Key); Do SHA1 digest from Encryption-Key, return 20 bytes SHA1-Key. + * 2.) Decrypt-Data = RC4 (Encrypt-Data, substr(SHA1-Key, 0, 15)); Decryption of encrypted data, used 16 bytes SHA1-Key; + */ + + guchar k2[SIPE_DIGEST_SHA1_LENGTH]; + + /* 1.) SHA1 sum */ + sipe_digest_sha1(enc_key, SIPE_FT_KEY_LENGTH, k2); + + /* 2.) RC4 decryption */ + return sipe_crypt_ft_start(k2); +} + +static gpointer sipe_hmac_context_init(const guchar *hash_key) +{ + /* + * Count MAC digest + * + * HMAC digest: + * 1.) SHA1-Key = SHA1sum (Hash-Key); Do SHA1 digest from Hash-Key, return 20 bytes SHA1-Key. + * 2.) MAC = HMAC_SHA1 (Decrypt-Data, substr(HMAC-Key,0,15)); Digest of decrypted file and SHA1-Key (used again only 16 bytes) + */ + + guchar k2[SIPE_DIGEST_SHA1_LENGTH]; + + /* 1.) SHA1 sum */ + sipe_digest_sha1(hash_key, SIPE_FT_KEY_LENGTH, k2); + + /* 2.) HMAC (initialization only) */ + return sipe_digest_ft_start(k2); +} + +static gchar *sipe_hmac_finalize(gpointer hmac_context) +{ + guchar hmac_digest[SIPE_DIGEST_FILETRANSFER_LENGTH]; + + /* MAC = Digest of decrypted file and SHA1-Key (used again only 16 bytes) */ + sipe_digest_ft_end(hmac_context, hmac_digest); + + return g_base64_encode(hmac_digest, sizeof (hmac_digest)); +} + +struct sipe_file_transfer *sipe_core_ft_allocate(struct sipe_core_public *sipe_public) +{ + struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE; + struct sipe_file_transfer_private *ft_private = + g_new0(struct sipe_file_transfer_private, 1); + + ft_private->sipe_private = sipe_private; + ft_private->invitation_cookie = g_strdup_printf("%u", rand() % 1000000000); + + return(SIPE_FILE_TRANSFER_PUBLIC); +} + +void sipe_ft_deallocate(struct sipe_file_transfer *ft) +{ + struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE; + + if (ft->backend_private) + sipe_backend_ft_deallocate(ft); + + if (ft_private->cipher_context) + sipe_crypt_ft_destroy(ft_private->cipher_context); + + if (ft_private->hmac_context) + sipe_digest_ft_destroy(ft_private->hmac_context); + + g_free(ft_private->invitation_cookie); + g_free(ft_private->encrypted_outbuf); + g_free(ft_private); +} + +void sipe_core_ft_deallocate(struct sipe_file_transfer *ft) +{ + struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE; + + g_hash_table_remove(ft_private->sipe_private->filetransfers, + ft_private->invitation_cookie); + sipe_ft_deallocate(ft); +} + +void sipe_core_ft_cancel(struct sipe_file_transfer *ft) +{ + struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE; + struct sip_dialog *dialog = ft_private->dialog; + + gchar *body = g_strdup_printf("Invitation-Command: CANCEL\r\n" + "Invitation-Cookie: %s\r\n" + "Cancel-Code: REJECT\r\n", + ft_private->invitation_cookie); + + send_sip_request(ft_private->sipe_private, "MESSAGE", dialog->with, dialog->with, + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n", + body, dialog, NULL); + + g_free(body); +} + +void sipe_core_ft_incoming_init(struct sipe_file_transfer *ft) +{ + struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE; + struct sip_dialog *dialog = ft_private->dialog; + gchar *b64_encryption_key = g_base64_encode(ft_private->encryption_key, + SIPE_FT_KEY_LENGTH); + gchar *b64_hash_key = g_base64_encode(ft_private->hash_key, + SIPE_FT_KEY_LENGTH); + + gchar *body = g_strdup_printf("Invitation-Command: ACCEPT\r\n" + "Request-Data: IP-Address:\r\n" + "Invitation-Cookie: %s\r\n" + "Encryption-Key: %s\r\n" + "Hash-Key: %s\r\n" + /*"IP-Address: %s\r\n" + "Port: 6900\r\n" + "PortX: 11178\r\n" + "Auth-Cookie: 11111111\r\n" + "Sender-Connect: TRUE\r\n"*/, + ft_private->invitation_cookie, + b64_encryption_key, + b64_hash_key + /*,sipe_backend_network_ip_address()*/ + ); + + send_sip_request(ft_private->sipe_private, "MESSAGE", dialog->with, dialog->with, + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n", + body, dialog, NULL); + + g_free(body); + g_free(b64_hash_key); + g_free(b64_encryption_key); +} + +void sipe_core_ft_incoming_accept(struct sipe_file_transfer *ft, + const gchar *who, + int fd, + unsigned short port) +{ + struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE; + gchar *body; + + ft_private->auth_cookie = rand() % 1000000000; + + body = g_strdup_printf("Invitation-Command: ACCEPT\r\n" + "Invitation-Cookie: %s\r\n" + "IP-Address: %s\r\n" + "Port: %u\r\n" + "PortX: 11178\r\n" + "AuthCookie: %u\r\n" + "Request-Data: IP-Address:\r\n", + ft_private->invitation_cookie, + sipe_utils_get_suitable_local_ip(fd), + port, + ft_private->auth_cookie); + + if (!ft_private->dialog) { + struct sip_session *session = sipe_session_find_or_add_im(ft_private->sipe_private, + who); + ft_private->dialog = sipe_dialog_find(session, who); + } + + if (ft_private->dialog) { + send_sip_request(ft_private->sipe_private, "MESSAGE", ft_private->dialog->with, ft_private->dialog->with, + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n", + body, ft_private->dialog, NULL); + } + g_free(body); +} + +void sipe_core_ft_incoming_start(struct sipe_file_transfer *ft, + gsize total_size) +{ + static const guchar VER[] = "VER MSN_SECURE_FTP\r\n"; + static const guchar TFR[] = "TFR\r\n"; + const gsize BUFFER_SIZE = 50; + const gsize FILE_SIZE_OFFSET = 4; + + struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE; + guchar buf[BUFFER_SIZE]; + gchar *request; + gsize file_size; + + if (!write_exact(ft_private, VER, sizeof(VER) - 1)) { + raise_ft_socket_read_error_and_cancel(ft_private); + return; + } + if (!read_line(ft_private, buf, BUFFER_SIZE)) { + raise_ft_socket_read_error_and_cancel(ft_private); + return; + } + + request = g_strdup_printf("USR %s %u\r\n", + ft_private->sipe_private->username, + ft_private->auth_cookie); + if (!write_exact(ft_private, (guchar *)request, strlen(request))) { + raise_ft_socket_write_error_and_cancel(ft_private); + g_free(request); + return; + } + g_free(request); + + if (!read_line(ft_private, buf, BUFFER_SIZE)) { + raise_ft_socket_read_error_and_cancel(ft_private); + return; + } + + file_size = g_ascii_strtoull((gchar *) buf + FILE_SIZE_OFFSET, NULL, 10); + if (file_size != total_size) { + raise_ft_error_and_cancel(ft_private, + _("File size is different from the advertised value.")); + return; + } + + if (!sipe_backend_ft_write(SIPE_FILE_TRANSFER_PUBLIC, TFR, sizeof(TFR) - 1)) { + raise_ft_socket_write_error_and_cancel(ft_private); + return; + } + + ft_private->bytes_remaining_chunk = 0; + ft_private->cipher_context = sipe_cipher_context_init(ft_private->encryption_key); + ft_private->hmac_context = sipe_hmac_context_init(ft_private->hash_key); +} + +gboolean sipe_core_ft_incoming_stop(struct sipe_file_transfer *ft) +{ + static const guchar BYE[] = "BYE 16777989\r\n"; + const gsize BUFFER_SIZE = 50; + const gsize MAC_OFFSET = 4; + const gsize CRLF_LEN = 2; + + struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE; + gchar buffer[BUFFER_SIZE]; + gsize mac_len; + gchar *mac; + gchar *mac1; + + if (!sipe_backend_ft_write(SIPE_FILE_TRANSFER_PUBLIC, BYE, sizeof(BYE) - 1)) { + raise_ft_socket_write_error_and_cancel(ft_private); + return FALSE; + } + + if (!read_line(ft_private, (guchar *) buffer, BUFFER_SIZE)) { + raise_ft_socket_read_error_and_cancel(ft_private); + return FALSE; + } + + mac_len = strlen(buffer); + if (mac_len < (MAC_OFFSET + CRLF_LEN)) { + raise_ft_error_and_cancel(ft_private, + _("Received MAC is corrupted")); + return FALSE; + } + + /* Check MAC */ + mac = g_strndup(buffer + MAC_OFFSET, mac_len - MAC_OFFSET - CRLF_LEN); + mac1 = sipe_hmac_finalize(ft_private->hmac_context); + if (!sipe_strequal(mac, mac1)) { + g_free(mac1); + g_free(mac); + raise_ft_error_and_cancel(ft_private, + _("Received file is corrupted")); + return(FALSE); + } + g_free(mac1); + g_free(mac); + + return(TRUE); +} + +void sipe_core_ft_outgoing_init(struct sipe_file_transfer *ft, + const gchar *filename, gsize size, + const gchar *who) +{ + struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE; + struct sipe_core_private *sipe_private = ft_private->sipe_private; + struct sip_dialog *dialog; + + gchar *body = g_strdup_printf("Application-Name: File Transfer\r\n" + "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n" + "Invitation-Command: INVITE\r\n" + "Invitation-Cookie: %s\r\n" + "Application-File: %s\r\n" + "Application-FileSize: %" G_GSIZE_FORMAT "\r\n" + //"Connectivity: N\r\n" TODO + "Encryption: R\r\n", // TODO: non encrypted file transfer support + ft_private->invitation_cookie, + filename, + size); + + struct sip_session *session = sipe_session_find_or_add_im(sipe_private, + who); + + g_hash_table_insert(sipe_private->filetransfers, + g_strdup(ft_private->invitation_cookie), + ft_private); + + // Queue the message + sipe_session_enqueue_message(session, body, "text/x-msmsgsinvite"); + + dialog = sipe_dialog_find(session, who); + if (dialog && !dialog->outgoing_invite) { + ft_private->dialog = dialog; + sipe_im_process_queue(sipe_private, session); + } else if (!dialog || !dialog->outgoing_invite) { + // Need to send the INVITE to get the outgoing dialog setup + sipe_invite(sipe_private, session, who, body, "text/x-msmsgsinvite", NULL, FALSE); + } + + g_free(body); + +} + +void sipe_core_ft_outgoing_start(struct sipe_file_transfer *ft, + gsize total_size) +{ + static const guchar VER[] = "VER MSN_SECURE_FTP\r\n"; + const gsize BUFFER_SIZE = 50; + + struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE; + guchar buf[BUFFER_SIZE]; + gchar **parts; + unsigned auth_cookie_received; + gboolean users_match; + + if (!read_line(ft_private, buf, BUFFER_SIZE)) { + raise_ft_socket_read_error_and_cancel(ft_private); + return; + } + + if (!sipe_strequal((gchar *)buf, (gchar *)VER)) { + raise_ft_error_and_cancel(ft_private, + _("File transfer initialization failed.")); + SIPE_DEBUG_INFO("File transfer VER string incorrect, received: %s expected: %s", + buf, VER); + return; + } + + if (!write_exact(ft_private, VER, sizeof(VER) - 1)) { + raise_ft_socket_write_error_and_cancel(ft_private); + return; + } + + if (!read_line(ft_private, buf, BUFFER_SIZE)) { + raise_ft_socket_read_error_and_cancel(ft_private); + return; + } + + parts = g_strsplit((gchar *)buf, " ", 3); + auth_cookie_received = g_ascii_strtoull(parts[2], NULL, 10); + /* dialog->with has 'sip:' prefix, skip these four characters */ + users_match = sipe_strcase_equal(parts[1], + (ft_private->dialog->with + 4)); + g_strfreev(parts); + + SIPE_DEBUG_INFO("File transfer authentication: %s Expected: USR %s %u", + buf, + ft_private->dialog->with + 4, + ft_private->auth_cookie); + + if (!users_match || + (ft_private->auth_cookie != auth_cookie_received)) { + raise_ft_error_and_cancel(ft_private, + _("File transfer authentication failed.")); + return; + } + + g_sprintf((gchar *)buf, "FIL %" G_GSIZE_FORMAT "\r\n", total_size); + if (!write_exact(ft_private, buf, strlen((gchar *)buf))) { + raise_ft_socket_write_error_and_cancel(ft_private); + return; + } + + /* TFR */ + if (!read_line(ft_private ,buf, BUFFER_SIZE)) { + raise_ft_socket_read_error_and_cancel(ft_private); + return; + } + + ft_private->bytes_remaining_chunk = 0; + ft_private->cipher_context = sipe_cipher_context_init(ft_private->encryption_key); + ft_private->hmac_context = sipe_hmac_context_init(ft_private->hash_key); +} + +gboolean sipe_core_ft_outgoing_stop(struct sipe_file_transfer *ft) +{ + gsize BUFFER_SIZE = 50; + + struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE; + guchar buffer[BUFFER_SIZE]; + gchar *mac; + gsize mac_len; + + /* BYE */ + if (!read_line(ft_private, buffer, BUFFER_SIZE)) { + raise_ft_socket_read_error_and_cancel(ft_private); + return FALSE; + } + + mac = sipe_hmac_finalize(ft_private->hmac_context); + g_sprintf((gchar *)buffer, "MAC %s \r\n", mac); + g_free(mac); + + mac_len = strlen((gchar *)buffer); + /* There must be this zero byte between mac and \r\n */ + buffer[mac_len - 3] = 0; + + if (!write_exact(ft_private, buffer, mac_len)) { + raise_ft_socket_write_error_and_cancel(ft_private); + return FALSE; + } + + return TRUE; +} + +gssize sipe_core_ft_read(struct sipe_file_transfer *ft, guchar **buffer, + gsize bytes_remaining, gsize bytes_available) +{ + struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE; + gsize bytes_to_read; + gssize bytes_read; + + if (ft_private->bytes_remaining_chunk == 0) { + guchar hdr_buf[SIPE_FT_CHUNK_HEADER_LENGTH]; + + /* read chunk header */ + if (!read_exact(ft_private, hdr_buf, sizeof(hdr_buf))) { + raise_ft_error(ft_private, _("Socket read failed")); + return -1; + } + + /* chunk header format: + * + * 0: 00 unknown (always zero?) + * 1: LL chunk size in bytes (low byte) + * 2: HH chunk size in bytes (high byte) + * + * Convert size from little endian to host order + */ + ft_private->bytes_remaining_chunk = + hdr_buf[1] + (hdr_buf[2] << 8); + } + + bytes_to_read = MIN(bytes_remaining, bytes_available); + bytes_to_read = MIN(bytes_to_read, ft_private->bytes_remaining_chunk); + + *buffer = g_malloc(bytes_to_read); + if (!*buffer) { + sipe_backend_ft_error(SIPE_FILE_TRANSFER_PUBLIC, _("Out of memory")); + SIPE_DEBUG_ERROR("sipe_core_ft_read: can't allocate %" G_GSIZE_FORMAT " bytes for receive buffer", + bytes_to_read); + return -1; + } + + bytes_read = sipe_backend_ft_read(SIPE_FILE_TRANSFER_PUBLIC, *buffer, bytes_to_read); + if (bytes_read < 0) { + raise_ft_error(ft_private, _("Socket read failed")); + g_free(*buffer); + *buffer = NULL; + return -1; + } + + if (bytes_read > 0) { + guchar *decrypted = g_malloc(bytes_read); + + if (!decrypted) { + sipe_backend_ft_error(SIPE_FILE_TRANSFER_PUBLIC, _("Out of memory")); + SIPE_DEBUG_ERROR("sipe_core_ft_read: can't allocate %" G_GSIZE_FORMAT " bytes for decryption buffer", + (gsize)bytes_read); + g_free(*buffer); + *buffer = NULL; + return -1; + } + sipe_crypt_ft_stream(ft_private->cipher_context, + *buffer, bytes_read, decrypted); + g_free(*buffer); + *buffer = decrypted; + + sipe_digest_ft_update(ft_private->hmac_context, + decrypted, bytes_read); + + ft_private->bytes_remaining_chunk -= bytes_read; + } + + return(bytes_read); +} + +gssize sipe_core_ft_write(struct sipe_file_transfer *ft, + const guchar *buffer, gsize size) +{ + struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE; + gssize bytes_written; + + /* When sending data via server with ForeFront installed, block bigger than + * this default causes ending of transmission. Hard limit block to this value + * when libpurple sends us more data. */ + const gsize DEFAULT_BLOCK_SIZE = 2045; + if (size > DEFAULT_BLOCK_SIZE) + size = DEFAULT_BLOCK_SIZE; + + if (ft_private->bytes_remaining_chunk == 0) { + gssize bytes_read; + guchar local_buf[16]; + guchar hdr_buf[SIPE_FT_CHUNK_HEADER_LENGTH]; + + memset(local_buf, 0, sizeof local_buf); + + /* Check if receiver did not cancel the transfer + before it is finished */ + bytes_read = sipe_backend_ft_read(SIPE_FILE_TRANSFER_PUBLIC, + local_buf, + sizeof(local_buf)); + if (bytes_read < 0) { + sipe_backend_ft_error(SIPE_FILE_TRANSFER_PUBLIC, + _("Socket read failed")); + return -1; + } else if ((bytes_read > 0) && + (g_str_has_prefix((gchar *)local_buf, "CCL\r\n") || + g_str_has_prefix((gchar *)local_buf, "BYE 2164261682\r\n"))) { + return -1; + } + + if (ft_private->outbuf_size < size) { + g_free(ft_private->encrypted_outbuf); + ft_private->outbuf_size = size; + ft_private->encrypted_outbuf = g_malloc(ft_private->outbuf_size); + if (!ft_private->encrypted_outbuf) { + sipe_backend_ft_error(SIPE_FILE_TRANSFER_PUBLIC, + _("Out of memory")); + SIPE_DEBUG_ERROR("sipe_core_ft_write: can't allocate %" G_GSIZE_FORMAT " bytes for send buffer", + ft_private->outbuf_size); + return -1; + } + } + + ft_private->bytes_remaining_chunk = size; + ft_private->outbuf_ptr = ft_private->encrypted_outbuf; + sipe_crypt_ft_stream(ft_private->cipher_context, + buffer, size, + ft_private->encrypted_outbuf); + sipe_digest_ft_update(ft_private->hmac_context, + buffer, size); + + /* chunk header format: + * + * 0: 00 unknown (always zero?) + * 1: LL chunk size in bytes (low byte) + * 2: HH chunk size in bytes (high byte) + * + * Convert size from host order to little endian + */ + hdr_buf[0] = 0; + hdr_buf[1] = (ft_private->bytes_remaining_chunk & 0x00FF); + hdr_buf[2] = (ft_private->bytes_remaining_chunk & 0xFF00) >> 8; + + /* write chunk header */ + if (!sipe_backend_ft_write(SIPE_FILE_TRANSFER_PUBLIC, hdr_buf, sizeof(hdr_buf))) { + sipe_backend_ft_error(SIPE_FILE_TRANSFER_PUBLIC, + _("Socket write failed")); + return -1; + } + } + + bytes_written = sipe_backend_ft_write(SIPE_FILE_TRANSFER_PUBLIC, + ft_private->outbuf_ptr, + ft_private->bytes_remaining_chunk); + if (bytes_written < 0) { + raise_ft_error(ft_private, _("Socket write failed")); + } else if (bytes_written > 0) { + ft_private->bytes_remaining_chunk -= bytes_written; + ft_private->outbuf_ptr += bytes_written; + } + + return bytes_written; +} + +void sipe_ft_incoming_transfer(struct sipe_core_private *sipe_private, + struct sipmsg *msg, + const GSList *body) +{ + struct sipe_file_transfer_private *ft_private; + const gchar *callid = sipmsg_find_header(msg, "Call-ID"); + gchar *from = parse_from(sipmsg_find_header(msg, "From")); + struct sip_session *session = sipe_session_find_chat_or_im(sipe_private, + callid, + from); + gsize file_size; + + g_free(from); + if (!session) { + SIPE_DEBUG_ERROR_NOFORMAT("sipe_ft_incoming_transfer: can't find session for remote party"); + return; + } + + ft_private = g_new0(struct sipe_file_transfer_private, 1); + ft_private->sipe_private = sipe_private; + + generate_key(ft_private->encryption_key, SIPE_FT_KEY_LENGTH); + generate_key(ft_private->hash_key, SIPE_FT_KEY_LENGTH); + + ft_private->invitation_cookie = g_strdup(sipe_utils_nameval_find(body, "Invitation-Cookie")); + + ft_private->dialog = sipe_dialog_find(session, session->with); + + file_size = g_ascii_strtoull(sipe_utils_nameval_find(body, + "Application-FileSize"), + NULL, 10); + sipe_backend_ft_incoming(SIPE_CORE_PUBLIC, + SIPE_FILE_TRANSFER_PUBLIC, + session->with, + sipe_utils_nameval_find(body, "Application-File"), + file_size); + + if (ft_private->public.backend_private != NULL) { + g_hash_table_insert(sipe_private->filetransfers, + g_strdup(ft_private->invitation_cookie), + ft_private); + } else { + sipe_ft_deallocate(SIPE_FILE_TRANSFER_PUBLIC); + } +} + +static struct sipe_file_transfer_private *sipe_find_ft(struct sipe_core_private *sipe_private, + const GSList *body) +{ + return g_hash_table_lookup(sipe_private->filetransfers, + sipe_utils_nameval_find(body, + "Invitation-Cookie")); +} + +void sipe_ft_incoming_accept(struct sipe_core_private *sipe_private, + const GSList *body) +{ + struct sipe_file_transfer_private *ft_private = sipe_find_ft(sipe_private, + body); + + if (ft_private) { + const gchar *ip = sipe_utils_nameval_find(body, "IP-Address"); + const gchar *port_str = sipe_utils_nameval_find(body, "Port"); + const gchar *auth_cookie = sipe_utils_nameval_find(body, "AuthCookie"); + const gchar *enc_key_b64 = sipe_utils_nameval_find(body, "Encryption-Key"); + const gchar *hash_key_b64 = sipe_utils_nameval_find(body, "Hash-Key"); + + if (auth_cookie) + ft_private->auth_cookie = g_ascii_strtoull(auth_cookie, + NULL, 10); + if (enc_key_b64) { + gsize ret_len; + guchar *enc_key = g_base64_decode(enc_key_b64, + &ret_len); + if (ret_len == SIPE_FT_KEY_LENGTH) { + memcpy(ft_private->encryption_key, + enc_key, SIPE_FT_KEY_LENGTH); + } else { + raise_ft_error_and_cancel(ft_private, + _("Received encryption key has wrong size.")); + g_free(enc_key); + return; + } + g_free(enc_key); + } + if (hash_key_b64) { + gsize ret_len; + guchar *hash_key = g_base64_decode(hash_key_b64, + &ret_len); + if (ret_len == SIPE_FT_KEY_LENGTH) { + memcpy(ft_private->hash_key, + hash_key, SIPE_FT_KEY_LENGTH); + } else { + raise_ft_error_and_cancel(ft_private, + _("Received hash key has wrong size.")); + g_free(hash_key); + return; + } + g_free(hash_key); + } + + + if (ip && port_str) { + unsigned short port = g_ascii_strtoull(port_str, + NULL, 10); + + sipe_backend_ft_incoming_accept(SIPE_FILE_TRANSFER_PUBLIC, + ip, + port, + port); + } else { + if (!sipe_backend_ft_incoming_accept(SIPE_FILE_TRANSFER_PUBLIC, + NULL, + SIPE_FT_TCP_PORT_MIN, + SIPE_FT_TCP_PORT_MAX)) { + raise_ft_error_and_cancel(ft_private, + _("Could not create listen socket")); + return; + } + } + } +} + +void sipe_ft_incoming_cancel(struct sipe_core_private *sipe_private, + const GSList *body) +{ + struct sipe_file_transfer_private *ft_private = sipe_find_ft(sipe_private, + body); + + if (ft_private) + sipe_backend_ft_cancel_remote(SIPE_FILE_TRANSFER_PUBLIC); +} + +GSList *sipe_ft_parse_msg_body(const gchar *body) +{ + GSList *list = NULL; + gchar **lines = g_strsplit(body, "\r\n", 0); + if (sipe_utils_parse_lines(&list, lines, ":") == FALSE) { + sipe_utils_nameval_free(list); + list = NULL; + } + g_strfreev(lines); + return list; +} + +/* + Local Variables: + mode: c + c-file-style: "bsd" + indent-tabs-mode: t + tab-width: 8 + End: +*/ diff --git a/src/core/sipe-ft.h b/src/core/sipe-ft.h index 451c0a29..0ef02a40 100644 --- a/src/core/sipe-ft.h +++ b/src/core/sipe-ft.h @@ -3,6 +3,7 @@ * * pidgin-sipe * + * Copyright (C) 2010 SIPE Project * Copyright (C) 2010 Jakub Adam * Copyright (C) 2010 Tomáš Hrabčík * @@ -21,15 +22,16 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -/* - * Interface dependencies: - * - * - */ - /* Forward declarations */ struct sipmsg; -struct _PurpleAccount; +struct sipe_core_private; +struct sipe_file_transfer; + + +/** + * Deallocate file transfer data structure + */ +void sipe_ft_deallocate(struct sipe_file_transfer *ft); /** * Called when remote peer wants to send a file. @@ -37,30 +39,37 @@ struct _PurpleAccount; * Function initializes libpurple filetransfer API structure and calls * purple_xfer_request(). * - * @param account PurpleAccount corresponding to the request - * @param msg SIP message - * @param body parsed SIP message body as name-value pairs + * @param sipe_private Sipe core private data + * @param msg SIP message + * @param body parsed SIP message body as name-value pairs */ -void sipe_ft_incoming_transfer(struct _PurpleAccount *account, struct sipmsg *msg, const GSList *body); +void sipe_ft_incoming_transfer(struct sipe_core_private *sipe_private, + struct sipmsg *msg, + const GSList *body); + /** * Handles incoming filetransfer message with ACCEPT invitation command. * * This message is sent during the negotiation phase when parameters of the * transfer like IP address or TCP port are going to be set up. * - * @param account PurpleAccount corresponding to the request - * @param body parsed SIP message body as name-value pairs + * @param sipe_private Sipe core private data + * @param body parsed SIP message body as name-value pairs */ -void sipe_ft_incoming_accept(struct _PurpleAccount *account, const GSList *body); +void sipe_ft_incoming_accept(struct sipe_core_private *sipe_private, + const GSList *body); + /** * Called when remote peer cancels ongoing file transfer. * * Function dispatches the request to libpurple * - * @param account PurpleAccount corresponding to the request - * @param body parsed SIP message body as name-value pairs + * @param sipe_private Sipe core private data + * @param body SIP message body + * @param body parsed SIP message body as name-value pairs */ -void sipe_ft_incoming_cancel(struct _PurpleAccount *account, GSList *body); +void sipe_ft_incoming_cancel(struct sipe_core_private *sipe_private, + const GSList *body); /** * Parses file transfer message body and creates a list with name-value pairs @@ -70,4 +79,4 @@ void sipe_ft_incoming_cancel(struct _PurpleAccount *account, GSList *body); * @return GSList of name-value pairs parsed from message body, NULL if body has * incorrect format */ -GSList * sipe_ft_parse_msg_body(const gchar *body); +GSList *sipe_ft_parse_msg_body(const gchar *body); diff --git a/src/core/sipe.c b/src/core/sipe.c index 9ba15364..375cf067 100644 --- a/src/core/sipe.c +++ b/src/core/sipe.c @@ -3597,7 +3597,7 @@ process_message_response(struct sipe_core_private *sipe_private, message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite")) { GSList *parsed_body = sipe_ft_parse_msg_body(msg->body); - sipe_ft_incoming_cancel(sip->gc->account, parsed_body); + sipe_ft_incoming_cancel(sipe_private, parsed_body); sipe_utils_nameval_free(parsed_body); } @@ -3869,7 +3869,7 @@ process_invite_response(struct sipe_core_private *sipe_private, message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite")) { GSList *parsed_body = sipe_ft_parse_msg_body(message->body); - sipe_ft_incoming_cancel(sip->gc->account, parsed_body); + sipe_ft_incoming_cancel(sipe_private, parsed_body); sipe_utils_nameval_free(parsed_body); } @@ -4475,20 +4475,19 @@ sipe_process_incoming_x_msmsgsinvite(struct sipe_core_private *sipe_private, struct sipmsg *msg, GSList *parsed_body) { - struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE; gboolean found = FALSE; if (parsed_body) { const gchar *invitation_command = sipe_utils_nameval_find(parsed_body, "Invitation-Command"); if (sipe_strequal(invitation_command, "INVITE")) { - sipe_ft_incoming_transfer(sip->gc->account, msg, parsed_body); + sipe_ft_incoming_transfer(sipe_private, msg, parsed_body); found = TRUE; } else if (sipe_strequal(invitation_command, "CANCEL")) { - sipe_ft_incoming_cancel(sip->gc->account, parsed_body); + sipe_ft_incoming_cancel(sipe_private, parsed_body); found = TRUE; } else if (sipe_strequal(invitation_command, "ACCEPT")) { - sipe_ft_incoming_accept(sip->gc->account, parsed_body); + sipe_ft_incoming_accept(sipe_private, parsed_body); found = TRUE; } } @@ -7751,7 +7750,8 @@ struct sipe_core_public *sipe_core_allocate(const gchar *signin_name, g_free, (GDestroyNotify)g_hash_table_destroy); sip->subscriptions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)sipe_subscription_free); - sip->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal,g_free,NULL); + sipe_private->filetransfers = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, (GDestroyNotify)sipe_ft_deallocate); sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN); return((struct sipe_core_public *)sipe_private); @@ -7866,7 +7866,7 @@ void sipe_core_deallocate(struct sipe_core_public *sipe_public) g_hash_table_destroy(sip->our_publications); g_hash_table_destroy(sip->user_state_publications); g_hash_table_destroy(sip->subscriptions); - g_hash_table_destroy(sip->filetransfers); + g_hash_table_destroy(sipe_private->filetransfers); if (sip->groups) { GSList *entry = sip->groups; diff --git a/src/core/sipe.h b/src/core/sipe.h index bf3142bd..777c6d39 100644 --- a/src/core/sipe.h +++ b/src/core/sipe.h @@ -123,7 +123,6 @@ struct sipe_account_data { struct _PurpleAccount *account; gchar *regcallid; GSList *groups; - GHashTable *filetransfers; gboolean processing_input; struct sipe_calendar *cal; gchar *email; diff --git a/src/purple/Makefile.am b/src/purple/Makefile.am index 3d160ff5..b4d7ac28 100644 --- a/src/purple/Makefile.am +++ b/src/purple/Makefile.am @@ -12,6 +12,7 @@ libsipe_backend_la_SOURCES = \ purple-connection.c \ purple-debug.c \ purple-dnsquery.c \ + purple-ft.c \ purple-markup.c \ purple-network.c \ purple-schedule.c \ diff --git a/src/purple/purple-ft.c b/src/purple/purple-ft.c new file mode 100644 index 00000000..3b75d24d --- /dev/null +++ b/src/purple/purple-ft.c @@ -0,0 +1,402 @@ +/** + * @file purple-ft.c + * + * pidgin-sipe + * + * Copyright (C) 2010 SIPE Project + * Copyright (C) 2010 Jakub Adam + * Copyright (C) 2010 Tomáš Hrabčík + * + * 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 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include + +#include "connection.h" +#include "eventloop.h" +#include "ft.h" +#include "network.h" +#include "request.h" + +#ifdef _WIN32 +/* for network */ +#include "libc_interface.h" +#include +#else +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_SOCKIO_H +#include /* SIOCGIFCONF for Solaris */ +#endif +#endif + +#include "sipe-common.h" +#include "sipe-backend.h" +#include "sipe-core.h" +#include "sipe-nls.h" + +#include "purple-private.h" + +struct sipe_backend_file_transfer { + PurpleXfer *xfer; + PurpleNetworkListenData *listener; + int listenfd; +}; +#define PURPLE_XFER_TO_SIPE_FILE_TRANSFER ((struct sipe_file_transfer *) xfer->data) +#define PURPLE_XFER_TO_SIPE_CORE_PUBLIC ((struct sipe_core_public *) xfer->account->gc->proto_data) + +void sipe_backend_ft_error(struct sipe_file_transfer *ft, + const char *errmsg) +{ + PurpleXfer *xfer = ft->backend_private->xfer; + purple_xfer_error(purple_xfer_get_type(xfer), + xfer->account, xfer->who, + errmsg); +} + +const gchar *sipe_backend_ft_get_error(SIPE_UNUSED_PARAMETER struct sipe_file_transfer *ft) +{ + return strerror(errno); +} + +void sipe_backend_ft_deallocate(struct sipe_file_transfer *ft) +{ + struct sipe_backend_file_transfer *backend_ft = ft->backend_private; + + if (backend_ft->listenfd >= 0) { + SIPE_DEBUG_INFO("sipe_ft_free_xfer_struct: closing listening socket %d", + backend_ft->listenfd); + close(backend_ft->listenfd); + } + if (backend_ft->listener) + purple_network_listen_cancel(backend_ft->listener); + g_free(backend_ft); +} + +gssize sipe_backend_ft_read(struct sipe_file_transfer *ft, + guchar *data, + gsize size) +{ + gssize bytes_read = read(ft->backend_private->xfer->fd, data, size); + if (bytes_read == 0) { + /* Sender canceled transfer before it was finished */ + return -2; + } else if (bytes_read == -1) { + if (errno == EAGAIN) + return 0; + else + return -1; + } + return bytes_read; +} + +gssize sipe_backend_ft_write(struct sipe_file_transfer *ft, + const guchar *data, + gsize size) +{ + gssize bytes_written = write(ft->backend_private->xfer->fd, + data, size); + if (bytes_written == -1) { + if (errno == EAGAIN) + return 0; + else + return -1; + } + return bytes_written; +} + +void sipe_backend_ft_cancel_local(struct sipe_file_transfer *ft) +{ + purple_xfer_cancel_local(ft->backend_private->xfer); +} + +void sipe_backend_ft_cancel_remote(struct sipe_file_transfer *ft) +{ + purple_xfer_cancel_remote(ft->backend_private->xfer); +} + +static void +sipe_ft_free_xfer_struct(PurpleXfer *xfer) +{ + struct sipe_file_transfer *ft = PURPLE_XFER_TO_SIPE_FILE_TRANSFER; + + if (ft) { + if (xfer->watcher) { + purple_input_remove(xfer->watcher); + xfer->watcher = 0; + } + sipe_core_ft_deallocate(ft); + xfer->data = NULL; + } +} + +static void +sipe_ft_request_denied(PurpleXfer *xfer) +{ + if (xfer->type == PURPLE_XFER_RECEIVE) + sipe_core_ft_cancel(PURPLE_XFER_TO_SIPE_FILE_TRANSFER); + sipe_ft_free_xfer_struct(xfer); +} + +static void +sipe_ft_incoming_init(PurpleXfer *xfer) +{ + sipe_core_ft_incoming_init(PURPLE_XFER_TO_SIPE_FILE_TRANSFER); +} + +static void +sipe_ft_incoming_start(PurpleXfer *xfer) +{ + sipe_core_ft_incoming_start(PURPLE_XFER_TO_SIPE_FILE_TRANSFER, + xfer->size); +} + +static void +sipe_ft_incoming_stop(PurpleXfer *xfer) +{ + if (sipe_core_ft_incoming_stop(PURPLE_XFER_TO_SIPE_FILE_TRANSFER)) { + /* We're done with this transfer */ + sipe_ft_free_xfer_struct(xfer); + } else { + unlink(xfer->local_filename); + } +} + +static gssize +sipe_ft_read(guchar **buffer, PurpleXfer *xfer) +{ + return sipe_core_ft_read(PURPLE_XFER_TO_SIPE_FILE_TRANSFER, + buffer, + purple_xfer_get_bytes_remaining(xfer), + xfer->current_buffer_size); +} + +static void +sipe_ft_outgoing_init(PurpleXfer *xfer) +{ + sipe_core_ft_outgoing_init(PURPLE_XFER_TO_SIPE_FILE_TRANSFER, + purple_xfer_get_filename(xfer), + purple_xfer_get_size(xfer), + xfer->who); +} + +static void +sipe_ft_outgoing_start(PurpleXfer *xfer) +{ + /* Set socket to non-blocking mode */ + int flags = fcntl(xfer->fd, F_GETFL, 0); + if (flags == -1) + flags = 0; + fcntl(xfer->fd, F_SETFL, flags | O_NONBLOCK); + + sipe_core_ft_outgoing_start(PURPLE_XFER_TO_SIPE_FILE_TRANSFER, + xfer->size); +} + +static void +sipe_ft_outgoing_stop(PurpleXfer *xfer) +{ + if (sipe_core_ft_incoming_stop(PURPLE_XFER_TO_SIPE_FILE_TRANSFER)) { + /* We're done with this transfer */ + sipe_ft_free_xfer_struct(xfer); + } +} + +static gssize +sipe_ft_write(const guchar *buffer, size_t size, PurpleXfer *xfer) +{ + gssize bytes_written = sipe_core_ft_write(PURPLE_XFER_TO_SIPE_FILE_TRANSFER, + buffer, + size); + + if ((xfer->bytes_remaining - bytes_written) == 0) + purple_xfer_set_completed(xfer, TRUE); + + return bytes_written; +} + +//****************************************************************************** + +static void sipe_backend_private_init(struct sipe_file_transfer *ft, + PurpleXfer *xfer) +{ + struct sipe_backend_file_transfer *backend_ft = g_new0(struct sipe_backend_file_transfer, 1); + + ft->backend_private = backend_ft; + backend_ft->xfer = xfer; + backend_ft->listenfd = -1; + + xfer->data = ft; +} + +void sipe_backend_ft_incoming(struct sipe_core_public *sipe_public, + struct sipe_file_transfer *ft, + const gchar *who, + const gchar *file_name, + gsize file_size) +{ + struct sipe_backend_private *purple_private = sipe_public->backend_private; + PurpleXfer *xfer; + + xfer = purple_xfer_new(purple_private->account, + PURPLE_XFER_RECEIVE, + who); + + if (xfer) { + sipe_backend_private_init(ft, xfer); + + purple_xfer_set_filename(xfer, file_name); + purple_xfer_set_size(xfer, file_size); + + purple_xfer_set_init_fnc(xfer, sipe_ft_incoming_init); + purple_xfer_set_start_fnc(xfer, sipe_ft_incoming_start); + purple_xfer_set_end_fnc(xfer, sipe_ft_incoming_stop); + purple_xfer_set_request_denied_fnc(xfer, sipe_ft_request_denied); + purple_xfer_set_read_fnc(xfer, sipe_ft_read); + purple_xfer_set_cancel_send_fnc(xfer, sipe_ft_free_xfer_struct); + purple_xfer_set_cancel_recv_fnc(xfer, sipe_ft_free_xfer_struct); + + purple_xfer_request(xfer); + } +} + +static +void sipe_ft_client_connected(gpointer data, gint listenfd, + SIPE_UNUSED_PARAMETER PurpleInputCondition cond) +{ + struct sipe_file_transfer *ft = data; + struct sipe_backend_file_transfer *backend_ft = ft->backend_private; + PurpleXfer *xfer = backend_ft->xfer; + struct sockaddr_in saddr; + socklen_t slen = sizeof (saddr); + + int fd = accept(listenfd, (struct sockaddr*)&saddr, &slen); + + purple_input_remove(xfer->watcher); + xfer->watcher = 0; + close(listenfd); + backend_ft->listenfd = -1; + + if (fd < 0) { + sipe_backend_ft_error(ft, _("Socket read failed")); + sipe_backend_ft_cancel_local(ft); + } else { + purple_xfer_start(xfer, fd, NULL, 0); + } +} + +static +void sipe_ft_listen_socket_created(int listenfd, gpointer data) +{ + struct sipe_file_transfer *ft = data; + struct sipe_backend_file_transfer *backend_ft = ft->backend_private; + struct sockaddr_in addr; + socklen_t socklen = sizeof (addr); + + backend_ft->listener = NULL; + backend_ft->listenfd = listenfd; + + getsockname(listenfd, (struct sockaddr*)&addr, &socklen); + + backend_ft->xfer->watcher = purple_input_add(listenfd, + PURPLE_INPUT_READ, + sipe_ft_client_connected, + ft); + + sipe_core_ft_incoming_accept(ft, + backend_ft->xfer->who, + listenfd, + ntohs(addr.sin_port)); +} + +gboolean sipe_backend_ft_incoming_accept(struct sipe_file_transfer *ft, + const gchar *ip, + unsigned short port_min, + unsigned short port_max) +{ + struct sipe_backend_file_transfer *backend_ft = ft->backend_private; + + if (ip && (port_min == port_max)) { + purple_xfer_start(backend_ft->xfer, -1, ip, port_min); + } else { + backend_ft->listener = purple_network_listen_range(port_min, + port_max, + SOCK_STREAM, + sipe_ft_listen_socket_created, + ft); + if (!backend_ft->listener) + return FALSE; + } + return(TRUE); +} + +void sipe_ft_send_file(PurpleConnection *gc, const char *who, const char *file) +{ + PurpleXfer *xfer = sipe_ft_new_xfer(gc, who); + + if (xfer) { + if (file != NULL) + purple_xfer_request_accepted(xfer, file); + else + purple_xfer_request(xfer); + } +} + +PurpleXfer *sipe_ft_new_xfer(PurpleConnection *gc, const char *who) +{ + PurpleXfer *xfer = NULL; + + if (PURPLE_CONNECTION_IS_VALID(gc)) { + xfer = purple_xfer_new(purple_connection_get_account(gc), + PURPLE_XFER_SEND, who); + + if (xfer) { + struct sipe_file_transfer *ft = sipe_core_ft_allocate(PURPLE_GC_TO_SIPE_CORE_PUBLIC); + + sipe_backend_private_init(ft, xfer); + + purple_xfer_set_init_fnc(xfer, sipe_ft_outgoing_init); + purple_xfer_set_start_fnc(xfer, sipe_ft_outgoing_start); + purple_xfer_set_end_fnc(xfer, sipe_ft_outgoing_stop); + purple_xfer_set_request_denied_fnc(xfer, sipe_ft_request_denied); + purple_xfer_set_write_fnc(xfer, sipe_ft_write); + purple_xfer_set_cancel_send_fnc(xfer, sipe_ft_free_xfer_struct); + purple_xfer_set_cancel_recv_fnc(xfer, sipe_ft_free_xfer_struct); + } + } + + return xfer; +} + +/* + Local Variables: + mode: c + c-file-style: "bsd" + indent-tabs-mode: t + tab-width: 8 + End: +*/ diff --git a/src/purple/purple-private.h b/src/purple/purple-private.h index bd974b77..e07fe7b5 100644 --- a/src/purple/purple-private.h +++ b/src/purple/purple-private.h @@ -25,6 +25,7 @@ struct sipe_core_public; struct _PurpleAccount; struct _PurpleConnection; struct _PurpleSrvQueryData; +struct _PurpleXfer; struct sipe_backend_private { struct sipe_core_public *public; @@ -34,6 +35,27 @@ struct sipe_backend_private { time_t last_keepalive; }; +/** + * Initiates outgoing file transfer, sending @c file to remote peer identified + * by @c who. + * + * @param gc a PurpleConnection + * @param who string identifying receiver of the file + * @param file local file system path of the file to send + */ +void sipe_ft_send_file(struct _PurpleConnection *gc, + const char *who, + const char *file); + +/** + * Creates new PurpleXfer structure representing a file transfer. + * + * @param gc a PurpleConnection + * @param who remote participant in the file transfer session + */ +struct _PurpleXfer *sipe_ft_new_xfer(struct _PurpleConnection *gc, + const char *who); + /* Convenience macros */ #define PURPLE_ACCOUNT_TO_SIPE_CORE_PUBLIC ((struct sipe_core_public *) account->gc->proto_data) #define PURPLE_GC_TO_SIPE_CORE_PUBLIC ((struct sipe_core_public *) gc->proto_data) -- 2.11.4.GIT