Committed WHOIS search by public key patch from pat.
authorPekka Riikonen <priikone@silcnet.org>
Thu, 6 Nov 2003 08:43:02 +0000 (08:43 +0000)
committerPekka Riikonen <priikone@silcnet.org>
Thu, 6 Nov 2003 08:43:02 +0000 (08:43 +0000)
CHANGES
apps/irssi/docs/help/in/whois.in
apps/irssi/src/silc/core/silc-servers.c
apps/silcd/command_reply.c
apps/silcd/packet_receive.c
apps/silcd/server.c
apps/silcd/server_internal.h
apps/silcd/server_query.c
apps/silcd/server_util.c
lib/silcclient/command.c

diff --git a/CHANGES b/CHANGES
index ea4b837bd74e55f8354ef5f179ef0a166ee8c4c4..93bc57d769a6d83ab9855fe317a9826295dc51d5 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,14 @@
+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
index b47f9b9ad4f504bd5021a4af4f21882ee1bcd775..ee168145ba9b12e5ee408ec7f955e340053aa127 100644 (file)
@@ -19,6 +19,9 @@ MAY return following information about the user:
   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
index 0eb2ff3a3a12bba81a937b5bfabb59f17c620361..d5885194e2a284ca8866d535f4b1597d93db8dd3 100644 (file)
@@ -421,7 +421,7 @@ char *silc_server_get_channels(SILC_SERVER_REC *server)
 /* 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 */
index 1350aee19ddb515a8a6e549b49f941bb9eeedf07..5a0ce2e813642c0253040424bf4b10cb8297c7aa 100644 (file)
@@ -147,6 +147,12 @@ silc_server_command_process_error(SilcServerCommandReplyContext cmd,
        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);
@@ -1277,6 +1283,11 @@ SILC_SERVER_CMD_REPLY_FUNC(getkey)
        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) {
index b9a0224f052f99174af426457632390cdc458960..1767b6fa676fefc336024a00d9ddf811741a4689 100644 (file)
@@ -361,6 +361,11 @@ void silc_server_notify(SilcServer 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);
@@ -1360,6 +1365,12 @@ void silc_server_notify(SilcServer server,
            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 :
@@ -1607,6 +1618,12 @@ void silc_server_notify(SilcServer server,
       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)
@@ -1789,6 +1806,11 @@ void silc_server_notify(SilcServer server,
          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);
@@ -2806,6 +2828,22 @@ static void silc_server_new_id_real(SilcServer server,
       /* 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;
 
@@ -3798,6 +3836,13 @@ void silc_server_resume_client(SilcServer server,
       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);
index 9860302b1b3cc192d70506d984dd54b31ca6d29f..1fdaaa492d0eaf6be24909d318065021be85a9e6 100644 (file)
@@ -148,6 +148,9 @@ void silc_server_free(SilcServer server)
   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) &&
@@ -356,6 +359,15 @@ bool silc_server_init(SilcServer server)
   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 :
@@ -1959,6 +1971,10 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection_final)
          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;
     }
@@ -3268,6 +3284,11 @@ void silc_server_free_client_data(SilcServer server,
   /* 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--;
index 7cf42a8ed8964553455acac8f70a10e81438c231..6c99d1fdba4814d552a3edc232ef6f26d0f1ba1f 100644 (file)
@@ -146,6 +146,9 @@ struct SilcServerStruct {
   /* 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.
index 2993c5f6d6bfdd9746267b5c304088c65ae32f14..7ec5daf051fe92fcbe36dda273b15efd7b9dde1b 100644 (file)
@@ -234,11 +234,15 @@ bool silc_server_query_command(SilcServer server, SilcCommand querycmd,
   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;
     }
@@ -357,13 +361,27 @@ void silc_server_query_parse(SilcServer server, SilcServerQuery query)
   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);
@@ -371,8 +389,8 @@ void silc_server_query_parse(SilcServer server, SilcServerQuery 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);
@@ -425,20 +443,7 @@ void silc_server_query_parse(SilcServer server, SilcServerQuery 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 */
@@ -563,6 +568,103 @@ void silc_server_query_parse(SilcServer server, SilcServerQuery query)
   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. */
@@ -718,6 +820,11 @@ void silc_server_query_process(SilcServer server, SilcServerQuery query,
     }
   }
 
+  /* 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));
@@ -1151,6 +1258,9 @@ void silc_server_query_send_reply(SilcServer server,
     /* 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 ||
@@ -1379,7 +1489,7 @@ void silc_server_query_send_reply(SilcServer server,
       /* 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);
     }
index 2cc4907804fabca8ebffc23fe4fb2b43de50d6aa..4002efa17795d0410af03f14c6f4ae846cb3c97b 100644 (file)
@@ -211,6 +211,10 @@ bool silc_server_remove_clients_by_server(SilcServer server,
        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);
@@ -271,6 +275,10 @@ bool silc_server_remove_clients_by_server(SilcServer server,
        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);
@@ -1640,6 +1648,11 @@ void silc_server_kill_client(SilcServer server,
       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)) {
index 220ed86ee2fe77be685090c08ba0ba1a5da18559..f0dcf465ff114f824ef96d2b47aca142636bb208 100644 (file)
@@ -285,6 +285,9 @@ SILC_CLIENT_CMD_FUNC(whois)
   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);
@@ -302,28 +305,75 @@ SILC_CLIENT_CMD_FUNC(whois)
     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);
@@ -2734,7 +2784,7 @@ void silc_client_commands_register(SilcClient client)
   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);