purple: remove purple >= 2.7.0 flagging
[siplcs.git] / src / core / sipe-ft.c
blob919097db15a1f7c1a68a0f0d5926aeab5d127506
1 /**
2 * @file sipe-ft.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2016 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010 Jakub Adam <jakub.adam@ktknet.cz>
8 * Copyright (C) 2010 Tomáš Hrabčík <tomas.hrabcik@tieto.com>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
29 #include <stdlib.h>
30 #include <string.h>
32 #include <glib.h>
34 #include "sipmsg.h"
35 #include "sip-transport.h"
36 #include "sipe-backend.h"
37 #include "sipe-common.h"
38 #include "sipe-core.h"
39 #include "sipe-core-private.h"
40 #include "sipe-crypt.h"
41 #include "sipe-dialog.h"
42 #include "sipe-digest.h"
43 #include "sipe-ft.h"
44 #include "sipe-ft-lync.h"
45 #include "sipe-ft-tftp.h"
46 #include "sipe-im.h"
47 #include "sipe-nls.h"
48 #include "sipe-session.h"
49 #include "sipe-utils.h"
52 * DO NOT CHANGE THE FOLLOWING CONSTANTS!!!
54 * It seems that Microsoft Office Communicator client will accept
55 * file transfer invitations *only* within this port range!
57 * If a firewall is active on your system you need to open these ports if
58 * you want to *send* files to other users. Receiving files uses an outgoing
59 * connection and should therefore automatically penetrate your firewall.
61 #define SIPE_FT_TCP_PORT_MIN 6891
62 #define SIPE_FT_TCP_PORT_MAX 6901
64 static void
65 ft_outgoing_init(struct sipe_file_transfer *ft, const gchar *filename,
66 gsize size, const gchar *who);
68 void sipe_ft_raise_error_and_cancel(struct sipe_file_transfer_private *ft_private,
69 const gchar *errmsg)
71 sipe_backend_ft_error(SIPE_FILE_TRANSFER_PUBLIC, errmsg);
72 sipe_backend_ft_cancel_local(SIPE_FILE_TRANSFER_PUBLIC);
75 static void generate_key(guchar *buffer, gsize size)
77 gsize i = 0;
78 while (i < size) buffer[i++] = rand();
81 static struct sipe_file_transfer *
82 sipe_file_transfer_new_outgoing(struct sipe_core_private *sipe_private)
84 struct sipe_file_transfer_private *ft_private;
86 ft_private = g_new0(struct sipe_file_transfer_private, 1);
88 ft_private->sipe_private = sipe_private;
90 ft_private->public.ft_init = ft_outgoing_init;
91 ft_private->public.ft_start = sipe_ft_tftp_start_sending;
92 ft_private->public.ft_write = sipe_ft_tftp_write;
93 ft_private->public.ft_cancelled = sipe_ft_free;
94 ft_private->public.ft_end = sipe_ft_tftp_stop_sending;
96 ft_private->invitation_cookie = g_strdup_printf("%u",
97 rand() % 1000000000);
99 return SIPE_FILE_TRANSFER_PUBLIC;
102 struct sipe_file_transfer *
103 sipe_core_ft_create_outgoing(struct sipe_core_public *sipe_public,
104 const gchar *who,
105 const gchar *file)
107 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
108 struct sipe_file_transfer *ft;
110 #ifdef HAVE_XDATA
111 if (SIPE_CORE_PRIVATE_FLAG_IS(LYNC2013)) {
112 ft = sipe_file_transfer_lync_new_outgoing(sipe_private);
113 } else
114 #endif
116 ft = sipe_file_transfer_new_outgoing(sipe_private);
119 if (!ft) {
120 SIPE_DEBUG_ERROR_NOFORMAT("Couldn't initialize core file "
121 "transfer structure");
122 return NULL;
125 sipe_backend_ft_outgoing(sipe_public, ft, who, file);
127 return ft;
130 void
131 sipe_ft_free(struct sipe_file_transfer *ft)
133 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
134 struct sip_dialog *dialog = ft_private->dialog;
136 if (dialog)
137 dialog->filetransfers =
138 g_slist_remove(dialog->filetransfers, ft_private);
140 if (ft->backend_private)
141 sipe_backend_ft_deallocate(ft);
143 if (ft_private->listendata)
144 sipe_backend_network_listen_cancel(ft_private->listendata);
146 if (ft_private->cipher_context)
147 sipe_crypt_ft_destroy(ft_private->cipher_context);
149 if (ft_private->hmac_context)
150 sipe_digest_ft_destroy(ft_private->hmac_context);
152 g_free(ft_private->invitation_cookie);
153 g_free(ft_private->encrypted_outbuf);
154 g_free(ft_private);
157 static void sipe_ft_request(struct sipe_file_transfer_private *ft_private,
158 const gchar *body)
160 struct sip_dialog *dialog = ft_private->dialog;
161 sip_transport_request(ft_private->sipe_private,
162 "MESSAGE",
163 dialog->with,
164 dialog->with,
165 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
166 body,
167 dialog,
168 NULL);
171 static void
172 ft_request_denied(struct sipe_file_transfer *ft)
174 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
176 gchar *body = g_strdup_printf("Invitation-Command: CANCEL\r\n"
177 "Invitation-Cookie: %s\r\n"
178 "Cancel-Code: REJECT\r\n",
179 ft_private->invitation_cookie);
180 sipe_ft_request(ft_private, body);
181 g_free(body);
183 sipe_ft_free(ft);
186 static void
187 send_ft_accept(struct sipe_file_transfer_private *ft_private,
188 gboolean send_enc_key,
189 gboolean send_connect_data,
190 gboolean sender_connect)
192 GString *body = g_string_new("");
194 g_string_append_printf(body,
195 "Invitation-Command: ACCEPT\r\n"
196 "Request-Data: IP-Address:\r\n"
197 "Invitation-Cookie: %s\r\n",
198 ft_private->invitation_cookie);
200 if (send_enc_key) {
201 gchar *b64_encryption_key;
202 gchar *b64_hash_key;
204 b64_encryption_key = g_base64_encode(ft_private->encryption_key,
205 SIPE_FT_KEY_LENGTH);
206 b64_hash_key = g_base64_encode(ft_private->hash_key,
207 SIPE_FT_KEY_LENGTH);
209 g_string_append_printf(body,
210 "Encryption-Key: %s\r\n"
211 "Hash-Key: %s\r\n",
212 b64_encryption_key,
213 b64_hash_key);
215 g_free(b64_hash_key);
216 g_free(b64_encryption_key);
219 if (send_connect_data) {
220 struct sipe_core_private *sipe_private = ft_private->sipe_private;
222 g_string_append_printf(body,
223 "IP-Address: %s\r\n"
224 "Port: %d\r\n"
225 "PortX: 11178\r\n"
226 "AuthCookie: %u\r\n",
227 sip_transport_ip_address(sipe_private),
228 ft_private->port,
229 ft_private->auth_cookie);
232 if (sender_connect) {
233 g_string_append(body,
234 "Sender-Connect: TRUE\r\n");
237 sipe_ft_request(ft_private, body->str);
239 g_string_free(body, TRUE);
242 static void
243 listen_socket_created_cb(unsigned short port, gpointer data)
245 struct sipe_file_transfer *ft = data;
247 SIPE_FILE_TRANSFER_PRIVATE->port = port;
248 SIPE_FILE_TRANSFER_PRIVATE->auth_cookie = rand() % 1000000000;
250 if (sipe_backend_ft_is_incoming(ft))
251 send_ft_accept(SIPE_FILE_TRANSFER_PRIVATE, TRUE, TRUE, TRUE);
252 else
253 send_ft_accept(SIPE_FILE_TRANSFER_PRIVATE, FALSE, TRUE, FALSE);
256 static void
257 client_connected_cb(struct sipe_backend_fd *fd, gpointer data)
259 struct sipe_file_transfer *ft = data;
261 SIPE_FILE_TRANSFER_PRIVATE->listendata = NULL;
263 if (!sipe_backend_fd_is_valid(fd)) {
264 sipe_backend_ft_error(ft, _("Socket read failed"));
265 sipe_backend_ft_cancel_local(ft);
266 } else {
267 sipe_backend_ft_start(ft, fd, NULL, 0);
270 sipe_backend_fd_free(fd);
273 static void
274 ft_incoming_init(struct sipe_file_transfer *ft,
275 SIPE_UNUSED_PARAMETER const gchar *filename,
276 SIPE_UNUSED_PARAMETER gsize size,
277 SIPE_UNUSED_PARAMETER const gchar *who)
279 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
281 if (ft_private->peer_using_nat) {
282 ft_private->listendata =
283 sipe_backend_network_listen_range(SIPE_FT_TCP_PORT_MIN,
284 SIPE_FT_TCP_PORT_MAX,
285 listen_socket_created_cb,
286 client_connected_cb,
287 ft);
288 } else {
289 send_ft_accept(ft_private, TRUE, FALSE, FALSE);
293 static void
294 ft_outgoing_init(struct sipe_file_transfer *ft, const gchar *filename,
295 gsize size, const gchar *who)
297 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
298 struct sipe_core_private *sipe_private = ft_private->sipe_private;
299 struct sip_dialog *dialog;
301 const gchar *ip = sip_transport_ip_address(sipe_private);
302 gchar *body = g_strdup_printf("Application-Name: File Transfer\r\n"
303 "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n"
304 "Invitation-Command: INVITE\r\n"
305 "Invitation-Cookie: %s\r\n"
306 "Application-File: %s\r\n"
307 "Application-FileSize: %" G_GSIZE_FORMAT "\r\n"
308 "%s"
309 "Encryption: R\r\n", // TODO: non encrypted file transfer support
310 ft_private->invitation_cookie,
311 filename,
312 size,
313 sipe_utils_ip_is_private(ip) ? "Connectivity: N\r\n" : "");
315 struct sip_session *session = sipe_session_find_or_add_im(sipe_private, who);
317 // Queue the message
318 sipe_session_enqueue_message(session, body, "text/x-msmsgsinvite");
320 dialog = sipe_dialog_find(session, who);
321 if (dialog && !dialog->outgoing_invite) {
322 sipe_im_process_queue(sipe_private, session);
323 } else if (!dialog || !dialog->outgoing_invite) {
324 // Need to send the INVITE to get the outgoing dialog setup
325 sipe_im_invite(sipe_private, session, who, body, "text/x-msmsgsinvite", NULL, FALSE);
326 dialog = sipe_dialog_find(session, who);
329 dialog->filetransfers = g_slist_append(dialog->filetransfers, ft_private);
330 ft_private->dialog = dialog;
332 g_free(body);
335 void sipe_ft_incoming_transfer(struct sipe_core_private *sipe_private,
336 struct sip_dialog *dialog,
337 const GSList *body)
339 struct sipe_file_transfer_private *ft_private;
340 gsize file_size;
342 ft_private = g_new0(struct sipe_file_transfer_private, 1);
343 ft_private->sipe_private = sipe_private;
345 ft_private->public.ft_init = ft_incoming_init;
346 ft_private->public.ft_start = sipe_ft_tftp_start_receiving;
347 ft_private->public.ft_read = sipe_ft_tftp_read;
348 ft_private->public.ft_cancelled = sipe_ft_free;
349 ft_private->public.ft_end = sipe_ft_tftp_stop_receiving;
350 ft_private->public.ft_request_denied = ft_request_denied;
352 generate_key(ft_private->encryption_key, SIPE_FT_KEY_LENGTH);
353 generate_key(ft_private->hash_key, SIPE_FT_KEY_LENGTH);
355 ft_private->invitation_cookie = g_strdup(sipe_utils_nameval_find(body, "Invitation-Cookie"));
356 ft_private->peer_using_nat = sipe_strequal(sipe_utils_nameval_find(body, "Connectivity"), "N");
358 ft_private->dialog = dialog;
360 file_size = g_ascii_strtoull(sipe_utils_nameval_find(body,
361 "Application-FileSize"),
362 NULL, 10);
363 sipe_backend_ft_incoming(SIPE_CORE_PUBLIC,
364 SIPE_FILE_TRANSFER_PUBLIC,
365 dialog->with,
366 sipe_utils_nameval_find(body, "Application-File"),
367 file_size);
369 if (ft_private->public.backend_private != NULL) {
370 ft_private->dialog->filetransfers = g_slist_append(ft_private->dialog->filetransfers, ft_private);
371 } else {
372 sipe_ft_free(SIPE_FILE_TRANSFER_PUBLIC);
376 static struct sipe_file_transfer_private *
377 sipe_find_ft(const struct sip_dialog *dialog, const gchar *inv_cookie)
379 GSList *ftlist = dialog->filetransfers;
380 for (; ftlist != NULL; ftlist = ftlist->next) {
381 struct sipe_file_transfer_private *ft_private = ftlist->data;
382 if (sipe_strequal(ft_private->invitation_cookie, inv_cookie))
383 return ft_private;
385 return NULL;
388 void sipe_ft_incoming_accept(struct sip_dialog *dialog, const GSList *body)
390 const gchar *inv_cookie = sipe_utils_nameval_find(body, "Invitation-Cookie");
391 struct sipe_file_transfer_private *ft_private = sipe_find_ft(dialog, inv_cookie);
393 if (ft_private) {
394 const gchar *ip = sipe_utils_nameval_find(body, "IP-Address");
395 const gchar *port_str = sipe_utils_nameval_find(body, "Port");
396 const gchar *auth_cookie = sipe_utils_nameval_find(body, "AuthCookie");
397 const gchar *enc_key_b64 = sipe_utils_nameval_find(body, "Encryption-Key");
398 const gchar *hash_key_b64 = sipe_utils_nameval_find(body, "Hash-Key");
400 if (auth_cookie)
401 ft_private->auth_cookie = g_ascii_strtoull(auth_cookie,
402 NULL, 10);
403 if (enc_key_b64) {
404 gsize ret_len;
405 guchar *enc_key = g_base64_decode(enc_key_b64,
406 &ret_len);
407 if (ret_len == SIPE_FT_KEY_LENGTH) {
408 memcpy(ft_private->encryption_key,
409 enc_key, SIPE_FT_KEY_LENGTH);
410 } else {
411 sipe_ft_raise_error_and_cancel(ft_private,
412 _("Received encryption key has wrong size."));
413 g_free(enc_key);
414 return;
416 g_free(enc_key);
418 if (hash_key_b64) {
419 gsize ret_len;
420 guchar *hash_key = g_base64_decode(hash_key_b64,
421 &ret_len);
422 if (ret_len == SIPE_FT_KEY_LENGTH) {
423 memcpy(ft_private->hash_key,
424 hash_key, SIPE_FT_KEY_LENGTH);
425 } else {
426 sipe_ft_raise_error_and_cancel(ft_private,
427 _("Received hash key has wrong size."));
428 g_free(hash_key);
429 return;
431 g_free(hash_key);
435 if (ip && port_str) {
436 sipe_backend_ft_start(SIPE_FILE_TRANSFER_PUBLIC, NULL, ip,
437 g_ascii_strtoull(port_str, NULL, 10));
438 } else {
439 ft_private->listendata =
440 sipe_backend_network_listen_range(SIPE_FT_TCP_PORT_MIN,
441 SIPE_FT_TCP_PORT_MAX,
442 listen_socket_created_cb,
443 client_connected_cb,
444 ft_private);
445 if (!ft_private->listendata)
446 sipe_ft_raise_error_and_cancel(ft_private,
447 _("Could not create listen socket"));
452 void sipe_ft_incoming_cancel(struct sip_dialog *dialog, const GSList *body)
454 const gchar *inv_cookie = sipe_utils_nameval_find(body, "Invitation-Cookie");
455 struct sipe_file_transfer_private *ft_private = sipe_find_ft(dialog, inv_cookie);
457 if (ft_private)
458 sipe_backend_ft_cancel_remote(SIPE_FILE_TRANSFER_PUBLIC);
461 GSList *sipe_ft_parse_msg_body(const gchar *body)
463 GSList *list = NULL;
464 gchar **lines = g_strsplit(body, "\r\n", 0);
465 if (sipe_utils_parse_lines(&list, lines, ":") == FALSE) {
466 sipe_utils_nameval_free(list);
467 list = NULL;
469 g_strfreev(lines);
470 return list;
474 Local Variables:
475 mode: c
476 c-file-style: "bsd"
477 indent-tabs-mode: t
478 tab-width: 8
479 End: