Updated media patches README
[siplcs.git] / src / core / sipe-ft.c
blob5acfcbbc738aa28c5bb100c43b76c3bd7c7bd3c0
1 /**
2 * @file sipe-ft.c
4 * pidgin-sipe
6 * Copyright (C) 2010-11 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-core.h"
38 #include "sipe-core-private.h"
39 #include "sipe-crypt.h"
40 #include "sipe-dialog.h"
41 #include "sipe-digest.h"
42 #include "sipe-ft.h"
43 #include "sipe-im.h"
44 #include "sipe-nls.h"
45 #include "sipe-session.h"
46 #include "sipe-utils.h"
49 * DO NOT CHANGE THE FOLLOWING CONSTANTS!!!
51 * It seems that Microsoft Office Communicator client will accept
52 * file transfer invitations *only* within this port range!
54 * If a firewall is active on your system you need to open these ports if
55 * you want to *send* files to other users. Receiving files uses an outgoing
56 * connection and should therefore automatically penetrate your firewall.
58 #define SIPE_FT_TCP_PORT_MIN 6891
59 #define SIPE_FT_TCP_PORT_MAX 6901
61 void sipe_ft_raise_error_and_cancel(struct sipe_file_transfer_private *ft_private,
62 const gchar *errmsg)
64 sipe_backend_ft_error(SIPE_FILE_TRANSFER_PUBLIC, errmsg);
65 sipe_backend_ft_cancel_local(SIPE_FILE_TRANSFER_PUBLIC);
68 static void generate_key(guchar *buffer, gsize size)
70 gsize i = 0;
71 while (i < size) buffer[i++] = rand();
74 struct sipe_file_transfer *sipe_core_ft_allocate(struct sipe_core_public *sipe_public)
76 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
77 struct sipe_file_transfer_private *ft_private =
78 g_new0(struct sipe_file_transfer_private, 1);
80 ft_private->sipe_private = sipe_private;
81 ft_private->invitation_cookie = g_strdup_printf("%u", rand() % 1000000000);
83 return(SIPE_FILE_TRANSFER_PUBLIC);
86 static void sipe_ft_deallocate(struct sipe_file_transfer *ft)
88 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
90 if (ft->backend_private)
91 sipe_backend_ft_deallocate(ft);
93 if (ft_private->listendata)
94 sipe_backend_network_listen_cancel(ft_private->listendata);
96 if (ft_private->cipher_context)
97 sipe_crypt_ft_destroy(ft_private->cipher_context);
99 if (ft_private->hmac_context)
100 sipe_digest_ft_destroy(ft_private->hmac_context);
102 g_free(ft_private->invitation_cookie);
103 g_free(ft_private->encrypted_outbuf);
104 g_free(ft_private);
107 void sipe_core_ft_deallocate(struct sipe_file_transfer *ft)
109 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
110 struct sip_dialog *dialog = ft_private->dialog;
112 if (dialog)
113 dialog->filetransfers = g_slist_remove(dialog->filetransfers, ft_private);
115 sipe_ft_deallocate(ft);
118 static void sipe_ft_request(struct sipe_file_transfer_private *ft_private,
119 const gchar *body)
121 struct sip_dialog *dialog = ft_private->dialog;
122 sip_transport_request(ft_private->sipe_private,
123 "MESSAGE",
124 dialog->with,
125 dialog->with,
126 "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n",
127 body,
128 dialog,
129 NULL);
132 void sipe_core_ft_cancel(struct sipe_file_transfer *ft)
134 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
136 gchar *body = g_strdup_printf("Invitation-Command: CANCEL\r\n"
137 "Invitation-Cookie: %s\r\n"
138 "Cancel-Code: REJECT\r\n",
139 ft_private->invitation_cookie);
140 sipe_ft_request(ft_private, body);
141 g_free(body);
144 static void
145 send_ft_accept(struct sipe_file_transfer_private *ft_private,
146 gboolean send_enc_key,
147 gboolean send_connect_data,
148 gboolean sender_connect)
150 GString *body = g_string_new("");
152 g_string_append_printf(body,
153 "Invitation-Command: ACCEPT\r\n"
154 "Request-Data: IP-Address:\r\n"
155 "Invitation-Cookie: %s\r\n",
156 ft_private->invitation_cookie);
158 if (send_enc_key) {
159 gchar *b64_encryption_key;
160 gchar *b64_hash_key;
162 b64_encryption_key = g_base64_encode(ft_private->encryption_key,
163 SIPE_FT_KEY_LENGTH);
164 b64_hash_key = g_base64_encode(ft_private->hash_key,
165 SIPE_FT_KEY_LENGTH);
167 g_string_append_printf(body,
168 "Encryption-Key: %s\r\n"
169 "Hash-Key: %s\r\n",
170 b64_encryption_key,
171 b64_hash_key);
173 g_free(b64_hash_key);
174 g_free(b64_encryption_key);
177 if (send_connect_data) {
178 g_string_append_printf(body,
179 "IP-Address: %s\r\n"
180 "Port: %d\r\n"
181 "PortX: 11178\r\n"
182 "AuthCookie: %u\r\n",
183 sipe_utils_get_suitable_local_ip(-1),
184 ft_private->port,
185 ft_private->auth_cookie);
188 if (sender_connect) {
189 g_string_append(body,
190 "Sender-Connect: TRUE\r\n");
193 sipe_ft_request(ft_private, body->str);
195 g_string_free(body, TRUE);
198 static void
199 listen_socket_created_cb(unsigned short port, gpointer data)
201 struct sipe_file_transfer *ft = data;
203 SIPE_FILE_TRANSFER_PRIVATE->port = port;
204 SIPE_FILE_TRANSFER_PRIVATE->auth_cookie = rand() % 1000000000;
206 if (sipe_backend_ft_is_incoming(ft))
207 send_ft_accept(SIPE_FILE_TRANSFER_PRIVATE, TRUE, TRUE, TRUE);
208 else
209 send_ft_accept(SIPE_FILE_TRANSFER_PRIVATE, FALSE, TRUE, FALSE);
212 static void
213 client_connected_cb(gint fd, gpointer data)
215 struct sipe_file_transfer *ft = data;
217 SIPE_FILE_TRANSFER_PRIVATE->listendata = NULL;
219 if (fd < 0) {
220 sipe_backend_ft_error(ft, _("Socket read failed"));
221 sipe_backend_ft_cancel_local(ft);
222 } else {
223 sipe_backend_ft_start(ft, fd, NULL, 0);
227 void sipe_core_ft_incoming_init(struct sipe_file_transfer *ft)
229 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
231 if (ft_private->peer_using_nat) {
232 ft_private->listendata =
233 sipe_backend_network_listen_range(SIPE_FT_TCP_PORT_MIN,
234 SIPE_FT_TCP_PORT_MAX,
235 listen_socket_created_cb,
236 client_connected_cb,
237 ft);
238 } else {
239 send_ft_accept(ft_private, TRUE, FALSE, FALSE);
243 void sipe_core_ft_outgoing_init(struct sipe_file_transfer *ft,
244 const gchar *filename, gsize size,
245 const gchar *who)
247 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
248 struct sipe_core_private *sipe_private = ft_private->sipe_private;
249 struct sip_dialog *dialog;
251 const gchar *ip = sipe_utils_get_suitable_local_ip(-1);
252 gchar *body = g_strdup_printf("Application-Name: File Transfer\r\n"
253 "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n"
254 "Invitation-Command: INVITE\r\n"
255 "Invitation-Cookie: %s\r\n"
256 "Application-File: %s\r\n"
257 "Application-FileSize: %" G_GSIZE_FORMAT "\r\n"
258 "%s"
259 "Encryption: R\r\n", // TODO: non encrypted file transfer support
260 ft_private->invitation_cookie,
261 filename,
262 size,
263 sipe_utils_ip_is_private(ip) ? "Connectivity: N\r\n" : "");
265 struct sip_session *session = sipe_session_find_or_add_im(sipe_private, who);
267 // Queue the message
268 sipe_session_enqueue_message(session, body, "text/x-msmsgsinvite");
270 dialog = sipe_dialog_find(session, who);
271 if (dialog && !dialog->outgoing_invite) {
272 sipe_im_process_queue(sipe_private, session);
273 } else if (!dialog || !dialog->outgoing_invite) {
274 // Need to send the INVITE to get the outgoing dialog setup
275 sipe_im_invite(sipe_private, session, who, body, "text/x-msmsgsinvite", NULL, FALSE);
276 dialog = sipe_dialog_find(session, who);
279 dialog->filetransfers = g_slist_append(dialog->filetransfers, ft_private);
280 ft_private->dialog = dialog;
282 g_free(body);
285 void sipe_ft_incoming_transfer(struct sipe_core_private *sipe_private,
286 struct sip_dialog *dialog,
287 const GSList *body)
289 struct sipe_file_transfer_private *ft_private;
290 gsize file_size;
292 ft_private = g_new0(struct sipe_file_transfer_private, 1);
293 ft_private->sipe_private = sipe_private;
295 generate_key(ft_private->encryption_key, SIPE_FT_KEY_LENGTH);
296 generate_key(ft_private->hash_key, SIPE_FT_KEY_LENGTH);
298 ft_private->invitation_cookie = g_strdup(sipe_utils_nameval_find(body, "Invitation-Cookie"));
299 ft_private->peer_using_nat = sipe_strequal(sipe_utils_nameval_find(body, "Connectivity"), "N");
301 ft_private->dialog = dialog;
303 file_size = g_ascii_strtoull(sipe_utils_nameval_find(body,
304 "Application-FileSize"),
305 NULL, 10);
306 sipe_backend_ft_incoming(SIPE_CORE_PUBLIC,
307 SIPE_FILE_TRANSFER_PUBLIC,
308 dialog->with,
309 sipe_utils_nameval_find(body, "Application-File"),
310 file_size);
312 if (ft_private->public.backend_private != NULL) {
313 ft_private->dialog->filetransfers = g_slist_append(ft_private->dialog->filetransfers, ft_private);
314 } else {
315 sipe_ft_deallocate(SIPE_FILE_TRANSFER_PUBLIC);
319 static struct sipe_file_transfer_private *
320 sipe_find_ft(const struct sip_dialog *dialog, const gchar *inv_cookie)
322 GSList *ftlist = dialog->filetransfers;
323 for (; ftlist != NULL; ftlist = ftlist->next) {
324 struct sipe_file_transfer_private *ft_private = ftlist->data;
325 if (sipe_strequal(ft_private->invitation_cookie, inv_cookie))
326 return ft_private;
328 return NULL;
331 void sipe_ft_incoming_accept(struct sip_dialog *dialog, const GSList *body)
333 const gchar *inv_cookie = sipe_utils_nameval_find(body, "Invitation-Cookie");
334 struct sipe_file_transfer_private *ft_private = sipe_find_ft(dialog, inv_cookie);
336 if (ft_private) {
337 const gchar *ip = sipe_utils_nameval_find(body, "IP-Address");
338 const gchar *port_str = sipe_utils_nameval_find(body, "Port");
339 const gchar *auth_cookie = sipe_utils_nameval_find(body, "AuthCookie");
340 const gchar *enc_key_b64 = sipe_utils_nameval_find(body, "Encryption-Key");
341 const gchar *hash_key_b64 = sipe_utils_nameval_find(body, "Hash-Key");
343 if (auth_cookie)
344 ft_private->auth_cookie = g_ascii_strtoull(auth_cookie,
345 NULL, 10);
346 if (enc_key_b64) {
347 gsize ret_len;
348 guchar *enc_key = g_base64_decode(enc_key_b64,
349 &ret_len);
350 if (ret_len == SIPE_FT_KEY_LENGTH) {
351 memcpy(ft_private->encryption_key,
352 enc_key, SIPE_FT_KEY_LENGTH);
353 } else {
354 sipe_ft_raise_error_and_cancel(ft_private,
355 _("Received encryption key has wrong size."));
356 g_free(enc_key);
357 return;
359 g_free(enc_key);
361 if (hash_key_b64) {
362 gsize ret_len;
363 guchar *hash_key = g_base64_decode(hash_key_b64,
364 &ret_len);
365 if (ret_len == SIPE_FT_KEY_LENGTH) {
366 memcpy(ft_private->hash_key,
367 hash_key, SIPE_FT_KEY_LENGTH);
368 } else {
369 sipe_ft_raise_error_and_cancel(ft_private,
370 _("Received hash key has wrong size."));
371 g_free(hash_key);
372 return;
374 g_free(hash_key);
378 if (ip && port_str) {
379 sipe_backend_ft_start(SIPE_FILE_TRANSFER_PUBLIC, -1, ip,
380 g_ascii_strtoull(port_str, NULL, 10));
381 } else {
382 ft_private->listendata =
383 sipe_backend_network_listen_range(SIPE_FT_TCP_PORT_MIN,
384 SIPE_FT_TCP_PORT_MAX,
385 listen_socket_created_cb,
386 client_connected_cb,
387 ft_private);
388 if (!ft_private->listendata)
389 sipe_ft_raise_error_and_cancel(ft_private,
390 _("Could not create listen socket"));
395 void sipe_ft_incoming_cancel(struct sip_dialog *dialog, const GSList *body)
397 const gchar *inv_cookie = sipe_utils_nameval_find(body, "Invitation-Cookie");
398 struct sipe_file_transfer_private *ft_private = sipe_find_ft(dialog, inv_cookie);
400 if (ft_private)
401 sipe_backend_ft_cancel_remote(SIPE_FILE_TRANSFER_PUBLIC);
404 GSList *sipe_ft_parse_msg_body(const gchar *body)
406 GSList *list = NULL;
407 gchar **lines = g_strsplit(body, "\r\n", 0);
408 if (sipe_utils_parse_lines(&list, lines, ":") == FALSE) {
409 sipe_utils_nameval_free(list);
410 list = NULL;
412 g_strfreev(lines);
413 return list;
417 Local Variables:
418 mode: c
419 c-file-style: "bsd"
420 indent-tabs-mode: t
421 tab-width: 8
422 End: