5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 2002 Pekka Riikonen
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; version 2 of the License.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
21 #include "serverincludes.h"
22 #include "server_internal.h"
30 SilcUInt32 index; /* Index to IDs */
31 bool from_cmd; /* TRUE if `index' is from command args,
32 otherwise from query->ids */
33 SilcStatus error; /* The actual error */
34 } *SilcServerQueryError;
37 SilcCommand querycmd; /* Query command */
38 SilcServerCommandContext cmd; /* Command context for query */
39 SilcUInt32 num_query; /* Number of ongoing queries left */
41 char *nickname; /* Queried nickname */
42 char *nick_server; /* Queried nickname's server */
43 char *server_name; /* Queried server name */
44 char *channel_name; /* Queried channel name */
45 SilcServerQueryID ids; /* Queried IDs */
46 SilcUInt32 ids_count; /* number of queried IDs */
47 SilcUInt32 reply_count; /* Requested reply count */
48 SilcDList attrs; /* Requested Attributes in WHOIS */
50 SilcServerQueryError errors; /* Query errors */
51 SilcUInt32 errors_count; /* number of errors */
54 void silc_server_query_free(SilcServerQuery query);
55 bool silc_server_query_check_error(SilcServer server,
56 SilcServerQuery query,
57 SilcServerCommandReplyContext cmdr);
58 void silc_server_query_send_error(SilcServer server,
59 SilcServerQuery query,
60 SilcStatus error, ...);
61 void silc_server_query_add_error(SilcServer server,
62 SilcServerQuery query,
66 void silc_server_query_send_router(SilcServer server, SilcServerQuery query);
67 void silc_server_query_send_router_reply(void *context, void *reply);
68 void silc_server_query_parse(SilcServer server, SilcServerQuery query);
69 void silc_server_query_process(SilcServer server, SilcServerQuery query);
70 void silc_server_query_resolve(SilcServer server, SilcServerQuery query,
71 SilcSocketConnection sock,
72 SilcClientEntry client_entry);
73 void silc_server_query_resolve_reply(void *context, void *reply);
74 void silc_server_query_send_reply(SilcServer server,
75 SilcServerQuery query,
76 SilcClientEntry *clients,
77 SilcUInt32 clients_count,
78 SilcServerEntry *servers,
79 SilcUInt32 servers_count,
80 SilcChannelEntry *channels,
81 SilcUInt32 channels_count);
83 /* Free the query context structure and all allocated resources. */
85 void silc_server_query_free(SilcServerQuery query)
89 silc_server_command_free(query->cmd);
91 silc_free(query->nickname);
92 silc_free(query->nick_server);
93 silc_free(query->server_name);
94 silc_free(query->channel_name);
96 for (i = 0; i < query->ids_count; i++)
97 silc_free(query->ids[i].id);
98 silc_free(query->ids);
101 silc_attribute_payload_list_free(query->attrs);
103 silc_free(query->errors);
105 memset(query, 'F', sizeof(*query));
109 /* Check whether command reply contained error, and reply the error to
110 the original sender if it occurred. */
112 bool silc_server_query_check_error(SilcServer server,
113 SilcServerQuery query,
114 SilcServerCommandReplyContext cmdr)
119 if (!silc_command_get_status(cmdr->payload, NULL, NULL)) {
122 /* Send the same command reply payload which contains the error */
123 silc_command_set_command(cmdr->payload, query->querycmd);
124 silc_command_set_ident(cmdr->payload,
125 silc_command_get_ident(query->cmd->payload));
126 buffer = silc_command_payload_encode_payload(cmdr->payload);
127 silc_server_packet_send(server, query->cmd->sock,
128 SILC_PACKET_COMMAND_REPLY, 0,
129 buffer->data, buffer->len, FALSE);
130 silc_buffer_free(buffer);
137 /* Send error reply indicated by the `error' to the original sender of
140 void silc_server_query_send_error(SilcServer server,
141 SilcServerQuery query,
142 SilcStatus error, ...)
146 unsigned char *data = NULL;
147 SilcUInt32 data_len = 0, data_type = 0, argc = 0;
150 data_type = va_arg(va, SilcUInt32);
153 data = va_arg(va, unsigned char *);
154 data_len = va_arg(va, SilcUInt32);
157 /* Send the command reply with error */
158 packet = silc_command_reply_payload_encode_va(
159 query->querycmd, error, 0,
160 silc_command_get_ident(query->cmd->payload),
161 argc, data_type, data, data_len);
162 silc_server_packet_send(server, query->cmd->sock,
163 SILC_PACKET_COMMAND_REPLY, 0,
164 packet->data, packet->len, FALSE);
166 silc_buffer_free(packet);
170 /* Add error to error list. Multiple errors may occur during the query
171 processing and this function can be used to add one error. The
172 `index' is the index to the command context which includes the argument
173 which caused the error, or it is the index to query->ids, depending
174 on value of `from_cmd'. */
176 void silc_server_query_add_error(SilcServer server,
177 SilcServerQuery query,
182 query->errors = silc_realloc(query->errors, sizeof(*query->errors) *
183 (query->errors_count + 1));
186 query->errors[query->errors_count].index = index;
187 query->errors[query->errors_count].from_cmd = from_cmd;
188 query->errors[query->errors_count].error = error;
189 query->errors_count++;
192 /* Processes query as command. The `query' is the command that is
193 being processed indicated by the `cmd'. The `query' can be one of
194 the following: SILC_COMMAND_WHOIS, SILC_COMMAND_WHOWAS or
195 SILC_COMMAND_IDENTIFY. This function handles the reply sending
196 to the entity who sent this query to us automatically. Returns
197 TRUE if the query is being processed or FALSE on error. */
199 bool silc_server_query_command(SilcServer server, SilcCommand querycmd,
200 SilcServerCommandContext cmd)
202 SilcServerQuery query;
204 query = silc_calloc(1, sizeof(*query));
205 query->querycmd = querycmd;
206 query->cmd = silc_server_command_dup(cmd);
210 case SILC_COMMAND_WHOIS:
211 /* If we are normal server and query contains nickname, send it
212 directly to router. */
213 if (server->server_type == SILC_SERVER && !server->standalone &&
214 silc_argument_get_arg_type(cmd->args, 1, NULL)) {
215 silc_server_query_send_router(server, query);
220 case SILC_COMMAND_WHOWAS:
221 /* WHOWAS query is always sent to router if we are normal server */
222 if (server->server_type == SILC_SERVER && !server->standalone) {
223 silc_server_query_send_router(server, query);
228 case SILC_COMMAND_IDENTIFY:
229 /* If we are normal server and query does not contain IDs, send it
230 directly to router (it contains nickname, server name or channel
232 if (server->server_type == SILC_SERVER && !server->standalone &&
233 !silc_argument_get_arg_type(cmd->args, 5, NULL)) {
234 silc_server_query_send_router(server, query);
240 SILC_LOG_ERROR(("Bad query using %d command", querycmd));
241 silc_server_query_free(query);
245 /* Now parse the request */
246 silc_server_query_parse(server, query);
251 /* Send the received query to our primary router since we could not
252 handle the query directly. We will reprocess the query after our
253 router replies back. */
255 void silc_server_query_send_router(SilcServer server, SilcServerQuery query)
258 SilcUInt16 old_ident;
260 /* Send WHOIS command to our router */
261 old_ident = silc_command_get_ident(query->cmd->payload);
262 silc_command_set_ident(query->cmd->payload, ++server->cmd_ident);
263 tmpbuf = silc_command_payload_encode_payload(query->cmd->payload);
264 silc_server_packet_send(server,
265 SILC_PRIMARY_ROUTE(server),
266 SILC_PACKET_COMMAND, 0,
267 tmpbuf->data, tmpbuf->len, TRUE);
268 silc_command_set_ident(query->cmd->payload, old_ident);
269 silc_buffer_free(tmpbuf);
271 /* Continue parsing the query after received reply from router */
272 silc_server_command_pending(server, query->querycmd, server->cmd_ident,
273 silc_server_query_send_router_reply, query);
276 /* Reply callback called after primary router has replied to our initial
277 sending of the query to it. We will proceed the query in this function. */
279 void silc_server_query_send_router_reply(void *context, void *reply)
281 SilcServerQuery query = context;
282 SilcServer server = query->cmd->server;
284 /* Check if router sent error reply */
285 if (!silc_server_query_check_error(server, query, reply)) {
286 silc_server_query_free(query);
290 /* Continue with parsing */
291 silc_server_query_parse(server, query);
294 /* Parse the command query and start processing the queries in detail. */
296 void silc_server_query_parse(SilcServer server, SilcServerQuery query)
298 SilcServerCommandContext cmd = query->cmd;
300 SilcUInt32 tmp_len, argc = silc_argument_get_arg_num(cmd->args);
305 switch (query->querycmd) {
307 case SILC_COMMAND_WHOIS:
308 /* Get Client IDs if present. Take IDs always instead of nickname. */
309 tmp = silc_argument_get_arg_type(cmd->args, 4, &tmp_len);
313 tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
315 silc_server_query_send_error(server, query,
316 SILC_STATUS_ERR_NOT_ENOUGH_PARAMS, 0);
317 silc_server_query_free(query);
321 /* Get the nickname@server string and parse it */
322 if (!silc_parse_userfqdn(tmp, &query->nickname, &query->nick_server)) {
323 silc_server_query_send_error(server, query,
324 SILC_STATUS_ERR_BAD_NICKNAME, 0);
325 silc_server_query_free(query);
330 /* Parse the IDs included in the query */
331 query->ids = silc_calloc(argc, sizeof(*query->ids));
333 for (i = 0; i < argc; i++) {
334 tmp = silc_argument_get_arg_type(cmd->args, i + 4, &tmp_len);
338 id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
340 silc_server_query_add_error(server, query, TRUE, i + 4,
341 SILC_STATUS_ERR_BAD_CLIENT_ID);
345 query->ids[query->ids_count].id = id;
346 query->ids[query->ids_count].id_type = SILC_ID_CLIENT;
351 /* Get the max count of reply messages allowed */
352 tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
353 if (tmp && tmp_len == sizeof(SilcUInt32))
354 SILC_GET32_MSB(query->reply_count, tmp);
356 /* Get requested attributes if set */
357 tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
359 query->attrs = silc_attribute_payload_parse_list(tmp, tmp_len);
362 case SILC_COMMAND_WHOWAS:
364 tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
366 silc_server_query_send_error(server, query,
367 SILC_STATUS_ERR_NOT_ENOUGH_PARAMS, 0);
368 silc_server_query_free(query);
372 /* Get the nickname@server string and parse it */
373 if (!silc_parse_userfqdn(tmp, &query->nickname, &query->nick_server)) {
374 silc_server_query_send_error(server, query,
375 SILC_STATUS_ERR_BAD_NICKNAME, 0);
376 silc_server_query_free(query);
380 /* Get the max count of reply messages allowed */
381 tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
382 if (tmp && tmp_len == sizeof(SilcUInt32))
383 SILC_GET32_MSB(query->reply_count, tmp);
386 case SILC_COMMAND_IDENTIFY:
387 /* Get IDs if present. Take IDs always instead of names. */
388 tmp = silc_argument_get_arg_type(cmd->args, 5, &tmp_len);
391 /* Try get nickname */
392 tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len);
394 /* Get the nickname@server string and parse it */
395 if (!silc_parse_userfqdn(tmp, &query->nickname, &query->nick_server))
396 silc_server_query_add_error(server, query, TRUE, 1,
397 SILC_STATUS_ERR_BAD_NICKNAME);
400 /* Try get server name */
401 tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
403 query->server_name = silc_memdup(tmp, tmp_len);
405 /* Get channel name */
406 tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
408 query->channel_name = silc_memdup(tmp, tmp_len);
411 /* Parse the IDs included in the query */
412 query->ids = silc_calloc(argc, sizeof(*query->ids));
414 for (i = 0; i < argc; i++) {
415 tmp = silc_argument_get_arg_type(cmd->args, i + 5, &tmp_len);
419 id = silc_id_payload_parse_id(tmp, tmp_len, &id_type);
421 silc_server_query_add_error(server, query, TRUE, i + 5,
422 SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
426 query->ids[query->ids_count].id = id;
427 query->ids[query->ids_count].id_type = id_type;
432 /* Get the max count of reply messages allowed */
433 tmp = silc_argument_get_arg_type(cmd->args, 4, &tmp_len);
434 if (tmp && tmp_len == sizeof(SilcUInt32))
435 SILC_GET32_MSB(query->reply_count, tmp);
439 /* Start processing the query information */
440 silc_server_query_process(server, query);
443 /* Processes the parsed query. This does the actual finding of the
444 queried information and prepares for sending reply to the original
445 sender of the query command. It is guaranteed that this function
446 (which may be slow) is called only once for entire query. */
448 void silc_server_query_process(SilcServer server, SilcServerQuery query)
450 SilcServerCommandContext cmd = query->cmd;
451 bool check_global = FALSE;
453 SilcClientEntry *clients = NULL, client_entry;
454 SilcChannelEntry *channels = NULL, channel_entry;
455 SilcServerEntry *servers = NULL, server_entry;
456 SilcUInt32 clients_count = 0, channels_count = 0, servers_count = 0;
459 /* Check global lists if query is coming from client or we are not
460 normal server (we know global information). */
461 if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT)
463 else if (server->server_type != SILC_SERVER)
466 if (query->nickname) {
467 /* Get all clients matching nickname from local list */
468 if (!silc_idlist_get_clients_by_hash(server->local_list,
469 query->nickname, server->md5hash,
470 &clients, &clients_count))
471 silc_idlist_get_clients_by_nickname(server->local_list,
474 &clients, &clients_count);
476 /* Check global list as well */
478 if (!silc_idlist_get_clients_by_hash(server->global_list,
479 query->nickname, server->md5hash,
480 &clients, &clients_count))
481 silc_idlist_get_clients_by_nickname(server->global_list,
484 &clients, &clients_count);
488 silc_server_query_add_error(server, query, TRUE, 1,
489 SILC_STATUS_ERR_NO_SUCH_NICK);
492 if (query->server_name) {
493 /* Find server by name */
494 entry = silc_idlist_find_server_by_name(server->local_list,
495 query->server_name, TRUE, NULL);
496 if (!entry && check_global)
497 entry = silc_idlist_find_server_by_name(server->global_list,
498 query->server_name, TRUE, NULL);
500 servers = silc_realloc(servers, sizeof(*servers) * (servers_count + 1));
501 servers[servers_count++] = (SilcServerEntry)entry;
505 silc_server_query_add_error(server, query, TRUE, 2,
506 SILC_STATUS_ERR_NO_SUCH_SERVER);
509 if (query->channel_name) {
510 /* Find channel by name */
511 entry = silc_idlist_find_channel_by_name(server->local_list,
512 query->channel_name, NULL);
513 if (!entry && check_global)
514 entry = silc_idlist_find_channel_by_name(server->global_list,
515 query->channel_name, NULL);
517 channels = silc_realloc(channels, sizeof(*channels) *
518 (channels_count + 1));
519 channels[channels_count++] = (SilcChannelEntry)entry;
523 silc_server_query_add_error(server, query, TRUE, 3,
524 SILC_STATUS_ERR_NO_SUCH_CHANNEL);
527 if (query->ids_count) {
528 /* Find entries by the queried IDs */
529 for (i = 0; i < query->ids_count; i++) {
530 void *id = query->ids[i].id;
534 switch (query->ids[i].id_type) {
537 /* Get client entry */
538 entry = silc_idlist_find_client_by_id(server->local_list,
540 if (!entry && check_global)
541 entry = silc_idlist_find_client_by_id(server->global_list,
544 silc_server_query_add_error(server, query, FALSE, i,
545 SILC_STATUS_ERR_NO_SUCH_CLIENT_ID);
549 clients = silc_realloc(clients, sizeof(*clients) *
550 (clients_count + 1));
551 clients[clients_count++] = (SilcClientEntry)entry;
555 /* Get server entry */
556 entry = silc_idlist_find_server_by_id(server->local_list,
558 if (!entry && check_global)
559 entry = silc_idlist_find_server_by_id(server->global_list,
562 silc_server_query_add_error(server, query, FALSE, i,
563 SILC_STATUS_ERR_NO_SUCH_SERVER_ID);
567 servers = silc_realloc(servers, sizeof(*servers) *
568 (servers_count + 1));
569 servers[servers_count++] = (SilcServerEntry)entry;
572 case SILC_ID_CHANNEL:
573 /* Get channel entry */
574 entry = silc_idlist_find_channel_by_id(server->local_list, id, NULL);
575 if (!entry && check_global)
576 entry = silc_idlist_find_channel_by_id(server->global_list, id,
579 silc_server_query_add_error(server, query, FALSE, i,
580 SILC_STATUS_ERR_NO_SUCH_CHANNEL_ID);
584 channels = silc_realloc(channels, sizeof(*channels) *
585 (channels_count + 1));
586 channels[channels_count++] = (SilcChannelEntry)entry;
595 /* If nothing was found, then just send the errors */
596 if (!clients && !channels && !servers) {
597 silc_server_query_send_reply(server, query, NULL, 0, NULL, 0, NULL, 0);
598 silc_server_query_free(query);
602 /* Now process all found information and if necessary do some more
604 switch (query->querycmd) {
606 case SILC_COMMAND_WHOIS:
609 case SILC_COMMAND_WHOWAS:
610 for (i = 0; i < clients_count; i++) {
611 client_entry = clients[i];
613 /* Check if cannot query this anyway, so take next one */
614 if (!client_entry || !client_entry->router)
617 /* If both nickname and username are present no resolving is needed */
618 if (client_entry->nickname && client_entry->username)
621 /* Resolve the detailed client information */
622 silc_server_query_resolve(server, query,
623 client_entry->router->connection,
628 case SILC_COMMAND_IDENTIFY:
629 for (i = 0; i < clients_count; i++) {
630 client_entry = clients[i];
632 /* Check if cannot query this anyway, so take next one */
633 if (!client_entry || !client_entry->router)
636 if (client_entry->nickname ||
637 !(client_entry->data.status & SILC_IDLIST_STATUS_REGISTERED)) {
638 /* If we are router, client is local to us, or client is on channel
639 we do not need to resolve the client information. */
640 if (server->server_type != SILC_SERVER || SILC_IS_LOCAL(client_entry)
641 || silc_hash_table_count(client_entry->channels))
645 /* Resolve the detailed client information */
646 silc_server_query_resolve(server, query,
647 client_entry->router->connection,
653 /* If we didn't have to do any resolving, continue with sending the
654 command reply to the original sender. */
655 if (!query->num_query)
656 silc_server_query_send_reply(server, query, clients, clients_count,
657 servers, servers_count, channels,
665 /* Resolve the detailed information for the `client_entry'. Only client
666 information needs to be resolved for being incomplete. Each incomplete
667 client entry calls this function to do the resolving. */
669 void silc_server_query_resolve(SilcServer server, SilcServerQuery query,
670 SilcSocketConnection sock,
671 SilcClientEntry client_entry)
679 if (client_entry->data.status & SILC_IDLIST_STATUS_RESOLVING) {
680 /* The entry is being resolved by some other external query already.
681 Attach to that query instead of resolving again. */
682 ident = client_entry->resolve_cmd_ident;
683 silc_server_command_pending(server, SILC_COMMAND_NONE, ident,
684 silc_server_query_resolve_reply, query);
686 ident = ++server->cmd_ident;
688 switch (query->querycmd) {
690 case SILC_COMMAND_WHOIS:
691 case SILC_COMMAND_IDENTIFY:
694 case SILC_COMMAND_WHOWAS:
695 /* We must send WHOWAS command since it's the only the way of
696 resolving clients that are not present in the network anymore. */
697 silc_server_send_command(server, sock, query->querycmd, ident, 1,
698 1, query->nickname, strlen(query->nickname));
702 silc_server_command_pending(server, query->querycmd, ident,
703 silc_server_query_resolve_reply, query);
706 /* Mark the entry as being resolved */
707 client_entry->data.status |= SILC_IDLIST_STATUS_RESOLVING;
708 client_entry->data.status &= ~SILC_IDLIST_STATUS_RESOLVED;
709 client_entry->resovle_cmd_ident = ident;
711 /* Save the query information, which we will reprocess after we
712 get this and all other queries back. */
713 query->ids = silc_realloc(query->ids, sizeof(*query->ids) *
714 (query->ids_count + 1));
716 query->ids[query->ids_count].id = silc_id_dup(id, id_type);
717 query->ids[query->ids_count].id_type = id_type;
718 query->ids[query->ids_count].ident = ident;
725 /* Reply callback called after one resolving has been completed. If
726 all resolvings has been received then we will continue with sending
727 the command reply to the original sender of the query. */
729 void silc_server_query_resolve_reply(void *context, void *reply)
731 SilcServerQuery query = context;
736 void silc_server_query_send_reply(SilcServer server,
737 SilcServerQuery query,
738 SilcClientEntry *clients,
739 SilcUInt32 clients_count,
740 SilcServerEntry *servers,
741 SilcUInt32 servers_count,
742 SilcChannelEntry *channels,
743 SilcUInt32 channels_count)
748 /* Find client by the Client ID indicated by the `client_id', and if not
749 found then query it by using WHOIS command. The client information
750 is also resolved if the cached information is incomplete or if the
751 `always_resolve' is set to TRUE. The indication whether requested
752 client was being resolved is saved into `resolved'. If the client
753 is not being resolved its entry is returned by this function. NULL
754 is returned if client is resolved. */
756 SilcClientEntry silc_server_query_client(SilcServer server,
757 const SilcClientID *client_id,
761 SilcClientEntry client;
766 client = silc_idlist_find_client_by_id(server->local_list,
767 (SilcClientID *)client_id,
770 client = silc_idlist_find_client_by_id(server->global_list,
771 (SilcClientID *)client_id,
773 if (!client && server->server_type == SILC_ROUTER)
777 if (!client && server->standalone)
780 if (!client || !client->nickname || !client->username ||
782 SilcBuffer buffer, idp;
785 client->data.status |= SILC_IDLIST_STATUS_RESOLVING;
786 client->data.status &= ~SILC_IDLIST_STATUS_RESOLVED;
787 client->resolve_cmd_ident = ++server->cmd_ident;
790 idp = silc_id_payload_encode(client_id, SILC_ID_CLIENT);
791 buffer = silc_command_payload_encode_va(SILC_COMMAND_WHOIS,
792 server->cmd_ident, 1,
793 4, idp->data, idp->len);
794 silc_server_packet_send(server, client ? client->router->connection :
795 SILC_PRIMARY_ROUTE(server),
796 SILC_PACKET_COMMAND, 0,
797 buffer->data, buffer->len, FALSE);
798 silc_buffer_free(idp);
799 silc_buffer_free(buffer);