Added preliminary Symbian support.
[silc.git] / lib / silcclient / client_ftp.c
index 5d554d3764160ffac59901486a77ff34933b6462..09aac70782c21c3f1854aef78c0ac50b6bc8ab20 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 - 2004 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
 */
 /* $Id$ */
 
-#include "clientlibincludes.h"
+#include "silc.h"
+#include "silcclient.h"
 #include "client_internal.h"
 
 static int
-silc_client_connect_to_client(SilcClient client, 
+silc_client_connect_to_client(SilcClient client,
                              SilcClientConnection conn, int port,
                              char *host, void *context);
-static int 
+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,
@@ -33,92 +34,171 @@ static void silc_client_ftp_start_key_agreement(SilcClientFtpSession session,
 
 /* File transmission session */
 struct SilcClientFtpSessionStruct {
-  uint32 session_id;
+  SilcUInt32 session_id;
   SilcClient client;
   SilcClientConnection conn;
   SilcClientEntry client_entry;
 
   SilcSocketConnection sock;
+  SilcBuffer packet;
 
   char *hostname;
-  uint16 port;
+  SilcUInt16 port;
   int listener;
 
   SilcClientFileMonitor monitor;
   void *monitor_context;
+  SilcClientFileAskName ask_name;
+  void *ask_name_context;
   char *filepath;
+  char *path;
 
   SilcSFTP sftp;
   SilcSFTPFilesystem fs;
-  bool server;
+  unsigned int server : 1;     /* File sender sets this to TRUE */
+  unsigned int bound  : 1;
 
   SilcSFTPHandle dir_handle;
   SilcSFTPHandle read_handle;
-  uint64 filesize;
-  uint64 read_offset;
+  SilcUInt64 filesize;
+  SilcUInt64 read_offset;
   int fd;
 };
 
-void silc_client_ftp_free_sessions(SilcClient client,
-                                  SilcClientConnection conn)
+SILC_TASK_CALLBACK(silc_client_ftp_connected)
 {
-  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;
+  SilcClientInternalConnectContext *ctx =
+    (SilcClientInternalConnectContext *)context;
+  SilcClient client = ctx->client;
+  SilcClientConnection conn = ctx->conn;
+  SilcClientFtpSession session = (SilcClientFtpSession)ctx->context;
+  int opt, opt_len = sizeof(opt);
+
+  SILC_LOG_DEBUG(("Start"));
+
+  /* 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->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
+                                "Could not connect to client %s: %s",
+                                ctx->host, strerror(opt));
+      client->internal->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->internal->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);
     }
-    silc_dlist_del(conn->ftp_sessions, session);
-    silc_dlist_uninit(conn->ftp_sessions);
+    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);
 }
 
-void silc_client_ftp_session_free_client(SilcClientConnection conn,
-                                        SilcClientEntry client_entry)
+static int
+silc_client_connect_to_client_internal(SilcClientInternalConnectContext *ctx)
 {
-  SilcClientFtpSession session;
+  int sock;
 
-  if (!conn->ftp_sessions)
-    return;
+  /* Create connection to server asynchronously */
+  sock = silc_net_create_connection_async(NULL, ctx->port, ctx->host);
+  if (sock < 0)
+    return -1;
 
-  /* 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;
-    }
-  }
+  /* 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,
+                             FALSE);
+  ctx->sock = sock;
+  return sock;
+}
+
+static int
+silc_client_connect_to_client(SilcClient client,
+                             SilcClientConnection conn, int port,
+                             char *host, 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);
 }
 
-/* SFTP packet send callback */
+/* SFTP packet send callback. This will use preallocated buffer to avoid
+   reallocation of outgoing data buffer everytime. */
 
-static void silc_client_ftp_send_packet(SilcSocketConnection sock,
-                                       SilcBuffer packet, void *context)
+static void silc_client_ftp_send_packet(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, 
+  /* Allocate outgoing packet */
+  if (!session->packet)
+    session->packet = silc_buffer_alloc(1 + packet->len);
+
+  /* Enlarge outgoing packet if needed */
+  if (session->packet->truelen < 1 + packet->len)
+    session->packet = silc_buffer_realloc(session->packet, 1 + packet->len);
+
+  /* Encode packet */
+  silc_buffer_pull_tail(session->packet, 1 + packet->len);
+  silc_buffer_format(session->packet,
                     SILC_STR_UI_CHAR(1),
                     SILC_STR_UI_XNSTRING(packet->data, packet->len),
                     SILC_STR_END);
 
   /* Send the packet immediately */
-  silc_client_packet_send(client, sock, SILC_PACKET_FTP, NULL, 0, NULL, NULL,
-                         buffer->data, buffer->len, TRUE);
+  silc_client_packet_send(client, session->sock, SILC_PACKET_FTP, NULL,
+                         0, NULL, NULL, session->packet->data,
+                         session->packet->len, TRUE);
 
-  silc_buffer_free(buffer);
+  /* Clear buffer */
+  session->packet->data = session->packet->tail = session->packet->head;
+  session->packet->len = 0;
 }
 
-/* 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 +212,22 @@ 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 */
+/* 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,6 +235,7 @@ 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);
@@ -162,7 +247,17 @@ static void silc_client_ftp_data(SilcSFTP sftp,
   }
 
   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);
@@ -175,58 +270,123 @@ static void silc_client_ftp_data(SilcSFTP sftp,
 
   /* 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);
 
   /* 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 */
+  /* Write the read data to the real file */
   silc_file_write(session->fd, data, data_len);
 }
 
+/* Returns handle for the opened file. This is the downloader's function.
+   This will begin reading the data from the file. */
+
 static void silc_client_ftp_open_handle(SilcSFTP sftp,
                                        SilcSFTPStatus status,
                                        SilcSFTPHandle handle,
                                        void *context)
 {
   SilcClientFtpSession session = (SilcClientFtpSession)context;
+  char path[512];
 
   SILC_LOG_DEBUG(("Start"));
 
   if (status != SILC_SFTP_STATUS_OK) {
-    /* XXX errror */
+    /* Call monitor callback */
+    if (session->monitor)
+      (*session->monitor)(session->client, session->conn,
+                         SILC_CLIENT_FILE_MONITOR_ERROR,
+                         (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
+                          SILC_CLIENT_FILE_NO_SUCH_FILE :
+                          status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
+                          SILC_CLIENT_FILE_PERMISSION_DENIED :
+                          SILC_CLIENT_FILE_ERROR), 0, 0,
+                         session->client_entry, session->session_id,
+                         session->filepath, session->monitor_context);
     return;
   }
 
   /* Open the actual local file */
-  session->fd = silc_file_open(session->filepath, O_RDWR | O_CREAT);
+  memset(path, 0, sizeof(path));
+  silc_snprintf(path, sizeof(path) - 1, "%s%s", session->path ?
+          session->path : "", session->filepath);
+  session->fd = silc_file_open(path, O_RDWR | O_CREAT | O_EXCL);
   if (session->fd < 0) {
-    /* XXX errror */
+    /* Call monitor callback */
+    session->client->internal->ops->say(session->client, session->conn,
+                                       SILC_CLIENT_MESSAGE_ERROR,
+                                       "File `%s' open failed: %s",
+                                       session->filepath,
+                                       strerror(errno));
+
+    if (session->monitor)
+      (*session->monitor)(session->client, session->conn,
+                         SILC_CLIENT_FILE_MONITOR_ERROR,
+                         SILC_CLIENT_FILE_PERMISSION_DENIED, 0, 0,
+                         session->client_entry, session->session_id,
+                         session->filepath, session->monitor_context);
     return;
   }
 
-  session->read_handle =  handle;
+  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. */
+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 (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(session->sftp, remote_file, SILC_SFTP_FXF_READ, &attr,
+                silc_client_ftp_open_handle, session);
+
+  /* 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,
@@ -234,29 +394,43 @@ static void silc_client_ftp_readdir_name(SilcSFTP sftp,
                                         void *context)
 {
   SilcClientFtpSession session = (SilcClientFtpSession)context;
-  SilcSFTPAttributesStruct attr;
 
   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 open the file */
-  memset(&attr, 0, sizeof(attr));
-  silc_sftp_open(sftp, name->filename[0], SILC_SFTP_FXF_READ, &attr,
-                silc_client_ftp_open_handle, session);
-
-  /* Save the important attributes */
+  /* 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 +442,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 +461,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,7 +475,18 @@ 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. */
@@ -301,7 +499,7 @@ static void silc_client_ftp_version(SilcSFTP sftp,
 SILC_TASK_CALLBACK(silc_client_ftp_key_agreement_final)
 {
   SilcProtocol protocol = (SilcProtocol)context;
-  SilcClientKEInternalContext *ctx = 
+  SilcClientKEInternalContext *ctx =
     (SilcClientKEInternalContext *)protocol->context;
   SilcClientFtpSession session = (SilcClientFtpSession)ctx->context;
   SilcClientConnection conn = (SilcClientConnection)ctx->sock->user_data;
@@ -310,6 +508,14 @@ SILC_TASK_CALLBACK(silc_client_ftp_key_agreement_final)
 
   if (protocol->state == SILC_PROTOCOL_STATE_ERROR ||
       protocol->state == SILC_PROTOCOL_STATE_FAILURE) {
+    /* Call monitor callback */
+    if (session->monitor)
+      (*session->monitor)(session->client, session->conn,
+                         SILC_CLIENT_FILE_MONITOR_ERROR,
+                         SILC_CLIENT_FILE_KEY_AGREEMENT_FAILED, 0, 0,
+                         session->client_entry, session->session_id,
+                         session->filepath, session->monitor_context);
+
     /* Error occured during protocol */
     silc_ske_free_key_material(ctx->keymat);
     goto out;
@@ -324,17 +530,24 @@ SILC_TASK_CALLBACK(silc_client_ftp_key_agreement_final)
                                   ctx->ske->prop->group,
                                   ctx->responder);
 
-  /* 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);
+    /* 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(silc_client_ftp_send_packet,
+                                          session, silc_client_ftp_version,
+                                          session);
+  } else {
+    /* Start SFTP server */
+    session->sftp = silc_sftp_server_start(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);
   }
 
   /* Set this as active session */
-  conn->active_session = session;
+  conn->internal->active_session = session;
 
  out:
   silc_ske_free_key_material(ctx->keymat);
@@ -347,6 +560,9 @@ SILC_TASK_CALLBACK(silc_client_ftp_key_agreement_final)
   silc_protocol_free(protocol);
 }
 
+/* The downloader's function to start the key agreement protocol with the
+   remote client after we have connected to it. */
+
 static void silc_client_ftp_start_key_agreement(SilcClientFtpSession session,
                                                int sock)
 {
@@ -358,8 +574,16 @@ static void silc_client_ftp_start_key_agreement(SilcClientFtpSession session,
 
   SILC_LOG_DEBUG(("Start"));
 
+  /* Call monitor callback */
+  if (session->monitor)
+    (*session->monitor)(session->client, session->conn,
+                       SILC_CLIENT_FILE_MONITOR_KEY_AGREEMENT,
+                       SILC_CLIENT_FILE_OK, 0, 0,
+                       session->client_entry, session->session_id,
+                       NULL, session->monitor_context);
+
   /* Add new connection for this session */
-  conn = silc_client_add_connection(client, session->hostname,
+  conn = silc_client_add_connection(client, NULL, session->hostname,
                                    session->port, session);
 
   /* Allocate new socket connection object */
@@ -368,17 +592,6 @@ static void silc_client_ftp_start_key_agreement(SilcClientFtpSession session,
   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));
@@ -391,15 +604,15 @@ static void silc_client_ftp_start_key_agreement(SilcClientFtpSession session,
   proto_ctx->verify = silc_client_protocol_ke_verify_key;
 
   /* Perform key exchange protocol. */
-  silc_protocol_alloc(SILC_PROTOCOL_CLIENT_KEY_EXCHANGE, 
+  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 
+     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 
+     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;
@@ -409,148 +622,9 @@ static void silc_client_ftp_start_key_agreement(SilcClientFtpSession session,
   silc_protocol_execute(protocol, client->schedule, 0, 0);
 }
 
-SILC_TASK_CALLBACK(silc_client_ftp_connected)
-{
-  SilcClientInternalConnectContext *ctx =
-    (SilcClientInternalConnectContext *)context;
-  SilcClient client = ctx->client;
-  SilcClientConnection conn = ctx->conn;
-  SilcClientFtpSession session = (SilcClientFtpSession)ctx->context;
-  int opt, opt_len = sizeof(opt);
-
-  SILC_LOG_DEBUG(("Start"));
-
-  /* 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);
-    }
-    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);
-}
-
-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;
-
-  /* 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);
-
-  ctx->sock = sock;
-
-  return sock;
-}
-
-static int
-silc_client_connect_to_client(SilcClient client, 
-                             SilcClientConnection conn, int port,
-                             char *host, 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);
-}
-
-/* Free session */
-
-void silc_client_ftp_session_free(SilcClientFtpSession session)
-{
-  SilcClientConnection conn;
-
-  silc_dlist_del(session->conn->ftp_sessions, session);
-
-  if (session->sftp) {
-    if (session->server)
-      silc_sftp_server_shutdown(session->sftp);
-    else
-      silc_sftp_client_shutdown(session->sftp);
-  }
-
-  if (session->fs)
-    silc_sftp_fs_memory_free(session->fs);
-
-  if (session->listener) {
-    silc_schedule_unset_listen_fd(session->client->schedule, 
-                                 session->listener);
-    silc_net_close_connection(session->listener);
-  }
-
-  if (session->sock) {
-    silc_schedule_unset_listen_fd(session->client->schedule, 
-                                 session->sock->sock);
-    silc_net_close_connection(session->sock->sock);
-
-    if (session->sock->user_data) {
-      conn = (SilcClientConnection)session->sock->user_data;
-
-      if (conn->active_session == session)
-       conn->active_session = NULL;
-
-      silc_client_close_connection(session->client, session->sock, conn);
-    } else {
-      silc_socket_free(session->sock);
-    }
-  }
-
-  silc_free(session->hostname);
-  silc_free(session->filepath);
-  silc_free(session);
-}
+/* The remote client's (the client who made the file available for download)
+   function for accepting incoming connection. This will also start the
+   key agreement protocol with the other client. */
 
 SILC_TASK_CALLBACK(silc_client_ftp_process_key_agreement)
 {
@@ -565,7 +639,13 @@ SILC_TASK_CALLBACK(silc_client_ftp_process_key_agreement)
 
   sock = silc_net_accept_connection(session->listener);
   if (sock < 0) {
-    /* XXX error */
+    /* 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);
     return;
   }
 
@@ -579,15 +659,29 @@ SILC_TASK_CALLBACK(silc_client_ftp_process_key_agreement)
   /* 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 */
+    /* 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);
     return;
   }
   if (!newsocket->hostname)
     newsocket->hostname = strdup(newsocket->ip);
   newsocket->port = silc_net_get_remote_port(sock);
 
+  /* Call monitor callback */
+  if (session->monitor)
+    (*session->monitor)(session->client, session->conn,
+                       SILC_CLIENT_FILE_MONITOR_KEY_AGREEMENT,
+                       SILC_CLIENT_FILE_OK, 0, 0,
+                       session->client_entry, session->session_id,
+                       NULL, session->monitor_context);
+
   /* Add new connection for this session */
-  conn = silc_client_add_connection(client, newsocket->hostname,
+  conn = silc_client_add_connection(client, NULL, newsocket->hostname,
                                    newsocket->port, session);
   conn->sock = newsocket;
   conn->sock->user_data = conn;
@@ -606,14 +700,14 @@ SILC_TASK_CALLBACK(silc_client_ftp_process_key_agreement)
 
   /* 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 
+     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_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 
+     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,
@@ -622,42 +716,160 @@ SILC_TASK_CALLBACK(silc_client_ftp_process_key_agreement)
   SILC_CLIENT_REGISTER_CONNECTION_FOR_IO(sock);
 }
 
-uint32 silc_client_file_send(SilcClient client,
-                            SilcClientConnection conn,
-                            SilcClientFileMonitor monitor,
-                            void *monitor_context,
-                            SilcClientEntry client_entry,
-                            const char *filepath)
+/* Free all file transfer sessions. */
+
+void silc_client_ftp_free_sessions(SilcClient client,
+                                  SilcClientConnection conn)
+{
+  if (conn->internal->ftp_sessions) {
+    SilcClientFtpSession session;
+    silc_dlist_start(conn->internal->ftp_sessions);
+    while ((session = silc_dlist_get(conn->internal->ftp_sessions))
+          != SILC_LIST_END) {
+      if (session->sock)
+       session->sock->user_data = NULL;
+      silc_client_ftp_session_free(session);
+    }
+    silc_dlist_del(conn->internal->ftp_sessions, session);
+  }
+}
+
+/* Free file transfer session by client entry. */
+
+void silc_client_ftp_session_free_client(SilcClientConnection conn,
+                                        SilcClientEntry client_entry)
+{
+  SilcClientFtpSession session;
+
+  if (!conn->internal->ftp_sessions)
+    return;
+
+  /* Get the session */
+  silc_dlist_start(conn->internal->ftp_sessions);
+  while ((session = silc_dlist_get(conn->internal->ftp_sessions))
+        != SILC_LIST_END) {
+    if (session->client_entry == client_entry)
+      silc_client_ftp_session_free(session);
+  }
+}
+
+/* Free session resources. */
+
+void silc_client_ftp_session_free(SilcClientFtpSession session)
+{
+  SilcClientConnection conn;
+
+  SILC_LOG_DEBUG(("Free session"));
+
+  if (session->conn && session->conn->internal->ftp_sessions)
+    silc_dlist_del(session->conn->internal->ftp_sessions, session);
+
+  if (session->conn && session->conn->internal->active_session == session)
+    session->conn->internal->active_session = NULL;
+
+  if (session->sftp) {
+    if (session->server)
+      silc_sftp_server_shutdown(session->sftp);
+    else
+      silc_sftp_client_shutdown(session->sftp);
+  }
+
+  if (session->fs)
+    silc_sftp_fs_memory_free(session->fs);
+
+  /* Destroy listener */
+  if (session->listener) {
+    silc_schedule_unset_listen_fd(session->client->schedule,
+                                 session->listener);
+    silc_net_close_connection(session->listener);
+    silc_schedule_task_del_by_fd(session->client->schedule, session->listener);
+  }
+
+  /* Destroy session connection */
+  if (session->sock) {
+    silc_schedule_unset_listen_fd(session->client->schedule,
+                                 session->sock->sock);
+    silc_net_close_connection(session->sock->sock);
+
+    if (session->sock->user_data) {
+      conn = (SilcClientConnection)session->sock->user_data;
+
+      if (conn->internal->active_session == session)
+       conn->internal->active_session = NULL;
+
+      silc_client_close_connection_real(session->client, session->sock, conn);
+    } else {
+      silc_socket_free(session->sock);
+    }
+  }
+
+  if (session->packet)
+    silc_buffer_free(session->packet);
+
+  silc_free(session->hostname);
+  silc_free(session->filepath);
+  silc_free(session->path);
+  memset(session, 'F', sizeof(*session));
+  silc_free(session);
+}
+
+/* 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,
+                     SilcClientFileMonitor monitor,
+                     void *monitor_context,
+                     const char *local_ip,
+                     SilcUInt32 local_port,
+                     SilcBool do_not_bind,
+                     SilcClientEntry client_entry,
+                     const char *filepath,
+                     SilcUInt32 *session_id)
 {
   SilcClientFtpSession session;
   SilcBuffer keyagr, ftp;
   char *filename, *path;
+  int fd;
+
+  assert(client && conn && client_entry);
 
   SILC_LOG_DEBUG(("Start"));
 
   /* 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(conn->internal->ftp_sessions);
+  while ((session = silc_dlist_get(conn->internal->ftp_sessions))
+        != SILC_LIST_END) {
+    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 in generally speaking */
+  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++;
+  session->session_id = ++conn->internal->next_session_id;
   session->client = client;
   session->conn = conn;
+  session->server = TRUE;
   session->client_entry = client_entry;
   session->monitor = monitor;
   session->monitor_context = monitor_context;
   session->filepath = strdup(filepath);
-  session->server = TRUE;
-  silc_dlist_add(conn->ftp_sessions, session);
+  silc_dlist_add(conn->internal->ftp_sessions, session);
 
-  path = silc_calloc(strlen(filepath) + 8, sizeof(*path));
-  strcat(path, "file://");
-  strncat(path, filepath, strlen(filepath));
+  path = silc_calloc(strlen(filepath) + 9, sizeof(*path));
+  silc_strncat(path, strlen(filepath) + 9, "file://", 7);
+  silc_strncat(path, strlen(filepath) + 9, filepath, strlen(filepath));
 
   /* Allocate memory filesystem and put the file to it */
   if (strrchr(path, '/'))
@@ -671,8 +883,41 @@ uint32 silc_client_file_send(SilcClient client,
 
   session->filesize = silc_file_size(filepath);
 
+  /* Create the listener for incoming key exchange protocol. */
+  if (!do_not_bind) {
+    session->listener = -1;
+    if (local_ip)
+      session->hostname = strdup(local_ip);
+    else
+      silc_net_check_local_by_sock(conn->sock->sock, NULL,
+                                  &session->hostname);
+    if (session->hostname)
+      session->listener = silc_net_create_server(local_port,
+                                                session->hostname);
+    if (session->listener < 0) {
+      /* Could not create listener. Do the second best thing; send empty
+        key agreement packet and let the remote client provide the point
+        for the key exchange. */
+      SILC_LOG_DEBUG(("Could not create listener"));
+      silc_free(session->hostname);
+      session->listener = 0;
+      session->hostname = NULL;
+      session->port = 0;
+    } else {
+      /* Listener ready */
+      SILC_LOG_DEBUG(("Bound listener"));
+      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);
+      session->bound = TRUE;
+    }
+  }
+
+  SILC_LOG_DEBUG(("Sending key agreement for file transfer"));
+
   /* Send the key agreement inside FTP packet */
-  keyagr = silc_key_agreement_payload_encode(NULL, 0);
+  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));
@@ -688,24 +933,40 @@ uint32 silc_client_file_send(SilcClient client,
   silc_buffer_free(ftp);
   silc_free(path);
 
-  return session->session_id;
+  if (session_id)
+    *session_id = session->session_id;
+
+  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,
+                        SilcClientFileMonitor monitor,
+                        void *monitor_context,
+                        const char *path,
+                        SilcUInt32 session_id,
+                        SilcClientFileAskName ask_name,
+                        void *ask_name_context)
 {
   SilcClientFtpSession session;
   SilcBuffer keyagr, ftp;
 
+  assert(client && conn);
+
   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(conn->internal->ftp_sessions);
+  while ((session = silc_dlist_get(conn->internal->ftp_sessions))
+        != SILC_LIST_END) {
     if (session->session_id == session_id) {
       break;
     }
@@ -713,78 +974,119 @@ 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->ask_name = ask_name;
+  session->ask_name_context = ask_name_context;
   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->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"));
+    if (silc_client_connect_to_client(client, conn, session->port,
+                                     session->hostname, session) < 0)
+      return SILC_CLIENT_FILE_ERROR;
+  } else {
+    /* Add the listener for the key agreement */
+    SILC_LOG_DEBUG(("Creating listener for file transfer"));
+    session->listener = -1;
+    silc_net_check_local_by_sock(conn->sock->sock, NULL, &session->hostname);
+    if (session->hostname)
+      session->listener = silc_net_create_server(0, session->hostname);
+    if (session->listener < 0) {
+      SILC_LOG_DEBUG(("Could not create listener"));
+      session->listener = 0;
+      client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
+                                "Cannot create listener on %s: %s",
+                                session->hostname, strerror(errno));
+      return SILC_CLIENT_FILE_ERROR;
+    }
+    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 */
+    SILC_LOG_DEBUG(("Sending key agreement for file transfer"));
+    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,
+                           session->client_entry->id,
+                           SILC_ID_CLIENT, NULL, NULL,
+                           ftp->data, ftp->len, FALSE);
+
+    silc_buffer_free(keyagr);
+    silc_buffer_free(ftp);
   }
-  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 SILC_CLIENT_FILE_OK;
+}
 
-  return TRUE;
+SILC_TASK_CALLBACK(silc_client_file_close_final)
+{
+  silc_client_ftp_session_free(context);
 }
 
-/* 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;
 
+  assert(client && conn);
+
   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) {
-    if (session->session_id == session_id) {
+  silc_dlist_start(conn->internal->ftp_sessions);
+  while ((session = silc_dlist_get(conn->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;
   }
 
-  silc_client_ftp_session_free(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);
+
+  /* Destroy via timeout */
+  silc_schedule_task_add(session->client->schedule, 0,
+                        silc_client_file_close_final, session,
+                        0, 1, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
 
-  return TRUE;
+  return SILC_CLIENT_FILE_OK;
 }
 
 /* Callback called after remote client information has been resolved.
@@ -792,20 +1094,18 @@ bool silc_client_file_close(SilcClient client,
    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)
+static void silc_client_ftp_resolve_cb(SilcClient client,
+                                      SilcClientConnection conn,
+                                      SilcClientEntry *clients,
+                                      SilcUInt32 clients_count,
+                                      void *context)
 {
   SilcPacketContext *packet = (SilcPacketContext *)context;
   SilcClientFtpSession session;
-  SilcKeyAgreementPayload payload;
+  SilcKeyAgreementPayload payload = NULL;
   SilcClientEntry client_entry;
   char *hostname;
-  uint16 port;
-  int sock;
+  SilcUInt16 port;
 
   SILC_LOG_DEBUG(("Start"));
 
@@ -814,57 +1114,80 @@ silc_client_ftp_resolve_cb(SilcClient client,
 
   client_entry = clients[0];
 
-  silc_dlist_start(conn->ftp_sessions);
-  while ((session = silc_dlist_get(conn->ftp_sessions)) != SILC_LIST_END) {
-    if (session->client_entry == client_entry)
+  silc_dlist_start(conn->internal->ftp_sessions);
+  while ((session = silc_dlist_get(conn->internal->ftp_sessions))
+        != SILC_LIST_END) {
+    if (session->client_entry == client_entry &&
+       (!session->server || !session->bound))
       break;
   }
 
   /* Parse the key agreement payload */
-  payload = silc_key_agreement_payload_parse(packet->buffer);
+  payload = silc_key_agreement_payload_parse(packet->buffer->data,
+                                            packet->buffer->len);
   if (!payload)
     goto out;
 
   hostname = silc_key_agreement_get_hostname(payload);
   port = silc_key_agreement_get_port(payload);
-
-  if (session == SILC_LIST_END) {
+  if (!hostname)
+    port = 0;
+  if (!port)
+    hostname = NULL;
+
+  /* If session doesn't exist, we create one and let applicationi know about
+     incoming file transfer request.  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 == SILC_LIST_END || (!hostname && !port) ||
+      (session && session->server == FALSE)) {
     /* No session found, create one and let the application know about
-       incomoing file transfer request. */
-    
+       incoming file transfer request. */
+    SILC_LOG_DEBUG(("New file transfer session ID: %d",
+                   conn->internal->next_session_id + 1));
+
     /* Add new session */
     session = silc_calloc(1, sizeof(*session));
-    session->session_id = conn->next_session_id++;
+    session->session_id = ++conn->internal->next_session_id;
     session->client = client;
     session->conn = conn;
-    silc_dlist_add(conn->ftp_sessions, session);
+    session->client_entry = client_entry;
+    silc_dlist_add(conn->internal->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. */
     if (hostname && port) {
-      /* XXX */
+      session->hostname = strdup(hostname);
+      session->port = port;
     }
 
-    silc_key_agreement_payload_free(payload);
+    /* Let the application know */
+    client->internal->ops->ftp(client, conn, client_entry,
+                              session->session_id, hostname, port);
+
     goto out;
   }
 
-  if (!hostname)
-    goto out;
+  /* Session exists, continue with key agreement protocol. */
+  SILC_LOG_DEBUG(("Session ID %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;
+  if (silc_client_connect_to_client(client, conn, port,
+                                   hostname, session) < 0) {
+    /* 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:
+  if (payload)
+    silc_key_agreement_payload_free(payload);
   silc_packet_context_free(packet);
 }
 
@@ -876,7 +1199,7 @@ void silc_client_ftp(SilcClient client,
                     SilcPacketContext *packet)
 {
   SilcClientConnection conn = (SilcClientConnection)sock->user_data;
-  uint8 type;
+  SilcUInt8 type;
   int ret;
 
   SILC_LOG_DEBUG(("Start"));
@@ -894,16 +1217,16 @@ void silc_client_ftp(SilcClient client,
 
   silc_buffer_pull(packet->buffer, 1);
 
-  /* If we have active FTP session then give the packet to the
+  /* If we have active FTP session then give the packet directly to the
      protocol processor. */
-  if (conn->active_session) {
+  if (conn->internal->active_session) {
     /* Give it to the SFTP */
-    if (conn->active_session->server)
-      silc_sftp_server_receive_process(conn->active_session->sftp, sock, 
-                                      packet);
+    if (conn->internal->active_session->server)
+      silc_sftp_server_receive_process(conn->internal->active_session->sftp,
+                                      sock, packet);
     else
-      silc_sftp_client_receive_process(conn->active_session->sftp, sock, 
-                                      packet);
+      silc_sftp_client_receive_process(conn->internal->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. */
@@ -912,14 +1235,14 @@ void silc_client_ftp(SilcClient client,
     if (packet->src_id_type != SILC_ID_CLIENT)
       return;
 
-    remote_id = silc_id_str2id(packet->src_id, packet->src_id_len, 
+    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,
+                                        NULL, silc_client_ftp_resolve_cb,
                                         silc_packet_context_dup(packet));
     silc_free(remote_id);
   }