Send BYE when response to IM message is 408/480/481
[siplcs.git] / src / core / sipe-ft.c
blob08c64117b74738948e29bf76bf738ec6a2a1b305
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 #include <nspapi.h>
44 #else
45 #include <sys/types.h>
46 #include <sys/socket.h>
47 #include <sys/ioctl.h>
48 #include <netinet/in.h>
49 #include <net/if.h>
50 #include <arpa/inet.h>
51 #endif
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;
73 unsigned auth_cookie;
74 struct sipe_account_data *sip;
75 struct sip_dialog *dialog;
76 PurpleCipherContext *cipher_context;
77 PurpleCipherContext *hmac_context;
79 PurpleNetworkListenData *listener;
80 int listenfd;
82 gsize bytes_remaining_chunk;
83 guchar* encrypted_outbuf;
84 guchar* outbuf_ptr;
85 gsize outbuf_size;
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 //******************************************************************************
106 static void
107 sipe_ft_incoming_init(PurpleXfer *xfer)
109 send_filetransfer_accept(xfer);
112 static void
113 sipe_ft_free_xfer_struct(PurpleXfer *xfer)
115 sipe_file_transfer *ft = xfer->data;
116 if (ft) {
117 struct sipe_account_data *sip = xfer->account->gc->proto_data;
119 g_hash_table_remove(sip->filetransfers,ft->invitation_cookie);
121 if (xfer->watcher) {
122 purple_input_remove(xfer->watcher);
123 xfer->watcher = 0;
125 if (ft->listenfd >= 0) {
126 purple_debug_info("sipe", "sipe_ft_free_xfer_struct: closing listening socket %d\n", ft->listenfd);
127 close(ft->listenfd);
129 if (ft->listener)
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);
139 g_free(ft);
140 xfer->data = NULL;
144 static void
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);
152 static
153 void raise_ft_error(PurpleXfer *xfer, const char *errmsg)
155 purple_xfer_error(purple_xfer_get_type(xfer),
156 xfer->account, xfer->who,
157 errmsg);
160 static
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);
165 g_free(tmp);
168 static
169 void raise_ft_error_and_cancel(PurpleXfer *xfer, const char *errmsg)
171 raise_ft_error(xfer, errmsg);
172 purple_xfer_cancel_local(xfer);
175 static
176 void raise_ft_socket_read_error_and_cancel(PurpleXfer *xfer)
178 raise_ft_error_and_cancel(xfer, _("Socket read failed"));
181 static
182 void raise_ft_socket_write_error_and_cancel(PurpleXfer *xfer)
184 raise_ft_error_and_cancel(xfer, _("Socket write failed"));
187 static void
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;
196 gchar* request;
197 const gsize FILE_SIZE_OFFSET = 4;
198 gsize file_size;
200 ft = xfer->data;
202 if (write(xfer->fd,VER,strlen(VER)) == -1) {
203 raise_ft_socket_write_error_and_cancel(xfer);
204 return;
206 if (read_line(xfer, buf, BUFFER_SIZE) < 0) {
207 raise_ft_socket_read_error_and_cancel(xfer);
208 return;
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);
216 g_free(request);
217 return;
219 g_free(request);
221 if (read_line(xfer, buf, BUFFER_SIZE) < 0) {
222 raise_ft_socket_read_error_and_cancel(xfer);
223 return;
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."));
230 return;
233 if (write(xfer->fd,TFR,strlen(TFR)) == -1) {
234 raise_ft_socket_write_error_and_cancel(xfer);
235 return;
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);
244 static void
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;
252 gssize macLen;
253 sipe_file_transfer *ft;
254 gchar *mac;
255 gchar *mac1;
257 if (write(xfer->fd,BYE,strlen(BYE)) == -1) {
258 raise_ft_socket_write_error_and_cancel(xfer);
259 return;
262 macLen = read_line(xfer, buffer, BUFFER_SIZE);
264 if (macLen < 0) {
265 raise_ft_socket_read_error_and_cancel(xfer);
266 return;
267 } else if (macLen < (MAC_OFFSET + CRLF_LEN)) {
268 raise_ft_error_and_cancel(xfer, _("Received MAC is corrupted"));
269 return;
272 // Check MAC
273 ft = xfer->data;
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"));
281 g_free(mac1);
282 g_free(mac);
284 sipe_ft_free_xfer_struct(xfer);
287 static gssize
288 sipe_ft_read(guchar **buffer, PurpleXfer *xfer)
290 gsize bytes_to_read;
291 ssize_t bytes_read;
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"));
301 return -1;
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);
320 if (!*buffer) {
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",
323 bytes_to_read);
324 return -1;
327 bytes_read = do_read(xfer, *buffer, bytes_to_read);
328 if (bytes_read < 0) {
329 raise_ft_strerror(xfer, _("Socket read failed"));
330 return -1;
333 if (bytes_read > 0) {
334 guchar *decrypted = g_malloc(bytes_read);
336 if (!decrypted) {
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",
339 (gsize)bytes_read);
340 g_free(*buffer);
341 *buffer = NULL;
342 return -1;
344 purple_cipher_context_encrypt(ft->cipher_context, *buffer, bytes_read, decrypted, NULL);
345 g_free(*buffer);
346 *buffer = decrypted;
348 purple_cipher_context_append(ft->hmac_context, decrypted, bytes_read);
350 ft->bytes_remaining_chunk -= bytes_read;
353 return bytes_read;
356 static gssize
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) {
370 ssize_t bytes_read;
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"));
380 return -1;
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"))) {
384 return -1;
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",
394 ft->outbuf_size);
395 return -1;
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
413 hdr_buf[0] = 0;
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"));
420 return -1;
424 bytes_written = write(xfer->fd, ft->outbuf_ptr, ft->bytes_remaining_chunk);
425 if (bytes_written == -1) {
426 if (errno == EAGAIN)
427 bytes_written = 0;
428 else {
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;
444 static void
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);
467 // Queue the message
468 sipe_session_enqueue_message(session, body, "text/x-msmsgsinvite");
470 dialog = sipe_dialog_find(session, xfer->who);
471 if (dialog && !dialog->outgoing_invite) {
472 ft->dialog = dialog;
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);
479 g_free(body);
482 static void
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];
489 gchar** parts;
490 unsigned auth_cookie_received;
491 gboolean users_match;
492 gchar *tmp;
493 ssize_t bytes_written;
495 set_socket_nonblock(xfer->fd, TRUE);
497 ft = xfer->data;
499 if (read_line(xfer, buf, BUFFER_SIZE) < 0) {
500 raise_ft_socket_read_error_and_cancel(xfer);
501 return;
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",
507 buf, VER);
508 return;
511 if (write(xfer->fd,VER,strlen(VER)) == -1) {
512 raise_ft_socket_write_error_and_cancel(xfer);
513 return;
516 if (read_line(xfer, buf, BUFFER_SIZE) < 0) {
517 raise_ft_socket_read_error_and_cancel(xfer);
518 return;
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));
527 g_strfreev(parts);
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."));
535 return;
538 tmp = g_strdup_printf("FIL %lu\r\n",(long unsigned) xfer->size);
539 bytes_written = write(xfer->fd, tmp, strlen(tmp));
540 g_free(tmp);
542 if (bytes_written == -1) {
543 raise_ft_socket_write_error_and_cancel(xfer);
544 return;
547 // TFR
548 if (read_line(xfer,buf,BUFFER_SIZE) < 0) {
549 raise_ft_socket_read_error_and_cancel(xfer);
550 return;
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);
559 static void
560 sipe_ft_outgoing_stop(PurpleXfer *xfer)
562 sipe_file_transfer *ft = xfer->data;
563 gsize BUFFER_SIZE = 50;
564 char buffer[BUFFER_SIZE];
565 gchar *mac;
566 gsize mac_strlen;
568 // BYE
569 if (read_line(xfer, buffer, BUFFER_SIZE) < 0) {
570 raise_ft_socket_read_error_and_cancel(xfer);
571 return;
574 mac = sipe_hmac_finalize(ft->hmac_context);
575 g_sprintf(buffer, "MAC %s \r\n", mac);
576 g_free(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);
584 return;
587 sipe_ft_free_xfer_struct(xfer);
590 //******************************************************************************
592 void sipe_ft_incoming_transfer(PurpleAccount *account, struct sipmsg *msg, const GSList *body)
594 PurpleXfer *xfer;
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);
598 if (!session) {
599 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
600 session = sipe_session_find_im(sip, from);
601 g_free(from);
604 if (!session) {
605 purple_debug_error("sipe", "sipe_ft_incoming_transfer: can't find session for remote party\n");
606 return;
609 xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE, session->with);
611 if (xfer) {
612 size_t file_size;
613 sipe_file_transfer *ft = g_new0(sipe_file_transfer, 1);
614 ft->invitation_cookie = g_strdup(sipe_utils_nameval_find(body, "Invitation-Cookie"));
615 ft->sip = sip;
616 ft->dialog = sipe_dialog_find(session, session->with);
617 ft->listenfd = -1;
618 generate_key(ft->encryption_key, SIPE_FT_KEY_LENGTH);
619 generate_key(ft->hash_key, SIPE_FT_KEY_LENGTH);
620 xfer->data = ft;
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);
647 if (xfer) {
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;
656 if (auth_cookie)
657 ft->auth_cookie = g_ascii_strtoull(auth_cookie,NULL,10);
658 if (enc_key_b64) {
659 gsize ret_len;
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);
663 } else {
664 raise_ft_error_and_cancel(xfer,
665 _("Received encryption key has wrong size."));
666 g_free(enc_key);
667 return;
669 g_free(enc_key);
671 if (hash_key_b64) {
672 gsize ret_len;
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);
676 } else {
677 raise_ft_error_and_cancel(xfer,
678 _("Received hash key has wrong size."));
679 g_free(hash_key);
680 return;
682 g_free(hash_key);
685 if (ip && port_str) {
686 purple_xfer_start(xfer, -1, ip, g_ascii_strtoull(port_str,NULL,10));
687 } else {
688 ft->listener = purple_network_listen_range(SIPE_FT_TCP_PORT_MIN,
689 SIPE_FT_TCP_PORT_MAX,
690 SOCK_STREAM,
691 sipe_ft_listen_socket_created,
692 xfer);
693 if (!ft->listener) {
694 raise_ft_error_and_cancel(xfer,
695 _("Could not create listen socket"));
696 return;
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"
724 "Hash-Key: %s\r\n"
725 /*"IP-Address: %s\r\n"
726 "Port: 6900\r\n"
727 "PortX: 11178\r\n"
728 "Auth-Cookie: 11111111\r\n"
729 "Sender-Connect: TRUE\r\n"*/,
730 ft->invitation_cookie,
731 b64_encryption_key,
732 b64_hash_key
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",
738 body, dialog, NULL);
740 g_free(body);
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",
756 body, dialog, NULL);
758 g_free(body);
761 static ssize_t
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
767 return -2;
768 } else if (bytes_read == -1) {
769 if (errno == EAGAIN)
770 return 0;
771 else
772 return -1;
774 return bytes_read;
777 static gboolean
778 read_fully(PurpleXfer *xfer, guchar *buf, size_t len)
780 const gulong READ_TIMEOUT = 10000000;
781 gulong time_spent = 0;
783 while (len) {
784 ssize_t bytes_read = do_read(xfer, buf, len);
785 if (bytes_read == 0) {
786 g_usleep(100000);
787 time_spent += 100000;
788 } else if (bytes_read < 0 || time_spent > READ_TIMEOUT) {
789 return FALSE;
790 } else {
791 len -= bytes_read;
792 buf += bytes_read;
793 time_spent = 0;
796 return TRUE;
799 static gssize read_line(PurpleXfer *xfer, gchar *buffer, gssize size)
801 gssize pos = 0;
803 memset(buffer,0,size);
804 do {
805 if (!read_fully(xfer, (guchar*) buffer + pos, 1))
806 return -1;
807 } while (buffer[pos] != '\n' && ++pos != (size - 1));
809 if (pos == (size - 1) && buffer[pos - 1] != '\n') {
810 // Buffer too short
811 return -2;
814 return pos;
817 static void sipe_cipher_context_init(PurpleCipherContext **rc4_context, const guchar *enc_key)
820 * Decryption of file from SIPE file transfer
822 * Decryption:
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;
828 guchar k2[20];
830 /* 1.) SHA1 sum */
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)
846 * Count MAC digest
848 * HMAC digest:
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;
854 guchar k2[20];
856 /* 1.) SHA1 sum */
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)
880 gsize i;
881 for (i = 0; i != size; ++i)
882 buffer[i] = rand();
885 static void set_socket_nonblock(int fd, gboolean state)
887 int flags = fcntl(fd, F_GETFL, 0);
888 if (flags == -1)
889 flags = 0;
891 if (state == TRUE)
892 fcntl(fd, F_SETFL, flags | O_NONBLOCK);
893 else
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);
901 if (xfer) {
902 if (file != NULL)
903 purple_xfer_request_accepted(xfer, file);
904 else
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);
917 if (xfer) {
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);
922 ft->sip = sip;
924 xfer->data = ft;
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);
936 return xfer;
939 static
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);
952 xfer->watcher = 0;
953 close(listenfd);
954 ft->listenfd = -1;
956 if (fd < 0) {
957 raise_ft_socket_read_error_and_cancel(xfer);
958 } else {
959 purple_xfer_start(xfer, fd, NULL, 0);
963 static
964 void sipe_ft_listen_socket_created(int listenfd, gpointer data)
966 gchar *body;
967 PurpleXfer *xfer = data;
968 sipe_file_transfer *ft = xfer->data;
970 struct sockaddr_in addr;
972 socklen_t socklen = sizeof (addr);
974 ft->listener = NULL;
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"
986 "IP-Address: %s\r\n"
987 "Port: %u\r\n"
988 "PortX: 11178\r\n"
989 "AuthCookie: %u\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),
994 ft->auth_cookie);
996 if (!ft->dialog) {
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);
1002 if (ft->dialog) {
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);
1007 g_free(body);
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)
1016 #else
1017 # define HX_SIZE_OF_IFREQ(a) sizeof(a)
1018 #endif
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
1027 * are ignored.
1029 * Maybe this should be fixed in libpurple or some better solution found.
1031 static
1032 const char * sipe_ft_get_suitable_local_ip(int fd)
1034 int source = (fd >= 0) ? fd : socket(PF_INET,SOCK_STREAM, 0);
1036 if (source >= 0) {
1037 char buffer[1024];
1038 static char ip[16];
1039 char *tmp;
1040 struct ifconf ifc;
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);
1048 if (fd < 0)
1049 close(source);
1051 tmp = buffer;
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),
1067 ((add >> 8) & 255),
1068 add & 255);
1070 return ip;
1076 return "0.0.0.0";
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);
1085 list = NULL;
1087 g_strfreev(lines);
1088 return list;
1092 Local Variables:
1093 mode: c
1094 c-file-style: "bsd"
1095 indent-tabs-mode: t
1096 tab-width: 8
1097 End: