| |
1 /* |
| |
2 |
| |
3 silcgaim_ft.c |
| |
4 |
| |
5 Author: Pekka Riikonen <priikone@silcnet.org> |
| |
6 |
| |
7 Copyright (C) 2004 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 "silcincludes.h" |
| |
21 #include "silcclient.h" |
| |
22 #include "silcgaim.h" |
| |
23 |
| |
24 /****************************** File Transfer ********************************/ |
| |
25 |
| |
26 /* This implements the secure file transfer protocol (SFTP) using the SILC |
| |
27 SFTP library implementation. The API we use from the SILC Toolkit is the |
| |
28 SILC Client file transfer API, as it provides a simple file transfer we |
| |
29 need in this case. We could use the SILC SFTP API directly, but it would |
| |
30 be an overkill since we'd effectively re-implement the file transfer what |
| |
31 the SILC Client's file transfer API already provides. |
| |
32 |
| |
33 From Gaim we do NOT use the FT API to do the transfer as it is very limiting. |
| |
34 In fact it does not suite to file transfers like SFTP at all. For example, |
| |
35 it assumes that read operations are synchronous what they are not in SFTP. |
| |
36 It also assumes that the file transfer socket is to be handled by the Gaim |
| |
37 eventloop, and this naturally is something we don't want to do in case of |
| |
38 SILC Toolkit. The FT API suites well to purely stream based file transfers |
| |
39 like HTTP GET and similar. |
| |
40 |
| |
41 For this reason, we directly access the Gaim GKT FT API and hack the FT |
| |
42 API to merely provide the user interface experience and all the magic |
| |
43 is done in the SILC Toolkit. Ie. we update the statistics information in |
| |
44 the FT API for user interface, and that's it. A bit dirty but until the |
| |
45 FT API gets better this is the way to go. Good thing that FT API allowed |
| |
46 us to do this. */ |
| |
47 |
| |
48 typedef struct { |
| |
49 SilcGaim sg; |
| |
50 SilcClientEntry client_entry; |
| |
51 SilcUInt32 session_id; |
| |
52 char *hostname; |
| |
53 SilcUInt16 port; |
| |
54 GaimXfer *xfer; |
| |
55 |
| |
56 SilcClientFileName completion; |
| |
57 void *completion_context; |
| |
58 } *SilcGaimXfer; |
| |
59 |
| |
60 static void |
| |
61 silcgaim_ftp_monitor(SilcClient client, |
| |
62 SilcClientConnection conn, |
| |
63 SilcClientMonitorStatus status, |
| |
64 SilcClientFileError error, |
| |
65 SilcUInt64 offset, |
| |
66 SilcUInt64 filesize, |
| |
67 SilcClientEntry client_entry, |
| |
68 SilcUInt32 session_id, |
| |
69 const char *filepath, |
| |
70 void *context) |
| |
71 { |
| |
72 SilcGaimXfer xfer = context; |
| |
73 GaimConnection *gc = xfer->sg->gc; |
| |
74 char tmp[256]; |
| |
75 |
| |
76 if (status == SILC_CLIENT_FILE_MONITOR_CLOSED) { |
| |
77 gaim_xfer_unref(xfer->xfer); |
| |
78 silc_free(xfer); |
| |
79 return; |
| |
80 } |
| |
81 |
| |
82 if (status == SILC_CLIENT_FILE_MONITOR_KEY_AGREEMENT) |
| |
83 return; |
| |
84 |
| |
85 if (status == SILC_CLIENT_FILE_MONITOR_ERROR) { |
| |
86 if (error == SILC_CLIENT_FILE_NO_SUCH_FILE) { |
| |
87 g_snprintf(tmp, sizeof(tmp), "No such file %s", |
| |
88 filepath ? filepath : "[N/A]"); |
| |
89 gaim_notify_error(gc, _("Secure File Transfer"), |
| |
90 _("Error during file transfer"), tmp); |
| |
91 } else if (error == SILC_CLIENT_FILE_PERMISSION_DENIED) { |
| |
92 gaim_notify_error(gc, _("Secure File Transfer"), |
| |
93 _("Error during file transfer"), |
| |
94 _("Permission denied")); |
| |
95 } else if (error == SILC_CLIENT_FILE_KEY_AGREEMENT_FAILED) { |
| |
96 gaim_notify_error(gc, _("Secure File Transfer"), |
| |
97 _("Error during file transfer"), |
| |
98 _("Key agreement failed")); |
| |
99 } else if (error == SILC_CLIENT_FILE_UNKNOWN_SESSION) { |
| |
100 gaim_notify_error(gc, _("Secure File Transfer"), |
| |
101 _("Error during file transfer"), |
| |
102 _("File transfer session does not exist")); |
| |
103 } else { |
| |
104 gaim_notify_error(gc, _("Secure File Transfer"), |
| |
105 _("Error during file transfer"), NULL); |
| |
106 } |
| |
107 silc_client_file_close(client, conn, session_id); |
| |
108 gaim_xfer_unref(xfer->xfer); |
| |
109 silc_free(xfer); |
| |
110 return; |
| |
111 } |
| |
112 |
| |
113 /* Update file transfer UI */ |
| |
114 if (!offset && filesize) |
| |
115 gaim_xfer_set_size(xfer->xfer, filesize); |
| |
116 if (offset && filesize) { |
| |
117 xfer->xfer->bytes_sent = offset; |
| |
118 xfer->xfer->bytes_remaining = filesize - offset; |
| |
119 } |
| |
120 gaim_xfer_update_progress(xfer->xfer); |
| |
121 |
| |
122 if (status == SILC_CLIENT_FILE_MONITOR_SEND || |
| |
123 status == SILC_CLIENT_FILE_MONITOR_RECEIVE) { |
| |
124 if (offset == filesize) { |
| |
125 /* Download finished */ |
| |
126 gaim_xfer_set_completed(xfer->xfer, TRUE); |
| |
127 silc_client_file_close(client, conn, session_id); |
| |
128 } |
| |
129 } |
| |
130 } |
| |
131 |
| |
132 static void |
| |
133 silcgaim_ftp_cancel(GaimXfer *x) |
| |
134 { |
| |
135 SilcGaimXfer xfer = x->data; |
| |
136 xfer->xfer->status = GAIM_XFER_STATUS_CANCEL_LOCAL; |
| |
137 gaim_xfer_update_progress(xfer->xfer); |
| |
138 silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id); |
| |
139 } |
| |
140 |
| |
141 static void |
| |
142 silcgaim_ftp_ask_name_cancel(GaimXfer *x) |
| |
143 { |
| |
144 SilcGaimXfer xfer = x->data; |
| |
145 |
| |
146 /* Cancel the transmission */ |
| |
147 xfer->completion(NULL, xfer->completion_context); |
| |
148 silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id); |
| |
149 } |
| |
150 |
| |
151 static void |
| |
152 silcgaim_ftp_ask_name_ok(GaimXfer *x) |
| |
153 { |
| |
154 SilcGaimXfer xfer = x->data; |
| |
155 const char *name; |
| |
156 |
| |
157 name = gaim_xfer_get_local_filename(x); |
| |
158 g_unlink(name); |
| |
159 xfer->completion(name, xfer->completion_context); |
| |
160 } |
| |
161 |
| |
162 static void |
| |
163 silcgaim_ftp_ask_name(SilcClient client, |
| |
164 SilcClientConnection conn, |
| |
165 SilcUInt32 session_id, |
| |
166 const char *remote_filename, |
| |
167 SilcClientFileName completion, |
| |
168 void *completion_context, |
| |
169 void *context) |
| |
170 { |
| |
171 SilcGaimXfer xfer = context; |
| |
172 |
| |
173 xfer->completion = completion; |
| |
174 xfer->completion_context = completion_context; |
| |
175 |
| |
176 gaim_xfer_set_init_fnc(xfer->xfer, silcgaim_ftp_ask_name_ok); |
| |
177 gaim_xfer_set_request_denied_fnc(xfer->xfer, silcgaim_ftp_ask_name_cancel); |
| |
178 |
| |
179 /* Request to save the file */ |
| |
180 gaim_xfer_set_filename(xfer->xfer, remote_filename); |
| |
181 gaim_xfer_request(xfer->xfer); |
| |
182 } |
| |
183 |
| |
184 static void |
| |
185 silcgaim_ftp_request_result(GaimXfer *x) |
| |
186 { |
| |
187 SilcGaimXfer xfer = x->data; |
| |
188 SilcClientFileError status; |
| |
189 GaimConnection *gc = xfer->sg->gc; |
| |
190 |
| |
191 if (gaim_xfer_get_status(x) != GAIM_XFER_STATUS_ACCEPTED) |
| |
192 return; |
| |
193 |
| |
194 /* Start the file transfer */ |
| |
195 status = silc_client_file_receive(xfer->sg->client, xfer->sg->conn, |
| |
196 silcgaim_ftp_monitor, xfer, |
| |
197 NULL, xfer->session_id, |
| |
198 silcgaim_ftp_ask_name, xfer); |
| |
199 switch (status) { |
| |
200 case SILC_CLIENT_FILE_OK: |
| |
201 return; |
| |
202 break; |
| |
203 |
| |
204 case SILC_CLIENT_FILE_UNKNOWN_SESSION: |
| |
205 gaim_notify_error(gc, _("Secure File Transfer"), |
| |
206 _("No file transfer session active"), NULL); |
| |
207 break; |
| |
208 |
| |
209 case SILC_CLIENT_FILE_ALREADY_STARTED: |
| |
210 gaim_notify_error(gc, _("Secure File Transfer"), |
| |
211 _("File transfer already started"), NULL); |
| |
212 break; |
| |
213 |
| |
214 case SILC_CLIENT_FILE_KEY_AGREEMENT_FAILED: |
| |
215 gaim_notify_error(gc, _("Secure File Transfer"), |
| |
216 _("Could not perform key agreement for file transfer"), |
| |
217 NULL); |
| |
218 break; |
| |
219 |
| |
220 default: |
| |
221 gaim_notify_error(gc, _("Secure File Transfer"), |
| |
222 _("Could not start the file transfer"), NULL); |
| |
223 break; |
| |
224 } |
| |
225 |
| |
226 /* Error */ |
| |
227 gaim_xfer_unref(xfer->xfer); |
| |
228 g_free(xfer->hostname); |
| |
229 silc_free(xfer); |
| |
230 } |
| |
231 |
| |
232 static void |
| |
233 silcgaim_ftp_request_denied(GaimXfer *x) |
| |
234 { |
| |
235 |
| |
236 } |
| |
237 |
| |
238 void silcgaim_ftp_request(SilcClient client, SilcClientConnection conn, |
| |
239 SilcClientEntry client_entry, SilcUInt32 session_id, |
| |
240 const char *hostname, SilcUInt16 port) |
| |
241 { |
| |
242 GaimConnection *gc = client->application; |
| |
243 SilcGaim sg = gc->proto_data; |
| |
244 SilcGaimXfer xfer; |
| |
245 |
| |
246 xfer = silc_calloc(1, sizeof(*xfer)); |
| |
247 if (!xfer) { |
| |
248 silc_client_file_close(sg->client, sg->conn, session_id); |
| |
249 return; |
| |
250 } |
| |
251 |
| |
252 xfer->sg = sg; |
| |
253 xfer->client_entry = client_entry; |
| |
254 xfer->session_id = session_id; |
| |
255 xfer->hostname = g_strdup(hostname); |
| |
256 xfer->port = port; |
| |
257 xfer->xfer = gaim_xfer_new(xfer->sg->account, GAIM_XFER_RECEIVE, |
| |
258 xfer->client_entry->nickname); |
| |
259 if (!xfer->xfer) { |
| |
260 silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id); |
| |
261 g_free(xfer->hostname); |
| |
262 silc_free(xfer); |
| |
263 return; |
| |
264 } |
| |
265 gaim_xfer_set_init_fnc(xfer->xfer, silcgaim_ftp_request_result); |
| |
266 gaim_xfer_set_request_denied_fnc(xfer->xfer, silcgaim_ftp_request_denied); |
| |
267 gaim_xfer_set_cancel_recv_fnc(xfer->xfer, silcgaim_ftp_cancel); |
| |
268 xfer->xfer->remote_ip = g_strdup(hostname); |
| |
269 xfer->xfer->remote_port = port; |
| |
270 xfer->xfer->data = xfer; |
| |
271 |
| |
272 /* File transfer request */ |
| |
273 gaim_xfer_request(xfer->xfer); |
| |
274 } |
| |
275 |
| |
276 static void |
| |
277 silcgaim_ftp_send_cancel(GaimXfer *x) |
| |
278 { |
| |
279 SilcGaimXfer xfer = x->data; |
| |
280 silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id); |
| |
281 gaim_xfer_unref(xfer->xfer); |
| |
282 g_free(xfer->hostname); |
| |
283 silc_free(xfer); |
| |
284 } |
| |
285 |
| |
286 static void |
| |
287 silcgaim_ftp_send(GaimXfer *x) |
| |
288 { |
| |
289 SilcGaimXfer xfer = x->data; |
| |
290 const char *name; |
| |
291 char *local_ip = NULL, *remote_ip = NULL; |
| |
292 gboolean local = TRUE; |
| |
293 |
| |
294 name = gaim_xfer_get_local_filename(x); |
| |
295 |
| |
296 /* Do the same magic what we do with key agreement (see silcgaim_buddy.c) |
| |
297 to see if we are behind NAT. */ |
| |
298 if (silc_net_check_local_by_sock(xfer->sg->conn->sock->sock, |
| |
299 NULL, &local_ip)) { |
| |
300 /* Check if the IP is private */ |
| |
301 if (silcgaim_ip_is_private(local_ip)) { |
| |
302 local = FALSE; |
| |
303 /* Local IP is private, resolve the remote server IP to see whether |
| |
304 we are talking to Internet or just on LAN. */ |
| |
305 if (silc_net_check_host_by_sock(xfer->sg->conn->sock->sock, NULL, |
| |
306 &remote_ip)) |
| |
307 if (silcgaim_ip_is_private(remote_ip)) |
| |
308 /* We assume we are in LAN. Let's provide the connection point. */ |
| |
309 local = TRUE; |
| |
310 } |
| |
311 } |
| |
312 |
| |
313 if (local && !local_ip) |
| |
314 local_ip = silc_net_localip(); |
| |
315 |
| |
316 /* Send the file */ |
| |
317 silc_client_file_send(xfer->sg->client, xfer->sg->conn, |
| |
318 silcgaim_ftp_monitor, xfer, |
| |
319 local_ip, 0, !local, xfer->client_entry, |
| |
320 name, &xfer->session_id); |
| |
321 |
| |
322 silc_free(local_ip); |
| |
323 silc_free(remote_ip); |
| |
324 } |
| |
325 |
| |
326 static void |
| |
327 silcgaim_ftp_send_file_resolved(SilcClient client, |
| |
328 SilcClientConnection conn, |
| |
329 SilcClientEntry *clients, |
| |
330 SilcUInt32 clients_count, |
| |
331 void *context) |
| |
332 { |
| |
333 GaimConnection *gc = client->application; |
| |
334 char tmp[256]; |
| |
335 |
| |
336 if (!clients) { |
| |
337 g_snprintf(tmp, sizeof(tmp), |
| |
338 _("User %s is not present in the network"), |
| |
339 (const char *)context); |
| |
340 gaim_notify_error(gc, _("Secure File Transfer"), |
| |
341 _("Cannot send file"), tmp); |
| |
342 silc_free(context); |
| |
343 return; |
| |
344 } |
| |
345 |
| |
346 silcgaim_ftp_send_file(client->application, (const char *)context, NULL); |
| |
347 silc_free(context); |
| |
348 } |
| |
349 |
| |
350 GaimXfer *silcgaim_ftp_new_xfer(GaimConnection *gc, const char *name) |
| |
351 { |
| |
352 SilcGaim sg = gc->proto_data; |
| |
353 SilcClient client = sg->client; |
| |
354 SilcClientConnection conn = sg->conn; |
| |
355 SilcClientEntry *clients; |
| |
356 SilcUInt32 clients_count; |
| |
357 SilcGaimXfer xfer; |
| |
358 char *nickname; |
| |
359 |
| |
360 g_return_val_if_fail(name != NULL, NULL); |
| |
361 |
| |
362 if (!silc_parse_userfqdn(name, &nickname, NULL)) |
| |
363 return NULL; |
| |
364 |
| |
365 /* Find client entry */ |
| |
366 clients = silc_client_get_clients_local(client, conn, nickname, name, |
| |
367 &clients_count); |
| |
368 if (!clients) { |
| |
369 silc_client_get_clients(client, conn, nickname, NULL, |
| |
370 silcgaim_ftp_send_file_resolved, |
| |
371 strdup(name)); |
| |
372 silc_free(nickname); |
| |
373 return NULL; |
| |
374 } |
| |
375 |
| |
376 xfer = silc_calloc(1, sizeof(*xfer)); |
| |
377 |
| |
378 g_return_val_if_fail(xfer != NULL, NULL); |
| |
379 |
| |
380 xfer->sg = sg; |
| |
381 xfer->client_entry = clients[0]; |
| |
382 xfer->xfer = gaim_xfer_new(xfer->sg->account, GAIM_XFER_SEND, |
| |
383 xfer->client_entry->nickname); |
| |
384 if (!xfer->xfer) { |
| |
385 silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id); |
| |
386 g_free(xfer->hostname); |
| |
387 silc_free(xfer); |
| |
388 return NULL; |
| |
389 } |
| |
390 gaim_xfer_set_init_fnc(xfer->xfer, silcgaim_ftp_send); |
| |
391 gaim_xfer_set_request_denied_fnc(xfer->xfer, silcgaim_ftp_request_denied); |
| |
392 gaim_xfer_set_cancel_send_fnc(xfer->xfer, silcgaim_ftp_send_cancel); |
| |
393 xfer->xfer->data = xfer; |
| |
394 |
| |
395 silc_free(clients); |
| |
396 silc_free(nickname); |
| |
397 |
| |
398 return xfer->xfer; |
| |
399 } |
| |
400 |
| |
401 void silcgaim_ftp_send_file(GaimConnection *gc, const char *name, const char *file) |
| |
402 { |
| |
403 GaimXfer *xfer = silcgaim_ftp_new_xfer(gc, name); |
| |
404 |
| |
405 g_return_if_fail(xfer != NULL); |
| |
406 |
| |
407 /* Choose file to send */ |
| |
408 if (file) |
| |
409 gaim_xfer_request_accepted(xfer, file); |
| |
410 else |
| |
411 gaim_xfer_request(xfer); |
| |
412 } |