Merge branch 'mob' of git+ssh://localhost/srv/git/siplcs into mob
[siplcs.git] / src / core / sipe-ft.c
blobe72c849a31d2df05b48c79821940886547971b88
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>
28 #include <libpurple/debug.h>
30 #include "sipe.h"
31 #include "sipe-ft.h"
32 #include "sipe-dialog.h"
33 #include "sipe-nls.h"
34 #include "sipe-session.h"
35 #include "sipe-utils.h"
37 #ifndef _WIN32
38 #include <sys/socket.h>
39 #include <netinet/in.h>
40 #endif
42 #define SIPE_FT_KEY_LENGTH 24
44 #define SIPE_FT_TCP_PORT_MIN 6891
45 #define SIPE_FT_TCP_PORT_MAX 6901
47 struct _sipe_file_transfer {
48 guchar encryption_key[SIPE_FT_KEY_LENGTH];
49 guchar hash_key[SIPE_FT_KEY_LENGTH];
50 gchar *invitation_cookie;
51 unsigned auth_cookie;
52 struct sipe_account_data *sip;
53 struct sip_dialog *dialog;
54 PurpleCipherContext *cipher_context;
56 gsize bytes_remaining_chunk;
57 guchar* encrypted_outbuf;
58 guchar* outbuf_ptr;
59 gsize outbuf_size;
61 typedef struct _sipe_file_transfer sipe_file_transfer;
63 static void send_filetransfer_accept(PurpleXfer* xfer);
64 static void send_filetransfer_cancel(PurpleXfer* xfer);
65 static gssize read_line(int fd, gchar *buffer, gssize size);
66 static void sipe_cipher_context_init(PurpleCipherContext **rc4_context, const guchar *enc_key);
67 static gchar* sipe_get_mac(const guchar *data, size_t data_len, const guchar *hash_key);
68 static void generate_key(guchar *buffer, gsize size);
69 static void set_socket_nonblock(int fd, gboolean state);
70 static void sipe_ft_listen_socket_created(int listenfd, gpointer data);
71 static const char * sipe_ft_get_suitable_local_ip(int fd);
73 //******************************************************************************
74 // I/O operations for PurpleXfer structure
75 //******************************************************************************
77 static void
78 sipe_ft_incoming_init(PurpleXfer *xfer)
80 send_filetransfer_accept(xfer);
83 static void
84 sipe_ft_free_xfer_struct(PurpleXfer *xfer)
86 sipe_file_transfer *ft = xfer->data;
87 if (ft) {
88 struct sipe_account_data *sip = xfer->account->gc->proto_data;
90 g_hash_table_remove(sip->filetransfers,ft->invitation_cookie);
92 if (ft->cipher_context)
93 purple_cipher_context_destroy(ft->cipher_context);
95 g_free(ft->encrypted_outbuf);
96 g_free(ft->invitation_cookie);
97 g_free(ft);
98 xfer->data = NULL;
102 static void
103 sipe_ft_request_denied(PurpleXfer *xfer)
105 if (xfer->type == PURPLE_XFER_RECEIVE)
106 send_filetransfer_cancel(xfer);
107 sipe_ft_free_xfer_struct(xfer);
110 static
111 void raise_ft_error(PurpleXfer *xfer, const char *errmsg)
113 purple_xfer_error(purple_xfer_get_type(xfer),
114 xfer->account, xfer->who,
115 errmsg);
118 static
119 void raise_ft_strerror(PurpleXfer *xfer, const char *errmsg)
121 gchar *tmp = g_strdup_printf("%s: %s", errmsg, strerror(errno));
122 raise_ft_error(xfer, tmp);
123 g_free(tmp);
126 static
127 void raise_ft_error_and_cancel(PurpleXfer *xfer, const char *errmsg)
129 raise_ft_error(xfer, errmsg);
130 purple_xfer_cancel_local(xfer);
133 static
134 void raise_ft_socket_read_error_and_cancel(PurpleXfer *xfer)
136 raise_ft_error_and_cancel(xfer, _("Socket read failed"));
139 static
140 void raise_ft_socket_write_error_and_cancel(PurpleXfer *xfer)
142 raise_ft_error_and_cancel(xfer, _("Socket write failed"));
145 static void
146 sipe_ft_incoming_start(PurpleXfer *xfer)
148 sipe_file_transfer *ft;
149 static const gchar VER[] = "VER MSN_SECURE_FTP\r\n";
150 static const gchar TFR[] = "TFR\r\n";
151 const gsize BUFFER_SIZE = 50;
152 gchar buf[BUFFER_SIZE];
153 struct sipe_account_data *sip;
154 gchar* request;
155 const gsize FILE_SIZE_OFFSET = 4;
156 gsize file_size;
158 set_socket_nonblock(xfer->fd,FALSE);
160 ft = xfer->data;
162 if (write(xfer->fd,VER,strlen(VER)) == -1) {
163 raise_ft_socket_write_error_and_cancel(xfer);
164 return;
166 if (read(xfer->fd,buf,strlen(VER)) == -1) {
167 raise_ft_socket_read_error_and_cancel(xfer);
168 return;
171 sip = xfer->account->gc->proto_data;
173 request = g_strdup_printf("USR %s %u\r\n", sip->username, ft->auth_cookie);
174 if (write(xfer->fd,request,strlen(request)) == -1) {
175 raise_ft_socket_write_error_and_cancel(xfer);
176 g_free(request);
177 return;
179 g_free(request);
181 read_line(xfer->fd, buf, BUFFER_SIZE);
183 file_size = g_ascii_strtoull(buf + FILE_SIZE_OFFSET,NULL,10);
184 if (file_size != xfer->size) {
185 raise_ft_error_and_cancel(xfer,
186 _("File size is different from the advertised value."));
187 return;
190 if (write(xfer->fd,TFR,strlen(TFR)) == -1) {
191 raise_ft_socket_write_error_and_cancel(xfer);
192 return;
195 ft->bytes_remaining_chunk = 0;
197 set_socket_nonblock(xfer->fd,TRUE);
199 sipe_cipher_context_init(&ft->cipher_context, ft->encryption_key);
202 static void
203 sipe_ft_incoming_stop(PurpleXfer *xfer)
205 static const gchar BYE[] = "BYE 16777989\r\n";
206 gsize BUFFER_SIZE = 50;
207 char buffer[BUFFER_SIZE];
208 const gssize MAC_OFFSET = 4;
209 const gssize CRLF_LEN = 2;
210 gssize macLen;
211 FILE *fdread;
212 guchar *filebuf;
213 sipe_file_transfer *ft;
214 gchar *mac;
215 gchar *mac1;
217 set_socket_nonblock(xfer->fd,FALSE);
219 if (write(xfer->fd,BYE,strlen(BYE)) == -1) {
220 raise_ft_socket_write_error_and_cancel(xfer);
221 return;
224 macLen = read_line(xfer->fd,buffer,BUFFER_SIZE);
226 if (macLen < (MAC_OFFSET + CRLF_LEN)) {
227 raise_ft_error_and_cancel(xfer,
228 _("Received MAC is corrupted"));
229 return;
232 fflush(xfer->dest_fp);
234 // Check MAC
236 fdread = fopen(xfer->local_filename,"rb");
237 if (!fdread) {
238 raise_ft_error_and_cancel(xfer,
239 _("Unable to open received file."));
240 return;
243 filebuf = g_malloc(xfer->size);
244 if (!filebuf) {
245 fclose(fdread);
246 raise_ft_error_and_cancel(xfer,
247 _("Out of memory"));
248 purple_debug_error("sipe", "sipe_ft_incoming_stop: can't allocate %" G_GSIZE_FORMAT " bytes for file buffer\n",
249 xfer->size);
250 return;
253 if (fread(filebuf, 1, xfer->size, fdread) < 1) {
254 purple_debug_error("sipe", "sipe_ft_incoming_stop: can't read received file: %s\n",
255 strerror(errno));
256 g_free(filebuf);
257 fclose(fdread);
258 raise_ft_error_and_cancel(xfer,
259 _("Unable to read received file."));
260 return;
262 fclose(fdread);
264 ft = xfer->data;
266 mac = g_strndup(buffer + MAC_OFFSET, macLen - MAC_OFFSET - CRLF_LEN);
267 mac1 = sipe_get_mac(filebuf, xfer->size, ft->hash_key);
268 if (!sipe_strequal(mac, mac1)) {
269 unlink(xfer->local_filename);
270 raise_ft_error_and_cancel(xfer,
271 _("Received file is corrupted"));
273 g_free(mac);
274 g_free(filebuf);
276 sipe_ft_free_xfer_struct(xfer);
279 static gssize
280 sipe_ft_read(guchar **buffer, PurpleXfer *xfer)
282 gsize bytes_to_read;
283 ssize_t bytes_read;
285 sipe_file_transfer *ft = xfer->data;
287 if (ft->bytes_remaining_chunk == 0) {
288 guchar chunk_buf[3];
290 set_socket_nonblock(xfer->fd, FALSE);
292 if (read(xfer->fd,chunk_buf,3) == -1) {
293 raise_ft_strerror(xfer, _("Socket read failed"));
294 return -1;
297 ft->bytes_remaining_chunk = chunk_buf[1] + (chunk_buf[2] << 8);
298 set_socket_nonblock(xfer->fd, TRUE);
301 bytes_to_read = MIN(purple_xfer_get_bytes_remaining(xfer),
302 xfer->current_buffer_size);
303 bytes_to_read = MIN(bytes_to_read, ft->bytes_remaining_chunk);
305 *buffer = g_malloc(bytes_to_read);
306 if (!*buffer) {
307 raise_ft_error(xfer, _("Out of memory"));
308 purple_debug_error("sipe", "sipe_ft_read: can't allocate %" G_GSIZE_FORMAT " bytes for receive buffer\n",
309 bytes_to_read);
310 return -1;
313 bytes_read = read(xfer->fd, *buffer, bytes_to_read);
314 if (bytes_read == -1) {
315 if (errno == EAGAIN)
316 bytes_read = 0;
317 else {
318 raise_ft_strerror(xfer, _("Socket read failed"));
322 if (bytes_read > 0) {
323 guchar *decrypted = g_malloc(bytes_read);
325 if (!decrypted) {
326 raise_ft_error(xfer, _("Out of memory"));
327 purple_debug_error("sipe", "sipe_ft_read: can't allocate %" G_GSIZE_FORMAT " bytes for decryption buffer\n",
328 (gsize)bytes_read);
329 g_free(*buffer);
330 return -1;
332 purple_cipher_context_encrypt(ft->cipher_context, *buffer, bytes_read, decrypted, NULL);
333 g_free(*buffer);
334 *buffer = decrypted;
336 ft->bytes_remaining_chunk -= bytes_read;
339 return bytes_read;
342 static gssize
343 sipe_ft_write(const guchar *buffer, size_t size, PurpleXfer *xfer)
345 ssize_t bytes_written;
346 sipe_file_transfer *ft = xfer->data;
348 /* When sending data via server with ForeFront installed, block bigger than
349 * this default causes ending of transmission. Hard limit block to this value
350 * when libpurple sends us more data. */
351 const gsize DEFAULT_BLOCK_SIZE = 2045;
352 if (size > DEFAULT_BLOCK_SIZE)
353 size = DEFAULT_BLOCK_SIZE;
355 if (ft->bytes_remaining_chunk == 0) {
356 guchar chunk_buf[3];
358 if (ft->outbuf_size < size) {
359 g_free(ft->encrypted_outbuf);
360 ft->outbuf_size = size;
361 ft->encrypted_outbuf = g_malloc(ft->outbuf_size);
362 if (!ft->encrypted_outbuf) {
363 raise_ft_error(xfer, _("Out of memory"));
364 purple_debug_error("sipe", "sipe_ft_write: can't allocate %" G_GSIZE_FORMAT " bytes for send buffer\n",
365 ft->outbuf_size);
366 return -1;
370 ft->bytes_remaining_chunk = size;
371 ft->outbuf_ptr = ft->encrypted_outbuf;
372 purple_cipher_context_encrypt(ft->cipher_context, buffer, size,
373 ft->encrypted_outbuf, NULL);
375 chunk_buf[0] = 0;
376 chunk_buf[1] = ft->bytes_remaining_chunk & 0x00FF;
377 chunk_buf[2] = (ft->bytes_remaining_chunk & 0xFF00) >> 8;
379 set_socket_nonblock(xfer->fd, FALSE);
380 if (write(xfer->fd,chunk_buf,3) == -1) {
381 raise_ft_strerror(xfer, _("Socket write failed"));
382 return -1;
384 set_socket_nonblock(xfer->fd, TRUE);
387 bytes_written = write(xfer->fd, ft->outbuf_ptr, ft->bytes_remaining_chunk);
388 if (bytes_written == -1) {
389 if (errno == EAGAIN)
390 bytes_written = 0;
391 else {
392 raise_ft_strerror(xfer, _("Socket write failed"));
396 if (bytes_written > 0) {
397 ft->bytes_remaining_chunk -= bytes_written;
398 ft->outbuf_ptr += bytes_written;
401 if ((xfer->bytes_remaining - bytes_written) == 0)
402 purple_xfer_set_completed(xfer, TRUE);
404 return bytes_written;
407 static void
408 sipe_ft_outgoing_init(PurpleXfer *xfer)
410 struct sip_dialog *dialog;
411 sipe_file_transfer *ft = xfer->data;
413 gchar *body = g_strdup_printf("Application-Name: File Transfer\r\n"
414 "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n"
415 "Invitation-Command: INVITE\r\n"
416 "Invitation-Cookie: %s\r\n"
417 "Application-File: %s\r\n"
418 "Application-FileSize: %lu\r\n"
419 //"Connectivity: N\r\n" TODO
420 "Encryption: R\r\n", // TODO: non encrypted file transfer support
421 ft->invitation_cookie,
422 purple_xfer_get_filename(xfer),
423 (long unsigned) purple_xfer_get_size(xfer));
425 struct sipe_account_data *sip = xfer->account->gc->proto_data;
426 struct sip_session *session = sipe_session_find_or_add_im(sip, xfer->who);
428 g_hash_table_insert(sip->filetransfers,g_strdup(ft->invitation_cookie),xfer);
430 // Queue the message
431 sipe_session_enqueue_message(session, body, "text/x-msmsgsinvite");
433 dialog = sipe_dialog_find(session, xfer->who);
434 if (dialog && !dialog->outgoing_invite) {
435 ft->dialog = dialog;
436 sipe_im_process_queue(sip, session);
437 } else if (!dialog || !dialog->outgoing_invite) {
438 // Need to send the INVITE to get the outgoing dialog setup
439 sipe_invite(sip, session, xfer->who, body, "text/x-msmsgsinvite", NULL, FALSE);
442 g_free(body);
445 static void
446 sipe_ft_outgoing_start(PurpleXfer *xfer)
448 sipe_file_transfer *ft;
449 static const gchar VER[] = "VER MSN_SECURE_FTP\r\n";
450 const gsize BUFFER_SIZE = 50;
451 gchar buf[BUFFER_SIZE];
452 gchar** parts;
453 unsigned auth_cookie_received;
454 gboolean users_match;
455 gchar *tmp;
456 ssize_t bytes_written;
458 set_socket_nonblock(xfer->fd,FALSE);
460 ft = xfer->data;
462 memset(buf,0,BUFFER_SIZE);
463 if (read(xfer->fd,buf,strlen(VER)) == -1) {
464 raise_ft_socket_read_error_and_cancel(xfer);
465 return;
468 if (!sipe_strequal(buf,VER)) {
469 raise_ft_error_and_cancel(xfer,_("File transfer initialization failed."));
470 purple_debug_info("sipe","File transfer VER string incorrect, received: %s expected: %s",
471 buf, VER);
472 return;
475 if (write(xfer->fd,VER,strlen(VER)) == -1) {
476 raise_ft_socket_write_error_and_cancel(xfer);
477 return;
480 read_line(xfer->fd, buf, BUFFER_SIZE);
482 parts = g_strsplit(buf, " ", 3);
484 auth_cookie_received = g_ascii_strtoull(parts[2],NULL,10);
486 // xfer->who has 'sip:' prefix, skip these four characters
487 users_match = sipe_strequal(parts[1], (xfer->who + 4));
488 g_strfreev(parts);
490 purple_debug_info("sipe","File transfer authentication: %s Expected: USR %s %u\n",
491 buf, xfer->who + 4, ft->auth_cookie);
493 if (!users_match || (ft->auth_cookie != auth_cookie_received)) {
494 raise_ft_error_and_cancel(xfer,
495 _("File transfer authentication failed."));
496 return;
499 tmp = g_strdup_printf("FIL %lu\r\n",(long unsigned) xfer->size);
500 bytes_written = write(xfer->fd, tmp, strlen(tmp));
501 g_free(tmp);
503 if (bytes_written == -1) {
504 raise_ft_socket_write_error_and_cancel(xfer);
505 return;
508 // TFR
509 read_line(xfer->fd,buf,BUFFER_SIZE);
511 ft->bytes_remaining_chunk = 0;
513 set_socket_nonblock(xfer->fd,TRUE);
515 sipe_cipher_context_init(&ft->cipher_context, ft->encryption_key);
518 static void
519 sipe_ft_outgoing_stop(PurpleXfer *xfer)
521 gsize BUFFER_SIZE = 50;
522 char buffer[BUFFER_SIZE];
523 guchar *filebuf;
524 sipe_file_transfer *ft;
525 gchar *mac;
526 gsize mac_strlen;
528 set_socket_nonblock(xfer->fd,FALSE);
530 // BYE
531 read_line(xfer->fd, buffer, BUFFER_SIZE);
533 filebuf = g_malloc(xfer->size);
534 if (!filebuf) {
535 raise_ft_error_and_cancel(xfer,
536 _("Out of memory"));
537 purple_debug_error("sipe", "sipe_ft_outgoing_stop: can't allocate %" G_GSIZE_FORMAT " bytes for file buffer\n",
538 xfer->size);
539 return;
541 fseek(xfer->dest_fp,0,SEEK_SET);
542 if (fread(filebuf,xfer->size,1,xfer->dest_fp) < 1) {
543 purple_debug_error("sipe", "sipe_ft_outgoing_stop: can't read sent file: %s\n",
544 strerror(errno));
545 g_free(filebuf);
546 raise_ft_socket_read_error_and_cancel(xfer);
547 return;
550 ft = xfer->data;
551 mac = sipe_get_mac(filebuf,xfer->size,ft->hash_key);
552 g_free(filebuf);
553 g_sprintf(buffer, "MAC %s \r\n", mac);
554 g_free(mac);
556 mac_strlen = strlen(buffer);
557 // There must be this zero byte between mac and \r\n
558 buffer[mac_strlen - 3] = 0;
560 if (write(xfer->fd,buffer,mac_strlen) == -1) {
561 raise_ft_socket_write_error_and_cancel(xfer);
562 return;
565 sipe_ft_free_xfer_struct(xfer);
568 //******************************************************************************
570 void sipe_ft_incoming_transfer(PurpleAccount *account, struct sipmsg *msg)
572 PurpleXfer *xfer;
573 struct sipe_account_data *sip = account->gc->proto_data;
574 gchar *callid = sipmsg_find_header(msg, "Call-ID");
575 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
576 if (!session) {
577 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
578 session = sipe_session_find_im(sip, from);
579 g_free(from);
582 xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE, session->with);
584 if (xfer) {
585 size_t file_size;
586 sipe_file_transfer *ft = g_new0(sipe_file_transfer, 1);
587 ft->invitation_cookie = g_strdup(sipmsg_find_header(msg, "Invitation-Cookie"));
588 ft->sip = sip;
589 ft->dialog = sipe_dialog_find(session, session->with);
590 generate_key(ft->encryption_key, SIPE_FT_KEY_LENGTH);
591 generate_key(ft->hash_key, SIPE_FT_KEY_LENGTH);
592 xfer->data = ft;
594 purple_xfer_set_filename(xfer, sipmsg_find_header(msg,"Application-File"));
596 file_size = g_ascii_strtoull(sipmsg_find_header(msg,"Application-FileSize"),NULL,10);
597 purple_xfer_set_size(xfer, file_size);
599 purple_xfer_set_init_fnc(xfer, sipe_ft_incoming_init);
600 purple_xfer_set_start_fnc(xfer,sipe_ft_incoming_start);
601 purple_xfer_set_end_fnc(xfer,sipe_ft_incoming_stop);
602 purple_xfer_set_request_denied_fnc(xfer, sipe_ft_request_denied);
603 purple_xfer_set_read_fnc(xfer,sipe_ft_read);
604 purple_xfer_set_cancel_send_fnc(xfer,sipe_ft_free_xfer_struct);
605 purple_xfer_set_cancel_recv_fnc(xfer,sipe_ft_free_xfer_struct);
607 g_hash_table_insert(sip->filetransfers,g_strdup(ft->invitation_cookie),xfer);
609 send_sip_response(sip->gc, msg, 200, "OK", NULL);
611 purple_xfer_request(xfer);
615 void sipe_ft_incoming_accept(PurpleAccount *account, struct sipmsg *msg)
617 struct sipe_account_data *sip = account->gc->proto_data;
618 gchar *inv_cookie = sipmsg_find_header(msg,"Invitation-Cookie");
619 PurpleXfer *xfer = g_hash_table_lookup(sip->filetransfers,inv_cookie);
621 if (xfer) {
622 /* ip and port_str must be copied, because send_sip_response changes
623 * the headers and we need to use this values afterwards. */
624 gchar *ip = g_strdup(sipmsg_find_header(msg, "IP-Address"));
625 gchar *port_str = g_strdup(sipmsg_find_header(msg, "Port"));
626 gchar *auth_cookie = sipmsg_find_header(msg, "AuthCookie");
627 gchar *enc_key_b64 = sipmsg_find_header(msg, "Encryption-Key");
628 gchar *hash_key_b64 = sipmsg_find_header(msg, "Hash-Key");
630 sipe_file_transfer *ft = xfer->data;
632 if (auth_cookie)
633 ft->auth_cookie = g_ascii_strtoull(auth_cookie,NULL,10);
634 if (enc_key_b64) {
635 gsize ret_len;
636 guchar *enc_key = purple_base64_decode(enc_key_b64, &ret_len);
637 if (ret_len == SIPE_FT_KEY_LENGTH) {
638 memcpy(ft->encryption_key,enc_key,SIPE_FT_KEY_LENGTH);
639 } else {
640 raise_ft_error_and_cancel(xfer,
641 _("Received encryption key has wrong size."));
642 g_free(enc_key);
643 g_free(port_str);
644 g_free(ip);
645 return;
647 g_free(enc_key);
649 if (hash_key_b64) {
650 gsize ret_len;
651 guchar *hash_key = purple_base64_decode(hash_key_b64, &ret_len);
652 if (ret_len == SIPE_FT_KEY_LENGTH) {
653 memcpy(ft->hash_key,hash_key,SIPE_FT_KEY_LENGTH);
654 } else {
655 raise_ft_error_and_cancel(xfer,
656 _("Received hash key has wrong size."));
657 g_free(hash_key);
658 g_free(port_str);
659 g_free(ip);
660 return;
662 g_free(hash_key);
665 send_sip_response(sip->gc, msg, 200, "OK", NULL);
667 if (ip && port_str) {
668 purple_xfer_start(xfer, -1, ip, g_ascii_strtoull(port_str,NULL,10));
669 } else {
670 purple_network_listen_range(SIPE_FT_TCP_PORT_MIN, SIPE_FT_TCP_PORT_MAX,
671 SOCK_STREAM, sipe_ft_listen_socket_created,xfer);
674 g_free(port_str);
675 g_free(ip);
679 void sipe_ft_incoming_cancel(PurpleAccount *account, struct sipmsg *msg)
681 gchar *inv_cookie = g_strdup(sipmsg_find_header(msg, "Invitation-Cookie"));
683 struct sipe_account_data *sip = account->gc->proto_data;
684 PurpleXfer *xfer = g_hash_table_lookup(sip->filetransfers,inv_cookie);
686 send_sip_response(sip->gc, msg, 200, "OK", NULL);
688 purple_xfer_cancel_remote(xfer);
691 static void send_filetransfer_accept(PurpleXfer* xfer)
693 sipe_file_transfer* ft = xfer->data;
694 struct sip_dialog *dialog = ft->dialog;
696 gchar *b64_encryption_key = purple_base64_encode(ft->encryption_key,24);
697 gchar *b64_hash_key = purple_base64_encode(ft->hash_key,24);
699 gchar *body = g_strdup_printf("Invitation-Command: ACCEPT\r\n"
700 "Request-Data: IP-Address:\r\n"
701 "Invitation-Cookie: %s\r\n"
702 "Encryption-Key: %s\r\n"
703 "Hash-Key: %s\r\n"
704 /*"IP-Address: %s\r\n"
705 "Port: 6900\r\n"
706 "PortX: 11178\r\n"
707 "Auth-Cookie: 11111111\r\n"
708 "Sender-Connect: TRUE\r\n"*/,
709 ft->invitation_cookie,
710 b64_encryption_key,
711 b64_hash_key
712 /*,purple_network_get_my_ip(-1)*/
715 send_sip_request(ft->sip->gc, "MESSAGE", dialog->with, dialog->with,
716 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
717 body, dialog, NULL);
719 g_free(body);
720 g_free(b64_encryption_key);
721 g_free(b64_hash_key);
724 static void send_filetransfer_cancel(PurpleXfer* xfer) {
725 sipe_file_transfer* ft = xfer->data;
726 struct sip_dialog* dialog = ft->dialog;
728 gchar *body = g_strdup_printf("Invitation-Command: CANCEL\r\n"
729 "Invitation-Cookie: %s\r\n"
730 "Cancel-Code: REJECT\r\n",
731 ft->invitation_cookie);
733 send_sip_request(ft->sip->gc, "MESSAGE", dialog->with, dialog->with,
734 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
735 body, dialog, NULL);
737 g_free(body);
740 static gssize read_line(int fd, gchar *buffer, gssize size)
742 gssize pos = 0;
744 memset(buffer,0,size);
745 do {
746 if (read(fd,buffer + pos,1) == -1)
747 return -1;
748 } while (buffer[pos] != '\n' && ++pos < size);
750 return pos;
753 static void sipe_cipher_context_init(PurpleCipherContext **rc4_context, const guchar *enc_key)
756 * Decryption of file from SIPE file transfer
758 * Decryption:
759 * 1.) SHA1-Key = SHA1sum (Encryption-Key); Do SHA1 digest from Encryption-Key, return 20 bytes SHA1-Key.
760 * 2.) Decrypt-Data = RC4 (Encrypt-Data, substr(SHA1-Key, 0, 15)); Decryption of encrypted data, used 16 bytes SHA1-Key;
763 PurpleCipherContext *sha1_context;
764 guchar k2[20];
766 /* 1.) SHA1 sum */
767 sha1_context = purple_cipher_context_new_by_name("sha1", NULL);
768 purple_cipher_context_append(sha1_context, enc_key, SIPE_FT_KEY_LENGTH);
769 purple_cipher_context_digest(sha1_context, sizeof(k2), k2, NULL);
770 purple_cipher_context_destroy(sha1_context);
772 /* 2.) RC4 decryption */
773 *rc4_context = purple_cipher_context_new_by_name("rc4", NULL);
774 purple_cipher_context_set_option(*rc4_context, "key_len", (gpointer)0x10); // only 16 chars key used
775 purple_cipher_context_set_key(*rc4_context, k2);
779 static gchar* sipe_get_mac(const guchar *data, size_t data_len, const guchar *hash_key)
782 * Count MAC digest
784 * HMAC digest:
785 * 1.) SHA1-Key = SHA1sum (Hash-Key); Do SHA1 digest from Hash-Key, return 20 bytes SHA1-Key.
786 * 2.) MAC = HMAC_SHA1 (Decrypt-Data, substr(HMAC-Key,0,15)); Digest of decrypted file and SHA1-Key (used again only 16 bytes)
789 PurpleCipherContext *sha1_context;
790 PurpleCipherContext *hmac_context;
791 guchar hmac_digest[20];
792 guchar k2[20];
794 /* 1.) SHA1 sum */
795 sha1_context = purple_cipher_context_new_by_name("sha1", NULL);
796 purple_cipher_context_append(sha1_context, hash_key, SIPE_FT_KEY_LENGTH);
797 purple_cipher_context_digest(sha1_context, sizeof(k2), k2, NULL);
798 purple_cipher_context_destroy(sha1_context);
800 /* 2.) HMAC check */
801 hmac_context = purple_cipher_context_new_by_name("hmac", NULL);
802 purple_cipher_context_set_option(hmac_context, "hash", "sha1");
803 purple_cipher_context_set_key_with_len(hmac_context, k2, 16);
804 purple_cipher_context_append(hmac_context, data, data_len);
805 purple_cipher_context_digest(hmac_context, sizeof(hmac_digest), hmac_digest, NULL);
806 purple_cipher_context_destroy(hmac_context);
808 return purple_base64_encode(hmac_digest, sizeof (hmac_digest));
811 static void generate_key(guchar *buffer, gsize size)
813 gsize i;
814 for (i = 0; i != size; ++i)
815 buffer[i] = rand();
818 static void set_socket_nonblock(int fd, gboolean state)
820 int flags = fcntl(fd, F_GETFL, 0);
821 if (flags == -1)
822 flags = 0;
824 if (state == TRUE)
825 fcntl(fd, F_SETFL, flags | O_NONBLOCK);
826 else
827 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
830 void sipe_ft_send_file(PurpleConnection *gc, const char *who, const char *file)
832 PurpleXfer *xfer;
834 xfer = sipe_ft_new_xfer(gc, who);
836 if (file != NULL)
837 purple_xfer_request_accepted(xfer, file);
838 else
839 purple_xfer_request(xfer);
842 PurpleXfer * sipe_ft_new_xfer(PurpleConnection *gc, const char *who)
844 PurpleAccount *account = purple_connection_get_account(gc);
845 PurpleXfer *xfer = purple_xfer_new(account, PURPLE_XFER_SEND, who);
847 if (xfer) {
848 struct sipe_account_data *sip = purple_connection_get_protocol_data(gc);
850 sipe_file_transfer *ft = g_new0(sipe_file_transfer, 1);
851 ft->invitation_cookie = g_strdup_printf("%u", rand() % 1000000000);
852 ft->sip = sip;
854 xfer->data = ft;
856 purple_xfer_set_init_fnc(xfer, sipe_ft_outgoing_init);
857 purple_xfer_set_start_fnc(xfer,sipe_ft_outgoing_start);
858 purple_xfer_set_end_fnc(xfer,sipe_ft_outgoing_stop);
859 purple_xfer_set_request_denied_fnc(xfer, sipe_ft_request_denied);
860 purple_xfer_set_write_fnc(xfer,sipe_ft_write);
861 purple_xfer_set_cancel_send_fnc(xfer,sipe_ft_free_xfer_struct);
862 purple_xfer_set_cancel_recv_fnc(xfer,sipe_ft_free_xfer_struct);
865 return xfer;
868 static
869 void sipe_ft_client_connected(gpointer p_xfer, gint listenfd,
870 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
872 struct sockaddr_in saddr;
873 socklen_t slen = sizeof (saddr);
875 int fd = accept(listenfd, (struct sockaddr*)&saddr, &slen);
877 PurpleXfer *xfer = p_xfer;
879 purple_input_remove(xfer->watcher);
880 xfer->watcher = 0;
881 close(listenfd);
883 purple_xfer_start(xfer,fd,NULL,0);
886 static
887 void sipe_ft_listen_socket_created(int listenfd, gpointer data)
889 gchar *body;
890 PurpleXfer *xfer = data;
891 sipe_file_transfer *ft = xfer->data;
893 struct sockaddr_in addr;
895 socklen_t socklen = sizeof (addr);
897 getsockname(listenfd, (struct sockaddr*)&addr, &socklen);
899 xfer->watcher = purple_input_add(listenfd, PURPLE_INPUT_READ,
900 sipe_ft_client_connected, xfer);
902 ft->auth_cookie = rand() % 1000000000;
904 body = g_strdup_printf("Invitation-Command: ACCEPT\r\n"
905 "Invitation-Cookie: %s\r\n"
906 "IP-Address: %s\r\n"
907 "Port: %u\r\n"
908 "PortX: 11178\r\n"
909 "AuthCookie: %u\r\n"
910 "Request-Data: IP-Address:\r\n",
911 ft->invitation_cookie,
912 sipe_ft_get_suitable_local_ip(listenfd),
913 ntohs(addr.sin_port),
914 ft->auth_cookie);
916 if (!ft->dialog) {
917 struct sipe_account_data *sip = xfer->account->gc->proto_data;
918 struct sip_session *session = sipe_session_find_or_add_im(sip, xfer->who);
919 ft->dialog = sipe_dialog_find(session, xfer->who);
922 if (ft->dialog) {
923 send_sip_request(ft->sip->gc, "MESSAGE", ft->dialog->with, ft->dialog->with,
924 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
925 body, ft->dialog, NULL);
927 g_free(body);
930 #ifndef _WIN32
931 #include <net/if.h>
932 #include <sys/ioctl.h>
933 #else
934 #include <nspapi.h>
935 #endif
938 * Calling sizeof(struct ifreq) isn't always correct on
939 * Mac OS X (and maybe others).
941 #ifdef _SIZEOF_ADDR_IFREQ
942 # define HX_SIZE_OF_IFREQ(a) _SIZEOF_ADDR_IFREQ(a)
943 #else
944 # define HX_SIZE_OF_IFREQ(a) sizeof(a)
945 #endif
948 * Returns local IP address suitable for connection.
950 * purple_network_get_my_ip() will not do this, because it might return an
951 * address within 169.254.x.x range that was assigned to interface disconnected
952 * from the network (when multiple network adapters are available). This is a
953 * copy-paste from libpurple's network.c, only change is that link local addresses
954 * are ignored.
956 * Maybe this should be fixed in libpurple or some better solution found.
958 static
959 const char * sipe_ft_get_suitable_local_ip(int fd)
961 int source = (fd >= 0) ? fd : socket(PF_INET,SOCK_STREAM, 0);
963 if (source >= 0) {
964 char buffer[1024];
965 static char ip[16];
966 char *tmp;
967 struct ifconf ifc;
968 guint32 lhost = htonl(127 * 256 * 256 * 256 + 1);
969 guint32 llocal = htonl((169 << 24) + (254 << 16));
971 ifc.ifc_len = sizeof(buffer);
972 ifc.ifc_req = (struct ifreq *)buffer;
973 ioctl(source, SIOCGIFCONF, &ifc);
975 if (fd < 0)
976 close(source);
978 tmp = buffer;
979 while (tmp < buffer + ifc.ifc_len)
981 struct ifreq *ifr = (struct ifreq *)tmp;
982 tmp += HX_SIZE_OF_IFREQ(*ifr);
984 if (ifr->ifr_addr.sa_family == AF_INET)
986 struct sockaddr_in *sinptr = (struct sockaddr_in *)&ifr->ifr_addr;
987 if (sinptr->sin_addr.s_addr != lhost
988 && (sinptr->sin_addr.s_addr & htonl(0xFFFF0000)) != llocal)
990 long unsigned int add = ntohl(sinptr->sin_addr.s_addr);
991 g_snprintf(ip, 16, "%lu.%lu.%lu.%lu",
992 ((add >> 24) & 255),
993 ((add >> 16) & 255),
994 ((add >> 8) & 255),
995 add & 255);
997 return ip;
1003 return "0.0.0.0";
1007 Local Variables:
1008 mode: c
1009 c-file-style: "bsd"
1010 indent-tabs-mode: t
1011 tab-width: 8
1012 End: