transport: add automatic authentication scheme
[siplcs.git] / src / core / sipe-ft-tftp.c
blob60c7fa93b8e4573e1998ab6bd70e7f8c290bb633
1 /**
2 * @file sipe-ft-tftp.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 <string.h>
31 #include <glib.h>
32 #include <glib/gprintf.h>
34 #include "sipe-backend.h"
35 #include "sipe-core.h"
36 #include "sipe-core-private.h"
37 #include "sipe-crypt.h"
38 #include "sipe-dialog.h"
39 #include "sipe-digest.h"
40 #include "sipe-ft.h"
41 #include "sipe-nls.h"
42 #include "sipe-utils.h"
44 #define BUFFER_SIZE 50
45 #define SIPE_FT_CHUNK_HEADER_LENGTH 3
47 static gboolean
48 write_exact(struct sipe_file_transfer_private *ft_private, const guchar *data,
49 gsize size)
51 gssize bytes_written = sipe_backend_ft_write(SIPE_FILE_TRANSFER_PUBLIC,
52 data, size);
53 if ((bytes_written < 0) || ((gsize) bytes_written != size))
54 return FALSE;
55 return TRUE;
58 static gboolean
59 read_exact(struct sipe_file_transfer_private *ft_private, guchar *data,
60 gsize size)
62 const gulong READ_TIMEOUT = 10000000;
63 gulong time_spent = 0;
65 while (size) {
66 gssize bytes_read = sipe_backend_ft_read(SIPE_FILE_TRANSFER_PUBLIC,
67 data, size);
68 if (bytes_read == 0) {
69 g_usleep(100000);
70 time_spent += 100000;
71 } else if (bytes_read < 0 || time_spent > READ_TIMEOUT) {
72 return FALSE;
73 } else {
74 size -= bytes_read;
75 data += bytes_read;
76 time_spent = 0;
79 return TRUE;
82 static gboolean
83 read_line(struct sipe_file_transfer_private *ft_private, guchar *data,
84 gsize size)
86 gsize pos = 0;
88 if (size < 2) return FALSE;
90 memset(data, 0, size--);
91 do {
92 if (!read_exact(ft_private, data + pos, 1))
93 return FALSE;
94 } while ((data[pos] != '\n') && (++pos < size));
96 /* Buffer too short? */
97 if ((pos == size) && (data[pos - 1] != '\n')) {
98 return FALSE;
101 return TRUE;
105 static void
106 raise_ft_socket_read_error_and_cancel(struct sipe_file_transfer_private *ft_private)
108 sipe_ft_raise_error_and_cancel(ft_private, _("Socket read failed"));
111 static void
112 raise_ft_socket_write_error_and_cancel(struct sipe_file_transfer_private *ft_private)
114 sipe_ft_raise_error_and_cancel(ft_private, _("Socket write failed"));
117 static gpointer
118 sipe_cipher_context_init(const guchar *enc_key)
121 * Decryption of file from SIPE file transfer
123 * Decryption:
124 * 1.) SHA1-Key = SHA1sum (Encryption-Key); Do SHA1 digest from Encryption-Key, return 20 bytes SHA1-Key.
125 * 2.) Decrypt-Data = RC4 (Encrypt-Data, substr(SHA1-Key, 0, 15)); Decryption of encrypted data, used 16 bytes SHA1-Key;
128 guchar k2[SIPE_DIGEST_SHA1_LENGTH];
130 /* 1.) SHA1 sum */
131 sipe_digest_sha1(enc_key, SIPE_FT_KEY_LENGTH, k2);
133 /* 2.) RC4 decryption */
134 return sipe_crypt_ft_start(k2);
137 static gpointer
138 sipe_hmac_context_init(const guchar *hash_key)
141 * Count MAC digest
143 * HMAC digest:
144 * 1.) SHA1-Key = SHA1sum (Hash-Key); Do SHA1 digest from Hash-Key, return 20 bytes SHA1-Key.
145 * 2.) MAC = HMAC_SHA1 (Decrypt-Data, substr(HMAC-Key,0,15)); Digest of decrypted file and SHA1-Key (used again only 16 bytes)
148 guchar k2[SIPE_DIGEST_SHA1_LENGTH];
150 /* 1.) SHA1 sum */
151 sipe_digest_sha1(hash_key, SIPE_FT_KEY_LENGTH, k2);
153 /* 2.) HMAC (initialization only) */
154 return sipe_digest_ft_start(k2);
157 static gchar *
158 sipe_hmac_finalize(gpointer hmac_context)
160 guchar hmac_digest[SIPE_DIGEST_FILETRANSFER_LENGTH];
162 /* MAC = Digest of decrypted file and SHA1-Key (used again only 16 bytes) */
163 sipe_digest_ft_end(hmac_context, hmac_digest);
165 return g_base64_encode(hmac_digest, sizeof (hmac_digest));
168 void
169 sipe_core_tftp_incoming_start(struct sipe_file_transfer *ft, gsize total_size)
171 static const guchar VER[] = "VER MSN_SECURE_FTP\r\n";
172 static const guchar TFR[] = "TFR\r\n";
173 const gsize FILE_SIZE_OFFSET = 4;
175 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
176 guchar buf[BUFFER_SIZE];
177 gchar *request;
178 gsize file_size;
180 if (!write_exact(ft_private, VER, sizeof(VER) - 1)) {
181 raise_ft_socket_read_error_and_cancel(ft_private);
182 return;
184 if (!read_line(ft_private, buf, BUFFER_SIZE)) {
185 raise_ft_socket_read_error_and_cancel(ft_private);
186 return;
189 request = g_strdup_printf("USR %s %u\r\n",
190 ft_private->sipe_private->username,
191 ft_private->auth_cookie);
192 if (!write_exact(ft_private, (guchar *)request, strlen(request))) {
193 raise_ft_socket_write_error_and_cancel(ft_private);
194 g_free(request);
195 return;
197 g_free(request);
199 if (!read_line(ft_private, buf, BUFFER_SIZE)) {
200 raise_ft_socket_read_error_and_cancel(ft_private);
201 return;
204 file_size = g_ascii_strtoull((gchar *) buf + FILE_SIZE_OFFSET, NULL, 10);
205 if (file_size != total_size) {
206 sipe_ft_raise_error_and_cancel(ft_private,
207 _("File size is different from the advertised value."));
208 return;
211 if (!sipe_backend_ft_write(SIPE_FILE_TRANSFER_PUBLIC, TFR, sizeof(TFR) - 1)) {
212 raise_ft_socket_write_error_and_cancel(ft_private);
213 return;
216 ft_private->bytes_remaining_chunk = 0;
217 ft_private->cipher_context = sipe_cipher_context_init(ft_private->encryption_key);
218 ft_private->hmac_context = sipe_hmac_context_init(ft_private->hash_key);
221 gboolean
222 sipe_core_tftp_incoming_stop(struct sipe_file_transfer *ft)
224 static const guchar BYE[] = "BYE 16777989\r\n";
225 const gsize MAC_OFFSET = 4;
227 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
228 gchar buffer[BUFFER_SIZE];
229 gsize mac_len;
230 gchar *mac;
231 gchar *mac1;
233 if (!sipe_backend_ft_write(SIPE_FILE_TRANSFER_PUBLIC, BYE, sizeof(BYE) - 1)) {
234 raise_ft_socket_write_error_and_cancel(ft_private);
235 return FALSE;
238 if (!read_line(ft_private, (guchar *) buffer, BUFFER_SIZE)) {
239 raise_ft_socket_read_error_and_cancel(ft_private);
240 return FALSE;
243 mac_len = strlen(buffer);
244 if (mac_len < (MAC_OFFSET)) {
245 sipe_ft_raise_error_and_cancel(ft_private,
246 _("Received MAC is corrupted"));
247 return FALSE;
250 /* Check MAC */
251 mac = g_strndup(buffer + MAC_OFFSET, mac_len - MAC_OFFSET);
252 mac1 = sipe_hmac_finalize(ft_private->hmac_context);
253 if (!sipe_strequal(mac, mac1)) {
254 g_free(mac1);
255 g_free(mac);
256 sipe_ft_raise_error_and_cancel(ft_private,
257 _("Received file is corrupted"));
258 return(FALSE);
260 g_free(mac1);
261 g_free(mac);
263 return(TRUE);
266 void
267 sipe_core_tftp_outgoing_start(struct sipe_file_transfer *ft, gsize total_size)
269 static const guchar VER[] = "VER MSN_SECURE_FTP\r\n";
271 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
272 guchar buf[BUFFER_SIZE];
273 gchar **parts;
274 unsigned auth_cookie_received;
275 gboolean users_match;
277 if (!read_line(ft_private, buf, BUFFER_SIZE)) {
278 raise_ft_socket_read_error_and_cancel(ft_private);
279 return;
282 if (!sipe_strequal((gchar *)buf, (gchar *)VER)) {
283 sipe_ft_raise_error_and_cancel(ft_private,
284 _("File transfer initialization failed."));
285 SIPE_DEBUG_INFO("File transfer VER string incorrect, received: %s expected: %s",
286 buf, VER);
287 return;
290 if (!write_exact(ft_private, VER, sizeof(VER) - 1)) {
291 raise_ft_socket_write_error_and_cancel(ft_private);
292 return;
295 if (!read_line(ft_private, buf, BUFFER_SIZE)) {
296 raise_ft_socket_read_error_and_cancel(ft_private);
297 return;
300 parts = g_strsplit((gchar *)buf, " ", 3);
301 auth_cookie_received = g_ascii_strtoull(parts[2], NULL, 10);
302 /* dialog->with has 'sip:' prefix, skip these four characters */
303 users_match = sipe_strcase_equal(parts[1],
304 (ft_private->dialog->with + 4));
305 g_strfreev(parts);
307 SIPE_DEBUG_INFO("File transfer authentication: %s Expected: USR %s %u",
308 buf,
309 ft_private->dialog->with + 4,
310 ft_private->auth_cookie);
312 if (!users_match ||
313 (ft_private->auth_cookie != auth_cookie_received)) {
314 sipe_ft_raise_error_and_cancel(ft_private,
315 _("File transfer authentication failed."));
316 return;
319 g_sprintf((gchar *)buf, "FIL %" G_GSIZE_FORMAT "\r\n", total_size);
320 if (!write_exact(ft_private, buf, strlen((gchar *)buf))) {
321 raise_ft_socket_write_error_and_cancel(ft_private);
322 return;
325 /* TFR */
326 if (!read_line(ft_private ,buf, BUFFER_SIZE)) {
327 raise_ft_socket_read_error_and_cancel(ft_private);
328 return;
331 ft_private->bytes_remaining_chunk = 0;
332 ft_private->cipher_context = sipe_cipher_context_init(ft_private->encryption_key);
333 ft_private->hmac_context = sipe_hmac_context_init(ft_private->hash_key);
336 gboolean
337 sipe_core_tftp_outgoing_stop(struct sipe_file_transfer *ft)
339 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
340 guchar buffer[BUFFER_SIZE];
341 gchar *mac;
342 gsize mac_len;
344 /* BYE */
345 if (!read_line(ft_private, buffer, BUFFER_SIZE)) {
346 raise_ft_socket_read_error_and_cancel(ft_private);
347 return FALSE;
350 mac = sipe_hmac_finalize(ft_private->hmac_context);
351 g_sprintf((gchar *)buffer, "MAC %s \r\n", mac);
352 g_free(mac);
354 mac_len = strlen((gchar *)buffer);
355 /* There must be this zero byte between mac and \r\n */
356 buffer[mac_len - 3] = 0;
358 if (!write_exact(ft_private, buffer, mac_len)) {
359 raise_ft_socket_write_error_and_cancel(ft_private);
360 return FALSE;
363 return TRUE;
366 static void raise_ft_error(struct sipe_file_transfer_private *ft_private,
367 const gchar *errmsg)
369 gchar *tmp = g_strdup_printf("%s: %s", errmsg,
370 sipe_backend_ft_get_error(SIPE_FILE_TRANSFER_PUBLIC));
371 sipe_backend_ft_error(SIPE_FILE_TRANSFER_PUBLIC, tmp);
372 g_free(tmp);
375 gssize
376 sipe_core_tftp_read(struct sipe_file_transfer *ft, guchar **buffer,
377 gsize bytes_remaining, gsize bytes_available)
379 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
380 gsize bytes_to_read;
381 gssize bytes_read;
383 if (ft_private->bytes_remaining_chunk == 0) {
384 guchar hdr_buf[SIPE_FT_CHUNK_HEADER_LENGTH];
386 /* read chunk header */
387 if (!read_exact(ft_private, hdr_buf, sizeof(hdr_buf))) {
388 raise_ft_error(ft_private, _("Socket read failed"));
389 return -1;
392 /* chunk header format:
394 * 0: 00 unknown (always zero?)
395 * 1: LL chunk size in bytes (low byte)
396 * 2: HH chunk size in bytes (high byte)
398 * Convert size from little endian to host order
400 ft_private->bytes_remaining_chunk =
401 hdr_buf[1] + (hdr_buf[2] << 8);
404 bytes_to_read = MIN(bytes_remaining, bytes_available);
405 bytes_to_read = MIN(bytes_to_read, ft_private->bytes_remaining_chunk);
407 *buffer = g_malloc(bytes_to_read);
408 if (!*buffer) {
409 sipe_backend_ft_error(SIPE_FILE_TRANSFER_PUBLIC, _("Out of memory"));
410 SIPE_DEBUG_ERROR("sipe_core_ft_read: can't allocate %" G_GSIZE_FORMAT " bytes for receive buffer",
411 bytes_to_read);
412 return -1;
415 bytes_read = sipe_backend_ft_read(SIPE_FILE_TRANSFER_PUBLIC, *buffer, bytes_to_read);
416 if (bytes_read < 0) {
417 raise_ft_error(ft_private, _("Socket read failed"));
418 g_free(*buffer);
419 *buffer = NULL;
420 return -1;
423 if (bytes_read > 0) {
424 guchar *decrypted = g_malloc(bytes_read);
426 if (!decrypted) {
427 sipe_backend_ft_error(SIPE_FILE_TRANSFER_PUBLIC, _("Out of memory"));
428 SIPE_DEBUG_ERROR("sipe_core_ft_read: can't allocate %" G_GSIZE_FORMAT " bytes for decryption buffer",
429 (gsize)bytes_read);
430 g_free(*buffer);
431 *buffer = NULL;
432 return -1;
434 sipe_crypt_ft_stream(ft_private->cipher_context,
435 *buffer, bytes_read, decrypted);
436 g_free(*buffer);
437 *buffer = decrypted;
439 sipe_digest_ft_update(ft_private->hmac_context,
440 decrypted, bytes_read);
442 ft_private->bytes_remaining_chunk -= bytes_read;
445 return(bytes_read);
448 gssize
449 sipe_core_tftp_write(struct sipe_file_transfer *ft, const guchar *buffer,
450 gsize size)
452 struct sipe_file_transfer_private *ft_private = SIPE_FILE_TRANSFER_PRIVATE;
453 gssize bytes_written;
455 /* When sending data via server with ForeFront installed, block bigger than
456 * this default causes ending of transmission. Hard limit block to this value
457 * when libpurple sends us more data. */
458 const gsize DEFAULT_BLOCK_SIZE = 2045;
459 if (size > DEFAULT_BLOCK_SIZE)
460 size = DEFAULT_BLOCK_SIZE;
462 if (ft_private->bytes_remaining_chunk == 0) {
463 gssize bytes_read;
464 guchar local_buf[16 + 1]; /* space for string terminator */
465 guchar hdr_buf[SIPE_FT_CHUNK_HEADER_LENGTH];
467 /* Check if receiver did not cancel the transfer
468 before it is finished */
469 bytes_read = sipe_backend_ft_read(SIPE_FILE_TRANSFER_PUBLIC,
470 local_buf,
471 sizeof(local_buf) - 1);
472 local_buf[sizeof(local_buf) - 1] = '\0';
474 if (bytes_read < 0) {
475 sipe_backend_ft_error(SIPE_FILE_TRANSFER_PUBLIC,
476 _("Socket read failed"));
477 return -1;
478 } else if ((bytes_read > 0) &&
479 (g_str_has_prefix((gchar *)local_buf, "CCL\r\n") ||
480 g_str_has_prefix((gchar *)local_buf, "BYE 2164261682\r\n"))) {
481 return -1;
484 if (ft_private->outbuf_size < size) {
485 g_free(ft_private->encrypted_outbuf);
486 ft_private->outbuf_size = size;
487 ft_private->encrypted_outbuf = g_malloc(ft_private->outbuf_size);
488 if (!ft_private->encrypted_outbuf) {
489 sipe_backend_ft_error(SIPE_FILE_TRANSFER_PUBLIC,
490 _("Out of memory"));
491 SIPE_DEBUG_ERROR("sipe_core_ft_write: can't allocate %" G_GSIZE_FORMAT " bytes for send buffer",
492 ft_private->outbuf_size);
493 return -1;
497 ft_private->bytes_remaining_chunk = size;
498 ft_private->outbuf_ptr = ft_private->encrypted_outbuf;
499 sipe_crypt_ft_stream(ft_private->cipher_context,
500 buffer, size,
501 ft_private->encrypted_outbuf);
502 sipe_digest_ft_update(ft_private->hmac_context,
503 buffer, size);
505 /* chunk header format:
507 * 0: 00 unknown (always zero?)
508 * 1: LL chunk size in bytes (low byte)
509 * 2: HH chunk size in bytes (high byte)
511 * Convert size from host order to little endian
513 hdr_buf[0] = 0;
514 hdr_buf[1] = (ft_private->bytes_remaining_chunk & 0x00FF);
515 hdr_buf[2] = (ft_private->bytes_remaining_chunk & 0xFF00) >> 8;
517 /* write chunk header */
518 if (!sipe_backend_ft_write(SIPE_FILE_TRANSFER_PUBLIC, hdr_buf, sizeof(hdr_buf))) {
519 sipe_backend_ft_error(SIPE_FILE_TRANSFER_PUBLIC,
520 _("Socket write failed"));
521 return -1;
525 bytes_written = sipe_backend_ft_write(SIPE_FILE_TRANSFER_PUBLIC,
526 ft_private->outbuf_ptr,
527 ft_private->bytes_remaining_chunk);
528 if (bytes_written < 0) {
529 raise_ft_error(ft_private, _("Socket write failed"));
530 } else if (bytes_written > 0) {
531 ft_private->bytes_remaining_chunk -= bytes_written;
532 ft_private->outbuf_ptr += bytes_written;
535 return bytes_written;
539 Local Variables:
540 mode: c
541 c-file-style: "bsd"
542 indent-tabs-mode: t
543 tab-width: 8
544 End: