Fixed FTP session closing in SIGNOFF.
[silc.git] / lib / silcclient / client_ftp.c
index 73681d15d4165b8bfd2881276d9a0faf25df830e..ef727a8584d128e6a2946914bf864647deaea487 100644 (file)
@@ -49,10 +49,12 @@ struct SilcClientFtpSessionStruct {
   SilcClientFileMonitor monitor;
   void *monitor_context;
   char *filepath;
+  char *path;
 
   SilcSFTP sftp;
   SilcSFTPFilesystem fs;
-  bool server;
+  unsigned int server : 1;
+  unsigned int bound  : 1;
 
   SilcSFTPHandle dir_handle;
   SilcSFTPHandle read_handle;
@@ -130,7 +132,8 @@ silc_client_connect_to_client_internal(SilcClientInternalConnectContext *ctx)
                                     (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;
 }
@@ -159,8 +162,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,8 +185,9 @@ 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;
@@ -265,7 +268,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 +294,7 @@ static void silc_client_ftp_open_handle(SilcSFTP sftp,
                                        void *context)
 {
   SilcClientFtpSession session = (SilcClientFtpSession)context;
+  char path[512];
 
   SILC_LOG_DEBUG(("Start"));
 
@@ -309,8 +314,10 @@ 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));
+  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, 
@@ -331,7 +338,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 */
@@ -377,7 +385,7 @@ static void silc_client_ftp_readdir_name(SilcSFTP sftp,
   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;
 
@@ -490,14 +498,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 +512,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);
@@ -542,7 +548,7 @@ static void silc_client_ftp_start_key_agreement(SilcClientFtpSession session,
                        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 */
@@ -640,7 +646,7 @@ SILC_TASK_CALLBACK(silc_client_ftp_process_key_agreement)
                        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;
@@ -680,16 +686,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 +706,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 +726,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)
@@ -751,10 +759,10 @@ void silc_client_ftp_session_free(SilcClientFtpSession session)
     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,6 +773,8 @@ 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);
 }
 
@@ -781,6 +791,7 @@ silc_client_file_send(SilcClient client,
                      void *monitor_context,
                      const char *local_ip,
                      SilcUInt32 local_port,
+                     bool do_not_bind,
                      SilcClientEntry client_entry,
                      const char *filepath,
                      SilcUInt32 *session_id)
@@ -790,11 +801,14 @@ silc_client_file_send(SilcClient client,
   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) {
+  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 SILC_CLIENT_FILE_ALREADY_STARTED;
@@ -808,19 +822,19 @@ silc_client_file_send(SilcClient client,
 
   /* 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, '/'))
@@ -835,27 +849,33 @@ 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 (!do_not_bind) {
+    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 */
+      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(session->hostname, session->port);
 
@@ -891,16 +911,20 @@ silc_client_file_receive(SilcClient client,
                         SilcClientConnection conn,
                         SilcClientFileMonitor monitor,
                         void *monitor_context,
+                        const char *path,
                         SilcUInt32 session_id)
 {
   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;
     }
@@ -920,16 +944,19 @@ silc_client_file_receive(SilcClient client,
   session->monitor = monitor;
   session->monitor_context = monitor_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) {
+    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->hostname = silc_net_localip();
     session->listener = silc_net_create_server(0, session->hostname);
     if (session->listener < 0) {
@@ -945,6 +972,7 @@ silc_client_file_receive(SilcClient client,
                           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);
@@ -977,11 +1005,14 @@ 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) {
+  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;
     }
@@ -1022,9 +1053,11 @@ static void 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;
   }
 
@@ -1044,14 +1077,16 @@ static void silc_client_ftp_resolve_cb(SilcClient client,
   if (session == SILC_LIST_END || (!hostname && !port)) {
     /* 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);
+    silc_dlist_add(conn->internal->ftp_sessions, session);
 
     /* Let the application know */
     client->internal->ops->ftp(client, conn, client_entry,
@@ -1069,6 +1104,8 @@ static void silc_client_ftp_resolve_cb(SilcClient client,
   session->port = port;
 
   /* Session exists, continue with key agreement protocol. */
+  SILC_LOG_DEBUG(("Session ID %d exists, connecting to remote client",
+                 session->session_id));
   if (silc_client_connect_to_client(client, conn, port, 
                                    hostname, session) < 0) {
     /* Call monitor callback */
@@ -1114,14 +1151,14 @@ void silc_client_ftp(SilcClient client,
 
   /* 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. */
@@ -1137,7 +1174,7 @@ void silc_client_ftp(SilcClient client,
 
     /* 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);
   }