+ /* Check if cannot query this anyway, so take next one */
+ if (!client_entry ||
+ !(client_entry->data.status & SILC_IDLIST_STATUS_REGISTERED))
+ continue;
+
+ /* If Requested Attributes is set then we always resolve the client
+ information, if not then check whether the entry is complete or not
+ and decide whether we need to resolve or not. */
+ if (!query->attrs) {
+
+ /* Even if nickname and stuff are present, we may need to resolve
+ the entry */
+ if (client_entry->nickname && client_entry->username &&
+ client_entry->userinfo) {
+ /* Check if cannot query this anyway, so take next one */
+ if (!client_entry->router)
+ continue;
+
+ /* If we are router, client is local to us, or client is on channel
+ we do not need to resolve the client information. */
+ if (server->server_type != SILC_SERVER || SILC_IS_LOCAL(client_entry)
+ || silc_hash_table_count(client_entry->channels) ||
+ query->resolved)
+ continue;
+ }
+ }
+
+ /* 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. */
+ if (query->attrs && SILC_IS_LOCAL(client_entry) &&
+ (client_entry->mode & SILC_UMODE_DETACHED ||
+ client_entry->data.status & SILC_IDLIST_STATUS_NOATTR))
+ continue;
+
+ /* If attributes are present in query, and in the entry and we have
+ done resolvings already we don't need to resolve anymore */
+ if (query->resolved && query->attrs && client_entry->attrs)
+ continue;
+
+ /* Resolve the detailed client information. If client is local we
+ know that attributes were present and we will resolve directly
+ from the client. Otherwise resolve from client's owner. */
+ silc_server_query_resolve(server, query,
+ (SILC_IS_LOCAL(client_entry) ?
+ client_entry->connection :
+ client_entry->router->connection),
+ client_entry);
+ }
+ break;
+
+ case SILC_COMMAND_WHOWAS:
+ for (i = 0; i < clients_count; i++) {
+ client_entry = clients[i];
+
+ /* Check if cannot query this anyway, so take next one */
+ if (!client_entry || !client_entry->router ||
+ client_entry->data.status & SILC_IDLIST_STATUS_REGISTERED)
+ continue;
+
+ /* If both nickname and username are present no resolving is needed */
+ if (client_entry->nickname && client_entry->username)
+ continue;
+
+ /* Resolve the detailed client information */
+ silc_server_query_resolve(server, query,
+ client_entry->router->connection,
+ client_entry);
+ }
+ break;
+
+ case SILC_COMMAND_IDENTIFY:
+ for (i = 0; i < clients_count; i++) {
+ client_entry = clients[i];
+
+ /* Check if cannot query this anyway, so take next one */
+ if (!client_entry || !client_entry->router ||
+ !(client_entry->data.status & SILC_IDLIST_STATUS_REGISTERED))
+ continue;
+
+ /* Even if nickname is present, we may need to resolve the entry */
+ if (client_entry->nickname) {
+
+ /* If we are router, client is local to us, or client is on channel
+ we do not need to resolve the client information. */
+ if (server->server_type != SILC_SERVER || SILC_IS_LOCAL(client_entry)
+ || silc_hash_table_count(client_entry->channels) ||
+ query->resolved)
+ continue;
+ }
+
+ /* Resolve the detailed client information */
+ silc_server_query_resolve(server, query,
+ client_entry->router->connection,
+ client_entry);
+ }
+ break;
+ }
+
+ if (!query->queries_count)
+ /* If we didn't have to do any resolving, continue with sending the
+ command reply to the original sender. */
+ silc_server_query_send_reply(server, query, clients, clients_count,
+ servers, servers_count, channels,
+ channels_count);
+ else
+ /* Now actually send the resolvings we gathered earlier */
+ silc_server_query_resolve(server, query, NULL, NULL);
+
+ silc_free(clients);
+ silc_free(servers);
+ silc_free(channels);
+}
+
+/* Resolve the detailed information for the `client_entry'. Only client
+ information needs to be resolved for being incomplete. Each incomplete
+ client entry calls this function to do the resolving. */
+
+void silc_server_query_resolve(SilcServer server, SilcServerQuery query,
+ SilcPacketStream sock,
+ SilcClientEntry client_entry)
+{
+ SilcServerCommandContext cmd = query->cmd;
+ SilcServerQueryList r = NULL;
+ SilcBuffer idp;
+ unsigned char *tmp;
+ SilcUInt32 len;
+ SilcUInt16 ident;
+ int i;
+
+ if (!sock && client_entry)
+ return;
+
+ /* If arguments are NULL we will now actually send the resolvings
+ that earlier has been gathered by calling this function. */
+ if (!sock && !client_entry) {
+ SilcBuffer res_cmd;
+
+ SILC_LOG_DEBUG(("Sending the resolvings"));
+
+ /* WHOWAS resolving has been done at the same time this function
+ was called to add the resolving for WHOWAS, so just return. */
+ if (query->querycmd == SILC_COMMAND_WHOWAS)
+ return;
+
+ 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++;
+ }
+
+ /* Statistics */
+ server->stat.commands_sent++;
+
+ /* Send WHOIS command */
+ res_cmd = silc_command_payload_encode(SILC_COMMAND_WHOIS,
+ r->argc, r->arg, r->arg_lens,
+ r->arg_types, r->ident);
+ silc_server_packet_send(server, r->sock, SILC_PACKET_COMMAND, 0,
+ res_cmd->data, silc_buffer_len(res_cmd));
+ silc_buffer_free(res_cmd);
+
+ /* Reprocess this packet after received reply */
+ if (silc_server_command_pending_timed(server, SILC_COMMAND_WHOIS,
+ r->ident,
+ silc_server_query_resolve_reply,
+ query, r->timeout))
+ query->queries_left++;
+ }
+
+ /* Cleanup this temporary context */
+ for (i = 0; i < query->querylist_count; i++) {
+ int k;
+ for (k = 0; k < query->querylist[i].argc; k++)
+ silc_free(query->querylist[i].arg[k]);
+ silc_free(query->querylist[i].arg);
+ silc_free(query->querylist[i].arg_lens);
+ silc_free(query->querylist[i].arg_types);
+ }
+ silc_free(query->querylist);
+ query->querylist = NULL;
+ query->querylist_count = 0;
+ return;
+ }
+
+ SILC_LOG_DEBUG(("Resolving client information"));
+
+ if (client_entry->data.status & SILC_IDLIST_STATUS_RESOLVING) {
+ /* The entry is being resolved by some other external query already.
+ Attach to that query instead of resolving again. */
+ ident = client_entry->resolve_cmd_ident;
+ if (silc_server_command_pending(server, SILC_COMMAND_NONE, ident,
+ silc_server_query_resolve_reply, query))
+ query->queries_left++;
+ } else {
+ /* This entry will be resolved */
+ ident = ++server->cmd_ident;
+
+ switch (query->querycmd) {
+
+ case SILC_COMMAND_WHOIS:
+ case SILC_COMMAND_IDENTIFY:
+ /* Take existing query context if exist for this connection */
+ for (i = 0; i < query->querylist_count; i++)
+ if (query->querylist[i].sock == sock) {
+ r = &query->querylist[i];
+ break;
+ }
+
+ if (!r) {
+ /* Allocate new temp query list context */
+ query->querylist = silc_realloc(query->querylist,
+ sizeof(*query->querylist) *
+ (query->querylist_count + 1));
+ r = &query->querylist[query->querylist_count];
+ query->querylist_count++;
+ memset(r, 0, sizeof(*r));
+ r->sock = sock;
+ r->ident = ident;
+ if (SILC_IS_LOCAL(client_entry))
+ r->timeout = 3;
+ }
+
+ 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);
+
+ /* Add the client entry to be resolved */
+ idp = silc_id_payload_encode(client_entry->id, SILC_ID_CLIENT);
+ r->arg[r->argc] = silc_memdup(idp->data, silc_buffer_len(idp));
+ r->arg_lens[r->argc] = silc_buffer_len(idp);
+ r->arg_types[r->argc] = r->argc + 4;
+ r->argc++;
+ silc_buffer_free(idp);
+
+ break;
+
+ case SILC_COMMAND_WHOWAS:
+ /* We must send WHOWAS command since it's the only the way of
+ resolving clients that are not present in the network anymore. */
+ silc_server_send_command(server, sock, query->querycmd, ident, 1,
+ 1, query->nickname, strlen(query->nickname));
+ if (silc_server_command_pending(server, query->querycmd, ident,
+ silc_server_query_resolve_reply, query))
+ query->queries_left++;
+ break;
+ }
+ }
+
+ /* Mark the entry as being resolved */
+ 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. */
+ query->queries = silc_realloc(query->queries, sizeof(*query->queries) *
+ (query->queries_count + 1));
+ if (query->queries) {
+ i = query->queries_count;
+ query->queries[i].id = silc_id_dup(client_entry->id, SILC_ID_CLIENT);
+ query->queries[i].id_type = SILC_ID_CLIENT;
+ query->queries[i].ident = ident;
+ query->queries_count++;
+ }
+}
+
+/* Reply callback called after one resolving has been completed. If
+ all resolvings has been received then we will continue with sending
+ the command reply to the original sender of the query. */
+
+void silc_server_query_resolve_reply(void *context, void *reply)
+{
+ SilcServerQuery query = context;
+ SilcServer server = query->cmd->server;
+ SilcServerCommandReplyContext cmdr = reply;
+ SilcUInt16 ident = cmdr->ident;
+ SilcStatus error = SILC_STATUS_OK;
+ SilcServerQueryID id = NULL;
+ SilcClientEntry client_entry;
+ int i;
+
+ /* One less query left */
+ query->queries_left--;
+
+ silc_command_get_status(cmdr->payload, NULL, &error);
+ SILC_LOG_DEBUG(("Received reply to resolving (%d left) (status=%d)",
+ query->queries_left, error));
+
+ /* If no error then skip to other stuff */
+ if (error == SILC_STATUS_OK)
+ goto out;
+
+ /* Error occurred during resolving */
+
+ /* Find the resolved client ID */
+ for (i = 0; i < query->queries_count; i++) {
+ if (query->queries[i].ident != ident)
+ continue;
+
+ id = &query->queries[i];
+
+ if (error == SILC_STATUS_ERR_TIMEDOUT) {
+
+ /* If timeout occurred for local entry when resolving attributes
+ mark that this client doesn't support attributes in WHOIS. This
+ assures we won't send the request again to the client. */
+ if (query->querycmd == SILC_COMMAND_WHOIS && query->attrs) {
+ client_entry = silc_idlist_find_client_by_id(server->local_list,
+ id->id, TRUE, NULL);
+ SILC_LOG_DEBUG(("Client %s does not support Requested Attributes",
+ silc_id_render(id->id, SILC_ID_CLIENT)));
+ if (client_entry && SILC_IS_LOCAL(client_entry)) {
+ client_entry->data.status |= SILC_IDLIST_STATUS_NOATTR;
+ client_entry->data.status &= ~SILC_IDLIST_STATUS_RESOLVING;
+ continue;
+ }
+ }
+
+ /* Remove the RESOLVING status from the client entry */
+ if (query->querycmd != SILC_COMMAND_WHOWAS) {
+ client_entry = silc_idlist_find_client_by_id(server->local_list,
+ id->id, TRUE, NULL);
+ if (!client_entry)
+ client_entry = silc_idlist_find_client_by_id(server->global_list,
+ id->id, TRUE, NULL);
+ if (client_entry)
+ client_entry->data.status &= ~SILC_IDLIST_STATUS_RESOLVING;
+ }
+ }
+ }
+
+ out:
+
+ /* If there are queries left then wait for them */
+ if (query->queries_left)
+ return;
+
+ SILC_LOG_DEBUG(("Reprocess the query"));
+
+ /* If the original command caller has gone away, just stop. */
+ if (!silc_packet_stream_is_valid(query->cmd->sock)) {
+ SILC_LOG_DEBUG(("Original command caller vanished"));
+ silc_server_query_free(query);
+ return;
+ }
+
+ /* We have received all queries. Now re-search all information required
+ to complete this query. Reason we cannot save the values found in
+ the first search is that SilcClientEntry, SilcServerEntry and
+ SilcChannelEntry pointers may become invalid while we were waiting
+ for these resolvings. */
+ silc_server_query_process(server, query, FALSE);
+}
+
+/* Send the reply to the original query. If arguments are NULL then this
+ sends only the errors that has occurred during the processing of the
+ query. This sends the errors always after sending all the found
+ information. The query is over after this function returns and the
+ `query' will become invalid. This is called only after all informations
+ has been resolved. This means that if something is not found or is
+ incomplete in this function we were unable to resolve the information
+ or it does not exist at all. */
+
+void silc_server_query_send_reply(SilcServer server,
+ SilcServerQuery query,
+ SilcClientEntry *clients,
+ SilcUInt32 clients_count,
+ SilcServerEntry *servers,
+ SilcUInt32 servers_count,
+ SilcChannelEntry *channels,
+ SilcUInt32 channels_count)
+{
+ SilcServerCommandContext cmd = query->cmd;
+ SilcIDListData idata = silc_packet_get_context(cmd->sock);
+ SilcUInt16 ident = silc_command_get_ident(cmd->payload);
+ SilcStatus status;
+ unsigned char *tmp;
+ char *tmp2;
+ SilcUInt32 len;
+ SilcBuffer idp;
+ int i, k, valid_count;
+ char nh[384], uh[384];
+ SilcBool sent_reply = FALSE;
+
+ SILC_LOG_DEBUG(("Sending reply to query"));
+ SILC_LOG_DEBUG(("Sending %d clients", clients_count));
+ SILC_LOG_DEBUG(("Sending %d servers", servers_count));
+ SILC_LOG_DEBUG(("Sending %d channels", channels_count));
+ SILC_LOG_DEBUG(("Sending %d errors", query->errors_count));
+
+ status = SILC_STATUS_OK;
+
+ /* Send clients */
+ if (clients_count) {
+ SilcClientEntry entry;
+ SilcPacketStream hsock;
+
+ /* 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 ||
+ !(entry->data.status & SILC_IDLIST_STATUS_REGISTERED)) {
+ /* When querying by ID, every "unfound" entry must cause error */
+ if (query->ids)
+ silc_server_query_add_error_id(server, query,
+ SILC_STATUS_ERR_TIMEDOUT,
+ entry->id, SILC_ID_CLIENT);
+ clients[i] = NULL;
+ continue;
+ }
+ break;
+
+ case SILC_COMMAND_IDENTIFY:
+ if (!entry->nickname ||
+ !(entry->data.status & SILC_IDLIST_STATUS_REGISTERED)) {
+ /* When querying by ID, every "unfound" entry must cause error */
+ if (query->ids)
+ silc_server_query_add_error_id(server, query,
+ SILC_STATUS_ERR_TIMEDOUT,
+ entry->id, SILC_ID_CLIENT);
+ clients[i] = NULL;
+ continue;
+ }
+ break;
+
+ case SILC_COMMAND_WHOWAS:
+ if (!entry->nickname || !entry->username ||
+ entry->data.status & SILC_IDLIST_STATUS_REGISTERED) {
+ clients[i] = NULL;
+ continue;
+ }
+ break;
+ }
+ valid_count++;
+ }
+
+ /* Start processing found clients */
+ status = SILC_STATUS_OK;
+ if (valid_count > 1)
+ status = SILC_STATUS_LIST_START;
+
+ /* Now do the sending of valid entries */
+ k = 0;
+ for (i = 0; i < clients_count && valid_count; i++) {
+ entry = clients[i];
+ if (!entry)
+ continue;
+
+ if (k >= 1)
+ status = SILC_STATUS_LIST_ITEM;
+ if (valid_count > 1 && k == valid_count - 1
+ && !servers_count && !channels_count && !query->errors_count)
+ status = SILC_STATUS_LIST_END;
+ if (query->reply_count && k - 1 == query->reply_count)
+ status = SILC_STATUS_LIST_END;
+
+ SILC_LOG_DEBUG(("%s: client %s",
+ (status == SILC_STATUS_OK ? " OK" :
+ status == SILC_STATUS_LIST_START ? "START" :
+ status == SILC_STATUS_LIST_ITEM ? " ITEM" :
+ status == SILC_STATUS_LIST_END ? " END" :
+ " : "), entry->nickname));
+
+ idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT);
+ memset(nh, 0, sizeof(nh));
+
+ silc_strncat(nh, sizeof(nh), entry->nickname, strlen(entry->nickname));
+ if (!strchr(entry->nickname, '@')) {
+ silc_strncat(nh, sizeof(nh), "@", 1);
+ if (entry->servername) {
+ silc_strncat(nh, sizeof(nh), entry->servername,
+ strlen(entry->servername));
+ } else {
+ len = entry->router ? strlen(entry->router->server_name) :
+ strlen(server->server_name);
+ silc_strncat(nh, sizeof(nh), entry->router ?
+ entry->router->server_name :
+ server->server_name, len);
+ }
+ }
+
+ switch (query->querycmd) {
+
+ case SILC_COMMAND_WHOIS:
+ {
+ unsigned char idle[4], mode[4];
+ unsigned char *fingerprint, fempty[20], *attrs = NULL;
+ SilcBuffer channels, umode_list = NULL, tmpattrs = NULL;
+
+ memset(fempty, 0, sizeof(fempty));
+ memset(idle, 0, sizeof(idle));
+ memset(uh, 0, sizeof(uh));
+
+ silc_strncat(uh, sizeof(uh), entry->username,
+ strlen(entry->username));
+ if (!strchr(entry->username, '@') && entry->connection) {
+ hsock = entry->connection;
+ silc_strncat(uh, sizeof(uh), "@", 1);
+ silc_socket_stream_get_info(silc_packet_stream_get_stream(hsock),
+ NULL, (const char **)&tmp2, NULL, NULL);
+ silc_strncat(uh, sizeof(uh), tmp2, strlen(tmp2));
+ }
+
+ if (idata->conn_type == SILC_CONN_CLIENT)
+ channels =
+ silc_server_get_client_channel_list(server, entry, FALSE,
+ FALSE, &umode_list);
+ else
+ channels =
+ silc_server_get_client_channel_list(server, entry, TRUE,
+ TRUE, &umode_list);
+
+ if (memcmp(entry->data.fingerprint, fempty, sizeof(fempty)))
+ fingerprint = entry->data.fingerprint;
+ else
+ fingerprint = NULL;
+
+ SILC_PUT32_MSB(entry->mode, mode);
+ if (entry->connection)
+ SILC_PUT32_MSB((time(NULL) - entry->data.last_receive), idle);
+
+ /* If Requested Attribute were present, and we do not have the
+ attributes we will reply to them on behalf of the client. */
+ len = 0;
+ if (query->attrs) {
+ if (!entry->attrs && SILC_IS_LOCAL(entry)) {
+ tmpattrs = silc_server_query_reply_attrs(server, query, entry);
+ entry->attrs = silc_buffer_steal(tmpattrs, &len);
+ entry->attrs_len = len;
+ silc_buffer_free(tmpattrs);
+ }
+ attrs = entry->attrs;
+ len = entry->attrs_len;
+ }
+
+ /* Send command reply */
+ silc_server_send_command_reply(server, cmd->sock, query->querycmd,
+ status, 0, ident, 10,
+ 2, idp->data, silc_buffer_len(idp),
+ 3, nh, strlen(nh),
+ 4, uh, strlen(uh),
+ 5, entry->userinfo,
+ strlen(entry->userinfo),
+ 6, channels ? channels->data : NULL,
+ channels ? silc_buffer_len(channels)
+ : 0,
+ 7, mode, 4,
+ 8, idle, 4,
+ 9, fingerprint,
+ fingerprint ? 20 : 0,
+ 10, umode_list ? umode_list->data :
+ NULL, umode_list ?
+ silc_buffer_len(umode_list) :
+ 0, 11, attrs, len);
+
+ sent_reply = TRUE;
+
+ /* 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);
+ if (umode_list) {
+ silc_buffer_free(umode_list);
+ umode_list = NULL;
+ }
+ }
+ break;
+
+ case SILC_COMMAND_IDENTIFY:
+ if (!entry->username) {
+ silc_server_send_command_reply(server, cmd->sock, query->querycmd,
+ status, 0, ident, 2,
+ 2, idp->data, silc_buffer_len(idp),
+ 3, nh, strlen(nh));
+ sent_reply = TRUE;
+ } else {
+ memset(uh, 0, sizeof(uh));
+ silc_strncat(uh, sizeof(uh), entry->username,
+ strlen(entry->username));
+ if (!strchr(entry->username, '@') && entry->connection) {
+ hsock = entry->connection;
+ silc_strncat(uh, sizeof(uh), "@", 1);
+ silc_socket_stream_get_info(silc_packet_stream_get_stream(hsock),
+ NULL, (const char **)&tmp2,
+ NULL, NULL);
+ silc_strncat(uh, sizeof(uh), tmp2, strlen(tmp2));
+ }
+
+ silc_server_send_command_reply(server, cmd->sock, query->querycmd,
+ status, 0, ident, 3,
+ 2, idp->data, silc_buffer_len(idp),
+ 3, nh, strlen(nh),
+ 4, uh, strlen(uh));
+ sent_reply = TRUE;
+ }
+ break;
+
+ case SILC_COMMAND_WHOWAS:
+ memset(uh, 0, sizeof(uh));
+ silc_strncat(uh, sizeof(uh), entry->username, strlen(entry->username));
+ if (!strchr(entry->username, '@'))
+ silc_strncat(uh, sizeof(uh), "@-private-", 10);
+
+ /* Send command reply */
+ silc_server_send_command_reply(server, cmd->sock, query->querycmd,
+ status, 0, ident, 4,
+ 2, idp->data, silc_buffer_len(idp),
+ 3, nh, strlen(nh),
+ 4, uh, strlen(uh),
+ 5, entry->userinfo,
+ entry->userinfo ?
+ strlen(entry->userinfo) : 0);
+ sent_reply = TRUE;
+ break;
+ }
+
+ silc_buffer_free(idp);
+
+ if (status == SILC_STATUS_LIST_END)
+ break;
+ k++;
+ }
+
+ if (k == 0) {
+ /* 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[0] || (!query->ids && query->attrs))
+ silc_server_query_add_error(server, query, 1, 1,
+ SILC_STATUS_ERR_NO_SUCH_NICK);
+
+ /* Make sure some error is sent */
+ if (!query->errors_count && !servers_count && !channels_count)
+ silc_server_query_add_error(server, query, 2, 0,
+ SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
+ }
+ }
+
+ /* Send servers */
+ if (query->querycmd == SILC_COMMAND_IDENTIFY && servers_count) {
+ SilcServerEntry entry;
+
+ if (status == SILC_STATUS_OK && servers_count > 1)
+ status = SILC_STATUS_LIST_START;
+
+ k = 0;
+ for (i = 0; i < servers_count; i++) {
+ entry = servers[i];
+
+ if (k >= 1)
+ status = SILC_STATUS_LIST_ITEM;
+ if (servers_count == 1 && status != SILC_STATUS_OK && !channels_count &&
+ !query->errors_count)
+ status = SILC_STATUS_LIST_END;
+ if (servers_count > 1 && k == servers_count - 1 && !channels_count &&
+ !query->errors_count)
+ status = SILC_STATUS_LIST_END;
+ if (query->reply_count && k - 1 == query->reply_count)
+ status = SILC_STATUS_LIST_END;
+
+ SILC_LOG_DEBUG(("%s: server %s",
+ (status == SILC_STATUS_OK ? " OK" :
+ status == SILC_STATUS_LIST_START ? "START" :
+ status == SILC_STATUS_LIST_ITEM ? " ITEM" :
+ status == SILC_STATUS_LIST_END ? " END" :
+ " : "),
+ entry->server_name ? entry->server_name : ""));
+
+ /* Send command reply */
+ idp = silc_id_payload_encode(entry->id, SILC_ID_SERVER);
+ silc_server_send_command_reply(server, cmd->sock, SILC_COMMAND_IDENTIFY,
+ status, 0, ident, 2,
+ 2, idp->data, silc_buffer_len(idp),
+ 3, entry->server_name,
+ entry->server_name ?
+ strlen(entry->server_name) : 0);
+ silc_buffer_free(idp);
+ sent_reply = TRUE;
+
+ if (status == SILC_STATUS_LIST_END)
+ break;
+ k++;
+ }
+ }
+
+ /* Send channels */
+ if (query->querycmd == SILC_COMMAND_IDENTIFY && channels_count) {
+ SilcChannelEntry entry;
+
+ if (status == SILC_STATUS_OK && channels_count > 1)
+ status = SILC_STATUS_LIST_START;
+
+ k = 0;
+ for (i = 0; i < channels_count; i++) {
+ entry = channels[i];
+
+ if (k >= 1)
+ status = SILC_STATUS_LIST_ITEM;
+ if (channels_count == 1 && status != SILC_STATUS_OK &&
+ !query->errors_count)
+ status = SILC_STATUS_LIST_END;
+ if (channels_count > 1 && k == channels_count - 1 &&
+ !query->errors_count)
+ status = SILC_STATUS_LIST_END;
+ if (query->reply_count && k - 1 == query->reply_count)
+ status = SILC_STATUS_LIST_END;
+
+ SILC_LOG_DEBUG(("%s: channel %s",
+ (status == SILC_STATUS_OK ? " OK" :
+ status == SILC_STATUS_LIST_START ? "START" :
+ status == SILC_STATUS_LIST_ITEM ? " ITEM" :
+ status == SILC_STATUS_LIST_END ? " END" :
+ " : "),
+ entry->channel_name ? entry->channel_name : ""));
+
+ /* Send command reply */
+ idp = silc_id_payload_encode(entry->id, SILC_ID_CHANNEL);
+ silc_server_send_command_reply(server, cmd->sock, SILC_COMMAND_IDENTIFY,
+ status, 0, ident, 2,
+ 2, idp->data, silc_buffer_len(idp),
+ 3, entry->channel_name,
+ entry->channel_name ?
+ strlen(entry->channel_name) : 0);
+ silc_buffer_free(idp);
+ sent_reply = TRUE;
+
+ if (status == SILC_STATUS_LIST_END)
+ break;
+ k++;
+ }
+ }
+
+ /* Send errors */
+ if (query->errors_count) {
+ int type;
+
+ if (status == SILC_STATUS_OK && query->errors_count > 1)
+ status = SILC_STATUS_LIST_START;
+
+ k = 0;
+ for (i = 0; i < query->errors_count; i++) {
+ idp = NULL;
+
+ /* Take error argument */
+ if (query->errors[i].type == 1) {
+ /* Take from sent arguments */
+ len = 0;
+ tmp = silc_argument_get_arg_type(cmd->args,
+ query->errors[i].index, &len);
+ type = 2;
+ } else if (query->errors[i].type == 2) {
+ /* No argument */
+ len = 0;
+ tmp = NULL;
+ type = 0;
+ } else if (!query->errors[i].id) {
+ /* Take from query->ids */
+ idp =
+ silc_id_payload_encode(query->ids[query->errors[i].index].id,
+ query->ids[query->errors[k].index].id_type);
+ tmp = idp->data;
+ len = silc_buffer_len(idp);
+ type = 2;
+ } else {
+ /* Take added ID. */
+ idp = silc_id_payload_encode(query->errors[i].id,
+ query->errors[k].id_type);
+ tmp = idp->data;
+ len = silc_buffer_len(idp);
+ type = 2;
+ }
+
+ if (k >= 1)
+ status = SILC_STATUS_LIST_ITEM;
+ if (query->errors_count == 1 && status != SILC_STATUS_OK)
+ status = SILC_STATUS_LIST_END;
+ if (query->errors_count > 1 && k == query->errors_count - 1)
+ status = SILC_STATUS_LIST_END;
+ if (query->reply_count && k - 1 == query->reply_count)
+ status = SILC_STATUS_LIST_END;
+
+ SILC_LOG_DEBUG(("%s: ERROR: %s (%d)",
+ (status == SILC_STATUS_OK ? " OK" :
+ status == SILC_STATUS_LIST_START ? "START" :
+ status == SILC_STATUS_LIST_ITEM ? " ITEM" :
+ status == SILC_STATUS_LIST_END ? " END" :
+ " : "),
+ silc_get_status_message(query->errors[i].error),
+ query->errors[i].error));
+
+#if 1 /* XXX Backwards compatibility. Remove in 1.0. */
+ if (query->errors[i].error == SILC_STATUS_ERR_NO_SUCH_NICK)
+ /* Send error */
+ silc_server_send_command_reply(server, cmd->sock, query->querycmd,
+ (status == SILC_STATUS_OK ?
+ query->errors[i].error : status),
+ (status == SILC_STATUS_OK ?
+ 0 : query->errors[i].error), ident, 2,
+ type, tmp, len,
+ 3, tmp, len);
+ else
+#endif
+ /* Send error */
+ silc_server_send_command_reply(server, cmd->sock, query->querycmd,
+ (status == SILC_STATUS_OK ?
+ query->errors[i].error : status),
+ (status == SILC_STATUS_OK ?
+ 0 : query->errors[i].error), ident, 1,
+ type, tmp, len);
+
+ silc_buffer_free(idp);
+ sent_reply = TRUE;
+
+ if (status == SILC_STATUS_LIST_END)
+ break;
+ k++;
+ }
+ }
+
+ if (!sent_reply)
+ SILC_LOG_ERROR(("BUG: Query did not send anything"));
+
+ /* Cleanup */
+ silc_server_query_free(query);
+}
+
+/* This routine is used to reply to Requested Attributes in WHOIS on behalf
+ of the client since we were unable to resolve them from the client.
+ Either client does not support Requested Attributes or isn't replying
+ to them like it should. */
+
+SilcBuffer silc_server_query_reply_attrs(SilcServer server,
+ SilcServerQuery query,
+ SilcClientEntry client_entry)
+{
+ 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"));
+
+ /* 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_private_key_get_len(server->private_key) / 8 <=
+ sizeof(sign) -1 &&
+ silc_pkcs_sign(server->private_key, buffer->data,
+ silc_buffer_len(buffer), sign, sizeof(sign), &sign_len,
+ TRUE, server->sha1hash)) {
+ 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 buffer;