X-Git-Url: http://git.silcnet.org/gitweb/?a=blobdiff_plain;f=lib%2Fsilcclient%2Fclient_ftp.c;h=b4e8d46ec2724c77da6af638777fb44f5820fc55;hb=1ea936cbf1bb3b19bd55839b904ef59ada84b8b5;hp=5d554d3764160ffac59901486a77ff34933b6462;hpb=83c73dffa89141bc59e62436abb63b3d3efca6bb;p=silc.git diff --git a/lib/silcclient/client_ftp.c b/lib/silcclient/client_ftp.c index 5d554d37..b4e8d46e 100644 --- a/lib/silcclient/client_ftp.c +++ b/lib/silcclient/client_ftp.c @@ -1,10 +1,10 @@ /* - client_ftp.c + client_ftp.c Author: Pekka Riikonen - Copyright (C) 2001 Pekka Riikonen + Copyright (C) 2001 - 2008 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 @@ -16,109 +16,53 @@ GNU General Public License for more details. */ -/* $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); +/************************** Types and definitions ***************************/ /* File transmission session */ struct SilcClientFtpSessionStruct { - uint32 session_id; - SilcClient client; - SilcClientConnection conn; - SilcClientEntry client_entry; - - SilcSocketConnection sock; - - 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 */ }; -void silc_client_ftp_free_sessions(SilcClient client, - SilcClientConnection conn) -{ - if (conn->ftp_sessions) { - SilcClientFtpSession session; - silc_dlist_start(conn->ftp_sessions); - while ((session = silc_dlist_get(conn->ftp_sessions)) != SILC_LIST_END) { - session->sock->user_data = NULL; - silc_client_ftp_session_free(session); - } - silc_dlist_del(conn->ftp_sessions, session); - silc_dlist_uninit(conn->ftp_sessions); - } -} - -void silc_client_ftp_session_free_client(SilcClientConnection conn, - SilcClientEntry client_entry) -{ - SilcClientFtpSession session; - - if (!conn->ftp_sessions) - return; - - /* Get the session */ - silc_dlist_start(conn->ftp_sessions); - while ((session = silc_dlist_get(conn->ftp_sessions)) != SILC_LIST_END) { - if (session->client_entry == client_entry) { - session->sock->user_data = NULL; - silc_client_ftp_session_free(session); - break; - } - } -} - -/* SFTP packet send callback */ - -static void silc_client_ftp_send_packet(SilcSocketConnection sock, - SilcBuffer packet, void *context) -{ - SilcClientFtpSession session = (SilcClientFtpSession)context; - SilcClient client = session->client; - SilcBuffer buffer; - - SILC_LOG_DEBUG(("Start")); - - buffer = silc_buffer_alloc(1 + packet->len); - silc_buffer_pull_tail(buffer, SILC_BUFFER_END(buffer)); - silc_buffer_format(buffer, - SILC_STR_UI_CHAR(1), - SILC_STR_UI_XNSTRING(packet->data, packet->len), - SILC_STR_END); +/************************* SFTP Server Callbacks ****************************/ - /* Send the packet immediately */ - silc_client_packet_send(client, sock, SILC_PACKET_FTP, NULL, 0, NULL, NULL, - buffer->data, buffer->len, TRUE); - - silc_buffer_free(buffer); -} - -/* SFTP monitor callback for SFTP server */ +/* 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_monitor(SilcSFTP sftp, SilcSFTPMonitors type, @@ -132,18 +76,24 @@ static void silc_client_ftp_monitor(SilcSFTP sftp, 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; @@ -151,112 +101,205 @@ static void silc_client_ftp_data(SilcSFTP sftp, 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, + silc_errno_string(silc_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; + session->read_handle = handle; /* Now, start reading the file */ - 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); /* 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, @@ -268,7 +311,18 @@ static void silc_client_ftp_opendir_handle(SilcSFTP sftp, 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 */ @@ -276,7 +330,9 @@ static void silc_client_ftp_opendir_handle(SilcSFTP sftp, 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, @@ -288,376 +344,375 @@ static void silc_client_ftp_version(SilcSFTP sftp, 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; - 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, - ctx->responder); +/* Free session resources. Connection must be closed before getting + here. */ + +static void silc_client_ftp_session_free(SilcClientFtpSession session) +{ + SILC_LOG_DEBUG(("Free session %d", session->session_id)); - /* 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); + 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); - /* Set this as active session */ - conn->active_session = session; + /* Destroy listener */ + if (session->listener) + silc_client_listener_free(session->listener); - out: - silc_ske_free_key_material(ctx->keymat); - if (ctx->ske) - silc_ske_free(ctx->ske); - silc_free(ctx->dest_id); - ctx->sock->protocol = NULL; - silc_socket_free(ctx->sock); - silc_free(ctx); - silc_protocol_free(protocol); + /* 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")); + + /* 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); - /* 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_CLIENT, (void *)conn, &conn->sock); - conn->sock->hostname = strdup(session->hostname); - conn->sock->port = silc_net_get_remote_port(sock); - session->sock = silc_socket_dup(conn->sock); - - /* Allocate the SFTP */ - if (session->server) { - session->sftp = silc_sftp_server_start(conn->sock, - silc_client_ftp_send_packet, - session, session->fs); - - /* Monitor transmission */ - silc_sftp_server_set_monitor(session->sftp, SILC_SFTP_MONITOR_READ, - silc_client_ftp_monitor, session); - } - - /* 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); - conn->sock->protocol = protocol; - - /* 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); + 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; + } -void silc_client_ftp_session_free(SilcClientFtpSession session) -{ - SilcClientConnection conn; + 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); + } - silc_dlist_del(session->conn->ftp_sessions, session); + break; - if (session->sftp) { - if (session->server) - silc_sftp_server_shutdown(session->sftp); - else - silc_sftp_client_shutdown(session->sftp); - } + case SILC_CLIENT_CONN_DISCONNECTED: + SILC_LOG_DEBUG(("Disconnected %p", conn)); - if (session->fs) - silc_sftp_fs_memory_free(session->fs); + /* 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); - if (session->listener) { - silc_schedule_unset_listen_fd(session->client->schedule, - session->listener); - silc_net_close_connection(session->listener); - } + /* Connection already closed */ + session->conn = NULL; - if (session->sock) { - silc_schedule_unset_listen_fd(session->client->schedule, - session->sock->sock); - silc_net_close_connection(session->sock->sock); + /* If closed by user, destroy the session now */ + if (session->closed) + silc_client_ftp_session_free(session); + break; - if (session->sock->user_data) { - conn = (SilcClientConnection)session->sock->user_data; + case SILC_CLIENT_CONN_ERROR_TIMEOUT: + SILC_LOG_DEBUG(("Connecting timeout")); - if (conn->active_session == session) - conn->active_session = NULL; + /* 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_close_connection(session->client, session->sock, conn); - } else { - silc_socket_free(session->sock); - } - } + /* Connection already closed */ + session->conn = NULL; + break; - silc_free(session->hostname); - silc_free(session->filepath); - silc_free(session); + 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. */ + +void silc_client_ftp_free_sessions(SilcClient client) +{ + SilcClientFtpSession session; - sock = silc_net_accept_connection(session->listener); - if (sock < 0) { - /* XXX error */ + 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); +} - /* Allocate new socket connection object */ - silc_socket_alloc(sock, SILC_SOCKET_TYPE_CLIENT, NULL, &newsocket); +/* Free file transfer session by client entry. */ - /* 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 */ +void silc_client_ftp_session_free_client(SilcClient client, + SilcClientEntry client_entry) +{ + SilcClientFtpSession session; + + 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; - session->sock = silc_socket_dup(conn->sock); - - /* 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; + 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; - path = silc_calloc(strlen(filepath) + 8, sizeof(*path)); - strcat(path, "file://"); - strncat(path, filepath, strlen(filepath)); + 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(path, '/')) @@ -671,41 +726,94 @@ uint32 silc_client_file_send(SilcClient client, 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", silc_errno_string(silc_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); - return session->session_id; + silc_dlist_add(client->internal->ftp_sessions, session); + if (session_id) + *session_id = 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; + + 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; } @@ -713,214 +821,274 @@ bool silc_client_file_receive(SilcClient client, 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; - - /* Add the listener for the key agreement */ - session->hostname = silc_net_localip(); - session->listener = silc_net_create_server(0, session->hostname); - if (session->listener < 0) { - /* XXX Error */ - SILC_LOG_DEBUG(("Could not create listener")); - return FALSE; + 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", silc_errno_string(silc_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); } - 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(session->hostname, session->port); - - 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); - silc_buffer_free(keyagr); - silc_buffer_free(ftp); - - return TRUE; + return SILC_CLIENT_FILE_OK; } -/* Closes FTP session */ +/* 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. */ -bool silc_client_file_close(SilcClient client, - SilcClientConnection conn, - uint32 session_id) +SilcClientFileError silc_client_file_close(SilcClient client, + SilcClientConnection conn, + SilcUInt32 session_id) { SilcClientFtpSession session; - SILC_LOG_DEBUG(("Start, Session ID: %d", session_id)); + if (!client || !conn) + return SILC_CLIENT_FILE_ERROR; + + SILC_LOG_DEBUG(("Closing file transer session %d", session_id)); /* Get the session */ - silc_dlist_start(conn->ftp_sessions); - while ((session = silc_dlist_get(conn->ftp_sessions)) != SILC_LIST_END) { - if (session->session_id == session_id) { + 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; + } + + 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); + + /* No more callbacks to application */ + session->monitor = NULL; } - silc_client_ftp_session_free(session); + silc_schedule_task_del_by_context(client->schedule, session); + + session->closed = TRUE; + + /* Destroy via timeout */ + silc_schedule_task_add_timeout(conn->internal->schedule, + silc_client_file_close_final, session, + 0, 1); - return TRUE; + 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 (silc_buffer_len(&packet->buffer) < 1) + goto out; - if (!clients) + /* 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; + } - client_entry = clients[0]; + 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; + } - silc_dlist_start(conn->ftp_sessions); - while ((session = silc_dlist_get(conn->ftp_sessions)) != SILC_LIST_END) { - if (session->client_entry == client_entry) + /* 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; }