SILC_FSM_* macro API changes.
[silc.git] / lib / silcclient / client_ftp.c
index 73681d15d4165b8bfd2881276d9a0faf25df830e..c3bbd177cb3adc87a0c61b312e51095630e4bdbf 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 - 2007 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
 */
 /* $Id$ */
 
-#include "silcincludes.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,
@@ -48,11 +48,15 @@ struct SilcClientFtpSessionStruct {
 
   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;
@@ -80,8 +84,8 @@ SILC_TASK_CALLBACK(silc_client_ftp_connected)
       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", 
+      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 */
@@ -113,7 +117,7 @@ SILC_TASK_CALLBACK(silc_client_ftp_connected)
   silc_client_ftp_start_key_agreement(session, fd);
 }
 
-static int 
+static int
 silc_client_connect_to_client_internal(SilcClientInternalConnectContext *ctx)
 {
   int sock;
@@ -125,18 +129,19 @@ silc_client_connect_to_client_internal(SilcClientInternalConnectContext *ctx)
 
   /* Register task that will receive the async connect and will
      read the result. */
-  ctx->task = silc_schedule_task_add(ctx->client->schedule, sock, 
+  ctx->task = silc_schedule_task_add(ctx->client->schedule, sock,
                                     silc_client_ftp_connected,
-                                    (void *)ctx, 0, 0, 
+                                    (void *)ctx, 0, 0,
                                     SILC_TASK_FD,
                                     SILC_TASK_PRI_NORMAL);
-  silc_schedule_set_listen_fd(ctx->client->schedule, sock, SILC_TASK_WRITE);
+  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, 
+silc_client_connect_to_client(SilcClient client,
                              SilcClientConnection conn, int port,
                              char *host, void *context)
 {
@@ -159,8 +164,7 @@ silc_client_connect_to_client(SilcClient client,
 /* 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;
@@ -183,15 +187,16 @@ static void silc_client_ftp_send_packet(SilcSocketConnection sock,
                     SILC_STR_END);
 
   /* Send the packet immediately */
-  silc_client_packet_send(client, sock, SILC_PACKET_FTP, NULL, 0, NULL, NULL,
-                         session->packet->data, session->packet->len, TRUE);
+  silc_client_packet_send(client, session->sock, SILC_PACKET_FTP, NULL,
+                         0, NULL, NULL, session->packet->data,
+                         session->packet->len, TRUE);
 
   /* Clear buffer */
   session->packet->data = session->packet->tail = session->packet->head;
   session->packet->len = 0;
 }
 
-/* SFTP monitor callback for SFTP server. This reports the application 
+/* 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. */
 
@@ -245,7 +250,7 @@ static void silc_client_ftp_data(SilcSFTP sftp,
     /* Call monitor callback */
     if (session->monitor)
       (*session->monitor)(session->client, session->conn,
-                         SILC_CLIENT_FILE_MONITOR_ERROR, 
+                         SILC_CLIENT_FILE_MONITOR_ERROR,
                          (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
                           SILC_CLIENT_FILE_NO_SUCH_FILE :
                           status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
@@ -265,7 +270,8 @@ 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, 64512,
+  silc_sftp_read(sftp, session->read_handle, session->read_offset,
+                 SILC_PACKET_MAX_LEN - 1024,
                 silc_client_ftp_data, session);
 
   /* Call monitor callback */
@@ -290,6 +296,7 @@ static void silc_client_ftp_open_handle(SilcSFTP sftp,
                                        void *context)
 {
   SilcClientFtpSession session = (SilcClientFtpSession)context;
+  char path[512];
 
   SILC_LOG_DEBUG(("Start"));
 
@@ -297,7 +304,7 @@ static void silc_client_ftp_open_handle(SilcSFTP sftp,
     /* Call monitor callback */
     if (session->monitor)
       (*session->monitor)(session->client, session->conn,
-                         SILC_CLIENT_FILE_MONITOR_ERROR, 
+                         SILC_CLIENT_FILE_MONITOR_ERROR,
                          (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
                           SILC_CLIENT_FILE_NO_SUCH_FILE :
                           status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
@@ -309,20 +316,22 @@ static void silc_client_ftp_open_handle(SilcSFTP sftp,
   }
 
   /* Open the actual local file */
-  session->fd = silc_file_open(session->filepath, 
-                              O_RDWR | O_CREAT | O_EXCL);
+  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) {
     /* Call monitor callback */
-    session->client->internal->ops->say(session->client, session->conn, 
-                                       SILC_CLIENT_MESSAGE_ERROR, 
-                                       "File `%s' open failed: %s", 
+    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_ERROR, 0, 0,
+                         SILC_CLIENT_FILE_MONITOR_ERROR,
+                         SILC_CLIENT_FILE_PERMISSION_DENIED, 0, 0,
                          session->client_entry, session->session_id,
                          session->filepath, session->monitor_context);
     return;
@@ -331,7 +340,8 @@ static void silc_client_ftp_open_handle(SilcSFTP sftp,
   session->read_handle = handle;
 
   /* Now, start reading the file */
-  silc_sftp_read(sftp, session->read_handle, session->read_offset, 64512,
+  silc_sftp_read(sftp, session->read_handle, session->read_offset,
+                 SILC_PACKET_MAX_LEN - 1024,
                 silc_client_ftp_data, session);
 
   /* Call monitor callback */
@@ -344,6 +354,37 @@ static void silc_client_ftp_open_handle(SilcSFTP sftp,
                        session->filepath, session->monitor_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 (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. */
 
@@ -353,7 +394,6 @@ static void silc_client_ftp_readdir_name(SilcSFTP sftp,
                                         void *context)
 {
   SilcClientFtpSession session = (SilcClientFtpSession)context;
-  SilcSFTPAttributesStruct attr;
 
   SILC_LOG_DEBUG(("Start"));
 
@@ -361,7 +401,7 @@ static void silc_client_ftp_readdir_name(SilcSFTP sftp,
     /* Call monitor callback */
     if (session->monitor)
       (*session->monitor)(session->client, session->conn,
-                         SILC_CLIENT_FILE_MONITOR_ERROR, 
+                         SILC_CLIENT_FILE_MONITOR_ERROR,
                          (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
                           SILC_CLIENT_FILE_NO_SUCH_FILE :
                           status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
@@ -372,18 +412,21 @@ static void silc_client_ftp_readdir_name(SilcSFTP sftp,
     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. This is the
@@ -402,7 +445,7 @@ static void silc_client_ftp_opendir_handle(SilcSFTP sftp,
     /* Call monitor callback */
     if (session->monitor)
       (*session->monitor)(session->client, session->conn,
-                         SILC_CLIENT_FILE_MONITOR_ERROR, 
+                         SILC_CLIENT_FILE_MONITOR_ERROR,
                          (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
                           SILC_CLIENT_FILE_NO_SUCH_FILE :
                           status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
@@ -435,7 +478,7 @@ static void silc_client_ftp_version(SilcSFTP sftp,
     /* Call monitor callback */
     if (session->monitor)
       (*session->monitor)(session->client, session->conn,
-                         SILC_CLIENT_FILE_MONITOR_ERROR, 
+                         SILC_CLIENT_FILE_MONITOR_ERROR,
                          (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
                           SILC_CLIENT_FILE_NO_SUCH_FILE :
                           status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
@@ -456,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;
@@ -468,7 +511,7 @@ SILC_TASK_CALLBACK(silc_client_ftp_key_agreement_final)
     /* Call monitor callback */
     if (session->monitor)
       (*session->monitor)(session->client, session->conn,
-                         SILC_CLIENT_FILE_MONITOR_ERROR, 
+                         SILC_CLIENT_FILE_MONITOR_ERROR,
                          SILC_CLIENT_FILE_KEY_AGREEMENT_FAILED, 0, 0,
                          session->client_entry, session->session_id,
                          session->filepath, session->monitor_context);
@@ -490,14 +533,12 @@ SILC_TASK_CALLBACK(silc_client_ftp_key_agreement_final)
   if (!session->server) {
     /* 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(conn->sock,
-                                          silc_client_ftp_send_packet,
-                                          session, 
-                                          silc_client_ftp_version, session);
+    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(conn->sock,
-                                          silc_client_ftp_send_packet,
+    session->sftp = silc_sftp_server_start(silc_client_ftp_send_packet,
                                           session, session->fs);
 
     /* Monitor transmission */
@@ -506,7 +547,7 @@ SILC_TASK_CALLBACK(silc_client_ftp_key_agreement_final)
   }
 
   /* Set this as active session */
-  conn->active_session = session;
+  conn->internal->active_session = session;
 
  out:
   silc_ske_free_key_material(ctx->keymat);
@@ -536,13 +577,13 @@ static void silc_client_ftp_start_key_agreement(SilcClientFtpSession session,
   /* Call monitor callback */
   if (session->monitor)
     (*session->monitor)(session->client, session->conn,
-                       SILC_CLIENT_FILE_MONITOR_KEY_AGREEMENT, 
+                       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 */
@@ -563,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;
@@ -601,7 +642,7 @@ SILC_TASK_CALLBACK(silc_client_ftp_process_key_agreement)
     /* Call monitor callback */
     if (session->monitor)
       (*session->monitor)(session->client, session->conn,
-                         SILC_CLIENT_FILE_MONITOR_ERROR, 
+                         SILC_CLIENT_FILE_MONITOR_ERROR,
                          SILC_CLIENT_FILE_ERROR, 0, 0,
                          session->client_entry, session->session_id,
                          session->filepath, session->monitor_context);
@@ -621,7 +662,7 @@ SILC_TASK_CALLBACK(silc_client_ftp_process_key_agreement)
     /* Call monitor callback */
     if (session->monitor)
       (*session->monitor)(session->client, session->conn,
-                         SILC_CLIENT_FILE_MONITOR_ERROR, 
+                         SILC_CLIENT_FILE_MONITOR_ERROR,
                          SILC_CLIENT_FILE_ERROR, 0, 0,
                          session->client_entry, session->session_id,
                          session->filepath, session->monitor_context);
@@ -634,13 +675,13 @@ SILC_TASK_CALLBACK(silc_client_ftp_process_key_agreement)
   /* Call monitor callback */
   if (session->monitor)
     (*session->monitor)(session->client, session->conn,
-                       SILC_CLIENT_FILE_MONITOR_KEY_AGREEMENT, 
+                       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;
@@ -659,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,
@@ -680,16 +721,16 @@ SILC_TASK_CALLBACK(silc_client_ftp_process_key_agreement)
 void silc_client_ftp_free_sessions(SilcClient client,
                                   SilcClientConnection conn)
 {
-  if (conn->ftp_sessions) {
+  if (conn->internal->ftp_sessions) {
     SilcClientFtpSession 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->sock)
        session->sock->user_data = NULL;
       silc_client_ftp_session_free(session);
     }
-    silc_dlist_del(conn->ftp_sessions, session);
-    silc_dlist_uninit(conn->ftp_sessions);
+    silc_dlist_del(conn->internal->ftp_sessions, session);
   }
 }
 
@@ -700,17 +741,15 @@ void silc_client_ftp_session_free_client(SilcClientConnection conn,
 {
   SilcClientFtpSession session;
 
-  if (!conn->ftp_sessions)
+  if (!conn->internal->ftp_sessions)
     return;
 
   /* Get the session */
-  silc_dlist_start(conn->ftp_sessions);
-  while ((session = silc_dlist_get(conn->ftp_sessions)) != SILC_LIST_END) {
-    if (session->client_entry == client_entry) {
-      if (session->sock)
-       session->sock->user_data = NULL;
+  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);
-    }
   }
 }
 
@@ -722,7 +761,11 @@ void silc_client_ftp_session_free(SilcClientFtpSession session)
 
   SILC_LOG_DEBUG(("Free session"));
 
-  silc_dlist_del(session->conn->ftp_sessions, 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)
@@ -736,7 +779,7 @@ void silc_client_ftp_session_free(SilcClientFtpSession session)
 
   /* Destroy listener */
   if (session->listener) {
-    silc_schedule_unset_listen_fd(session->client->schedule, 
+    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);
@@ -744,17 +787,17 @@ void silc_client_ftp_session_free(SilcClientFtpSession session)
 
   /* Destroy session connection */
   if (session->sock) {
-    silc_schedule_unset_listen_fd(session->client->schedule, 
+    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;
+      if (conn->internal->active_session == session)
+       conn->internal->active_session = NULL;
 
-      silc_client_close_connection(session->client, session->sock, conn);
+      silc_client_close_connection_real(session->client, session->sock, conn);
     } else {
       silc_socket_free(session->sock);
     }
@@ -765,23 +808,25 @@ void silc_client_ftp_session_free(SilcClientFtpSession session)
 
   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 
+/* 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 
+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 *local_ip,
-                     SilcUInt32 local_port,
-                     SilcClientEntry client_entry,
                      const char *filepath,
                      SilcUInt32 *session_id)
 {
@@ -790,17 +835,20 @@ silc_client_file_send(SilcClient client,
   char *filename, *path;
   int fd;
 
-  SILC_LOG_DEBUG(("Start"));
+  SILC_LOG_DEBUG(("File send request (file: %s), filepath"));
+
+  if (!client || !client_entry || !filepath)
+    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 (session->filepath && !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 SILC_CLIENT_FILE_ALREADY_STARTED;
   }
 
-  /* See whether the file exists, and can be opened in generally speaking */
+  /* 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;
@@ -808,19 +856,19 @@ silc_client_file_send(SilcClient client,
 
   /* 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->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));
+  silc_asprintf(&path, "file://%s", filepath);
 
   /* Allocate memory filesystem and put the file to it */
   if (strrchr(path, '/'))
@@ -834,28 +882,48 @@ silc_client_file_send(SilcClient client,
 
   session->filesize = silc_file_size(filepath);
 
-  /* Create the listener for incoming key exchange protocol. */
-  if (local_ip)
-    session->hostname = strdup(local_ip);
-  else
-    session->hostname = silc_net_localip();
-  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->hostname = NULL;
-    session->port = 0;
-  } else {
-    /* Listener ready */
-    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);
+  /* If local IP is provided, create listener for incoming key exchange */
+  if (params && (params->local_ip || params->bind_ip)) {
+    ke = silc_calloc(1, sizeof(*ke));
+    if (!ke) {
+      completion(client, conn, client_entry, SILC_KEY_AGREEMENT_NO_MEMORY,
+                NULL, context);
+      return;
+    }
+
+    /* TCP listener */
+    session->listener =
+      silc_net_tcp_create_listener(params->bind_ip ?
+                                  (const char **)&params->bind_ip :
+                                  (const char **)&params->local_ip,
+                                  1, params->local_port, FALSE, FALSE,
+                                  conn->internal->schedule,
+                                  silc_client_tcp_accept,
+                                  client_entry);
+    if (!session->listener) {
+      /* 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->hostname = NULL;
+      session->port = 0;
+    } else {
+      SILC_LOG_DEBUG(("Bound listener"));
+      session->bound = TRUE;
+      session->port = params->local_port;
+      if (!session->port) {
+       /* Get listener port */
+       SilcUInt16 *ports;
+       ports = silc_net_listener_get_port(ke->tcp_listener, NULL);
+       session->port = ports[0];
+       silc_free(ports);
+      }
+    }
   }
 
+  SILC_LOG_DEBUG(("Sending key agreement for file transfer"));
+
   /* Send the key agreement inside FTP packet */
   keyagr = silc_key_agreement_payload_encode(session->hostname, session->port);
 
@@ -886,21 +954,27 @@ silc_client_file_send(SilcClient client,
    actually starting the file transmission.  The `monitor' callback
    will be called to monitor the transmission. */
 
-SilcClientFileError 
+SilcClientFileError
 silc_client_file_receive(SilcClient client,
                         SilcClientConnection conn,
                         SilcClientFileMonitor monitor,
                         void *monitor_context,
-                        SilcUInt32 session_id)
+                        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;
     }
@@ -919,23 +993,31 @@ silc_client_file_receive(SilcClient client,
 
   session->monitor = monitor;
   session->monitor_context = monitor_context;
+  session->ask_name = ask_name;
+  session->ask_name_context = ask_name_context;
   session->conn = conn;
+  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) {
-    if (silc_client_connect_to_client(client, conn, 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 */
-    session->hostname = silc_net_localip();
-    session->listener = silc_net_create_server(0, session->hostname);
+    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"));
-      client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR, 
-                                "Cannot create listener on %s: %s", 
+      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;
     }
@@ -943,9 +1025,10 @@ silc_client_file_receive(SilcClient client,
     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, 
+    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));
@@ -954,10 +1037,10 @@ silc_client_file_receive(SilcClient client,
                       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, 
+                           session->client_entry->id,
                            SILC_ID_CLIENT, NULL, NULL,
                            ftp->data, ftp->len, FALSE);
-    
+
     silc_buffer_free(keyagr);
     silc_buffer_free(ftp);
   }
@@ -965,6 +1048,11 @@ silc_client_file_receive(SilcClient client,
   return SILC_CLIENT_FILE_OK;
 }
 
+SILC_TASK_CALLBACK(silc_client_file_close_final)
+{
+  silc_client_ftp_session_free(context);
+}
+
 /* 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
@@ -977,14 +1065,16 @@ SilcClientFileError silc_client_file_close(SilcClient client,
 {
   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) {
@@ -992,47 +1082,108 @@ SilcClientFileError silc_client_file_close(SilcClient client,
     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 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. */
+/************************** FTP Request Processing **************************/
+
+/* Client resolving callback.  Continues with the FTP processing */
 
-static void silc_client_ftp_resolve_cb(SilcClient client,
-                                      SilcClientConnection conn,
-                                      SilcClientEntry *clients,
-                                      SilcUInt32 clients_count,
-                                      void *context)
+static void silc_client_ftp_client_resolved(SilcClient client,
+                                           SilcClientConnection conn,
+                                           SilcStatus status,
+                                           SilcDList clients,
+                                           void *context)
 {
-  SilcPacketContext *packet = (SilcPacketContext *)context;
-  SilcClientFtpSession session;
-  SilcKeyAgreementPayload payload = NULL;
-  SilcClientEntry client_entry;
+  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;
+  }
+
+  /* Continue processing the packet */
+  SILC_FSM_CALL_CONTINUE(context);
+}
+
+/* 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)
+{
+  SilcClientConnection conn = fsm_context;
+  SilcClient client = conn->client;
+  SilcPacket packet = state_context;
+  SilcClientID remote_id;
+  SilcClientEntry remote_client;
+  SilcKeyAgreementPayload payload;
+  SilcUInt8 type;
   char *hostname;
   SilcUInt16 port;
+  int ret;
 
-  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->nickname[0]) {
+    /** 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 */
+  }
+
+
+  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->data,
-                                            packet->buffer->len);
-  if (!payload)
+  payload = silc_key_agreement_payload_parse(packet->buffer->data + 1,
+                                            packet->buffer->len - 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);
@@ -1041,104 +1192,59 @@ static void silc_client_ftp_resolve_cb(SilcClient client,
   if (!port)
     hostname = NULL;
 
-  if (session == SILC_LIST_END || (!hostname && !port)) {
+  /* 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
        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;
     session->client_entry = client_entry;
-    silc_dlist_add(conn->ftp_sessions, session);
-
-    /* Let the application know */
-    client->internal->ops->ftp(client, conn, client_entry,
-                              session->session_id, hostname, port);
+    silc_dlist_add(conn->internal->ftp_sessions, session);
 
     if (hostname && port) {
       session->hostname = strdup(hostname);
       session->port = port;
     }
-    
+
+    /* Let the application know */
+    client->internal->ops->ftp(client, conn, client_entry,
+                              session->session_id, hostname, port);
+
     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. */
-  if (silc_client_connect_to_client(client, conn, port, 
+  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_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);
-}
-
-/* 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;
-  SilcUInt8 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 directly 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);
-  }
+ out:
+  silc_packet_free(packet);
+  return SILC_FSM_FINISH;
 }