Merged from silc_1_0_branch.
[silc.git] / apps / silcd / server_query.c
index adcbc3405561bf4232ca44c58caea08f97e4c0bb..6475c57301b06b7dcde972704472061994bf0c7a 100644 (file)
@@ -18,8 +18,6 @@
 */
 /* $Id$ */
 
-/* XXX TODO Requested Attributes to WHOIS */
-
 #include "serverincludes.h"
 #include "server_internal.h"
 
@@ -106,9 +104,9 @@ void silc_server_query_send_reply(SilcServer server,
                                  SilcUInt32 servers_count,
                                  SilcChannelEntry *channels,
                                  SilcUInt32 channels_count);
-unsigned char *silc_server_query_reply_attrs(SilcServer server,
-                                            SilcServerQuery query,
-                                            SilcUInt32 *attrs_len);
+SilcBuffer silc_server_query_reply_attrs(SilcServer server,
+                                        SilcServerQuery query,
+                                        SilcClientEntry client_entry);
 
 /* Free the query context structure and all allocated resources. */
 
@@ -390,8 +388,8 @@ void silc_server_query_parse(SilcServer server, SilcServerQuery query)
        if (!tmp)
          continue;
 
-       id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-       if (!id) {
+       id = silc_id_payload_parse_id(tmp, tmp_len, &id_type);
+       if (!id || id_type != SILC_ID_CLIENT) {
          silc_server_query_add_error(server, query, TRUE, i + 4,
                                      SILC_STATUS_ERR_BAD_CLIENT_ID);
          continue;
@@ -409,6 +407,8 @@ void silc_server_query_parse(SilcServer server, SilcServerQuery query)
              for (i = 0; i < query->ids_count; i++)
                silc_free(query->ids[i].id);
              silc_free(query->ids);
+             query->ids = NULL;
+             query->ids_count = 0;
              silc_free(id);
              return;
            }
@@ -428,8 +428,16 @@ void silc_server_query_parse(SilcServer server, SilcServerQuery query)
 
     /* Get requested attributes if set */
     tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
-    if (tmp)
-      query->attrs = silc_attribute_payload_parse_list(tmp, 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;
 
   case SILC_COMMAND_WHOWAS:
@@ -508,18 +516,33 @@ void silc_server_query_parse(SilcServer server, SilcServerQuery query)
        /* Normal server must check whether this ID exist, and if not then
           send the query to router, unless done so already */
        if (server->server_type == SILC_SERVER && !query->resolved) {
-         if (!silc_idlist_find_client_by_id(server->local_list,
-                                            id, TRUE, NULL)) {
-           if (cmd->sock->type != SILC_SOCKET_TYPE_CLIENT ||
-               !silc_idlist_find_client_by_id(server->global_list,
+         if (id_type == SILC_ID_CLIENT) {
+           if (!silc_idlist_find_client_by_id(server->local_list,
                                               id, TRUE, NULL)) {
-             silc_server_query_send_router(server, query);
-             for (i = 0; i < query->ids_count; i++)
-               silc_free(query->ids[i].id);
-             silc_free(query->ids);
-             silc_free(id);
-             return;
+             if (cmd->sock->type != SILC_SOCKET_TYPE_CLIENT ||
+                 !silc_idlist_find_client_by_id(server->global_list,
+                                                id, TRUE, NULL)) {
+               silc_server_query_send_router(server, query);
+               for (i = 0; i < query->ids_count; i++)
+                 silc_free(query->ids[i].id);
+               silc_free(query->ids);
+               query->ids = NULL;
+               query->ids_count = 0;
+               silc_free(id);
+               return;
+             }
            }
+         } else {
+           /* For now all other ID's except Client ID's are explicitly
+              sent to router for resolving. */
+           silc_server_query_send_router(server, query);
+           for (i = 0; i < query->ids_count; i++)
+             silc_free(query->ids[i].id);
+           silc_free(query->ids);
+           query->ids = NULL;
+           query->ids_count = 0;
+           silc_free(id);
+           return;
          }
        }
 
@@ -748,6 +771,11 @@ void silc_server_query_process(SilcServer server, SilcServerQuery query,
        }
       }
 
+      /* Remove the NOATTR status periodically */
+      if (client_entry->data.status & SILC_IDLIST_STATUS_NOATTR &&
+         client_entry->updated + 600 < time(NULL))
+       client_entry->data.status &= ~SILC_IDLIST_STATUS_NOATTR;
+
       /* When requested attributes is present and local client is detached
         we cannot send the command to the client, we'll reply on behalf of
         the client instead. */
@@ -864,6 +892,21 @@ void silc_server_query_resolve(SilcServer server, SilcServerQuery query,
     for (i = 0; i < query->querylist_count; i++) {
       r = &query->querylist[i];
 
+      /* If Requested Attributes were present put them to this resolving */
+      if (query->attrs && query->querycmd == SILC_COMMAND_WHOIS) {
+       len = r->argc + 1;
+       r->arg = silc_realloc(r->arg, sizeof(*r->arg) * len);
+       r->arg_lens = silc_realloc(r->arg_lens, sizeof(*r->arg_lens) * len);
+       r->arg_types = silc_realloc(r->arg_types, sizeof(*r->arg_types) * len);
+
+       tmp = silc_argument_get_arg_type(cmd->args, 3, &len);
+       if (tmp)
+         r->arg[r->argc] = silc_memdup(tmp, len);
+       r->arg_lens[r->argc] = len;
+       r->arg_types[r->argc] = 3;
+       r->argc++;
+      }
+
       /* Send WHOIS command */
       res_cmd = silc_command_payload_encode(SILC_COMMAND_WHOIS,
                                            r->argc, r->arg, r->arg_lens,
@@ -913,7 +956,7 @@ void silc_server_query_resolve(SilcServer server, SilcServerQuery query,
     case SILC_COMMAND_WHOIS:
     case SILC_COMMAND_IDENTIFY:
       /* Take existing query context if exist for this connection */
-      for (i = 0; i < query->queries_count; i++)
+      for (i = 0; i < query->querylist_count; i++)
        if (query->querylist[i].sock == sock) {
          r = &query->querylist[i];
          break;
@@ -933,21 +976,6 @@ void silc_server_query_resolve(SilcServer server, SilcServerQuery query,
          r->timeout = 3;
       }
 
-      /* If Requested Attributes were present put them to this resolving */
-      if (query->attrs && query->querycmd == SILC_COMMAND_WHOIS) {
-       len = r->argc + 1;
-       r->arg = silc_realloc(r->arg, sizeof(*r->arg) * len);
-       r->arg_lens = silc_realloc(r->arg_lens, sizeof(*r->arg_lens) * len);
-       r->arg_types = silc_realloc(r->arg_types, sizeof(*r->arg_types) * len);
-
-       tmp = silc_argument_get_arg_type(cmd->args, 3, &len);
-       if (tmp)
-         r->arg[r->argc] = silc_memdup(tmp, len);
-       r->arg_lens[r->argc] = len;
-       r->arg_types[r->argc] = 3;
-       r->argc++;
-      }
-
       len = r->argc + 1;
       r->arg = silc_realloc(r->arg, sizeof(*r->arg) * len);
       r->arg_lens = silc_realloc(r->arg_lens, sizeof(*r->arg_lens) * len);
@@ -979,6 +1007,7 @@ void silc_server_query_resolve(SilcServer server, SilcServerQuery query,
   client_entry->data.status |= SILC_IDLIST_STATUS_RESOLVING;
   client_entry->data.status &= ~SILC_IDLIST_STATUS_RESOLVED;
   client_entry->resolve_cmd_ident = ident;
+  client_entry->updated = time(NULL);
 
   /* Save the queried ID, which we will reprocess after we get this and
      all other queries back. */
@@ -1204,9 +1233,10 @@ void silc_server_query_send_reply(SilcServer server,
        {
          unsigned char idle[4], mode[4];
          unsigned char *fingerprint, fempty[20], *attrs = NULL;
-         SilcBuffer channels, umode_list = NULL;
+         SilcBuffer channels, umode_list = NULL, tmpattrs = NULL;
 
          memset(fempty, 0, sizeof(fempty));
+         memset(idle, 0, sizeof(idle));
          silc_strncat(uh, sizeof(uh), entry->username,
                       strlen(entry->username));
          if (!strchr(entry->username, '@') && entry->connection) {
@@ -1238,12 +1268,14 @@ void silc_server_query_send_reply(SilcServer server,
             attributes we will reply to them on behalf of the client. */
          len = 0;
          if (query->attrs) {
-           if (!entry->attrs) {
-             attrs = silc_server_query_reply_attrs(server, query, &len);
-           } else {
-             attrs = entry->attrs;
-             len = entry->attrs_len;
+           if (!entry->attrs && SILC_IS_LOCAL(entry)) {
+             tmpattrs = silc_server_query_reply_attrs(server, query, entry);
+             entry->attrs = silc_memdup(tmpattrs->data, tmpattrs->len);
+             entry->attrs_len = tmpattrs->len;
+             silc_buffer_free(tmpattrs);
            }
+           attrs = entry->attrs;
+           len = entry->attrs_len;
          }
 
          /* Send command reply */
@@ -1266,9 +1298,14 @@ void silc_server_query_send_reply(SilcServer server,
 
          sent_reply = TRUE;
 
-         /* For now we will delete Requested Attributes */
-         silc_free(entry->attrs);
-         entry->attrs = NULL;
+         /* For now we always delete Requested Attributes, unless the client
+            is detached, in which case we don't want to reconstruct the
+            same data everytime */
+         if (!(entry->mode & SILC_UMODE_DETACHED) &&
+             !(entry->data.status & SILC_IDLIST_STATUS_NOATTR)) {
+           silc_free(entry->attrs);
+           entry->attrs = NULL;
+         }
 
          if (channels)
            silc_buffer_free(channels);
@@ -1507,19 +1544,151 @@ void silc_server_query_send_reply(SilcServer server,
    Either client does not support Requested Attributes or isn't replying
    to them like it should. */
 
-unsigned char *silc_server_query_reply_attrs(SilcServer server,
-                                            SilcServerQuery query,
-                                            SilcUInt32 *attrs_len)
+SilcBuffer silc_server_query_reply_attrs(SilcServer server,
+                                        SilcServerQuery query,
+                                        SilcClientEntry client_entry)
 {
-  unsigned char *attrs = NULL;
-  SilcUInt32 len = 0;
+  SilcBuffer buffer = NULL;
+  SilcAttribute attribute;
+  SilcAttributePayload attr;
+  SilcAttributeObjPk pk;
+  SilcAttributeObjService service;
+  unsigned char *tmp;
+  unsigned char sign[2048 + 1];
+  SilcUInt32 sign_len;
 
   SILC_LOG_DEBUG(("Constructing Requested Attributes"));
 
-  if (attrs_len)
-    *attrs_len = len;
+  /* Go through all requested attributes */
+  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_SERVICE:
+      /* Put SERVICE.  Put only SILC service. */
+      memset(&service, 0, sizeof(service));
+      service.port = (server->config->server_info->primary ?
+                     server->config->server_info->primary->port : SILC_PORT);
+      silc_strncat(service.address, sizeof(service.address),
+                  server->server_name, strlen(server->server_name));
+      service.status = !(client_entry->mode & SILC_UMODE_DETACHED);
+      if (client_entry->connection)
+       service.idle = time(NULL) - client_entry->data.last_receive;
+      buffer = silc_attribute_payload_encode(buffer, attribute,
+                                            SILC_ATTRIBUTE_FLAG_VALID,
+                                            &service, sizeof(service));
+      if (!buffer)
+       return NULL;
+      break;
+
+    case SILC_ATTRIBUTE_STATUS_MOOD:
+      /* Put STATUS_MOOD */
+      buffer = silc_attribute_payload_encode(buffer, attribute,
+                                            SILC_ATTRIBUTE_FLAG_VALID,
+                                            (void *)
+                                            SILC_ATTRIBUTE_MOOD_NORMAL,
+                                            sizeof(SilcUInt32));
+      if (!buffer)
+       return NULL;
+      break;
+
+    case SILC_ATTRIBUTE_STATUS_FREETEXT:
+      /* Put STATUS_FREETEXT.  We just tell in the message that we are
+        replying on behalf of the client. */
+      tmp = 
+       "This information was provided by the server on behalf of the user";
+      buffer = silc_attribute_payload_encode(buffer, attribute,
+                                            SILC_ATTRIBUTE_FLAG_VALID,
+                                            tmp, strlen(tmp));
+      if (!buffer)
+       return NULL;
+      break;
+
+    case SILC_ATTRIBUTE_PREFERRED_CONTACT:
+      /* Put PREFERRED_CONTACT */
+      buffer = silc_attribute_payload_encode(buffer, attribute,
+                                            SILC_ATTRIBUTE_FLAG_VALID,
+                                            (void *)
+                                            SILC_ATTRIBUTE_CONTACT_CHAT,
+                                            sizeof(SilcUInt32));
+      if (!buffer)
+       return NULL;
+      break;
+
+    case SILC_ATTRIBUTE_USER_PUBLIC_KEY:
+      /* Put USER_PUBLIC_KEY */
+      if (client_entry->data.public_key) {
+       pk.type = "silc-rsa";
+       pk.data = silc_pkcs_public_key_encode(client_entry->data.public_key,
+                                             &pk.data_len);
+       buffer = silc_attribute_payload_encode(buffer, attribute, pk.data ?
+                                              SILC_ATTRIBUTE_FLAG_VALID :
+                                              SILC_ATTRIBUTE_FLAG_INVALID,
+                                              &pk, sizeof(pk));
+       silc_free(pk.data);
+       if (!buffer)
+         return NULL;
+       break;
+      }
+
+      /* No public key available */
+      buffer = silc_attribute_payload_encode(buffer, attribute,
+                                            SILC_ATTRIBUTE_FLAG_INVALID,
+                                            NULL, 0);
+      if (!buffer)
+       return NULL;
+      break;
+
+    default:
+      /* Ignore SERVER_PUBLIC_KEY since we are going to put it anyway later */
+      if (attribute == SILC_ATTRIBUTE_SERVER_PUBLIC_KEY ||
+         attribute == SILC_ATTRIBUTE_SERVER_DIGITAL_SIGNATURE)
+       break;
+      
+      /* For other attributes we cannot reply so mark it invalid */
+      buffer = silc_attribute_payload_encode(buffer, attribute,
+                                            SILC_ATTRIBUTE_FLAG_INVALID,
+                                            NULL, 0);
+      if (!buffer)
+       return NULL;
+      break;
+    }
+  }
+
+  /* Always put our public key.  This assures that we send at least
+     something valid back always. */
+  pk.type = "silc-rsa";
+  pk.data = silc_pkcs_public_key_encode(server->public_key, &pk.data_len);
+  buffer = silc_attribute_payload_encode(buffer,
+                                        SILC_ATTRIBUTE_SERVER_PUBLIC_KEY,
+                                        pk.data ? SILC_ATTRIBUTE_FLAG_VALID :
+                                        SILC_ATTRIBUTE_FLAG_INVALID,
+                                        &pk, sizeof(pk));
+  silc_free(pk.data);
+  if (!buffer)
+    return NULL;
+
+  /* Finally compute the digital signature of all the data we provided
+     as an indication that we provided rightfull information, and this
+     also authenticates our public key. */
+  if (silc_pkcs_get_key_len(server->pkcs) / 8 <= sizeof(sign) -1  &&
+      silc_pkcs_sign_with_hash(server->pkcs, server->sha1hash,
+                              buffer->data, buffer->len,
+                              sign, &sign_len)) {
+    pk.type = NULL;
+    pk.data = sign;
+    pk.data_len = sign_len;
+    buffer =
+      silc_attribute_payload_encode(buffer,
+                                   SILC_ATTRIBUTE_SERVER_DIGITAL_SIGNATURE,
+                                   SILC_ATTRIBUTE_FLAG_VALID,
+                                   &pk, sizeof(pk));
+  }
+  if (!buffer)
+    return NULL;
 
-  return attrs;
+  return buffer;
 }
 
 /* Find client by the Client ID indicated by the `client_id', and if not