interface cleanup: decouple header files
[siplcs.git] / src / core / sipe-ft.c
blobf54197a53f5677e30ede7e53e61b5b0f9c347088
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 "cipher.h"
37 #include "connection.h"
38 #include "debug.h"
39 #include "eventloop.h"
40 #include "ft.h"
41 #include "network.h"
42 #include "util.h"
44 #include "sipmsg.h"
45 #include "sip-sec.h"
46 #include "sipe-dialog.h"
47 #include "sipe-nls.h"
48 #include "sipe-ft.h"
49 #include "sipe-session.h"
50 #include "sipe-utils.h"
51 #include "sipe.h"
53 #ifdef _WIN32
54 /* for network */
55 #include "libc_interface.h"
56 #include <nspapi.h>
57 #else
58 #include <sys/types.h>
59 #include <sys/socket.h>
60 #include <sys/ioctl.h>
61 #include <netinet/in.h>
62 #include <net/if.h>
63 #include <arpa/inet.h>
64 #ifdef HAVE_SYS_SOCKIO_H
65 #include <sys/sockio.h> /* SIOCGIFCONF for Solaris */
66 #endif
67 #endif
69 #define SIPE_FT_KEY_LENGTH 24
70 #define SIPE_FT_CHUNK_HEADER_LENGTH 3
73 * DO NOT CHANGE THE FOLLOWING CONSTANTS!!!
75 * It seems that Microsoft Office Communicator client will accept
76 * file transfer invitations *only* within this port range!
78 * If a firewall is active on your system you need to open these ports if
79 * you want to *send* files to other users. Receiving files uses an ougoing
80 * connection and should therefore automatically penetrate your firewall.
82 #define SIPE_FT_TCP_PORT_MIN 6891
83 #define SIPE_FT_TCP_PORT_MAX 6901
85 struct _sipe_file_transfer {
86 guchar encryption_key[SIPE_FT_KEY_LENGTH];
87 guchar hash_key[SIPE_FT_KEY_LENGTH];
88 gchar *invitation_cookie;
89 unsigned auth_cookie;
90 struct sipe_account_data *sip;
91 struct sip_dialog *dialog;
92 PurpleCipherContext *cipher_context;
93 PurpleCipherContext *hmac_context;
95 PurpleNetworkListenData *listener;
96 int listenfd;
98 gsize bytes_remaining_chunk;
99 guchar* encrypted_outbuf;
100 guchar* outbuf_ptr;
101 gsize outbuf_size;
103 typedef struct _sipe_file_transfer sipe_file_transfer;
105 static void send_filetransfer_accept(PurpleXfer* xfer);
106 static void send_filetransfer_cancel(PurpleXfer* xfer);
107 static ssize_t do_read(PurpleXfer *xfer, guchar *buf, size_t len);
108 static gboolean read_fully(PurpleXfer *xfer, guchar *buf, size_t len);
109 static gssize read_line(PurpleXfer *xfer, gchar *buffer, gssize size);
110 static void sipe_cipher_context_init(PurpleCipherContext **rc4_context, const guchar *enc_key);
111 static void sipe_hmac_context_init(PurpleCipherContext **hmac_context, const guchar *hash_key);
112 static gchar *sipe_hmac_finalize(PurpleCipherContext *hmac_context);
113 static void generate_key(guchar *buffer, gsize size);
114 static void set_socket_nonblock(int fd, gboolean state);
115 static void sipe_ft_listen_socket_created(int listenfd, gpointer data);
116 static const char * sipe_ft_get_suitable_local_ip(int fd);
118 //******************************************************************************
119 // I/O operations for PurpleXfer structure
120 //******************************************************************************
122 static void
123 sipe_ft_incoming_init(PurpleXfer *xfer)
125 send_filetransfer_accept(xfer);
128 static void
129 sipe_ft_free_xfer_struct(PurpleXfer *xfer)
131 sipe_file_transfer *ft = xfer->data;
132 if (ft) {
133 struct sipe_account_data *sip = xfer->account->gc->proto_data;
135 g_hash_table_remove(sip->filetransfers,ft->invitation_cookie);
137 if (xfer->watcher) {
138 purple_input_remove(xfer->watcher);
139 xfer->watcher = 0;
141 if (ft->listenfd >= 0) {
142 purple_debug_info("sipe", "sipe_ft_free_xfer_struct: closing listening socket %d\n", ft->listenfd);
143 close(ft->listenfd);
145 if (ft->listener)
146 purple_network_listen_cancel(ft->listener);
147 if (ft->cipher_context)
148 purple_cipher_context_destroy(ft->cipher_context);
150 if (ft->hmac_context)
151 purple_cipher_context_destroy(ft->hmac_context);
153 g_free(ft->encrypted_outbuf);
154 g_free(ft->invitation_cookie);
155 g_free(ft);
156 xfer->data = NULL;
160 static void
161 sipe_ft_request_denied(PurpleXfer *xfer)
163 if (xfer->type == PURPLE_XFER_RECEIVE)
164 send_filetransfer_cancel(xfer);
165 sipe_ft_free_xfer_struct(xfer);
168 static
169 void raise_ft_error(PurpleXfer *xfer, const char *errmsg)
171 purple_xfer_error(purple_xfer_get_type(xfer),
172 xfer->account, xfer->who,
173 errmsg);
176 static
177 void raise_ft_strerror(PurpleXfer *xfer, const char *errmsg)
179 gchar *tmp = g_strdup_printf("%s: %s", errmsg, strerror(errno));
180 raise_ft_error(xfer, tmp);
181 g_free(tmp);
184 static
185 void raise_ft_error_and_cancel(PurpleXfer *xfer, const char *errmsg)
187 raise_ft_error(xfer, errmsg);
188 purple_xfer_cancel_local(xfer);
191 static
192 void raise_ft_socket_read_error_and_cancel(PurpleXfer *xfer)
194 raise_ft_error_and_cancel(xfer, _("Socket read failed"));
197 static
198 void raise_ft_socket_write_error_and_cancel(PurpleXfer *xfer)
200 raise_ft_error_and_cancel(xfer, _("Socket write failed"));
203 static void
204 sipe_ft_incoming_start(PurpleXfer *xfer)
206 sipe_file_transfer *ft;
207 static const gchar VER[] = "VER MSN_SECURE_FTP\r\n";
208 static const gchar TFR[] = "TFR\r\n";
209 const gsize BUFFER_SIZE = 50;
210 gchar buf[BUFFER_SIZE];
211 struct sipe_account_data *sip;
212 gchar* request;
213 const gsize FILE_SIZE_OFFSET = 4;
214 gsize file_size;
216 ft = xfer->data;
218 if (write(xfer->fd,VER,strlen(VER)) == -1) {
219 raise_ft_socket_write_error_and_cancel(xfer);
220 return;
222 if (read_line(xfer, buf, BUFFER_SIZE) < 0) {
223 raise_ft_socket_read_error_and_cancel(xfer);
224 return;
227 sip = xfer->account->gc->proto_data;
229 request = g_strdup_printf("USR %s %u\r\n", sip->username, ft->auth_cookie);
230 if (write(xfer->fd,request,strlen(request)) == -1) {
231 raise_ft_socket_write_error_and_cancel(xfer);
232 g_free(request);
233 return;
235 g_free(request);
237 if (read_line(xfer, buf, BUFFER_SIZE) < 0) {
238 raise_ft_socket_read_error_and_cancel(xfer);
239 return;
242 file_size = g_ascii_strtoull(buf + FILE_SIZE_OFFSET,NULL,10);
243 if (file_size != xfer->size) {
244 raise_ft_error_and_cancel(xfer,
245 _("File size is different from the advertised value."));
246 return;
249 if (write(xfer->fd,TFR,strlen(TFR)) == -1) {
250 raise_ft_socket_write_error_and_cancel(xfer);
251 return;
254 ft->bytes_remaining_chunk = 0;
256 sipe_cipher_context_init(&ft->cipher_context, ft->encryption_key);
257 sipe_hmac_context_init(&ft->hmac_context, ft->hash_key);
260 static void
261 sipe_ft_incoming_stop(PurpleXfer *xfer)
263 static const gchar BYE[] = "BYE 16777989\r\n";
264 gsize BUFFER_SIZE = 50;
265 char buffer[BUFFER_SIZE];
266 const gssize MAC_OFFSET = 4;
267 const gssize CRLF_LEN = 2;
268 gssize macLen;
269 sipe_file_transfer *ft;
270 gchar *mac;
271 gchar *mac1;
273 if (write(xfer->fd,BYE,strlen(BYE)) == -1) {
274 raise_ft_socket_write_error_and_cancel(xfer);
275 return;
278 macLen = read_line(xfer, buffer, BUFFER_SIZE);
280 if (macLen < 0) {
281 raise_ft_socket_read_error_and_cancel(xfer);
282 return;
283 } else if (macLen < (MAC_OFFSET + CRLF_LEN)) {
284 raise_ft_error_and_cancel(xfer, _("Received MAC is corrupted"));
285 return;
288 // Check MAC
289 ft = xfer->data;
290 mac = g_strndup(buffer + MAC_OFFSET, macLen - MAC_OFFSET - CRLF_LEN);
291 mac1 = sipe_hmac_finalize(ft->hmac_context);
292 if (!sipe_strequal(mac, mac1)) {
293 unlink(xfer->local_filename);
294 raise_ft_error_and_cancel(xfer,
295 _("Received file is corrupted"));
297 g_free(mac1);
298 g_free(mac);
300 sipe_ft_free_xfer_struct(xfer);
303 static gssize
304 sipe_ft_read(guchar **buffer, PurpleXfer *xfer)
306 gsize bytes_to_read;
307 ssize_t bytes_read;
309 sipe_file_transfer *ft = xfer->data;
311 if (ft->bytes_remaining_chunk == 0) {
312 guchar hdr_buf[SIPE_FT_CHUNK_HEADER_LENGTH];
314 /* read chunk header */
315 if (!read_fully(xfer, hdr_buf, sizeof(hdr_buf))) {
316 raise_ft_strerror(xfer, _("Socket read failed"));
317 return -1;
320 /* chunk header format:
322 * 0: 00 unknown (always zero?)
323 * 1: LL chunk size in bytes (low byte)
324 * 2: HH chunk size in bytes (high byte)
326 * Convert size from little endian to host order
328 ft->bytes_remaining_chunk = hdr_buf[1] + (hdr_buf[2] << 8);
331 bytes_to_read = MIN(purple_xfer_get_bytes_remaining(xfer),
332 xfer->current_buffer_size);
333 bytes_to_read = MIN(bytes_to_read, ft->bytes_remaining_chunk);
335 *buffer = g_malloc(bytes_to_read);
336 if (!*buffer) {
337 raise_ft_error(xfer, _("Out of memory"));
338 purple_debug_error("sipe", "sipe_ft_read: can't allocate %" G_GSIZE_FORMAT " bytes for receive buffer\n",
339 bytes_to_read);
340 return -1;
343 bytes_read = do_read(xfer, *buffer, bytes_to_read);
344 if (bytes_read < 0) {
345 raise_ft_strerror(xfer, _("Socket read failed"));
346 return -1;
349 if (bytes_read > 0) {
350 guchar *decrypted = g_malloc(bytes_read);
352 if (!decrypted) {
353 raise_ft_error(xfer, _("Out of memory"));
354 purple_debug_error("sipe", "sipe_ft_read: can't allocate %" G_GSIZE_FORMAT " bytes for decryption buffer\n",
355 (gsize)bytes_read);
356 g_free(*buffer);
357 *buffer = NULL;
358 return -1;
360 purple_cipher_context_encrypt(ft->cipher_context, *buffer, bytes_read, decrypted, NULL);
361 g_free(*buffer);
362 *buffer = decrypted;
364 purple_cipher_context_append(ft->hmac_context, decrypted, bytes_read);
366 ft->bytes_remaining_chunk -= bytes_read;
369 return bytes_read;
372 static gssize
373 sipe_ft_write(const guchar *buffer, size_t size, PurpleXfer *xfer)
375 ssize_t bytes_written;
376 sipe_file_transfer *ft = xfer->data;
378 /* When sending data via server with ForeFront installed, block bigger than
379 * this default causes ending of transmission. Hard limit block to this value
380 * when libpurple sends us more data. */
381 const gsize DEFAULT_BLOCK_SIZE = 2045;
382 if (size > DEFAULT_BLOCK_SIZE)
383 size = DEFAULT_BLOCK_SIZE;
385 if (ft->bytes_remaining_chunk == 0) {
386 ssize_t bytes_read;
387 guchar local_buf[16];
388 guchar hdr_buf[SIPE_FT_CHUNK_HEADER_LENGTH];
390 memset(local_buf, 0, sizeof local_buf);
392 // Check if receiver did not cancel the transfer before it is finished
393 bytes_read = read(xfer->fd,local_buf,sizeof (local_buf));
394 if (bytes_read == -1 && errno != EAGAIN) {
395 raise_ft_strerror(xfer, _("Socket read failed"));
396 return -1;
397 } else if (bytes_read > 0
398 && (g_str_has_prefix((gchar*)local_buf,"CCL\r\n")
399 || g_str_has_prefix((gchar*)local_buf,"BYE 2164261682\r\n"))) {
400 return -1;
403 if (ft->outbuf_size < size) {
404 g_free(ft->encrypted_outbuf);
405 ft->outbuf_size = size;
406 ft->encrypted_outbuf = g_malloc(ft->outbuf_size);
407 if (!ft->encrypted_outbuf) {
408 raise_ft_error(xfer, _("Out of memory"));
409 purple_debug_error("sipe", "sipe_ft_write: can't allocate %" G_GSIZE_FORMAT " bytes for send buffer\n",
410 ft->outbuf_size);
411 return -1;
415 ft->bytes_remaining_chunk = size;
416 ft->outbuf_ptr = ft->encrypted_outbuf;
417 purple_cipher_context_encrypt(ft->cipher_context, buffer, size,
418 ft->encrypted_outbuf, NULL);
419 purple_cipher_context_append(ft->hmac_context, buffer, size);
421 /* chunk header format:
423 * 0: 00 unknown (always zero?)
424 * 1: LL chunk size in bytes (low byte)
425 * 2: HH chunk size in bytes (high byte)
427 * Convert size from host order to little endian
429 hdr_buf[0] = 0;
430 hdr_buf[1] = (ft->bytes_remaining_chunk & 0x00FF);
431 hdr_buf[2] = (ft->bytes_remaining_chunk & 0xFF00) >> 8;
433 /* write chunk header */
434 if (write(xfer->fd, hdr_buf, sizeof(hdr_buf)) == -1) {
435 raise_ft_strerror(xfer, _("Socket write failed"));
436 return -1;
440 bytes_written = write(xfer->fd, ft->outbuf_ptr, ft->bytes_remaining_chunk);
441 if (bytes_written == -1) {
442 if (errno == EAGAIN)
443 bytes_written = 0;
444 else {
445 raise_ft_strerror(xfer, _("Socket write failed"));
449 if (bytes_written > 0) {
450 ft->bytes_remaining_chunk -= bytes_written;
451 ft->outbuf_ptr += bytes_written;
454 if ((xfer->bytes_remaining - bytes_written) == 0)
455 purple_xfer_set_completed(xfer, TRUE);
457 return bytes_written;
460 static void
461 sipe_ft_outgoing_init(PurpleXfer *xfer)
463 struct sip_dialog *dialog;
464 sipe_file_transfer *ft = xfer->data;
466 gchar *body = g_strdup_printf("Application-Name: File Transfer\r\n"
467 "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n"
468 "Invitation-Command: INVITE\r\n"
469 "Invitation-Cookie: %s\r\n"
470 "Application-File: %s\r\n"
471 "Application-FileSize: %lu\r\n"
472 //"Connectivity: N\r\n" TODO
473 "Encryption: R\r\n", // TODO: non encrypted file transfer support
474 ft->invitation_cookie,
475 purple_xfer_get_filename(xfer),
476 (long unsigned) purple_xfer_get_size(xfer));
478 struct sipe_account_data *sip = xfer->account->gc->proto_data;
479 struct sip_session *session = sipe_session_find_or_add_im(sip, xfer->who);
481 g_hash_table_insert(sip->filetransfers,g_strdup(ft->invitation_cookie),xfer);
483 // Queue the message
484 sipe_session_enqueue_message(session, body, "text/x-msmsgsinvite");
486 dialog = sipe_dialog_find(session, xfer->who);
487 if (dialog && !dialog->outgoing_invite) {
488 ft->dialog = dialog;
489 sipe_im_process_queue(sip, session);
490 } else if (!dialog || !dialog->outgoing_invite) {
491 // Need to send the INVITE to get the outgoing dialog setup
492 sipe_invite(sip, session, xfer->who, body, "text/x-msmsgsinvite", NULL, FALSE);
495 g_free(body);
498 static void
499 sipe_ft_outgoing_start(PurpleXfer *xfer)
501 sipe_file_transfer *ft;
502 static const gchar VER[] = "VER MSN_SECURE_FTP\r\n";
503 const gsize BUFFER_SIZE = 50;
504 gchar buf[BUFFER_SIZE];
505 gchar** parts;
506 unsigned auth_cookie_received;
507 gboolean users_match;
508 gchar *tmp;
509 ssize_t bytes_written;
511 set_socket_nonblock(xfer->fd, TRUE);
513 ft = xfer->data;
515 if (read_line(xfer, buf, BUFFER_SIZE) < 0) {
516 raise_ft_socket_read_error_and_cancel(xfer);
517 return;
520 if (!sipe_strequal(buf,VER)) {
521 raise_ft_error_and_cancel(xfer,_("File transfer initialization failed."));
522 purple_debug_info("sipe","File transfer VER string incorrect, received: %s expected: %s",
523 buf, VER);
524 return;
527 if (write(xfer->fd,VER,strlen(VER)) == -1) {
528 raise_ft_socket_write_error_and_cancel(xfer);
529 return;
532 if (read_line(xfer, buf, BUFFER_SIZE) < 0) {
533 raise_ft_socket_read_error_and_cancel(xfer);
534 return;
537 parts = g_strsplit(buf, " ", 3);
539 auth_cookie_received = g_ascii_strtoull(parts[2],NULL,10);
541 // xfer->who has 'sip:' prefix, skip these four characters
542 users_match = sipe_strcase_equal(parts[1], (xfer->who + 4));
543 g_strfreev(parts);
545 purple_debug_info("sipe","File transfer authentication: %s Expected: USR %s %u\n",
546 buf, xfer->who + 4, ft->auth_cookie);
548 if (!users_match || (ft->auth_cookie != auth_cookie_received)) {
549 raise_ft_error_and_cancel(xfer,
550 _("File transfer authentication failed."));
551 return;
554 tmp = g_strdup_printf("FIL %lu\r\n",(long unsigned) xfer->size);
555 bytes_written = write(xfer->fd, tmp, strlen(tmp));
556 g_free(tmp);
558 if (bytes_written == -1) {
559 raise_ft_socket_write_error_and_cancel(xfer);
560 return;
563 // TFR
564 if (read_line(xfer,buf,BUFFER_SIZE) < 0) {
565 raise_ft_socket_read_error_and_cancel(xfer);
566 return;
569 ft->bytes_remaining_chunk = 0;
571 sipe_cipher_context_init(&ft->cipher_context, ft->encryption_key);
572 sipe_hmac_context_init(&ft->hmac_context, ft->hash_key);
575 static void
576 sipe_ft_outgoing_stop(PurpleXfer *xfer)
578 sipe_file_transfer *ft = xfer->data;
579 gsize BUFFER_SIZE = 50;
580 char buffer[BUFFER_SIZE];
581 gchar *mac;
582 gsize mac_strlen;
584 // BYE
585 if (read_line(xfer, buffer, BUFFER_SIZE) < 0) {
586 raise_ft_socket_read_error_and_cancel(xfer);
587 return;
590 mac = sipe_hmac_finalize(ft->hmac_context);
591 g_sprintf(buffer, "MAC %s \r\n", mac);
592 g_free(mac);
594 mac_strlen = strlen(buffer);
595 // There must be this zero byte between mac and \r\n
596 buffer[mac_strlen - 3] = 0;
598 if (write(xfer->fd,buffer,mac_strlen) == -1) {
599 raise_ft_socket_write_error_and_cancel(xfer);
600 return;
603 sipe_ft_free_xfer_struct(xfer);
606 //******************************************************************************
608 void sipe_ft_incoming_transfer(PurpleAccount *account, struct sipmsg *msg, const GSList *body)
610 PurpleXfer *xfer;
611 struct sipe_account_data *sip = account->gc->proto_data;
612 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
613 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
614 if (!session) {
615 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
616 session = sipe_session_find_im(sip, from);
617 g_free(from);
620 if (!session) {
621 purple_debug_error("sipe", "sipe_ft_incoming_transfer: can't find session for remote party\n");
622 return;
625 xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE, session->with);
627 if (xfer) {
628 size_t file_size;
629 sipe_file_transfer *ft = g_new0(sipe_file_transfer, 1);
630 ft->invitation_cookie = g_strdup(sipe_utils_nameval_find(body, "Invitation-Cookie"));
631 ft->sip = sip;
632 ft->dialog = sipe_dialog_find(session, session->with);
633 ft->listenfd = -1;
634 generate_key(ft->encryption_key, SIPE_FT_KEY_LENGTH);
635 generate_key(ft->hash_key, SIPE_FT_KEY_LENGTH);
636 xfer->data = ft;
638 purple_xfer_set_filename(xfer, sipe_utils_nameval_find(body, "Application-File"));
640 file_size = g_ascii_strtoull(sipe_utils_nameval_find(body, "Application-FileSize"),NULL,10);
641 purple_xfer_set_size(xfer, file_size);
643 purple_xfer_set_init_fnc(xfer, sipe_ft_incoming_init);
644 purple_xfer_set_start_fnc(xfer,sipe_ft_incoming_start);
645 purple_xfer_set_end_fnc(xfer,sipe_ft_incoming_stop);
646 purple_xfer_set_request_denied_fnc(xfer, sipe_ft_request_denied);
647 purple_xfer_set_read_fnc(xfer,sipe_ft_read);
648 purple_xfer_set_cancel_send_fnc(xfer,sipe_ft_free_xfer_struct);
649 purple_xfer_set_cancel_recv_fnc(xfer,sipe_ft_free_xfer_struct);
651 g_hash_table_insert(sip->filetransfers,g_strdup(ft->invitation_cookie),xfer);
653 purple_xfer_request(xfer);
657 void sipe_ft_incoming_accept(PurpleAccount *account, const GSList *body)
659 struct sipe_account_data *sip = account->gc->proto_data;
660 const gchar *inv_cookie = sipe_utils_nameval_find(body, "Invitation-Cookie");
661 PurpleXfer *xfer = g_hash_table_lookup(sip->filetransfers,inv_cookie);
663 if (xfer) {
664 const gchar *ip = sipe_utils_nameval_find(body, "IP-Address");
665 const gchar *port_str = sipe_utils_nameval_find(body, "Port");
666 const gchar *auth_cookie = sipe_utils_nameval_find(body, "AuthCookie");
667 const gchar *enc_key_b64 = sipe_utils_nameval_find(body, "Encryption-Key");
668 const gchar *hash_key_b64 = sipe_utils_nameval_find(body, "Hash-Key");
670 sipe_file_transfer *ft = xfer->data;
672 if (auth_cookie)
673 ft->auth_cookie = g_ascii_strtoull(auth_cookie,NULL,10);
674 if (enc_key_b64) {
675 gsize ret_len;
676 guchar *enc_key = purple_base64_decode(enc_key_b64, &ret_len);
677 if (ret_len == SIPE_FT_KEY_LENGTH) {
678 memcpy(ft->encryption_key,enc_key,SIPE_FT_KEY_LENGTH);
679 } else {
680 raise_ft_error_and_cancel(xfer,
681 _("Received encryption key has wrong size."));
682 g_free(enc_key);
683 return;
685 g_free(enc_key);
687 if (hash_key_b64) {
688 gsize ret_len;
689 guchar *hash_key = purple_base64_decode(hash_key_b64, &ret_len);
690 if (ret_len == SIPE_FT_KEY_LENGTH) {
691 memcpy(ft->hash_key,hash_key,SIPE_FT_KEY_LENGTH);
692 } else {
693 raise_ft_error_and_cancel(xfer,
694 _("Received hash key has wrong size."));
695 g_free(hash_key);
696 return;
698 g_free(hash_key);
701 if (ip && port_str) {
702 purple_xfer_start(xfer, -1, ip, g_ascii_strtoull(port_str,NULL,10));
703 } else {
704 ft->listener = purple_network_listen_range(SIPE_FT_TCP_PORT_MIN,
705 SIPE_FT_TCP_PORT_MAX,
706 SOCK_STREAM,
707 sipe_ft_listen_socket_created,
708 xfer);
709 if (!ft->listener) {
710 raise_ft_error_and_cancel(xfer,
711 _("Could not create listen socket"));
712 return;
718 void sipe_ft_incoming_cancel(PurpleAccount *account, GSList *body)
720 gchar *inv_cookie = g_strdup(sipe_utils_nameval_find(body, "Invitation-Cookie"));
722 struct sipe_account_data *sip = account->gc->proto_data;
723 PurpleXfer *xfer = g_hash_table_lookup(sip->filetransfers,inv_cookie);
725 purple_xfer_cancel_remote(xfer);
728 static void send_filetransfer_accept(PurpleXfer* xfer)
730 sipe_file_transfer* ft = xfer->data;
731 struct sip_dialog *dialog = ft->dialog;
733 gchar *b64_encryption_key = purple_base64_encode(ft->encryption_key,24);
734 gchar *b64_hash_key = purple_base64_encode(ft->hash_key,24);
736 gchar *body = g_strdup_printf("Invitation-Command: ACCEPT\r\n"
737 "Request-Data: IP-Address:\r\n"
738 "Invitation-Cookie: %s\r\n"
739 "Encryption-Key: %s\r\n"
740 "Hash-Key: %s\r\n"
741 /*"IP-Address: %s\r\n"
742 "Port: 6900\r\n"
743 "PortX: 11178\r\n"
744 "Auth-Cookie: 11111111\r\n"
745 "Sender-Connect: TRUE\r\n"*/,
746 ft->invitation_cookie,
747 b64_encryption_key,
748 b64_hash_key
749 /*,purple_network_get_my_ip(-1)*/
752 send_sip_request(ft->sip->gc, "MESSAGE", dialog->with, dialog->with,
753 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
754 body, dialog, NULL);
756 g_free(body);
757 g_free(b64_encryption_key);
758 g_free(b64_hash_key);
761 static void send_filetransfer_cancel(PurpleXfer* xfer) {
762 sipe_file_transfer* ft = xfer->data;
763 struct sip_dialog* dialog = ft->dialog;
765 gchar *body = g_strdup_printf("Invitation-Command: CANCEL\r\n"
766 "Invitation-Cookie: %s\r\n"
767 "Cancel-Code: REJECT\r\n",
768 ft->invitation_cookie);
770 send_sip_request(ft->sip->gc, "MESSAGE", dialog->with, dialog->with,
771 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
772 body, dialog, NULL);
774 g_free(body);
777 static ssize_t
778 do_read(PurpleXfer *xfer, guchar *buf, size_t len)
780 ssize_t bytes_read = read(xfer->fd, buf, len);
781 if (bytes_read == 0) {
782 // Sender canceled transfer before it was finished
783 return -2;
784 } else if (bytes_read == -1) {
785 if (errno == EAGAIN)
786 return 0;
787 else
788 return -1;
790 return bytes_read;
793 static gboolean
794 read_fully(PurpleXfer *xfer, guchar *buf, size_t len)
796 const gulong READ_TIMEOUT = 10000000;
797 gulong time_spent = 0;
799 while (len) {
800 ssize_t bytes_read = do_read(xfer, buf, len);
801 if (bytes_read == 0) {
802 g_usleep(100000);
803 time_spent += 100000;
804 } else if (bytes_read < 0 || time_spent > READ_TIMEOUT) {
805 return FALSE;
806 } else {
807 len -= bytes_read;
808 buf += bytes_read;
809 time_spent = 0;
812 return TRUE;
815 static gssize read_line(PurpleXfer *xfer, gchar *buffer, gssize size)
817 gssize pos = 0;
819 memset(buffer,0,size);
820 do {
821 if (!read_fully(xfer, (guchar*) buffer + pos, 1))
822 return -1;
823 } while (buffer[pos] != '\n' && ++pos != (size - 1));
825 if (pos == (size - 1) && buffer[pos - 1] != '\n') {
826 // Buffer too short
827 return -2;
830 return pos;
833 static void sipe_cipher_context_init(PurpleCipherContext **rc4_context, const guchar *enc_key)
836 * Decryption of file from SIPE file transfer
838 * Decryption:
839 * 1.) SHA1-Key = SHA1sum (Encryption-Key); Do SHA1 digest from Encryption-Key, return 20 bytes SHA1-Key.
840 * 2.) Decrypt-Data = RC4 (Encrypt-Data, substr(SHA1-Key, 0, 15)); Decryption of encrypted data, used 16 bytes SHA1-Key;
843 PurpleCipherContext *sha1_context;
844 guchar k2[20];
846 /* 1.) SHA1 sum */
847 sha1_context = purple_cipher_context_new_by_name("sha1", NULL);
848 purple_cipher_context_append(sha1_context, enc_key, SIPE_FT_KEY_LENGTH);
849 purple_cipher_context_digest(sha1_context, sizeof(k2), k2, NULL);
850 purple_cipher_context_destroy(sha1_context);
852 /* 2.) RC4 decryption */
853 *rc4_context = purple_cipher_context_new_by_name("rc4", NULL);
854 purple_cipher_context_set_option(*rc4_context, "key_len", (gpointer)0x10); // only 16 chars key used
855 purple_cipher_context_set_key(*rc4_context, k2);
859 static void sipe_hmac_context_init(PurpleCipherContext **hmac_context, const guchar *hash_key)
862 * Count MAC digest
864 * HMAC digest:
865 * 1.) SHA1-Key = SHA1sum (Hash-Key); Do SHA1 digest from Hash-Key, return 20 bytes SHA1-Key.
866 * 2.) MAC = HMAC_SHA1 (Decrypt-Data, substr(HMAC-Key,0,15)); Digest of decrypted file and SHA1-Key (used again only 16 bytes)
869 PurpleCipherContext *sha1_context;
870 guchar k2[20];
872 /* 1.) SHA1 sum */
873 sha1_context = purple_cipher_context_new_by_name("sha1", NULL);
874 purple_cipher_context_append(sha1_context, hash_key, SIPE_FT_KEY_LENGTH);
875 purple_cipher_context_digest(sha1_context, sizeof(k2), k2, NULL);
876 purple_cipher_context_destroy(sha1_context);
878 /* 2.) HMAC (initialization only) */
879 *hmac_context = purple_cipher_context_new_by_name("hmac", NULL);
880 purple_cipher_context_set_option(*hmac_context, "hash", "sha1");
881 purple_cipher_context_set_key_with_len(*hmac_context, k2, 16);
884 static gchar* sipe_hmac_finalize(PurpleCipherContext *hmac_context)
886 guchar hmac_digest[20];
888 /* MAC = Digest of decrypted file and SHA1-Key (used again only 16 bytes) */
889 purple_cipher_context_digest(hmac_context, sizeof(hmac_digest), hmac_digest, NULL);
891 return purple_base64_encode(hmac_digest, sizeof (hmac_digest));
894 static void generate_key(guchar *buffer, gsize size)
896 gsize i;
897 for (i = 0; i != size; ++i)
898 buffer[i] = rand();
901 static void set_socket_nonblock(int fd, gboolean state)
903 int flags = fcntl(fd, F_GETFL, 0);
904 if (flags == -1)
905 flags = 0;
907 if (state == TRUE)
908 fcntl(fd, F_SETFL, flags | O_NONBLOCK);
909 else
910 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
913 void sipe_ft_send_file(PurpleConnection *gc, const char *who, const char *file)
915 PurpleXfer *xfer = sipe_ft_new_xfer(gc, who);
917 if (xfer) {
918 if (file != NULL)
919 purple_xfer_request_accepted(xfer, file);
920 else
921 purple_xfer_request(xfer);
925 PurpleXfer * sipe_ft_new_xfer(PurpleConnection *gc, const char *who)
927 PurpleXfer *xfer = NULL;
929 if (PURPLE_CONNECTION_IS_VALID(gc)) {
930 xfer = purple_xfer_new(purple_connection_get_account(gc),
931 PURPLE_XFER_SEND, who);
933 if (xfer) {
934 struct sipe_account_data *sip = gc->proto_data;
936 sipe_file_transfer *ft = g_new0(sipe_file_transfer, 1);
937 ft->invitation_cookie = g_strdup_printf("%u", rand() % 1000000000);
938 ft->sip = sip;
940 xfer->data = ft;
942 purple_xfer_set_init_fnc(xfer, sipe_ft_outgoing_init);
943 purple_xfer_set_start_fnc(xfer, sipe_ft_outgoing_start);
944 purple_xfer_set_end_fnc(xfer, sipe_ft_outgoing_stop);
945 purple_xfer_set_request_denied_fnc(xfer, sipe_ft_request_denied);
946 purple_xfer_set_write_fnc(xfer, sipe_ft_write);
947 purple_xfer_set_cancel_send_fnc(xfer, sipe_ft_free_xfer_struct);
948 purple_xfer_set_cancel_recv_fnc(xfer, sipe_ft_free_xfer_struct);
952 return xfer;
955 static
956 void sipe_ft_client_connected(gpointer p_xfer, gint listenfd,
957 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
959 struct sockaddr_in saddr;
960 socklen_t slen = sizeof (saddr);
962 int fd = accept(listenfd, (struct sockaddr*)&saddr, &slen);
964 PurpleXfer *xfer = p_xfer;
965 sipe_file_transfer *ft = xfer->data;
967 purple_input_remove(xfer->watcher);
968 xfer->watcher = 0;
969 close(listenfd);
970 ft->listenfd = -1;
972 if (fd < 0) {
973 raise_ft_socket_read_error_and_cancel(xfer);
974 } else {
975 purple_xfer_start(xfer, fd, NULL, 0);
979 static
980 void sipe_ft_listen_socket_created(int listenfd, gpointer data)
982 gchar *body;
983 PurpleXfer *xfer = data;
984 sipe_file_transfer *ft = xfer->data;
986 struct sockaddr_in addr;
988 socklen_t socklen = sizeof (addr);
990 ft->listener = NULL;
991 ft->listenfd = listenfd;
993 getsockname(listenfd, (struct sockaddr*)&addr, &socklen);
995 xfer->watcher = purple_input_add(listenfd, PURPLE_INPUT_READ,
996 sipe_ft_client_connected, xfer);
998 ft->auth_cookie = rand() % 1000000000;
1000 body = g_strdup_printf("Invitation-Command: ACCEPT\r\n"
1001 "Invitation-Cookie: %s\r\n"
1002 "IP-Address: %s\r\n"
1003 "Port: %u\r\n"
1004 "PortX: 11178\r\n"
1005 "AuthCookie: %u\r\n"
1006 "Request-Data: IP-Address:\r\n",
1007 ft->invitation_cookie,
1008 sipe_ft_get_suitable_local_ip(listenfd),
1009 ntohs(addr.sin_port),
1010 ft->auth_cookie);
1012 if (!ft->dialog) {
1013 struct sipe_account_data *sip = xfer->account->gc->proto_data;
1014 struct sip_session *session = sipe_session_find_or_add_im(sip, xfer->who);
1015 ft->dialog = sipe_dialog_find(session, xfer->who);
1018 if (ft->dialog) {
1019 send_sip_request(ft->sip->gc, "MESSAGE", ft->dialog->with, ft->dialog->with,
1020 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
1021 body, ft->dialog, NULL);
1023 g_free(body);
1027 * Calling sizeof(struct ifreq) isn't always correct on
1028 * Mac OS X (and maybe others).
1030 #ifdef _SIZEOF_ADDR_IFREQ
1031 # define HX_SIZE_OF_IFREQ(a) _SIZEOF_ADDR_IFREQ(a)
1032 #else
1033 # define HX_SIZE_OF_IFREQ(a) sizeof(a)
1034 #endif
1037 * Returns local IP address suitable for connection.
1039 * purple_network_get_my_ip() will not do this, because it might return an
1040 * address within 169.254.x.x range that was assigned to interface disconnected
1041 * from the network (when multiple network adapters are available). This is a
1042 * copy-paste from libpurple's network.c, only change is that link local addresses
1043 * are ignored.
1045 * Maybe this should be fixed in libpurple or some better solution found.
1047 static
1048 const char * sipe_ft_get_suitable_local_ip(int fd)
1050 int source = (fd >= 0) ? fd : socket(PF_INET,SOCK_STREAM, 0);
1052 if (source >= 0) {
1053 char buffer[1024];
1054 static char ip[16];
1055 char *tmp;
1056 struct ifconf ifc;
1057 guint32 lhost = htonl(127 * 256 * 256 * 256 + 1);
1058 guint32 llocal = htonl((169 << 24) + (254 << 16));
1060 ifc.ifc_len = sizeof(buffer);
1061 ifc.ifc_req = (struct ifreq *)buffer;
1062 ioctl(source, SIOCGIFCONF, &ifc);
1064 if (fd < 0)
1065 close(source);
1067 tmp = buffer;
1068 while (tmp < buffer + ifc.ifc_len)
1070 struct ifreq *ifr = (struct ifreq *)tmp;
1071 tmp += HX_SIZE_OF_IFREQ(*ifr);
1073 if (ifr->ifr_addr.sa_family == AF_INET)
1075 struct sockaddr_in *sinptr = (struct sockaddr_in *)&ifr->ifr_addr;
1076 if (sinptr->sin_addr.s_addr != lhost
1077 && (sinptr->sin_addr.s_addr & htonl(0xFFFF0000)) != llocal)
1079 long unsigned int add = ntohl(sinptr->sin_addr.s_addr);
1080 g_snprintf(ip, 16, "%lu.%lu.%lu.%lu",
1081 ((add >> 24) & 255),
1082 ((add >> 16) & 255),
1083 ((add >> 8) & 255),
1084 add & 255);
1086 return ip;
1092 return "0.0.0.0";
1095 GSList * sipe_ft_parse_msg_body(const gchar *body)
1097 GSList *list = NULL;
1098 gchar **lines = g_strsplit(body, "\r\n", 0);
1099 if (sipe_utils_parse_lines(&list, lines) == FALSE) {
1100 sipe_utils_nameval_free(list);
1101 list = NULL;
1103 g_strfreev(lines);
1104 return list;
1108 Local Variables:
1109 mode: c
1110 c-file-style: "bsd"
1111 indent-tabs-mode: t
1112 tab-width: 8
1113 End: