SilcClientFileError silc_client_file_close(SilcClient client, SilcClientConnection conn, SilcUInt32 session_id) { SilcClientFtpSession session; if (!client || !conn) return SILC_CLIENT_FILE_ERROR; SILC_LOG_DEBUG(("Closing file transer session %d", session_id)); /* Get the session */ silc_dlist_start(client->internal->ftp_sessions); while ((session = silc_dlist_get(client->internal->ftp_sessions)) != SILC_LIST_END) { if (session->session_id == session_id) break; } if (session == SILC_LIST_END) { SILC_LOG_DEBUG(("Unknown session ID: %d\n", session_id)); return SILC_CLIENT_FILE_UNKNOWN_SESSION; } if (session->monitor) { (*session->monitor)(session->client, session->conn, SILC_CLIENT_FILE_MONITOR_CLOSED, SILC_CLIENT_FILE_OK, 0, 0, session->client_entry, session->session_id, session->filepath, session->monitor_context); /* No more callbacks to application */ session->monitor = NULL; } silc_schedule_task_del_by_context(client->schedule, session); session->closed = TRUE; /* Destroy via timeout */ silc_schedule_task_add_timeout(conn->internal->schedule, silc_client_file_close_final, session, 0, 1); return SILC_CLIENT_FILE_OK; }
SilcClientFileError silc_client_file_receive(SilcClient client, SilcClientConnection conn, SilcClientConnectionParams *params, SilcPublicKey public_key, SilcPrivateKey private_key, SilcClientFileMonitor monitor, void *monitor_context, const char *path, SilcUInt32 session_id, SilcClientFileAskName ask_name, void *ask_name_context) { SilcClientFtpSession session; SilcBuffer keyagr; if (!client || !conn) return SILC_CLIENT_FILE_ERROR; SILC_LOG_DEBUG(("Start, Session ID: %d", session_id)); /* Get the session */ silc_dlist_start(client->internal->ftp_sessions); while ((session = silc_dlist_get(client->internal->ftp_sessions)) != SILC_LIST_END) { if (session->session_id == session_id) { break; } } if (session == SILC_LIST_END) { SILC_LOG_DEBUG(("Unknown session ID: %d\n", session_id)); return 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 SILC_CLIENT_FILE_ALREADY_STARTED; } session->monitor = monitor; session->monitor_context = monitor_context; session->ask_name = ask_name; session->ask_name_context = ask_name_context; 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")); /* Connect to the remote client. Performs key exchange automatically. */ session->op = silc_client_connect_to_client(client, params, public_key, private_key, session->hostname, session->port, silc_client_ftp_connect_completion, session); if (!session->op) { silc_free(session); return SILC_CLIENT_FILE_ERROR; } } else { /* Add the listener for the key agreement */ SILC_LOG_DEBUG(("Creating listener for file transfer")); if (!params || (!params->local_ip && !params->bind_ip)) { session->client->internal->ops->say(session->client, session->conn, SILC_CLIENT_MESSAGE_ERROR, "Cannot create listener for file " "transfer; IP address and/or port " "not provided"); silc_free(session); return SILC_CLIENT_FILE_ERROR; } session->listener = silc_client_listener_add(client, conn->internal->schedule, params, public_key, private_key, silc_client_ftp_connect_completion, session); if (!session->listener) { client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR, "Cannot create listener for file transfer: " "%s", strerror(errno)); silc_free(session); return SILC_CLIENT_FILE_NO_MEMORY; } session->hostname = (params->bind_ip ? strdup(params->bind_ip) : strdup(params->local_ip)); session->port = silc_client_listener_get_local_port(session->listener); /* Send the key agreement inside FTP packet */ SILC_LOG_DEBUG(("Sending key agreement for file transfer")); keyagr = silc_key_agreement_payload_encode(session->hostname, 0, session->port); if (!keyagr) { silc_client_listener_free(session->listener); silc_free(session); return SILC_CLIENT_FILE_NO_MEMORY; } silc_packet_send_va_ext(conn->stream, SILC_PACKET_FTP, 0, 0, NULL, SILC_ID_CLIENT, &session->client_entry->id, NULL, NULL, SILC_STR_UI_CHAR(1), SILC_STR_DATA(silc_buffer_data(keyagr), silc_buffer_len(keyagr)), SILC_STR_END); silc_buffer_free(keyagr); /* Add session request timeout */ if (params && params->timeout_secs) silc_schedule_task_add_timeout(client->schedule, silc_client_ftp_timeout, session, params->timeout_secs, 0); } return SILC_CLIENT_FILE_OK; }
SilcClientFileError silc_client_file_send(SilcClient client, SilcClientConnection conn, SilcClientEntry client_entry, SilcClientConnectionParams *params, SilcPublicKey public_key, SilcPrivateKey private_key, SilcClientFileMonitor monitor, void *monitor_context, const char *filepath, SilcUInt32 *session_id) { SilcClientFtpSession session; SilcBuffer keyagr; char *filename, *path; int fd; SILC_LOG_DEBUG(("File send request (file: %s)", filepath)); if (!client || !client_entry || !filepath || !params || !public_key || !private_key) return SILC_CLIENT_FILE_ERROR; /* Check for existing session for `filepath'. */ silc_dlist_start(client->internal->ftp_sessions); while ((session = silc_dlist_get(client->internal->ftp_sessions))) { if (session->filepath && !strcmp(session->filepath, filepath) && session->client_entry == client_entry) return SILC_CLIENT_FILE_ALREADY_STARTED; } /* See whether the file exists and can be opened */ 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)); if (!session) return SILC_CLIENT_FILE_ERROR; session->session_id = ++client->internal->next_session_id; session->client = client; session->server_conn = conn; session->initiator = TRUE; session->client_entry = silc_client_ref_client(client, conn, client_entry); session->monitor = monitor; session->monitor_context = monitor_context; session->filepath = strdup(filepath); session->params = *params; session->public_key = public_key; session->private_key = private_key; if (silc_asprintf(&path, "file://%s", filepath) < 0) { silc_free(session); return SILC_CLIENT_FILE_NO_MEMORY; } /* Allocate memory filesystem and put the file to it */ if (strrchr(path, '/')) filename = strrchr(path, '/') + 1; else 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, path); session->filesize = silc_file_size(filepath); /* If local IP is provided, create listener for incoming key exchange */ if (params->local_ip || params->bind_ip) { session->listener = silc_client_listener_add(client, conn->internal->schedule, params, public_key, private_key, silc_client_ftp_connect_completion, session); if (!session->listener) { client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR, "Cannot create listener for file transfer: " "%s", strerror(errno)); silc_free(session); return SILC_CLIENT_FILE_NO_MEMORY; } session->hostname = (params->bind_ip ? strdup(params->bind_ip) : strdup(params->local_ip)); session->port = silc_client_listener_get_local_port(session->listener); } SILC_LOG_DEBUG(("Sending key agreement for file transfer")); /* Send the key agreement inside FTP packet */ keyagr = silc_key_agreement_payload_encode(session->hostname, 0, session->port); if (!keyagr) { if (session->listener) silc_client_listener_free(session->listener); silc_free(session); return SILC_CLIENT_FILE_NO_MEMORY; } silc_packet_send_va_ext(conn->stream, SILC_PACKET_FTP, 0, 0, NULL, SILC_ID_CLIENT, &client_entry->id, NULL, NULL, SILC_STR_UI_CHAR(1), SILC_STR_DATA(silc_buffer_data(keyagr), silc_buffer_len(keyagr)), SILC_STR_END); silc_buffer_free(keyagr); silc_free(path); silc_dlist_add(client->internal->ftp_sessions, session); if (session_id) *session_id = session->session_id; /* Add session request timeout */ if (params && params->timeout_secs) silc_schedule_task_add_timeout(client->schedule, silc_client_ftp_timeout, session, params->timeout_secs, 0); return SILC_CLIENT_FILE_OK; }
int silc_socket_stream_read(SilcStream stream, unsigned char *buf, SilcUInt32 buf_len) { SilcSocketStream sock = stream; int len = 0; struct timeval curtime; unsigned char *qosbuf; SILC_LOG_DEBUG(("Reading data from socket %d", sock->sock)); /* Handle the simple non-QoS reading. */ if (!sock->qos) { len = read(sock->sock, buf, buf_len); if (len < 0) { if (errno == EAGAIN || errno == EINTR) { SILC_LOG_DEBUG(("Could not read immediately, will do it later")); silc_schedule_set_listen_fd(sock->schedule, sock->sock, silc_schedule_get_fd_events(sock->schedule, sock->sock) | SILC_TASK_READ, FALSE); return -1; } SILC_LOG_DEBUG(("Cannot read from socket: %d:%s", sock->sock, strerror(errno))); silc_schedule_unset_listen_fd(sock->schedule, sock->sock); sock->sock_error = errno; return -2; } SILC_LOG_DEBUG(("Read %d bytes", len)); if (!len) silc_schedule_unset_listen_fd(sock->schedule, sock->sock); return len; } /* We have QoS set, and reading is done via the QoS system. */ qosbuf = sock->qos->buffer; /* If QoS was applied, return the data that was pending. */ if (sock->qos->applied && sock->qos->data_len) { memcpy(buf, qosbuf, sock->qos->data_len); len = sock->qos->data_len; sock->qos->data_len = 0; return len; } /* If we have active QoS data pending, return with no data */ if (sock->qos->data_len) { silc_schedule_unset_listen_fd(sock->schedule, sock->sock); return -1; } /* Read the data from the socket. The qosbuf size is always the max read limit size. */ len = (buf_len < sock->qos->read_limit_bytes ? buf_len : sock->qos->read_limit_bytes); len = read(sock->sock, qosbuf, len); if (len < 0) { if (errno == EAGAIN || errno == EINTR) { SILC_LOG_DEBUG(("Could not read immediately, will do it later")); silc_schedule_set_listen_fd(sock->schedule, sock->sock, silc_schedule_get_fd_events(sock->schedule, sock->sock) | SILC_TASK_READ, FALSE); return -1; } SILC_LOG_DEBUG(("Cannot read from socket: %d:%s", sock->sock, strerror(errno))); silc_schedule_unset_listen_fd(sock->schedule, sock->sock); silc_schedule_task_del_by_context(sock->schedule, sock->qos); sock->qos->data_len = 0; sock->sock_error = errno; return -2; } SILC_LOG_DEBUG(("Read %d bytes", len)); if (!len) { silc_schedule_unset_listen_fd(sock->schedule, sock->sock); silc_schedule_task_del_by_context(sock->schedule, sock->qos); sock->qos->data_len = 0; return 0; } /* If we have passed the rate time limit, set our new time limit, and zero the rate limit. This limits reads per second. */ silc_gettimeofday(&curtime); if (silc_compare_timeval(&curtime, &sock->qos->next_limit) > 0) { curtime.tv_sec++; sock->qos->next_limit = curtime; sock->qos->cur_rate = 0; } sock->qos->cur_rate++; /* If we are not within rate limit apply QoS for the read data */ if (sock->qos->cur_rate > sock->qos->read_rate) { silc_schedule_task_add_timeout(sock->schedule, silc_socket_read_qos, sock->qos, sock->qos->limit_sec, sock->qos->limit_usec); sock->qos->data_len = len; /* Rate limit kicked in, do not return data yet */ silc_schedule_unset_listen_fd(sock->schedule, sock->sock); return -1; } /* Return the data from the QoS buffer */ memcpy(buf, qosbuf, len); return len; }
SilcBool silc_client_save_channel_key(SilcClient client, SilcClientConnection conn, SilcBuffer key_payload, SilcChannelEntry channel) { unsigned char *id_string, *key, *cipher, *hmac, hash[SILC_HASH_MAXLEN]; SilcUInt32 tmp_len; SilcChannelID id; SilcChannelKeyPayload payload; SILC_LOG_DEBUG(("New channel key")); payload = silc_channel_key_payload_parse(silc_buffer_data(key_payload), silc_buffer_len(key_payload)); if (!payload) return FALSE; id_string = silc_channel_key_get_id(payload, &tmp_len); if (!id_string) { silc_channel_key_payload_free(payload); return FALSE; } if (!silc_id_str2id(id_string, tmp_len, SILC_ID_CHANNEL, &id, sizeof(id))) { silc_channel_key_payload_free(payload); return FALSE; } /* Find channel. */ if (!channel) { channel = silc_client_get_channel_by_id(client, conn, &id); if (!channel) { SILC_LOG_DEBUG(("Key for unknown channel")); silc_channel_key_payload_free(payload); return FALSE; } } else { silc_client_ref_channel(client, conn, channel); } /* Save the old key for a short period of time so that we can decrypt channel message even after the rekey if some client would be sending messages with the old key after the rekey. */ if (!channel->internal.old_channel_keys) channel->internal.old_channel_keys = silc_dlist_init(); if (!channel->internal.old_hmacs) channel->internal.old_hmacs = silc_dlist_init(); if (channel->internal.old_channel_keys && channel->internal.old_hmacs) { silc_dlist_add(channel->internal.old_channel_keys, channel->internal.receive_key); silc_dlist_add(channel->internal.old_hmacs, channel->internal.hmac); silc_schedule_task_add_timeout(client->schedule, silc_client_save_channel_key_rekey, channel, 15, 0); } /* Get channel cipher */ cipher = silc_channel_key_get_cipher(payload, NULL); if (!silc_cipher_alloc(cipher, &channel->internal.send_key)) { client->internal->ops->say( conn->client, conn, SILC_CLIENT_MESSAGE_AUDIT, "Cannot talk to channel: unsupported cipher %s", cipher); silc_client_unref_channel(client, conn, channel); silc_channel_key_payload_free(payload); return FALSE; } if (!silc_cipher_alloc(cipher, &channel->internal.receive_key)) { client->internal->ops->say( conn->client, conn, SILC_CLIENT_MESSAGE_AUDIT, "Cannot talk to channel: unsupported cipher %s", cipher); silc_client_unref_channel(client, conn, channel); silc_channel_key_payload_free(payload); return FALSE; } /* Set the cipher key. Both sending and receiving keys are same */ key = silc_channel_key_get_key(payload, &tmp_len); silc_cipher_set_key(channel->internal.send_key, key, tmp_len * 8, TRUE); silc_cipher_set_key(channel->internal.receive_key, key, tmp_len * 8, FALSE); /* Get channel HMAC */ hmac = (channel->internal.hmac ? (char *)silc_hmac_get_name(channel->internal.hmac) : SILC_DEFAULT_HMAC); if (!silc_hmac_alloc(hmac, NULL, &channel->internal.hmac)) { client->internal->ops->say( conn->client, conn, SILC_CLIENT_MESSAGE_AUDIT, "Cannot talk to channel: unsupported HMAC %s", hmac); silc_client_unref_channel(client, conn, channel); silc_channel_key_payload_free(payload); return FALSE; } channel->cipher = silc_cipher_get_name(channel->internal.send_key); channel->hmac = silc_hmac_get_name(channel->internal.hmac); /* Set HMAC key */ silc_hash_make(silc_hmac_get_hash(channel->internal.hmac), key, tmp_len, hash); silc_hmac_set_key(channel->internal.hmac, hash, silc_hash_len(silc_hmac_get_hash(channel->internal.hmac))); memset(hash, 0, sizeof(hash)); silc_channel_key_payload_free(payload); silc_client_unref_channel(client, conn, channel); return TRUE; }
static void *silc_thread_pool_run_thread(void *context) { SilcThreadPoolThread t = context, o, q; SilcThreadPool tp = t->tp; SilcMutex lock = t->lock; SilcCond thread_signal = t->thread_signal; silc_mutex_lock(lock); while (1) { /* Wait here for code to execute */ while (!t->run && !t->stop) silc_cond_wait(thread_signal, lock); if (t->stop) goto stop; /* Execute code */ silc_mutex_unlock(lock); execute: SILC_LOG_DEBUG(("Execute call %p, context %p, thread %p", t->run, t->run_context, t)); t->run(t->schedule, t->run_context); /* If scheduler is NULL, call completion directly from here. Otherwise it is called through the scheduler in the thread where the scheduler is running. */ if (t->completion) { if (t->schedule) { SILC_LOG_DEBUG(("Run completion through scheduler %p", t->schedule)); if (!silc_schedule_task_add_timeout(t->schedule, t->completion, t->completion_context, 0, 0)) { SILC_LOG_DEBUG(("Run completion directly")); t->completion(NULL, NULL, 0, 0, t->completion_context); } silc_schedule_wakeup(t->schedule); } else { SILC_LOG_DEBUG(("Run completion directly")); t->completion(NULL, NULL, 0, 0, t->completion_context); } } silc_mutex_lock(lock); if (t->stop) goto stop; /* Check if there are calls in queue. Takes the most recently added call since new ones are added at the start of the list. */ if (silc_list_count(t->queue) > 0) { execute_queue: silc_list_start(t->queue); q = silc_list_get(t->queue); SILC_LOG_DEBUG(("Execute call from queue")); /* Execute this call now */ t->run = q->run; t->run_context = q->run_context; t->completion = q->completion; t->completion_context = q->completion_context; t->schedule = q->schedule; silc_list_del(t->queue, q); silc_list_add(t->free_queue, q); silc_mutex_unlock(lock); goto execute; } silc_mutex_unlock(lock); silc_mutex_lock(tp->lock); /* Nothing to do. Attempt to steal call from some other thread. */ o = silc_list_get(tp->threads); if (!o) { /* List wraps around */ silc_list_start(tp->threads); o = silc_list_get(tp->threads); } /* Check that the other thread is valid and has something to execute. */ silc_mutex_lock(o->lock); if (o == t || o->stop || silc_list_count(o->queue) == 0) { silc_mutex_unlock(o->lock); o = NULL; } if (o) { silc_mutex_unlock(tp->lock); silc_list_start(o->queue); q = silc_list_get(o->queue); SILC_LOG_DEBUG(("Execute call from queue from thread %p", o)); /* Execute this call now */ t->run = q->run; t->run_context = q->run_context; t->completion = q->completion; t->completion_context = q->completion_context; t->schedule = q->schedule; silc_list_del(o->queue, q); silc_list_add(o->free_queue, q); silc_mutex_unlock(o->lock); goto execute; } silc_mutex_lock(lock); if (t->stop) { silc_mutex_unlock(tp->lock); goto stop; } /* Now that we have the lock back, check the queue again. */ if (silc_list_count(t->queue) > 0) { silc_mutex_unlock(tp->lock); goto execute_queue; } /* The thread is now free for use again. */ t->run = NULL; t->completion = NULL; t->schedule = NULL; silc_list_add(tp->free_threads, t); silc_mutex_unlock(tp->lock); } stop: /* Stop the thread. Remove from threads list. */ SILC_LOG_DEBUG(("Stop thread %p", t)); /* We can unlock the thread now. After we get the thread pool lock no one can retrieve the thread anymore. */ silc_mutex_unlock(lock); silc_mutex_lock(tp->lock); silc_list_del(tp->threads, t); silc_list_start(tp->threads); /* Clear thread's call queue. */ silc_list_start(t->queue); silc_list_start(t->free_queue); while ((q = silc_list_get(t->queue))) silc_sfree(tp->stack, q); while ((q = silc_list_get(t->free_queue))) silc_sfree(tp->stack, q); /* Destroy the thread */ silc_mutex_free(lock); silc_cond_free(thread_signal); silc_sfree(tp->stack, t); /* If we are last thread, signal the waiting destructor. */ if (silc_list_count(tp->threads) == 0) silc_cond_signal(tp->pool_signal); /* Release pool reference. Releases lock also. */ silc_thread_pool_unref(tp); return NULL; }