filetransfer: use 'end' callback in sipe_file_transfer
[siplcs.git] / src / core / sipe-ft.c
blob9a1bb44fa21d4e4509814edea461f9221b134911
1 /**
2 * @file sipe-ft.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2015 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-tftp.h"
45 #include "sipe-im.h"
46 #include "sipe-nls.h"
47 #include "sipe-session.h"
48 #include "sipe-utils.h"
51 * DO NOT CHANGE THE FOLLOWING CONSTANTS!!!
53 * It seems that Microsoft Office Communicator client will accept
54 * file transfer invitations *only* within this port range!
56 * If a firewall is active on your system you need to open these ports if
57 * you want to *send* files to other users. Receiving files uses an outgoing
58 * connection and should therefore automatically penetrate your firewall.
60 #define SIPE_FT_TCP_PORT_MIN 6891
61 #define SIPE_FT_TCP_PORT_MAX 6901
63 static void
64 ft_outgoing_init(struct sipe_file_transfer *ft, const gchar *filename,
65 gsize size, const gchar *who);
67 void sipe_ft_raise_error_and_cancel(struct sipe_file_transfer_private *ft_private,
68 const gchar *errmsg)
70 sipe_backend_ft_error(SIPE_FILE_TRANSFER_PUBLIC, errmsg);
71 sipe_backend_ft_cancel_local(SIPE_FILE_TRANSFER_PUBLIC);
74 static void generate_key(guchar *buffer, gsize size)
76 gsize i = 0;
77 while (i < size) buffer[i++] = rand();
80 struct sipe_file_transfer *sipe_core_ft_allocate(struct sipe_core_public *sipe_public)
82 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
83 struct sipe_file_transfer_private *ft_private =
84 g_new0(struct sipe_file_transfer_private, 1);
86 ft_private->sipe_private = sipe_private;
88 ft_private->public.init = ft_outgoing_init;
89 ft_private->public.start = sipe_ft_tftp_start_sending;
90 ft_private->public.end = sipe_ft_tftp_stop_sending;
91 ft_private->public.deallocate = sipe_ft_free;
93 ft_private->invitation_cookie = g_strdup_printf("%u", rand() % 1000000000);
95 return(SIPE_FILE_TRANSFER_PUBLIC);
98 void
99 sipe_ft_free(struct sipe_file_transfer *ft)
101 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
102 struct sip_dialog *dialog = ft_private->dialog;
104 if (dialog)
105 dialog->filetransfers =
106 g_slist_remove(dialog->filetransfers, ft_private);
108 if (ft->backend_private)
109 sipe_backend_ft_deallocate(ft);
111 if (ft_private->listendata)
112 sipe_backend_network_listen_cancel(ft_private->listendata);
114 if (ft_private->cipher_context)
115 sipe_crypt_ft_destroy(ft_private->cipher_context);
117 if (ft_private->hmac_context)
118 sipe_digest_ft_destroy(ft_private->hmac_context);
120 g_free(ft_private->invitation_cookie);
121 g_free(ft_private->encrypted_outbuf);
122 g_free(ft_private);
125 static void sipe_ft_request(struct sipe_file_transfer_private *ft_private,
126 const gchar *body)
128 struct sip_dialog *dialog = ft_private->dialog;
129 sip_transport_request(ft_private->sipe_private,
130 "MESSAGE",
131 dialog->with,
132 dialog->with,
133 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
134 body,
135 dialog,
136 NULL);
139 static void
140 ft_request_denied(struct sipe_file_transfer *ft)
142 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
144 gchar *body = g_strdup_printf("Invitation-Command: CANCEL\r\n"
145 "Invitation-Cookie: %s\r\n"
146 "Cancel-Code: REJECT\r\n",
147 ft_private->invitation_cookie);
148 sipe_ft_request(ft_private, body);
149 g_free(body);
152 static void
153 send_ft_accept(struct sipe_file_transfer_private *ft_private,
154 gboolean send_enc_key,
155 gboolean send_connect_data,
156 gboolean sender_connect)
158 GString *body = g_string_new("");
160 g_string_append_printf(body,
161 "Invitation-Command: ACCEPT\r\n"
162 "Request-Data: IP-Address:\r\n"
163 "Invitation-Cookie: %s\r\n",
164 ft_private->invitation_cookie);
166 if (send_enc_key) {
167 gchar *b64_encryption_key;
168 gchar *b64_hash_key;
170 b64_encryption_key = g_base64_encode(ft_private->encryption_key,
171 SIPE_FT_KEY_LENGTH);
172 b64_hash_key = g_base64_encode(ft_private->hash_key,
173 SIPE_FT_KEY_LENGTH);
175 g_string_append_printf(body,
176 "Encryption-Key: %s\r\n"
177 "Hash-Key: %s\r\n",
178 b64_encryption_key,
179 b64_hash_key);
181 g_free(b64_hash_key);
182 g_free(b64_encryption_key);
185 if (send_connect_data) {
186 struct sipe_core_private *sipe_private = ft_private->sipe_private;
188 g_string_append_printf(body,
189 "IP-Address: %s\r\n"
190 "Port: %d\r\n"
191 "PortX: 11178\r\n"
192 "AuthCookie: %u\r\n",
193 sipe_backend_network_ip_address(SIPE_CORE_PUBLIC),
194 ft_private->port,
195 ft_private->auth_cookie);
198 if (sender_connect) {
199 g_string_append(body,
200 "Sender-Connect: TRUE\r\n");
203 sipe_ft_request(ft_private, body->str);
205 g_string_free(body, TRUE);
208 static void
209 listen_socket_created_cb(unsigned short port, gpointer data)
211 struct sipe_file_transfer *ft = data;
213 SIPE_FILE_TRANSFER_PRIVATE->port = port;
214 SIPE_FILE_TRANSFER_PRIVATE->auth_cookie = rand() % 1000000000;
216 if (sipe_backend_ft_is_incoming(ft))
217 send_ft_accept(SIPE_FILE_TRANSFER_PRIVATE, TRUE, TRUE, TRUE);
218 else
219 send_ft_accept(SIPE_FILE_TRANSFER_PRIVATE, FALSE, TRUE, FALSE);
222 static void
223 client_connected_cb(struct sipe_backend_fd *fd, gpointer data)
225 struct sipe_file_transfer *ft = data;
227 SIPE_FILE_TRANSFER_PRIVATE->listendata = NULL;
229 if (!sipe_backend_fd_is_valid(fd)) {
230 sipe_backend_ft_error(ft, _("Socket read failed"));
231 sipe_backend_ft_cancel_local(ft);
232 } else {
233 sipe_backend_ft_start(ft, fd, NULL, 0);
236 sipe_backend_fd_free(fd);
239 static void
240 ft_incoming_init(struct sipe_file_transfer *ft,
241 SIPE_UNUSED_PARAMETER const gchar *filename,
242 SIPE_UNUSED_PARAMETER gsize size,
243 SIPE_UNUSED_PARAMETER const gchar *who)
245 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
247 if (ft_private->peer_using_nat) {
248 ft_private->listendata =
249 sipe_backend_network_listen_range(SIPE_FT_TCP_PORT_MIN,
250 SIPE_FT_TCP_PORT_MAX,
251 listen_socket_created_cb,
252 client_connected_cb,
253 ft);
254 } else {
255 send_ft_accept(ft_private, TRUE, FALSE, FALSE);
259 static void
260 ft_outgoing_init(struct sipe_file_transfer *ft, const gchar *filename,
261 gsize size, const gchar *who)
263 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
264 struct sipe_core_private *sipe_private = ft_private->sipe_private;
265 struct sip_dialog *dialog;
267 const gchar *ip = sipe_backend_network_ip_address(SIPE_CORE_PUBLIC);
268 gchar *body = g_strdup_printf("Application-Name: File Transfer\r\n"
269 "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n"
270 "Invitation-Command: INVITE\r\n"
271 "Invitation-Cookie: %s\r\n"
272 "Application-File: %s\r\n"
273 "Application-FileSize: %" G_GSIZE_FORMAT "\r\n"
274 "%s"
275 "Encryption: R\r\n", // TODO: non encrypted file transfer support
276 ft_private->invitation_cookie,
277 filename,
278 size,
279 sipe_utils_ip_is_private(ip) ? "Connectivity: N\r\n" : "");
281 struct sip_session *session = sipe_session_find_or_add_im(sipe_private, who);
283 // Queue the message
284 sipe_session_enqueue_message(session, body, "text/x-msmsgsinvite");
286 dialog = sipe_dialog_find(session, who);
287 if (dialog && !dialog->outgoing_invite) {
288 sipe_im_process_queue(sipe_private, session);
289 } else if (!dialog || !dialog->outgoing_invite) {
290 // Need to send the INVITE to get the outgoing dialog setup
291 sipe_im_invite(sipe_private, session, who, body, "text/x-msmsgsinvite", NULL, FALSE);
292 dialog = sipe_dialog_find(session, who);
295 dialog->filetransfers = g_slist_append(dialog->filetransfers, ft_private);
296 ft_private->dialog = dialog;
298 g_free(body);
301 void sipe_ft_incoming_transfer(struct sipe_core_private *sipe_private,
302 struct sip_dialog *dialog,
303 const GSList *body)
305 struct sipe_file_transfer_private *ft_private;
306 gsize file_size;
308 ft_private = g_new0(struct sipe_file_transfer_private, 1);
309 ft_private->sipe_private = sipe_private;
311 ft_private->public.init = ft_incoming_init;
312 ft_private->public.start = sipe_ft_tftp_start_receiving;
313 ft_private->public.end = sipe_ft_tftp_stop_receiving;
314 ft_private->public.request_denied = ft_request_denied;
315 ft_private->public.deallocate = sipe_ft_free;
317 generate_key(ft_private->encryption_key, SIPE_FT_KEY_LENGTH);
318 generate_key(ft_private->hash_key, SIPE_FT_KEY_LENGTH);
320 ft_private->invitation_cookie = g_strdup(sipe_utils_nameval_find(body, "Invitation-Cookie"));
321 ft_private->peer_using_nat = sipe_strequal(sipe_utils_nameval_find(body, "Connectivity"), "N");
323 ft_private->dialog = dialog;
325 file_size = g_ascii_strtoull(sipe_utils_nameval_find(body,
326 "Application-FileSize"),
327 NULL, 10);
328 sipe_backend_ft_incoming(SIPE_CORE_PUBLIC,
329 SIPE_FILE_TRANSFER_PUBLIC,
330 dialog->with,
331 sipe_utils_nameval_find(body, "Application-File"),
332 file_size);
334 if (ft_private->public.backend_private != NULL) {
335 ft_private->dialog->filetransfers = g_slist_append(ft_private->dialog->filetransfers, ft_private);
336 } else {
337 sipe_ft_free(SIPE_FILE_TRANSFER_PUBLIC);
341 static struct sipe_file_transfer_private *
342 sipe_find_ft(const struct sip_dialog *dialog, const gchar *inv_cookie)
344 GSList *ftlist = dialog->filetransfers;
345 for (; ftlist != NULL; ftlist = ftlist->next) {
346 struct sipe_file_transfer_private *ft_private = ftlist->data;
347 if (sipe_strequal(ft_private->invitation_cookie, inv_cookie))
348 return ft_private;
350 return NULL;
353 void sipe_ft_incoming_accept(struct sip_dialog *dialog, const GSList *body)
355 const gchar *inv_cookie = sipe_utils_nameval_find(body, "Invitation-Cookie");
356 struct sipe_file_transfer_private *ft_private = sipe_find_ft(dialog, inv_cookie);
358 if (ft_private) {
359 const gchar *ip = sipe_utils_nameval_find(body, "IP-Address");
360 const gchar *port_str = sipe_utils_nameval_find(body, "Port");
361 const gchar *auth_cookie = sipe_utils_nameval_find(body, "AuthCookie");
362 const gchar *enc_key_b64 = sipe_utils_nameval_find(body, "Encryption-Key");
363 const gchar *hash_key_b64 = sipe_utils_nameval_find(body, "Hash-Key");
365 if (auth_cookie)
366 ft_private->auth_cookie = g_ascii_strtoull(auth_cookie,
367 NULL, 10);
368 if (enc_key_b64) {
369 gsize ret_len;
370 guchar *enc_key = g_base64_decode(enc_key_b64,
371 &ret_len);
372 if (ret_len == SIPE_FT_KEY_LENGTH) {
373 memcpy(ft_private->encryption_key,
374 enc_key, SIPE_FT_KEY_LENGTH);
375 } else {
376 sipe_ft_raise_error_and_cancel(ft_private,
377 _("Received encryption key has wrong size."));
378 g_free(enc_key);
379 return;
381 g_free(enc_key);
383 if (hash_key_b64) {
384 gsize ret_len;
385 guchar *hash_key = g_base64_decode(hash_key_b64,
386 &ret_len);
387 if (ret_len == SIPE_FT_KEY_LENGTH) {
388 memcpy(ft_private->hash_key,
389 hash_key, SIPE_FT_KEY_LENGTH);
390 } else {
391 sipe_ft_raise_error_and_cancel(ft_private,
392 _("Received hash key has wrong size."));
393 g_free(hash_key);
394 return;
396 g_free(hash_key);
400 if (ip && port_str) {
401 sipe_backend_ft_start(SIPE_FILE_TRANSFER_PUBLIC, NULL, ip,
402 g_ascii_strtoull(port_str, NULL, 10));
403 } else {
404 ft_private->listendata =
405 sipe_backend_network_listen_range(SIPE_FT_TCP_PORT_MIN,
406 SIPE_FT_TCP_PORT_MAX,
407 listen_socket_created_cb,
408 client_connected_cb,
409 ft_private);
410 if (!ft_private->listendata)
411 sipe_ft_raise_error_and_cancel(ft_private,
412 _("Could not create listen socket"));
417 void sipe_ft_incoming_cancel(struct sip_dialog *dialog, const GSList *body)
419 const gchar *inv_cookie = sipe_utils_nameval_find(body, "Invitation-Cookie");
420 struct sipe_file_transfer_private *ft_private = sipe_find_ft(dialog, inv_cookie);
422 if (ft_private)
423 sipe_backend_ft_cancel_remote(SIPE_FILE_TRANSFER_PUBLIC);
426 GSList *sipe_ft_parse_msg_body(const gchar *body)
428 GSList *list = NULL;
429 gchar **lines = g_strsplit(body, "\r\n", 0);
430 if (sipe_utils_parse_lines(&list, lines, ":") == FALSE) {
431 sipe_utils_nameval_free(list);
432 list = NULL;
434 g_strfreev(lines);
435 return list;
439 Local Variables:
440 mode: c
441 c-file-style: "bsd"
442 indent-tabs-mode: t
443 tab-width: 8
444 End: