/*
- client_ftp.c
+ client_ftp.c
Author: Pekka Riikonen <priikone@silcnet.org>
- Copyright (C) 2001 Pekka Riikonen
+ Copyright (C) 2001 - 2007 Pekka Riikonen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
/* $Id$ */
-#include "clientlibincludes.h"
+#include "silc.h"
+#include "silcclient.h"
#include "client_internal.h"
-static int
-silc_client_connect_to_client(SilcClient client,
- SilcClientConnection conn, int port,
- char *host, void *context);
-static int
-silc_client_connect_to_client_internal(SilcClientInternalConnectContext *ctx);
-SILC_TASK_CALLBACK(silc_client_ftp_connected);
-static void silc_client_ftp_start_key_agreement(SilcClientFtpSession session,
- int sock);
-static void silc_client_ftp_session_free(SilcClientFtpSession session);
+/************************** Types and definitions ***************************/
/* File transmission session */
struct SilcClientFtpSessionStruct {
- uint32 session_id;
- SilcClient client;
- SilcClientConnection conn;
- SilcClientEntry client_entry;
-
- char *hostname;
- uint16 port;
- int listener;
-
- SilcClientFileMonitor monitor;
+ SilcClient client; /* Client */
+ SilcClientConnection server_conn; /* Connection to server */
+ SilcClientConnection conn; /* Connection to remote host */
+ SilcClientEntry client_entry; /* The client entry */
+ SilcClientListener listener; /* Listener */
+ SilcAsyncOperation op; /* Operation for connecting */
+ SilcClientConnectionParams params; /* Connection params */
+ SilcPublicKey public_key; /* Public key used in key exchange */
+ SilcPrivateKey private_key; /* Private key used in key exchange */
+ SilcUInt32 session_id; /* File transfer ID */
+
+ SilcClientFileMonitor monitor; /* File transfer monitor callback */
void *monitor_context;
- char *filepath;
-
- SilcSFTP sftp;
- SilcSFTPFilesystem fs;
- bool server;
-
- SilcSFTPHandle dir_handle;
- SilcSFTPHandle read_handle;
- uint64 filesize;
- uint64 read_offset;
- int fd;
+ SilcClientFileAskName ask_name; /* File name asking callback */
+ void *ask_name_context;
+ char *filepath; /* The remote filename */
+ char *path; /* User given path to save the file */
+
+ SilcStream stream; /* Wrapped SilcPacketStream */
+ SilcSFTP sftp; /* SFTP server/client */
+ SilcSFTPFilesystem fs; /* SFTP memory file system */
+ SilcSFTPHandle dir_handle; /* SFTP session directory handle */
+ SilcSFTPHandle read_handle; /* SFTP session file handles */
+
+ char *hostname; /* Remote host */
+ SilcUInt16 port; /* Remote port */
+ SilcUInt64 filesize; /* File size */
+ SilcUInt64 read_offset; /* Current read offset */
+ int fd; /* File descriptor */
+ unsigned int initiator : 1; /* File sender sets this to TRUE */
+ unsigned int closed : 1; /* silc_client_file_close called */
};
-/* SFTP packet send callback */
+/************************* SFTP Server Callbacks ****************************/
+
+/* SFTP monitor callback for SFTP server. This reports the application
+ how the transmission is going along. This function is for the client
+ who made the file available for download. */
-static void silc_client_ftp_send_packet(SilcSocketConnection sock,
- SilcBuffer packet, void *context)
+static void silc_client_ftp_monitor(SilcSFTP sftp,
+ SilcSFTPMonitors type,
+ const SilcSFTPMonitorData data,
+ void *context)
{
SilcClientFtpSession session = (SilcClientFtpSession)context;
- SilcClient client = session->client;
-
- SILC_LOG_DEBUG(("Start"));
- /* Send the packet immediately */
- silc_client_packet_send(client, sock, SILC_PACKET_FTP, NULL, 0, NULL, NULL,
- packet->data, packet->len, TRUE);
+ if (type == SILC_SFTP_MONITOR_READ) {
+ /* Call the monitor for application */
+ if (session->monitor)
+ (*session->monitor)(session->client, session->conn,
+ SILC_CLIENT_FILE_MONITOR_SEND,
+ SILC_CLIENT_FILE_OK,
+ data->offset, session->filesize,
+ session->client_entry, session->session_id,
+ session->filepath, session->monitor_context);
+ }
}
-/* Returns the read data */
+/************************* SFTP Client Callbacks ****************************/
+
+/* Returns the read data. This is the downloader's function (client side)
+ to receive the read data and read more until EOF is received from
+ the other side. This will also monitor the transmission and notify
+ the application. */
static void silc_client_ftp_data(SilcSFTP sftp,
SilcSFTPStatus status,
const unsigned char *data,
- uint32 data_len,
+ SilcUInt32 data_len,
void *context)
{
SilcClientFtpSession session = (SilcClientFtpSession)context;
SILC_LOG_DEBUG(("Start"));
if (status == SILC_SFTP_STATUS_EOF) {
+ /* EOF received */
/* Close the handle */
silc_sftp_close(sftp, session->read_handle, NULL, NULL);
session->read_handle = NULL;
- /* Close the read file descriptor */
+ /* Close the real file descriptor */
silc_file_close(session->fd);
return;
}
if (status != SILC_SFTP_STATUS_OK) {
- /* XXX errror */
+ /* Call monitor callback */
+ if (session->monitor)
+ (*session->monitor)(session->client, session->conn,
+ SILC_CLIENT_FILE_MONITOR_ERROR,
+ (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
+ SILC_CLIENT_FILE_NO_SUCH_FILE :
+ status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
+ SILC_CLIENT_FILE_PERMISSION_DENIED :
+ SILC_CLIENT_FILE_ERROR), 0, 0,
+ session->client_entry, session->session_id,
+ session->filepath, session->monitor_context);
/* Close the handle */
silc_sftp_close(sftp, session->read_handle, NULL, NULL);
session->read_handle = NULL;
- /* Close the read file descriptor */
+ /* Close the real file descriptor */
silc_file_close(session->fd);
return;
}
/* Read more, until EOF is received */
session->read_offset += data_len;
- silc_sftp_read(sftp, session->read_handle, session->read_offset, 16384,
+ silc_sftp_read(sftp, session->read_handle, session->read_offset,
+ SILC_PACKET_MAX_LEN - 1024,
silc_client_ftp_data, session);
+ /* Write the read data to the real file */
+ silc_file_write(session->fd, data, data_len);
+
/* Call monitor callback */
if (session->monitor)
(*session->monitor)(session->client, session->conn,
SILC_CLIENT_FILE_MONITOR_RECEIVE,
+ SILC_CLIENT_FILE_OK,
session->read_offset, session->filesize,
session->client_entry, session->session_id,
session->filepath, session->monitor_context);
-
- /* Write the read data */
- silc_file_write(session->fd, data, data_len);
}
+/* Returns handle for the opened file. This is the downloader's function.
+ This will begin reading the data from the file. */
+
static void silc_client_ftp_open_handle(SilcSFTP sftp,
SilcSFTPStatus status,
SilcSFTPHandle handle,
void *context)
{
SilcClientFtpSession session = (SilcClientFtpSession)context;
+ char path[512];
SILC_LOG_DEBUG(("Start"));
if (status != SILC_SFTP_STATUS_OK) {
- /* XXX errror */
+ /* Call monitor callback */
+ if (session->monitor)
+ (*session->monitor)(session->client, session->conn,
+ SILC_CLIENT_FILE_MONITOR_ERROR,
+ (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
+ SILC_CLIENT_FILE_NO_SUCH_FILE :
+ status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
+ SILC_CLIENT_FILE_PERMISSION_DENIED :
+ SILC_CLIENT_FILE_ERROR), 0, 0,
+ session->client_entry, session->session_id,
+ session->filepath, session->monitor_context);
+ return;
}
/* Open the actual local file */
- session->fd = silc_file_open(session->filepath, O_RDWR | O_CREAT);
+ memset(path, 0, sizeof(path));
+ silc_snprintf(path, sizeof(path) - 1, "%s%s", session->path ?
+ session->path : "", session->filepath);
+ session->fd = silc_file_open(path, O_RDWR | O_CREAT | O_EXCL);
if (session->fd < 0) {
- /* XXX errror */
+ /* Call monitor callback */
+ session->client->internal->ops->say(session->client, session->conn,
+ SILC_CLIENT_MESSAGE_ERROR,
+ "File `%s' open failed: %s",
+ session->filepath,
+ strerror(errno));
+
+ if (session->monitor)
+ (*session->monitor)(session->client, session->conn,
+ SILC_CLIENT_FILE_MONITOR_ERROR,
+ SILC_CLIENT_FILE_PERMISSION_DENIED, 0, 0,
+ session->client_entry, session->session_id,
+ session->filepath, session->monitor_context);
+ return;
}
+ session->read_handle = handle;
+
/* Now, start reading the file */
- silc_sftp_read(sftp, handle, session->read_offset, 16384,
+ silc_sftp_read(sftp, session->read_handle, session->read_offset,
+ SILC_PACKET_MAX_LEN - 1024,
silc_client_ftp_data, session);
/* Call monitor callback */
if (session->monitor)
(*session->monitor)(session->client, session->conn,
SILC_CLIENT_FILE_MONITOR_RECEIVE,
+ SILC_CLIENT_FILE_OK,
session->read_offset, session->filesize,
session->client_entry, session->session_id,
session->filepath, session->monitor_context);
}
-/* Returns the file name available for download. */
+/* Ask filename completion callback. Delivers the filepath selected by
+ user. */
-static void silc_client_ftp_readdir_name(SilcSFTP sftp,
- SilcSFTPStatus status,
- const SilcSFTPName name,
- void *context)
+static void silc_client_ftp_ask_name(const char *filepath,
+ void *context)
{
SilcClientFtpSession session = (SilcClientFtpSession)context;
SilcSFTPAttributesStruct attr;
+ char *remote_file = NULL;
SILC_LOG_DEBUG(("Start"));
- if (status != SILC_SFTP_STATUS_OK) {
- /* XXX errror */
+ if (filepath) {
+ remote_file = session->filepath;
+ session->filepath = NULL;
+ silc_free(session->path);
+ session->path = NULL;
+ session->filepath = strdup(filepath);
+ } else {
+ remote_file = strdup(session->filepath);
}
/* Now open the file */
memset(&attr, 0, sizeof(attr));
- silc_sftp_open(sftp, name->filename[0], SILC_SFTP_FXF_READ, &attr,
+ silc_sftp_open(session->sftp, remote_file, SILC_SFTP_FXF_READ, &attr,
silc_client_ftp_open_handle, session);
- /* Save the important attributes */
+ /* Close the directory handle */
+ silc_sftp_close(session->sftp, session->dir_handle, NULL, NULL);
+ session->dir_handle = NULL;
+
+ silc_free(remote_file);
+}
+
+/* Returns the file name available for download. This is the downloader's
+ function. */
+
+static void silc_client_ftp_readdir_name(SilcSFTP sftp,
+ SilcSFTPStatus status,
+ const SilcSFTPName name,
+ void *context)
+{
+ SilcClientFtpSession session = (SilcClientFtpSession)context;
+
+ SILC_LOG_DEBUG(("Start"));
+
+ if (status != SILC_SFTP_STATUS_OK) {
+ /* Call monitor callback */
+ if (session->monitor)
+ (*session->monitor)(session->client, session->conn,
+ SILC_CLIENT_FILE_MONITOR_ERROR,
+ (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
+ SILC_CLIENT_FILE_NO_SUCH_FILE :
+ status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
+ SILC_CLIENT_FILE_PERMISSION_DENIED :
+ SILC_CLIENT_FILE_ERROR), 0, 0,
+ session->client_entry, session->session_id,
+ session->filepath, session->monitor_context);
+ return;
+ }
+
+ /* Save the important attributes like filename and file size */
session->filepath = strdup(name->filename[0]);
session->filesize = name->attrs[0]->size;
- /* Close the directory handle */
- silc_sftp_close(sftp, session->dir_handle, NULL, NULL);
- session->dir_handle = NULL;
+ /* If the path was not provided, ask from application where to save the
+ downloaded file. */
+ if (!session->path && session->ask_name) {
+ session->ask_name(session->client, session->conn, session->session_id,
+ name->filename[0], silc_client_ftp_ask_name, session,
+ session->ask_name_context);
+ return;
+ }
+
+ /* Start downloading immediately to current directory. */
+ silc_client_ftp_ask_name(NULL, session);
}
-/* Returns the file handle after giving opendir command. */
+/* Returns the file handle after giving opendir command. This is the
+ downloader's function. */
static void silc_client_ftp_opendir_handle(SilcSFTP sftp,
SilcSFTPStatus status,
SILC_LOG_DEBUG(("Start"));
if (status != SILC_SFTP_STATUS_OK) {
- /* XXX errror */
+ /* Call monitor callback */
+ if (session->monitor)
+ (*session->monitor)(session->client, session->conn,
+ SILC_CLIENT_FILE_MONITOR_ERROR,
+ (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
+ SILC_CLIENT_FILE_NO_SUCH_FILE :
+ status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
+ SILC_CLIENT_FILE_PERMISSION_DENIED :
+ SILC_CLIENT_FILE_ERROR), 0, 0,
+ session->client_entry, session->session_id,
+ session->filepath, session->monitor_context);
+ return;
}
/* Now, read the directory */
session->dir_handle = handle;
}
-/* SFTP version callback for SFTP client */
+/* SFTP version callback for SFTP client. This is the downloader's function
+ after initializing the SFTP connection to the remote client. This will
+ find out the filename available for download. */
static void silc_client_ftp_version(SilcSFTP sftp,
SilcSFTPStatus status,
SILC_LOG_DEBUG(("Start"));
if (status != SILC_SFTP_STATUS_OK) {
- /* XXX errror */
+ /* Call monitor callback */
+ if (session->monitor)
+ (*session->monitor)(session->client, session->conn,
+ SILC_CLIENT_FILE_MONITOR_ERROR,
+ (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
+ SILC_CLIENT_FILE_NO_SUCH_FILE :
+ status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
+ SILC_CLIENT_FILE_PERMISSION_DENIED :
+ SILC_CLIENT_FILE_ERROR), 0, 0,
+ session->client_entry, session->session_id,
+ session->filepath, session->monitor_context);
+ return;
}
/* The SFTP session is open, now retrieve the info about available file. */
silc_sftp_opendir(sftp, "", silc_client_ftp_opendir_handle, session);
}
-/* This callback is called after the key agreement protocol has been
- performed. This calls the final completion callback for the application. */
+/* SFTP stream error callback */
-SILC_TASK_CALLBACK(silc_client_ftp_key_agreement_final)
+static void silc_client_ftp_error(SilcSFTP sftp, SilcSFTPStatus status,
+ void *context)
{
- SilcProtocol protocol = (SilcProtocol)context;
- SilcClientKEInternalContext *ctx =
- (SilcClientKEInternalContext *)protocol->context;
- SilcClient client = (SilcClient)ctx->client;
- SilcClientFtpSession session = (SilcClientFtpSession)ctx->context;
- SilcClientConnection conn = (SilcClientConnection)ctx->sock->user_data;
- SILC_LOG_DEBUG(("Start"));
+}
- if (protocol->state == SILC_PROTOCOL_STATE_ERROR ||
- protocol->state == SILC_PROTOCOL_STATE_FAILURE) {
- /* Error occured during protocol */
- silc_ske_free_key_material(ctx->keymat);
- goto out;
- }
+/************************ Static utility functions **************************/
- /* Set keys into use */
- silc_client_protocol_ke_set_keys(ctx->ske, ctx->sock, ctx->keymat,
- ctx->ske->prop->cipher,
- ctx->ske->prop->pkcs,
- ctx->ske->prop->hash,
- ctx->ske->prop->hmac,
- ctx->ske->prop->group);
+/* Free session resources. Connection must be closed before getting
+ here. */
- /* If we are the SFTP client then start the SFTP session and retrieve
- the info about the file available for download. */
- if (!session->server) {
- session->sftp = silc_sftp_client_start(conn->sock,
- silc_client_ftp_send_packet,
- session,
- silc_client_ftp_version, session);
+static void silc_client_ftp_session_free(SilcClientFtpSession session)
+{
+ SILC_LOG_DEBUG(("Free session %d", session->session_id));
+
+ silc_schedule_task_del_by_context(session->client->schedule, session);
+
+ silc_dlist_del(session->client->internal->ftp_sessions, session);
+
+ /* Abort connecting */
+ if (session->op)
+ silc_async_abort(session->op, NULL, NULL);
+
+ /* Destroy SFTP */
+ if (session->sftp) {
+ if (session->initiator)
+ silc_sftp_server_shutdown(session->sftp);
+ else
+ silc_sftp_client_shutdown(session->sftp);
}
+ if (session->fs)
+ silc_sftp_fs_memory_free(session->fs);
- out:
- silc_ske_free_key_material(ctx->keymat);
- if (ctx->ske)
- silc_ske_free(ctx->ske);
- silc_free(ctx->dest_id);
- silc_socket_free(ctx->sock);
- silc_free(ctx);
- ctx->sock->protocol = NULL;
- silc_protocol_free(protocol);
+ /* Destroy listener */
+ if (session->listener)
+ silc_client_listener_free(session->listener);
+
+ /* Destroy wrapped stream */
+ if (session->stream)
+ silc_stream_destroy(session->stream);
+
+ silc_client_unref_client(session->client, session->server_conn,
+ session->client_entry);
+ silc_free(session->hostname);
+ silc_free(session->filepath);
+ silc_free(session->path);
+ silc_free(session);
}
-static void silc_client_ftp_start_key_agreement(SilcClientFtpSession session,
- int sock)
+/* File transfer session timeout */
+
+SILC_TASK_CALLBACK(silc_client_ftp_timeout)
{
- SilcClient client = session->client;
- SilcClientKEInternalContext *proto_ctx;
- SilcProtocol protocol;
- SilcClientConnection conn;
- void *context;
+ SilcClientFtpSession session = context;
- SILC_LOG_DEBUG(("Start"));
+ SILC_LOG_DEBUG(("Timeout"));
- /* Add new connection for this session */
- conn = silc_client_add_connection(client, session->hostname,
- session->port, session);
-
- /* Allocate new socket connection object */
- silc_socket_alloc(sock, SILC_SOCKET_TYPE_UNKNOWN, (void *)conn, &conn->sock);
- conn->sock->hostname = strdup(session->hostname);
- conn->sock->port = silc_net_get_remote_port(sock);
-
- /* Allocate the SFTP */
- if (session->server)
- session->sftp = silc_sftp_server_start(conn->sock,
- silc_client_ftp_send_packet,
- session, session->fs);
-
- /* Allocate internal context for key exchange protocol. This is
- sent as context for the protocol. */
- proto_ctx = silc_calloc(1, sizeof(*proto_ctx));
- proto_ctx->client = client;
- proto_ctx->sock = silc_socket_dup(conn->sock);
- proto_ctx->rng = client->rng;
- proto_ctx->responder = FALSE;
- proto_ctx->context = session;
- proto_ctx->send_packet = silc_client_protocol_ke_send_packet;
- proto_ctx->verify = silc_client_protocol_ke_verify_key;
-
- /* Perform key exchange protocol. */
- silc_protocol_alloc(SILC_PROTOCOL_CLIENT_KEY_EXCHANGE,
- &protocol, (void *)proto_ctx,
- silc_client_ftp_key_agreement_final);
-
- /* Register the connection for network input and output. This sets
- that scheduler will listen for incoming packets for this connection
- and sets that outgoing packets may be sent to this connection as well.
- However, this doesn't set the scheduler for outgoing traffic, it will
- be set separately by calling SILC_CLIENT_SET_CONNECTION_FOR_OUTPUT,
- later when outgoing data is available. */
- context = (void *)client;
- SILC_CLIENT_REGISTER_CONNECTION_FOR_IO(sock);
-
- /* Execute the protocol */
- silc_protocol_execute(protocol, client->schedule, 0, 0);
+ /* Close connection (destroyes the session context later). If it is
+ already closed, destroy the session now. */
+ if (session->conn) {
+ silc_client_close_connection(session->client, session->conn);
+ session->conn = NULL;
+ } else {
+ /* Call monitor callback */
+ if (session->monitor)
+ (*session->monitor)(session->client, session->conn,
+ SILC_CLIENT_FILE_MONITOR_ERROR,
+ SILC_CLIENT_FILE_TIMEOUT, 0, 0,
+ session->client_entry, session->session_id,
+ session->filepath, session->monitor_context);
+
+ silc_client_ftp_session_free(context);
+ }
}
-SILC_TASK_CALLBACK(silc_client_ftp_connected)
+/* File transfer session closing task callback */
+
+SILC_TASK_CALLBACK(silc_client_file_close_final)
{
- SilcClientInternalConnectContext *ctx =
- (SilcClientInternalConnectContext *)context;
- SilcClient client = ctx->client;
- SilcClientConnection conn = ctx->conn;
- SilcClientFtpSession session = (SilcClientFtpSession)ctx->context;
- int opt, opt_len = sizeof(opt);
+ SilcClientFtpSession session = context;
- SILC_LOG_DEBUG(("Start"));
+ /* Close connection (destroyes the session context later). If it is
+ already closed, destroy the session now. */
+ if (session->conn) {
+ silc_client_close_connection(session->client, session->conn);
+ session->conn = NULL;
+ } else {
+ silc_client_ftp_session_free(context);
+ }
+}
- /* Check the socket status as it might be in error */
- silc_net_get_socket_opt(fd, SOL_SOCKET, SO_ERROR, &opt, &opt_len);
- if (opt != 0) {
- if (ctx->tries < 2) {
- /* Connection failed but lets try again */
- client->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
- "Could not connect to client %s: %s",
- ctx->host, strerror(opt));
- client->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT,
- "Connecting to port %d of client %s resumed",
- ctx->port, ctx->host);
-
- /* Unregister old connection try */
- silc_schedule_unset_listen_fd(client->schedule, fd);
- silc_net_close_connection(fd);
- silc_schedule_task_del(client->schedule, ctx->task);
-
- /* Try again */
- silc_client_connect_to_client_internal(ctx);
- ctx->tries++;
- } else {
- /* Connection failed and we won't try anymore */
- client->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
- "Could not connect to client %s: %s",
- ctx->host, strerror(opt));
- silc_schedule_unset_listen_fd(client->schedule, fd);
- silc_net_close_connection(fd);
- silc_schedule_task_del(client->schedule, ctx->task);
- silc_free(ctx);
- silc_client_ftp_session_free(session);
- }
+/* Client resolving callback. Continues with the FTP packet processing */
+
+static void silc_client_ftp_client_resolved(SilcClient client,
+ SilcClientConnection conn,
+ SilcStatus status,
+ SilcDList clients,
+ void *context)
+{
+ SilcFSMThread thread = context;
+ SilcPacket packet = silc_fsm_get_state_context(thread);
+
+ /* If no client found, ignore the packet, a silent error */
+ if (!clients) {
+ silc_packet_free(packet);
+ silc_fsm_finish(thread);
return;
}
- silc_schedule_unset_listen_fd(client->schedule, fd);
- silc_schedule_task_del(client->schedule, ctx->task);
-
- /* Start the key agreement */
- silc_client_ftp_start_key_agreement(session, fd);
+ /* Continue processing the packet */
+ SILC_FSM_CALL_CONTINUE(context);
}
-static int
-silc_client_connect_to_client_internal(SilcClientInternalConnectContext *ctx)
-{
- int sock;
-
- /* Create connection to server asynchronously */
- sock = silc_net_create_connection_async(NULL, ctx->port, ctx->host);
- if (sock < 0)
- return -1;
+/* FTP packet payload encoder/decoder. This is called for every FTP packet.
+ We add/remove FTP payload in this function, because SFTP library does not
+ add/remove it. */
- /* Register task that will receive the async connect and will
- read the result. */
- ctx->task = silc_schedule_task_add(ctx->client->schedule, sock,
- silc_client_ftp_connected,
- (void *)ctx, 0, 0,
- SILC_TASK_FD,
- SILC_TASK_PRI_NORMAL);
- silc_schedule_set_listen_fd(ctx->client->schedule, sock, SILC_TASK_WRITE);
+static SilcBool
+silc_client_ftp_coder(SilcStream stream, SilcStreamStatus status,
+ SilcBuffer buffer, void *context)
+{
+ /* Pull FTP type in the payload, revealing SFTP payload */
+ if (status == SILC_STREAM_CAN_READ) {
+ if (silc_buffer_len(buffer) >= 1)
+ silc_buffer_pull(buffer, 1);
+ return TRUE;
+ }
- ctx->sock = sock;
+ /* Add FTP type before SFTP data */
+ if (status == SILC_STREAM_CAN_WRITE) {
+ if (silc_buffer_format(buffer,
+ SILC_STR_UI_CHAR(1),
+ SILC_STR_END) < 0)
+ return FALSE;
+ return TRUE;
+ }
- return sock;
+ return FALSE;
}
-static int
-silc_client_connect_to_client(SilcClient client,
- SilcClientConnection conn, int port,
- char *host, void *context)
+/* FTP Connection callback. The SFTP session is started here. */
+
+static void
+silc_client_ftp_connect_completion(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientConnectionStatus status,
+ SilcStatus error,
+ const char *message,
+ void *context)
{
- SilcClientInternalConnectContext *ctx;
-
- /* Allocate internal context for connection process. This is
- needed as we are doing async connecting. */
- ctx = silc_calloc(1, sizeof(*ctx));
- ctx->client = client;
- ctx->conn = conn;
- ctx->host = strdup(host);
- ctx->port = port;
- ctx->tries = 0;
- ctx->context = context;
-
- /* Do the actual connecting process */
- return silc_client_connect_to_client_internal(ctx);
-}
+ SilcClientFtpSession session = context;
-/* Free session */
+ session->conn = conn;
+ session->op = NULL;
+
+ silc_schedule_task_del_by_context(client->schedule, session);
+
+ switch (status) {
+ case SILC_CLIENT_CONN_SUCCESS:
+ SILC_LOG_DEBUG(("Connected, conn %p", conn));
+
+ /* Wrap the connection packet stream */
+ session->stream = silc_packet_stream_wrap(conn->stream, SILC_PACKET_FTP,
+ 0, FALSE,
+ silc_client_ftp_coder, session);
+ if (!session->stream) {
+ /* Call monitor callback */
+ if (session->monitor)
+ (*session->monitor)(session->client, session->conn,
+ SILC_CLIENT_FILE_MONITOR_ERROR,
+ SILC_CLIENT_FILE_ERROR, 0, 0,
+ session->client_entry, session->session_id,
+ session->filepath, session->monitor_context);
+ silc_client_close_connection(client, conn);
+ session->conn = NULL;
+ return;
+ }
-static void silc_client_ftp_session_free(SilcClientFtpSession session)
-{
- silc_dlist_del(session->conn->ftp_sessions, session);
+ if (!session->initiator) {
+ /* If we are the SFTP client then start the SFTP session and retrieve
+ the info about the file available for download. */
+ session->sftp = silc_sftp_client_start(session->stream,
+ conn->internal->schedule,
+ silc_client_ftp_version,
+ silc_client_ftp_error, session);
+ } else {
+ /* Start SFTP server */
+ session->sftp = silc_sftp_server_start(session->stream,
+ conn->internal->schedule,
+ silc_client_ftp_error, session,
+ session->fs);
+
+ /* Monitor transmission */
+ silc_sftp_server_set_monitor(session->sftp, SILC_SFTP_MONITOR_READ,
+ silc_client_ftp_monitor, session);
+ }
- if (session->sftp) {
- if (session->server)
- silc_sftp_server_shutdown(session->sftp);
- else
- silc_sftp_client_shutdown(session->sftp);
- }
+ break;
- if (session->fs)
- silc_sftp_fs_memory_free(session->fs);
+ case SILC_CLIENT_CONN_DISCONNECTED:
+ SILC_LOG_DEBUG(("Disconnected %p", conn));
- silc_free(session->hostname);
- silc_free(session->filepath);
- silc_free(session);
+ /* Call monitor callback */
+ if (session->monitor)
+ (*session->monitor)(session->client, session->conn,
+ SILC_CLIENT_FILE_MONITOR_DISCONNECT,
+ SILC_CLIENT_FILE_ERROR, 0, 0,
+ session->client_entry, session->session_id,
+ session->filepath, session->monitor_context);
+
+ /* Connection already closed */
+ session->conn = NULL;
+
+ /* If closed by user, destroy the session now */
+ if (session->closed)
+ silc_client_ftp_session_free(session);
+ break;
+
+ case SILC_CLIENT_CONN_ERROR_TIMEOUT:
+ SILC_LOG_DEBUG(("Connecting timeout"));
+
+ /* Call monitor callback */
+ if (session->monitor)
+ (*session->monitor)(session->client, session->conn,
+ SILC_CLIENT_FILE_MONITOR_ERROR,
+ SILC_CLIENT_FILE_TIMEOUT, 0, 0,
+ session->client_entry, session->session_id,
+ session->filepath, session->monitor_context);
+
+ /* Connection already closed */
+ session->conn = NULL;
+ break;
+
+ default:
+ SILC_LOG_DEBUG(("Connecting error %d", status));
+
+ /* Call monitor callback */
+ if (session->monitor)
+ (*session->monitor)(session->client, session->conn,
+ SILC_CLIENT_FILE_MONITOR_ERROR,
+ status != SILC_CLIENT_CONN_ERROR ?
+ SILC_CLIENT_FILE_KEY_AGREEMENT_FAILED :
+ SILC_CLIENT_FILE_CONNECT_FAILED, 0, 0,
+ session->client_entry, session->session_id,
+ session->filepath, session->monitor_context);
+
+ /* Connection already closed */
+ session->conn = NULL;
+ break;
+ }
}
-SILC_TASK_CALLBACK(silc_client_ftp_process_key_agreement)
-{
- SilcClientFtpSession session = (SilcClientFtpSession)context;
- SilcClient client = session->client;
- SilcClientConnection conn;
- SilcSocketConnection newsocket;
- SilcClientKEInternalContext *proto_ctx;
- int sock;
+/*************************** File Transfer API ******************************/
- SILC_LOG_DEBUG(("Start"));
+/* Free all file transfer sessions. */
- sock = silc_net_accept_connection(session->listener);
- if (sock < 0) {
- /* XXX error */
+void silc_client_ftp_free_sessions(SilcClient client)
+{
+ SilcClientFtpSession session;
+
+ if (!client->internal->ftp_sessions)
return;
- }
- /* Set socket options */
- silc_net_set_socket_nonblock(sock);
- silc_net_set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
+ silc_dlist_start(client->internal->ftp_sessions);
+ while ((session = silc_dlist_get(client->internal->ftp_sessions)))
+ silc_client_ftp_session_free(session);
+ silc_dlist_del(client->internal->ftp_sessions, session);
+}
+
+/* Free file transfer session by client entry. */
- /* Allocate new socket connection object */
- silc_socket_alloc(sock, SILC_SOCKET_TYPE_UNKNOWN, NULL, &newsocket);
+void silc_client_ftp_session_free_client(SilcClient client,
+ SilcClientEntry client_entry)
+{
+ SilcClientFtpSession session;
- /* Perform name and address lookups for the remote host. */
- silc_net_check_host_by_sock(sock, &newsocket->hostname, &newsocket->ip);
- if (!newsocket->hostname && !newsocket->ip) {
- /* XXX error */
+ if (!client->internal->ftp_sessions)
return;
- }
- if (!newsocket->hostname)
- newsocket->hostname = strdup(newsocket->ip);
- newsocket->port = silc_net_get_remote_port(sock);
-
- /* Add new connection for this session */
- conn = silc_client_add_connection(client, newsocket->hostname,
- newsocket->port, session);
- conn->sock = newsocket;
- conn->sock->user_data = conn;
-
- /* Allocate internal context for key exchange protocol. This is
- sent as context for the protocol. */
- proto_ctx = silc_calloc(1, sizeof(*proto_ctx));
- proto_ctx->client = client;
- proto_ctx->sock = silc_socket_dup(conn->sock);
- proto_ctx->rng = client->rng;
- proto_ctx->responder = TRUE;
- proto_ctx->context = session;
- proto_ctx->send_packet = silc_client_protocol_ke_send_packet;
- proto_ctx->verify = silc_client_protocol_ke_verify_key;
-
- /* Prepare the connection for key exchange protocol. We allocate the
- protocol but will not start it yet. The connector will be the
- initiator of the protocol thus we will wait for initiation from
- there before we start the protocol. */
- silc_protocol_alloc(SILC_PROTOCOL_CLIENT_KEY_EXCHANGE,
- &newsocket->protocol, proto_ctx,
- silc_client_ftp_key_agreement_final);
-
- /* Register the connection for network input and output. This sets
- that scheduler will listen for incoming packets for this connection
- and sets that outgoing packets may be sent to this connection as well.
- However, this doesn't set the scheduler for outgoing traffic, it
- will be set separately by calling SILC_CLIENT_SET_CONNECTION_FOR_OUTPUT,
- later when outgoing data is available. */
- context = (void *)client;
- SILC_CLIENT_REGISTER_CONNECTION_FOR_IO(sock);
+
+ /* Get the session */
+ silc_dlist_start(client->internal->ftp_sessions);
+ while ((session = silc_dlist_get(client->internal->ftp_sessions)))
+ if (session->client_entry == client_entry)
+ silc_client_ftp_session_free(session);
}
-uint32 silc_client_file_send(SilcClient client,
- SilcClientConnection conn,
- SilcClientFileMonitor monitor,
- void *monitor_context,
- SilcClientEntry client_entry,
- const char *filepath)
+/* Sends a file indicated by the `filepath' to the remote client
+ indicated by the `client_entry'. This will negotiate a secret key
+ with the remote client before actually starting the transmission of
+ the file. The `monitor' callback will be called to monitor the
+ transmission of the file. */
+
+SilcClientFileError
+silc_client_file_send(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientEntry client_entry,
+ SilcClientConnectionParams *params,
+ SilcPublicKey public_key,
+ SilcPrivateKey private_key,
+ SilcClientFileMonitor monitor,
+ void *monitor_context,
+ const char *filepath,
+ SilcUInt32 *session_id)
{
SilcClientFtpSession session;
- SilcBuffer keyagr, ftp;
- char *filename;
+ SilcBuffer keyagr;
+ char *filename, *path;
+ int fd;
- SILC_LOG_DEBUG(("Start"));
+ SILC_LOG_DEBUG(("File send request (file: %s)", filepath));
+
+ if (!client || !client_entry || !filepath || !params ||
+ !public_key || !private_key)
+ return SILC_CLIENT_FILE_ERROR;
/* Check for existing session for `filepath'. */
- silc_dlist_start(conn->ftp_sessions);
- while ((session = silc_dlist_get(conn->ftp_sessions)) != SILC_LIST_END) {
- if (!strcmp(session->filepath, filepath) &&
+ silc_dlist_start(client->internal->ftp_sessions);
+ while ((session = silc_dlist_get(client->internal->ftp_sessions))) {
+ if (session->filepath && !strcmp(session->filepath, filepath) &&
session->client_entry == client_entry)
- return 0;
+ return SILC_CLIENT_FILE_ALREADY_STARTED;
}
+ /* See whether the file exists and can be opened */
+ fd = silc_file_open(filepath, O_RDONLY);
+ if (fd < 0)
+ return SILC_CLIENT_FILE_NO_SUCH_FILE;
+ silc_file_close(fd);
+
/* Add new session */
session = silc_calloc(1, sizeof(*session));
- session->session_id = conn->next_session_id++;
+ if (!session)
+ return SILC_CLIENT_FILE_ERROR;
+ session->session_id = ++client->internal->next_session_id;
session->client = client;
- session->conn = conn;
- session->client_entry = client_entry;
+ session->server_conn = conn;
+ session->initiator = TRUE;
+ session->client_entry = silc_client_ref_client(client, conn, client_entry);
session->monitor = monitor;
session->monitor_context = monitor_context;
session->filepath = strdup(filepath);
- session->server = TRUE;
- silc_dlist_add(conn->ftp_sessions, session);
+ session->params = *params;
+ session->public_key = public_key;
+ session->private_key = private_key;
+
+ if (silc_asprintf(&path, "file://%s", filepath) < 0) {
+ silc_free(session);
+ return SILC_CLIENT_FILE_NO_MEMORY;
+ }
/* Allocate memory filesystem and put the file to it */
- if (strrchr(filepath, '/'))
- filename = strrchr(filepath, '/') + 1;
+ if (strrchr(path, '/'))
+ filename = strrchr(path, '/') + 1;
else
- filename = (char *)filepath;
+ filename = (char *)path;
session->fs = silc_sftp_fs_memory_alloc(SILC_SFTP_FS_PERM_READ |
SILC_SFTP_FS_PERM_EXEC);
silc_sftp_fs_memory_add_file(session->fs, NULL, SILC_SFTP_FS_PERM_READ,
- filename, filepath);
+ filename, path);
+
+ session->filesize = silc_file_size(filepath);
+
+ /* If local IP is provided, create listener for incoming key exchange */
+ if (params->local_ip || params->bind_ip) {
+ session->listener =
+ silc_client_listener_add(client,
+ conn->internal->schedule,
+ params, public_key, private_key,
+ silc_client_ftp_connect_completion,
+ session);
+ if (!session->listener) {
+ client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
+ "Cannot create listener for file transfer: "
+ "%s", strerror(errno));
+ silc_free(session);
+ return SILC_CLIENT_FILE_NO_MEMORY;
+ }
+
+ session->hostname = (params->bind_ip ? strdup(params->bind_ip) :
+ strdup(params->local_ip));
+ session->port = silc_client_listener_get_local_port(session->listener);
+ }
+
+ SILC_LOG_DEBUG(("Sending key agreement for file transfer"));
/* Send the key agreement inside FTP packet */
- keyagr = silc_key_agreement_payload_encode(NULL, 0);
-
- ftp = silc_buffer_alloc(1 + keyagr->len);
- silc_buffer_pull_tail(ftp, SILC_BUFFER_END(ftp));
- silc_buffer_format(ftp,
- SILC_STR_UI_CHAR(1),
- SILC_STR_UI_XNSTRING(keyagr->data, keyagr->len),
- SILC_STR_END);
- silc_client_packet_send(client, conn->sock, SILC_PACKET_FTP,
- client_entry->id, SILC_ID_CLIENT, NULL, NULL,
- ftp->data, ftp->len, FALSE);
+ keyagr = silc_key_agreement_payload_encode(session->hostname, 0,
+ session->port);
+ if (!keyagr) {
+ if (session->listener)
+ silc_client_listener_free(session->listener);
+ silc_free(session);
+ return SILC_CLIENT_FILE_NO_MEMORY;
+ }
+ silc_packet_send_va_ext(conn->stream, SILC_PACKET_FTP, 0, 0, NULL,
+ SILC_ID_CLIENT, &client_entry->id, NULL, NULL,
+ SILC_STR_UI_CHAR(1),
+ SILC_STR_DATA(silc_buffer_data(keyagr),
+ silc_buffer_len(keyagr)),
+ SILC_STR_END);
silc_buffer_free(keyagr);
- silc_buffer_free(ftp);
+ silc_free(path);
+
+ silc_dlist_add(client->internal->ftp_sessions, session);
+ if (session_id)
+ *session_id = session->session_id;
- return session->session_id;
+ /* Add session request timeout */
+ if (params && params->timeout_secs)
+ silc_schedule_task_add_timeout(client->schedule,
+ silc_client_ftp_timeout, session,
+ params->timeout_secs, 0);
+
+ return SILC_CLIENT_FILE_OK;
}
-bool silc_client_file_receive(SilcClient client,
- SilcClientConnection conn,
- SilcClientFileMonitor monitor,
- void *monitor_context,
- SilcClientEntry client_entry,
- uint32 session_id)
+/* Receives a file from a client indicated by the `client_entry'. The
+ `session_id' indicates the file transmission session and it has been
+ received in the `ftp' client operation function. This will actually
+ perform the key agreement protocol with the remote client before
+ actually starting the file transmission. The `monitor' callback
+ will be called to monitor the transmission. */
+
+SilcClientFileError
+silc_client_file_receive(SilcClient client,
+ SilcClientConnection conn,
+ SilcClientConnectionParams *params,
+ SilcPublicKey public_key,
+ SilcPrivateKey private_key,
+ SilcClientFileMonitor monitor,
+ void *monitor_context,
+ const char *path,
+ SilcUInt32 session_id,
+ SilcClientFileAskName ask_name,
+ void *ask_name_context)
{
SilcClientFtpSession session;
- SilcBuffer keyagr, ftp;
+ SilcBuffer keyagr;
- SILC_LOG_DEBUG(("Start"));
+ if (!client || !conn)
+ return SILC_CLIENT_FILE_ERROR;
+
+ SILC_LOG_DEBUG(("Start, Session ID: %d", session_id));
/* Get the session */
- silc_dlist_start(conn->ftp_sessions);
- while ((session = silc_dlist_get(conn->ftp_sessions)) != SILC_LIST_END) {
+ silc_dlist_start(client->internal->ftp_sessions);
+ while ((session = silc_dlist_get(client->internal->ftp_sessions))
+ != SILC_LIST_END) {
if (session->session_id == session_id) {
break;
}
if (session == SILC_LIST_END) {
SILC_LOG_DEBUG(("Unknown session ID: %d\n", session_id));
- return FALSE;
+ return SILC_CLIENT_FILE_UNKNOWN_SESSION;
}
/* See if we have this session running already */
if (session->sftp || session->listener) {
SILC_LOG_DEBUG(("Session already started"));
- return FALSE;
+ return SILC_CLIENT_FILE_ALREADY_STARTED;
}
session->monitor = monitor;
session->monitor_context = monitor_context;
- session->client_entry = client_entry;
- session->conn = conn;
+ session->ask_name = ask_name;
+ session->ask_name_context = ask_name_context;
+ session->path = path ? strdup(path) : NULL;
+
+ /* If the hostname and port already exists then the remote client did
+ provide the connection point to us and we won't create listener, but
+ create the connection ourselves. */
+ if (session->hostname && session->port) {
+ SILC_LOG_DEBUG(("Connecting to remote client"));
+ /* Connect to the remote client. Performs key exchange automatically. */
+ session->op =
+ silc_client_connect_to_client(client, params, public_key, private_key,
+ session->hostname, session->port,
+ silc_client_ftp_connect_completion,
+ session);
+ if (!session->op) {
+ silc_free(session);
+ return SILC_CLIENT_FILE_ERROR;
+ }
+ } else {
+ /* Add the listener for the key agreement */
+ SILC_LOG_DEBUG(("Creating listener for file transfer"));
+ if (!params || (!params->local_ip && !params->bind_ip)) {
+ session->client->internal->ops->say(session->client, session->conn,
+ SILC_CLIENT_MESSAGE_ERROR,
+ "Cannot create listener for file "
+ "transfer; IP address and/or port "
+ "not provided");
+ silc_free(session);
+ return SILC_CLIENT_FILE_ERROR;
+ }
+ session->listener =
+ silc_client_listener_add(client, conn->internal->schedule, params,
+ public_key, private_key,
+ silc_client_ftp_connect_completion,
+ session);
+ if (!session->listener) {
+ client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
+ "Cannot create listener for file transfer: "
+ "%s", strerror(errno));
+ silc_free(session);
+ return SILC_CLIENT_FILE_NO_MEMORY;
+ }
+ session->hostname = (params->bind_ip ? strdup(params->bind_ip) :
+ strdup(params->local_ip));
+ session->port = silc_client_listener_get_local_port(session->listener);
+
+ /* Send the key agreement inside FTP packet */
+ SILC_LOG_DEBUG(("Sending key agreement for file transfer"));
+ keyagr = silc_key_agreement_payload_encode(session->hostname, 0,
+ session->port);
+ if (!keyagr) {
+ silc_client_listener_free(session->listener);
+ silc_free(session);
+ return SILC_CLIENT_FILE_NO_MEMORY;
+ }
+ silc_packet_send_va_ext(conn->stream, SILC_PACKET_FTP, 0, 0, NULL,
+ SILC_ID_CLIENT, &session->client_entry->id,
+ NULL, NULL,
+ SILC_STR_UI_CHAR(1),
+ SILC_STR_DATA(silc_buffer_data(keyagr),
+ silc_buffer_len(keyagr)),
+ SILC_STR_END);
+ silc_buffer_free(keyagr);
+
+ /* Add session request timeout */
+ if (params && params->timeout_secs)
+ silc_schedule_task_add_timeout(client->schedule,
+ silc_client_ftp_timeout, session,
+ params->timeout_secs, 0);
+ }
+
+ return SILC_CLIENT_FILE_OK;
+}
+
+/* Closes file transmission session indicated by the `session_id'.
+ If file transmission is being conducted it will be aborted
+ automatically. This function is also used to close the session
+ after successful file transmission. This function can be used
+ also to reject incoming file transmission request. */
+
+SilcClientFileError silc_client_file_close(SilcClient client,
+ SilcClientConnection conn,
+ SilcUInt32 session_id)
+{
+ SilcClientFtpSession session;
+
+ if (!client || !conn)
+ return SILC_CLIENT_FILE_ERROR;
+
+ SILC_LOG_DEBUG(("Closing file transer session %d", session_id));
- /* Add the listener for the key agreement */
- session->hostname = silc_net_localhost();
- session->listener = silc_net_create_server(0, session->hostname);
- if (session->listener < 0) {
- /* XXX Error */
- SILC_LOG_DEBUG(("Could not create listener"));
- return FALSE;
+ /* Get the session */
+ silc_dlist_start(client->internal->ftp_sessions);
+ while ((session = silc_dlist_get(client->internal->ftp_sessions))
+ != SILC_LIST_END) {
+ if (session->session_id == session_id)
+ break;
}
- session->port = silc_net_get_local_port(session->listener);
- silc_schedule_task_add(client->schedule, session->listener,
- silc_client_ftp_process_key_agreement, session,
- 0, 0, SILC_TASK_FD, SILC_TASK_PRI_NORMAL);
- /* Send the key agreement inside FTP packet */
- keyagr = silc_key_agreement_payload_encode(NULL, 0);
-
- ftp = silc_buffer_alloc(1 + keyagr->len);
- silc_buffer_pull_tail(ftp, SILC_BUFFER_END(ftp));
- silc_buffer_format(ftp,
- SILC_STR_UI_CHAR(1),
- SILC_STR_UI_XNSTRING(keyagr->data, keyagr->len),
- SILC_STR_END);
- silc_client_packet_send(client, conn->sock, SILC_PACKET_FTP,
- client_entry->id, SILC_ID_CLIENT, NULL, NULL,
- ftp->data, ftp->len, FALSE);
+ if (session == SILC_LIST_END) {
+ SILC_LOG_DEBUG(("Unknown session ID: %d\n", session_id));
+ return SILC_CLIENT_FILE_UNKNOWN_SESSION;
+ }
- silc_buffer_free(keyagr);
- silc_buffer_free(ftp);
+ if (session->monitor) {
+ (*session->monitor)(session->client, session->conn,
+ SILC_CLIENT_FILE_MONITOR_CLOSED,
+ SILC_CLIENT_FILE_OK, 0, 0,
+ session->client_entry, session->session_id,
+ session->filepath, session->monitor_context);
- return TRUE;
-}
+ /* No more callbacks to application */
+ session->monitor = NULL;
+ }
-bool silc_client_file_close(SilcClient client,
- SilcClientConnection conn,
- uint32 session_id)
-{
+ silc_schedule_task_del_by_context(client->schedule, session);
- SILC_LOG_DEBUG(("Start"));
+ session->closed = TRUE;
- return TRUE;
+ /* Destroy via timeout */
+ silc_schedule_task_add_timeout(conn->internal->schedule,
+ silc_client_file_close_final, session,
+ 0, 1);
+
+ return SILC_CLIENT_FILE_OK;
}
-/* Callback called after remote client information has been resolved.
- This will try to find existing session for the client entry. If found
- then continue with the key agreement protocol. If not then it means
- this is a file transfer request and we let the application know. */
-
-static void
-silc_client_ftp_resolve_cb(SilcClient client,
- SilcClientConnection conn,
- SilcClientEntry *clients,
- uint32 clients_count,
- void *context)
+/************************** FTP Request Processing **************************/
+
+/* Received file transfer packet. Only file transfer requests get here.
+ The actual file transfer is handled by the SFTP library when we give it
+ the packet stream wrapped into SilcStream context. */
+
+SILC_FSM_STATE(silc_client_ftp)
{
- SilcPacketContext *packet = (SilcPacketContext *)context;
+ SilcClientConnection conn = fsm_context;
+ SilcClient client = conn->client;
+ SilcPacket packet = state_context;
SilcClientFtpSession session;
- SilcKeyAgreementPayload payload;
- SilcClientEntry client_entry;
+ SilcClientID remote_id;
+ SilcClientEntry remote_client;
+ SilcKeyAgreementPayload payload = NULL;
char *hostname;
- uint16 port;
- int sock;
+ SilcUInt16 port;
- SILC_LOG_DEBUG(("Start"));
+ SILC_LOG_DEBUG(("Process file transfer packet"));
- if (!clients)
+ if (silc_buffer_len(&packet->buffer) < 1)
goto out;
- client_entry = clients[0];
+ /* We support file transfer type number 1 (== SFTP) */
+ if (packet->buffer.data[0] != 0x01) {
+ SILC_LOG_DEBUG(("Unsupported file transfer type %d",
+ packet->buffer.data[0]));
+ goto out;
+ }
- silc_dlist_start(conn->ftp_sessions);
- while ((session = silc_dlist_get(conn->ftp_sessions)) != SILC_LIST_END) {
- if (session->client_entry == client_entry)
+ if (!silc_id_str2id(packet->src_id, packet->src_id_len,
+ SILC_ID_CLIENT, &remote_id, sizeof(remote_id))) {
+ SILC_LOG_DEBUG(("Invalid client ID"));
+ goto out;
+ }
+
+ /* Check whether we know this client already */
+ remote_client = silc_client_get_client_by_id(client, conn, &remote_id);
+ if (!remote_client || !remote_client->internal.valid) {
+ /** Resolve client info */
+ silc_client_unref_client(client, conn, remote_client);
+ SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
+ client, conn, &remote_id, NULL,
+ silc_client_ftp_client_resolved,
+ fsm));
+ /* NOT REACHED */
+ }
+
+ /* Get session */
+ silc_dlist_start(client->internal->ftp_sessions);
+ while ((session = silc_dlist_get(client->internal->ftp_sessions))) {
+ if (session->client_entry == remote_client &&
+ (!session->initiator || !session->listener))
break;
}
/* Parse the key agreement payload */
- payload = silc_key_agreement_payload_parse(packet->buffer);
- if (!payload)
+ payload =
+ silc_key_agreement_payload_parse(silc_buffer_data(&packet->buffer) + 1,
+ silc_buffer_len(&packet->buffer) - 1);
+ if (!payload) {
+ SILC_LOG_DEBUG(("Invalid key agreement payload"));
goto out;
+ }
hostname = silc_key_agreement_get_hostname(payload);
port = silc_key_agreement_get_port(payload);
+ if (!hostname || !port) {
+ hostname = NULL;
+ port = 0;
+ }
+
+ /* If session doesn't exist, we create new one. If session exists, but
+ we are responder it means that the remote sent another request and user
+ hasn't even accepted the first one yet. We assume this session is new
+ session as well. */
+ if (!session || !hostname || !session->initiator) {
+ /* New file transfer session */
+ SILC_LOG_DEBUG(("New file transfer session %d",
+ client->internal->next_session_id + 1));
- if (session == SILC_LIST_END) {
- /* No session found, create one and let the application know about
- incomoing file transfer request. */
-
- /* Add new session */
session = silc_calloc(1, sizeof(*session));
- session->session_id = conn->next_session_id++;
+ if (!session)
+ goto out;
+ session->session_id = ++client->internal->next_session_id;
session->client = client;
- session->conn = conn;
- silc_dlist_add(conn->ftp_sessions, session);
-
- /* Let the application know */
- client->ops->ftp(client, conn, client_entry,
- session->session_id, hostname, port);
-
- /* If hostname was provided we'll start the key exchange now. */
+ session->server_conn = conn;
+ session->client_entry = silc_client_ref_client(client, conn,
+ remote_client);
if (hostname && port) {
- /* XXX */
+ session->hostname = strdup(hostname);
+ session->port = port;
}
+ silc_dlist_add(client->internal->ftp_sessions, session);
- silc_key_agreement_payload_free(payload);
+ /* Notify application of incoming FTP request */
+ client->internal->ops->ftp(client, conn, remote_client,
+ session->session_id, hostname, port);
goto out;
}
- if (!hostname)
- goto out;
+ /* Session exists, continue with key agreement protocol. */
+ SILC_LOG_DEBUG(("Session %d exists, connecting to remote client",
+ session->session_id));
session->hostname = strdup(hostname);
session->port = port;
- /* Session exists, continue with key agreement protocol. */
- sock = silc_client_connect_to_client(client, conn, port, hostname,
- session);
- if (sock < 0)
- goto out;
+ /* Connect to the remote client. Performs key exchange automatically. */
+ session->op =
+ silc_client_connect_to_client(client, &session->params,
+ session->public_key, session->private_key,
+ session->hostname, session->port,
+ silc_client_ftp_connect_completion,
+ session);
+ if (!session->op) {
+ /* Call monitor callback */
+ if (session->monitor)
+ (*session->monitor)(session->client, session->conn,
+ SILC_CLIENT_FILE_MONITOR_ERROR,
+ SILC_CLIENT_FILE_ERROR, 0, 0,
+ session->client_entry, session->session_id,
+ session->filepath, session->monitor_context);
+ }
out:
- silc_packet_context_free(packet);
-}
-
-/* Called when file transfer packet is received. This will parse the
- packet and give it to the file transfer protocol. */
-
-void silc_client_ftp(SilcClient client,
- SilcSocketConnection sock,
- SilcPacketContext *packet)
-{
- SilcClientConnection conn = (SilcClientConnection)sock->user_data;
- uint8 type;
- int ret;
-
- SILC_LOG_DEBUG(("Start"));
-
- /* Parse the payload */
- ret = silc_buffer_unformat(packet->buffer,
- SILC_STR_UI_CHAR(&type),
- SILC_STR_END);
- if (ret == -1)
- return;
-
- /* We support only type number 1 (== SFTP) */
- if (type != 1)
- return;
-
- silc_buffer_pull(packet->buffer, 1);
-
- /* If we have active FTP session then give the packet to the
- protocol processor. */
- if (conn->active_session) {
- /* Give it to the SFTP */
- if (conn->active_session->server)
- silc_sftp_server_receive_process(conn->active_session->sftp, sock,
- packet);
- else
- silc_sftp_client_receive_process(conn->active_session->sftp, sock,
- packet);
- } else {
- /* We don't have active session, resolve the remote client information
- and then try to find the correct session. */
- SilcClientID *remote_id;
-
- if (packet->src_id_type != SILC_ID_CLIENT)
- return;
-
- remote_id = silc_id_str2id(packet->src_id, packet->src_id_len,
- SILC_ID_CLIENT);
- if (!remote_id)
- return;
-
- /* Resolve the client */
- silc_client_get_client_by_id_resolve(client, sock->user_data, remote_id,
- silc_client_ftp_resolve_cb,
- silc_packet_context_dup(packet));
- silc_free(remote_id);
- }
+ if (payload)
+ silc_key_agreement_payload_free(payload);
+ silc_packet_free(packet);
+ return SILC_FSM_FINISH;
}