audio: send 486 Busy Here when call is in progress and another call invitation arrives.
[siplcs.git] / src / core / sipe-ft.c
blob721107d67c9fb5fca2c145b0facddebe46f075ec
1 /**
2 * @file sipe-ft.c
4 * pidgin-sipe
6 * Copyright (C) 2010 Jakub Adam <jakub.adam@tieto.com>
7 * Copyright (C) 2010 Tomáš Hrabčík <tomas.hrabcik@tieto.com>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
28 #include <string.h>
29 #include <fcntl.h>
30 #include <errno.h>
31 #include <time.h>
33 #include <glib.h>
34 #include <glib/gprintf.h>
36 #include "connection.h"
37 #include "eventloop.h"
38 #include "ft.h"
39 #include "network.h"
40 #include "request.h"
42 #ifdef _WIN32
43 /* for network */
44 #include "libc_interface.h"
45 #include <nspapi.h>
46 #else
47 #include <sys/types.h>
48 #include <sys/socket.h>
49 #include <netinet/in.h>
50 #include <net/if.h>
51 #include <arpa/inet.h>
52 #ifdef HAVE_SYS_SOCKIO_H
53 #include <sys/sockio.h> /* SIOCGIFCONF for Solaris */
54 #endif
55 #endif
57 #include "core-depurple.h" /* Temporary for the core de-purple transition */
59 #include "sipe-common.h"
60 #include "sipmsg.h"
61 #include "sip-transport.h"
62 #include "sipe-backend.h"
63 #include "sipe-core.h"
64 #include "sipe-core-private.h"
65 #include "sipe-crypt.h"
66 #include "sipe-dialog.h"
67 #include "sipe-digest.h"
68 #include "sipe-nls.h"
69 #include "sipe-ft.h"
70 #include "sipe-session.h"
71 #include "sipe-utils.h"
72 #include "sipe.h"
74 #define SIPE_FT_KEY_LENGTH 24
75 #define SIPE_FT_CHUNK_HEADER_LENGTH 3
78 * DO NOT CHANGE THE FOLLOWING CONSTANTS!!!
80 * It seems that Microsoft Office Communicator client will accept
81 * file transfer invitations *only* within this port range!
83 * If a firewall is active on your system you need to open these ports if
84 * you want to *send* files to other users. Receiving files uses an ougoing
85 * connection and should therefore automatically penetrate your firewall.
87 #define SIPE_FT_TCP_PORT_MIN 6891
88 #define SIPE_FT_TCP_PORT_MAX 6901
90 struct _sipe_file_transfer {
91 guchar encryption_key[SIPE_FT_KEY_LENGTH];
92 guchar hash_key[SIPE_FT_KEY_LENGTH];
93 gchar *invitation_cookie;
94 unsigned auth_cookie;
95 struct sipe_account_data *sip;
96 struct sip_dialog *dialog;
97 gpointer cipher_context;
98 gpointer hmac_context;
100 PurpleNetworkListenData *listener;
101 int listenfd;
103 gsize bytes_remaining_chunk;
104 guchar* encrypted_outbuf;
105 guchar* outbuf_ptr;
106 gsize outbuf_size;
108 typedef struct _sipe_file_transfer sipe_file_transfer;
110 static void send_filetransfer_accept(PurpleXfer* xfer);
111 static void send_filetransfer_cancel(PurpleXfer* xfer);
112 static ssize_t do_read(PurpleXfer *xfer, guchar *buf, size_t len);
113 static gboolean read_fully(PurpleXfer *xfer, guchar *buf, size_t len);
114 static gssize read_line(PurpleXfer *xfer, gchar *buffer, gssize size);
115 static gpointer sipe_cipher_context_init(const guchar *enc_key);
116 static gpointer sipe_hmac_context_init(const guchar *hash_key);
117 static gchar *sipe_hmac_finalize(gpointer hmac_context);
118 static void generate_key(guchar *buffer, gsize size);
119 static void set_socket_nonblock(int fd, gboolean state);
120 static void sipe_ft_listen_socket_created(int listenfd, gpointer data);
122 //******************************************************************************
123 // I/O operations for PurpleXfer structure
124 //******************************************************************************
126 static void
127 sipe_ft_incoming_init(PurpleXfer *xfer)
129 send_filetransfer_accept(xfer);
132 static void
133 sipe_ft_free_xfer_struct(PurpleXfer *xfer)
135 sipe_file_transfer *ft = xfer->data;
136 if (ft) {
137 struct sipe_account_data *sip = PURPLE_XFER_TO_SIPE_ACCOUNT_DATA;
139 g_hash_table_remove(sip->filetransfers,ft->invitation_cookie);
141 if (xfer->watcher) {
142 purple_input_remove(xfer->watcher);
143 xfer->watcher = 0;
145 if (ft->listenfd >= 0) {
146 SIPE_DEBUG_INFO("sipe_ft_free_xfer_struct: closing listening socket %d", ft->listenfd);
147 close(ft->listenfd);
149 if (ft->listener)
150 purple_network_listen_cancel(ft->listener);
151 if (ft->cipher_context)
152 sipe_crypt_ft_destroy(ft->cipher_context);
154 if (ft->hmac_context)
155 sipe_digest_ft_destroy(ft->hmac_context);
157 g_free(ft->encrypted_outbuf);
158 g_free(ft->invitation_cookie);
159 g_free(ft);
160 xfer->data = NULL;
164 static void
165 sipe_ft_request_denied(PurpleXfer *xfer)
167 if (xfer->type == PURPLE_XFER_RECEIVE)
168 send_filetransfer_cancel(xfer);
169 sipe_ft_free_xfer_struct(xfer);
172 static
173 void raise_ft_error(PurpleXfer *xfer, const char *errmsg)
175 purple_xfer_error(purple_xfer_get_type(xfer),
176 xfer->account, xfer->who,
177 errmsg);
180 static
181 void raise_ft_strerror(PurpleXfer *xfer, const char *errmsg)
183 gchar *tmp = g_strdup_printf("%s: %s", errmsg, strerror(errno));
184 raise_ft_error(xfer, tmp);
185 g_free(tmp);
188 static
189 void raise_ft_error_and_cancel(PurpleXfer *xfer, const char *errmsg)
191 raise_ft_error(xfer, errmsg);
192 purple_xfer_cancel_local(xfer);
195 static
196 void raise_ft_socket_read_error_and_cancel(PurpleXfer *xfer)
198 raise_ft_error_and_cancel(xfer, _("Socket read failed"));
201 static
202 void raise_ft_socket_write_error_and_cancel(PurpleXfer *xfer)
204 raise_ft_error_and_cancel(xfer, _("Socket write failed"));
207 static void
208 sipe_ft_incoming_start(PurpleXfer *xfer)
210 sipe_file_transfer *ft;
211 static const gchar VER[] = "VER MSN_SECURE_FTP\r\n";
212 static const gchar TFR[] = "TFR\r\n";
213 const gsize BUFFER_SIZE = 50;
214 gchar buf[BUFFER_SIZE];
215 struct sipe_account_data *sip;
216 gchar* request;
217 const gsize FILE_SIZE_OFFSET = 4;
218 gsize file_size;
220 ft = xfer->data;
222 if (write(xfer->fd,VER,strlen(VER)) == -1) {
223 raise_ft_socket_write_error_and_cancel(xfer);
224 return;
226 if (read_line(xfer, buf, BUFFER_SIZE) < 0) {
227 raise_ft_socket_read_error_and_cancel(xfer);
228 return;
231 sip = PURPLE_XFER_TO_SIPE_ACCOUNT_DATA;
233 request = g_strdup_printf("USR %s %u\r\n", sip->username, ft->auth_cookie);
234 if (write(xfer->fd,request,strlen(request)) == -1) {
235 raise_ft_socket_write_error_and_cancel(xfer);
236 g_free(request);
237 return;
239 g_free(request);
241 if (read_line(xfer, buf, BUFFER_SIZE) < 0) {
242 raise_ft_socket_read_error_and_cancel(xfer);
243 return;
246 file_size = g_ascii_strtoull(buf + FILE_SIZE_OFFSET,NULL,10);
247 if (file_size != xfer->size) {
248 raise_ft_error_and_cancel(xfer,
249 _("File size is different from the advertised value."));
250 return;
253 if (write(xfer->fd,TFR,strlen(TFR)) == -1) {
254 raise_ft_socket_write_error_and_cancel(xfer);
255 return;
258 ft->bytes_remaining_chunk = 0;
260 ft->cipher_context = sipe_cipher_context_init(ft->encryption_key);
261 ft->hmac_context = sipe_hmac_context_init(ft->hash_key);
264 static void
265 sipe_ft_incoming_stop(PurpleXfer *xfer)
267 static const gchar BYE[] = "BYE 16777989\r\n";
268 gsize BUFFER_SIZE = 50;
269 char buffer[BUFFER_SIZE];
270 const gssize MAC_OFFSET = 4;
271 const gssize CRLF_LEN = 2;
272 gssize macLen;
273 sipe_file_transfer *ft;
274 gchar *mac;
275 gchar *mac1;
277 if (write(xfer->fd,BYE,strlen(BYE)) == -1) {
278 raise_ft_socket_write_error_and_cancel(xfer);
279 return;
282 macLen = read_line(xfer, buffer, BUFFER_SIZE);
284 if (macLen < 0) {
285 raise_ft_socket_read_error_and_cancel(xfer);
286 return;
287 } else if (macLen < (MAC_OFFSET + CRLF_LEN)) {
288 raise_ft_error_and_cancel(xfer, _("Received MAC is corrupted"));
289 return;
292 // Check MAC
293 ft = xfer->data;
294 mac = g_strndup(buffer + MAC_OFFSET, macLen - MAC_OFFSET - CRLF_LEN);
295 mac1 = sipe_hmac_finalize(ft->hmac_context);
296 if (!sipe_strequal(mac, mac1)) {
297 unlink(xfer->local_filename);
298 raise_ft_error_and_cancel(xfer,
299 _("Received file is corrupted"));
301 g_free(mac1);
302 g_free(mac);
304 sipe_ft_free_xfer_struct(xfer);
307 static gssize
308 sipe_ft_read(guchar **buffer, PurpleXfer *xfer)
310 gsize bytes_to_read;
311 ssize_t bytes_read;
313 sipe_file_transfer *ft = xfer->data;
315 if (ft->bytes_remaining_chunk == 0) {
316 guchar hdr_buf[SIPE_FT_CHUNK_HEADER_LENGTH];
318 /* read chunk header */
319 if (!read_fully(xfer, hdr_buf, sizeof(hdr_buf))) {
320 raise_ft_strerror(xfer, _("Socket read failed"));
321 return -1;
324 /* chunk header format:
326 * 0: 00 unknown (always zero?)
327 * 1: LL chunk size in bytes (low byte)
328 * 2: HH chunk size in bytes (high byte)
330 * Convert size from little endian to host order
332 ft->bytes_remaining_chunk = hdr_buf[1] + (hdr_buf[2] << 8);
335 bytes_to_read = MIN(purple_xfer_get_bytes_remaining(xfer),
336 xfer->current_buffer_size);
337 bytes_to_read = MIN(bytes_to_read, ft->bytes_remaining_chunk);
339 *buffer = g_malloc(bytes_to_read);
340 if (!*buffer) {
341 raise_ft_error(xfer, _("Out of memory"));
342 SIPE_DEBUG_ERROR("sipe_ft_read: can't allocate %" G_GSIZE_FORMAT " bytes for receive buffer",
343 bytes_to_read);
344 return -1;
347 bytes_read = do_read(xfer, *buffer, bytes_to_read);
348 if (bytes_read < 0) {
349 raise_ft_strerror(xfer, _("Socket read failed"));
350 return -1;
353 if (bytes_read > 0) {
354 guchar *decrypted = g_malloc(bytes_read);
356 if (!decrypted) {
357 raise_ft_error(xfer, _("Out of memory"));
358 SIPE_DEBUG_ERROR("sipe_ft_read: can't allocate %" G_GSIZE_FORMAT " bytes for decryption buffer",
359 (gsize)bytes_read);
360 g_free(*buffer);
361 *buffer = NULL;
362 return -1;
364 sipe_crypt_ft_stream(ft->cipher_context, *buffer, bytes_read, decrypted);
365 g_free(*buffer);
366 *buffer = decrypted;
368 sipe_digest_ft_update(ft->hmac_context, decrypted, bytes_read);
370 ft->bytes_remaining_chunk -= bytes_read;
373 return bytes_read;
376 static gssize
377 sipe_ft_write(const guchar *buffer, size_t size, PurpleXfer *xfer)
379 ssize_t bytes_written;
380 sipe_file_transfer *ft = xfer->data;
382 /* When sending data via server with ForeFront installed, block bigger than
383 * this default causes ending of transmission. Hard limit block to this value
384 * when libpurple sends us more data. */
385 const gsize DEFAULT_BLOCK_SIZE = 2045;
386 if (size > DEFAULT_BLOCK_SIZE)
387 size = DEFAULT_BLOCK_SIZE;
389 if (ft->bytes_remaining_chunk == 0) {
390 ssize_t bytes_read;
391 guchar local_buf[16];
392 guchar hdr_buf[SIPE_FT_CHUNK_HEADER_LENGTH];
394 memset(local_buf, 0, sizeof local_buf);
396 // Check if receiver did not cancel the transfer before it is finished
397 bytes_read = read(xfer->fd,local_buf,sizeof (local_buf));
398 if (bytes_read == -1 && errno != EAGAIN) {
399 raise_ft_strerror(xfer, _("Socket read failed"));
400 return -1;
401 } else if (bytes_read > 0
402 && (g_str_has_prefix((gchar*)local_buf,"CCL\r\n")
403 || g_str_has_prefix((gchar*)local_buf,"BYE 2164261682\r\n"))) {
404 return -1;
407 if (ft->outbuf_size < size) {
408 g_free(ft->encrypted_outbuf);
409 ft->outbuf_size = size;
410 ft->encrypted_outbuf = g_malloc(ft->outbuf_size);
411 if (!ft->encrypted_outbuf) {
412 raise_ft_error(xfer, _("Out of memory"));
413 SIPE_DEBUG_ERROR("sipe_ft_write: can't allocate %" G_GSIZE_FORMAT " bytes for send buffer",
414 ft->outbuf_size);
415 return -1;
419 ft->bytes_remaining_chunk = size;
420 ft->outbuf_ptr = ft->encrypted_outbuf;
421 sipe_crypt_ft_stream(ft->cipher_context, buffer, size,
422 ft->encrypted_outbuf);
423 sipe_digest_ft_update(ft->hmac_context, buffer, size);
425 /* chunk header format:
427 * 0: 00 unknown (always zero?)
428 * 1: LL chunk size in bytes (low byte)
429 * 2: HH chunk size in bytes (high byte)
431 * Convert size from host order to little endian
433 hdr_buf[0] = 0;
434 hdr_buf[1] = (ft->bytes_remaining_chunk & 0x00FF);
435 hdr_buf[2] = (ft->bytes_remaining_chunk & 0xFF00) >> 8;
437 /* write chunk header */
438 if (write(xfer->fd, hdr_buf, sizeof(hdr_buf)) == -1) {
439 raise_ft_strerror(xfer, _("Socket write failed"));
440 return -1;
444 bytes_written = write(xfer->fd, ft->outbuf_ptr, ft->bytes_remaining_chunk);
445 if (bytes_written == -1) {
446 if (errno == EAGAIN)
447 bytes_written = 0;
448 else {
449 raise_ft_strerror(xfer, _("Socket write failed"));
453 if (bytes_written > 0) {
454 ft->bytes_remaining_chunk -= bytes_written;
455 ft->outbuf_ptr += bytes_written;
458 if ((xfer->bytes_remaining - bytes_written) == 0)
459 purple_xfer_set_completed(xfer, TRUE);
461 return bytes_written;
464 static void
465 sipe_ft_outgoing_init(PurpleXfer *xfer)
467 struct sip_dialog *dialog;
468 sipe_file_transfer *ft = xfer->data;
470 gchar *body = g_strdup_printf("Application-Name: File Transfer\r\n"
471 "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n"
472 "Invitation-Command: INVITE\r\n"
473 "Invitation-Cookie: %s\r\n"
474 "Application-File: %s\r\n"
475 "Application-FileSize: %lu\r\n"
476 //"Connectivity: N\r\n" TODO
477 "Encryption: R\r\n", // TODO: non encrypted file transfer support
478 ft->invitation_cookie,
479 purple_xfer_get_filename(xfer),
480 (long unsigned) purple_xfer_get_size(xfer));
482 struct sipe_account_data *sip = PURPLE_XFER_TO_SIPE_ACCOUNT_DATA;
483 struct sip_session *session = sipe_session_find_or_add_im(sip, xfer->who);
485 g_hash_table_insert(sip->filetransfers,g_strdup(ft->invitation_cookie),xfer);
487 // Queue the message
488 sipe_session_enqueue_message(session, body, "text/x-msmsgsinvite");
490 dialog = sipe_dialog_find(session, xfer->who);
491 if (dialog && !dialog->outgoing_invite) {
492 ft->dialog = dialog;
493 sipe_im_process_queue(sip, session);
494 } else if (!dialog || !dialog->outgoing_invite) {
495 // Need to send the INVITE to get the outgoing dialog setup
496 sipe_invite(sip, session, xfer->who, body, "text/x-msmsgsinvite", NULL, FALSE);
499 g_free(body);
502 static void
503 sipe_ft_outgoing_start(PurpleXfer *xfer)
505 sipe_file_transfer *ft;
506 static const gchar VER[] = "VER MSN_SECURE_FTP\r\n";
507 const gsize BUFFER_SIZE = 50;
508 gchar buf[BUFFER_SIZE];
509 gchar** parts;
510 unsigned auth_cookie_received;
511 gboolean users_match;
512 gchar *tmp;
513 ssize_t bytes_written;
515 set_socket_nonblock(xfer->fd, TRUE);
517 ft = xfer->data;
519 if (read_line(xfer, buf, BUFFER_SIZE) < 0) {
520 raise_ft_socket_read_error_and_cancel(xfer);
521 return;
524 if (!sipe_strequal(buf,VER)) {
525 raise_ft_error_and_cancel(xfer,_("File transfer initialization failed."));
526 SIPE_DEBUG_INFO("File transfer VER string incorrect, received: %s expected: %s",
527 buf, VER);
528 return;
531 if (write(xfer->fd,VER,strlen(VER)) == -1) {
532 raise_ft_socket_write_error_and_cancel(xfer);
533 return;
536 if (read_line(xfer, buf, BUFFER_SIZE) < 0) {
537 raise_ft_socket_read_error_and_cancel(xfer);
538 return;
541 parts = g_strsplit(buf, " ", 3);
543 auth_cookie_received = g_ascii_strtoull(parts[2],NULL,10);
545 // xfer->who has 'sip:' prefix, skip these four characters
546 users_match = sipe_strcase_equal(parts[1], (xfer->who + 4));
547 g_strfreev(parts);
549 SIPE_DEBUG_INFO("File transfer authentication: %s Expected: USR %s %u",
550 buf, xfer->who + 4, ft->auth_cookie);
552 if (!users_match || (ft->auth_cookie != auth_cookie_received)) {
553 raise_ft_error_and_cancel(xfer,
554 _("File transfer authentication failed."));
555 return;
558 tmp = g_strdup_printf("FIL %lu\r\n",(long unsigned) xfer->size);
559 bytes_written = write(xfer->fd, tmp, strlen(tmp));
560 g_free(tmp);
562 if (bytes_written == -1) {
563 raise_ft_socket_write_error_and_cancel(xfer);
564 return;
567 // TFR
568 if (read_line(xfer,buf,BUFFER_SIZE) < 0) {
569 raise_ft_socket_read_error_and_cancel(xfer);
570 return;
573 ft->bytes_remaining_chunk = 0;
575 ft->cipher_context = sipe_cipher_context_init(ft->encryption_key);
576 ft->hmac_context = sipe_hmac_context_init(ft->hash_key);
579 static void
580 sipe_ft_outgoing_stop(PurpleXfer *xfer)
582 sipe_file_transfer *ft = xfer->data;
583 gsize BUFFER_SIZE = 50;
584 char buffer[BUFFER_SIZE];
585 gchar *mac;
586 gsize mac_strlen;
588 // BYE
589 if (read_line(xfer, buffer, BUFFER_SIZE) < 0) {
590 raise_ft_socket_read_error_and_cancel(xfer);
591 return;
594 mac = sipe_hmac_finalize(ft->hmac_context);
595 g_sprintf(buffer, "MAC %s \r\n", mac);
596 g_free(mac);
598 mac_strlen = strlen(buffer);
599 // There must be this zero byte between mac and \r\n
600 buffer[mac_strlen - 3] = 0;
602 if (write(xfer->fd,buffer,mac_strlen) == -1) {
603 raise_ft_socket_write_error_and_cancel(xfer);
604 return;
607 sipe_ft_free_xfer_struct(xfer);
610 //******************************************************************************
612 void sipe_ft_incoming_transfer(PurpleAccount *account, struct sipmsg *msg, const GSList *body)
614 PurpleXfer *xfer;
615 struct sipe_account_data *sip = PURPLE_ACCOUNT_TO_SIPE_ACCOUNT_DATA;
616 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
617 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
618 if (!session) {
619 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
620 session = sipe_session_find_im(sip, from);
621 g_free(from);
624 if (!session) {
625 SIPE_DEBUG_ERROR_NOFORMAT("sipe_ft_incoming_transfer: can't find session for remote party");
626 return;
629 xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE, session->with);
631 if (xfer) {
632 size_t file_size;
633 sipe_file_transfer *ft = g_new0(sipe_file_transfer, 1);
634 ft->invitation_cookie = g_strdup(sipe_utils_nameval_find(body, "Invitation-Cookie"));
635 ft->sip = sip;
636 ft->dialog = sipe_dialog_find(session, session->with);
637 ft->listenfd = -1;
638 generate_key(ft->encryption_key, SIPE_FT_KEY_LENGTH);
639 generate_key(ft->hash_key, SIPE_FT_KEY_LENGTH);
640 xfer->data = ft;
642 purple_xfer_set_filename(xfer, sipe_utils_nameval_find(body, "Application-File"));
644 file_size = g_ascii_strtoull(sipe_utils_nameval_find(body, "Application-FileSize"),NULL,10);
645 purple_xfer_set_size(xfer, file_size);
647 purple_xfer_set_init_fnc(xfer, sipe_ft_incoming_init);
648 purple_xfer_set_start_fnc(xfer,sipe_ft_incoming_start);
649 purple_xfer_set_end_fnc(xfer,sipe_ft_incoming_stop);
650 purple_xfer_set_request_denied_fnc(xfer, sipe_ft_request_denied);
651 purple_xfer_set_read_fnc(xfer,sipe_ft_read);
652 purple_xfer_set_cancel_send_fnc(xfer,sipe_ft_free_xfer_struct);
653 purple_xfer_set_cancel_recv_fnc(xfer,sipe_ft_free_xfer_struct);
655 g_hash_table_insert(sip->filetransfers,g_strdup(ft->invitation_cookie),xfer);
657 purple_xfer_request(xfer);
661 void sipe_ft_incoming_accept(PurpleAccount *account, const GSList *body)
663 struct sipe_account_data *sip = PURPLE_ACCOUNT_TO_SIPE_ACCOUNT_DATA;
664 const gchar *inv_cookie = sipe_utils_nameval_find(body, "Invitation-Cookie");
665 PurpleXfer *xfer = g_hash_table_lookup(sip->filetransfers,inv_cookie);
667 if (xfer) {
668 const gchar *ip = sipe_utils_nameval_find(body, "IP-Address");
669 const gchar *port_str = sipe_utils_nameval_find(body, "Port");
670 const gchar *auth_cookie = sipe_utils_nameval_find(body, "AuthCookie");
671 const gchar *enc_key_b64 = sipe_utils_nameval_find(body, "Encryption-Key");
672 const gchar *hash_key_b64 = sipe_utils_nameval_find(body, "Hash-Key");
674 sipe_file_transfer *ft = xfer->data;
676 if (auth_cookie)
677 ft->auth_cookie = g_ascii_strtoull(auth_cookie,NULL,10);
678 if (enc_key_b64) {
679 gsize ret_len;
680 guchar *enc_key = g_base64_decode(enc_key_b64, &ret_len);
681 if (ret_len == SIPE_FT_KEY_LENGTH) {
682 memcpy(ft->encryption_key,enc_key,SIPE_FT_KEY_LENGTH);
683 } else {
684 raise_ft_error_and_cancel(xfer,
685 _("Received encryption key has wrong size."));
686 g_free(enc_key);
687 return;
689 g_free(enc_key);
691 if (hash_key_b64) {
692 gsize ret_len;
693 guchar *hash_key = g_base64_decode(hash_key_b64, &ret_len);
694 if (ret_len == SIPE_FT_KEY_LENGTH) {
695 memcpy(ft->hash_key,hash_key,SIPE_FT_KEY_LENGTH);
696 } else {
697 raise_ft_error_and_cancel(xfer,
698 _("Received hash key has wrong size."));
699 g_free(hash_key);
700 return;
702 g_free(hash_key);
705 if (ip && port_str) {
706 purple_xfer_start(xfer, -1, ip, g_ascii_strtoull(port_str,NULL,10));
707 } else {
708 ft->listener = purple_network_listen_range(SIPE_FT_TCP_PORT_MIN,
709 SIPE_FT_TCP_PORT_MAX,
710 SOCK_STREAM,
711 sipe_ft_listen_socket_created,
712 xfer);
713 if (!ft->listener) {
714 raise_ft_error_and_cancel(xfer,
715 _("Could not create listen socket"));
716 return;
722 void sipe_ft_incoming_cancel(PurpleAccount *account, GSList *body)
724 gchar *inv_cookie = g_strdup(sipe_utils_nameval_find(body, "Invitation-Cookie"));
726 struct sipe_account_data *sip = PURPLE_ACCOUNT_TO_SIPE_ACCOUNT_DATA;
727 PurpleXfer *xfer = g_hash_table_lookup(sip->filetransfers,inv_cookie);
729 purple_xfer_cancel_remote(xfer);
732 static void send_filetransfer_accept(PurpleXfer* xfer)
734 sipe_file_transfer* ft = xfer->data;
735 struct sip_dialog *dialog = ft->dialog;
737 gchar *b64_encryption_key = g_base64_encode(ft->encryption_key, 24);
738 gchar *b64_hash_key = g_base64_encode(ft->hash_key, 24);
740 gchar *body = g_strdup_printf("Invitation-Command: ACCEPT\r\n"
741 "Request-Data: IP-Address:\r\n"
742 "Invitation-Cookie: %s\r\n"
743 "Encryption-Key: %s\r\n"
744 "Hash-Key: %s\r\n"
745 /*"IP-Address: %s\r\n"
746 "Port: 6900\r\n"
747 "PortX: 11178\r\n"
748 "Auth-Cookie: 11111111\r\n"
749 "Sender-Connect: TRUE\r\n"*/,
750 ft->invitation_cookie,
751 b64_encryption_key,
752 b64_hash_key
753 /*,sipe_backend_network_ip_address()*/
756 send_sip_request(ft->sip->private, "MESSAGE", dialog->with, dialog->with,
757 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
758 body, dialog, NULL);
760 g_free(body);
761 g_free(b64_encryption_key);
762 g_free(b64_hash_key);
765 static void send_filetransfer_cancel(PurpleXfer* xfer) {
766 sipe_file_transfer* ft = xfer->data;
767 struct sip_dialog* dialog = ft->dialog;
769 gchar *body = g_strdup_printf("Invitation-Command: CANCEL\r\n"
770 "Invitation-Cookie: %s\r\n"
771 "Cancel-Code: REJECT\r\n",
772 ft->invitation_cookie);
774 send_sip_request(ft->sip->private, "MESSAGE", dialog->with, dialog->with,
775 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
776 body, dialog, NULL);
778 g_free(body);
781 static ssize_t
782 do_read(PurpleXfer *xfer, guchar *buf, size_t len)
784 ssize_t bytes_read = read(xfer->fd, buf, len);
785 if (bytes_read == 0) {
786 // Sender canceled transfer before it was finished
787 return -2;
788 } else if (bytes_read == -1) {
789 if (errno == EAGAIN)
790 return 0;
791 else
792 return -1;
794 return bytes_read;
797 static gboolean
798 read_fully(PurpleXfer *xfer, guchar *buf, size_t len)
800 const gulong READ_TIMEOUT = 10000000;
801 gulong time_spent = 0;
803 while (len) {
804 ssize_t bytes_read = do_read(xfer, buf, len);
805 if (bytes_read == 0) {
806 g_usleep(100000);
807 time_spent += 100000;
808 } else if (bytes_read < 0 || time_spent > READ_TIMEOUT) {
809 return FALSE;
810 } else {
811 len -= bytes_read;
812 buf += bytes_read;
813 time_spent = 0;
816 return TRUE;
819 static gssize read_line(PurpleXfer *xfer, gchar *buffer, gssize size)
821 gssize pos = 0;
823 memset(buffer,0,size);
824 do {
825 if (!read_fully(xfer, (guchar*) buffer + pos, 1))
826 return -1;
827 } while (buffer[pos] != '\n' && ++pos != (size - 1));
829 if (pos == (size - 1) && buffer[pos - 1] != '\n') {
830 // Buffer too short
831 return -2;
834 return pos;
837 static gpointer sipe_cipher_context_init(const guchar *enc_key)
840 * Decryption of file from SIPE file transfer
842 * Decryption:
843 * 1.) SHA1-Key = SHA1sum (Encryption-Key); Do SHA1 digest from Encryption-Key, return 20 bytes SHA1-Key.
844 * 2.) Decrypt-Data = RC4 (Encrypt-Data, substr(SHA1-Key, 0, 15)); Decryption of encrypted data, used 16 bytes SHA1-Key;
847 guchar k2[SIPE_DIGEST_SHA1_LENGTH];
849 /* 1.) SHA1 sum */
850 sipe_digest_sha1(enc_key, SIPE_FT_KEY_LENGTH, k2);
852 /* 2.) RC4 decryption */
853 return sipe_crypt_ft_start(k2);
856 static gpointer sipe_hmac_context_init(const guchar *hash_key)
859 * Count MAC digest
861 * HMAC digest:
862 * 1.) SHA1-Key = SHA1sum (Hash-Key); Do SHA1 digest from Hash-Key, return 20 bytes SHA1-Key.
863 * 2.) MAC = HMAC_SHA1 (Decrypt-Data, substr(HMAC-Key,0,15)); Digest of decrypted file and SHA1-Key (used again only 16 bytes)
866 guchar k2[SIPE_DIGEST_SHA1_LENGTH];
868 /* 1.) SHA1 sum */
869 sipe_digest_sha1(hash_key, SIPE_FT_KEY_LENGTH, k2);
871 /* 2.) HMAC (initialization only) */
872 return sipe_digest_ft_start(k2);
875 static gchar* sipe_hmac_finalize(gpointer hmac_context)
877 guchar hmac_digest[SIPE_DIGEST_FILETRANSFER_LENGTH];
879 /* MAC = Digest of decrypted file and SHA1-Key (used again only 16 bytes) */
880 sipe_digest_ft_end(hmac_context, hmac_digest);
882 return g_base64_encode(hmac_digest, sizeof (hmac_digest));
885 static void generate_key(guchar *buffer, gsize size)
887 gsize i;
888 for (i = 0; i != size; ++i)
889 buffer[i] = rand();
892 static void set_socket_nonblock(int fd, gboolean state)
894 int flags = fcntl(fd, F_GETFL, 0);
895 if (flags == -1)
896 flags = 0;
898 if (state == TRUE)
899 fcntl(fd, F_SETFL, flags | O_NONBLOCK);
900 else
901 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
904 void sipe_ft_send_file(PurpleConnection *gc, const char *who, const char *file)
906 PurpleXfer *xfer = sipe_ft_new_xfer(gc, who);
908 if (xfer) {
909 if (file != NULL)
910 purple_xfer_request_accepted(xfer, file);
911 else
912 purple_xfer_request(xfer);
916 PurpleXfer * sipe_ft_new_xfer(PurpleConnection *gc, const char *who)
918 PurpleXfer *xfer = NULL;
920 if (PURPLE_CONNECTION_IS_VALID(gc)) {
921 xfer = purple_xfer_new(purple_connection_get_account(gc),
922 PURPLE_XFER_SEND, who);
924 if (xfer) {
925 struct sipe_account_data *sip = PURPLE_GC_TO_SIPE_ACCOUNT_DATA;
927 sipe_file_transfer *ft = g_new0(sipe_file_transfer, 1);
928 ft->invitation_cookie = g_strdup_printf("%u", rand() % 1000000000);
929 ft->sip = sip;
931 xfer->data = ft;
933 purple_xfer_set_init_fnc(xfer, sipe_ft_outgoing_init);
934 purple_xfer_set_start_fnc(xfer, sipe_ft_outgoing_start);
935 purple_xfer_set_end_fnc(xfer, sipe_ft_outgoing_stop);
936 purple_xfer_set_request_denied_fnc(xfer, sipe_ft_request_denied);
937 purple_xfer_set_write_fnc(xfer, sipe_ft_write);
938 purple_xfer_set_cancel_send_fnc(xfer, sipe_ft_free_xfer_struct);
939 purple_xfer_set_cancel_recv_fnc(xfer, sipe_ft_free_xfer_struct);
943 return xfer;
946 static
947 void sipe_ft_client_connected(gpointer p_xfer, gint listenfd,
948 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
950 struct sockaddr_in saddr;
951 socklen_t slen = sizeof (saddr);
953 int fd = accept(listenfd, (struct sockaddr*)&saddr, &slen);
955 PurpleXfer *xfer = p_xfer;
956 sipe_file_transfer *ft = xfer->data;
958 purple_input_remove(xfer->watcher);
959 xfer->watcher = 0;
960 close(listenfd);
961 ft->listenfd = -1;
963 if (fd < 0) {
964 raise_ft_socket_read_error_and_cancel(xfer);
965 } else {
966 purple_xfer_start(xfer, fd, NULL, 0);
970 static
971 void sipe_ft_listen_socket_created(int listenfd, gpointer data)
973 gchar *body;
974 PurpleXfer *xfer = data;
975 sipe_file_transfer *ft = xfer->data;
977 struct sockaddr_in addr;
979 socklen_t socklen = sizeof (addr);
981 ft->listener = NULL;
982 ft->listenfd = listenfd;
984 getsockname(listenfd, (struct sockaddr*)&addr, &socklen);
986 xfer->watcher = purple_input_add(listenfd, PURPLE_INPUT_READ,
987 sipe_ft_client_connected, xfer);
989 ft->auth_cookie = rand() % 1000000000;
991 body = g_strdup_printf("Invitation-Command: ACCEPT\r\n"
992 "Invitation-Cookie: %s\r\n"
993 "IP-Address: %s\r\n"
994 "Port: %u\r\n"
995 "PortX: 11178\r\n"
996 "AuthCookie: %u\r\n"
997 "Request-Data: IP-Address:\r\n",
998 ft->invitation_cookie,
999 sipe_utils_get_suitable_local_ip(listenfd),
1000 ntohs(addr.sin_port),
1001 ft->auth_cookie);
1003 if (!ft->dialog) {
1004 struct sipe_account_data *sip = PURPLE_XFER_TO_SIPE_ACCOUNT_DATA;
1005 struct sip_session *session = sipe_session_find_or_add_im(sip, xfer->who);
1006 ft->dialog = sipe_dialog_find(session, xfer->who);
1009 if (ft->dialog) {
1010 send_sip_request(ft->sip->private, "MESSAGE", ft->dialog->with, ft->dialog->with,
1011 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
1012 body, ft->dialog, NULL);
1014 g_free(body);
1017 GSList * sipe_ft_parse_msg_body(const gchar *body)
1019 GSList *list = NULL;
1020 gchar **lines = g_strsplit(body, "\r\n", 0);
1021 if (sipe_utils_parse_lines(&list, lines, ":") == FALSE) {
1022 sipe_utils_nameval_free(list);
1023 list = NULL;
1025 g_strfreev(lines);
1026 return list;
1030 Local Variables:
1031 mode: c
1032 c-file-style: "bsd"
1033 indent-tabs-mode: t
1034 tab-width: 8
1035 End: