| 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(¶ms, 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 ¶ms, 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(¶ms, 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, ¶ms, |
|
| 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 } |
|