libpurple/protocols/silc/ft.c

branch
release-2.x.y
changeset 43264
50facee54d1d
parent 43263
b9cf92c8b16b
equal deleted inserted replaced
43263:b9cf92c8b16b 43264:50facee54d1d
1 /*
2
3 silcpurple_ft.c
4
5 Author: Pekka Riikonen <priikone@silcnet.org>
6
7 Copyright (C) 2004 - 2007 Pekka Riikonen
8
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; version 2 of the License.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 */
19
20 #include "internal.h"
21 #include "silc.h"
22 #include "silcclient.h"
23 #include "silcpurple.h"
24
25 /****************************** File Transfer ********************************/
26
27 /* This implements the secure file transfer protocol (SFTP) using the SILC
28 SFTP library implementation. The API we use from the SILC Toolkit is the
29 SILC Client file transfer API, as it provides a simple file transfer we
30 need in this case. We could use the SILC SFTP API directly, but it would
31 be an overkill since we'd effectively re-implement the file transfer what
32 the SILC Client's file transfer API already provides.
33
34 From Purple we do NOT use the FT API to do the transfer as it is very limiting.
35 In fact it does not suite to file transfers like SFTP at all. For example,
36 it assumes that read operations are synchronous what they are not in SFTP.
37 It also assumes that the file transfer socket is to be handled by the Purple
38 eventloop, and this naturally is something we don't want to do in case of
39 SILC Toolkit. The FT API suites well to purely stream based file transfers
40 like HTTP GET and similar.
41
42 For this reason, we directly access the Purple GKT FT API and hack the FT
43 API to merely provide the user interface experience and all the magic
44 is done in the SILC Toolkit. Ie. we update the statistics information in
45 the FT API for user interface, and that's it. A bit dirty but until the
46 FT API gets better this is the way to go. Good thing that FT API allowed
47 us to do this. */
48
49 typedef struct {
50 SilcPurple sg;
51 SilcClientEntry client_entry;
52 SilcUInt32 session_id;
53 char *hostname;
54 SilcUInt16 port;
55 PurpleXfer *xfer;
56
57 SilcClientFileName completion;
58 void *completion_context;
59 } *SilcPurpleXfer;
60
61 static void
62 silcpurple_ftp_monitor(SilcClient client,
63 SilcClientConnection conn,
64 SilcClientMonitorStatus status,
65 SilcClientFileError error,
66 SilcUInt64 offset,
67 SilcUInt64 filesize,
68 SilcClientEntry client_entry,
69 SilcUInt32 session_id,
70 const char *filepath,
71 void *context)
72 {
73 SilcPurpleXfer xfer = context;
74 PurpleConnection *gc = xfer->sg->gc;
75 char tmp[256];
76
77 if (status == SILC_CLIENT_FILE_MONITOR_CLOSED) {
78 /* All started sessions terminate here */
79 xfer->xfer->data = NULL;
80 purple_xfer_unref(xfer->xfer);
81 silc_free(xfer);
82 return;
83 }
84
85 if (status == SILC_CLIENT_FILE_MONITOR_DISCONNECT) {
86 purple_notify_error(gc, _("Secure File Transfer"),
87 _("Error during file transfer"),
88 _("Remote disconnected"));
89 xfer->xfer->status = PURPLE_XFER_STATUS_CANCEL_REMOTE;
90 purple_xfer_update_progress(xfer->xfer);
91 silc_client_file_close(client, conn, session_id);
92 return;
93 }
94
95 if (status == SILC_CLIENT_FILE_MONITOR_KEY_AGREEMENT)
96 return;
97
98 if (status == SILC_CLIENT_FILE_MONITOR_ERROR) {
99 if (error == SILC_CLIENT_FILE_NO_SUCH_FILE) {
100 g_snprintf(tmp, sizeof(tmp), "No such file %s",
101 filepath ? filepath : "[N/A]");
102 purple_notify_error(gc, _("Secure File Transfer"),
103 _("Error during file transfer"), tmp);
104 } else if (error == SILC_CLIENT_FILE_PERMISSION_DENIED) {
105 purple_notify_error(gc, _("Secure File Transfer"),
106 _("Error during file transfer"),
107 _("Permission denied"));
108 } else if (error == SILC_CLIENT_FILE_KEY_AGREEMENT_FAILED) {
109 purple_notify_error(gc, _("Secure File Transfer"),
110 _("Error during file transfer"),
111 _("Key agreement failed"));
112 } else if (error == SILC_CLIENT_FILE_TIMEOUT) {
113 purple_notify_error(gc, _("Secure File Transfer"),
114 _("Error during file transfer"),
115 _("Connection timed out"));
116 } else if (error == SILC_CLIENT_FILE_CONNECT_FAILED) {
117 purple_notify_error(gc, _("Secure File Transfer"),
118 _("Error during file transfer"),
119 _("Creating connection failed"));
120 } else if (error == SILC_CLIENT_FILE_UNKNOWN_SESSION) {
121 purple_notify_error(gc, _("Secure File Transfer"),
122 _("Error during file transfer"),
123 _("File transfer session does not exist"));
124 }
125 xfer->xfer->status = PURPLE_XFER_STATUS_CANCEL_REMOTE;
126 purple_xfer_update_progress(xfer->xfer);
127 silc_client_file_close(client, conn, session_id);
128 return;
129 }
130
131 /* Update file transfer UI */
132 if (!offset && filesize)
133 purple_xfer_set_size(xfer->xfer, filesize);
134 if (offset && filesize) {
135 xfer->xfer->bytes_sent = offset;
136 xfer->xfer->bytes_remaining = filesize - offset;
137 }
138 purple_xfer_update_progress(xfer->xfer);
139
140 if (status == SILC_CLIENT_FILE_MONITOR_SEND ||
141 status == SILC_CLIENT_FILE_MONITOR_RECEIVE) {
142 if (offset == filesize) {
143 /* Download finished */
144 purple_xfer_set_completed(xfer->xfer, TRUE);
145 silc_client_file_close(client, conn, session_id);
146 }
147 }
148 }
149
150 static void
151 silcpurple_ftp_cancel(PurpleXfer *x)
152 {
153 SilcPurpleXfer xfer = x->data;
154
155 if (!xfer)
156 return;
157
158 xfer->xfer->status = PURPLE_XFER_STATUS_CANCEL_LOCAL;
159 purple_xfer_update_progress(xfer->xfer);
160 silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
161 }
162
163 static void
164 silcpurple_ftp_ask_name_cancel(PurpleXfer *x)
165 {
166 SilcPurpleXfer xfer = x->data;
167
168 if (!xfer)
169 return;
170
171 /* Cancel the transmission */
172 xfer->completion(NULL, xfer->completion_context);
173 silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
174 }
175
176 static void
177 silcpurple_ftp_ask_name_ok(PurpleXfer *x)
178 {
179 SilcPurpleXfer xfer = x->data;
180 const char *name;
181
182 if (!xfer)
183 return;
184
185 name = purple_xfer_get_local_filename(x);
186 g_unlink(name);
187 xfer->completion(name, xfer->completion_context);
188 }
189
190 static void
191 silcpurple_ftp_ask_name(SilcClient client,
192 SilcClientConnection conn,
193 SilcUInt32 session_id,
194 const char *remote_filename,
195 SilcClientFileName completion,
196 void *completion_context,
197 void *context)
198 {
199 SilcPurpleXfer xfer = context;
200
201 xfer->completion = completion;
202 xfer->completion_context = completion_context;
203
204 purple_xfer_set_init_fnc(xfer->xfer, silcpurple_ftp_ask_name_ok);
205 purple_xfer_set_request_denied_fnc(xfer->xfer, silcpurple_ftp_ask_name_cancel);
206
207 /* Request to save the file */
208 purple_xfer_set_filename(xfer->xfer, remote_filename);
209 purple_xfer_request(xfer->xfer);
210 }
211
212 static void
213 silcpurple_ftp_request_result(PurpleXfer *x)
214 {
215 SilcPurpleXfer xfer = x->data;
216 SilcClientFileError status;
217 PurpleConnection *gc = xfer->sg->gc;
218 SilcClientConnectionParams params;
219 gboolean local = xfer->hostname ? FALSE : TRUE;
220 char *local_ip = NULL, *remote_ip = NULL;
221 SilcSocket sock;
222
223 if (purple_xfer_get_status(x) != PURPLE_XFER_STATUS_ACCEPTED)
224 return;
225
226 silc_socket_stream_get_info(silc_packet_stream_get_stream(xfer->sg->conn->stream),
227 &sock, NULL, NULL, NULL);
228
229 if (local) {
230 /* Do the same magic what we do with key agreement (see silcpurple_buddy.c)
231 to see if we are behind NAT. */
232 if (silc_net_check_local_by_sock(sock, NULL, &local_ip)) {
233 /* Check if the IP is private */
234 if (silcpurple_ip_is_private(local_ip)) {
235 local = TRUE;
236 /* Local IP is private, resolve the remote server IP to see whether
237 we are talking to Internet or just on LAN. */
238 if (silc_net_check_host_by_sock(sock, NULL,
239 &remote_ip))
240 if (silcpurple_ip_is_private(remote_ip))
241 /* We assume we are in LAN. Let's provide the connection point. */
242 local = TRUE;
243 }
244 }
245
246 if (local && !local_ip)
247 local_ip = silc_net_localip();
248 }
249
250 memset(&params, 0, sizeof(params));
251 params.timeout_secs = 60;
252 if (local)
253 /* Provide connection point */
254 params.local_ip = local_ip;
255
256 /* Start the file transfer */
257 status = silc_client_file_receive(xfer->sg->client, xfer->sg->conn,
258 &params, xfer->sg->public_key,
259 xfer->sg->private_key,
260 silcpurple_ftp_monitor, xfer,
261 NULL, xfer->session_id,
262 silcpurple_ftp_ask_name, xfer);
263 switch (status) {
264 case SILC_CLIENT_FILE_OK:
265 silc_free(local_ip);
266 silc_free(remote_ip);
267 return;
268 break;
269
270 case SILC_CLIENT_FILE_UNKNOWN_SESSION:
271 purple_notify_error(gc, _("Secure File Transfer"),
272 _("No file transfer session active"), NULL);
273 break;
274
275 case SILC_CLIENT_FILE_ALREADY_STARTED:
276 purple_notify_error(gc, _("Secure File Transfer"),
277 _("File transfer already started"), NULL);
278 break;
279
280 case SILC_CLIENT_FILE_KEY_AGREEMENT_FAILED:
281 purple_notify_error(gc, _("Secure File Transfer"),
282 _("Could not perform key agreement for file transfer"),
283 NULL);
284 break;
285
286 default:
287 purple_notify_error(gc, _("Secure File Transfer"),
288 _("Could not start the file transfer"), NULL);
289 break;
290 }
291
292 /* Error */
293 purple_xfer_unref(xfer->xfer);
294 g_free(xfer->hostname);
295 silc_free(xfer);
296 silc_free(local_ip);
297 silc_free(remote_ip);
298 }
299
300 static void
301 silcpurple_ftp_request_denied(PurpleXfer *x)
302 {
303
304 }
305
306 void silcpurple_ftp_request(SilcClient client, SilcClientConnection conn,
307 SilcClientEntry client_entry, SilcUInt32 session_id,
308 const char *hostname, SilcUInt16 port)
309 {
310 PurpleConnection *gc = client->application;
311 SilcPurple sg = gc->proto_data;
312 SilcPurpleXfer xfer;
313
314 xfer = silc_calloc(1, sizeof(*xfer));
315 if (!xfer) {
316 silc_client_file_close(sg->client, sg->conn, session_id);
317 return;
318 }
319
320 xfer->sg = sg;
321 xfer->client_entry = client_entry;
322 xfer->session_id = session_id;
323 xfer->hostname = g_strdup(hostname);
324 xfer->port = port;
325 xfer->xfer = purple_xfer_new(xfer->sg->account, PURPLE_XFER_RECEIVE,
326 xfer->client_entry->nickname);
327 if (!xfer->xfer) {
328 silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
329 g_free(xfer->hostname);
330 silc_free(xfer);
331 return;
332 }
333 purple_xfer_set_init_fnc(xfer->xfer, silcpurple_ftp_request_result);
334 purple_xfer_set_request_denied_fnc(xfer->xfer, silcpurple_ftp_request_denied);
335 purple_xfer_set_cancel_recv_fnc(xfer->xfer, silcpurple_ftp_cancel);
336 xfer->xfer->remote_ip = g_strdup(hostname);
337 xfer->xfer->remote_port = port;
338 xfer->xfer->data = xfer;
339
340 /* File transfer request */
341 purple_xfer_request(xfer->xfer);
342 }
343
344 static void
345 silcpurple_ftp_send_cancel(PurpleXfer *x)
346 {
347 SilcPurpleXfer xfer = x->data;
348
349 if (!xfer)
350 return;
351
352 /* This call will free all resources */
353 silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
354 }
355
356 static void
357 silcpurple_ftp_send(PurpleXfer *x)
358 {
359 SilcPurpleXfer xfer = x->data;
360 const char *name;
361 char *local_ip = NULL, *remote_ip = NULL;
362 gboolean local = TRUE;
363 SilcClientConnectionParams params;
364 SilcSocket sock;
365
366 if (!xfer)
367 return;
368
369 name = purple_xfer_get_local_filename(x);
370
371 silc_socket_stream_get_info(silc_packet_stream_get_stream(xfer->sg->conn->stream),
372 &sock, NULL, NULL, NULL);
373
374 /* Do the same magic what we do with key agreement (see silcpurple_buddy.c)
375 to see if we are behind NAT. */
376 if (silc_net_check_local_by_sock(sock, NULL, &local_ip)) {
377 /* Check if the IP is private */
378 if (silcpurple_ip_is_private(local_ip)) {
379 local = FALSE;
380 /* Local IP is private, resolve the remote server IP to see whether
381 we are talking to Internet or just on LAN. */
382 if (silc_net_check_host_by_sock(sock, NULL,
383 &remote_ip))
384 if (silcpurple_ip_is_private(remote_ip))
385 /* We assume we are in LAN. Let's provide the connection point. */
386 local = TRUE;
387 }
388 }
389
390 if (local && !local_ip)
391 local_ip = silc_net_localip();
392
393 memset(&params, 0, sizeof(params));
394 params.timeout_secs = 60;
395 if (local)
396 /* Provide connection point */
397 params.local_ip = local_ip;
398
399 /* Send the file */
400 silc_client_file_send(xfer->sg->client, xfer->sg->conn,
401 xfer->client_entry, &params,
402 xfer->sg->public_key, xfer->sg->private_key,
403 silcpurple_ftp_monitor, xfer,
404 name, &xfer->session_id);
405
406 silc_free(local_ip);
407 silc_free(remote_ip);
408 }
409
410 static void
411 silcpurple_ftp_send_file_resolved(SilcClient client,
412 SilcClientConnection conn,
413 SilcStatus status,
414 SilcDList clients,
415 void *context)
416 {
417 PurpleConnection *gc = client->application;
418 char tmp[256];
419
420 if (!clients) {
421 g_snprintf(tmp, sizeof(tmp),
422 _("User %s is not present in the network"),
423 (const char *)context);
424 purple_notify_error(gc, _("Secure File Transfer"),
425 _("Cannot send file"), tmp);
426 g_free(context);
427 return;
428 }
429
430 silcpurple_ftp_send_file(client->application, (const char *)context, NULL);
431 g_free(context);
432 }
433
434 PurpleXfer *silcpurple_ftp_new_xfer(PurpleConnection *gc, const char *name)
435 {
436 SilcPurple sg = gc->proto_data;
437 SilcClient client = sg->client;
438 SilcClientConnection conn = sg->conn;
439 SilcDList clients;
440 SilcPurpleXfer xfer;
441
442 g_return_val_if_fail(name != NULL, NULL);
443
444 /* Find client entry */
445 clients = silc_client_get_clients_local(client, conn, name, FALSE);
446 if (!clients) {
447 silc_client_get_clients(client, conn, name, NULL,
448 silcpurple_ftp_send_file_resolved,
449 g_strdup(name));
450 return NULL;
451 }
452 silc_dlist_start(clients);
453
454 xfer = silc_calloc(1, sizeof(*xfer));
455 g_return_val_if_fail(xfer != NULL, NULL);
456
457 xfer->sg = sg;
458 xfer->client_entry = silc_dlist_get(clients);
459 xfer->xfer = purple_xfer_new(xfer->sg->account, PURPLE_XFER_SEND,
460 xfer->client_entry->nickname);
461 if (!xfer->xfer) {
462 silc_free(xfer);
463 return NULL;
464 }
465 purple_xfer_set_init_fnc(xfer->xfer, silcpurple_ftp_send);
466 purple_xfer_set_request_denied_fnc(xfer->xfer, silcpurple_ftp_request_denied);
467 purple_xfer_set_cancel_send_fnc(xfer->xfer, silcpurple_ftp_send_cancel);
468 xfer->xfer->data = xfer;
469
470 silc_free(clients);
471
472 return xfer->xfer;
473 }
474
475 void silcpurple_ftp_send_file(PurpleConnection *gc, const char *name, const char *file)
476 {
477 PurpleXfer *xfer = silcpurple_ftp_new_xfer(gc, name);
478
479 g_return_if_fail(xfer != NULL);
480
481 /* Choose file to send */
482 if (file)
483 purple_xfer_request_accepted(xfer, file);
484 else
485 purple_xfer_request(xfer);
486 }

mercurial