configure: add check for sys/sockio.h
[siplcs.git] / src / core / sipe-ft.c
blob0b2a42aa49e99f29295b04532461d408a473c32f
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 <glib/gprintf.h>
33 #include "debug.h"
35 #include "sipe.h"
36 #include "sipe-ft.h"
37 #include "sipe-dialog.h"
38 #include "sipe-nls.h"
39 #include "sipe-session.h"
40 #include "sipe-utils.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 <sys/ioctl.h>
50 #include <netinet/in.h>
51 #include <net/if.h>
52 #include <arpa/inet.h>
53 #ifdef HAVE_SYS_SOCKIO_H
54 #include <sys/sockio.h> /* SIOCGIFCONF for Solaris */
55 #endif
56 #endif
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;
78 unsigned auth_cookie;
79 struct sipe_account_data *sip;
80 struct sip_dialog *dialog;
81 PurpleCipherContext *cipher_context;
82 PurpleCipherContext *hmac_context;
84 PurpleNetworkListenData *listener;
85 int listenfd;
87 gsize bytes_remaining_chunk;
88 guchar* encrypted_outbuf;
89 guchar* outbuf_ptr;
90 gsize outbuf_size;
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 //******************************************************************************
111 static void
112 sipe_ft_incoming_init(PurpleXfer *xfer)
114 send_filetransfer_accept(xfer);
117 static void
118 sipe_ft_free_xfer_struct(PurpleXfer *xfer)
120 sipe_file_transfer *ft = xfer->data;
121 if (ft) {
122 struct sipe_account_data *sip = xfer->account->gc->proto_data;
124 g_hash_table_remove(sip->filetransfers,ft->invitation_cookie);
126 if (xfer->watcher) {
127 purple_input_remove(xfer->watcher);
128 xfer->watcher = 0;
130 if (ft->listenfd >= 0) {
131 purple_debug_info("sipe", "sipe_ft_free_xfer_struct: closing listening socket %d\n", ft->listenfd);
132 close(ft->listenfd);
134 if (ft->listener)
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);
144 g_free(ft);
145 xfer->data = NULL;
149 static void
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);
157 static
158 void raise_ft_error(PurpleXfer *xfer, const char *errmsg)
160 purple_xfer_error(purple_xfer_get_type(xfer),
161 xfer->account, xfer->who,
162 errmsg);
165 static
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);
170 g_free(tmp);
173 static
174 void raise_ft_error_and_cancel(PurpleXfer *xfer, const char *errmsg)
176 raise_ft_error(xfer, errmsg);
177 purple_xfer_cancel_local(xfer);
180 static
181 void raise_ft_socket_read_error_and_cancel(PurpleXfer *xfer)
183 raise_ft_error_and_cancel(xfer, _("Socket read failed"));
186 static
187 void raise_ft_socket_write_error_and_cancel(PurpleXfer *xfer)
189 raise_ft_error_and_cancel(xfer, _("Socket write failed"));
192 static void
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;
201 gchar* request;
202 const gsize FILE_SIZE_OFFSET = 4;
203 gsize file_size;
205 ft = xfer->data;
207 if (write(xfer->fd,VER,strlen(VER)) == -1) {
208 raise_ft_socket_write_error_and_cancel(xfer);
209 return;
211 if (read_line(xfer, buf, BUFFER_SIZE) < 0) {
212 raise_ft_socket_read_error_and_cancel(xfer);
213 return;
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);
221 g_free(request);
222 return;
224 g_free(request);
226 if (read_line(xfer, buf, BUFFER_SIZE) < 0) {
227 raise_ft_socket_read_error_and_cancel(xfer);
228 return;
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."));
235 return;
238 if (write(xfer->fd,TFR,strlen(TFR)) == -1) {
239 raise_ft_socket_write_error_and_cancel(xfer);
240 return;
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);
249 static void
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;
257 gssize macLen;
258 sipe_file_transfer *ft;
259 gchar *mac;
260 gchar *mac1;
262 if (write(xfer->fd,BYE,strlen(BYE)) == -1) {
263 raise_ft_socket_write_error_and_cancel(xfer);
264 return;
267 macLen = read_line(xfer, buffer, BUFFER_SIZE);
269 if (macLen < 0) {
270 raise_ft_socket_read_error_and_cancel(xfer);
271 return;
272 } else if (macLen < (MAC_OFFSET + CRLF_LEN)) {
273 raise_ft_error_and_cancel(xfer, _("Received MAC is corrupted"));
274 return;
277 // Check MAC
278 ft = xfer->data;
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"));
286 g_free(mac1);
287 g_free(mac);
289 sipe_ft_free_xfer_struct(xfer);
292 static gssize
293 sipe_ft_read(guchar **buffer, PurpleXfer *xfer)
295 gsize bytes_to_read;
296 ssize_t bytes_read;
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"));
306 return -1;
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);
325 if (!*buffer) {
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",
328 bytes_to_read);
329 return -1;
332 bytes_read = do_read(xfer, *buffer, bytes_to_read);
333 if (bytes_read < 0) {
334 raise_ft_strerror(xfer, _("Socket read failed"));
335 return -1;
338 if (bytes_read > 0) {
339 guchar *decrypted = g_malloc(bytes_read);
341 if (!decrypted) {
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",
344 (gsize)bytes_read);
345 g_free(*buffer);
346 *buffer = NULL;
347 return -1;
349 purple_cipher_context_encrypt(ft->cipher_context, *buffer, bytes_read, decrypted, NULL);
350 g_free(*buffer);
351 *buffer = decrypted;
353 purple_cipher_context_append(ft->hmac_context, decrypted, bytes_read);
355 ft->bytes_remaining_chunk -= bytes_read;
358 return bytes_read;
361 static gssize
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) {
375 ssize_t bytes_read;
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"));
385 return -1;
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"))) {
389 return -1;
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",
399 ft->outbuf_size);
400 return -1;
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
418 hdr_buf[0] = 0;
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"));
425 return -1;
429 bytes_written = write(xfer->fd, ft->outbuf_ptr, ft->bytes_remaining_chunk);
430 if (bytes_written == -1) {
431 if (errno == EAGAIN)
432 bytes_written = 0;
433 else {
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;
449 static void
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);
472 // Queue the message
473 sipe_session_enqueue_message(session, body, "text/x-msmsgsinvite");
475 dialog = sipe_dialog_find(session, xfer->who);
476 if (dialog && !dialog->outgoing_invite) {
477 ft->dialog = dialog;
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);
484 g_free(body);
487 static void
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];
494 gchar** parts;
495 unsigned auth_cookie_received;
496 gboolean users_match;
497 gchar *tmp;
498 ssize_t bytes_written;
500 set_socket_nonblock(xfer->fd, TRUE);
502 ft = xfer->data;
504 if (read_line(xfer, buf, BUFFER_SIZE) < 0) {
505 raise_ft_socket_read_error_and_cancel(xfer);
506 return;
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",
512 buf, VER);
513 return;
516 if (write(xfer->fd,VER,strlen(VER)) == -1) {
517 raise_ft_socket_write_error_and_cancel(xfer);
518 return;
521 if (read_line(xfer, buf, BUFFER_SIZE) < 0) {
522 raise_ft_socket_read_error_and_cancel(xfer);
523 return;
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));
532 g_strfreev(parts);
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."));
540 return;
543 tmp = g_strdup_printf("FIL %lu\r\n",(long unsigned) xfer->size);
544 bytes_written = write(xfer->fd, tmp, strlen(tmp));
545 g_free(tmp);
547 if (bytes_written == -1) {
548 raise_ft_socket_write_error_and_cancel(xfer);
549 return;
552 // TFR
553 if (read_line(xfer,buf,BUFFER_SIZE) < 0) {
554 raise_ft_socket_read_error_and_cancel(xfer);
555 return;
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);
564 static void
565 sipe_ft_outgoing_stop(PurpleXfer *xfer)
567 sipe_file_transfer *ft = xfer->data;
568 gsize BUFFER_SIZE = 50;
569 char buffer[BUFFER_SIZE];
570 gchar *mac;
571 gsize mac_strlen;
573 // BYE
574 if (read_line(xfer, buffer, BUFFER_SIZE) < 0) {
575 raise_ft_socket_read_error_and_cancel(xfer);
576 return;
579 mac = sipe_hmac_finalize(ft->hmac_context);
580 g_sprintf(buffer, "MAC %s \r\n", mac);
581 g_free(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);
589 return;
592 sipe_ft_free_xfer_struct(xfer);
595 //******************************************************************************
597 void sipe_ft_incoming_transfer(PurpleAccount *account, struct sipmsg *msg, const GSList *body)
599 PurpleXfer *xfer;
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);
603 if (!session) {
604 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
605 session = sipe_session_find_im(sip, from);
606 g_free(from);
609 if (!session) {
610 purple_debug_error("sipe", "sipe_ft_incoming_transfer: can't find session for remote party\n");
611 return;
614 xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE, session->with);
616 if (xfer) {
617 size_t file_size;
618 sipe_file_transfer *ft = g_new0(sipe_file_transfer, 1);
619 ft->invitation_cookie = g_strdup(sipe_utils_nameval_find(body, "Invitation-Cookie"));
620 ft->sip = sip;
621 ft->dialog = sipe_dialog_find(session, session->with);
622 ft->listenfd = -1;
623 generate_key(ft->encryption_key, SIPE_FT_KEY_LENGTH);
624 generate_key(ft->hash_key, SIPE_FT_KEY_LENGTH);
625 xfer->data = ft;
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);
652 if (xfer) {
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;
661 if (auth_cookie)
662 ft->auth_cookie = g_ascii_strtoull(auth_cookie,NULL,10);
663 if (enc_key_b64) {
664 gsize ret_len;
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);
668 } else {
669 raise_ft_error_and_cancel(xfer,
670 _("Received encryption key has wrong size."));
671 g_free(enc_key);
672 return;
674 g_free(enc_key);
676 if (hash_key_b64) {
677 gsize ret_len;
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);
681 } else {
682 raise_ft_error_and_cancel(xfer,
683 _("Received hash key has wrong size."));
684 g_free(hash_key);
685 return;
687 g_free(hash_key);
690 if (ip && port_str) {
691 purple_xfer_start(xfer, -1, ip, g_ascii_strtoull(port_str,NULL,10));
692 } else {
693 ft->listener = purple_network_listen_range(SIPE_FT_TCP_PORT_MIN,
694 SIPE_FT_TCP_PORT_MAX,
695 SOCK_STREAM,
696 sipe_ft_listen_socket_created,
697 xfer);
698 if (!ft->listener) {
699 raise_ft_error_and_cancel(xfer,
700 _("Could not create listen socket"));
701 return;
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"
729 "Hash-Key: %s\r\n"
730 /*"IP-Address: %s\r\n"
731 "Port: 6900\r\n"
732 "PortX: 11178\r\n"
733 "Auth-Cookie: 11111111\r\n"
734 "Sender-Connect: TRUE\r\n"*/,
735 ft->invitation_cookie,
736 b64_encryption_key,
737 b64_hash_key
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",
743 body, dialog, NULL);
745 g_free(body);
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",
761 body, dialog, NULL);
763 g_free(body);
766 static ssize_t
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
772 return -2;
773 } else if (bytes_read == -1) {
774 if (errno == EAGAIN)
775 return 0;
776 else
777 return -1;
779 return bytes_read;
782 static gboolean
783 read_fully(PurpleXfer *xfer, guchar *buf, size_t len)
785 const gulong READ_TIMEOUT = 10000000;
786 gulong time_spent = 0;
788 while (len) {
789 ssize_t bytes_read = do_read(xfer, buf, len);
790 if (bytes_read == 0) {
791 g_usleep(100000);
792 time_spent += 100000;
793 } else if (bytes_read < 0 || time_spent > READ_TIMEOUT) {
794 return FALSE;
795 } else {
796 len -= bytes_read;
797 buf += bytes_read;
798 time_spent = 0;
801 return TRUE;
804 static gssize read_line(PurpleXfer *xfer, gchar *buffer, gssize size)
806 gssize pos = 0;
808 memset(buffer,0,size);
809 do {
810 if (!read_fully(xfer, (guchar*) buffer + pos, 1))
811 return -1;
812 } while (buffer[pos] != '\n' && ++pos != (size - 1));
814 if (pos == (size - 1) && buffer[pos - 1] != '\n') {
815 // Buffer too short
816 return -2;
819 return pos;
822 static void sipe_cipher_context_init(PurpleCipherContext **rc4_context, const guchar *enc_key)
825 * Decryption of file from SIPE file transfer
827 * Decryption:
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;
833 guchar k2[20];
835 /* 1.) SHA1 sum */
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)
851 * Count MAC digest
853 * HMAC digest:
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;
859 guchar k2[20];
861 /* 1.) SHA1 sum */
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)
885 gsize i;
886 for (i = 0; i != size; ++i)
887 buffer[i] = rand();
890 static void set_socket_nonblock(int fd, gboolean state)
892 int flags = fcntl(fd, F_GETFL, 0);
893 if (flags == -1)
894 flags = 0;
896 if (state == TRUE)
897 fcntl(fd, F_SETFL, flags | O_NONBLOCK);
898 else
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);
906 if (xfer) {
907 if (file != NULL)
908 purple_xfer_request_accepted(xfer, file);
909 else
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);
922 if (xfer) {
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);
927 ft->sip = sip;
929 xfer->data = ft;
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);
941 return xfer;
944 static
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);
957 xfer->watcher = 0;
958 close(listenfd);
959 ft->listenfd = -1;
961 if (fd < 0) {
962 raise_ft_socket_read_error_and_cancel(xfer);
963 } else {
964 purple_xfer_start(xfer, fd, NULL, 0);
968 static
969 void sipe_ft_listen_socket_created(int listenfd, gpointer data)
971 gchar *body;
972 PurpleXfer *xfer = data;
973 sipe_file_transfer *ft = xfer->data;
975 struct sockaddr_in addr;
977 socklen_t socklen = sizeof (addr);
979 ft->listener = NULL;
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"
991 "IP-Address: %s\r\n"
992 "Port: %u\r\n"
993 "PortX: 11178\r\n"
994 "AuthCookie: %u\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),
999 ft->auth_cookie);
1001 if (!ft->dialog) {
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);
1007 if (ft->dialog) {
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);
1012 g_free(body);
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)
1021 #else
1022 # define HX_SIZE_OF_IFREQ(a) sizeof(a)
1023 #endif
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
1032 * are ignored.
1034 * Maybe this should be fixed in libpurple or some better solution found.
1036 static
1037 const char * sipe_ft_get_suitable_local_ip(int fd)
1039 int source = (fd >= 0) ? fd : socket(PF_INET,SOCK_STREAM, 0);
1041 if (source >= 0) {
1042 char buffer[1024];
1043 static char ip[16];
1044 char *tmp;
1045 struct ifconf ifc;
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);
1053 if (fd < 0)
1054 close(source);
1056 tmp = buffer;
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),
1072 ((add >> 8) & 255),
1073 add & 255);
1075 return ip;
1081 return "0.0.0.0";
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);
1090 list = NULL;
1092 g_strfreev(lines);
1093 return list;
1097 Local Variables:
1098 mode: c
1099 c-file-style: "bsd"
1100 indent-tabs-mode: t
1101 tab-width: 8
1102 End: