filetransfer: don't leak listening socket on cancel
[siplcs.git] / src / core / sipe-ft.c
blob2db25353c410bc6764df15ab9afa9dde8936cd81
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 #include <string.h>
25 #include <fcntl.h>
26 #include <errno.h>
27 #include <glib/gprintf.h>
29 #include "debug.h"
31 #include "sipe.h"
32 #include "sipe-ft.h"
33 #include "sipe-dialog.h"
34 #include "sipe-nls.h"
35 #include "sipe-session.h"
36 #include "sipe-utils.h"
38 #ifndef _WIN32
39 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #endif
43 #define SIPE_FT_KEY_LENGTH 24
45 #define SIPE_FT_TCP_PORT_MIN 6891
46 #define SIPE_FT_TCP_PORT_MAX 6901
48 struct _sipe_file_transfer {
49 guchar encryption_key[SIPE_FT_KEY_LENGTH];
50 guchar hash_key[SIPE_FT_KEY_LENGTH];
51 gchar *invitation_cookie;
52 unsigned auth_cookie;
53 struct sipe_account_data *sip;
54 struct sip_dialog *dialog;
55 PurpleCipherContext *cipher_context;
56 PurpleCipherContext *hmac_context;
58 PurpleNetworkListenData *listener;
59 int listenfd;
61 gsize bytes_remaining_chunk;
62 guchar* encrypted_outbuf;
63 guchar* outbuf_ptr;
64 gsize outbuf_size;
66 typedef struct _sipe_file_transfer sipe_file_transfer;
68 static void send_filetransfer_accept(PurpleXfer* xfer);
69 static void send_filetransfer_cancel(PurpleXfer* xfer);
70 static gssize read_line(int fd, gchar *buffer, gssize size);
71 static void sipe_cipher_context_init(PurpleCipherContext **rc4_context, const guchar *enc_key);
72 static void sipe_hmac_context_init(PurpleCipherContext **hmac_context, const guchar *hash_key);
73 static gchar *sipe_hmac_finalize(PurpleCipherContext *hmac_context);
74 static void generate_key(guchar *buffer, gsize size);
75 static void set_socket_nonblock(int fd, gboolean state);
76 static void sipe_ft_listen_socket_created(int listenfd, gpointer data);
77 static const char * sipe_ft_get_suitable_local_ip(int fd);
79 //******************************************************************************
80 // I/O operations for PurpleXfer structure
81 //******************************************************************************
83 static void
84 sipe_ft_incoming_init(PurpleXfer *xfer)
86 send_filetransfer_accept(xfer);
89 static void
90 sipe_ft_free_xfer_struct(PurpleXfer *xfer)
92 sipe_file_transfer *ft = xfer->data;
93 if (ft) {
94 struct sipe_account_data *sip = xfer->account->gc->proto_data;
96 g_hash_table_remove(sip->filetransfers,ft->invitation_cookie);
98 if (xfer->watcher) {
99 purple_input_remove(xfer->watcher);
100 xfer->watcher = 0;
102 if (ft->listenfd >= 0) {
103 purple_debug_info("sipe", "sipe_ft_free_xfer_struct: closing listening socket %d\n", ft->listenfd);
104 close(ft->listenfd);
106 if (ft->listener)
107 purple_network_listen_cancel(ft->listener);
108 if (ft->cipher_context)
109 purple_cipher_context_destroy(ft->cipher_context);
111 if (ft->hmac_context)
112 purple_cipher_context_destroy(ft->hmac_context);
114 g_free(ft->encrypted_outbuf);
115 g_free(ft->invitation_cookie);
116 g_free(ft);
117 xfer->data = NULL;
121 static void
122 sipe_ft_request_denied(PurpleXfer *xfer)
124 if (xfer->type == PURPLE_XFER_RECEIVE)
125 send_filetransfer_cancel(xfer);
126 sipe_ft_free_xfer_struct(xfer);
129 static
130 void raise_ft_error(PurpleXfer *xfer, const char *errmsg)
132 purple_xfer_error(purple_xfer_get_type(xfer),
133 xfer->account, xfer->who,
134 errmsg);
137 static
138 void raise_ft_strerror(PurpleXfer *xfer, const char *errmsg)
140 gchar *tmp = g_strdup_printf("%s: %s", errmsg, strerror(errno));
141 raise_ft_error(xfer, tmp);
142 g_free(tmp);
145 static
146 void raise_ft_error_and_cancel(PurpleXfer *xfer, const char *errmsg)
148 raise_ft_error(xfer, errmsg);
149 purple_xfer_cancel_local(xfer);
152 static
153 void raise_ft_socket_read_error_and_cancel(PurpleXfer *xfer)
155 raise_ft_error_and_cancel(xfer, _("Socket read failed"));
158 static
159 void raise_ft_socket_write_error_and_cancel(PurpleXfer *xfer)
161 raise_ft_error_and_cancel(xfer, _("Socket write failed"));
164 static void
165 sipe_ft_incoming_start(PurpleXfer *xfer)
167 sipe_file_transfer *ft;
168 static const gchar VER[] = "VER MSN_SECURE_FTP\r\n";
169 static const gchar TFR[] = "TFR\r\n";
170 const gsize BUFFER_SIZE = 50;
171 gchar buf[BUFFER_SIZE];
172 struct sipe_account_data *sip;
173 gchar* request;
174 const gsize FILE_SIZE_OFFSET = 4;
175 gsize file_size;
177 set_socket_nonblock(xfer->fd,FALSE);
179 ft = xfer->data;
181 if (write(xfer->fd,VER,strlen(VER)) == -1) {
182 raise_ft_socket_write_error_and_cancel(xfer);
183 return;
185 if (read(xfer->fd,buf,strlen(VER)) == -1) {
186 raise_ft_socket_read_error_and_cancel(xfer);
187 return;
190 sip = xfer->account->gc->proto_data;
192 request = g_strdup_printf("USR %s %u\r\n", sip->username, ft->auth_cookie);
193 if (write(xfer->fd,request,strlen(request)) == -1) {
194 raise_ft_socket_write_error_and_cancel(xfer);
195 g_free(request);
196 return;
198 g_free(request);
200 read_line(xfer->fd, buf, BUFFER_SIZE);
202 file_size = g_ascii_strtoull(buf + FILE_SIZE_OFFSET,NULL,10);
203 if (file_size != xfer->size) {
204 raise_ft_error_and_cancel(xfer,
205 _("File size is different from the advertised value."));
206 return;
209 if (write(xfer->fd,TFR,strlen(TFR)) == -1) {
210 raise_ft_socket_write_error_and_cancel(xfer);
211 return;
214 ft->bytes_remaining_chunk = 0;
216 set_socket_nonblock(xfer->fd,TRUE);
218 sipe_cipher_context_init(&ft->cipher_context, ft->encryption_key);
219 sipe_hmac_context_init(&ft->hmac_context, ft->hash_key);
222 static void
223 sipe_ft_incoming_stop(PurpleXfer *xfer)
225 static const gchar BYE[] = "BYE 16777989\r\n";
226 gsize BUFFER_SIZE = 50;
227 char buffer[BUFFER_SIZE];
228 const gssize MAC_OFFSET = 4;
229 const gssize CRLF_LEN = 2;
230 gssize macLen;
231 sipe_file_transfer *ft;
232 gchar *mac;
233 gchar *mac1;
235 set_socket_nonblock(xfer->fd,FALSE);
237 if (write(xfer->fd,BYE,strlen(BYE)) == -1) {
238 raise_ft_socket_write_error_and_cancel(xfer);
239 return;
242 macLen = read_line(xfer->fd,buffer,BUFFER_SIZE);
244 if (macLen < (MAC_OFFSET + CRLF_LEN)) {
245 raise_ft_error_and_cancel(xfer,
246 _("Received MAC is corrupted"));
247 return;
250 // Check MAC
251 ft = xfer->data;
252 mac = g_strndup(buffer + MAC_OFFSET, macLen - MAC_OFFSET - CRLF_LEN);
253 mac1 = sipe_hmac_finalize(ft->hmac_context);
254 if (!sipe_strequal(mac, mac1)) {
255 unlink(xfer->local_filename);
256 raise_ft_error_and_cancel(xfer,
257 _("Received file is corrupted"));
259 g_free(mac1);
260 g_free(mac);
262 sipe_ft_free_xfer_struct(xfer);
265 static gssize
266 sipe_ft_read(guchar **buffer, PurpleXfer *xfer)
268 gsize bytes_to_read;
269 ssize_t bytes_read;
271 sipe_file_transfer *ft = xfer->data;
273 if (ft->bytes_remaining_chunk == 0) {
274 guchar chunk_buf[3];
276 set_socket_nonblock(xfer->fd, FALSE);
278 if (read(xfer->fd,chunk_buf,3) == -1) {
279 raise_ft_strerror(xfer, _("Socket read failed"));
280 return -1;
283 ft->bytes_remaining_chunk = chunk_buf[1] + (chunk_buf[2] << 8);
284 set_socket_nonblock(xfer->fd, TRUE);
287 bytes_to_read = MIN(purple_xfer_get_bytes_remaining(xfer),
288 xfer->current_buffer_size);
289 bytes_to_read = MIN(bytes_to_read, ft->bytes_remaining_chunk);
291 *buffer = g_malloc(bytes_to_read);
292 if (!*buffer) {
293 raise_ft_error(xfer, _("Out of memory"));
294 purple_debug_error("sipe", "sipe_ft_read: can't allocate %" G_GSIZE_FORMAT " bytes for receive buffer\n",
295 bytes_to_read);
296 return -1;
299 bytes_read = read(xfer->fd, *buffer, bytes_to_read);
300 if (bytes_read == -1) {
301 if (errno == EAGAIN)
302 bytes_read = 0;
303 else {
304 raise_ft_strerror(xfer, _("Socket read failed"));
308 if (bytes_read > 0) {
309 guchar *decrypted = g_malloc(bytes_read);
311 if (!decrypted) {
312 raise_ft_error(xfer, _("Out of memory"));
313 purple_debug_error("sipe", "sipe_ft_read: can't allocate %" G_GSIZE_FORMAT " bytes for decryption buffer\n",
314 (gsize)bytes_read);
315 g_free(*buffer);
316 *buffer = NULL;
317 return -1;
319 purple_cipher_context_encrypt(ft->cipher_context, *buffer, bytes_read, decrypted, NULL);
320 g_free(*buffer);
321 *buffer = decrypted;
323 purple_cipher_context_append(ft->hmac_context, decrypted, bytes_read);
325 ft->bytes_remaining_chunk -= bytes_read;
328 return bytes_read;
331 static gssize
332 sipe_ft_write(const guchar *buffer, size_t size, PurpleXfer *xfer)
334 ssize_t bytes_written;
335 sipe_file_transfer *ft = xfer->data;
337 /* When sending data via server with ForeFront installed, block bigger than
338 * this default causes ending of transmission. Hard limit block to this value
339 * when libpurple sends us more data. */
340 const gsize DEFAULT_BLOCK_SIZE = 2045;
341 if (size > DEFAULT_BLOCK_SIZE)
342 size = DEFAULT_BLOCK_SIZE;
344 if (ft->bytes_remaining_chunk == 0) {
345 ssize_t bytes_read;
346 guchar local_buf[16];
347 memset(local_buf, 0, sizeof local_buf);
349 // Check if receiver did not cancel the transfer before it is finished
350 bytes_read = read(xfer->fd,local_buf,sizeof (local_buf));
351 if (bytes_read == -1 && errno != EAGAIN) {
352 raise_ft_strerror(xfer, _("Socket read failed"));
353 return -1;
354 } else if (bytes_read != 0) {
355 if ( g_str_has_prefix((gchar*)local_buf,"CCL\r\n")
356 || g_str_has_prefix((gchar*)local_buf,"BYE 2164261682\r\n")) {
357 return -1;
361 if (ft->outbuf_size < size) {
362 g_free(ft->encrypted_outbuf);
363 ft->outbuf_size = size;
364 ft->encrypted_outbuf = g_malloc(ft->outbuf_size);
365 if (!ft->encrypted_outbuf) {
366 raise_ft_error(xfer, _("Out of memory"));
367 purple_debug_error("sipe", "sipe_ft_write: can't allocate %" G_GSIZE_FORMAT " bytes for send buffer\n",
368 ft->outbuf_size);
369 return -1;
373 ft->bytes_remaining_chunk = size;
374 ft->outbuf_ptr = ft->encrypted_outbuf;
375 purple_cipher_context_encrypt(ft->cipher_context, buffer, size,
376 ft->encrypted_outbuf, NULL);
377 purple_cipher_context_append(ft->hmac_context, buffer, size);
379 local_buf[0] = 0;
380 local_buf[1] = ft->bytes_remaining_chunk & 0x00FF;
381 local_buf[2] = (ft->bytes_remaining_chunk & 0xFF00) >> 8;
383 set_socket_nonblock(xfer->fd, FALSE);
384 if (write(xfer->fd,local_buf,3) == -1) {
385 raise_ft_strerror(xfer, _("Socket write failed"));
386 return -1;
388 set_socket_nonblock(xfer->fd, TRUE);
391 bytes_written = write(xfer->fd, ft->outbuf_ptr, ft->bytes_remaining_chunk);
392 if (bytes_written == -1) {
393 if (errno == EAGAIN)
394 bytes_written = 0;
395 else {
396 raise_ft_strerror(xfer, _("Socket write failed"));
400 if (bytes_written > 0) {
401 ft->bytes_remaining_chunk -= bytes_written;
402 ft->outbuf_ptr += bytes_written;
405 if ((xfer->bytes_remaining - bytes_written) == 0)
406 purple_xfer_set_completed(xfer, TRUE);
408 return bytes_written;
411 static void
412 sipe_ft_outgoing_init(PurpleXfer *xfer)
414 struct sip_dialog *dialog;
415 sipe_file_transfer *ft = xfer->data;
417 gchar *body = g_strdup_printf("Application-Name: File Transfer\r\n"
418 "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n"
419 "Invitation-Command: INVITE\r\n"
420 "Invitation-Cookie: %s\r\n"
421 "Application-File: %s\r\n"
422 "Application-FileSize: %lu\r\n"
423 //"Connectivity: N\r\n" TODO
424 "Encryption: R\r\n", // TODO: non encrypted file transfer support
425 ft->invitation_cookie,
426 purple_xfer_get_filename(xfer),
427 (long unsigned) purple_xfer_get_size(xfer));
429 struct sipe_account_data *sip = xfer->account->gc->proto_data;
430 struct sip_session *session = sipe_session_find_or_add_im(sip, xfer->who);
432 g_hash_table_insert(sip->filetransfers,g_strdup(ft->invitation_cookie),xfer);
434 // Queue the message
435 sipe_session_enqueue_message(session, body, "text/x-msmsgsinvite");
437 dialog = sipe_dialog_find(session, xfer->who);
438 if (dialog && !dialog->outgoing_invite) {
439 ft->dialog = dialog;
440 sipe_im_process_queue(sip, session);
441 } else if (!dialog || !dialog->outgoing_invite) {
442 // Need to send the INVITE to get the outgoing dialog setup
443 sipe_invite(sip, session, xfer->who, body, "text/x-msmsgsinvite", NULL, FALSE);
446 g_free(body);
449 static void
450 sipe_ft_outgoing_start(PurpleXfer *xfer)
452 sipe_file_transfer *ft;
453 static const gchar VER[] = "VER MSN_SECURE_FTP\r\n";
454 const gsize BUFFER_SIZE = 50;
455 gchar buf[BUFFER_SIZE];
456 gchar** parts;
457 unsigned auth_cookie_received;
458 gboolean users_match;
459 gchar *tmp;
460 ssize_t bytes_written;
462 set_socket_nonblock(xfer->fd,FALSE);
464 ft = xfer->data;
466 memset(buf,0,BUFFER_SIZE);
467 if (read(xfer->fd,buf,strlen(VER)) == -1) {
468 raise_ft_socket_read_error_and_cancel(xfer);
469 return;
472 if (!sipe_strequal(buf,VER)) {
473 raise_ft_error_and_cancel(xfer,_("File transfer initialization failed."));
474 purple_debug_info("sipe","File transfer VER string incorrect, received: %s expected: %s",
475 buf, VER);
476 return;
479 if (write(xfer->fd,VER,strlen(VER)) == -1) {
480 raise_ft_socket_write_error_and_cancel(xfer);
481 return;
484 read_line(xfer->fd, buf, BUFFER_SIZE);
486 parts = g_strsplit(buf, " ", 3);
488 auth_cookie_received = g_ascii_strtoull(parts[2],NULL,10);
490 // xfer->who has 'sip:' prefix, skip these four characters
491 users_match = !g_ascii_strcasecmp(parts[1], (xfer->who + 4));
492 g_strfreev(parts);
494 purple_debug_info("sipe","File transfer authentication: %s Expected: USR %s %u\n",
495 buf, xfer->who + 4, ft->auth_cookie);
497 if (!users_match || (ft->auth_cookie != auth_cookie_received)) {
498 raise_ft_error_and_cancel(xfer,
499 _("File transfer authentication failed."));
500 return;
503 tmp = g_strdup_printf("FIL %lu\r\n",(long unsigned) xfer->size);
504 bytes_written = write(xfer->fd, tmp, strlen(tmp));
505 g_free(tmp);
507 if (bytes_written == -1) {
508 raise_ft_socket_write_error_and_cancel(xfer);
509 return;
512 // TFR
513 read_line(xfer->fd,buf,BUFFER_SIZE);
515 ft->bytes_remaining_chunk = 0;
517 set_socket_nonblock(xfer->fd,TRUE);
519 sipe_cipher_context_init(&ft->cipher_context, ft->encryption_key);
520 sipe_hmac_context_init(&ft->hmac_context, ft->hash_key);
523 static void
524 sipe_ft_outgoing_stop(PurpleXfer *xfer)
526 sipe_file_transfer *ft = xfer->data;
527 gsize BUFFER_SIZE = 50;
528 char buffer[BUFFER_SIZE];
529 gchar *mac;
530 gsize mac_strlen;
532 set_socket_nonblock(xfer->fd,FALSE);
534 // BYE
535 read_line(xfer->fd, buffer, BUFFER_SIZE);
537 mac = sipe_hmac_finalize(ft->hmac_context);
538 g_sprintf(buffer, "MAC %s \r\n", mac);
539 g_free(mac);
541 mac_strlen = strlen(buffer);
542 // There must be this zero byte between mac and \r\n
543 buffer[mac_strlen - 3] = 0;
545 if (write(xfer->fd,buffer,mac_strlen) == -1) {
546 raise_ft_socket_write_error_and_cancel(xfer);
547 return;
550 sipe_ft_free_xfer_struct(xfer);
553 //******************************************************************************
555 void sipe_ft_incoming_transfer(PurpleAccount *account, struct sipmsg *msg, const GSList *body)
557 PurpleXfer *xfer;
558 struct sipe_account_data *sip = account->gc->proto_data;
559 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
560 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
561 if (!session) {
562 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
563 session = sipe_session_find_im(sip, from);
564 g_free(from);
567 xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE, session->with);
569 if (xfer) {
570 size_t file_size;
571 sipe_file_transfer *ft = g_new0(sipe_file_transfer, 1);
572 ft->invitation_cookie = g_strdup(sipe_utils_nameval_find(body, "Invitation-Cookie"));
573 ft->sip = sip;
574 ft->dialog = sipe_dialog_find(session, session->with);
575 ft->listenfd = -1;
576 generate_key(ft->encryption_key, SIPE_FT_KEY_LENGTH);
577 generate_key(ft->hash_key, SIPE_FT_KEY_LENGTH);
578 xfer->data = ft;
580 purple_xfer_set_filename(xfer, sipe_utils_nameval_find(body, "Application-File"));
582 file_size = g_ascii_strtoull(sipe_utils_nameval_find(body, "Application-FileSize"),NULL,10);
583 purple_xfer_set_size(xfer, file_size);
585 purple_xfer_set_init_fnc(xfer, sipe_ft_incoming_init);
586 purple_xfer_set_start_fnc(xfer,sipe_ft_incoming_start);
587 purple_xfer_set_end_fnc(xfer,sipe_ft_incoming_stop);
588 purple_xfer_set_request_denied_fnc(xfer, sipe_ft_request_denied);
589 purple_xfer_set_read_fnc(xfer,sipe_ft_read);
590 purple_xfer_set_cancel_send_fnc(xfer,sipe_ft_free_xfer_struct);
591 purple_xfer_set_cancel_recv_fnc(xfer,sipe_ft_free_xfer_struct);
593 g_hash_table_insert(sip->filetransfers,g_strdup(ft->invitation_cookie),xfer);
595 purple_xfer_request(xfer);
599 void sipe_ft_incoming_accept(PurpleAccount *account, const GSList *body)
601 struct sipe_account_data *sip = account->gc->proto_data;
602 const gchar *inv_cookie = sipe_utils_nameval_find(body, "Invitation-Cookie");
603 PurpleXfer *xfer = g_hash_table_lookup(sip->filetransfers,inv_cookie);
605 if (xfer) {
606 const gchar *ip = sipe_utils_nameval_find(body, "IP-Address");
607 const gchar *port_str = sipe_utils_nameval_find(body, "Port");
608 const gchar *auth_cookie = sipe_utils_nameval_find(body, "AuthCookie");
609 const gchar *enc_key_b64 = sipe_utils_nameval_find(body, "Encryption-Key");
610 const gchar *hash_key_b64 = sipe_utils_nameval_find(body, "Hash-Key");
612 sipe_file_transfer *ft = xfer->data;
614 if (auth_cookie)
615 ft->auth_cookie = g_ascii_strtoull(auth_cookie,NULL,10);
616 if (enc_key_b64) {
617 gsize ret_len;
618 guchar *enc_key = purple_base64_decode(enc_key_b64, &ret_len);
619 if (ret_len == SIPE_FT_KEY_LENGTH) {
620 memcpy(ft->encryption_key,enc_key,SIPE_FT_KEY_LENGTH);
621 } else {
622 raise_ft_error_and_cancel(xfer,
623 _("Received encryption key has wrong size."));
624 g_free(enc_key);
625 return;
627 g_free(enc_key);
629 if (hash_key_b64) {
630 gsize ret_len;
631 guchar *hash_key = purple_base64_decode(hash_key_b64, &ret_len);
632 if (ret_len == SIPE_FT_KEY_LENGTH) {
633 memcpy(ft->hash_key,hash_key,SIPE_FT_KEY_LENGTH);
634 } else {
635 raise_ft_error_and_cancel(xfer,
636 _("Received hash key has wrong size."));
637 g_free(hash_key);
638 return;
640 g_free(hash_key);
643 if (ip && port_str) {
644 purple_xfer_start(xfer, -1, ip, g_ascii_strtoull(port_str,NULL,10));
645 } else {
646 ft->listener = purple_network_listen_range(SIPE_FT_TCP_PORT_MIN,
647 SIPE_FT_TCP_PORT_MAX,
648 SOCK_STREAM,
649 sipe_ft_listen_socket_created,
650 xfer);
651 if (!ft->listener) {
652 raise_ft_error_and_cancel(xfer,
653 _("Could not create listen socket"));
654 return;
660 void sipe_ft_incoming_cancel(PurpleAccount *account, GSList *body)
662 gchar *inv_cookie = g_strdup(sipe_utils_nameval_find(body, "Invitation-Cookie"));
664 struct sipe_account_data *sip = account->gc->proto_data;
665 PurpleXfer *xfer = g_hash_table_lookup(sip->filetransfers,inv_cookie);
667 purple_xfer_cancel_remote(xfer);
670 static void send_filetransfer_accept(PurpleXfer* xfer)
672 sipe_file_transfer* ft = xfer->data;
673 struct sip_dialog *dialog = ft->dialog;
675 gchar *b64_encryption_key = purple_base64_encode(ft->encryption_key,24);
676 gchar *b64_hash_key = purple_base64_encode(ft->hash_key,24);
678 gchar *body = g_strdup_printf("Invitation-Command: ACCEPT\r\n"
679 "Request-Data: IP-Address:\r\n"
680 "Invitation-Cookie: %s\r\n"
681 "Encryption-Key: %s\r\n"
682 "Hash-Key: %s\r\n"
683 /*"IP-Address: %s\r\n"
684 "Port: 6900\r\n"
685 "PortX: 11178\r\n"
686 "Auth-Cookie: 11111111\r\n"
687 "Sender-Connect: TRUE\r\n"*/,
688 ft->invitation_cookie,
689 b64_encryption_key,
690 b64_hash_key
691 /*,purple_network_get_my_ip(-1)*/
694 send_sip_request(ft->sip->gc, "MESSAGE", dialog->with, dialog->with,
695 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
696 body, dialog, NULL);
698 g_free(body);
699 g_free(b64_encryption_key);
700 g_free(b64_hash_key);
703 static void send_filetransfer_cancel(PurpleXfer* xfer) {
704 sipe_file_transfer* ft = xfer->data;
705 struct sip_dialog* dialog = ft->dialog;
707 gchar *body = g_strdup_printf("Invitation-Command: CANCEL\r\n"
708 "Invitation-Cookie: %s\r\n"
709 "Cancel-Code: REJECT\r\n",
710 ft->invitation_cookie);
712 send_sip_request(ft->sip->gc, "MESSAGE", dialog->with, dialog->with,
713 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
714 body, dialog, NULL);
716 g_free(body);
719 static gssize read_line(int fd, gchar *buffer, gssize size)
721 gssize pos = 0;
723 memset(buffer,0,size);
724 do {
725 if (read(fd,buffer + pos,1) == -1)
726 return -1;
727 } while (buffer[pos] != '\n' && ++pos < size);
729 return pos;
732 static void sipe_cipher_context_init(PurpleCipherContext **rc4_context, const guchar *enc_key)
735 * Decryption of file from SIPE file transfer
737 * Decryption:
738 * 1.) SHA1-Key = SHA1sum (Encryption-Key); Do SHA1 digest from Encryption-Key, return 20 bytes SHA1-Key.
739 * 2.) Decrypt-Data = RC4 (Encrypt-Data, substr(SHA1-Key, 0, 15)); Decryption of encrypted data, used 16 bytes SHA1-Key;
742 PurpleCipherContext *sha1_context;
743 guchar k2[20];
745 /* 1.) SHA1 sum */
746 sha1_context = purple_cipher_context_new_by_name("sha1", NULL);
747 purple_cipher_context_append(sha1_context, enc_key, SIPE_FT_KEY_LENGTH);
748 purple_cipher_context_digest(sha1_context, sizeof(k2), k2, NULL);
749 purple_cipher_context_destroy(sha1_context);
751 /* 2.) RC4 decryption */
752 *rc4_context = purple_cipher_context_new_by_name("rc4", NULL);
753 purple_cipher_context_set_option(*rc4_context, "key_len", (gpointer)0x10); // only 16 chars key used
754 purple_cipher_context_set_key(*rc4_context, k2);
758 static void sipe_hmac_context_init(PurpleCipherContext **hmac_context, const guchar *hash_key)
761 * Count MAC digest
763 * HMAC digest:
764 * 1.) SHA1-Key = SHA1sum (Hash-Key); Do SHA1 digest from Hash-Key, return 20 bytes SHA1-Key.
765 * 2.) MAC = HMAC_SHA1 (Decrypt-Data, substr(HMAC-Key,0,15)); Digest of decrypted file and SHA1-Key (used again only 16 bytes)
768 PurpleCipherContext *sha1_context;
769 guchar k2[20];
771 /* 1.) SHA1 sum */
772 sha1_context = purple_cipher_context_new_by_name("sha1", NULL);
773 purple_cipher_context_append(sha1_context, hash_key, SIPE_FT_KEY_LENGTH);
774 purple_cipher_context_digest(sha1_context, sizeof(k2), k2, NULL);
775 purple_cipher_context_destroy(sha1_context);
777 /* 2.) HMAC (initialization only) */
778 *hmac_context = purple_cipher_context_new_by_name("hmac", NULL);
779 purple_cipher_context_set_option(*hmac_context, "hash", "sha1");
780 purple_cipher_context_set_key_with_len(*hmac_context, k2, 16);
783 static gchar* sipe_hmac_finalize(PurpleCipherContext *hmac_context)
785 guchar hmac_digest[20];
787 /* MAC = Digest of decrypted file and SHA1-Key (used again only 16 bytes) */
788 purple_cipher_context_digest(hmac_context, sizeof(hmac_digest), hmac_digest, NULL);
790 return purple_base64_encode(hmac_digest, sizeof (hmac_digest));
793 static void generate_key(guchar *buffer, gsize size)
795 gsize i;
796 for (i = 0; i != size; ++i)
797 buffer[i] = rand();
800 static void set_socket_nonblock(int fd, gboolean state)
802 int flags = fcntl(fd, F_GETFL, 0);
803 if (flags == -1)
804 flags = 0;
806 if (state == TRUE)
807 fcntl(fd, F_SETFL, flags | O_NONBLOCK);
808 else
809 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
812 void sipe_ft_send_file(PurpleConnection *gc, const char *who, const char *file)
814 PurpleXfer *xfer;
816 xfer = sipe_ft_new_xfer(gc, who);
818 if (file != NULL)
819 purple_xfer_request_accepted(xfer, file);
820 else
821 purple_xfer_request(xfer);
824 PurpleXfer * sipe_ft_new_xfer(PurpleConnection *gc, const char *who)
826 PurpleAccount *account = purple_connection_get_account(gc);
827 PurpleXfer *xfer = purple_xfer_new(account, PURPLE_XFER_SEND, who);
829 if (xfer) {
830 struct sipe_account_data *sip = purple_connection_get_protocol_data(gc);
832 sipe_file_transfer *ft = g_new0(sipe_file_transfer, 1);
833 ft->invitation_cookie = g_strdup_printf("%u", rand() % 1000000000);
834 ft->sip = sip;
836 xfer->data = ft;
838 purple_xfer_set_init_fnc(xfer, sipe_ft_outgoing_init);
839 purple_xfer_set_start_fnc(xfer,sipe_ft_outgoing_start);
840 purple_xfer_set_end_fnc(xfer,sipe_ft_outgoing_stop);
841 purple_xfer_set_request_denied_fnc(xfer, sipe_ft_request_denied);
842 purple_xfer_set_write_fnc(xfer,sipe_ft_write);
843 purple_xfer_set_cancel_send_fnc(xfer,sipe_ft_free_xfer_struct);
844 purple_xfer_set_cancel_recv_fnc(xfer,sipe_ft_free_xfer_struct);
847 return xfer;
850 static
851 void sipe_ft_client_connected(gpointer p_xfer, gint listenfd,
852 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
854 struct sockaddr_in saddr;
855 socklen_t slen = sizeof (saddr);
857 int fd = accept(listenfd, (struct sockaddr*)&saddr, &slen);
859 PurpleXfer *xfer = p_xfer;
860 sipe_file_transfer *ft = xfer->data;
862 purple_input_remove(xfer->watcher);
863 xfer->watcher = 0;
864 close(listenfd);
865 ft->listenfd = -1;
867 purple_xfer_start(xfer,fd,NULL,0);
870 static
871 void sipe_ft_listen_socket_created(int listenfd, gpointer data)
873 gchar *body;
874 PurpleXfer *xfer = data;
875 sipe_file_transfer *ft = xfer->data;
877 struct sockaddr_in addr;
879 socklen_t socklen = sizeof (addr);
881 ft->listener = NULL;
882 ft->listenfd = listenfd;
884 getsockname(listenfd, (struct sockaddr*)&addr, &socklen);
886 xfer->watcher = purple_input_add(listenfd, PURPLE_INPUT_READ,
887 sipe_ft_client_connected, xfer);
889 ft->auth_cookie = rand() % 1000000000;
891 body = g_strdup_printf("Invitation-Command: ACCEPT\r\n"
892 "Invitation-Cookie: %s\r\n"
893 "IP-Address: %s\r\n"
894 "Port: %u\r\n"
895 "PortX: 11178\r\n"
896 "AuthCookie: %u\r\n"
897 "Request-Data: IP-Address:\r\n",
898 ft->invitation_cookie,
899 sipe_ft_get_suitable_local_ip(listenfd),
900 ntohs(addr.sin_port),
901 ft->auth_cookie);
903 if (!ft->dialog) {
904 struct sipe_account_data *sip = xfer->account->gc->proto_data;
905 struct sip_session *session = sipe_session_find_or_add_im(sip, xfer->who);
906 ft->dialog = sipe_dialog_find(session, xfer->who);
909 if (ft->dialog) {
910 send_sip_request(ft->sip->gc, "MESSAGE", ft->dialog->with, ft->dialog->with,
911 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
912 body, ft->dialog, NULL);
914 g_free(body);
917 #ifndef _WIN32
918 #include <net/if.h>
919 #include <sys/ioctl.h>
920 #else
921 #include <nspapi.h>
922 #endif
925 * Calling sizeof(struct ifreq) isn't always correct on
926 * Mac OS X (and maybe others).
928 #ifdef _SIZEOF_ADDR_IFREQ
929 # define HX_SIZE_OF_IFREQ(a) _SIZEOF_ADDR_IFREQ(a)
930 #else
931 # define HX_SIZE_OF_IFREQ(a) sizeof(a)
932 #endif
935 * Returns local IP address suitable for connection.
937 * purple_network_get_my_ip() will not do this, because it might return an
938 * address within 169.254.x.x range that was assigned to interface disconnected
939 * from the network (when multiple network adapters are available). This is a
940 * copy-paste from libpurple's network.c, only change is that link local addresses
941 * are ignored.
943 * Maybe this should be fixed in libpurple or some better solution found.
945 static
946 const char * sipe_ft_get_suitable_local_ip(int fd)
948 int source = (fd >= 0) ? fd : socket(PF_INET,SOCK_STREAM, 0);
950 if (source >= 0) {
951 char buffer[1024];
952 static char ip[16];
953 char *tmp;
954 struct ifconf ifc;
955 guint32 lhost = htonl(127 * 256 * 256 * 256 + 1);
956 guint32 llocal = htonl((169 << 24) + (254 << 16));
958 ifc.ifc_len = sizeof(buffer);
959 ifc.ifc_req = (struct ifreq *)buffer;
960 ioctl(source, SIOCGIFCONF, &ifc);
962 if (fd < 0)
963 close(source);
965 tmp = buffer;
966 while (tmp < buffer + ifc.ifc_len)
968 struct ifreq *ifr = (struct ifreq *)tmp;
969 tmp += HX_SIZE_OF_IFREQ(*ifr);
971 if (ifr->ifr_addr.sa_family == AF_INET)
973 struct sockaddr_in *sinptr = (struct sockaddr_in *)&ifr->ifr_addr;
974 if (sinptr->sin_addr.s_addr != lhost
975 && (sinptr->sin_addr.s_addr & htonl(0xFFFF0000)) != llocal)
977 long unsigned int add = ntohl(sinptr->sin_addr.s_addr);
978 g_snprintf(ip, 16, "%lu.%lu.%lu.%lu",
979 ((add >> 24) & 255),
980 ((add >> 16) & 255),
981 ((add >> 8) & 255),
982 add & 255);
984 return ip;
990 return "0.0.0.0";
993 GSList * sipe_ft_parse_msg_body(const gchar *body)
995 GSList *list = NULL;
996 gchar **lines = g_strsplit(body, "\r\n", 0);
997 if (sipe_utils_parse_lines(&list, lines) == FALSE) {
998 sipe_utils_nameval_free(list);
999 list = NULL;
1001 g_strfreev(lines);
1002 return list;
1006 Local Variables:
1007 mode: c
1008 c-file-style: "bsd"
1009 indent-tabs-mode: t
1010 tab-width: 8
1011 End: