+Wed Nov 5 19:36:30 CET 2003 Patrik Weiskircher <pat@icore.at>
+
+ * Added support for whois using attributes.
+ Affected files silcd/command_reply.c, silcd/packet_receive.c,
+ silcd/server.c, silcd/server_internal.h, silcd/server_query.c,
+ silcd/server_util.c
+
+ * Added support for whois using public key attribute to /WHOIS i
+ client command. Affected files irssi/docs/help/in/whois.in,
+ irssi/src/silc/core/silc-servers.c, lib/silcclient/command.c
+
Wed Nov 5 23:37:36 EET 2003 Pekka Riikonen <priikone@silcnet.org>
* Fixed UMODE setting in server when the client has anonymous
o User's geographical location
o Information about the device user is using (computer, PDA, etc)
+If the -pubkey option is used WHOIS will only retrieve the clients
+with the corresponding public key.
+
NOTE: It is also possible to receive other information. Note that
all users do not want to send these informations or may send only
some of the information. It also possible that none of these
/* SYNTAX: SILCOPER <username> [-pubkey] */
/* SYNTAX: TOPIC <channel> [<topic>] */
/* SYNTAX: UMODE +|-<modes> */
-/* SYNTAX: WHOIS <nickname>[@<hostname>] [-details] [<count>] */
+/* SYNTAX: WHOIS [<nickname>[@<hostname>]] [-details] [-pubkey <pubkeyfile>] [<count>] */
/* SYNTAX: WHOWAS <nickname>[@<hostname>] [<count>] */
/* SYNTAX: CLOSE <server> [<port>] */
/* SYNTAX: SHUTDOWN */
client = silc_idlist_find_client_by_id(server->global_list,
client_id, FALSE, NULL);
if (client) {
+
+ if (client->data.public_key)
+ silc_hash_table_del_by_context(server->pk_hash,
+ client->data.public_key,
+ client);
+
silc_server_remove_from_channels(server, NULL, client, TRUE,
NULL, TRUE, FALSE);
silc_idlist_del_data(client);
goto out;
}
+ if (server->server_type != SILC_SERVER)
+ if (!silc_hash_table_find_by_context(server->pk_hash, public_key,
+ client, NULL))
+ silc_hash_table_add(server->pk_hash, public_key, client);
+
client->data.public_key = public_key;
public_key = NULL;
} else if (id_type == SILC_ID_SERVER) {
SILC_OPER_STATS_UPDATE(client, router, SILC_UMODE_ROUTER_OPERATOR);
silc_schedule_task_del_by_context(server->schedule, client);
+ /* Remove from public key hash table. */
+ if (client->data.public_key)
+ silc_hash_table_del_by_context(server->pk_hash, client->data.public_key,
+ client);
+
/* Remove the client from all channels. */
silc_server_remove_from_channels(server, NULL, client, TRUE,
tmp, FALSE, FALSE);
if (local)
silc_server_del_from_watcher_list(server, client);
+ /* Remove from public key hash table. */
+ if (client->data.public_key)
+ silc_hash_table_del_by_context(server->pk_hash,
+ client->data.public_key,
+ client);
+
/* Remove the client */
silc_idlist_del_data(client);
silc_idlist_del_client(local ? server->local_list :
silc_server_check_watcher_list(server, client, NULL,
SILC_NOTIFY_TYPE_KILLED);
+ /* Remove from public key hash table. */
+ if (client->data.public_key)
+ silc_hash_table_del_by_context(server->pk_hash,
+ client->data.public_key,
+ client);
+
/* Update statistics */
server->stat.clients--;
if (server->stat.cell_clients)
client = silc_idlist_find_client_by_id(server->global_list,
client_id, FALSE, NULL);
if (client) {
+ if (client->data.public_key)
+ silc_hash_table_del_by_context(server->pk_hash,
+ client->data.public_key,
+ client);
+
silc_server_remove_from_channels(server, NULL, client, TRUE,
NULL, TRUE, FALSE);
silc_idlist_del_data(client);
/* Check if anyone is watching this nickname */
if (server->server_type == SILC_ROUTER && id_list == server->local_list)
silc_server_check_watcher_list(server, entry, NULL, 0);
+
+ if (entry->data.public_key) {
+ silc_hash_table_add(server->pk_hash, entry->data.public_key, entry);
+ } else {
+ /* We need to get the public key using GETKEY */
+ SilcBuffer idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT);
+ SilcSocketConnection dest_sock;
+
+ dest_sock = silc_server_get_client_route(server, NULL, 0, entry->id,
+ NULL, NULL);
+ silc_server_send_command(server, dest_sock ? dest_sock
+ : SILC_PRIMARY_ROUTE(server),
+ SILC_COMMAND_GETKEY, ++server->cmd_ident,
+ 1, 1, idp->data, idp->len);
+ silc_buffer_free(idp);
+ }
}
break;
server->stat.cell_clients--;
silc_server_remove_from_channels(server, NULL, client, FALSE,
NULL, FALSE, FALSE);
+
+ /* Remove from public key hash table. */
+ if (client->data.public_key)
+ silc_hash_table_del_by_context(server->pk_hash,
+ client->data.public_key,
+ client);
+
silc_server_del_from_watcher_list(server, client);
if (!silc_idlist_del_client(server->local_list, client))
silc_idlist_del_client(server->global_list, client);
if (list)
silc_idcache_list_free(list);
+ if (server->pk_hash)
+ silc_hash_table_free(server->pk_hash);
+
/* Delete all servers */
list = NULL;
if (silc_idcache_get_all(server->local_list->servers, &list) &&
if (!server->watcher_list)
goto err;
+ /* Init public key list */
+ server->pk_hash =
+ silc_hash_table_alloc(0, silc_hash_public_key, NULL,
+ silc_hash_public_key_compare, NULL,
+ NULL, NULL, TRUE);
+
+ if (!server->pk_hash)
+ goto err;
+
/* Create a listening server */
if (!silc_server_listen(server,
server->config->server_info->primary == NULL ? NULL :
client->mode |= SILC_UMODE_ANONYMOUS;
}
+ /* Add public key to hash list (for whois using attributes) */
+ silc_hash_table_add(server->pk_hash,
+ entry->data.public_key, client);
+
id_entry = (void *)client;
break;
}
/* Remove this client from watcher list if it is */
silc_server_del_from_watcher_list(server, client);
+ /* Remove this client from the public key hash list */
+ if (client->data.public_key)
+ silc_hash_table_del_by_context(server->pk_hash,
+ client->data.public_key, client);
+
/* Update statistics */
server->stat.my_clients--;
server->stat.clients--;
/* SIM (SILC Module) list */
SilcDList sim;
#endif
+
+ /* Hash table for public keys of all clients */
+ SilcHashTable pk_hash;
};
/* Failure context. This is allocated when failure packet is received.
switch (querycmd) {
case SILC_COMMAND_WHOIS:
- /* If we are normal server and query contains nickname, send it
- directly to router. */
+ /* If we are normal server and query contains nickname OR query
+ doesn't contain nickname or ids BUT attributes, send it to the
+ router */
if (server->server_type == SILC_SERVER && !server->standalone &&
cmd->sock != SILC_PRIMARY_ROUTE(server) &&
- silc_argument_get_arg_type(cmd->args, 1, NULL)) {
+ (silc_argument_get_arg_type(cmd->args, 1, NULL) ||
+ (!silc_argument_get_arg_type(cmd->args, 1, NULL) &&
+ !silc_argument_get_arg_type(cmd->args, 4, NULL) &&
+ silc_argument_get_arg_type(cmd->args, 3, NULL)))) {
silc_server_query_send_router(server, query);
return TRUE;
}
switch (query->querycmd) {
case SILC_COMMAND_WHOIS:
+ /* Get requested attributes if set */
+ tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
+ if (tmp && tmp_len <= SILC_ATTRIBUTE_MAX_REQUEST_LEN) {
+ query->attrs = silc_attribute_payload_parse(tmp, tmp_len);
+
+ /* When Requested Attributes is present we will assure that this
+ client cannot execute the WHOIS command too fast. This would be
+ same as having SILC_CF_LAG_STRICT. */
+ if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT &&
+ cmd->sock->user_data)
+ ((SilcClientEntry)cmd->sock->user_data)->fast_command = 6;
+ }
+
/* Get Client IDs if present. Take IDs always instead of nickname. */
tmp = silc_argument_get_arg_type(cmd->args, 4, &tmp_len);
if (!tmp) {
/* Get nickname */
tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
- if (!tmp) {
+ if (!tmp && !query->attrs) {
+ /* No nickname, no ids and no attributes - send error */
silc_server_query_send_error(server, query,
SILC_STATUS_ERR_NOT_ENOUGH_PARAMS, 0);
silc_server_query_free(query);
}
/* Get the nickname@server string and parse it */
- if (tmp_len > 128 ||
- !silc_parse_userfqdn(tmp, &query->nickname, &query->nick_server)) {
+ if (tmp && ((tmp_len > 128) ||
+ !silc_parse_userfqdn(tmp, &query->nickname, &query->nick_server))) {
silc_server_query_send_error(server, query,
SILC_STATUS_ERR_BAD_NICKNAME, 0);
silc_server_query_free(query);
tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
if (tmp && tmp_len == sizeof(SilcUInt32))
SILC_GET32_MSB(query->reply_count, tmp);
-
- /* Get requested attributes if set */
- tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
- if (tmp && tmp_len <= SILC_ATTRIBUTE_MAX_REQUEST_LEN) {
- query->attrs = silc_attribute_payload_parse(tmp, tmp_len);
-
- /* When Requested Attributes is present we will assure that this
- client cannot execute the WHOIS command too fast. This would be
- same as having SILC_CF_LAG_STRICT. */
- if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT &&
- cmd->sock->user_data)
- ((SilcClientEntry)cmd->sock->user_data)->fast_command = 6;
- }
- break;
+ break;
case SILC_COMMAND_WHOWAS:
/* Get nickname */
silc_server_query_process(server, query, TRUE);
}
+/* Context for holding clients searched by public key. */
+typedef struct {
+ SilcClientEntry **clients;
+ SilcUInt32 *clients_count;
+} *SilcServerPublicKeyUser, SilcServerPublicKeyUserStruct;
+
+void silc_server_public_key_hash_foreach(void *key, void *context,
+ void *user_context)
+{
+ SilcServerPublicKeyUser uc = user_context;
+ SilcClientEntry entry = context;
+
+ /* Nothing was found, just return */
+ if (!context)
+ return;
+
+ (*uc->clients) = silc_realloc((*uc->clients),
+ sizeof((**uc->clients)) *
+ ((*uc->clients_count) + 1));
+ (*uc->clients)[(*uc->clients_count)++] = entry;
+}
+
+/* If clients are set, limit the found clients using the attributes in
+ the query. If clients are not set, try to find some clients using
+ the attributes */
+
+void silc_server_query_check_attributes(SilcServer server,
+ SilcServerQuery query,
+ SilcClientEntry **clients,
+ SilcUInt32 *clients_count) {
+ SilcClientEntry entry;
+ SilcAttributePayload attr;
+ SilcAttribute attribute;
+ SilcAttributeObjPk pk;
+ SilcPublicKey publickey;
+ int i;
+ bool found = FALSE, no_clients = FALSE;
+
+ /* If no clients were found, we only check the attributes
+ if the user wasn't searching for nickname/ids */
+ if (!*clients) {
+ no_clients = TRUE;
+ if (query->nickname || query->ids_count)
+ return;
+ }
+
+ silc_dlist_start(query->attrs);
+ while ((attr = silc_dlist_get(query->attrs)) != SILC_LIST_END) {
+ attribute = silc_attribute_get_attribute(attr);
+ switch (attribute) {
+
+ case SILC_ATTRIBUTE_USER_PUBLIC_KEY:
+ found = TRUE;
+
+ if (!silc_attribute_get_object(attr, &pk, sizeof(pk)))
+ continue;
+
+ if (!silc_pkcs_public_key_decode(pk.data, pk.data_len,
+ &publickey))
+ continue;
+
+ /* If no clients were set on calling this function, we
+ just search for clients, otherwise we try to limit
+ the clients */
+ if (no_clients) {
+ SilcServerPublicKeyUserStruct usercontext;
+
+ usercontext.clients = clients;
+ usercontext.clients_count = clients_count;
+
+ silc_hash_table_find_foreach(server->pk_hash, publickey,
+ silc_server_public_key_hash_foreach,
+ &usercontext);
+ } else {
+ for (i = 0; i < *clients_count; i++) {
+ entry = (*clients)[i];
+
+ if (!entry->data.public_key)
+ continue;
+
+ if (!silc_hash_table_find_by_context(server->pk_hash, publickey,
+ entry, NULL))
+ (*clients)[i] = NULL;
+ }
+ }
+ silc_pkcs_public_key_free(publickey);
+ break;
+ }
+ }
+
+ if (!found && !query->nickname && !query->ids) {
+ silc_server_query_send_error(server, query,
+ SILC_STATUS_ERR_NOT_ENOUGH_PARAMS, 0);
+ silc_server_query_free(query);
+ }
+}
+
/* Processes the parsed query. This does the actual finding of the
queried information and prepares for sending reply to the original
sender of the query command. */
}
}
+ /* Check the attributes to narrow down the search by using them. */
+ if (query->attrs)
+ silc_server_query_check_attributes(server, query, &clients,
+ &clients_count);
+
SILC_LOG_DEBUG(("Querying %d clients", clients_count));
SILC_LOG_DEBUG(("Querying %d servers", servers_count));
SILC_LOG_DEBUG(("Querying %d channels", channels_count));
/* Mark all invalid entries */
for (i = 0, valid_count = 0; i < clients_count; i++) {
entry = clients[i];
+ if (!entry)
+ continue;
+
switch (query->querycmd) {
case SILC_COMMAND_WHOIS:
if (!entry->nickname || !entry->username || !entry->userinfo ||
/* Not one valid entry was found, send error. If nickname was used
in query send error based on that, otherwise the query->errors
already includes proper errors. */
- if (query->nickname)
+ if (query->nickname || (!query->nickname && !query->ids && query->attrs))
silc_server_query_add_error(server, query, TRUE, 1,
SILC_STATUS_ERR_NO_SUCH_NICK);
}
SILC_OPER_STATS_UPDATE(client, server, SILC_UMODE_SERVER_OPERATOR);
SILC_OPER_STATS_UPDATE(client, router, SILC_UMODE_ROUTER_OPERATOR);
+ if (client->data.public_key)
+ silc_hash_table_del_by_context(server->pk_hash,
+ client->data.public_key,
+ client);
silc_server_remove_clients_channels(server, entry, clients,
client, channels);
silc_server_del_from_watcher_list(server, client);
SILC_OPER_STATS_UPDATE(client, server, SILC_UMODE_SERVER_OPERATOR);
SILC_OPER_STATS_UPDATE(client, router, SILC_UMODE_ROUTER_OPERATOR);
+ if (client->data.public_key)
+ silc_hash_table_del_by_context(server->pk_hash,
+ client->data.public_key,
+ client);
silc_server_remove_clients_channels(server, entry, clients,
client, channels);
silc_server_del_from_watcher_list(server, client);
silc_idlist_del_data(remote_client);
}
+ if (remote_client->data.public_key)
+ silc_hash_table_del_by_context(server->pk_hash,
+ remote_client->data.public_key,
+ remote_client);
+
/* Remove remote client */
silc_idlist_del_data(remote_client);
if (!silc_idlist_del_client(server->global_list, remote_client)) {
SilcClientConnection conn = cmd->conn;
SilcBuffer buffer, attrs = NULL;
unsigned char count[4], *tmp = NULL;
+ int i;
+ bool details = FALSE, nick = FALSE;
+ unsigned char *pubkey = NULL;
if (!cmd->conn) {
SILC_NOT_CONNECTED(cmd->client, cmd->conn);
goto out;
}
- if (cmd->argc == 2) {
- buffer = silc_command_payload_encode_va(SILC_COMMAND_WHOIS,
- ++conn->cmd_ident, 1,
- 1, cmd->argv[1],
- cmd->argv_lens[1]);
- } else {
- if (!strcasecmp(cmd->argv[2], "-details"))
+ for (i = 1; i < cmd->argc; i++) {
+ if (!strcasecmp(cmd->argv[i], "-details")) {
+ details = TRUE;
+ } else if (!strcasecmp(cmd->argv[i], "-pubkey") && cmd->argc > i + 1) {
+ pubkey = cmd->argv[i + 1];
+ i++;
+ } else {
+ /* We assume that the first parameter is the nickname, if it isn't
+ -details or -pubkey. The last parameter should always be the count */
+ if (i == 1) {
+ nick = TRUE;
+ } else if (i == cmd->argc - 1) {
+ int c = atoi(cmd->argv[i]);
+ SILC_PUT32_MSB(c, count);
+ tmp = count;
+ }
+ }
+ }
+
+ if (details) {
+ /* if pubkey is set, add all attributes to the
+ attrs buffer, except public key */
+ if (pubkey) {
+ attrs = silc_client_attributes_request(SILC_ATTRIBUTE_USER_INFO,
+ SILC_ATTRIBUTE_SERVICE,
+ SILC_ATTRIBUTE_STATUS_MOOD,
+ SILC_ATTRIBUTE_STATUS_FREETEXT,
+ SILC_ATTRIBUTE_STATUS_MESSAGE,
+ SILC_ATTRIBUTE_PREFERRED_LANGUAGE,
+ SILC_ATTRIBUTE_PREFERRED_CONTACT,
+ SILC_ATTRIBUTE_TIMEZONE,
+ SILC_ATTRIBUTE_GEOLOCATION,
+ SILC_ATTRIBUTE_DEVICE_INFO, 0);
+ } else {
attrs = silc_client_attributes_request(0);
+ }
+ }
- if (!attrs || cmd->argc > 3) {
- int c = atoi(cmd->argc > 3 ? cmd->argv[3] : cmd->argv[2]);
- SILC_PUT32_MSB(c, count);
- tmp = count;
+ if (pubkey) {
+ SilcAttributeObjPk obj;
+ SilcPublicKey pk;
+
+ if (!silc_pkcs_load_public_key(pubkey, &pk, SILC_PKCS_FILE_PEM)) {
+ if (!silc_pkcs_load_public_key(pubkey, &pk, SILC_PKCS_FILE_BIN)) {
+ SAY(cmd->client, conn, SILC_CLIENT_MESSAGE_ERROR,
+ "Could not load public key %s, check the filename",
+ pubkey);
+ COMMAND_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+ goto out;
+ }
}
- buffer = silc_command_payload_encode_va(SILC_COMMAND_WHOIS,
- ++conn->cmd_ident, 3,
- 1, cmd->argv[1], cmd->argv_lens[1],
- 2, tmp ? tmp : NULL, tmp ? 4 : 0,
- 3, attrs ? attrs->data : NULL,
- attrs ? attrs->len : 0);
+ obj.type = "silc-rsa";
+ obj.data = silc_pkcs_public_key_encode(pk, &obj.data_len);
+
+ attrs = silc_attribute_payload_encode(attrs,
+ SILC_ATTRIBUTE_USER_PUBLIC_KEY,
+ SILC_ATTRIBUTE_FLAG_VALID,
+ &obj, sizeof(obj));
}
+
+ buffer = silc_command_payload_encode_va(SILC_COMMAND_WHOIS,
+ ++conn->cmd_ident, 3,
+ 1, nick ? cmd->argv[1] : NULL,
+ nick ? cmd->argv_lens[1] : 0,
+ 2, tmp ? tmp : NULL, tmp ? 4 : 0,
+ 3, attrs ? attrs->data : NULL,
+ attrs ? attrs->len : 0);
+
silc_client_packet_send(cmd->client, cmd->conn->sock,
SILC_PACKET_COMMAND, NULL, 0, NULL, NULL,
buffer->data, buffer->len, TRUE);
silc_list_init(client->internal->commands, struct SilcClientCommandStruct,
next);
- SILC_CLIENT_CMD(whois, WHOIS, "WHOIS", 3);
+ SILC_CLIENT_CMD(whois, WHOIS, "WHOIS", 5);
SILC_CLIENT_CMD(whowas, WHOWAS, "WHOWAS", 3);
SILC_CLIENT_CMD(identify, IDENTIFY, "IDENTIFY", 3);
SILC_CLIENT_CMD(nick, NICK, "NICK", 2);