Integer type name change.
[silc.git] / lib / silcclient / client_ftp.c
index 0e83768bac35eddfca296a3066bb1b7e03b369e6..73681d15d4165b8bfd2881276d9a0faf25df830e 100644 (file)
@@ -18,7 +18,8 @@
 */
 /* $Id$ */
 
-#include "clientlibincludes.h"
+#include "silcincludes.h"
+#include "silcclient.h"
 #include "client_internal.h"
 
 static int
@@ -30,17 +31,19 @@ silc_client_connect_to_client_internal(SilcClientInternalConnectContext *ctx);
 SILC_TASK_CALLBACK(silc_client_ftp_connected);
 static void silc_client_ftp_start_key_agreement(SilcClientFtpSession session,
                                                int sock);
-static void silc_client_ftp_session_free(SilcClientFtpSession session);
 
 /* 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;
@@ -53,12 +56,108 @@ struct SilcClientFtpSessionStruct {
 
   SilcSFTPHandle dir_handle;
   SilcSFTPHandle read_handle;
-  uint64 filesize;
-  uint64 read_offset;
+  SilcUInt64 filesize;
+  SilcUInt64 read_offset;
   int fd;
 };
 
-/* SFTP packet send callback */
+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->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);
+    }
+    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);
+}
+
+/* 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)
@@ -68,17 +167,62 @@ static void silc_client_ftp_send_packet(SilcSocketConnection sock,
 
   SILC_LOG_DEBUG(("Start"));
 
+  /* 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,
-                         packet->data, packet->len, TRUE);
+                         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 
+   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,
+                                   const SilcSFTPMonitorData data,
+                                   void *context)
+{
+  SilcClientFtpSession session = (SilcClientFtpSession)context;
+
+  if (type == SILC_SFTP_MONITOR_READ) {
+    /* Call the monitor for application */
+    if (session->monitor)
+      (*session->monitor)(session->client, session->conn,
+                         SILC_CLIENT_FILE_MONITOR_SEND,
+                         SILC_CLIENT_FILE_OK,
+                         data->offset, session->filesize,
+                         session->client_entry, session->session_id,
+                         session->filepath, session->monitor_context);
+  }
 }
 
-/* Returns the read data */
+/* Returns the read data. This is the downloader's function (client side)
+   to receive the read data and read more until EOF is received from
+   the other side. This will also monitor the transmission and notify
+   the application. */
 
 static void silc_client_ftp_data(SilcSFTP sftp,
                                 SilcSFTPStatus status,
                                 const unsigned char *data,
-                                uint32 data_len,
+                                SilcUInt32 data_len,
                                 void *context)
 {
   SilcClientFtpSession session = (SilcClientFtpSession)context;
@@ -86,6 +230,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);
@@ -97,7 +242,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);
@@ -110,21 +265,25 @@ 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, 64512,
                 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,
@@ -135,29 +294,58 @@ static void silc_client_ftp_open_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;
   }
 
   /* Open the actual local file */
-  session->fd = silc_file_open(session->filepath, O_RDWR | O_CREAT);
+  session->fd = silc_file_open(session->filepath, 
+                              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_ERROR, 0, 0,
+                         session->client_entry, session->session_id,
+                         session->filepath, session->monitor_context);
+    return;
   }
 
+  session->read_handle = handle;
+
   /* Now, start reading the file */
-  silc_sftp_read(sftp, handle, session->read_offset, 16384,
+  silc_sftp_read(sftp, session->read_handle, session->read_offset, 64512,
                 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. */
+/* Returns the file name available for download. This is the downloader's
+   function. */
 
 static void silc_client_ftp_readdir_name(SilcSFTP sftp,
                                         SilcSFTPStatus status,
@@ -170,7 +358,18 @@ static void silc_client_ftp_readdir_name(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 open the file */
@@ -187,7 +386,8 @@ static void silc_client_ftp_readdir_name(SilcSFTP sftp,
   session->dir_handle = NULL;
 }
 
-/* Returns the file handle after giving opendir command. */
+/* Returns the file handle after giving opendir command. This is the
+   downloader's function. */
 
 static void silc_client_ftp_opendir_handle(SilcSFTP sftp,
                                           SilcSFTPStatus status,
@@ -199,7 +399,18 @@ static void silc_client_ftp_opendir_handle(SilcSFTP sftp,
   SILC_LOG_DEBUG(("Start"));
 
   if (status != SILC_SFTP_STATUS_OK) {
-    /* XXX errror */
+    /* Call monitor callback */
+    if (session->monitor)
+      (*session->monitor)(session->client, session->conn,
+                         SILC_CLIENT_FILE_MONITOR_ERROR, 
+                         (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
+                          SILC_CLIENT_FILE_NO_SUCH_FILE :
+                          status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
+                          SILC_CLIENT_FILE_PERMISSION_DENIED :
+                          SILC_CLIENT_FILE_ERROR), 0, 0,
+                         session->client_entry, session->session_id,
+                         session->filepath, session->monitor_context);
+    return;
   }
 
   /* Now, read the directory */
@@ -207,7 +418,9 @@ static void silc_client_ftp_opendir_handle(SilcSFTP sftp,
   session->dir_handle = handle;
 }
 
-/* SFTP version callback for SFTP client */
+/* SFTP version callback for SFTP client. This is the downloader's function
+   after initializing the SFTP connection to the remote client. This will
+   find out the filename available for download. */
 
 static void silc_client_ftp_version(SilcSFTP sftp,
                                    SilcSFTPStatus status,
@@ -219,7 +432,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. */
@@ -234,7 +458,6 @@ SILC_TASK_CALLBACK(silc_client_ftp_key_agreement_final)
   SilcProtocol protocol = (SilcProtocol)context;
   SilcClientKEInternalContext *ctx = 
     (SilcClientKEInternalContext *)protocol->context;
-  SilcClient client = (SilcClient)ctx->client;
   SilcClientFtpSession session = (SilcClientFtpSession)ctx->context;
   SilcClientConnection conn = (SilcClientConnection)ctx->sock->user_data;
 
@@ -242,6 +465,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;
@@ -253,28 +484,44 @@ SILC_TASK_CALLBACK(silc_client_ftp_key_agreement_final)
                                   ctx->ske->prop->pkcs,
                                   ctx->ske->prop->hash,
                                   ctx->ske->prop->hmac,
-                                  ctx->ske->prop->group);
+                                  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) {
+    /* 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);
+  } else {
+    /* Start SFTP 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);
   }
 
+  /* Set this as active session */
+  conn->active_session = session;
+
  out:
   silc_ske_free_key_material(ctx->keymat);
   if (ctx->ske)
     silc_ske_free(ctx->ske);
   silc_free(ctx->dest_id);
+  ctx->sock->protocol = NULL;
   silc_socket_free(ctx->sock);
   silc_free(ctx);
-  ctx->sock->protocol = NULL;
   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)
 {
@@ -286,20 +533,23 @@ 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,
                                    session->port, session);
 
   /* Allocate new socket connection object */
-  silc_socket_alloc(sock, SILC_SOCKET_TYPE_UNKNOWN, (void *)conn, &conn->sock);
+  silc_socket_alloc(sock, SILC_SOCKET_TYPE_CLIENT, (void *)conn, &conn->sock);
   conn->sock->hostname = strdup(session->hostname);
   conn->sock->port = silc_net_get_remote_port(sock);
-
-  /* Allocate the SFTP */
-  if (session->server)
-    session->sftp = silc_sftp_server_start(conn->sock,
-                                          silc_client_ftp_send_packet,
-                                          session, session->fs);
+  session->sock = silc_socket_dup(conn->sock);
 
   /* Allocate internal context for key exchange protocol. This is
      sent as context for the protocol. */
@@ -316,6 +566,7 @@ static void silc_client_ftp_start_key_agreement(SilcClientFtpSession session,
   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 
@@ -330,123 +581,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 */
-
-static void silc_client_ftp_session_free(SilcClientFtpSession session)
-{
-  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);
-
-  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)
 {
@@ -461,7 +598,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;
   }
 
@@ -470,23 +613,38 @@ SILC_TASK_CALLBACK(silc_client_ftp_process_key_agreement)
   silc_net_set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
 
   /* Allocate new socket connection object */
-  silc_socket_alloc(sock, SILC_SOCKET_TYPE_UNKNOWN, NULL, &newsocket);
+  silc_socket_alloc(sock, SILC_SOCKET_TYPE_CLIENT, NULL, &newsocket);
 
   /* 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,
                                    newsocket->port, session);
   conn->sock = newsocket;
   conn->sock->user_data = conn;
+  session->sock = silc_socket_dup(conn->sock);
 
   /* Allocate internal context for key exchange protocol. This is
      sent as context for the protocol. */
@@ -517,30 +675,140 @@ 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->ftp_sessions) {
+    SilcClientFtpSession session;
+    silc_dlist_start(conn->ftp_sessions);
+    while ((session = silc_dlist_get(conn->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);
+  }
+}
+
+/* Free file transfer session by client entry. */
+
+void silc_client_ftp_session_free_client(SilcClientConnection conn,
+                                        SilcClientEntry client_entry)
+{
+  SilcClientFtpSession session;
+
+  if (!conn->ftp_sessions)
+    return;
+
+  /* Get the session */
+  silc_dlist_start(conn->ftp_sessions);
+  while ((session = silc_dlist_get(conn->ftp_sessions)) != SILC_LIST_END) {
+    if (session->client_entry == client_entry) {
+      if (session->sock)
+       session->sock->user_data = NULL;
+      silc_client_ftp_session_free(session);
+    }
+  }
+}
+
+/* Free session resources. */
+
+void silc_client_ftp_session_free(SilcClientFtpSession session)
+{
+  SilcClientConnection conn;
+
+  SILC_LOG_DEBUG(("Free session"));
+
+  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);
+
+  /* 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->active_session == session)
+       conn->active_session = NULL;
+
+      silc_client_close_connection(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);
+}
+
+/* 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,
+                     SilcClientEntry client_entry,
+                     const char *filepath,
+                     SilcUInt32 *session_id)
 {
   SilcClientFtpSession session;
   SilcBuffer keyagr, ftp;
-  char *filename;
+  char *filename, *path;
+  int fd;
 
   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) && 
+    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->next_session_id;
   session->client = client;
   session->conn = conn;
   session->client_entry = client_entry;
@@ -550,18 +818,46 @@ uint32 silc_client_file_send(SilcClient client,
   session->server = TRUE;
   silc_dlist_add(conn->ftp_sessions, session);
 
+  path = silc_calloc(strlen(filepath) + 8, sizeof(*path));
+  strcat(path, "file://");
+  strncat(path, filepath, strlen(filepath));
+
   /* Allocate memory filesystem and put the file to it */
-  if (strrchr(filepath, '/'))
-    filename = strrchr(filepath, '/') + 1;
+  if (strrchr(path, '/'))
+    filename = strrchr(path, '/') + 1;
   else
-    filename = (char *)filepath;
+    filename = (char *)path;
   session->fs = silc_sftp_fs_memory_alloc(SILC_SFTP_FS_PERM_READ |
                                          SILC_SFTP_FS_PERM_EXEC);
   silc_sftp_fs_memory_add_file(session->fs, NULL, SILC_SFTP_FS_PERM_READ,
-                              filename, filepath);
+                              filename, path);
+
+  session->filesize = silc_file_size(filepath);
+
+  /* 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);
+  }
 
   /* 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));
@@ -575,21 +871,32 @@ uint32 silc_client_file_send(SilcClient client,
 
   silc_buffer_free(keyagr);
   silc_buffer_free(ftp);
+  silc_free(path);
+
+  if (session_id)
+    *session_id = session->session_id;
 
-  return 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,
+                        SilcUInt32 session_id)
 {
   SilcClientFtpSession session;
   SilcBuffer keyagr, ftp;
 
-  SILC_LOG_DEBUG(("Start"));
+  SILC_LOG_DEBUG(("Start, Session ID: %d", session_id));
 
   /* Get the session */
   silc_dlist_start(conn->ftp_sessions);
@@ -601,60 +908,93 @@ bool silc_client_file_receive(SilcClient client,
 
   if (session == SILC_LIST_END) {
     SILC_LOG_DEBUG(("Unknown session ID: %d\n", session_id));
-    return FALSE;
+    return SILC_CLIENT_FILE_UNKNOWN_SESSION;
   }
 
   /* See if we have this session running already */
   if (session->sftp || session->listener) {
     SILC_LOG_DEBUG(("Session already started"));
-    return FALSE;
+    return SILC_CLIENT_FILE_ALREADY_STARTED;
   }
 
   session->monitor = monitor;
   session->monitor_context = monitor_context;
-  session->client_entry = client_entry;
   session->conn = conn;
 
-  /* Add the listener for the key agreement */
-  session->hostname = silc_net_localhost();
-  session->listener = silc_net_create_server(0, session->hostname);
-  if (session->listener < 0) {
-    /* XXX Error */
-    SILC_LOG_DEBUG(("Could not create listener"));
-    return FALSE;
+  /* 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, 
+                                     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);
+    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->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 */
+    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(NULL, 0);
+  return SILC_CLIENT_FILE_OK;
+}
 
-  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);
+/* 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. */
 
-  silc_buffer_free(keyagr);
-  silc_buffer_free(ftp);
+SilcClientFileError silc_client_file_close(SilcClient client,
+                                          SilcClientConnection conn,
+                                          SilcUInt32 session_id)
+{
+  SilcClientFtpSession session;
 
-  return TRUE;
-}
+  SILC_LOG_DEBUG(("Start, Session ID: %d", session_id));
 
-bool silc_client_file_close(SilcClient client,
-                           SilcClientConnection conn,
-                           uint32 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) {
+      break;
+    }
+  }
 
-  SILC_LOG_DEBUG(("Start"));
+  if (session == SILC_LIST_END) {
+    SILC_LOG_DEBUG(("Unknown session ID: %d\n", session_id));
+    return SILC_CLIENT_FILE_UNKNOWN_SESSION;
+  }
+
+  silc_client_ftp_session_free(session);
 
-  return TRUE;
+  return SILC_CLIENT_FILE_OK;
 }
 
 /* Callback called after remote client information has been resolved.
@@ -662,20 +1002,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"));
 
@@ -691,50 +1029,60 @@ silc_client_ftp_resolve_cb(SilcClient client,
   }
 
   /* 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 (!hostname)
+    port = 0;
+  if (!port)
+    hostname = NULL;
 
-  if (session == SILC_LIST_END) {
+  if (session == SILC_LIST_END || (!hostname && !port)) {
     /* No session found, create one and let the application know about
-       incomoing file transfer request. */
+       incoming file transfer request. */
     
     /* Add new session */
     session = silc_calloc(1, sizeof(*session));
-    session->session_id = conn->next_session_id++;
+    session->session_id = ++conn->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->ops->ftp(client, conn, client_entry,
-                    session->session_id, hostname, port);
+    client->internal->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);
+    
     goto out;
   }
 
-  if (!hostname)
-    goto out;
-
   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);
 }
 
@@ -746,7 +1094,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"));
@@ -764,7 +1112,7 @@ 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) {
     /* Give it to the SFTP */