/* server_query.c Author: Pekka Riikonen Copyright (C) 2002 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 the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ /* $Id$ */ #include "serverincludes.h" #include "server_internal.h" /* Represents an SILC ID */ typedef struct { void *id; /* ID */ SilcIdType id_type; /* ID type */ } *SilcServerQueryID; /* Represents one error occurred during query */ typedef struct { SilcUInt32 index; /* Index to IDs */ bool from_cmd; /* TRUE if `index' is from command args, otherwise from query->ids */ SilcStatus error; /* The actual error */ } *SilcServerQueryError; /* Context for ongoing queries that server the original query (like to resolve detailed information from some other server before replying to the original sender). */ typedef struct { unsigned char **arg; /* Query argument */ SilcUInt32 *arg_lens; /* Query argument lengths */ SilcUInt32 *arg_types; /* Query argument types */ SilcUInt32 argc; /* Number of query arguments */ SilcUInt32 timeout; /* Max timeout for query to complete */ SilcUInt16 ident; /* Query command identifier */ } *SilcServerQueryList; /* Query session context */ typedef struct { /* Query session data */ SilcCommand querycmd; /* Query command */ SilcServerCommandContext cmd; /* Command context for query */ SilcServerQueryList queries; /* Ongoing queries */ SilcUInt32 num_query; /* Number of ongoing queries left */ /* Queried data */ char *nickname; /* Queried nickname */ char *nick_server; /* Queried nickname's server */ char *server_name; /* Queried server name */ char *channel_name; /* Queried channel name */ SilcServerQueryID ids; /* Queried IDs */ SilcUInt32 ids_count; /* number of queried IDs */ SilcUInt32 reply_count; /* Requested reply count */ SilcDList attrs; /* Requested Attributes in WHOIS */ SilcServerQueryError errors; /* Query errors */ SilcUInt32 errors_count; /* number of errors */ } *SilcServerQuery; void silc_server_query_free(SilcServerQuery query); bool silc_server_query_check_error(SilcServer server, SilcServerQuery query, SilcServerCommandReplyContext cmdr); void silc_server_query_send_error(SilcServer server, SilcServerQuery query, SilcStatus error, ...); void silc_server_query_add_error(SilcServer server, SilcServerQuery query, bool from_cmd, SilcUInt32 index, SilcStatus error); void silc_server_query_send_router(SilcServer server, SilcServerQuery query); void silc_server_query_send_router_reply(void *context, void *reply); void silc_server_query_parse(SilcServer server, SilcServerQuery query); void silc_server_query_process(SilcServer server, SilcServerQuery query); void silc_server_query_resolve(SilcServer server, SilcServerQuery query, SilcSocketConnection sock, SilcClientEntry client_entry); void silc_server_query_resolve_reply(void *context, void *reply); 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); /* Free the query context structure and all allocated resources. */ void silc_server_query_free(SilcServerQuery query) { int i; silc_server_command_free(query->cmd); silc_free(query->nickname); silc_free(query->nick_server); silc_free(query->server_name); silc_free(query->channel_name); for (i = 0; i < query->ids_count; i++) silc_free(query->ids[i].id); silc_free(query->ids); if (query->attrs) silc_attribute_payload_list_free(query->attrs); silc_free(query->errors); memset(query, 'F', sizeof(*query)); silc_free(query); } /* Check whether command reply contained error, and reply the error to the original sender if it occurred. */ bool silc_server_query_check_error(SilcServer server, SilcServerQuery query, SilcServerCommandReplyContext cmdr) { if (!cmdr) return FALSE; if (!silc_command_get_status(cmdr->payload, NULL, NULL)) { SilcBuffer buffer; /* Send the same command reply payload which contains the error */ silc_command_set_command(cmdr->payload, query->querycmd); silc_command_set_ident(cmdr->payload, silc_command_get_ident(query->cmd->payload)); buffer = silc_command_payload_encode_payload(cmdr->payload); silc_server_packet_send(server, query->cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, buffer->data, buffer->len, FALSE); silc_buffer_free(buffer); return TRUE; } return FALSE; } /* Send error reply indicated by the `error' to the original sender of the query. */ void silc_server_query_send_error(SilcServer server, SilcServerQuery query, SilcStatus error, ...) { va_list va; unsigned char *data = NULL; SilcUInt32 data_len = 0, data_type = 0, argc = 0; va_start(va, error); data_type = va_arg(va, SilcUInt32); if (data_type) { argc = 1; data = va_arg(va, unsigned char *); data_len = va_arg(va, SilcUInt32); } /* Send the command reply with error */ silc_server_send_command_reply(server, query->cmd->sock, query->querycmd, error, 0, silc_command_get_ident(query->cmd->payload), argc, data_type, data, data_len); va_end(va); } /* Add error to error list. Multiple errors may occur during the query processing and this function can be used to add one error. The `index' is the index to the command context which includes the argument which caused the error, or it is the index to query->ids, depending on value of `from_cmd'. */ void silc_server_query_add_error(SilcServer server, SilcServerQuery query, bool from_cmd, SilcUInt32 index, SilcStatus error) { query->errors = silc_realloc(query->errors, sizeof(*query->errors) * (query->errors_count + 1)); if (!query->errors) return; query->errors[query->errors_count].index = index; query->errors[query->errors_count].from_cmd = from_cmd; query->errors[query->errors_count].error = error; query->errors_count++; } /* Processes query as command. The `query' is the command that is being processed indicated by the `cmd'. The `query' can be one of the following: SILC_COMMAND_WHOIS, SILC_COMMAND_WHOWAS or SILC_COMMAND_IDENTIFY. This function handles the reply sending to the entity who sent this query to us automatically. Returns TRUE if the query is being processed or FALSE on error. */ bool silc_server_query_command(SilcServer server, SilcCommand querycmd, SilcServerCommandContext cmd) { SilcServerQuery query; query = silc_calloc(1, sizeof(*query)); query->querycmd = querycmd; query->cmd = silc_server_command_dup(cmd); switch (querycmd) { case SILC_COMMAND_WHOIS: /* If we are normal server and query contains nickname, send it directly to router. */ if (server->server_type == SILC_SERVER && !server->standalone && silc_argument_get_arg_type(cmd->args, 1, NULL)) { silc_server_query_send_router(server, query); break; } break; case SILC_COMMAND_WHOWAS: /* WHOWAS query is always sent to router if we are normal server */ if (server->server_type == SILC_SERVER && !server->standalone) { silc_server_query_send_router(server, query); break; } break; case SILC_COMMAND_IDENTIFY: /* If we are normal server and query does not contain IDs, send it directly to router (it contains nickname, server name or channel name). */ if (server->server_type == SILC_SERVER && !server->standalone && !silc_argument_get_arg_type(cmd->args, 5, NULL)) { silc_server_query_send_router(server, query); break; } break; default: SILC_LOG_ERROR(("Bad query using %d command", querycmd)); silc_server_query_free(query); return FALSE; } /* Now parse the request */ silc_server_query_parse(server, query); return TRUE; } /* Send the received query to our primary router since we could not handle the query directly. We will reprocess the query after our router replies back. */ void silc_server_query_send_router(SilcServer server, SilcServerQuery query) { SilcBuffer tmpbuf; SilcUInt16 old_ident; /* Send WHOIS command to our router */ old_ident = silc_command_get_ident(query->cmd->payload); silc_command_set_ident(query->cmd->payload, ++server->cmd_ident); tmpbuf = silc_command_payload_encode_payload(query->cmd->payload); silc_server_packet_send(server, SILC_PRIMARY_ROUTE(server), SILC_PACKET_COMMAND, 0, tmpbuf->data, tmpbuf->len, TRUE); silc_command_set_ident(query->cmd->payload, old_ident); silc_buffer_free(tmpbuf); /* Continue parsing the query after received reply from router */ silc_server_command_pending(server, query->querycmd, server->cmd_ident, silc_server_query_send_router_reply, query); } /* Reply callback called after primary router has replied to our initial sending of the query to it. We will proceed the query in this function. */ void silc_server_query_send_router_reply(void *context, void *reply) { SilcServerQuery query = context; SilcServer server = query->cmd->server; /* Check if router sent error reply */ if (!silc_server_query_check_error(server, query, reply)) { silc_server_query_free(query); return; } /* Continue with parsing */ silc_server_query_parse(server, query); } /* Parse the command query and start processing the queries in detail. */ void silc_server_query_parse(SilcServer server, SilcServerQuery query) { SilcServerCommandContext cmd = query->cmd; unsigned char *tmp; SilcUInt32 tmp_len, argc = silc_argument_get_arg_num(cmd->args); void *id; SilcIdType id_type; int i; switch (query->querycmd) { case SILC_COMMAND_WHOIS: /* 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) { silc_server_query_send_error(server, query, SILC_STATUS_ERR_NOT_ENOUGH_PARAMS, 0); silc_server_query_free(query); return; } /* Get the nickname@server string and parse it */ if (!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); return; } } else { /* Parse the IDs included in the query */ query->ids = silc_calloc(argc, sizeof(*query->ids)); for (i = 0; i < argc; i++) { tmp = silc_argument_get_arg_type(cmd->args, i + 4, &tmp_len); if (!tmp) continue; id = silc_id_payload_parse_id(tmp, tmp_len, NULL); if (!id) { silc_server_query_add_error(server, query, TRUE, i + 4, SILC_STATUS_ERR_BAD_CLIENT_ID); continue; } query->ids[query->ids_count].id = id; query->ids[query->ids_count].id_type = SILC_ID_CLIENT; query->ids_count++; } } /* Get the max count of reply messages allowed */ 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) query->attrs = silc_attribute_payload_parse_list(tmp, tmp_len); break; case SILC_COMMAND_WHOWAS: /* Get nickname */ tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len); if (!tmp) { silc_server_query_send_error(server, query, SILC_STATUS_ERR_NOT_ENOUGH_PARAMS, 0); silc_server_query_free(query); return; } /* Get the nickname@server string and parse it */ if (!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); return; } /* Get the max count of reply messages allowed */ tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len); if (tmp && tmp_len == sizeof(SilcUInt32)) SILC_GET32_MSB(query->reply_count, tmp); break; case SILC_COMMAND_IDENTIFY: /* Get IDs if present. Take IDs always instead of names. */ tmp = silc_argument_get_arg_type(cmd->args, 5, &tmp_len); if (!tmp) { /* Try get nickname */ tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len); if (tmp) { /* Get the nickname@server string and parse it */ if (!silc_parse_userfqdn(tmp, &query->nickname, &query->nick_server)) silc_server_query_add_error(server, query, TRUE, 1, SILC_STATUS_ERR_BAD_NICKNAME); } /* Try get server name */ tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len); if (tmp) query->server_name = silc_memdup(tmp, tmp_len); /* Get channel name */ tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len); if (tmp) query->channel_name = silc_memdup(tmp, tmp_len); } else { /* Parse the IDs included in the query */ query->ids = silc_calloc(argc, sizeof(*query->ids)); for (i = 0; i < argc; i++) { tmp = silc_argument_get_arg_type(cmd->args, i + 5, &tmp_len); if (!tmp) continue; id = silc_id_payload_parse_id(tmp, tmp_len, &id_type); if (!id) { silc_server_query_add_error(server, query, TRUE, i + 5, SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); continue; } query->ids[query->ids_count].id = id; query->ids[query->ids_count].id_type = id_type; query->ids_count++; } } /* Get the max count of reply messages allowed */ tmp = silc_argument_get_arg_type(cmd->args, 4, &tmp_len); if (tmp && tmp_len == sizeof(SilcUInt32)) SILC_GET32_MSB(query->reply_count, tmp); break; } /* Start processing the query information */ silc_server_query_process(server, 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. It is guaranteed that this function (which may be slow) is called only once for entire query. */ void silc_server_query_process(SilcServer server, SilcServerQuery query) { SilcServerCommandContext cmd = query->cmd; bool check_global = FALSE; void *entry; SilcClientEntry *clients = NULL, client_entry; SilcChannelEntry *channels = NULL; SilcServerEntry *servers = NULL; SilcUInt32 clients_count = 0, channels_count = 0, servers_count = 0; int i; /* Check global lists if query is coming from client or we are not normal server (we know global information). */ if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT) check_global = TRUE; else if (server->server_type != SILC_SERVER) check_global = TRUE; if (query->nickname) { /* Get all clients matching nickname from local list */ if (!silc_idlist_get_clients_by_hash(server->local_list, query->nickname, server->md5hash, &clients, &clients_count)) silc_idlist_get_clients_by_nickname(server->local_list, query->nickname, query->nick_server, &clients, &clients_count); /* Check global list as well */ if (check_global) { if (!silc_idlist_get_clients_by_hash(server->global_list, query->nickname, server->md5hash, &clients, &clients_count)) silc_idlist_get_clients_by_nickname(server->global_list, query->nickname, query->nick_server, &clients, &clients_count); } if (!clients) silc_server_query_add_error(server, query, TRUE, 1, SILC_STATUS_ERR_NO_SUCH_NICK); } if (query->server_name) { /* Find server by name */ entry = silc_idlist_find_server_by_name(server->local_list, query->server_name, TRUE, NULL); if (!entry && check_global) entry = silc_idlist_find_server_by_name(server->global_list, query->server_name, TRUE, NULL); if (entry) { servers = silc_realloc(servers, sizeof(*servers) * (servers_count + 1)); servers[servers_count++] = (SilcServerEntry)entry; } if (!servers) silc_server_query_add_error(server, query, TRUE, 2, SILC_STATUS_ERR_NO_SUCH_SERVER); } if (query->channel_name) { /* Find channel by name */ entry = silc_idlist_find_channel_by_name(server->local_list, query->channel_name, NULL); if (!entry && check_global) entry = silc_idlist_find_channel_by_name(server->global_list, query->channel_name, NULL); if (entry) { channels = silc_realloc(channels, sizeof(*channels) * (channels_count + 1)); channels[channels_count++] = (SilcChannelEntry)entry; } if (!channels) silc_server_query_add_error(server, query, TRUE, 3, SILC_STATUS_ERR_NO_SUCH_CHANNEL); } if (query->ids_count) { /* Find entries by the queried IDs */ for (i = 0; i < query->ids_count; i++) { void *id = query->ids[i].id; if (!id) continue; switch (query->ids[i].id_type) { case SILC_ID_CLIENT: /* Get client entry */ entry = silc_idlist_find_client_by_id(server->local_list, id, TRUE, NULL); if (!entry && check_global) entry = silc_idlist_find_client_by_id(server->global_list, id, TRUE, NULL); if (!entry) { silc_server_query_add_error(server, query, FALSE, i, SILC_STATUS_ERR_NO_SUCH_CLIENT_ID); continue; } clients = silc_realloc(clients, sizeof(*clients) * (clients_count + 1)); clients[clients_count++] = (SilcClientEntry)entry; break; case SILC_ID_SERVER: /* Get server entry */ entry = silc_idlist_find_server_by_id(server->local_list, id, TRUE, NULL); if (!entry && check_global) entry = silc_idlist_find_server_by_id(server->global_list, id, TRUE, NULL); if (!entry) { silc_server_query_add_error(server, query, FALSE, i, SILC_STATUS_ERR_NO_SUCH_SERVER_ID); continue; } servers = silc_realloc(servers, sizeof(*servers) * (servers_count + 1)); servers[servers_count++] = (SilcServerEntry)entry; break; case SILC_ID_CHANNEL: /* Get channel entry */ entry = silc_idlist_find_channel_by_id(server->local_list, id, NULL); if (!entry && check_global) entry = silc_idlist_find_channel_by_id(server->global_list, id, NULL); if (!entry) { silc_server_query_add_error(server, query, FALSE, i, SILC_STATUS_ERR_NO_SUCH_CHANNEL_ID); continue; } channels = silc_realloc(channels, sizeof(*channels) * (channels_count + 1)); channels[channels_count++] = (SilcChannelEntry)entry; break; default: break; } } } /* If nothing was found, then just send the errors */ if (!clients && !channels && !servers) { silc_server_query_send_reply(server, query, NULL, 0, NULL, 0, NULL, 0); silc_server_query_free(query); return; } /* Now process all found information and if necessary do some more querying. */ switch (query->querycmd) { case SILC_COMMAND_WHOIS: for (i = 0; i < clients_count; i++) { client_entry = clients[i]; if (!client_entry) 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) { if ((client_entry->nickname && client_entry->username && client_entry->userinfo) || !(client_entry->data.status & SILC_IDLIST_STATUS_REGISTERED)) { /* 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)) continue; } } /* 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) 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) 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) continue; if (client_entry->nickname || !(client_entry->data.status & SILC_IDLIST_STATUS_REGISTERED)) { /* 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)) continue; } /* Resolve the detailed client information */ silc_server_query_resolve(server, query, client_entry->router->connection, client_entry); } break; } if (!query->num_query) /* 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, SilcSocketConnection sock, SilcClientEntry client_entry) { #if 0 SilcUInt16 ident; 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) { return; } 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; silc_server_command_pending(server, SILC_COMMAND_NONE, ident, silc_server_query_resolve_reply, query); } else { ident = ++server->cmd_ident; switch (query->querycmd) { case SILC_COMMAND_WHOIS: case SILC_COMMAND_IDENTIFY: 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)); break; } silc_server_command_pending(server, query->querycmd, ident, silc_server_query_resolve_reply, query); } /* Mark the entry as being resolved */ client_entry->data.status |= SILC_IDLIST_STATUS_RESOLVING; client_entry->data.status &= ~SILC_IDLIST_STATUS_RESOLVED; client_entry->resovle_cmd_ident = ident; /* Save the query information, which we will reprocess after we get this and all other queries back. */ query->ids = silc_realloc(query->ids, sizeof(*query->ids) * (query->ids_count + 1)); if (query->ids) { query->ids[query->ids_count].id = silc_id_dup(id, id_type); query->ids[query->ids_count].id_type = id_type; query->ids[query->ids_count].ident = ident; query->ids_count++; } query->num_query++; #endif } /* 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; } 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) { } /* Find client by the Client ID indicated by the `client_id', and if not found then query it by using WHOIS command. The client information is also resolved if the cached information is incomplete or if the `always_resolve' is set to TRUE. The indication whether requested client was being resolved is saved into `resolved'. If the client is not being resolved its entry is returned by this function. NULL is returned if client is resolved. */ SilcClientEntry silc_server_query_client(SilcServer server, const SilcClientID *client_id, bool always_resolve, bool *resolved) { SilcClientEntry client; if (resolved) *resolved = FALSE; client = silc_idlist_find_client_by_id(server->local_list, (SilcClientID *)client_id, TRUE, NULL); if (!client) { client = silc_idlist_find_client_by_id(server->global_list, (SilcClientID *)client_id, TRUE, NULL); if (!client && server->server_type == SILC_ROUTER) return NULL; } if (!client && server->standalone) return NULL; if (!client || !client->nickname || !client->username || always_resolve) { SilcBuffer buffer, idp; if (client) { client->data.status |= SILC_IDLIST_STATUS_RESOLVING; client->data.status &= ~SILC_IDLIST_STATUS_RESOLVED; client->resolve_cmd_ident = ++server->cmd_ident; } idp = silc_id_payload_encode(client_id, SILC_ID_CLIENT); buffer = silc_command_payload_encode_va(SILC_COMMAND_WHOIS, server->cmd_ident, 1, 4, idp->data, idp->len); silc_server_packet_send(server, client ? client->router->connection : SILC_PRIMARY_ROUTE(server), SILC_PACKET_COMMAND, 0, buffer->data, buffer->len, FALSE); silc_buffer_free(idp); silc_buffer_free(buffer); if (resolved) *resolved = TRUE; return NULL; } return client; }