| 1 /* |
|
| 2 |
|
| 3 silcpurple_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 "silcpurple.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 Purple 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 Purple |
|
| 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 Purple 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 SilcPurple sg; |
|
| 50 SilcClientEntry client_entry; |
|
| 51 SilcUInt32 session_id; |
|
| 52 char *hostname; |
|
| 53 SilcUInt16 port; |
|
| 54 PurpleXfer *xfer; |
|
| 55 |
|
| 56 SilcClientFileName completion; |
|
| 57 void *completion_context; |
|
| 58 } *SilcPurpleXfer; |
|
| 59 |
|
| 60 static void |
|
| 61 silcpurple_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 SilcPurpleXfer xfer = context; |
|
| 73 PurpleConnection *gc = xfer->sg->gc; |
|
| 74 char tmp[256]; |
|
| 75 |
|
| 76 if (status == SILC_CLIENT_FILE_MONITOR_CLOSED) { |
|
| 77 purple_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 purple_notify_error(gc, _("Secure File Transfer"), |
|
| 90 _("Error during file transfer"), tmp); |
|
| 91 } else if (error == SILC_CLIENT_FILE_PERMISSION_DENIED) { |
|
| 92 purple_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 purple_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 purple_notify_error(gc, _("Secure File Transfer"), |
|
| 101 _("Error during file transfer"), |
|
| 102 _("File transfer session does not exist")); |
|
| 103 } else { |
|
| 104 purple_notify_error(gc, _("Secure File Transfer"), |
|
| 105 _("Error during file transfer"), NULL); |
|
| 106 } |
|
| 107 silc_client_file_close(client, conn, session_id); |
|
| 108 purple_xfer_unref(xfer->xfer); |
|
| 109 silc_free(xfer); |
|
| 110 return; |
|
| 111 } |
|
| 112 |
|
| 113 /* Update file transfer UI */ |
|
| 114 if (!offset && filesize) |
|
| 115 purple_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 purple_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 purple_xfer_set_completed(xfer->xfer, TRUE); |
|
| 127 silc_client_file_close(client, conn, session_id); |
|
| 128 } |
|
| 129 } |
|
| 130 } |
|
| 131 |
|
| 132 static void |
|
| 133 silcpurple_ftp_cancel(PurpleXfer *x) |
|
| 134 { |
|
| 135 SilcPurpleXfer xfer = x->data; |
|
| 136 xfer->xfer->status = PURPLE_XFER_STATUS_CANCEL_LOCAL; |
|
| 137 purple_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 silcpurple_ftp_ask_name_cancel(PurpleXfer *x) |
|
| 143 { |
|
| 144 SilcPurpleXfer 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 silcpurple_ftp_ask_name_ok(PurpleXfer *x) |
|
| 153 { |
|
| 154 SilcPurpleXfer xfer = x->data; |
|
| 155 const char *name; |
|
| 156 |
|
| 157 name = purple_xfer_get_local_filename(x); |
|
| 158 g_unlink(name); |
|
| 159 xfer->completion(name, xfer->completion_context); |
|
| 160 } |
|
| 161 |
|
| 162 static void |
|
| 163 silcpurple_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 SilcPurpleXfer xfer = context; |
|
| 172 |
|
| 173 xfer->completion = completion; |
|
| 174 xfer->completion_context = completion_context; |
|
| 175 |
|
| 176 purple_xfer_set_init_fnc(xfer->xfer, silcpurple_ftp_ask_name_ok); |
|
| 177 purple_xfer_set_request_denied_fnc(xfer->xfer, silcpurple_ftp_ask_name_cancel); |
|
| 178 |
|
| 179 /* Request to save the file */ |
|
| 180 purple_xfer_set_filename(xfer->xfer, remote_filename); |
|
| 181 purple_xfer_request(xfer->xfer); |
|
| 182 } |
|
| 183 |
|
| 184 static void |
|
| 185 silcpurple_ftp_request_result(PurpleXfer *x) |
|
| 186 { |
|
| 187 SilcPurpleXfer xfer = x->data; |
|
| 188 SilcClientFileError status; |
|
| 189 PurpleConnection *gc = xfer->sg->gc; |
|
| 190 |
|
| 191 if (purple_xfer_get_status(x) != PURPLE_XFER_STATUS_ACCEPTED) |
|
| 192 return; |
|
| 193 |
|
| 194 /* Start the file transfer */ |
|
| 195 status = silc_client_file_receive(xfer->sg->client, xfer->sg->conn, |
|
| 196 silcpurple_ftp_monitor, xfer, |
|
| 197 NULL, xfer->session_id, |
|
| 198 silcpurple_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 purple_notify_error(gc, _("Secure File Transfer"), |
|
| 206 _("No file transfer session active"), NULL); |
|
| 207 break; |
|
| 208 |
|
| 209 case SILC_CLIENT_FILE_ALREADY_STARTED: |
|
| 210 purple_notify_error(gc, _("Secure File Transfer"), |
|
| 211 _("File transfer already started"), NULL); |
|
| 212 break; |
|
| 213 |
|
| 214 case SILC_CLIENT_FILE_KEY_AGREEMENT_FAILED: |
|
| 215 purple_notify_error(gc, _("Secure File Transfer"), |
|
| 216 _("Could not perform key agreement for file transfer"), |
|
| 217 NULL); |
|
| 218 break; |
|
| 219 |
|
| 220 default: |
|
| 221 purple_notify_error(gc, _("Secure File Transfer"), |
|
| 222 _("Could not start the file transfer"), NULL); |
|
| 223 break; |
|
| 224 } |
|
| 225 |
|
| 226 /* Error */ |
|
| 227 purple_xfer_unref(xfer->xfer); |
|
| 228 g_free(xfer->hostname); |
|
| 229 silc_free(xfer); |
|
| 230 } |
|
| 231 |
|
| 232 static void |
|
| 233 silcpurple_ftp_request_denied(PurpleXfer *x) |
|
| 234 { |
|
| 235 |
|
| 236 } |
|
| 237 |
|
| 238 void silcpurple_ftp_request(SilcClient client, SilcClientConnection conn, |
|
| 239 SilcClientEntry client_entry, SilcUInt32 session_id, |
|
| 240 const char *hostname, SilcUInt16 port) |
|
| 241 { |
|
| 242 PurpleConnection *gc = client->application; |
|
| 243 SilcPurple sg = gc->proto_data; |
|
| 244 SilcPurpleXfer 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 = purple_xfer_new(xfer->sg->account, PURPLE_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 purple_xfer_set_init_fnc(xfer->xfer, silcpurple_ftp_request_result); |
|
| 266 purple_xfer_set_request_denied_fnc(xfer->xfer, silcpurple_ftp_request_denied); |
|
| 267 purple_xfer_set_cancel_recv_fnc(xfer->xfer, silcpurple_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 purple_xfer_request(xfer->xfer); |
|
| 274 } |
|
| 275 |
|
| 276 static void |
|
| 277 silcpurple_ftp_send_cancel(PurpleXfer *x) |
|
| 278 { |
|
| 279 SilcPurpleXfer xfer = x->data; |
|
| 280 silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id); |
|
| 281 purple_xfer_unref(xfer->xfer); |
|
| 282 g_free(xfer->hostname); |
|
| 283 silc_free(xfer); |
|
| 284 } |
|
| 285 |
|
| 286 static void |
|
| 287 silcpurple_ftp_send(PurpleXfer *x) |
|
| 288 { |
|
| 289 SilcPurpleXfer xfer = x->data; |
|
| 290 const char *name; |
|
| 291 char *local_ip = NULL, *remote_ip = NULL; |
|
| 292 gboolean local = TRUE; |
|
| 293 |
|
| 294 name = purple_xfer_get_local_filename(x); |
|
| 295 |
|
| 296 /* Do the same magic what we do with key agreement (see silcpurple_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 (silcpurple_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 (silcpurple_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 silcpurple_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 silcpurple_ftp_send_file_resolved(SilcClient client, |
|
| 328 SilcClientConnection conn, |
|
| 329 SilcClientEntry *clients, |
|
| 330 SilcUInt32 clients_count, |
|
| 331 void *context) |
|
| 332 { |
|
| 333 PurpleConnection *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 purple_notify_error(gc, _("Secure File Transfer"), |
|
| 341 _("Cannot send file"), tmp); |
|
| 342 silc_free(context); |
|
| 343 return; |
|
| 344 } |
|
| 345 |
|
| 346 silcpurple_ftp_send_file(client->application, (const char *)context, NULL); |
|
| 347 silc_free(context); |
|
| 348 } |
|
| 349 |
|
| 350 PurpleXfer *silcpurple_ftp_new_xfer(PurpleConnection *gc, const char *name) |
|
| 351 { |
|
| 352 SilcPurple sg = gc->proto_data; |
|
| 353 SilcClient client = sg->client; |
|
| 354 SilcClientConnection conn = sg->conn; |
|
| 355 SilcClientEntry *clients; |
|
| 356 SilcUInt32 clients_count; |
|
| 357 SilcPurpleXfer 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 silcpurple_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 = purple_xfer_new(xfer->sg->account, PURPLE_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 purple_xfer_set_init_fnc(xfer->xfer, silcpurple_ftp_send); |
|
| 391 purple_xfer_set_request_denied_fnc(xfer->xfer, silcpurple_ftp_request_denied); |
|
| 392 purple_xfer_set_cancel_send_fnc(xfer->xfer, silcpurple_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 silcpurple_ftp_send_file(PurpleConnection *gc, const char *name, const char *file) |
|
| 402 { |
|
| 403 PurpleXfer *xfer = silcpurple_ftp_new_xfer(gc, name); |
|
| 404 |
|
| 405 g_return_if_fail(xfer != NULL); |
|
| 406 |
|
| 407 /* Choose file to send */ |
|
| 408 if (file) |
|
| 409 purple_xfer_request_accepted(xfer, file); |
|
| 410 else |
|
| 411 purple_xfer_request(xfer); |
|
| 412 } |
|