Changed SILC code to use new SRT and SCT APIs.
[silc.git] / lib / silcclient / client_ftp.c
index 0e83768bac35eddfca296a3066bb1b7e03b369e6..b4e8d46ec2724c77da6af638777fb44f5820fc55 100644 (file)
@@ -1,10 +1,10 @@
 /*
 
-  client_ftp.c 
+  client_ftp.c
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  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
   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);
-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;
@@ -86,108 +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;
+
   /* 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,
@@ -199,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 */
@@ -207,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,
@@ -219,381 +344,476 @@ 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;
-  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", 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);
+
+  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;
     }
@@ -601,196 +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;
+  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);
+  }
+
+  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;
 }