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"
45 #include <sys/types.h>
46 #include <sys/socket.h>
47 #include <sys/ioctl.h>
48 #include <netinet/in.h>
50 #include <arpa/inet.h>
53 #define SIPE_FT_KEY_LENGTH 24
54 #define SIPE_FT_CHUNK_HEADER_LENGTH 3
57 * DO NOT CHANGE THE FOLLOWING CONSTANTS!!!
59 * It seems that Microsoft Office Communicator client will accept
60 * file transfer invitations *only* within this port range!
62 * If a firewall is active on your system you need to open these ports if
63 * you want to *send* files to other users. Receiving files uses an ougoing
64 * connection and should therefore automatically penetrate your firewall.
66 #define SIPE_FT_TCP_PORT_MIN 6891
67 #define SIPE_FT_TCP_PORT_MAX 6901
69 struct _sipe_file_transfer
{
70 guchar encryption_key
[SIPE_FT_KEY_LENGTH
];
71 guchar hash_key
[SIPE_FT_KEY_LENGTH
];
72 gchar
*invitation_cookie
;
74 struct sipe_account_data
*sip
;
75 struct sip_dialog
*dialog
;
76 PurpleCipherContext
*cipher_context
;
77 PurpleCipherContext
*hmac_context
;
79 PurpleNetworkListenData
*listener
;
82 gsize bytes_remaining_chunk
;
83 guchar
* encrypted_outbuf
;
87 typedef struct _sipe_file_transfer sipe_file_transfer
;
89 static void send_filetransfer_accept(PurpleXfer
* xfer
);
90 static void send_filetransfer_cancel(PurpleXfer
* xfer
);
91 static ssize_t
do_read(PurpleXfer
*xfer
, guchar
*buf
, size_t len
);
92 static gboolean
read_fully(PurpleXfer
*xfer
, guchar
*buf
, size_t len
);
93 static gssize
read_line(PurpleXfer
*xfer
, gchar
*buffer
, gssize size
);
94 static void sipe_cipher_context_init(PurpleCipherContext
**rc4_context
, const guchar
*enc_key
);
95 static void sipe_hmac_context_init(PurpleCipherContext
**hmac_context
, const guchar
*hash_key
);
96 static gchar
*sipe_hmac_finalize(PurpleCipherContext
*hmac_context
);
97 static void generate_key(guchar
*buffer
, gsize size
);
98 static void set_socket_nonblock(int fd
, gboolean state
);
99 static void sipe_ft_listen_socket_created(int listenfd
, gpointer data
);
100 static const char * sipe_ft_get_suitable_local_ip(int fd
);
102 //******************************************************************************
103 // I/O operations for PurpleXfer structure
104 //******************************************************************************
107 sipe_ft_incoming_init(PurpleXfer
*xfer
)
109 send_filetransfer_accept(xfer
);
113 sipe_ft_free_xfer_struct(PurpleXfer
*xfer
)
115 sipe_file_transfer
*ft
= xfer
->data
;
117 struct sipe_account_data
*sip
= xfer
->account
->gc
->proto_data
;
119 g_hash_table_remove(sip
->filetransfers
,ft
->invitation_cookie
);
122 purple_input_remove(xfer
->watcher
);
125 if (ft
->listenfd
>= 0) {
126 purple_debug_info("sipe", "sipe_ft_free_xfer_struct: closing listening socket %d\n", ft
->listenfd
);
130 purple_network_listen_cancel(ft
->listener
);
131 if (ft
->cipher_context
)
132 purple_cipher_context_destroy(ft
->cipher_context
);
134 if (ft
->hmac_context
)
135 purple_cipher_context_destroy(ft
->hmac_context
);
137 g_free(ft
->encrypted_outbuf
);
138 g_free(ft
->invitation_cookie
);
145 sipe_ft_request_denied(PurpleXfer
*xfer
)
147 if (xfer
->type
== PURPLE_XFER_RECEIVE
)
148 send_filetransfer_cancel(xfer
);
149 sipe_ft_free_xfer_struct(xfer
);
153 void raise_ft_error(PurpleXfer
*xfer
, const char *errmsg
)
155 purple_xfer_error(purple_xfer_get_type(xfer
),
156 xfer
->account
, xfer
->who
,
161 void raise_ft_strerror(PurpleXfer
*xfer
, const char *errmsg
)
163 gchar
*tmp
= g_strdup_printf("%s: %s", errmsg
, strerror(errno
));
164 raise_ft_error(xfer
, tmp
);
169 void raise_ft_error_and_cancel(PurpleXfer
*xfer
, const char *errmsg
)
171 raise_ft_error(xfer
, errmsg
);
172 purple_xfer_cancel_local(xfer
);
176 void raise_ft_socket_read_error_and_cancel(PurpleXfer
*xfer
)
178 raise_ft_error_and_cancel(xfer
, _("Socket read failed"));
182 void raise_ft_socket_write_error_and_cancel(PurpleXfer
*xfer
)
184 raise_ft_error_and_cancel(xfer
, _("Socket write failed"));
188 sipe_ft_incoming_start(PurpleXfer
*xfer
)
190 sipe_file_transfer
*ft
;
191 static const gchar VER
[] = "VER MSN_SECURE_FTP\r\n";
192 static const gchar TFR
[] = "TFR\r\n";
193 const gsize BUFFER_SIZE
= 50;
194 gchar buf
[BUFFER_SIZE
];
195 struct sipe_account_data
*sip
;
197 const gsize FILE_SIZE_OFFSET
= 4;
202 if (write(xfer
->fd
,VER
,strlen(VER
)) == -1) {
203 raise_ft_socket_write_error_and_cancel(xfer
);
206 if (read_line(xfer
, buf
, BUFFER_SIZE
) < 0) {
207 raise_ft_socket_read_error_and_cancel(xfer
);
211 sip
= xfer
->account
->gc
->proto_data
;
213 request
= g_strdup_printf("USR %s %u\r\n", sip
->username
, ft
->auth_cookie
);
214 if (write(xfer
->fd
,request
,strlen(request
)) == -1) {
215 raise_ft_socket_write_error_and_cancel(xfer
);
221 if (read_line(xfer
, buf
, BUFFER_SIZE
) < 0) {
222 raise_ft_socket_read_error_and_cancel(xfer
);
226 file_size
= g_ascii_strtoull(buf
+ FILE_SIZE_OFFSET
,NULL
,10);
227 if (file_size
!= xfer
->size
) {
228 raise_ft_error_and_cancel(xfer
,
229 _("File size is different from the advertised value."));
233 if (write(xfer
->fd
,TFR
,strlen(TFR
)) == -1) {
234 raise_ft_socket_write_error_and_cancel(xfer
);
238 ft
->bytes_remaining_chunk
= 0;
240 sipe_cipher_context_init(&ft
->cipher_context
, ft
->encryption_key
);
241 sipe_hmac_context_init(&ft
->hmac_context
, ft
->hash_key
);
245 sipe_ft_incoming_stop(PurpleXfer
*xfer
)
247 static const gchar BYE
[] = "BYE 16777989\r\n";
248 gsize BUFFER_SIZE
= 50;
249 char buffer
[BUFFER_SIZE
];
250 const gssize MAC_OFFSET
= 4;
251 const gssize CRLF_LEN
= 2;
253 sipe_file_transfer
*ft
;
257 if (write(xfer
->fd
,BYE
,strlen(BYE
)) == -1) {
258 raise_ft_socket_write_error_and_cancel(xfer
);
262 macLen
= read_line(xfer
, buffer
, BUFFER_SIZE
);
265 raise_ft_socket_read_error_and_cancel(xfer
);
267 } else if (macLen
< (MAC_OFFSET
+ CRLF_LEN
)) {
268 raise_ft_error_and_cancel(xfer
, _("Received MAC is corrupted"));
274 mac
= g_strndup(buffer
+ MAC_OFFSET
, macLen
- MAC_OFFSET
- CRLF_LEN
);
275 mac1
= sipe_hmac_finalize(ft
->hmac_context
);
276 if (!sipe_strequal(mac
, mac1
)) {
277 unlink(xfer
->local_filename
);
278 raise_ft_error_and_cancel(xfer
,
279 _("Received file is corrupted"));
284 sipe_ft_free_xfer_struct(xfer
);
288 sipe_ft_read(guchar
**buffer
, PurpleXfer
*xfer
)
293 sipe_file_transfer
*ft
= xfer
->data
;
295 if (ft
->bytes_remaining_chunk
== 0) {
296 guchar hdr_buf
[SIPE_FT_CHUNK_HEADER_LENGTH
];
298 /* read chunk header */
299 if (!read_fully(xfer
, hdr_buf
, sizeof(hdr_buf
))) {
300 raise_ft_strerror(xfer
, _("Socket read failed"));
304 /* chunk header format:
306 * 0: 00 unknown (always zero?)
307 * 1: LL chunk size in bytes (low byte)
308 * 2: HH chunk size in bytes (high byte)
310 * Convert size from little endian to host order
312 ft
->bytes_remaining_chunk
= hdr_buf
[1] + (hdr_buf
[2] << 8);
315 bytes_to_read
= MIN(purple_xfer_get_bytes_remaining(xfer
),
316 xfer
->current_buffer_size
);
317 bytes_to_read
= MIN(bytes_to_read
, ft
->bytes_remaining_chunk
);
319 *buffer
= g_malloc(bytes_to_read
);
321 raise_ft_error(xfer
, _("Out of memory"));
322 purple_debug_error("sipe", "sipe_ft_read: can't allocate %" G_GSIZE_FORMAT
" bytes for receive buffer\n",
327 bytes_read
= do_read(xfer
, *buffer
, bytes_to_read
);
328 if (bytes_read
< 0) {
329 raise_ft_strerror(xfer
, _("Socket read failed"));
333 if (bytes_read
> 0) {
334 guchar
*decrypted
= g_malloc(bytes_read
);
337 raise_ft_error(xfer
, _("Out of memory"));
338 purple_debug_error("sipe", "sipe_ft_read: can't allocate %" G_GSIZE_FORMAT
" bytes for decryption buffer\n",
344 purple_cipher_context_encrypt(ft
->cipher_context
, *buffer
, bytes_read
, decrypted
, NULL
);
348 purple_cipher_context_append(ft
->hmac_context
, decrypted
, bytes_read
);
350 ft
->bytes_remaining_chunk
-= bytes_read
;
357 sipe_ft_write(const guchar
*buffer
, size_t size
, PurpleXfer
*xfer
)
359 ssize_t bytes_written
;
360 sipe_file_transfer
*ft
= xfer
->data
;
362 /* When sending data via server with ForeFront installed, block bigger than
363 * this default causes ending of transmission. Hard limit block to this value
364 * when libpurple sends us more data. */
365 const gsize DEFAULT_BLOCK_SIZE
= 2045;
366 if (size
> DEFAULT_BLOCK_SIZE
)
367 size
= DEFAULT_BLOCK_SIZE
;
369 if (ft
->bytes_remaining_chunk
== 0) {
371 guchar local_buf
[16];
372 guchar hdr_buf
[SIPE_FT_CHUNK_HEADER_LENGTH
];
374 memset(local_buf
, 0, sizeof local_buf
);
376 // Check if receiver did not cancel the transfer before it is finished
377 bytes_read
= read(xfer
->fd
,local_buf
,sizeof (local_buf
));
378 if (bytes_read
== -1 && errno
!= EAGAIN
) {
379 raise_ft_strerror(xfer
, _("Socket read failed"));
381 } else if (bytes_read
> 0
382 && (g_str_has_prefix((gchar
*)local_buf
,"CCL\r\n")
383 || g_str_has_prefix((gchar
*)local_buf
,"BYE 2164261682\r\n"))) {
387 if (ft
->outbuf_size
< size
) {
388 g_free(ft
->encrypted_outbuf
);
389 ft
->outbuf_size
= size
;
390 ft
->encrypted_outbuf
= g_malloc(ft
->outbuf_size
);
391 if (!ft
->encrypted_outbuf
) {
392 raise_ft_error(xfer
, _("Out of memory"));
393 purple_debug_error("sipe", "sipe_ft_write: can't allocate %" G_GSIZE_FORMAT
" bytes for send buffer\n",
399 ft
->bytes_remaining_chunk
= size
;
400 ft
->outbuf_ptr
= ft
->encrypted_outbuf
;
401 purple_cipher_context_encrypt(ft
->cipher_context
, buffer
, size
,
402 ft
->encrypted_outbuf
, NULL
);
403 purple_cipher_context_append(ft
->hmac_context
, buffer
, size
);
405 /* chunk header format:
407 * 0: 00 unknown (always zero?)
408 * 1: LL chunk size in bytes (low byte)
409 * 2: HH chunk size in bytes (high byte)
411 * Convert size from host order to little endian
414 hdr_buf
[1] = (ft
->bytes_remaining_chunk
& 0x00FF);
415 hdr_buf
[2] = (ft
->bytes_remaining_chunk
& 0xFF00) >> 8;
417 /* write chunk header */
418 if (write(xfer
->fd
, hdr_buf
, sizeof(hdr_buf
)) == -1) {
419 raise_ft_strerror(xfer
, _("Socket write failed"));
424 bytes_written
= write(xfer
->fd
, ft
->outbuf_ptr
, ft
->bytes_remaining_chunk
);
425 if (bytes_written
== -1) {
429 raise_ft_strerror(xfer
, _("Socket write failed"));
433 if (bytes_written
> 0) {
434 ft
->bytes_remaining_chunk
-= bytes_written
;
435 ft
->outbuf_ptr
+= bytes_written
;
438 if ((xfer
->bytes_remaining
- bytes_written
) == 0)
439 purple_xfer_set_completed(xfer
, TRUE
);
441 return bytes_written
;
445 sipe_ft_outgoing_init(PurpleXfer
*xfer
)
447 struct sip_dialog
*dialog
;
448 sipe_file_transfer
*ft
= xfer
->data
;
450 gchar
*body
= g_strdup_printf("Application-Name: File Transfer\r\n"
451 "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n"
452 "Invitation-Command: INVITE\r\n"
453 "Invitation-Cookie: %s\r\n"
454 "Application-File: %s\r\n"
455 "Application-FileSize: %lu\r\n"
456 //"Connectivity: N\r\n" TODO
457 "Encryption: R\r\n", // TODO: non encrypted file transfer support
458 ft
->invitation_cookie
,
459 purple_xfer_get_filename(xfer
),
460 (long unsigned) purple_xfer_get_size(xfer
));
462 struct sipe_account_data
*sip
= xfer
->account
->gc
->proto_data
;
463 struct sip_session
*session
= sipe_session_find_or_add_im(sip
, xfer
->who
);
465 g_hash_table_insert(sip
->filetransfers
,g_strdup(ft
->invitation_cookie
),xfer
);
468 sipe_session_enqueue_message(session
, body
, "text/x-msmsgsinvite");
470 dialog
= sipe_dialog_find(session
, xfer
->who
);
471 if (dialog
&& !dialog
->outgoing_invite
) {
473 sipe_im_process_queue(sip
, session
);
474 } else if (!dialog
|| !dialog
->outgoing_invite
) {
475 // Need to send the INVITE to get the outgoing dialog setup
476 sipe_invite(sip
, session
, xfer
->who
, body
, "text/x-msmsgsinvite", NULL
, FALSE
);
483 sipe_ft_outgoing_start(PurpleXfer
*xfer
)
485 sipe_file_transfer
*ft
;
486 static const gchar VER
[] = "VER MSN_SECURE_FTP\r\n";
487 const gsize BUFFER_SIZE
= 50;
488 gchar buf
[BUFFER_SIZE
];
490 unsigned auth_cookie_received
;
491 gboolean users_match
;
493 ssize_t bytes_written
;
495 set_socket_nonblock(xfer
->fd
, TRUE
);
499 if (read_line(xfer
, buf
, BUFFER_SIZE
) < 0) {
500 raise_ft_socket_read_error_and_cancel(xfer
);
504 if (!sipe_strequal(buf
,VER
)) {
505 raise_ft_error_and_cancel(xfer
,_("File transfer initialization failed."));
506 purple_debug_info("sipe","File transfer VER string incorrect, received: %s expected: %s",
511 if (write(xfer
->fd
,VER
,strlen(VER
)) == -1) {
512 raise_ft_socket_write_error_and_cancel(xfer
);
516 if (read_line(xfer
, buf
, BUFFER_SIZE
) < 0) {
517 raise_ft_socket_read_error_and_cancel(xfer
);
521 parts
= g_strsplit(buf
, " ", 3);
523 auth_cookie_received
= g_ascii_strtoull(parts
[2],NULL
,10);
525 // xfer->who has 'sip:' prefix, skip these four characters
526 users_match
= !g_ascii_strcasecmp(parts
[1], (xfer
->who
+ 4));
529 purple_debug_info("sipe","File transfer authentication: %s Expected: USR %s %u\n",
530 buf
, xfer
->who
+ 4, ft
->auth_cookie
);
532 if (!users_match
|| (ft
->auth_cookie
!= auth_cookie_received
)) {
533 raise_ft_error_and_cancel(xfer
,
534 _("File transfer authentication failed."));
538 tmp
= g_strdup_printf("FIL %lu\r\n",(long unsigned) xfer
->size
);
539 bytes_written
= write(xfer
->fd
, tmp
, strlen(tmp
));
542 if (bytes_written
== -1) {
543 raise_ft_socket_write_error_and_cancel(xfer
);
548 if (read_line(xfer
,buf
,BUFFER_SIZE
) < 0) {
549 raise_ft_socket_read_error_and_cancel(xfer
);
553 ft
->bytes_remaining_chunk
= 0;
555 sipe_cipher_context_init(&ft
->cipher_context
, ft
->encryption_key
);
556 sipe_hmac_context_init(&ft
->hmac_context
, ft
->hash_key
);
560 sipe_ft_outgoing_stop(PurpleXfer
*xfer
)
562 sipe_file_transfer
*ft
= xfer
->data
;
563 gsize BUFFER_SIZE
= 50;
564 char buffer
[BUFFER_SIZE
];
569 if (read_line(xfer
, buffer
, BUFFER_SIZE
) < 0) {
570 raise_ft_socket_read_error_and_cancel(xfer
);
574 mac
= sipe_hmac_finalize(ft
->hmac_context
);
575 g_sprintf(buffer
, "MAC %s \r\n", mac
);
578 mac_strlen
= strlen(buffer
);
579 // There must be this zero byte between mac and \r\n
580 buffer
[mac_strlen
- 3] = 0;
582 if (write(xfer
->fd
,buffer
,mac_strlen
) == -1) {
583 raise_ft_socket_write_error_and_cancel(xfer
);
587 sipe_ft_free_xfer_struct(xfer
);
590 //******************************************************************************
592 void sipe_ft_incoming_transfer(PurpleAccount
*account
, struct sipmsg
*msg
, const GSList
*body
)
595 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
596 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
597 struct sip_session
*session
= sipe_session_find_chat_by_callid(sip
, callid
);
599 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
600 session
= sipe_session_find_im(sip
, from
);
605 purple_debug_error("sipe", "sipe_ft_incoming_transfer: can't find session for remote party\n");
609 xfer
= purple_xfer_new(account
, PURPLE_XFER_RECEIVE
, session
->with
);
613 sipe_file_transfer
*ft
= g_new0(sipe_file_transfer
, 1);
614 ft
->invitation_cookie
= g_strdup(sipe_utils_nameval_find(body
, "Invitation-Cookie"));
616 ft
->dialog
= sipe_dialog_find(session
, session
->with
);
618 generate_key(ft
->encryption_key
, SIPE_FT_KEY_LENGTH
);
619 generate_key(ft
->hash_key
, SIPE_FT_KEY_LENGTH
);
622 purple_xfer_set_filename(xfer
, sipe_utils_nameval_find(body
, "Application-File"));
624 file_size
= g_ascii_strtoull(sipe_utils_nameval_find(body
, "Application-FileSize"),NULL
,10);
625 purple_xfer_set_size(xfer
, file_size
);
627 purple_xfer_set_init_fnc(xfer
, sipe_ft_incoming_init
);
628 purple_xfer_set_start_fnc(xfer
,sipe_ft_incoming_start
);
629 purple_xfer_set_end_fnc(xfer
,sipe_ft_incoming_stop
);
630 purple_xfer_set_request_denied_fnc(xfer
, sipe_ft_request_denied
);
631 purple_xfer_set_read_fnc(xfer
,sipe_ft_read
);
632 purple_xfer_set_cancel_send_fnc(xfer
,sipe_ft_free_xfer_struct
);
633 purple_xfer_set_cancel_recv_fnc(xfer
,sipe_ft_free_xfer_struct
);
635 g_hash_table_insert(sip
->filetransfers
,g_strdup(ft
->invitation_cookie
),xfer
);
637 purple_xfer_request(xfer
);
641 void sipe_ft_incoming_accept(PurpleAccount
*account
, const GSList
*body
)
643 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
644 const gchar
*inv_cookie
= sipe_utils_nameval_find(body
, "Invitation-Cookie");
645 PurpleXfer
*xfer
= g_hash_table_lookup(sip
->filetransfers
,inv_cookie
);
648 const gchar
*ip
= sipe_utils_nameval_find(body
, "IP-Address");
649 const gchar
*port_str
= sipe_utils_nameval_find(body
, "Port");
650 const gchar
*auth_cookie
= sipe_utils_nameval_find(body
, "AuthCookie");
651 const gchar
*enc_key_b64
= sipe_utils_nameval_find(body
, "Encryption-Key");
652 const gchar
*hash_key_b64
= sipe_utils_nameval_find(body
, "Hash-Key");
654 sipe_file_transfer
*ft
= xfer
->data
;
657 ft
->auth_cookie
= g_ascii_strtoull(auth_cookie
,NULL
,10);
660 guchar
*enc_key
= purple_base64_decode(enc_key_b64
, &ret_len
);
661 if (ret_len
== SIPE_FT_KEY_LENGTH
) {
662 memcpy(ft
->encryption_key
,enc_key
,SIPE_FT_KEY_LENGTH
);
664 raise_ft_error_and_cancel(xfer
,
665 _("Received encryption key has wrong size."));
673 guchar
*hash_key
= purple_base64_decode(hash_key_b64
, &ret_len
);
674 if (ret_len
== SIPE_FT_KEY_LENGTH
) {
675 memcpy(ft
->hash_key
,hash_key
,SIPE_FT_KEY_LENGTH
);
677 raise_ft_error_and_cancel(xfer
,
678 _("Received hash key has wrong size."));
685 if (ip
&& port_str
) {
686 purple_xfer_start(xfer
, -1, ip
, g_ascii_strtoull(port_str
,NULL
,10));
688 ft
->listener
= purple_network_listen_range(SIPE_FT_TCP_PORT_MIN
,
689 SIPE_FT_TCP_PORT_MAX
,
691 sipe_ft_listen_socket_created
,
694 raise_ft_error_and_cancel(xfer
,
695 _("Could not create listen socket"));
702 void sipe_ft_incoming_cancel(PurpleAccount
*account
, GSList
*body
)
704 gchar
*inv_cookie
= g_strdup(sipe_utils_nameval_find(body
, "Invitation-Cookie"));
706 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
707 PurpleXfer
*xfer
= g_hash_table_lookup(sip
->filetransfers
,inv_cookie
);
709 purple_xfer_cancel_remote(xfer
);
712 static void send_filetransfer_accept(PurpleXfer
* xfer
)
714 sipe_file_transfer
* ft
= xfer
->data
;
715 struct sip_dialog
*dialog
= ft
->dialog
;
717 gchar
*b64_encryption_key
= purple_base64_encode(ft
->encryption_key
,24);
718 gchar
*b64_hash_key
= purple_base64_encode(ft
->hash_key
,24);
720 gchar
*body
= g_strdup_printf("Invitation-Command: ACCEPT\r\n"
721 "Request-Data: IP-Address:\r\n"
722 "Invitation-Cookie: %s\r\n"
723 "Encryption-Key: %s\r\n"
725 /*"IP-Address: %s\r\n"
728 "Auth-Cookie: 11111111\r\n"
729 "Sender-Connect: TRUE\r\n"*/,
730 ft
->invitation_cookie
,
733 /*,purple_network_get_my_ip(-1)*/
736 send_sip_request(ft
->sip
->gc
, "MESSAGE", dialog
->with
, dialog
->with
,
737 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
741 g_free(b64_encryption_key
);
742 g_free(b64_hash_key
);
745 static void send_filetransfer_cancel(PurpleXfer
* xfer
) {
746 sipe_file_transfer
* ft
= xfer
->data
;
747 struct sip_dialog
* dialog
= ft
->dialog
;
749 gchar
*body
= g_strdup_printf("Invitation-Command: CANCEL\r\n"
750 "Invitation-Cookie: %s\r\n"
751 "Cancel-Code: REJECT\r\n",
752 ft
->invitation_cookie
);
754 send_sip_request(ft
->sip
->gc
, "MESSAGE", dialog
->with
, dialog
->with
,
755 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
762 do_read(PurpleXfer
*xfer
, guchar
*buf
, size_t len
)
764 ssize_t bytes_read
= read(xfer
->fd
, buf
, len
);
765 if (bytes_read
== 0) {
766 // Sender canceled transfer before it was finished
768 } else if (bytes_read
== -1) {
778 read_fully(PurpleXfer
*xfer
, guchar
*buf
, size_t len
)
780 const gulong READ_TIMEOUT
= 10000000;
781 gulong time_spent
= 0;
784 ssize_t bytes_read
= do_read(xfer
, buf
, len
);
785 if (bytes_read
== 0) {
787 time_spent
+= 100000;
788 } else if (bytes_read
< 0 || time_spent
> READ_TIMEOUT
) {
799 static gssize
read_line(PurpleXfer
*xfer
, gchar
*buffer
, gssize size
)
803 memset(buffer
,0,size
);
805 if (!read_fully(xfer
, (guchar
*) buffer
+ pos
, 1))
807 } while (buffer
[pos
] != '\n' && ++pos
!= (size
- 1));
809 if (pos
== (size
- 1) && buffer
[pos
- 1] != '\n') {
817 static void sipe_cipher_context_init(PurpleCipherContext
**rc4_context
, const guchar
*enc_key
)
820 * Decryption of file from SIPE file transfer
823 * 1.) SHA1-Key = SHA1sum (Encryption-Key); Do SHA1 digest from Encryption-Key, return 20 bytes SHA1-Key.
824 * 2.) Decrypt-Data = RC4 (Encrypt-Data, substr(SHA1-Key, 0, 15)); Decryption of encrypted data, used 16 bytes SHA1-Key;
827 PurpleCipherContext
*sha1_context
;
831 sha1_context
= purple_cipher_context_new_by_name("sha1", NULL
);
832 purple_cipher_context_append(sha1_context
, enc_key
, SIPE_FT_KEY_LENGTH
);
833 purple_cipher_context_digest(sha1_context
, sizeof(k2
), k2
, NULL
);
834 purple_cipher_context_destroy(sha1_context
);
836 /* 2.) RC4 decryption */
837 *rc4_context
= purple_cipher_context_new_by_name("rc4", NULL
);
838 purple_cipher_context_set_option(*rc4_context
, "key_len", (gpointer
)0x10); // only 16 chars key used
839 purple_cipher_context_set_key(*rc4_context
, k2
);
843 static void sipe_hmac_context_init(PurpleCipherContext
**hmac_context
, const guchar
*hash_key
)
849 * 1.) SHA1-Key = SHA1sum (Hash-Key); Do SHA1 digest from Hash-Key, return 20 bytes SHA1-Key.
850 * 2.) MAC = HMAC_SHA1 (Decrypt-Data, substr(HMAC-Key,0,15)); Digest of decrypted file and SHA1-Key (used again only 16 bytes)
853 PurpleCipherContext
*sha1_context
;
857 sha1_context
= purple_cipher_context_new_by_name("sha1", NULL
);
858 purple_cipher_context_append(sha1_context
, hash_key
, SIPE_FT_KEY_LENGTH
);
859 purple_cipher_context_digest(sha1_context
, sizeof(k2
), k2
, NULL
);
860 purple_cipher_context_destroy(sha1_context
);
862 /* 2.) HMAC (initialization only) */
863 *hmac_context
= purple_cipher_context_new_by_name("hmac", NULL
);
864 purple_cipher_context_set_option(*hmac_context
, "hash", "sha1");
865 purple_cipher_context_set_key_with_len(*hmac_context
, k2
, 16);
868 static gchar
* sipe_hmac_finalize(PurpleCipherContext
*hmac_context
)
870 guchar hmac_digest
[20];
872 /* MAC = Digest of decrypted file and SHA1-Key (used again only 16 bytes) */
873 purple_cipher_context_digest(hmac_context
, sizeof(hmac_digest
), hmac_digest
, NULL
);
875 return purple_base64_encode(hmac_digest
, sizeof (hmac_digest
));
878 static void generate_key(guchar
*buffer
, gsize size
)
881 for (i
= 0; i
!= size
; ++i
)
885 static void set_socket_nonblock(int fd
, gboolean state
)
887 int flags
= fcntl(fd
, F_GETFL
, 0);
892 fcntl(fd
, F_SETFL
, flags
| O_NONBLOCK
);
894 fcntl(fd
, F_SETFL
, flags
& ~O_NONBLOCK
);
897 void sipe_ft_send_file(PurpleConnection
*gc
, const char *who
, const char *file
)
899 PurpleXfer
*xfer
= sipe_ft_new_xfer(gc
, who
);
903 purple_xfer_request_accepted(xfer
, file
);
905 purple_xfer_request(xfer
);
909 PurpleXfer
* sipe_ft_new_xfer(PurpleConnection
*gc
, const char *who
)
911 PurpleXfer
*xfer
= NULL
;
913 if (PURPLE_CONNECTION_IS_VALID(gc
)) {
914 xfer
= purple_xfer_new(purple_connection_get_account(gc
),
915 PURPLE_XFER_SEND
, who
);
918 struct sipe_account_data
*sip
= gc
->proto_data
;
920 sipe_file_transfer
*ft
= g_new0(sipe_file_transfer
, 1);
921 ft
->invitation_cookie
= g_strdup_printf("%u", rand() % 1000000000);
926 purple_xfer_set_init_fnc(xfer
, sipe_ft_outgoing_init
);
927 purple_xfer_set_start_fnc(xfer
, sipe_ft_outgoing_start
);
928 purple_xfer_set_end_fnc(xfer
, sipe_ft_outgoing_stop
);
929 purple_xfer_set_request_denied_fnc(xfer
, sipe_ft_request_denied
);
930 purple_xfer_set_write_fnc(xfer
, sipe_ft_write
);
931 purple_xfer_set_cancel_send_fnc(xfer
, sipe_ft_free_xfer_struct
);
932 purple_xfer_set_cancel_recv_fnc(xfer
, sipe_ft_free_xfer_struct
);
940 void sipe_ft_client_connected(gpointer p_xfer
, gint listenfd
,
941 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
943 struct sockaddr_in saddr
;
944 socklen_t slen
= sizeof (saddr
);
946 int fd
= accept(listenfd
, (struct sockaddr
*)&saddr
, &slen
);
948 PurpleXfer
*xfer
= p_xfer
;
949 sipe_file_transfer
*ft
= xfer
->data
;
951 purple_input_remove(xfer
->watcher
);
957 raise_ft_socket_read_error_and_cancel(xfer
);
959 purple_xfer_start(xfer
, fd
, NULL
, 0);
964 void sipe_ft_listen_socket_created(int listenfd
, gpointer data
)
967 PurpleXfer
*xfer
= data
;
968 sipe_file_transfer
*ft
= xfer
->data
;
970 struct sockaddr_in addr
;
972 socklen_t socklen
= sizeof (addr
);
975 ft
->listenfd
= listenfd
;
977 getsockname(listenfd
, (struct sockaddr
*)&addr
, &socklen
);
979 xfer
->watcher
= purple_input_add(listenfd
, PURPLE_INPUT_READ
,
980 sipe_ft_client_connected
, xfer
);
982 ft
->auth_cookie
= rand() % 1000000000;
984 body
= g_strdup_printf("Invitation-Command: ACCEPT\r\n"
985 "Invitation-Cookie: %s\r\n"
990 "Request-Data: IP-Address:\r\n",
991 ft
->invitation_cookie
,
992 sipe_ft_get_suitable_local_ip(listenfd
),
993 ntohs(addr
.sin_port
),
997 struct sipe_account_data
*sip
= xfer
->account
->gc
->proto_data
;
998 struct sip_session
*session
= sipe_session_find_or_add_im(sip
, xfer
->who
);
999 ft
->dialog
= sipe_dialog_find(session
, xfer
->who
);
1003 send_sip_request(ft
->sip
->gc
, "MESSAGE", ft
->dialog
->with
, ft
->dialog
->with
,
1004 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
1005 body
, ft
->dialog
, NULL
);
1011 * Calling sizeof(struct ifreq) isn't always correct on
1012 * Mac OS X (and maybe others).
1014 #ifdef _SIZEOF_ADDR_IFREQ
1015 # define HX_SIZE_OF_IFREQ(a) _SIZEOF_ADDR_IFREQ(a)
1017 # define HX_SIZE_OF_IFREQ(a) sizeof(a)
1021 * Returns local IP address suitable for connection.
1023 * purple_network_get_my_ip() will not do this, because it might return an
1024 * address within 169.254.x.x range that was assigned to interface disconnected
1025 * from the network (when multiple network adapters are available). This is a
1026 * copy-paste from libpurple's network.c, only change is that link local addresses
1029 * Maybe this should be fixed in libpurple or some better solution found.
1032 const char * sipe_ft_get_suitable_local_ip(int fd
)
1034 int source
= (fd
>= 0) ? fd
: socket(PF_INET
,SOCK_STREAM
, 0);
1041 guint32 lhost
= htonl(127 * 256 * 256 * 256 + 1);
1042 guint32 llocal
= htonl((169 << 24) + (254 << 16));
1044 ifc
.ifc_len
= sizeof(buffer
);
1045 ifc
.ifc_req
= (struct ifreq
*)buffer
;
1046 ioctl(source
, SIOCGIFCONF
, &ifc
);
1052 while (tmp
< buffer
+ ifc
.ifc_len
)
1054 struct ifreq
*ifr
= (struct ifreq
*)tmp
;
1055 tmp
+= HX_SIZE_OF_IFREQ(*ifr
);
1057 if (ifr
->ifr_addr
.sa_family
== AF_INET
)
1059 struct sockaddr_in
*sinptr
= (struct sockaddr_in
*)&ifr
->ifr_addr
;
1060 if (sinptr
->sin_addr
.s_addr
!= lhost
1061 && (sinptr
->sin_addr
.s_addr
& htonl(0xFFFF0000)) != llocal
)
1063 long unsigned int add
= ntohl(sinptr
->sin_addr
.s_addr
);
1064 g_snprintf(ip
, 16, "%lu.%lu.%lu.%lu",
1065 ((add
>> 24) & 255),
1066 ((add
>> 16) & 255),
1079 GSList
* sipe_ft_parse_msg_body(const gchar
*body
)
1081 GSList
*list
= NULL
;
1082 gchar
**lines
= g_strsplit(body
, "\r\n", 0);
1083 if (sipe_utils_parse_lines(&list
, lines
) == FALSE
) {
1084 sipe_utils_nameval_free(list
);