X-Git-Url: http://git.silcnet.org/gitweb/?p=silc.git;a=blobdiff_plain;f=lib%2Fsilcclient%2Fclient_entry.c;h=017f8f8a7aebed168f14779bff0149df5d0b944b;hp=0927606946509df606aba675f5e3590277ad870b;hb=HEAD;hpb=bc742fe07cda767cefc1e541c2794e897044a395 diff --git a/lib/silcclient/client_entry.c b/lib/silcclient/client_entry.c index 09276069..017f8f8a 100644 --- a/lib/silcclient/client_entry.c +++ b/lib/silcclient/client_entry.c @@ -4,7 +4,7 @@ Author: Pekka Riikonen - Copyright (C) 2001 - 2006 Pekka Riikonen + Copyright (C) 2001 - 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 @@ -62,31 +62,46 @@ SilcClientEntry silc_client_get_client_by_id(SilcClient client, /* Finds clients by nickname from local cache. */ -SilcDList silc_client_get_clients_local(SilcClient client, - SilcClientConnection conn, - const char *nickname, - const char *format) +SilcDList silc_client_get_clients_local_ext(SilcClient client, + SilcClientConnection conn, + const char *nickname, + SilcBool get_all, + SilcBool get_valid) { SilcIDCacheEntry id_cache; SilcList list; SilcDList clients; SilcClientEntry entry; - char *nicknamec; + char nick[128 + 1], *nicknamec, *parsed = NULL, *format = NULL; + char server[256 + 1]; if (!client || !conn || !nickname) return NULL; - SILC_LOG_DEBUG(("Find clients by nickname %s", nickname)); + /* Get nickname from nickname@server string */ + silc_parse_userfqdn(nickname, nick, sizeof(nick), server, sizeof(server)); + + /* Parse nickname in case it is formatted */ + if (!silc_client_nickname_parse(client, conn, (char *)nick, &parsed)) + return NULL; + + if (!get_all) + format = (char *)nick; + + SILC_LOG_DEBUG(("Find clients by nickname %s", parsed)); /* Normalize nickname for search */ - nicknamec = silc_identifier_check(nickname, strlen(nickname), + nicknamec = silc_identifier_check(parsed, strlen(parsed), SILC_STRING_UTF8, 128, NULL); - if (!nicknamec) + if (!nicknamec) { + silc_free(parsed); return NULL; + } clients = silc_dlist_init(); if (!clients) { silc_free(nicknamec); + silc_free(parsed); return NULL; } @@ -98,37 +113,74 @@ SilcDList silc_client_get_clients_local(SilcClient client, &list)) { silc_mutex_unlock(conn->internal->lock); silc_free(nicknamec); + silc_free(parsed); silc_dlist_uninit(clients); return NULL; } + silc_list_start(list); - if (!format) { + if (get_all) { /* Take all without any further checking */ - silc_list_start(list); while ((id_cache = silc_list_get(list))) { - silc_client_ref_client(client, conn, id_cache->context); - silc_dlist_add(clients, id_cache->context); + entry = id_cache->context; + if (!get_valid || entry->internal.valid) { + silc_client_ref_client(client, conn, id_cache->context); + silc_dlist_add(clients, id_cache->context); + } } } else { /* Check multiple cache entries for exact match */ - silc_list_start(list); while ((id_cache = silc_list_get(list))) { entry = id_cache->context; - if (silc_utf8_strcasecmp(entry->nickname, format)) { + + /* If server was provided, find entries that either have no server + set or have the same server. Ignore those that have different + server. */ + if (server[0] && entry->server && + !silc_utf8_strcasecmp(entry->server, server)) + continue; + + if (silc_utf8_strcasecmp(entry->nickname, + format ? format : parsed) && + (!get_valid || entry->internal.valid)) { silc_client_ref_client(client, conn, entry); silc_dlist_add(clients, entry); + + /* If format is NULL, we find one exact match with the base + nickname (parsed). */ + if (!format) + break; } } } silc_mutex_unlock(conn->internal->lock); - silc_dlist_start(clients); - silc_free(nicknamec); + silc_free(parsed); + + if (!silc_dlist_count(clients)) { + silc_dlist_uninit(clients); + return NULL; + } + + SILC_LOG_DEBUG(("Found %d clients", silc_dlist_count(clients))); + + silc_dlist_start(clients); return clients; } +/* Finds clients by nickname from local cache. */ + +SilcDList silc_client_get_clients_local(SilcClient client, + SilcClientConnection conn, + const char *nickname, + SilcBool return_all) +{ + return silc_client_get_clients_local_ext(client, conn, nickname, return_all, + TRUE); +} + /********************** Client Resolving from Server ************************/ /* Resolving context */ @@ -136,6 +188,7 @@ typedef struct { SilcDList clients; SilcGetClientCallback completion; void *context; + SilcClientEntry client_entry; } *SilcClientGetClientInternal; /* Resolving command callback */ @@ -153,6 +206,12 @@ static SilcBool silc_client_get_clients_cb(SilcClient client, if (error != SILC_STATUS_OK) { SILC_LOG_DEBUG(("Resolving failed: %s", silc_get_status_message(error))); + + if (i->client_entry) { + i->client_entry->internal.resolve_cmd_ident = 0; + silc_client_unref_client(client, conn, i->client_entry); + } + if (i->completion) i->completion(client, conn, error, NULL, i->context); goto out; @@ -170,6 +229,12 @@ static SilcBool silc_client_get_clients_cb(SilcClient client, /* Deliver the clients to the caller */ if (i->completion) { SILC_LOG_DEBUG(("Resolved %d clients", silc_dlist_count(i->clients))); + + if (i->client_entry) { + i->client_entry->internal.resolve_cmd_ident = 0; + silc_client_unref_client(client, conn, i->client_entry); + } + silc_dlist_start(i->clients); i->completion(client, conn, SILC_STATUS_OK, i->clients, i->context); } @@ -199,8 +264,11 @@ silc_client_get_client_by_id_resolve(SilcClient client, SilcBuffer idp; SilcUInt16 cmd_ident; - if (!client || !conn | !client_id) + if (!client || !conn | !client_id) { + SILC_LOG_ERROR(("Missing arguments to " + "silc_client_get_clients_by_id_resolve call")); return 0; + } SILC_LOG_DEBUG(("Resolve client by ID (%s)", silc_id_render(client_id, SILC_ID_CLIENT))); @@ -236,10 +304,13 @@ silc_client_get_client_by_id_resolve(SilcClient client, if (!cmd_ident && completion) completion(client, conn, SILC_STATUS_ERR_RESOURCE_LIMIT, NULL, context); - if (client_entry && cmd_ident) + if (client_entry && cmd_ident) { client_entry->internal.resolve_cmd_ident = cmd_ident; + i->client_entry = client_entry; + } else { + silc_client_unref_client(client, conn, client_entry); + } - silc_client_unref_client(client, conn, client_entry); silc_buffer_free(idp); return cmd_ident; @@ -259,22 +330,39 @@ static SilcUInt16 silc_client_get_clients_i(SilcClient client, void *context) { SilcClientGetClientInternal i; - char userhost[768 + 1]; + char nick[128 + 1], serv[256 + 1], userhost[768 + 1], *parsed = NULL; int len; SILC_LOG_DEBUG(("Resolve client by %s command", silc_get_command_name(command))); - if (!client || !conn) + if (!client || !conn) { + SILC_LOG_ERROR(("Missing arguments to silc_client_get_clients call")); return 0; - if (!nickname && !attributes) + } + if (!nickname && !attributes) { + SILC_LOG_ERROR(("Missing arguments to silc_client_get_clients call")); return 0; + } + + /* Parse server name from the nickname if set */ + if (silc_parse_userfqdn(nickname, nick, sizeof(nick), + serv, sizeof(serv)) == 2) + server = (const char *)serv; + nickname = (const char *)nick; + + /* Parse nickname in case it is formatted */ + if (silc_client_nickname_parse(client, conn, (char *)nickname, &parsed)) + nickname = (const char *)parsed; i = silc_calloc(1, sizeof(*i)); - if (!i) + if (!i) { + silc_free(parsed); return 0; + } i->clients = silc_dlist_init(); if (!i->clients) { + silc_free(parsed); silc_free(i); return 0; } @@ -290,6 +378,7 @@ static SilcUInt16 silc_client_get_clients_i(SilcClient client, } else if (nickname) { silc_strncat(userhost, sizeof(userhost) - 1, nickname, strlen(nickname)); } + silc_free(parsed); /* Send the command */ if (command == SILC_COMMAND_IDENTIFY) @@ -686,7 +775,7 @@ SilcClientEntry silc_client_add_client(SilcClient client, SilcUInt32 mode) { SilcClientEntry client_entry; - char *nick = NULL; + char *nick = NULL, parsed[128 + 1]; SILC_LOG_DEBUG(("Adding new client entry")); @@ -695,21 +784,29 @@ SilcClientEntry silc_client_add_client(SilcClient client, if (!client_entry) return NULL; - silc_atomic_init8(&client_entry->internal.refcnt, 0); + silc_rwlock_alloc(&client_entry->internal.lock); + silc_atomic_init32(&client_entry->internal.refcnt, 0); + silc_atomic_init32(&client_entry->internal.deleted, 1); client_entry->id = *id; - client_entry->internal.valid = TRUE; client_entry->mode = mode; client_entry->realname = userinfo ? strdup(userinfo) : NULL; - silc_parse_userfqdn(nickname, client_entry->nickname, - sizeof(client_entry->nickname), - client_entry->server, - sizeof(client_entry->server)); + + silc_parse_userfqdn(nickname, parsed, sizeof(parsed), + client_entry->server, sizeof(client_entry->server)); + if (nickname && client->internal->params->full_nicknames) + silc_snprintf(client_entry->nickname, sizeof(client_entry->nickname), + nickname); + else if (nickname) + silc_snprintf(client_entry->nickname, sizeof(client_entry->nickname), + parsed); + silc_parse_userfqdn(username, client_entry->username, sizeof(client_entry->username), client_entry->hostname, sizeof(client_entry->hostname)); - client_entry->channels = silc_hash_table_alloc(1, silc_hash_ptr, NULL, NULL, - NULL, NULL, NULL, TRUE); + + client_entry->channels = silc_hash_table_alloc(NULL, 1, silc_hash_ptr, NULL, + NULL, NULL, NULL, NULL, TRUE); if (!client_entry->channels) { silc_free(client_entry->realname); silc_free(client_entry); @@ -718,8 +815,7 @@ SilcClientEntry silc_client_add_client(SilcClient client, /* Normalize nickname */ if (client_entry->nickname[0]) { - nick = silc_identifier_check(client_entry->nickname, - strlen(client_entry->nickname), + nick = silc_identifier_check(parsed, strlen(parsed), SILC_STRING_UTF8, 128, NULL); if (!nick) { silc_free(client_entry->realname); @@ -729,9 +825,6 @@ SilcClientEntry silc_client_add_client(SilcClient client, } } - /* Format the nickname */ - silc_client_nickname_format(client, conn, client_entry); - silc_mutex_lock(conn->internal->lock); /* Add client to cache, the normalized nickname is saved to cache */ @@ -750,12 +843,19 @@ SilcClientEntry silc_client_add_client(SilcClient client, silc_mutex_unlock(conn->internal->lock); silc_client_ref_client(client, conn, client_entry); + /* Format the nickname */ + silc_client_nickname_format(client, conn, client_entry, FALSE); + + if (client_entry->nickname[0]) + client_entry->internal.valid = TRUE; + SILC_LOG_DEBUG(("Added %p", client_entry)); return client_entry; } -/* Updates the `client_entry' with the new information sent as argument. */ +/* Updates the `client_entry' with the new information sent as argument. + This handles entry locking internally. */ void silc_client_update_client(SilcClient client, SilcClientConnection conn, @@ -765,32 +865,42 @@ void silc_client_update_client(SilcClient client, const char *userinfo, SilcUInt32 mode) { - char *nick = NULL; + char *nick = NULL, parsed[128 + 1]; SILC_LOG_DEBUG(("Update client entry")); + silc_rwlock_wrlock(client_entry->internal.lock); + if (!client_entry->realname && userinfo) client_entry->realname = strdup(userinfo); + if ((!client_entry->username[0] || !client_entry->hostname[0]) && username) silc_parse_userfqdn(username, client_entry->username, sizeof(client_entry->username), client_entry->hostname, sizeof(client_entry->username)); + if (!client_entry->nickname[0] && nickname) { - silc_parse_userfqdn(nickname, client_entry->nickname, - sizeof(client_entry->nickname), - client_entry->server, - sizeof(client_entry->server)); + silc_parse_userfqdn(nickname, parsed, sizeof(parsed), + client_entry->server, sizeof(client_entry->server)); + if (client->internal->params->full_nicknames) + silc_snprintf(client_entry->nickname, sizeof(client_entry->nickname), + nickname); + else + silc_snprintf(client_entry->nickname, sizeof(client_entry->nickname), + parsed); /* Normalize nickname */ - nick = silc_identifier_check(client_entry->nickname, - strlen(client_entry->nickname), + nick = silc_identifier_check(parsed, strlen(parsed), SILC_STRING_UTF8, 128, NULL); - if (!nick) + if (!nick) { + silc_rwlock_unlock(client_entry->internal.lock); return; + } /* Format nickname */ - silc_client_nickname_format(client, conn, client_entry); + silc_client_nickname_format(client, conn, client_entry, + client_entry == conn->local_entry); /* Update cache entry */ silc_mutex_lock(conn->internal->lock); @@ -798,11 +908,14 @@ void silc_client_update_client(SilcClient client, client_entry, NULL, nick, TRUE); silc_mutex_unlock(conn->internal->lock); client_entry->nickname_normalized = nick; + client_entry->internal.valid = TRUE; } client_entry->mode = mode; + + silc_rwlock_unlock(client_entry->internal.lock); } -/* Change a client's nickname */ +/* Change a client's nickname. Must be called with `client_entry' locked. */ SilcBool silc_client_change_nickname(SilcClient client, SilcClientConnection conn, @@ -836,7 +949,8 @@ SilcBool silc_client_change_nickname(SilcClient client, memset(client_entry->nickname, 0, sizeof(client_entry->nickname)); memcpy(client_entry->nickname, new_nick, strlen(new_nick)); client_entry->nickname_normalized = tmp; - silc_client_nickname_format(client, conn, client_entry); + silc_client_nickname_format(client, conn, client_entry, + client_entry == conn->local_entry); /* For my client entry, update ID and set new ID to packet stream */ if (client_entry == conn->local_entry) { @@ -849,6 +963,7 @@ SilcBool silc_client_change_nickname(SilcClient client, 0, NULL); } + client_entry->internal.valid = TRUE; return TRUE; } @@ -872,12 +987,12 @@ void silc_client_del_client_entry(SilcClient client, silc_hmac_free(client_entry->internal.hmac_send); if (client_entry->internal.hmac_receive) silc_hmac_free(client_entry->internal.hmac_receive); -#if 0 - silc_client_ftp_session_free_client(conn, client_entry); - if (client_entry->internal->ke) + silc_client_ftp_session_free_client(client, client_entry); + if (client_entry->internal.ke) silc_client_abort_key_agreement(client, conn, client_entry); -#endif /* 0 */ - silc_atomic_uninit8(&client_entry->internal.refcnt); + silc_atomic_uninit32(&client_entry->internal.deleted); + silc_atomic_uninit32(&client_entry->internal.refcnt); + silc_rwlock_free(client_entry->internal.lock); silc_free(client_entry); } @@ -886,41 +1001,66 @@ void silc_client_del_client_entry(SilcClient client, SilcBool silc_client_del_client(SilcClient client, SilcClientConnection conn, SilcClientEntry client_entry) { - SilcBool ret; - if (!client_entry) return FALSE; - if (silc_atomic_sub_int8(&client_entry->internal.refcnt, 1) > 0) + SILC_LOG_DEBUG(("Marking client entry %p deleted")); + + if (silc_atomic_sub_int32(&client_entry->internal.deleted, 1) != 0) { + SILC_LOG_DEBUG(("Client entry %p already marked deleted")); return FALSE; + } - SILC_LOG_DEBUG(("Deleting client %p", client_entry)); + silc_client_unref_client(client, conn, client_entry); + return TRUE; +} - silc_mutex_lock(conn->internal->lock); - ret = silc_idcache_del_by_context(conn->internal->client_cache, - client_entry, NULL); - silc_mutex_unlock(conn->internal->lock); +/* Internal routine used to find client by ID and if not found this creates + new client entry and returns it. */ - if (ret) { - /* Remove from channels */ - silc_client_remove_from_channels(client, conn, client_entry); +SilcClientEntry silc_client_get_client(SilcClient client, + SilcClientConnection conn, + SilcClientID *client_id) +{ + SilcClientEntry client_entry; - /* Free the client entry data */ - silc_client_del_client_entry(client, conn, client_entry); + client_entry = silc_client_get_client_by_id(client, conn, client_id); + if (!client_entry) { + client_entry = silc_client_add_client(client, conn, NULL, NULL, NULL, + client_id, 0); + if (!client_entry) + return NULL; + silc_client_ref_client(client, conn, client_entry); } - return ret; + return client_entry; +} + +/* Lock client */ + +void silc_client_lock_client(SilcClientEntry client_entry) +{ + silc_rwlock_rdlock(client_entry->internal.lock); +} + +/* Unlock client */ + +void silc_client_unlock_client(SilcClientEntry client_entry) +{ + silc_rwlock_unlock(client_entry->internal.lock); } /* Take reference of client entry */ -void silc_client_ref_client(SilcClient client, SilcClientConnection conn, - SilcClientEntry client_entry) +SilcClientEntry silc_client_ref_client(SilcClient client, + SilcClientConnection conn, + SilcClientEntry client_entry) { - silc_atomic_add_int8(&client_entry->internal.refcnt, 1); + silc_atomic_add_int32(&client_entry->internal.refcnt, 1); SILC_LOG_DEBUG(("Client %p refcnt %d->%d", client_entry, - silc_atomic_get_int8(&client_entry->internal.refcnt) - 1, - silc_atomic_get_int8(&client_entry->internal.refcnt))); + silc_atomic_get_int32(&client_entry->internal.refcnt) - 1, + silc_atomic_get_int32(&client_entry->internal.refcnt))); + return client_entry; } /* Release reference of client entry */ @@ -928,11 +1068,32 @@ void silc_client_ref_client(SilcClient client, SilcClientConnection conn, void silc_client_unref_client(SilcClient client, SilcClientConnection conn, SilcClientEntry client_entry) { - if (client_entry) { - SILC_LOG_DEBUG(("Client %p refcnt %d->%d", client_entry, - silc_atomic_get_int8(&client_entry->internal.refcnt), - silc_atomic_get_int8(&client_entry->internal.refcnt) - 1)); - silc_client_del_client(client, conn, client_entry); + SilcBool ret; + + if (!client_entry) + return; + + SILC_LOG_DEBUG(("Client %p refcnt %d->%d", client_entry, + silc_atomic_get_int32(&client_entry->internal.refcnt), + silc_atomic_get_int32(&client_entry->internal.refcnt) - 1)); + + if (silc_atomic_sub_int32(&client_entry->internal.refcnt, 1) > 0) + return; + + SILC_LOG_DEBUG(("Deleting client %p (%d)", client_entry, + silc_atomic_get_int32(&client_entry->internal.deleted))); + + silc_mutex_lock(conn->internal->lock); + ret = silc_idcache_del_by_context(conn->internal->client_cache, + client_entry, NULL); + silc_mutex_unlock(conn->internal->lock); + + if (ret) { + /* Remove from channels */ + silc_client_remove_from_channels(client, conn, client_entry); + + /* Free the client entry data */ + silc_client_del_client_entry(client, conn, client_entry); } } @@ -955,58 +1116,101 @@ void silc_client_list_free(SilcClient client, SilcClientConnection conn, /* Formats the nickname of the client specified by the `client_entry'. If the format is specified by the application this will format the nickname and replace the old nickname in the client entry. If the - format string is not specified then this function has no effect. */ + format string is not specified then this function has no effect. + Returns the client entry that was formatted. */ -void silc_client_nickname_format(SilcClient client, - SilcClientConnection conn, - SilcClientEntry client_entry) +SilcClientEntry silc_client_nickname_format(SilcClient client, + SilcClientConnection conn, + SilcClientEntry client_entry, + SilcBool priority) { char *cp; char newnick[128 + 1]; int i, off = 0, len; - SilcBool freebase; SilcDList clients; SilcClientEntry entry, unformatted = NULL; - - SILC_LOG_DEBUG(("Start")); + SilcBool formatted = FALSE; if (!client->internal->params->nickname_format[0]) - return; - + return client_entry; if (!client_entry->nickname[0]) - return; + return NULL; + + SILC_LOG_DEBUG(("Format nickname")); /* Get all clients with same nickname. Do not perform the formatting if there aren't any clients with same nickname unless the application is forcing us to do so. */ - clients = silc_client_get_clients_local(client, conn, - client_entry->nickname, NULL); - if (!clients && !client->internal->params->nickname_force_format) - return; + clients = silc_client_get_clients_local_ext(client, conn, + client_entry->nickname, + TRUE, FALSE); + if (!clients) + return NULL; + if (silc_dlist_count(clients) == 1 && !priority && + !client->internal->params->nickname_force_format) { + silc_client_list_free(client, conn, clients); + return client_entry; + } - if (clients) { - len = 0; - freebase = TRUE; - while ((entry = silc_dlist_get(clients))) { - if (entry->internal.valid && entry != client_entry) - len++; - if (entry->internal.valid && entry != client_entry && - silc_utf8_strcasecmp(entry->nickname, client_entry->nickname)) { - freebase = FALSE; - unformatted = entry; - } + /* Is the requested client formatted already */ + if (client_entry->nickname_normalized && + !silc_utf8_strcasecmp(client_entry->nickname, + client_entry->nickname_normalized)) + formatted = TRUE; + + if (client->internal->params->nickname_force_format) + formatted = FALSE; + + /* Find unformatted client entry */ + while ((entry = silc_dlist_get(clients))) { + if (!entry->internal.valid) + continue; + if (entry == client_entry) + continue; + if (silc_utf8_strcasecmp(entry->nickname, entry->nickname_normalized)) { + unformatted = entry; + break; + } + } + + /* If there are no other unformatted clients and the requested client is + unformatted, just return it. */ + if (!unformatted && !formatted) { + silc_client_list_free(client, conn, clients); + return client_entry; + } + + /* If priority formatting then the requested client will get the + unformatted nickname, and the unformatted client will get a new + formatted nickname. */ + if (priority) { + if (formatted) { + /* Simply change the client's nickname to unformatted */ + if (!silc_client_nickname_parse(client, conn, client_entry->nickname, + &cp)) + return NULL; + + silc_snprintf(client_entry->nickname, sizeof(client_entry->nickname), + cp); + silc_free(cp); } - if (!len || freebase) { + + if (!unformatted) { + /* There was no other unformatted client */ silc_client_list_free(client, conn, clients); - return; + return client_entry; } - } - /* If we are changing nickname of our local entry we'll enforce - that we will always get the unformatted nickname. Give our - format number to the one that is not formatted now. */ - if (unformatted && client_entry == conn->local_entry) + /* Now format the previously unformatted client */ client_entry = unformatted; + formatted = FALSE; + } + + /* If already formatted just return it */ + if (formatted) { + silc_client_list_free(client, conn, clients); + return client_entry; + } memset(newnick, 0, sizeof(newnick)); cp = client->internal->params->nickname_format; @@ -1044,46 +1248,28 @@ void silc_client_nickname_format(SilcClient client, memcpy(&newnick[off], client_entry->hostname, len); off += len; break; - case 's': - /* Stripped server name */ - if (!client_entry->server) - break; - len = strcspn(client_entry->server, "."); - memcpy(&newnick[off], client_entry->server, len); - off += len; - break; - case 'S': - /* Full server name */ - if (!client_entry->server) - break; - len = strlen(client_entry->server); - memcpy(&newnick[off], client_entry->server, len); - off += len; - break; case 'a': /* Ascending number */ { char tmp[6]; int num, max = 1; - if (clients && silc_dlist_count(clients) == 1) + if (silc_dlist_count(clients) == 1) break; - if (clients) { - silc_dlist_start(clients); - while ((entry = silc_dlist_get(clients))) { - if (!silc_utf8_strncasecmp(entry->nickname, newnick, off)) - continue; - if (strlen(entry->nickname) <= off) - continue; - num = atoi(&entry->nickname[off]); - if (num > max) - max = num; - } + silc_dlist_start(clients); + while ((entry = silc_dlist_get(clients))) { + if (!silc_utf8_strncasecmp(entry->nickname, newnick, off)) + continue; + if (strlen(entry->nickname) <= off) + continue; + num = atoi(&entry->nickname[off]); + if (num > max) + max = num; } memset(tmp, 0, sizeof(tmp)); - snprintf(tmp, sizeof(tmp) - 1, "%d", ++max); + silc_snprintf(tmp, sizeof(tmp) - 1, "%d", ++max); len = strlen(tmp); memcpy(&newnick[off], tmp, len); off += len; @@ -1100,8 +1286,82 @@ void silc_client_nickname_format(SilcClient client, } newnick[off] = 0; + memset(client_entry->nickname, 0, sizeof(client_entry->nickname)); memcpy(client_entry->nickname, newnick, strlen(newnick)); silc_client_list_free(client, conn, clients); + + return client_entry; +} + +/* Parses nickname according to nickname format string */ + +SilcBool silc_client_nickname_parse(SilcClient client, + SilcClientConnection conn, + char *nickname, + char **ret_nick) +{ + char *cp, s = 0, e = 0, *nick; + SilcBool n = FALSE; + int len; + + if (!client->internal->params->nickname_format[0]) { + *ret_nick = silc_strdup(nickname); + return TRUE; + } + + if (!nickname || !nickname[0]) + return FALSE; + + cp = client->internal->params->nickname_format; + while (cp && *cp) { + if (*cp == '%') { + cp++; + continue; + } + + switch(*cp) { + case 'n': + n = TRUE; + break; + + case 'h': + case 'H': + case 'a': + break; + + default: + /* Get separator character */ + if (n) + e = *cp; + else + s = *cp; + break; + } + + cp++; + } + if (!n) + return FALSE; + + /* Parse the nickname */ + nick = nickname; + len = strlen(nick); + if (s) + if (strchr(nickname, s)) + nick = strchr(nickname, s) + 1; + if (e) + if (strchr(nick, e)) + len = strchr(nick, e) - nick; + if (!len) + return FALSE; + + *ret_nick = silc_memdup(nick, len); + if (!(*ret_nick)) + return FALSE; + + SILC_LOG_DEBUG(("Parsed nickname: %s", *ret_nick)); + + return TRUE; } /************************ Channel Searching Locally *************************/ @@ -1114,32 +1374,66 @@ SilcChannelEntry silc_client_get_channel(SilcClient client, SilcClientConnection conn, char *channel) { + SilcList list; SilcIDCacheEntry id_cache; - SilcChannelEntry entry; + SilcChannelEntry entry = NULL; + char chname[256 + 1], server[256 + 1]; if (!client || !conn || !channel) return NULL; SILC_LOG_DEBUG(("Find channel %s", channel)); + /* Parse server name from channel name */ + silc_parse_userfqdn(channel, chname, sizeof(chname), server, sizeof(server)); + /* Normalize name for search */ - channel = silc_channel_name_check(channel, strlen(channel), SILC_STRING_UTF8, + channel = silc_channel_name_check(chname, strlen(chname), SILC_STRING_UTF8, 256, NULL); if (!channel) return NULL; silc_mutex_lock(conn->internal->lock); - if (!silc_idcache_find_by_name_one(conn->internal->channel_cache, channel, - &id_cache)) { + if (!silc_idcache_find_by_name(conn->internal->channel_cache, channel, + &list)) { silc_mutex_unlock(conn->internal->lock); silc_free(channel); return NULL; } - SILC_LOG_DEBUG(("Found")); + /* If server name was specified with channel name, find the correct + channel entry with the server name. There can only be one channel + with same name on same server. */ + silc_list_start(list); + if (server[0]) { + while ((id_cache = silc_list_get(list))) { + entry = id_cache->context; + if (!entry->server[0]) + continue; + if (silc_utf8_strcasecmp(entry->server, server)) + break; + } + } else { + /* Get first channel without server name specified or one with our + current server connection name */ + while ((id_cache = silc_list_get(list))) { + entry = id_cache->context; + if (!entry->server[0]) + break; + if (silc_utf8_strcasecmp(entry->server, conn->remote_host)) + break; + } + } - entry = id_cache->context; + if (!id_cache) { + silc_mutex_unlock(conn->internal->lock); + silc_free(channel); + return NULL; + } + + SILC_LOG_DEBUG(("Found channel %s%s%s", entry->channel_name, + entry->server[0] ? "@" : "", entry->server)); /* Reference */ silc_client_ref_channel(client, conn, entry); @@ -1328,36 +1622,50 @@ SilcChannelEntry silc_client_add_channel(SilcClient client, SilcChannelID *channel_id) { SilcChannelEntry channel; - char *channel_namec; + char *channel_namec, name[256 + 1]; - SILC_LOG_DEBUG(("Start")); + SILC_LOG_DEBUG(("Adding channel %s", channel_name)); channel = silc_calloc(1, sizeof(*channel)); if (!channel) return NULL; - silc_atomic_init16(&channel->internal.refcnt, 0); + silc_rwlock_alloc(&channel->internal.lock); + silc_atomic_init32(&channel->internal.refcnt, 0); + silc_atomic_init32(&channel->internal.deleted, 1); channel->id = *channel_id; channel->mode = mode; - channel->channel_name = strdup(channel_name); + silc_parse_userfqdn(channel_name, name, sizeof(name), + channel->server, sizeof(channel->server)); + if (client->internal->params->full_channel_names) + channel->channel_name = strdup(channel_name); + else + channel->channel_name = strdup(name); + if (!channel->channel_name) { + silc_rwlock_free(channel->internal.lock); + silc_atomic_uninit32(&channel->internal.refcnt); silc_free(channel); return NULL; } - channel->user_list = silc_hash_table_alloc(1, silc_hash_ptr, NULL, NULL, + channel->user_list = silc_hash_table_alloc(NULL, 1, silc_hash_ptr, NULL, NULL, NULL, NULL, NULL, TRUE); if (!channel->user_list) { + silc_rwlock_free(channel->internal.lock); + silc_atomic_uninit32(&channel->internal.refcnt); silc_free(channel->channel_name); silc_free(channel); return NULL; } /* Normalize channel name */ - channel_namec = silc_channel_name_check(channel_name, strlen(channel_name), + channel_namec = silc_channel_name_check(name, strlen(name), SILC_STRING_UTF8, 256, NULL); if (!channel_namec) { + silc_rwlock_free(channel->internal.lock); + silc_atomic_uninit32(&channel->internal.refcnt); silc_free(channel->channel_name); silc_hash_table_free(channel->user_list); silc_free(channel); @@ -1369,6 +1677,8 @@ SilcChannelEntry silc_client_add_channel(SilcClient client, /* Add channel to cache, the normalized channel name is saved to cache */ if (!silc_idcache_add(conn->internal->channel_cache, channel_namec, &channel->id, channel)) { + silc_rwlock_free(channel->internal.lock); + silc_atomic_uninit32(&channel->internal.refcnt); silc_free(channel_namec); silc_free(channel->channel_name); silc_hash_table_free(channel->user_list); @@ -1390,61 +1700,25 @@ SilcChannelEntry silc_client_add_channel(SilcClient client, SilcBool silc_client_del_channel(SilcClient client, SilcClientConnection conn, SilcChannelEntry channel) { - SilcBool ret; - SilcCipher key; - SilcHmac hmac; if (!channel) return FALSE; - if (silc_atomic_sub_int16(&channel->internal.refcnt, 1) > 0) - return FALSE; - - SILC_LOG_DEBUG(("Deleting channel %p", channel)); + SILC_LOG_DEBUG(("Marking channel entry %p deleted")); - silc_mutex_lock(conn->internal->lock); - ret = silc_idcache_del_by_context(conn->internal->channel_cache, - channel, NULL); - silc_mutex_unlock(conn->internal->lock); - if (!ret) + if (silc_atomic_sub_int32(&channel->internal.deleted, 1) != 0) { + SILC_LOG_DEBUG(("Channel entry %p already marked deleted")); return FALSE; - - silc_client_empty_channel(client, conn, channel); - silc_hash_table_free(channel->user_list); - silc_free(channel->channel_name); - silc_free(channel->topic); - if (channel->founder_key) - silc_pkcs_public_key_free(channel->founder_key); - if (channel->internal.channel_key) - silc_cipher_free(channel->internal.channel_key); - if (channel->internal.hmac) - silc_hmac_free(channel->internal.hmac); - if (channel->internal.old_channel_keys) { - silc_dlist_start(channel->internal.old_channel_keys); - while ((key = silc_dlist_get(channel->internal.old_channel_keys))) - silc_cipher_free(key); - silc_dlist_uninit(channel->internal.old_channel_keys); - } - if (channel->internal.old_hmacs) { - silc_dlist_start(channel->internal.old_hmacs); - while ((hmac = silc_dlist_get(channel->internal.old_hmacs))) - silc_hmac_free(hmac); - silc_dlist_uninit(channel->internal.old_hmacs); - } - if (channel->channel_pubkeys) - silc_argument_list_free(channel->channel_pubkeys, - SILC_ARGUMENT_PUBLIC_KEY); silc_client_del_channel_private_keys(client, conn, channel); - silc_atomic_uninit16(&channel->internal.refcnt); - silc_schedule_task_del_by_context(conn->client->schedule, channel); - silc_free(channel); + } - return ret; + silc_client_unref_channel(client, conn, channel); + return TRUE; } /* Replaces the channel ID of the `channel' to `new_id'. Returns FALSE - if the ID could not be changed. */ + if the ID could not be changed. This handles entry locking internally. */ SilcBool silc_client_replace_channel_id(SilcClient client, SilcClientConnection conn, @@ -1462,23 +1736,41 @@ SilcBool silc_client_replace_channel_id(SilcClient client, silc_id_render(new_id, SILC_ID_CHANNEL))); /* Update the ID */ + silc_rwlock_wrlock(channel->internal.lock); silc_mutex_lock(conn->internal->lock); silc_idcache_update_by_context(conn->internal->channel_cache, channel, new_id, NULL, FALSE); silc_mutex_unlock(conn->internal->lock); + silc_rwlock_unlock(channel->internal.lock); return ret; } +/* Lock channel */ + +void silc_client_lock_channel(SilcChannelEntry channel_entry) +{ + silc_rwlock_rdlock(channel_entry->internal.lock); +} + +/* Unlock channel */ + +void silc_client_unlock_channel(SilcChannelEntry channel_entry) +{ + silc_rwlock_unlock(channel_entry->internal.lock); +} + /* Take reference of channel entry */ -void silc_client_ref_channel(SilcClient client, SilcClientConnection conn, - SilcChannelEntry channel_entry) +SilcChannelEntry silc_client_ref_channel(SilcClient client, + SilcClientConnection conn, + SilcChannelEntry channel_entry) { - silc_atomic_add_int16(&channel_entry->internal.refcnt, 1); + silc_atomic_add_int32(&channel_entry->internal.refcnt, 1); SILC_LOG_DEBUG(("Channel %p refcnt %d->%d", channel_entry, - silc_atomic_get_int16(&channel_entry->internal.refcnt) - 1, - silc_atomic_get_int16(&channel_entry->internal.refcnt))); + silc_atomic_get_int32(&channel_entry->internal.refcnt) - 1, + silc_atomic_get_int32(&channel_entry->internal.refcnt))); + return channel_entry; } /* Release reference of channel entry */ @@ -1486,13 +1778,71 @@ void silc_client_ref_channel(SilcClient client, SilcClientConnection conn, void silc_client_unref_channel(SilcClient client, SilcClientConnection conn, SilcChannelEntry channel_entry) { - if (channel_entry) { - SILC_LOG_DEBUG(("Channel %p refcnt %d->%d", channel_entry, - silc_atomic_get_int16(&channel_entry->internal.refcnt), - silc_atomic_get_int16(&channel_entry->internal.refcnt) - - 1)); - silc_client_del_channel(client, conn, channel_entry); + SilcIDCacheEntry id_cache; + SilcBool ret = TRUE; + SilcCipher key; + SilcHmac hmac; + char *namec; + + if (!channel_entry) + return; + + SILC_LOG_DEBUG(("Channel %p refcnt %d->%d", channel_entry, + silc_atomic_get_int32(&channel_entry->internal.refcnt), + silc_atomic_get_int32(&channel_entry->internal.refcnt) + - 1)); + + if (silc_atomic_sub_int32(&channel_entry->internal.refcnt, 1) > 0) + return; + + SILC_LOG_DEBUG(("Deleting channel %p", channel_entry)); + + silc_mutex_lock(conn->internal->lock); + if (silc_idcache_find_by_context(conn->internal->channel_cache, channel_entry, + &id_cache)) { + namec = id_cache->name; + ret = silc_idcache_del_by_context(conn->internal->channel_cache, + channel_entry, NULL); + silc_free(namec); + } + silc_mutex_unlock(conn->internal->lock); + + if (!ret) + return; + + silc_client_empty_channel(client, conn, channel_entry); + silc_client_del_channel_private_keys(client, conn, channel_entry); + silc_hash_table_free(channel_entry->user_list); + silc_free(channel_entry->channel_name); + silc_free(channel_entry->topic); + if (channel_entry->founder_key) + silc_pkcs_public_key_free(channel_entry->founder_key); + if (channel_entry->internal.send_key) + silc_cipher_free(channel_entry->internal.send_key); + if (channel_entry->internal.receive_key) + silc_cipher_free(channel_entry->internal.receive_key); + if (channel_entry->internal.hmac) + silc_hmac_free(channel_entry->internal.hmac); + if (channel_entry->internal.old_channel_keys) { + silc_dlist_start(channel_entry->internal.old_channel_keys); + while ((key = silc_dlist_get(channel_entry->internal.old_channel_keys))) + silc_cipher_free(key); + silc_dlist_uninit(channel_entry->internal.old_channel_keys); + } + if (channel_entry->internal.old_hmacs) { + silc_dlist_start(channel_entry->internal.old_hmacs); + while ((hmac = silc_dlist_get(channel_entry->internal.old_hmacs))) + silc_hmac_free(hmac); + silc_dlist_uninit(channel_entry->internal.old_hmacs); } + if (channel_entry->channel_pubkeys) + silc_argument_list_free(channel_entry->channel_pubkeys, + SILC_ARGUMENT_PUBLIC_KEY); + silc_atomic_uninit32(&channel_entry->internal.deleted); + silc_atomic_uninit32(&channel_entry->internal.refcnt); + silc_rwlock_free(channel_entry->internal.lock); + silc_schedule_task_del_by_context(conn->client->schedule, channel_entry); + silc_free(channel_entry); } /* Free channel entry list */ @@ -1726,7 +2076,8 @@ SilcServerEntry silc_client_add_server(SilcClient client, if (!server_entry) return NULL; - silc_atomic_init8(&server_entry->internal.refcnt, 0); + silc_rwlock_alloc(&server_entry->internal.lock); + silc_atomic_init32(&server_entry->internal.refcnt, 0); server_entry->id = *server_id; if (server_name) server_entry->server_name = strdup(server_name); @@ -1771,26 +2122,34 @@ SilcServerEntry silc_client_add_server(SilcClient client, SilcBool silc_client_del_server(SilcClient client, SilcClientConnection conn, SilcServerEntry server) { - SilcBool ret; + SilcIDCacheEntry id_cache; + SilcBool ret = TRUE; + char *namec; if (!server) return FALSE; - if (silc_atomic_sub_int8(&server->internal.refcnt, 1) > 0) + if (silc_atomic_sub_int32(&server->internal.refcnt, 1) > 0) return FALSE; SILC_LOG_DEBUG(("Deleting server %p", server)); silc_mutex_lock(conn->internal->lock); - ret = silc_idcache_del_by_context(conn->internal->server_cache, - server, NULL); + if (silc_idcache_find_by_context(conn->internal->server_cache, server, + &id_cache)) { + namec = id_cache->name; + ret = silc_idcache_del_by_context(conn->internal->server_cache, + server, NULL); + silc_free(namec); + } silc_mutex_unlock(conn->internal->lock); silc_free(server->server_name); silc_free(server->server_info); if (server->public_key) silc_pkcs_public_key_free(server->public_key); - silc_atomic_uninit8(&server->internal.refcnt); + silc_atomic_uninit32(&server->internal.refcnt); + silc_rwlock_free(server->internal.lock); silc_free(server); return ret; @@ -1837,15 +2196,31 @@ void silc_client_update_server(SilcClient client, } } +/* Lock server */ + +void silc_client_lock_server(SilcServerEntry server_entry) +{ + silc_rwlock_rdlock(server_entry->internal.lock); +} + +/* Unlock server */ + +void silc_client_unlock_server(SilcServerEntry server_entry) +{ + silc_rwlock_unlock(server_entry->internal.lock); +} + /* Take reference of server entry */ -void silc_client_ref_server(SilcClient client, SilcClientConnection conn, - SilcServerEntry server_entry) +SilcServerEntry silc_client_ref_server(SilcClient client, + SilcClientConnection conn, + SilcServerEntry server_entry) { - silc_atomic_add_int8(&server_entry->internal.refcnt, 1); + silc_atomic_add_int32(&server_entry->internal.refcnt, 1); SILC_LOG_DEBUG(("Server %p refcnt %d->%d", server_entry, - silc_atomic_get_int8(&server_entry->internal.refcnt) - 1, - silc_atomic_get_int8(&server_entry->internal.refcnt))); + silc_atomic_get_int32(&server_entry->internal.refcnt) - 1, + silc_atomic_get_int32(&server_entry->internal.refcnt))); + return server_entry; } /* Release reference of server entry */ @@ -1855,8 +2230,8 @@ void silc_client_unref_server(SilcClient client, SilcClientConnection conn, { if (server_entry) { SILC_LOG_DEBUG(("Server %p refcnt %d->%d", server_entry, - silc_atomic_get_int8(&server_entry->internal.refcnt), - silc_atomic_get_int8(&server_entry->internal.refcnt) + silc_atomic_get_int32(&server_entry->internal.refcnt), + silc_atomic_get_int32(&server_entry->internal.refcnt) - 1)); silc_client_del_server(client, conn, server_entry); }