X-Git-Url: http://git.silcnet.org/gitweb/?a=blobdiff_plain;f=lib%2Fsilcclient%2Fclient_ftp.c;h=f86ccbd5faaae973ec4d68a2eb7aad022524c994;hb=6394d86063413bc1c473723f3ef971840195bcd3;hp=5d554d3764160ffac59901486a77ff34933b6462;hpb=83c73dffa89141bc59e62436abb63b3d3efca6bb;p=silc.git diff --git a/lib/silcclient/client_ftp.c b/lib/silcclient/client_ftp.c index 5d554d37..f86ccbd5 100644 --- a/lib/silcclient/client_ftp.c +++ b/lib/silcclient/client_ftp.c @@ -39,6 +39,7 @@ struct SilcClientFtpSessionStruct { SilcClientEntry client_entry; SilcSocketConnection sock; + SilcBuffer packet; char *hostname; uint16 port; @@ -59,66 +60,139 @@ struct SilcClientFtpSessionStruct { int fd; }; -void silc_client_ftp_free_sessions(SilcClient client, - SilcClientConnection conn) +SILC_TASK_CALLBACK(silc_client_ftp_connected) { - if (conn->ftp_sessions) { - SilcClientFtpSession session; - silc_dlist_start(conn->ftp_sessions); - while ((session = silc_dlist_get(conn->ftp_sessions)) != SILC_LIST_END) { - session->sock->user_data = NULL; + SilcClientInternalConnectContext *ctx = + (SilcClientInternalConnectContext *)context; + SilcClient client = ctx->client; + SilcClientConnection conn = ctx->conn; + SilcClientFtpSession session = (SilcClientFtpSession)ctx->context; + int opt, opt_len = sizeof(opt); + + SILC_LOG_DEBUG(("Start")); + + /* Check the socket status as it might be in error */ + silc_net_get_socket_opt(fd, SOL_SOCKET, SO_ERROR, &opt, &opt_len); + if (opt != 0) { + if (ctx->tries < 2) { + /* Connection failed but lets try again */ + client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR, + "Could not connect to client %s: %s", + ctx->host, strerror(opt)); + client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT, + "Connecting to port %d of client %s resumed", + ctx->port, ctx->host); + + /* Unregister old connection try */ + silc_schedule_unset_listen_fd(client->schedule, fd); + silc_net_close_connection(fd); + silc_schedule_task_del(client->schedule, ctx->task); + + /* Try again */ + silc_client_connect_to_client_internal(ctx); + ctx->tries++; + } else { + /* Connection failed and we won't try anymore */ + client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR, + "Could not connect to client %s: %s", + ctx->host, strerror(opt)); + silc_schedule_unset_listen_fd(client->schedule, fd); + silc_net_close_connection(fd); + silc_schedule_task_del(client->schedule, ctx->task); + silc_free(ctx); silc_client_ftp_session_free(session); } - silc_dlist_del(conn->ftp_sessions, session); - silc_dlist_uninit(conn->ftp_sessions); + return; } + + silc_schedule_unset_listen_fd(client->schedule, fd); + silc_schedule_task_del(client->schedule, ctx->task); + + /* Start the key agreement */ + silc_client_ftp_start_key_agreement(session, fd); } -void silc_client_ftp_session_free_client(SilcClientConnection conn, - SilcClientEntry client_entry) +static int +silc_client_connect_to_client_internal(SilcClientInternalConnectContext *ctx) { - SilcClientFtpSession session; + int sock; - if (!conn->ftp_sessions) - return; + /* Create connection to server asynchronously */ + sock = silc_net_create_connection_async(NULL, ctx->port, ctx->host); + if (sock < 0) + return -1; - /* Get the session */ - silc_dlist_start(conn->ftp_sessions); - while ((session = silc_dlist_get(conn->ftp_sessions)) != SILC_LIST_END) { - if (session->client_entry == client_entry) { - session->sock->user_data = NULL; - silc_client_ftp_session_free(session); - break; - } - } + /* Register task that will receive the async connect and will + read the result. */ + ctx->task = silc_schedule_task_add(ctx->client->schedule, sock, + silc_client_ftp_connected, + (void *)ctx, 0, 0, + SILC_TASK_FD, + SILC_TASK_PRI_NORMAL); + silc_schedule_set_listen_fd(ctx->client->schedule, sock, SILC_TASK_WRITE); + ctx->sock = sock; + return sock; } -/* SFTP packet send callback */ +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) { SilcClientFtpSession session = (SilcClientFtpSession)context; SilcClient client = session->client; - SilcBuffer buffer; SILC_LOG_DEBUG(("Start")); - buffer = silc_buffer_alloc(1 + packet->len); - silc_buffer_pull_tail(buffer, SILC_BUFFER_END(buffer)); - silc_buffer_format(buffer, + /* Allocate outgoing packet */ + if (!session->packet) + session->packet = silc_buffer_alloc(1 + packet->len); + + /* Enlarge outgoing packet if needed */ + if (session->packet->truelen < 1 + packet->len) + session->packet = silc_buffer_realloc(session->packet, 1 + packet->len); + + /* Encode packet */ + silc_buffer_pull_tail(session->packet, 1 + packet->len); + silc_buffer_format(session->packet, SILC_STR_UI_CHAR(1), SILC_STR_UI_XNSTRING(packet->data, packet->len), SILC_STR_END); /* Send the packet immediately */ silc_client_packet_send(client, sock, SILC_PACKET_FTP, NULL, 0, NULL, NULL, - buffer->data, buffer->len, TRUE); + session->packet->data, session->packet->len, TRUE); - silc_buffer_free(buffer); + /* Clear buffer */ + session->packet->data = session->packet->tail = session->packet->head; + session->packet->len = 0; } -/* SFTP monitor callback for SFTP server */ +/* SFTP monitor callback for SFTP server. This reports the application + how the transmission is going along. This function is for the client + who made the file available for download. */ static void silc_client_ftp_monitor(SilcSFTP sftp, SilcSFTPMonitors type, @@ -132,13 +206,17 @@ static void silc_client_ftp_monitor(SilcSFTP sftp, if (session->monitor) (*session->monitor)(session->client, session->conn, SILC_CLIENT_FILE_MONITOR_SEND, + SILC_CLIENT_FILE_OK, data->offset, session->filesize, session->client_entry, session->session_id, session->filepath, session->monitor_context); } } -/* Returns the read data */ +/* Returns the read data. This is the downloader's function (client side) + to receive the read data and read more until EOF is received from + the other side. This will also monitor the transmission and notify + the application. */ static void silc_client_ftp_data(SilcSFTP sftp, SilcSFTPStatus status, @@ -151,6 +229,7 @@ static void silc_client_ftp_data(SilcSFTP sftp, SILC_LOG_DEBUG(("Start")); if (status == SILC_SFTP_STATUS_EOF) { + /* EOF received */ /* Close the handle */ silc_sftp_close(sftp, session->read_handle, NULL, NULL); @@ -162,7 +241,17 @@ static void silc_client_ftp_data(SilcSFTP sftp, } if (status != SILC_SFTP_STATUS_OK) { - /* XXX errror */ + /* Call monitor callback */ + if (session->monitor) + (*session->monitor)(session->client, session->conn, + SILC_CLIENT_FILE_MONITOR_ERROR, + (status == SILC_SFTP_STATUS_NO_SUCH_FILE ? + SILC_CLIENT_FILE_NO_SUCH_FILE : + status == SILC_SFTP_STATUS_PERMISSION_DENIED ? + SILC_CLIENT_FILE_PERMISSION_DENIED : + SILC_CLIENT_FILE_ERROR), 0, 0, + session->client_entry, session->session_id, + session->filepath, session->monitor_context); /* Close the handle */ silc_sftp_close(sftp, session->read_handle, NULL, NULL); @@ -175,21 +264,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, @@ -200,33 +293,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; + session->read_handle = handle; /* Now, start reading the file */ - silc_sftp_read(sftp, session->read_handle, session->read_offset, 16384, + silc_sftp_read(sftp, session->read_handle, session->read_offset, 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, @@ -239,7 +357,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 */ @@ -256,7 +385,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, @@ -268,7 +398,18 @@ static void silc_client_ftp_opendir_handle(SilcSFTP sftp, SILC_LOG_DEBUG(("Start")); if (status != SILC_SFTP_STATUS_OK) { - /* XXX errror */ + /* Call monitor callback */ + if (session->monitor) + (*session->monitor)(session->client, session->conn, + SILC_CLIENT_FILE_MONITOR_ERROR, + (status == SILC_SFTP_STATUS_NO_SUCH_FILE ? + SILC_CLIENT_FILE_NO_SUCH_FILE : + status == SILC_SFTP_STATUS_PERMISSION_DENIED ? + SILC_CLIENT_FILE_PERMISSION_DENIED : + SILC_CLIENT_FILE_ERROR), 0, 0, + session->client_entry, session->session_id, + session->filepath, session->monitor_context); + return; } /* Now, read the directory */ @@ -276,7 +417,9 @@ static void silc_client_ftp_opendir_handle(SilcSFTP sftp, session->dir_handle = handle; } -/* SFTP version callback for SFTP client */ +/* SFTP version callback for SFTP client. This is the downloader's function + after initializing the SFTP connection to the remote client. This will + find out the filename available for download. */ static void silc_client_ftp_version(SilcSFTP sftp, SilcSFTPStatus status, @@ -288,7 +431,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. */ @@ -324,13 +478,22 @@ SILC_TASK_CALLBACK(silc_client_ftp_key_agreement_final) ctx->ske->prop->group, ctx->responder); - /* If we are the SFTP client then start the SFTP session and retrieve - the info about the file available for download. */ if (!session->server) { + /* 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 */ @@ -347,6 +510,9 @@ SILC_TASK_CALLBACK(silc_client_ftp_key_agreement_final) silc_protocol_free(protocol); } +/* The downloader's function to start the key agreement protocol with the + remote client after we have connected to it. */ + static void silc_client_ftp_start_key_agreement(SilcClientFtpSession session, int sock) { @@ -358,6 +524,14 @@ 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); @@ -368,17 +542,6 @@ static void silc_client_ftp_start_key_agreement(SilcClientFtpSession session, conn->sock->port = silc_net_get_remote_port(sock); session->sock = silc_socket_dup(conn->sock); - /* Allocate the SFTP */ - if (session->server) { - session->sftp = silc_sftp_server_start(conn->sock, - silc_client_ftp_send_packet, - session, session->fs); - - /* Monitor transmission */ - silc_sftp_server_set_monitor(session->sftp, SILC_SFTP_MONITOR_READ, - silc_client_ftp_monitor, session); - } - /* Allocate internal context for key exchange protocol. This is sent as context for the protocol. */ proto_ctx = silc_calloc(1, sizeof(*proto_ctx)); @@ -409,148 +572,9 @@ static void silc_client_ftp_start_key_agreement(SilcClientFtpSession session, silc_protocol_execute(protocol, client->schedule, 0, 0); } -SILC_TASK_CALLBACK(silc_client_ftp_connected) -{ - SilcClientInternalConnectContext *ctx = - (SilcClientInternalConnectContext *)context; - SilcClient client = ctx->client; - SilcClientConnection conn = ctx->conn; - SilcClientFtpSession session = (SilcClientFtpSession)ctx->context; - int opt, opt_len = sizeof(opt); - - SILC_LOG_DEBUG(("Start")); - - /* Check the socket status as it might be in error */ - silc_net_get_socket_opt(fd, SOL_SOCKET, SO_ERROR, &opt, &opt_len); - if (opt != 0) { - if (ctx->tries < 2) { - /* Connection failed but lets try again */ - client->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR, - "Could not connect to client %s: %s", - ctx->host, strerror(opt)); - client->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT, - "Connecting to port %d of client %s resumed", - ctx->port, ctx->host); - - /* Unregister old connection try */ - silc_schedule_unset_listen_fd(client->schedule, fd); - silc_net_close_connection(fd); - silc_schedule_task_del(client->schedule, ctx->task); - - /* Try again */ - silc_client_connect_to_client_internal(ctx); - ctx->tries++; - } else { - /* Connection failed and we won't try anymore */ - client->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR, - "Could not connect to client %s: %s", - ctx->host, strerror(opt)); - silc_schedule_unset_listen_fd(client->schedule, fd); - silc_net_close_connection(fd); - silc_schedule_task_del(client->schedule, ctx->task); - silc_free(ctx); - silc_client_ftp_session_free(session); - } - return; - } - - silc_schedule_unset_listen_fd(client->schedule, fd); - silc_schedule_task_del(client->schedule, ctx->task); - - /* Start the key agreement */ - silc_client_ftp_start_key_agreement(session, fd); -} - -static int -silc_client_connect_to_client_internal(SilcClientInternalConnectContext *ctx) -{ - int sock; - - /* Create connection to server asynchronously */ - sock = silc_net_create_connection_async(NULL, ctx->port, ctx->host); - if (sock < 0) - return -1; - - /* Register task that will receive the async connect and will - read the result. */ - ctx->task = silc_schedule_task_add(ctx->client->schedule, sock, - silc_client_ftp_connected, - (void *)ctx, 0, 0, - SILC_TASK_FD, - SILC_TASK_PRI_NORMAL); - silc_schedule_set_listen_fd(ctx->client->schedule, sock, SILC_TASK_WRITE); - - ctx->sock = sock; - - return sock; -} - -static int -silc_client_connect_to_client(SilcClient client, - SilcClientConnection conn, int port, - char *host, void *context) -{ - SilcClientInternalConnectContext *ctx; - - /* Allocate internal context for connection process. This is - needed as we are doing async connecting. */ - ctx = silc_calloc(1, sizeof(*ctx)); - ctx->client = client; - ctx->conn = conn; - ctx->host = strdup(host); - ctx->port = port; - ctx->tries = 0; - ctx->context = context; - - /* Do the actual connecting process */ - return silc_client_connect_to_client_internal(ctx); -} - -/* Free session */ - -void silc_client_ftp_session_free(SilcClientFtpSession session) -{ - SilcClientConnection conn; - - silc_dlist_del(session->conn->ftp_sessions, session); - - if (session->sftp) { - if (session->server) - silc_sftp_server_shutdown(session->sftp); - else - silc_sftp_client_shutdown(session->sftp); - } - - if (session->fs) - silc_sftp_fs_memory_free(session->fs); - - if (session->listener) { - silc_schedule_unset_listen_fd(session->client->schedule, - session->listener); - silc_net_close_connection(session->listener); - } - - if (session->sock) { - silc_schedule_unset_listen_fd(session->client->schedule, - session->sock->sock); - silc_net_close_connection(session->sock->sock); - - if (session->sock->user_data) { - conn = (SilcClientConnection)session->sock->user_data; - - if (conn->active_session == session) - conn->active_session = NULL; - - silc_client_close_connection(session->client, session->sock, conn); - } else { - silc_socket_free(session->sock); - } - } - - silc_free(session->hostname); - silc_free(session->filepath); - silc_free(session); -} +/* The remote client's (the client who made the file available for download) + function for accepting incoming connection. This will also start the + key agreement protocol with the other client. */ SILC_TASK_CALLBACK(silc_client_ftp_process_key_agreement) { @@ -565,7 +589,13 @@ SILC_TASK_CALLBACK(silc_client_ftp_process_key_agreement) sock = silc_net_accept_connection(session->listener); if (sock < 0) { - /* XXX error */ + /* Call monitor callback */ + if (session->monitor) + (*session->monitor)(session->client, session->conn, + SILC_CLIENT_FILE_MONITOR_ERROR, + SILC_CLIENT_FILE_ERROR, 0, 0, + session->client_entry, session->session_id, + session->filepath, session->monitor_context); return; } @@ -579,13 +609,27 @@ SILC_TASK_CALLBACK(silc_client_ftp_process_key_agreement) /* Perform name and address lookups for the remote host. */ silc_net_check_host_by_sock(sock, &newsocket->hostname, &newsocket->ip); if (!newsocket->hostname && !newsocket->ip) { - /* XXX error */ + /* Call monitor callback */ + if (session->monitor) + (*session->monitor)(session->client, session->conn, + SILC_CLIENT_FILE_MONITOR_ERROR, + SILC_CLIENT_FILE_ERROR, 0, 0, + session->client_entry, session->session_id, + session->filepath, session->monitor_context); return; } if (!newsocket->hostname) newsocket->hostname = strdup(newsocket->ip); newsocket->port = silc_net_get_remote_port(sock); + /* Call monitor callback */ + if (session->monitor) + (*session->monitor)(session->client, session->conn, + SILC_CLIENT_FILE_MONITOR_KEY_AGREEMENT, + SILC_CLIENT_FILE_OK, 0, 0, + session->client_entry, session->session_id, + NULL, session->monitor_context); + /* Add new connection for this session */ conn = silc_client_add_connection(client, newsocket->hostname, newsocket->port, session); @@ -622,10 +666,118 @@ SILC_TASK_CALLBACK(silc_client_ftp_process_key_agreement) SILC_CLIENT_REGISTER_CONNECTION_FOR_IO(sock); } +/* 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. + + This returns a file session ID for the file transmission. It can + be used to close the session (and abort the file transmission) by + calling the silc_client_file_close function. The session ID is + also returned in the `monitor' callback. This returns 0 if the + file indicated by the `filepath' is being transmitted to the remote + client indicated by the `client_entry', already. */ + uint32 silc_client_file_send(SilcClient client, SilcClientConnection conn, SilcClientFileMonitor monitor, void *monitor_context, + const char *local_ip, + uint32 local_port, SilcClientEntry client_entry, const char *filepath) { @@ -645,7 +797,7 @@ uint32 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->next_session_id; session->client = client; session->conn = conn; session->client_entry = client_entry; @@ -671,8 +823,29 @@ uint32 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; + } 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)); @@ -691,12 +864,19 @@ uint32 silc_client_file_send(SilcClient client, return session->session_id; } -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, + uint32 session_id) { SilcClientFtpSession session; SilcBuffer keyagr, ftp; @@ -713,57 +893,72 @@ 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_localip(); - session->listener = silc_net_create_server(0, session->hostname); - if (session->listener < 0) { - /* XXX Error */ - SILC_LOG_DEBUG(("Could not create listener")); - return FALSE; + /* 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(session->hostname, session->port); - - ftp = silc_buffer_alloc(1 + keyagr->len); - silc_buffer_pull_tail(ftp, SILC_BUFFER_END(ftp)); - silc_buffer_format(ftp, - SILC_STR_UI_CHAR(1), - SILC_STR_UI_XNSTRING(keyagr->data, keyagr->len), - SILC_STR_END); - silc_client_packet_send(client, conn->sock, SILC_PACKET_FTP, - client_entry->id, SILC_ID_CLIENT, NULL, NULL, - ftp->data, ftp->len, FALSE); - - silc_buffer_free(keyagr); - silc_buffer_free(ftp); - return TRUE; + return SILC_CLIENT_FILE_OK; } -/* Closes FTP session */ +/* Closes file transmission session indicated by the `session_id'. + If file transmission is being conducted it will be aborted + automatically. This function is also used to close the session + after successful file transmission. This function can be used + also to reject incoming file transmission request. */ -bool silc_client_file_close(SilcClient client, - SilcClientConnection conn, - uint32 session_id) +SilcClientFileError silc_client_file_close(SilcClient client, + SilcClientConnection conn, + uint32 session_id) { SilcClientFtpSession session; @@ -779,12 +974,12 @@ bool silc_client_file_close(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; } silc_client_ftp_session_free(session); - return TRUE; + return SILC_CLIENT_FILE_OK; } /* Callback called after remote client information has been resolved. @@ -792,20 +987,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, + uint32 clients_count, + void *context) { SilcPacketContext *packet = (SilcPacketContext *)context; SilcClientFtpSession session; - SilcKeyAgreementPayload payload; + SilcKeyAgreementPayload payload = NULL; SilcClientEntry client_entry; char *hostname; uint16 port; - int sock; SILC_LOG_DEBUG(("Start")); @@ -821,50 +1014,56 @@ 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 (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); } @@ -894,7 +1093,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 */