X-Git-Url: http://git.silcnet.org/gitweb/?a=blobdiff_plain;f=apps%2Fsilcd%2Fidlist.c;h=1c75d7181bf0b8a73af4a9ed3c5562771a8ec2eb;hb=805fddcf6431e784f9f77114782a90c9d12f9cbe;hp=5f04a26fd691c81ce893bf4a0d34d1128d4b8de2;hpb=0f9738ce962b8498bbed0a75d5fb6fa127e3577f;p=silc.git diff --git a/apps/silcd/idlist.c b/apps/silcd/idlist.c index 5f04a26f..1c75d718 100644 --- a/apps/silcd/idlist.c +++ b/apps/silcd/idlist.c @@ -2,399 +2,856 @@ idlist.c - Author: Pekka Riikonen + Author: Pekka Riikonen - Copyright (C) 1997 - 2000 Pekka Riikonen + Copyright (C) 1997 - 2007 Pekka Riikonen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -/* - * $Id$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ +/* $Id$ */ #include "serverincludes.h" #include "idlist.h" +#include "server_internal.h" + +/****************************************************************************** + + Common functions + +******************************************************************************/ + +/* This function is used to add keys and stuff to common ID entry data + structure. */ + +void silc_idlist_add_data(void *entry, SilcIDListData idata) +{ + SilcIDListData data = entry; + data->conn_type = idata->conn_type; + data->sconn = idata->sconn; + data->hash = idata->hash; + data->public_key = idata->public_key; + memcpy(data->fingerprint, idata->fingerprint, sizeof(data->fingerprint)); + data->rekey = idata->rekey; + data->last_receive = idata->last_receive; + data->last_sent = idata->last_sent; + data->status = idata->status; + data->created = time(0); /* Update creation time */ +} -/* Adds a new server to the list. The pointer sent as argument is allocated - and returned. */ +/* Free's all data in the common ID entry data structure. */ -void silc_idlist_add_server(SilcServerList **list, - char *server_name, int server_type, - SilcServerID *id, SilcServerList *router, - SilcCipher send_key, SilcCipher receive_key, - SilcPKCS public_key, SilcHmac hmac, - SilcServerList **new_idlist) +void silc_idlist_del_data(void *entry) { - SilcServerList *last, *idlist; + SilcIDListData idata = (SilcIDListData)entry; + + if (idata->hash) + silc_hash_free(idata->hash); + + idata->hash = NULL; + idata->public_key = NULL; +} + +/****************************************************************************** + + Server entry functions - SILC_LOG_DEBUG(("Adding new server to id list")); +******************************************************************************/ - idlist = silc_calloc(1, sizeof(*idlist)); - if (idlist == NULL) { - SILC_LOG_ERROR(("Could not allocate new server list object")); - *new_idlist = NULL; - return; +/* Add new server entry. This adds the new server entry to ID cache and + returns the allocated entry object or NULL on error. This is called + when new server connects to us. We also add ourselves to cache with + this function. */ + +SilcServerEntry +silc_idlist_add_server(SilcIDList id_list, + char *server_name, int server_type, + SilcServerID *id, SilcServerEntry router, + void *connection) +{ + SilcServerEntry server; + char *server_namec = NULL; + + SILC_LOG_DEBUG(("Adding new server entry")); + + /* Normalize name. This is cached, original is in server context. */ + if (server_name) { + server_namec = silc_identifier_check(server_name, strlen(server_name), + SILC_STRING_UTF8, 256, NULL); + if (!server_namec) + return NULL; } - /* Set the pointers */ - idlist->server_name = server_name; - idlist->server_type = server_type; - idlist->id = id; - idlist->router = router; - idlist->send_key = send_key; - idlist->receive_key = receive_key; - idlist->public_key = public_key; - idlist->hmac = hmac; - idlist->next = idlist; - idlist->prev = idlist; - - /* First on the list? */ - if (!*list) { - *list = idlist; - *new_idlist = idlist; - return; + server = silc_calloc(1, sizeof(*server)); + server->server_name = server_name; + server->server_type = server_type; + server->id = id; + server->router = router; + server->connection = connection; + + if (!silc_idcache_add(id_list->servers, server_namec, + (void *)server->id, (void *)server)) { + silc_free(server); + silc_free(server_namec); + return NULL; } - /* Add it to the list */ - last = (*list)->prev; - last->next = idlist; - (*list)->prev = idlist; - idlist->next = (*list); - idlist->prev = last; + return server; +} + +/* Finds server by Server ID */ + +SilcServerEntry +silc_idlist_find_server_by_id(SilcIDList id_list, SilcServerID *id, + SilcBool registered, SilcIDCacheEntry *ret_entry) +{ + SilcIDCacheEntry id_cache = NULL; + SilcServerEntry server; + + if (!id) + return NULL; + + SILC_LOG_DEBUG(("Server ID (%s)", + silc_id_render(id, SILC_ID_SERVER))); + + if (!silc_idcache_find_by_id_one(id_list->servers, (void *)id, &id_cache)) + return NULL; + + server = (SilcServerEntry)id_cache->context; + + if (server && registered && + !(server->data.status & SILC_IDLIST_STATUS_REGISTERED)) + return NULL; - if (new_idlist) - *new_idlist = idlist; + if (ret_entry) + *ret_entry = id_cache; + + SILC_LOG_DEBUG(("Found")); + + return server; } -/* Adds a new client to the client list. This is called when new client - connection is accepted to the server. This adds all the relevant data - about the client and session with it to the list. This list is - referenced for example when sending message to the client. */ - -void silc_idlist_add_client(SilcClientList **list, char *nickname, - char *username, char *userinfo, - SilcClientID *id, SilcServerList *router, - SilcCipher send_key, SilcCipher receive_key, - SilcPKCS public_key, SilcHmac hmac, - SilcClientList **new_idlist) +/* Find server by name. The 'name' must be normalized already. */ + +SilcServerEntry +silc_idlist_find_server_by_name(SilcIDList id_list, char *name, + SilcBool registered, + SilcIDCacheEntry *ret_entry) { - SilcClientList *last, *idlist; + SilcIDCacheEntry id_cache = NULL; + SilcServerEntry server; - SILC_LOG_DEBUG(("Adding new client to id list")); + SILC_LOG_DEBUG(("Server by name `%s'", name)); - idlist = silc_calloc(1, sizeof(*idlist)); - if (idlist == NULL) { - SILC_LOG_ERROR(("Could not allocate new client list object")); - return; - } + if (!silc_idcache_find_by_name_one(id_list->servers, name, &id_cache)) + return NULL; - /* Set the pointers */ - idlist->nickname = nickname; - idlist->username = username; - idlist->userinfo = userinfo; - idlist->id = id; - idlist->router = router; - idlist->send_key = send_key; - idlist->receive_key = receive_key; - idlist->public_key = public_key; - idlist->hmac = hmac; - idlist->next = idlist; - idlist->prev = idlist; - - /* First on the list? */ - if (!(*list)) { - *list = idlist; - if (new_idlist) - *new_idlist = idlist; - return; - } + server = (SilcServerEntry)id_cache->context; - /* Add it to the list */ - last = (*list)->prev; - last->next = idlist; - (*list)->prev = idlist; - idlist->next = *list; - idlist->prev = last; + if (server && registered && + !(server->data.status & SILC_IDLIST_STATUS_REGISTERED)) + return NULL; - if (new_idlist) - *new_idlist = idlist; + if (ret_entry) + *ret_entry = id_cache; + + SILC_LOG_DEBUG(("Found")); + + return server; } -/* Free client entry. This free's everything. */ +/* Find server by connection parameters, hostname and port */ -void silc_idlist_del_client(SilcClientList **list, SilcClientList *entry) +SilcServerEntry +silc_idlist_find_server_by_conn(SilcIDList id_list, char *hostname, + int port, SilcBool registered, + SilcIDCacheEntry *ret_entry) { - if (entry) { - if (entry->nickname) - silc_free(entry->nickname); - if (entry->username) - silc_free(entry->username); - if (entry->userinfo) - silc_free(entry->userinfo); - if (entry->id) - silc_free(entry->id); - if (entry->send_key) - silc_cipher_free(entry->send_key); - if (entry->receive_key) - silc_cipher_free(entry->receive_key); - if (entry->public_key) - silc_pkcs_free(entry->public_key); - if (entry->hmac) - silc_hmac_free(entry->hmac); - if (entry->hmac_key) { - memset(entry->hmac_key, 0, entry->hmac_key_len); - silc_free(entry->hmac_key); - } + SilcList list; + SilcIDCacheEntry id_cache = NULL; + SilcServerEntry server = NULL; + SilcPacketStream sock; + const char *host = NULL, *ip = NULL; - /* Last one in list? */ - if (*list == entry && entry->next == entry) { - *list = NULL; - silc_free(entry); - return; - } + SILC_LOG_DEBUG(("Server by hostname %s and port %d", hostname, port)); + + if (!silc_idcache_get_all(id_list->servers, &list)) + return NULL; - /* At the start of list? */ - if (*list == entry && entry->next != entry) { - *list = entry->next; - entry->next->prev = entry->prev; - entry->prev->next = *list; - silc_free(entry); - return; + silc_list_start(list); + while ((id_cache = silc_list_get(list))) { + server = id_cache->context; + sock = server->connection; + + if (sock && silc_socket_stream_get_info( + silc_packet_stream_get_stream(sock), + NULL, &host, &ip, NULL)) { + if (((host && !strcasecmp(host, hostname)) || + (ip && !strcasecmp(ip, hostname))) && + server->id->port == SILC_SWAB_16(port)) + break; } - /* Remove from list */ - entry->prev->next = entry->next; - entry->next->prev = entry->prev; - silc_free(entry); - return; + id_cache = NULL; + server = NULL; } + + if (server && registered && + !(server->data.status & SILC_IDLIST_STATUS_REGISTERED)) + return NULL; + + if (ret_entry) + *ret_entry = id_cache; + + SILC_LOG_DEBUG(("Found")); + + return server; } -SilcClientList * -silc_idlist_find_client_by_nickname(SilcClientList *list, - char *nickname, - char *server) +/* Replaces old Server ID with new one */ + +SilcServerEntry +silc_idlist_replace_server_id(SilcIDList id_list, SilcServerID *old_id, + SilcServerID *new_id) { - SilcClientList *first, *entry; + SilcIDCacheEntry id_cache = NULL; + SilcServerEntry server; + char *name; - SILC_LOG_DEBUG(("Finding client by nickname")); + if (!old_id || !new_id) + return NULL; + + SILC_LOG_DEBUG(("Replacing Server ID")); - if (!list) + if (!silc_idcache_find_by_id_one(id_list->servers, (void *)old_id, &id_cache)) return NULL; - first = entry = list; - if (!strcmp(entry->nickname, nickname)) { - SILC_LOG_DEBUG(("Found")); - return entry; - } - entry = entry->next; + server = (SilcServerEntry)id_cache->context; + name = strdup(id_cache->name); - while(entry != first) { - if (!strcmp(entry->nickname, nickname)) { - SILC_LOG_DEBUG(("Found")); - return entry; + /* Remove the old entry and add a new one */ + + silc_idcache_del_by_id(id_list->servers, (void *)server->id, NULL); + *server->id = *new_id; + silc_idcache_add(id_list->servers, name, server->id, server); + + SILC_LOG_DEBUG(("Found")); + + return server; +} + +/* Removes and free's server entry from ID list */ + +int silc_idlist_del_server(SilcIDList id_list, SilcServerEntry entry) +{ + if (entry) { + /* Remove from cache */ + if (!silc_idcache_del_by_context(id_list->servers, entry, NULL)) { + SILC_LOG_DEBUG(("Unknown server, did not delete")); + return FALSE; } - entry = entry->next; + SILC_LOG_DEBUG(("Deleting server %s id %s", entry->server_name ? + entry->server_name : "", + entry->id ? + silc_id_render(entry->id, SILC_ID_SERVER) : "")); + + /* Free data */ + silc_free(entry->server_name); + silc_free(entry->id); + silc_free(entry->server_info); + + memset(entry, 'F', sizeof(*entry)); + silc_free(entry); + return TRUE; } - return NULL; + return FALSE; } -SilcClientList * -silc_idlist_find_client_by_hash(SilcClientList *list, - char *nickname, SilcHash md5hash) +/* ID Cache destructor */ + +void silc_idlist_server_destructor(SilcIDCache cache, + SilcIDCacheEntry entry, + void *dest_context, + void *app_context) { - SilcClientList *first, *entry; - unsigned char hash[16]; + silc_free(entry->name); +} - SILC_LOG_DEBUG(("Finding client by nickname hash")); +/****************************************************************************** - if (!list) - return NULL; + Client entry functions - /* Make hash of the nickname */ - silc_hash_make(md5hash, nickname, strlen(nickname), hash); +******************************************************************************/ + +/* Add new client entry. This adds the client entry to ID cache system + and returns the allocated client entry or NULL on error. This is + called when new client connection is accepted to the server. If The + `router' is provided then the all server routines assume that the client + is not directly connected local client but it has router set and is + remote. If this is the case then `connection' must be NULL. If, on the + other hand, the `connection' is provided then the client is assumed + to be directly connected local client and `router' must be NULL. */ + +SilcClientEntry +silc_idlist_add_client(SilcIDList id_list, char *nickname, char *username, + char *userinfo, SilcClientID *id, + SilcServerEntry router, void *connection) +{ + SilcClientEntry client; + char *nicknamec = NULL; + + SILC_LOG_DEBUG(("Adding new client entry")); - first = entry = list; - if (entry && !SILC_ID_COMPARE_HASH(entry->id, hash)) { - SILC_LOG_DEBUG(("Found")); - return entry; + /* Normalize name. This is cached, original is in client context. */ + if (nickname) { + nicknamec = silc_identifier_check(nickname, strlen(nickname), + SILC_STRING_UTF8, 128, NULL); + if (!nicknamec) + return NULL; } - entry = entry->next; - while(entry != first) { - if (entry && !SILC_ID_COMPARE_HASH(entry->id, hash)) { - SILC_LOG_DEBUG(("Found")); - return entry; - } + /* Check username. */ + if (username) { + char u[128 + 1], h[256 + 1]; + int ret; + + ret = silc_parse_userfqdn(username, u, sizeof(u), h, sizeof(h)); + if (!ret) + return NULL; + if (!silc_identifier_verify(u, strlen(u), SILC_STRING_UTF8, 128)) + return NULL; + if (ret > 1 && !silc_identifier_verify(h, strlen(h), + SILC_STRING_UTF8, 256)) + return NULL; + } - entry = entry->next; + client = silc_calloc(1, sizeof(*client)); + if (!client) + return NULL; + client->nickname = nickname; + client->username = username ? strdup(username) : NULL; + client->userinfo = userinfo; + client->id = id; + client->router = router; + client->connection = connection; + client->channels = silc_hash_table_alloc(3, silc_hash_ptr, NULL, + NULL, NULL, NULL, NULL, TRUE); + + if (!silc_idcache_add(id_list->clients, nicknamec, (void *)client->id, + (void *)client)) { + silc_hash_table_free(client->channels); + silc_free(client); + silc_free(nicknamec); + return NULL; } - return NULL; + return client; } -SilcClientList * -silc_idlist_find_client_by_id(SilcClientList *list, SilcClientID *id) +/* Free client entry. This free's everything and removes the entry + from ID cache. Call silc_idlist_del_data before calling this one. */ + +int silc_idlist_del_client(SilcIDList id_list, SilcClientEntry entry) { - SilcClientList *first, *entry; + SILC_LOG_DEBUG(("Delete client %p", entry)); + + if (entry) { + /* Delete client, destructor will free data */ + if (!silc_idcache_del_by_context(id_list->clients, entry, NULL)) { + SILC_LOG_DEBUG(("Unknown client, did not delete")); + return FALSE; + } + return TRUE; + } - SILC_LOG_DEBUG(("Finding client by Client ID")); + return FALSE; +} - if (!list) - return NULL; +/* ID Cache destructor */ - first = entry = list; - if (entry && !SILC_ID_CLIENT_COMPARE(entry->id, id)) { - SILC_LOG_DEBUG(("Found")); - return entry; +void silc_idlist_client_destructor(SilcIDCache cache, + SilcIDCacheEntry entry, + void *dest_context, + void *app_context) +{ + SilcServer server = dest_context; + SilcClientEntry client; + + client = (SilcClientEntry)entry->context; + if (client) { + /* Remove client's public key from repository, this will free it too. */ + if (client->data.public_key) + silc_skr_del_public_key(server->repository, client->data.public_key, + client); + + assert(!silc_hash_table_count(client->channels)); + silc_free(client->nickname); + silc_free(client->servername); + silc_free(client->username); + silc_free(client->userinfo); + silc_free(client->id); + silc_free(client->attrs); + silc_hash_table_free(client->channels); + + memset(client, 'A', sizeof(*client)); + silc_free(client); } - entry = entry->next; +} - while(entry != first) { - if (entry && !SILC_ID_CLIENT_COMPARE(entry->id, id)) { - SILC_LOG_DEBUG(("Found")); - return entry; - } +/* Returns all clients matching requested nickname. Number of clients is + returned to `clients_count'. Caller must free the returned table. + The 'nickname' must be normalized already. */ - entry = entry->next; - } +int silc_idlist_get_clients_by_nickname(SilcIDList id_list, char *nickname, + char *server, + SilcClientEntry **clients, + SilcUInt32 *clients_count) +{ + SilcList list; + SilcIDCacheEntry id_cache = NULL; + + SILC_LOG_DEBUG(("Start")); + + if (!silc_idcache_find_by_name(id_list->clients, nickname, &list)) + return FALSE; + + *clients = silc_realloc(*clients, + (silc_list_count(list) + *clients_count) * + sizeof(**clients)); - return NULL; + silc_list_start(list); + while ((id_cache = silc_list_get(list))) + (*clients)[(*clients_count)++] = id_cache->context; + + SILC_LOG_DEBUG(("Found total %d clients", *clients_count)); + + return TRUE; } -/* Adds new channel to the list. */ +/* Returns all clients matching requested nickname hash. Number of clients + is returned to `clients_count'. Caller must free the returned table. + The 'nickname' must be normalized already. */ -void silc_idlist_add_channel(SilcChannelList **list, - char *channel_name, int mode, - SilcChannelID *id, SilcServerList *router, - SilcCipher channel_key, - SilcChannelList **new_idlist) +int silc_idlist_get_clients_by_hash(SilcIDList id_list, + char *nickname, char *server, + SilcHash md5hash, + SilcClientEntry **clients, + SilcUInt32 *clients_count) { - SilcChannelList *last, *idlist; + SilcList list; + SilcIDCacheEntry id_cache = NULL; + unsigned char hash[SILC_HASH_MAXLEN]; + SilcClientID client_id; + SilcClientEntry client_entry; - SILC_LOG_DEBUG(("Adding new channel to id list")); + SILC_LOG_DEBUG(("Start")); - idlist = silc_calloc(1, sizeof(*idlist)); - if (idlist == NULL) { - SILC_LOG_ERROR(("Could not allocate new channel list object")); - return; - } + silc_hash_make(md5hash, nickname, strlen(nickname), hash); - /* Set the pointers */ - idlist->channel_name = channel_name; - idlist->mode = mode; - idlist->id = id; - idlist->router = router; - idlist->channel_key = channel_key; - idlist->next = idlist; - idlist->prev = idlist; - - /* First on the list? */ - if (!*list) { - *list = idlist; - if (new_idlist) - *new_idlist = idlist; - return; + /* As the Client ID is hashed in the ID cache by hashing only the hash + from the Client ID, we can do a lookup with only the hash not the + other parts of the ID and get all the clients with that hash, ie. + with that nickname, as the hash is from the nickname. */ + memset(&client_id, 0, sizeof(client_id)); + memcpy(&client_id.hash, hash, sizeof(client_id.hash)); + if (!silc_idcache_find_by_id(id_list->clients, &client_id, &list)) + return FALSE; + + /* If server is specified, narrow the search with it. */ + if (server) { + silc_list_start(list); + while ((id_cache = silc_list_get(list))) { + client_entry = id_cache->context; + if (!client_entry->servername) + continue; + if (!silc_utf8_strcasecmp(client_entry->servername, server)) + silc_list_del(list, id_cache); + } } - /* Add it to the list */ - last = (*list)->prev; - last->next = idlist; - (*list)->prev = idlist; - idlist->next = (*list); - idlist->prev = last; + if (!silc_list_count(list)) + return FALSE; + + *clients = silc_realloc(*clients, + (silc_list_count(list) + *clients_count) * + sizeof(**clients)); + + silc_list_start(list); + while ((id_cache = silc_list_get(list))) + (*clients)[(*clients_count)++] = id_cache->context; + + SILC_LOG_DEBUG(("Found total %d clients", *clients_count)); + + return TRUE; +} + +/* Finds client by Client ID */ + +SilcClientEntry +silc_idlist_find_client_by_id(SilcIDList id_list, SilcClientID *id, + SilcBool registered, SilcIDCacheEntry *ret_entry) +{ + SilcIDCacheEntry id_cache = NULL; + SilcClientEntry client; + + if (!id) + return NULL; + + SILC_LOG_DEBUG(("Client ID (%s)", + silc_id_render(id, SILC_ID_CLIENT))); + + /* Find the exact client with the exact Client ID */ + if (!silc_idcache_find_by_id_one(id_list->clients, (void *)id, &id_cache)) + return NULL; + + client = (SilcClientEntry)id_cache->context; + + if (client && registered && + !(client->data.status & SILC_IDLIST_STATUS_REGISTERED)) + return NULL; + + if (ret_entry) + *ret_entry = id_cache; + + SILC_LOG_DEBUG(("Found")); - if (new_idlist) - *new_idlist = idlist; + return client; } -SilcChannelList * -silc_idlist_find_channel_by_id(SilcChannelList *list, SilcChannelID *id) +/* Replaces old Client ID with new one */ + +SilcClientEntry +silc_idlist_replace_client_id(SilcServer server, + SilcIDList id_list, SilcClientID *old_id, + SilcClientID *new_id, const char *nickname) { - SilcChannelList *first, *entry; + SilcIDCacheEntry id_cache = NULL; + SilcClientEntry client; + char *nicknamec = NULL; + + if (!old_id || !new_id) + return NULL; - SILC_LOG_DEBUG(("Finding channel by Channel ID")); + SILC_LOG_DEBUG(("Replacing Client ID")); - if (!list) + /* Normalize name. This is cached, original is in client context. */ + if (nickname) { + nicknamec = silc_identifier_check(nickname, strlen(nickname), + SILC_STRING_UTF8, 128, NULL); + if (!nicknamec) + return NULL; + } + + /* Find exact client with exact Client ID */ + if (!silc_idcache_find_by_id_one(id_list->clients, old_id, &id_cache)) return NULL; - first = entry = list; - if (entry && !SILC_ID_CHANNEL_COMPARE(entry->id, id)) { - SILC_LOG_DEBUG(("Found")); - return entry; + client = (SilcClientEntry)id_cache->context; + + /* Check if anyone is watching old nickname */ + if (server->server_type == SILC_ROUTER) + silc_server_check_watcher_list(server, client, nickname, + SILC_NOTIFY_TYPE_NICK_CHANGE); + + /* Replace */ + if (!silc_idcache_update(id_list->clients, id_cache, new_id, nicknamec, + TRUE)) + return NULL; + + silc_free(client->nickname); + client->nickname = nickname ? strdup(nickname) : NULL; + + /* Check if anyone is watching new nickname */ + if (server->server_type == SILC_ROUTER) + silc_server_check_watcher_list(server, client, nickname, + SILC_NOTIFY_TYPE_NICK_CHANGE); + + SILC_LOG_DEBUG(("Replaced")); + + return client; +} + + +/****************************************************************************** + + Channel entry functions + +******************************************************************************/ + +/* Add new channel entry. This add the new channel entry to the ID cache + system and returns the allocated entry or NULL on error. */ + +SilcChannelEntry +silc_idlist_add_channel(SilcIDList id_list, char *channel_name, int mode, + SilcChannelID *id, SilcServerEntry router, + SilcCipher send_key, SilcCipher receive_key, + SilcHmac hmac) +{ + SilcChannelEntry channel; + char *channel_namec = NULL; + + SILC_LOG_DEBUG(("Adding new channel %s", channel_name)); + + /* Normalize name. This is cached, original is in client context. */ + if (channel_name) { + channel_namec = silc_channel_name_check(channel_name, strlen(channel_name), + SILC_STRING_UTF8, 256, NULL); + if (!channel_namec) + return NULL; } - entry = entry->next; - while(entry != first) { - if (entry && !SILC_ID_CHANNEL_COMPARE(entry->id, id)) { - SILC_LOG_DEBUG(("Found")); - return entry; + channel = silc_calloc(1, sizeof(*channel)); + channel->channel_name = channel_name; + channel->mode = mode; + channel->id = id; + channel->router = router; + channel->send_key = send_key; + channel->receive_key = receive_key; + channel->hmac = hmac; + channel->created = channel->updated = time(0); + if (!channel->hmac) + if (!silc_hmac_alloc(SILC_DEFAULT_HMAC, NULL, &channel->hmac)) { + silc_free(channel); + return NULL; } - entry = entry->next; + channel->user_list = silc_hash_table_alloc(3, silc_hash_ptr, NULL, NULL, + NULL, NULL, NULL, TRUE); + + if (!silc_idcache_add(id_list->channels, channel_namec, + (void *)channel->id, (void *)channel)) { + silc_hmac_free(channel->hmac); + silc_hash_table_free(channel->user_list); + silc_free(channel); + silc_free(channel_namec); + return NULL; } - return NULL; + return channel; +} + +/* ID Cache destructor */ + +void silc_idlist_channel_destructor(SilcIDCache cache, + SilcIDCacheEntry entry, + void *dest_context, + void *app_context) +{ + silc_free(entry->name); +} + +/* Foreach callbcak to free all users from the channel when deleting a + channel entry. */ + +static void silc_idlist_del_channel_foreach(void *key, void *context, + void *user_context) +{ + SilcChannelClientEntry chl = (SilcChannelClientEntry)context; + + SILC_LOG_DEBUG(("Removing client %s from channel %s", + chl->client->nickname ? chl->client->nickname : + (unsigned char *)"", chl->channel->channel_name)); + + /* Remove the context from the client's channel hash table as that + table and channel's user_list hash table share this same context. */ + silc_hash_table_del(chl->client->channels, chl->channel); + silc_free(chl); } /* Free channel entry. This free's everything. */ -void silc_idlist_del_channel(SilcChannelList **list, SilcChannelList *entry) +int silc_idlist_del_channel(SilcIDList id_list, SilcChannelEntry entry) { if (entry) { - if (entry->channel_name) - silc_free(entry->channel_name); - if (entry->id) - silc_free(entry->id); - if (entry->topic) - silc_free(entry->topic); - if (entry->channel_key) - silc_cipher_free(entry->channel_key); + /* Remove from cache */ + if (!silc_idcache_del_by_context(id_list->channels, entry, NULL)) { + SILC_LOG_DEBUG(("Unknown channel, did not delete")); + return FALSE; + } + + SILC_LOG_DEBUG(("Deleting channel %s", entry->channel_name)); + + /* Free all client entrys from the users list. The silc_hash_table_free + will free all the entries so they are not freed at the foreach + callback. */ + silc_hash_table_foreach(entry->user_list, silc_idlist_del_channel_foreach, + NULL); + silc_hash_table_free(entry->user_list); + + /* Free data */ + silc_free(entry->channel_name); + silc_free(entry->id); + silc_free(entry->topic); + + if (entry->invite_list) + silc_hash_table_free(entry->invite_list); + if (entry->ban_list) + silc_hash_table_free(entry->ban_list); + + if (entry->send_key) + silc_cipher_free(entry->send_key); + if (entry->receive_key) + silc_cipher_free(entry->receive_key); if (entry->key) { - memset(entry->key, 0, entry->key_len); + memset(entry->key, 0, entry->key_len / 8); silc_free(entry->key); } - memset(entry->iv, 0, sizeof(entry->iv)); + silc_free(entry->cipher); + if (entry->hmac) + silc_hmac_free(entry->hmac); + silc_free(entry->hmac_name); + silc_free(entry->rekey); + if (entry->founder_key) + silc_pkcs_public_key_free(entry->founder_key); + if (entry->channel_pubkeys) + silc_hash_table_free(entry->channel_pubkeys); + + memset(entry, 'F', sizeof(*entry)); + silc_free(entry); + return TRUE; + } - if (entry->user_list_count) - silc_free(entry->user_list); + return FALSE; +} - /* Last one in list? */ - if (*list == entry && entry->next == entry) { - *list = NULL; - silc_free(entry); - return; - } +/* Finds channel by channel name. Channel names are unique and they + are not case-sensitive. The 'name' must be normalized already. */ - /* At the start of list? */ - if (*list == entry && entry->next != entry) { - *list = entry->next; - entry->next->prev = entry->prev; - entry->prev->next = *list; - silc_free(entry); - return; - } +SilcChannelEntry +silc_idlist_find_channel_by_name(SilcIDList id_list, char *name, + SilcIDCacheEntry *ret_entry) +{ + SilcIDCacheEntry id_cache = NULL; - /* Remove from list */ - entry->prev->next = entry->next; - entry->next->prev = entry->prev; - silc_free(entry); - return; + SILC_LOG_DEBUG(("Channel by name %s", name)); + + if (!silc_idcache_find_by_name_one(id_list->channels, name, &id_cache)) + return NULL; + + if (ret_entry) + *ret_entry = id_cache; + + SILC_LOG_DEBUG(("Found")); + + /* Touch channel */ + ((SilcChannelEntry)id_cache->context)->updated = time(NULL); + + return id_cache->context; +} + +/* Finds channel by Channel ID. */ + +SilcChannelEntry +silc_idlist_find_channel_by_id(SilcIDList id_list, SilcChannelID *id, + SilcIDCacheEntry *ret_entry) +{ + SilcIDCacheEntry id_cache = NULL; + SilcChannelEntry channel; + + if (!id) + return NULL; + + SILC_LOG_DEBUG(("Channel ID (%s)", + silc_id_render(id, SILC_ID_CHANNEL))); + + if (!silc_idcache_find_by_id_one(id_list->channels, (void *)id, &id_cache)) + return NULL; + + channel = (SilcChannelEntry)id_cache->context; + + if (ret_entry) + *ret_entry = id_cache; + + SILC_LOG_DEBUG(("Found")); + + /* Touch channel */ + channel->updated = time(NULL); + + return channel; +} + +/* Replaces old Channel ID with new one. This is done when router forces + normal server to change Channel ID. */ + +SilcChannelEntry +silc_idlist_replace_channel_id(SilcIDList id_list, SilcChannelID *old_id, + SilcChannelID *new_id) +{ + SilcIDCacheEntry id_cache = NULL; + SilcChannelEntry channel; + char *name; + + if (!old_id || !new_id) + return NULL; + + SILC_LOG_DEBUG(("Replacing Channel ID")); + + if (!silc_idcache_find_by_id_one(id_list->channels, (void *)old_id, + &id_cache)) + return NULL; + + channel = (SilcChannelEntry)id_cache->context; + name = strdup(id_cache->name); + + /* Remove the old entry and add a new one */ + + silc_idcache_del_by_id(id_list->channels, (void *)channel->id, NULL); + *channel->id = *new_id; + silc_idcache_add(id_list->channels, name, channel->id, channel); + + SILC_LOG_DEBUG(("Replaced")); + + /* Touch channel */ + channel->updated = time(NULL); + + return channel; +} + +/* Returns channels from the ID list. If the `channel_id' is NULL then + all channels are returned. */ + +SilcChannelEntry * +silc_idlist_get_channels(SilcIDList id_list, SilcChannelID *channel_id, + SilcUInt32 *channels_count) +{ + SilcList list; + SilcIDCacheEntry id_cache = NULL; + SilcChannelEntry *channels = NULL; + int i = 0; + + SILC_LOG_DEBUG(("Start")); + + if (!channel_id) { + if (!silc_idcache_get_all(id_list->channels, &list)) + return NULL; + + channels = silc_calloc(silc_list_count(list), sizeof(*channels)); + + i = 0; + silc_list_start(list); + while ((id_cache = silc_list_get(list))) + channels[i++] = (SilcChannelEntry)id_cache->context; + } else { + if (!silc_idcache_find_by_id_one(id_list->channels, channel_id, &id_cache)) + return NULL; + + i = 1; + channels = silc_calloc(1, sizeof(*channels)); + channels[0] = (SilcChannelEntry)id_cache->context; } + + if (channels_count) + *channels_count = i; + + return channels; }