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
31 #include <glib/gprintf.h>
37 #include "sipe-dialog.h"
39 #include "sipe-session.h"
40 #include "sipe-utils.h"
44 #include "libc_interface.h"
47 #include <sys/types.h>
48 #include <sys/socket.h>
49 #include <sys/ioctl.h>
50 #include <netinet/in.h>
52 #include <arpa/inet.h>
53 #ifdef HAVE_SYS_SOCKIO_H
54 #include <sys/sockio.h> /* SIOCGIFCONF for Solaris */
58 #define SIPE_FT_KEY_LENGTH 24
59 #define SIPE_FT_CHUNK_HEADER_LENGTH 3
62 * DO NOT CHANGE THE FOLLOWING CONSTANTS!!!
64 * It seems that Microsoft Office Communicator client will accept
65 * file transfer invitations *only* within this port range!
67 * If a firewall is active on your system you need to open these ports if
68 * you want to *send* files to other users. Receiving files uses an ougoing
69 * connection and should therefore automatically penetrate your firewall.
71 #define SIPE_FT_TCP_PORT_MIN 6891
72 #define SIPE_FT_TCP_PORT_MAX 6901
74 struct _sipe_file_transfer
{
75 guchar encryption_key
[SIPE_FT_KEY_LENGTH
];
76 guchar hash_key
[SIPE_FT_KEY_LENGTH
];
77 gchar
*invitation_cookie
;
79 struct sipe_account_data
*sip
;
80 struct sip_dialog
*dialog
;
81 PurpleCipherContext
*cipher_context
;
82 PurpleCipherContext
*hmac_context
;
84 PurpleNetworkListenData
*listener
;
87 gsize bytes_remaining_chunk
;
88 guchar
* encrypted_outbuf
;
92 typedef struct _sipe_file_transfer sipe_file_transfer
;
94 static void send_filetransfer_accept(PurpleXfer
* xfer
);
95 static void send_filetransfer_cancel(PurpleXfer
* xfer
);
96 static ssize_t
do_read(PurpleXfer
*xfer
, guchar
*buf
, size_t len
);
97 static gboolean
read_fully(PurpleXfer
*xfer
, guchar
*buf
, size_t len
);
98 static gssize
read_line(PurpleXfer
*xfer
, gchar
*buffer
, gssize size
);
99 static void sipe_cipher_context_init(PurpleCipherContext
**rc4_context
, const guchar
*enc_key
);
100 static void sipe_hmac_context_init(PurpleCipherContext
**hmac_context
, const guchar
*hash_key
);
101 static gchar
*sipe_hmac_finalize(PurpleCipherContext
*hmac_context
);
102 static void generate_key(guchar
*buffer
, gsize size
);
103 static void set_socket_nonblock(int fd
, gboolean state
);
104 static void sipe_ft_listen_socket_created(int listenfd
, gpointer data
);
105 static const char * sipe_ft_get_suitable_local_ip(int fd
);
107 //******************************************************************************
108 // I/O operations for PurpleXfer structure
109 //******************************************************************************
112 sipe_ft_incoming_init(PurpleXfer
*xfer
)
114 send_filetransfer_accept(xfer
);
118 sipe_ft_free_xfer_struct(PurpleXfer
*xfer
)
120 sipe_file_transfer
*ft
= xfer
->data
;
122 struct sipe_account_data
*sip
= xfer
->account
->gc
->proto_data
;
124 g_hash_table_remove(sip
->filetransfers
,ft
->invitation_cookie
);
127 purple_input_remove(xfer
->watcher
);
130 if (ft
->listenfd
>= 0) {
131 purple_debug_info("sipe", "sipe_ft_free_xfer_struct: closing listening socket %d\n", ft
->listenfd
);
135 purple_network_listen_cancel(ft
->listener
);
136 if (ft
->cipher_context
)
137 purple_cipher_context_destroy(ft
->cipher_context
);
139 if (ft
->hmac_context
)
140 purple_cipher_context_destroy(ft
->hmac_context
);
142 g_free(ft
->encrypted_outbuf
);
143 g_free(ft
->invitation_cookie
);
150 sipe_ft_request_denied(PurpleXfer
*xfer
)
152 if (xfer
->type
== PURPLE_XFER_RECEIVE
)
153 send_filetransfer_cancel(xfer
);
154 sipe_ft_free_xfer_struct(xfer
);
158 void raise_ft_error(PurpleXfer
*xfer
, const char *errmsg
)
160 purple_xfer_error(purple_xfer_get_type(xfer
),
161 xfer
->account
, xfer
->who
,
166 void raise_ft_strerror(PurpleXfer
*xfer
, const char *errmsg
)
168 gchar
*tmp
= g_strdup_printf("%s: %s", errmsg
, strerror(errno
));
169 raise_ft_error(xfer
, tmp
);
174 void raise_ft_error_and_cancel(PurpleXfer
*xfer
, const char *errmsg
)
176 raise_ft_error(xfer
, errmsg
);
177 purple_xfer_cancel_local(xfer
);
181 void raise_ft_socket_read_error_and_cancel(PurpleXfer
*xfer
)
183 raise_ft_error_and_cancel(xfer
, _("Socket read failed"));
187 void raise_ft_socket_write_error_and_cancel(PurpleXfer
*xfer
)
189 raise_ft_error_and_cancel(xfer
, _("Socket write failed"));
193 sipe_ft_incoming_start(PurpleXfer
*xfer
)
195 sipe_file_transfer
*ft
;
196 static const gchar VER
[] = "VER MSN_SECURE_FTP\r\n";
197 static const gchar TFR
[] = "TFR\r\n";
198 const gsize BUFFER_SIZE
= 50;
199 gchar buf
[BUFFER_SIZE
];
200 struct sipe_account_data
*sip
;
202 const gsize FILE_SIZE_OFFSET
= 4;
207 if (write(xfer
->fd
,VER
,strlen(VER
)) == -1) {
208 raise_ft_socket_write_error_and_cancel(xfer
);
211 if (read_line(xfer
, buf
, BUFFER_SIZE
) < 0) {
212 raise_ft_socket_read_error_and_cancel(xfer
);
216 sip
= xfer
->account
->gc
->proto_data
;
218 request
= g_strdup_printf("USR %s %u\r\n", sip
->username
, ft
->auth_cookie
);
219 if (write(xfer
->fd
,request
,strlen(request
)) == -1) {
220 raise_ft_socket_write_error_and_cancel(xfer
);
226 if (read_line(xfer
, buf
, BUFFER_SIZE
) < 0) {
227 raise_ft_socket_read_error_and_cancel(xfer
);
231 file_size
= g_ascii_strtoull(buf
+ FILE_SIZE_OFFSET
,NULL
,10);
232 if (file_size
!= xfer
->size
) {
233 raise_ft_error_and_cancel(xfer
,
234 _("File size is different from the advertised value."));
238 if (write(xfer
->fd
,TFR
,strlen(TFR
)) == -1) {
239 raise_ft_socket_write_error_and_cancel(xfer
);
243 ft
->bytes_remaining_chunk
= 0;
245 sipe_cipher_context_init(&ft
->cipher_context
, ft
->encryption_key
);
246 sipe_hmac_context_init(&ft
->hmac_context
, ft
->hash_key
);
250 sipe_ft_incoming_stop(PurpleXfer
*xfer
)
252 static const gchar BYE
[] = "BYE 16777989\r\n";
253 gsize BUFFER_SIZE
= 50;
254 char buffer
[BUFFER_SIZE
];
255 const gssize MAC_OFFSET
= 4;
256 const gssize CRLF_LEN
= 2;
258 sipe_file_transfer
*ft
;
262 if (write(xfer
->fd
,BYE
,strlen(BYE
)) == -1) {
263 raise_ft_socket_write_error_and_cancel(xfer
);
267 macLen
= read_line(xfer
, buffer
, BUFFER_SIZE
);
270 raise_ft_socket_read_error_and_cancel(xfer
);
272 } else if (macLen
< (MAC_OFFSET
+ CRLF_LEN
)) {
273 raise_ft_error_and_cancel(xfer
, _("Received MAC is corrupted"));
279 mac
= g_strndup(buffer
+ MAC_OFFSET
, macLen
- MAC_OFFSET
- CRLF_LEN
);
280 mac1
= sipe_hmac_finalize(ft
->hmac_context
);
281 if (!sipe_strequal(mac
, mac1
)) {
282 unlink(xfer
->local_filename
);
283 raise_ft_error_and_cancel(xfer
,
284 _("Received file is corrupted"));
289 sipe_ft_free_xfer_struct(xfer
);
293 sipe_ft_read(guchar
**buffer
, PurpleXfer
*xfer
)
298 sipe_file_transfer
*ft
= xfer
->data
;
300 if (ft
->bytes_remaining_chunk
== 0) {
301 guchar hdr_buf
[SIPE_FT_CHUNK_HEADER_LENGTH
];
303 /* read chunk header */
304 if (!read_fully(xfer
, hdr_buf
, sizeof(hdr_buf
))) {
305 raise_ft_strerror(xfer
, _("Socket read failed"));
309 /* chunk header format:
311 * 0: 00 unknown (always zero?)
312 * 1: LL chunk size in bytes (low byte)
313 * 2: HH chunk size in bytes (high byte)
315 * Convert size from little endian to host order
317 ft
->bytes_remaining_chunk
= hdr_buf
[1] + (hdr_buf
[2] << 8);
320 bytes_to_read
= MIN(purple_xfer_get_bytes_remaining(xfer
),
321 xfer
->current_buffer_size
);
322 bytes_to_read
= MIN(bytes_to_read
, ft
->bytes_remaining_chunk
);
324 *buffer
= g_malloc(bytes_to_read
);
326 raise_ft_error(xfer
, _("Out of memory"));
327 purple_debug_error("sipe", "sipe_ft_read: can't allocate %" G_GSIZE_FORMAT
" bytes for receive buffer\n",
332 bytes_read
= do_read(xfer
, *buffer
, bytes_to_read
);
333 if (bytes_read
< 0) {
334 raise_ft_strerror(xfer
, _("Socket read failed"));
338 if (bytes_read
> 0) {
339 guchar
*decrypted
= g_malloc(bytes_read
);
342 raise_ft_error(xfer
, _("Out of memory"));
343 purple_debug_error("sipe", "sipe_ft_read: can't allocate %" G_GSIZE_FORMAT
" bytes for decryption buffer\n",
349 purple_cipher_context_encrypt(ft
->cipher_context
, *buffer
, bytes_read
, decrypted
, NULL
);
353 purple_cipher_context_append(ft
->hmac_context
, decrypted
, bytes_read
);
355 ft
->bytes_remaining_chunk
-= bytes_read
;
362 sipe_ft_write(const guchar
*buffer
, size_t size
, PurpleXfer
*xfer
)
364 ssize_t bytes_written
;
365 sipe_file_transfer
*ft
= xfer
->data
;
367 /* When sending data via server with ForeFront installed, block bigger than
368 * this default causes ending of transmission. Hard limit block to this value
369 * when libpurple sends us more data. */
370 const gsize DEFAULT_BLOCK_SIZE
= 2045;
371 if (size
> DEFAULT_BLOCK_SIZE
)
372 size
= DEFAULT_BLOCK_SIZE
;
374 if (ft
->bytes_remaining_chunk
== 0) {
376 guchar local_buf
[16];
377 guchar hdr_buf
[SIPE_FT_CHUNK_HEADER_LENGTH
];
379 memset(local_buf
, 0, sizeof local_buf
);
381 // Check if receiver did not cancel the transfer before it is finished
382 bytes_read
= read(xfer
->fd
,local_buf
,sizeof (local_buf
));
383 if (bytes_read
== -1 && errno
!= EAGAIN
) {
384 raise_ft_strerror(xfer
, _("Socket read failed"));
386 } else if (bytes_read
> 0
387 && (g_str_has_prefix((gchar
*)local_buf
,"CCL\r\n")
388 || g_str_has_prefix((gchar
*)local_buf
,"BYE 2164261682\r\n"))) {
392 if (ft
->outbuf_size
< size
) {
393 g_free(ft
->encrypted_outbuf
);
394 ft
->outbuf_size
= size
;
395 ft
->encrypted_outbuf
= g_malloc(ft
->outbuf_size
);
396 if (!ft
->encrypted_outbuf
) {
397 raise_ft_error(xfer
, _("Out of memory"));
398 purple_debug_error("sipe", "sipe_ft_write: can't allocate %" G_GSIZE_FORMAT
" bytes for send buffer\n",
404 ft
->bytes_remaining_chunk
= size
;
405 ft
->outbuf_ptr
= ft
->encrypted_outbuf
;
406 purple_cipher_context_encrypt(ft
->cipher_context
, buffer
, size
,
407 ft
->encrypted_outbuf
, NULL
);
408 purple_cipher_context_append(ft
->hmac_context
, buffer
, size
);
410 /* chunk header format:
412 * 0: 00 unknown (always zero?)
413 * 1: LL chunk size in bytes (low byte)
414 * 2: HH chunk size in bytes (high byte)
416 * Convert size from host order to little endian
419 hdr_buf
[1] = (ft
->bytes_remaining_chunk
& 0x00FF);
420 hdr_buf
[2] = (ft
->bytes_remaining_chunk
& 0xFF00) >> 8;
422 /* write chunk header */
423 if (write(xfer
->fd
, hdr_buf
, sizeof(hdr_buf
)) == -1) {
424 raise_ft_strerror(xfer
, _("Socket write failed"));
429 bytes_written
= write(xfer
->fd
, ft
->outbuf_ptr
, ft
->bytes_remaining_chunk
);
430 if (bytes_written
== -1) {
434 raise_ft_strerror(xfer
, _("Socket write failed"));
438 if (bytes_written
> 0) {
439 ft
->bytes_remaining_chunk
-= bytes_written
;
440 ft
->outbuf_ptr
+= bytes_written
;
443 if ((xfer
->bytes_remaining
- bytes_written
) == 0)
444 purple_xfer_set_completed(xfer
, TRUE
);
446 return bytes_written
;
450 sipe_ft_outgoing_init(PurpleXfer
*xfer
)
452 struct sip_dialog
*dialog
;
453 sipe_file_transfer
*ft
= xfer
->data
;
455 gchar
*body
= g_strdup_printf("Application-Name: File Transfer\r\n"
456 "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n"
457 "Invitation-Command: INVITE\r\n"
458 "Invitation-Cookie: %s\r\n"
459 "Application-File: %s\r\n"
460 "Application-FileSize: %lu\r\n"
461 //"Connectivity: N\r\n" TODO
462 "Encryption: R\r\n", // TODO: non encrypted file transfer support
463 ft
->invitation_cookie
,
464 purple_xfer_get_filename(xfer
),
465 (long unsigned) purple_xfer_get_size(xfer
));
467 struct sipe_account_data
*sip
= xfer
->account
->gc
->proto_data
;
468 struct sip_session
*session
= sipe_session_find_or_add_im(sip
, xfer
->who
);
470 g_hash_table_insert(sip
->filetransfers
,g_strdup(ft
->invitation_cookie
),xfer
);
473 sipe_session_enqueue_message(session
, body
, "text/x-msmsgsinvite");
475 dialog
= sipe_dialog_find(session
, xfer
->who
);
476 if (dialog
&& !dialog
->outgoing_invite
) {
478 sipe_im_process_queue(sip
, session
);
479 } else if (!dialog
|| !dialog
->outgoing_invite
) {
480 // Need to send the INVITE to get the outgoing dialog setup
481 sipe_invite(sip
, session
, xfer
->who
, body
, "text/x-msmsgsinvite", NULL
, FALSE
);
488 sipe_ft_outgoing_start(PurpleXfer
*xfer
)
490 sipe_file_transfer
*ft
;
491 static const gchar VER
[] = "VER MSN_SECURE_FTP\r\n";
492 const gsize BUFFER_SIZE
= 50;
493 gchar buf
[BUFFER_SIZE
];
495 unsigned auth_cookie_received
;
496 gboolean users_match
;
498 ssize_t bytes_written
;
500 set_socket_nonblock(xfer
->fd
, TRUE
);
504 if (read_line(xfer
, buf
, BUFFER_SIZE
) < 0) {
505 raise_ft_socket_read_error_and_cancel(xfer
);
509 if (!sipe_strequal(buf
,VER
)) {
510 raise_ft_error_and_cancel(xfer
,_("File transfer initialization failed."));
511 purple_debug_info("sipe","File transfer VER string incorrect, received: %s expected: %s",
516 if (write(xfer
->fd
,VER
,strlen(VER
)) == -1) {
517 raise_ft_socket_write_error_and_cancel(xfer
);
521 if (read_line(xfer
, buf
, BUFFER_SIZE
) < 0) {
522 raise_ft_socket_read_error_and_cancel(xfer
);
526 parts
= g_strsplit(buf
, " ", 3);
528 auth_cookie_received
= g_ascii_strtoull(parts
[2],NULL
,10);
530 // xfer->who has 'sip:' prefix, skip these four characters
531 users_match
= sipe_strcase_equal(parts
[1], (xfer
->who
+ 4));
534 purple_debug_info("sipe","File transfer authentication: %s Expected: USR %s %u\n",
535 buf
, xfer
->who
+ 4, ft
->auth_cookie
);
537 if (!users_match
|| (ft
->auth_cookie
!= auth_cookie_received
)) {
538 raise_ft_error_and_cancel(xfer
,
539 _("File transfer authentication failed."));
543 tmp
= g_strdup_printf("FIL %lu\r\n",(long unsigned) xfer
->size
);
544 bytes_written
= write(xfer
->fd
, tmp
, strlen(tmp
));
547 if (bytes_written
== -1) {
548 raise_ft_socket_write_error_and_cancel(xfer
);
553 if (read_line(xfer
,buf
,BUFFER_SIZE
) < 0) {
554 raise_ft_socket_read_error_and_cancel(xfer
);
558 ft
->bytes_remaining_chunk
= 0;
560 sipe_cipher_context_init(&ft
->cipher_context
, ft
->encryption_key
);
561 sipe_hmac_context_init(&ft
->hmac_context
, ft
->hash_key
);
565 sipe_ft_outgoing_stop(PurpleXfer
*xfer
)
567 sipe_file_transfer
*ft
= xfer
->data
;
568 gsize BUFFER_SIZE
= 50;
569 char buffer
[BUFFER_SIZE
];
574 if (read_line(xfer
, buffer
, BUFFER_SIZE
) < 0) {
575 raise_ft_socket_read_error_and_cancel(xfer
);
579 mac
= sipe_hmac_finalize(ft
->hmac_context
);
580 g_sprintf(buffer
, "MAC %s \r\n", mac
);
583 mac_strlen
= strlen(buffer
);
584 // There must be this zero byte between mac and \r\n
585 buffer
[mac_strlen
- 3] = 0;
587 if (write(xfer
->fd
,buffer
,mac_strlen
) == -1) {
588 raise_ft_socket_write_error_and_cancel(xfer
);
592 sipe_ft_free_xfer_struct(xfer
);
595 //******************************************************************************
597 void sipe_ft_incoming_transfer(PurpleAccount
*account
, struct sipmsg
*msg
, const GSList
*body
)
600 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
601 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
602 struct sip_session
*session
= sipe_session_find_chat_by_callid(sip
, callid
);
604 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
605 session
= sipe_session_find_im(sip
, from
);
610 purple_debug_error("sipe", "sipe_ft_incoming_transfer: can't find session for remote party\n");
614 xfer
= purple_xfer_new(account
, PURPLE_XFER_RECEIVE
, session
->with
);
618 sipe_file_transfer
*ft
= g_new0(sipe_file_transfer
, 1);
619 ft
->invitation_cookie
= g_strdup(sipe_utils_nameval_find(body
, "Invitation-Cookie"));
621 ft
->dialog
= sipe_dialog_find(session
, session
->with
);
623 generate_key(ft
->encryption_key
, SIPE_FT_KEY_LENGTH
);
624 generate_key(ft
->hash_key
, SIPE_FT_KEY_LENGTH
);
627 purple_xfer_set_filename(xfer
, sipe_utils_nameval_find(body
, "Application-File"));
629 file_size
= g_ascii_strtoull(sipe_utils_nameval_find(body
, "Application-FileSize"),NULL
,10);
630 purple_xfer_set_size(xfer
, file_size
);
632 purple_xfer_set_init_fnc(xfer
, sipe_ft_incoming_init
);
633 purple_xfer_set_start_fnc(xfer
,sipe_ft_incoming_start
);
634 purple_xfer_set_end_fnc(xfer
,sipe_ft_incoming_stop
);
635 purple_xfer_set_request_denied_fnc(xfer
, sipe_ft_request_denied
);
636 purple_xfer_set_read_fnc(xfer
,sipe_ft_read
);
637 purple_xfer_set_cancel_send_fnc(xfer
,sipe_ft_free_xfer_struct
);
638 purple_xfer_set_cancel_recv_fnc(xfer
,sipe_ft_free_xfer_struct
);
640 g_hash_table_insert(sip
->filetransfers
,g_strdup(ft
->invitation_cookie
),xfer
);
642 purple_xfer_request(xfer
);
646 void sipe_ft_incoming_accept(PurpleAccount
*account
, const GSList
*body
)
648 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
649 const gchar
*inv_cookie
= sipe_utils_nameval_find(body
, "Invitation-Cookie");
650 PurpleXfer
*xfer
= g_hash_table_lookup(sip
->filetransfers
,inv_cookie
);
653 const gchar
*ip
= sipe_utils_nameval_find(body
, "IP-Address");
654 const gchar
*port_str
= sipe_utils_nameval_find(body
, "Port");
655 const gchar
*auth_cookie
= sipe_utils_nameval_find(body
, "AuthCookie");
656 const gchar
*enc_key_b64
= sipe_utils_nameval_find(body
, "Encryption-Key");
657 const gchar
*hash_key_b64
= sipe_utils_nameval_find(body
, "Hash-Key");
659 sipe_file_transfer
*ft
= xfer
->data
;
662 ft
->auth_cookie
= g_ascii_strtoull(auth_cookie
,NULL
,10);
665 guchar
*enc_key
= purple_base64_decode(enc_key_b64
, &ret_len
);
666 if (ret_len
== SIPE_FT_KEY_LENGTH
) {
667 memcpy(ft
->encryption_key
,enc_key
,SIPE_FT_KEY_LENGTH
);
669 raise_ft_error_and_cancel(xfer
,
670 _("Received encryption key has wrong size."));
678 guchar
*hash_key
= purple_base64_decode(hash_key_b64
, &ret_len
);
679 if (ret_len
== SIPE_FT_KEY_LENGTH
) {
680 memcpy(ft
->hash_key
,hash_key
,SIPE_FT_KEY_LENGTH
);
682 raise_ft_error_and_cancel(xfer
,
683 _("Received hash key has wrong size."));
690 if (ip
&& port_str
) {
691 purple_xfer_start(xfer
, -1, ip
, g_ascii_strtoull(port_str
,NULL
,10));
693 ft
->listener
= purple_network_listen_range(SIPE_FT_TCP_PORT_MIN
,
694 SIPE_FT_TCP_PORT_MAX
,
696 sipe_ft_listen_socket_created
,
699 raise_ft_error_and_cancel(xfer
,
700 _("Could not create listen socket"));
707 void sipe_ft_incoming_cancel(PurpleAccount
*account
, GSList
*body
)
709 gchar
*inv_cookie
= g_strdup(sipe_utils_nameval_find(body
, "Invitation-Cookie"));
711 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
712 PurpleXfer
*xfer
= g_hash_table_lookup(sip
->filetransfers
,inv_cookie
);
714 purple_xfer_cancel_remote(xfer
);
717 static void send_filetransfer_accept(PurpleXfer
* xfer
)
719 sipe_file_transfer
* ft
= xfer
->data
;
720 struct sip_dialog
*dialog
= ft
->dialog
;
722 gchar
*b64_encryption_key
= purple_base64_encode(ft
->encryption_key
,24);
723 gchar
*b64_hash_key
= purple_base64_encode(ft
->hash_key
,24);
725 gchar
*body
= g_strdup_printf("Invitation-Command: ACCEPT\r\n"
726 "Request-Data: IP-Address:\r\n"
727 "Invitation-Cookie: %s\r\n"
728 "Encryption-Key: %s\r\n"
730 /*"IP-Address: %s\r\n"
733 "Auth-Cookie: 11111111\r\n"
734 "Sender-Connect: TRUE\r\n"*/,
735 ft
->invitation_cookie
,
738 /*,purple_network_get_my_ip(-1)*/
741 send_sip_request(ft
->sip
->gc
, "MESSAGE", dialog
->with
, dialog
->with
,
742 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
746 g_free(b64_encryption_key
);
747 g_free(b64_hash_key
);
750 static void send_filetransfer_cancel(PurpleXfer
* xfer
) {
751 sipe_file_transfer
* ft
= xfer
->data
;
752 struct sip_dialog
* dialog
= ft
->dialog
;
754 gchar
*body
= g_strdup_printf("Invitation-Command: CANCEL\r\n"
755 "Invitation-Cookie: %s\r\n"
756 "Cancel-Code: REJECT\r\n",
757 ft
->invitation_cookie
);
759 send_sip_request(ft
->sip
->gc
, "MESSAGE", dialog
->with
, dialog
->with
,
760 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
767 do_read(PurpleXfer
*xfer
, guchar
*buf
, size_t len
)
769 ssize_t bytes_read
= read(xfer
->fd
, buf
, len
);
770 if (bytes_read
== 0) {
771 // Sender canceled transfer before it was finished
773 } else if (bytes_read
== -1) {
783 read_fully(PurpleXfer
*xfer
, guchar
*buf
, size_t len
)
785 const gulong READ_TIMEOUT
= 10000000;
786 gulong time_spent
= 0;
789 ssize_t bytes_read
= do_read(xfer
, buf
, len
);
790 if (bytes_read
== 0) {
792 time_spent
+= 100000;
793 } else if (bytes_read
< 0 || time_spent
> READ_TIMEOUT
) {
804 static gssize
read_line(PurpleXfer
*xfer
, gchar
*buffer
, gssize size
)
808 memset(buffer
,0,size
);
810 if (!read_fully(xfer
, (guchar
*) buffer
+ pos
, 1))
812 } while (buffer
[pos
] != '\n' && ++pos
!= (size
- 1));
814 if (pos
== (size
- 1) && buffer
[pos
- 1] != '\n') {
822 static void sipe_cipher_context_init(PurpleCipherContext
**rc4_context
, const guchar
*enc_key
)
825 * Decryption of file from SIPE file transfer
828 * 1.) SHA1-Key = SHA1sum (Encryption-Key); Do SHA1 digest from Encryption-Key, return 20 bytes SHA1-Key.
829 * 2.) Decrypt-Data = RC4 (Encrypt-Data, substr(SHA1-Key, 0, 15)); Decryption of encrypted data, used 16 bytes SHA1-Key;
832 PurpleCipherContext
*sha1_context
;
836 sha1_context
= purple_cipher_context_new_by_name("sha1", NULL
);
837 purple_cipher_context_append(sha1_context
, enc_key
, SIPE_FT_KEY_LENGTH
);
838 purple_cipher_context_digest(sha1_context
, sizeof(k2
), k2
, NULL
);
839 purple_cipher_context_destroy(sha1_context
);
841 /* 2.) RC4 decryption */
842 *rc4_context
= purple_cipher_context_new_by_name("rc4", NULL
);
843 purple_cipher_context_set_option(*rc4_context
, "key_len", (gpointer
)0x10); // only 16 chars key used
844 purple_cipher_context_set_key(*rc4_context
, k2
);
848 static void sipe_hmac_context_init(PurpleCipherContext
**hmac_context
, const guchar
*hash_key
)
854 * 1.) SHA1-Key = SHA1sum (Hash-Key); Do SHA1 digest from Hash-Key, return 20 bytes SHA1-Key.
855 * 2.) MAC = HMAC_SHA1 (Decrypt-Data, substr(HMAC-Key,0,15)); Digest of decrypted file and SHA1-Key (used again only 16 bytes)
858 PurpleCipherContext
*sha1_context
;
862 sha1_context
= purple_cipher_context_new_by_name("sha1", NULL
);
863 purple_cipher_context_append(sha1_context
, hash_key
, SIPE_FT_KEY_LENGTH
);
864 purple_cipher_context_digest(sha1_context
, sizeof(k2
), k2
, NULL
);
865 purple_cipher_context_destroy(sha1_context
);
867 /* 2.) HMAC (initialization only) */
868 *hmac_context
= purple_cipher_context_new_by_name("hmac", NULL
);
869 purple_cipher_context_set_option(*hmac_context
, "hash", "sha1");
870 purple_cipher_context_set_key_with_len(*hmac_context
, k2
, 16);
873 static gchar
* sipe_hmac_finalize(PurpleCipherContext
*hmac_context
)
875 guchar hmac_digest
[20];
877 /* MAC = Digest of decrypted file and SHA1-Key (used again only 16 bytes) */
878 purple_cipher_context_digest(hmac_context
, sizeof(hmac_digest
), hmac_digest
, NULL
);
880 return purple_base64_encode(hmac_digest
, sizeof (hmac_digest
));
883 static void generate_key(guchar
*buffer
, gsize size
)
886 for (i
= 0; i
!= size
; ++i
)
890 static void set_socket_nonblock(int fd
, gboolean state
)
892 int flags
= fcntl(fd
, F_GETFL
, 0);
897 fcntl(fd
, F_SETFL
, flags
| O_NONBLOCK
);
899 fcntl(fd
, F_SETFL
, flags
& ~O_NONBLOCK
);
902 void sipe_ft_send_file(PurpleConnection
*gc
, const char *who
, const char *file
)
904 PurpleXfer
*xfer
= sipe_ft_new_xfer(gc
, who
);
908 purple_xfer_request_accepted(xfer
, file
);
910 purple_xfer_request(xfer
);
914 PurpleXfer
* sipe_ft_new_xfer(PurpleConnection
*gc
, const char *who
)
916 PurpleXfer
*xfer
= NULL
;
918 if (PURPLE_CONNECTION_IS_VALID(gc
)) {
919 xfer
= purple_xfer_new(purple_connection_get_account(gc
),
920 PURPLE_XFER_SEND
, who
);
923 struct sipe_account_data
*sip
= gc
->proto_data
;
925 sipe_file_transfer
*ft
= g_new0(sipe_file_transfer
, 1);
926 ft
->invitation_cookie
= g_strdup_printf("%u", rand() % 1000000000);
931 purple_xfer_set_init_fnc(xfer
, sipe_ft_outgoing_init
);
932 purple_xfer_set_start_fnc(xfer
, sipe_ft_outgoing_start
);
933 purple_xfer_set_end_fnc(xfer
, sipe_ft_outgoing_stop
);
934 purple_xfer_set_request_denied_fnc(xfer
, sipe_ft_request_denied
);
935 purple_xfer_set_write_fnc(xfer
, sipe_ft_write
);
936 purple_xfer_set_cancel_send_fnc(xfer
, sipe_ft_free_xfer_struct
);
937 purple_xfer_set_cancel_recv_fnc(xfer
, sipe_ft_free_xfer_struct
);
945 void sipe_ft_client_connected(gpointer p_xfer
, gint listenfd
,
946 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
948 struct sockaddr_in saddr
;
949 socklen_t slen
= sizeof (saddr
);
951 int fd
= accept(listenfd
, (struct sockaddr
*)&saddr
, &slen
);
953 PurpleXfer
*xfer
= p_xfer
;
954 sipe_file_transfer
*ft
= xfer
->data
;
956 purple_input_remove(xfer
->watcher
);
962 raise_ft_socket_read_error_and_cancel(xfer
);
964 purple_xfer_start(xfer
, fd
, NULL
, 0);
969 void sipe_ft_listen_socket_created(int listenfd
, gpointer data
)
972 PurpleXfer
*xfer
= data
;
973 sipe_file_transfer
*ft
= xfer
->data
;
975 struct sockaddr_in addr
;
977 socklen_t socklen
= sizeof (addr
);
980 ft
->listenfd
= listenfd
;
982 getsockname(listenfd
, (struct sockaddr
*)&addr
, &socklen
);
984 xfer
->watcher
= purple_input_add(listenfd
, PURPLE_INPUT_READ
,
985 sipe_ft_client_connected
, xfer
);
987 ft
->auth_cookie
= rand() % 1000000000;
989 body
= g_strdup_printf("Invitation-Command: ACCEPT\r\n"
990 "Invitation-Cookie: %s\r\n"
995 "Request-Data: IP-Address:\r\n",
996 ft
->invitation_cookie
,
997 sipe_ft_get_suitable_local_ip(listenfd
),
998 ntohs(addr
.sin_port
),
1002 struct sipe_account_data
*sip
= xfer
->account
->gc
->proto_data
;
1003 struct sip_session
*session
= sipe_session_find_or_add_im(sip
, xfer
->who
);
1004 ft
->dialog
= sipe_dialog_find(session
, xfer
->who
);
1008 send_sip_request(ft
->sip
->gc
, "MESSAGE", ft
->dialog
->with
, ft
->dialog
->with
,
1009 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
1010 body
, ft
->dialog
, NULL
);
1016 * Calling sizeof(struct ifreq) isn't always correct on
1017 * Mac OS X (and maybe others).
1019 #ifdef _SIZEOF_ADDR_IFREQ
1020 # define HX_SIZE_OF_IFREQ(a) _SIZEOF_ADDR_IFREQ(a)
1022 # define HX_SIZE_OF_IFREQ(a) sizeof(a)
1026 * Returns local IP address suitable for connection.
1028 * purple_network_get_my_ip() will not do this, because it might return an
1029 * address within 169.254.x.x range that was assigned to interface disconnected
1030 * from the network (when multiple network adapters are available). This is a
1031 * copy-paste from libpurple's network.c, only change is that link local addresses
1034 * Maybe this should be fixed in libpurple or some better solution found.
1037 const char * sipe_ft_get_suitable_local_ip(int fd
)
1039 int source
= (fd
>= 0) ? fd
: socket(PF_INET
,SOCK_STREAM
, 0);
1046 guint32 lhost
= htonl(127 * 256 * 256 * 256 + 1);
1047 guint32 llocal
= htonl((169 << 24) + (254 << 16));
1049 ifc
.ifc_len
= sizeof(buffer
);
1050 ifc
.ifc_req
= (struct ifreq
*)buffer
;
1051 ioctl(source
, SIOCGIFCONF
, &ifc
);
1057 while (tmp
< buffer
+ ifc
.ifc_len
)
1059 struct ifreq
*ifr
= (struct ifreq
*)tmp
;
1060 tmp
+= HX_SIZE_OF_IFREQ(*ifr
);
1062 if (ifr
->ifr_addr
.sa_family
== AF_INET
)
1064 struct sockaddr_in
*sinptr
= (struct sockaddr_in
*)&ifr
->ifr_addr
;
1065 if (sinptr
->sin_addr
.s_addr
!= lhost
1066 && (sinptr
->sin_addr
.s_addr
& htonl(0xFFFF0000)) != llocal
)
1068 long unsigned int add
= ntohl(sinptr
->sin_addr
.s_addr
);
1069 g_snprintf(ip
, 16, "%lu.%lu.%lu.%lu",
1070 ((add
>> 24) & 255),
1071 ((add
>> 16) & 255),
1084 GSList
* sipe_ft_parse_msg_body(const gchar
*body
)
1086 GSList
*list
= NULL
;
1087 gchar
**lines
= g_strsplit(body
, "\r\n", 0);
1088 if (sipe_utils_parse_lines(&list
, lines
) == FALSE
) {
1089 sipe_utils_nameval_free(list
);