updates.
[silc.git] / lib / silcclient / idlist.c
1 /*
2
3   idlist.c
4
5   Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
6
7   Copyright (C) 2000 Pekka Riikonen
8
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; either version 2 of the License, or
12   (at your option) any later version.
13   
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19 */
20 /* $Id$ */
21
22 #include "clientlibincludes.h"
23
24 typedef struct {
25   SilcClientCommandContext cmd;
26   SilcGetClientCallback completion;
27   char *nickname;
28   char *server;
29   void *context;
30   int found;
31 } *GetClientInternal;
32
33 SILC_CLIENT_CMD_FUNC(get_client_callback)
34 {
35   GetClientInternal i = (GetClientInternal)context;
36   SilcClientEntry *clients;
37   uint32 clients_count;
38
39   /* Get the clients */
40   clients = silc_client_get_clients_local(i->cmd->client, i->cmd->conn,
41                                           i->nickname, i->server,
42                                           &clients_count);
43   if (clients) {
44     i->completion(i->cmd->client, i->cmd->conn, clients, 
45                   clients_count, i->context);
46     i->found = TRUE;
47     silc_free(clients);
48   }
49 }
50
51 static void silc_client_get_client_destructor(void *context)
52 {
53   GetClientInternal i = (GetClientInternal)context;
54
55   if (i->found == FALSE)
56     i->completion(i->cmd->client, i->cmd->conn, NULL, 0, i->context);
57
58   silc_client_command_free(i->cmd);
59   if (i->nickname)
60     silc_free(i->nickname);
61   if (i->server)
62     silc_free(i->server);
63   silc_free(i);
64 }
65
66 /* Finds client entry or entries by the `nickname' and `server'. The 
67    completion callback will be called when the client entries has been found.
68
69    Note: this function is always asynchronous and resolves the client
70    information from the server. Thus, if you already know the client
71    information then use the silc_client_get_client_by_id function to
72    get the client entry since this function may be very slow and should
73    be used only to initially get the client entries. */
74
75 void silc_client_get_clients(SilcClient client,
76                              SilcClientConnection conn,
77                              char *nickname,
78                              char *server,
79                              SilcGetClientCallback completion,
80                              void *context)
81 {
82   char ident[512];
83   SilcClientCommandContext ctx;
84   GetClientInternal i = silc_calloc(1, sizeof(*i));
85       
86   /* No ID found. Do query from the server. The query is done by 
87      sending simple IDENTIFY command to the server. */
88   ctx = silc_client_command_alloc();
89   ctx->client = client;
90   ctx->conn = conn;
91   ctx->command = silc_client_command_find("IDENTIFY");
92   memset(ident, 0, sizeof(ident));
93   snprintf(ident, sizeof(ident), "IDENTIFY %s", nickname);
94   silc_parse_command_line(ident, &ctx->argv, &ctx->argv_lens, 
95                           &ctx->argv_types, &ctx->argc, 2);
96
97   i->cmd = silc_client_command_dup(ctx);
98   i->nickname = nickname ? strdup(nickname) : NULL;
99   i->server = server ? strdup(server) : NULL;
100   i->completion = completion;
101   i->context = context;
102
103   /* Call the command */
104   ctx->command->cb(ctx, NULL);
105
106   /* Add pending callback */
107   silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, 
108                               conn->cmd_ident, 
109                               silc_client_get_client_destructor,
110                               silc_client_command_get_client_callback, 
111                               (void *)i);
112 }
113
114 /* Same as above function but does not resolve anything from the server.
115    This checks local cache and returns all clients from the cache. */
116
117 SilcClientEntry *silc_client_get_clients_local(SilcClient client,
118                                                SilcClientConnection conn,
119                                                char *nickname,
120                                                char *server,
121                                                uint32 *clients_count)
122 {
123   SilcIDCacheEntry id_cache;
124   SilcIDCacheList list = NULL;
125   SilcClientEntry entry, *clients;
126   int i = 0;
127
128   /* Find ID from cache */
129   if (!silc_idcache_find_by_name(conn->client_cache, nickname, &list))
130     return NULL;
131
132   if (!silc_idcache_list_count(list)) {
133     silc_idcache_list_free(list);
134     return NULL;
135   }
136
137   clients = silc_calloc(silc_idcache_list_count(list), sizeof(*clients));
138   *clients_count = silc_idcache_list_count(list);
139
140   if (!server) {
141     /* Take all without any further checking */
142     silc_idcache_list_first(list, &id_cache);
143     while (id_cache) {
144       clients[i++] = id_cache->context;
145       if (!silc_idcache_list_next(list, &id_cache))
146         break;
147     }
148   } else {
149     /* Check multiple cache entries for match */
150     silc_idcache_list_first(list, &id_cache);
151     while (id_cache) {
152       entry = (SilcClientEntry)id_cache->context;
153       
154       if (entry->server && 
155           strncasecmp(server, entry->server, strlen(server))) {
156         if (!silc_idcache_list_next(list, &id_cache)) {
157           break;
158         } else {
159           continue;
160         }
161       }
162       
163       clients[i++] = id_cache->context;
164       if (!silc_idcache_list_next(list, &id_cache))
165         break;
166     }
167   }
168
169   if (list)
170     silc_idcache_list_free(list);
171
172   return clients;
173 }
174
175 typedef struct {
176   SilcClient client;
177   SilcClientConnection conn;
178   uint32 list_count;
179   SilcBuffer client_id_list;
180   SilcGetClientCallback completion;
181   void *context;
182   int found;
183 } *GetClientsByListInternal;
184
185 SILC_CLIENT_CMD_FUNC(get_clients_list_callback)
186 {
187   GetClientsByListInternal i = (GetClientsByListInternal)context;
188   SilcIDCacheEntry id_cache = NULL;
189   SilcBuffer client_id_list = i->client_id_list;
190   SilcClientEntry *clients = NULL;
191   uint32 clients_count = 0;
192   int c;
193
194   for (c = 0; c < i->list_count; c++) {
195     uint16 idp_len;
196     SilcClientID *client_id;
197
198     /* Get Client ID */
199     SILC_GET16_MSB(idp_len, client_id_list->data + 2);
200     idp_len += 4;
201     client_id = silc_id_payload_parse_id(client_id_list->data, idp_len);
202     if (!client_id)
203       continue;
204
205     /* Get the client entry */
206     if (silc_idcache_find_by_id_one_ext(i->conn->client_cache, 
207                                         (void *)client_id, 
208                                         NULL, NULL, 
209                                         silc_hash_client_id_compare, NULL,
210                                         &id_cache)) {
211       clients = silc_realloc(clients, sizeof(*clients) * 
212                              (clients_count + 1));
213       clients[clients_count] = (SilcClientEntry)id_cache->context;
214       clients_count++;
215       i->found = TRUE;
216     }
217
218     silc_free(client_id);
219     silc_buffer_pull(client_id_list, idp_len);
220   }
221
222   if (i->found) {
223     i->completion(i->client, i->conn, clients, clients_count, i->context);
224     silc_free(clients);
225   }
226 }
227
228 static void silc_client_get_clients_list_destructor(void *context)
229 {
230   GetClientsByListInternal i = (GetClientsByListInternal)context;
231
232   if (i->found == FALSE)
233     i->completion(i->client, i->conn, NULL, 0, i->context);
234
235   if (i->client_id_list)
236     silc_buffer_free(i->client_id_list);
237   silc_free(i);
238 }
239
240 /* Gets client entries by the list of client ID's `client_id_list'. This
241    always resolves those client ID's it does not know yet from the server
242    so this function might take a while. The `client_id_list' is a list
243    of ID Payloads added one after other.  JOIN command reply and USERS
244    command reply for example returns this sort of list. The `completion'
245    will be called after the entries are available. */
246
247 void silc_client_get_clients_by_list(SilcClient client,
248                                      SilcClientConnection conn,
249                                      uint32 list_count,
250                                      SilcBuffer client_id_list,
251                                      SilcGetClientCallback completion,
252                                      void *context)
253 {
254   SilcIDCacheEntry id_cache = NULL;
255   int i;
256   unsigned char **res_argv = NULL;
257   uint32 *res_argv_lens = NULL, *res_argv_types = NULL, res_argc = 0;
258   GetClientsByListInternal in;
259
260   in = silc_calloc(1, sizeof(*in));
261   in->client = client;
262   in->conn = conn;
263   in->list_count = list_count;
264   in->client_id_list = silc_buffer_copy(client_id_list);
265   in->completion = completion;
266   in->context = context;
267
268   for (i = 0; i < list_count; i++) {
269     uint16 idp_len;
270     SilcClientID *client_id;
271     SilcClientEntry entry;
272
273     /* Get Client ID */
274     SILC_GET16_MSB(idp_len, client_id_list->data + 2);
275     idp_len += 4;
276     client_id = silc_id_payload_parse_id(client_id_list->data, idp_len);
277     if (!client_id)
278       continue;
279
280     /* Check if we have this client cached already. */
281     id_cache = NULL;
282     silc_idcache_find_by_id_one_ext(conn->client_cache, (void *)client_id, 
283                                     NULL, NULL, 
284                                     silc_hash_client_id_compare, NULL,
285                                     &id_cache);
286
287     /* If we don't have the entry or it has incomplete info, then resolve
288        it from the server. */
289     entry = id_cache ? (SilcClientEntry)id_cache->context : NULL;
290     if (!id_cache || !entry->nickname) {
291       /* No we don't have it, query it from the server. Assemble argument
292          table that will be sent fr the IDENTIFY command later. */
293       res_argv = silc_realloc(res_argv, sizeof(*res_argv) *
294                               (res_argc + 1));
295       res_argv_lens = silc_realloc(res_argv_lens, sizeof(*res_argv_lens) *
296                                    (res_argc + 1));
297       res_argv_types = silc_realloc(res_argv_types, sizeof(*res_argv_types) *
298                                     (res_argc + 1));
299       res_argv[res_argc] = client_id_list->data;
300       res_argv_lens[res_argc] = idp_len;
301       res_argv_types[res_argc] = res_argc + 5;
302       res_argc++;
303     }
304
305     silc_free(client_id);
306     silc_buffer_pull(client_id_list, idp_len);
307   }
308
309   /* Query the client information from server if the list included clients
310      that we don't know about. */
311   if (res_argc) {
312     SilcBuffer res_cmd;
313
314     /* Send the IDENTIFY command to server */
315     res_cmd = silc_command_payload_encode(SILC_COMMAND_IDENTIFY,
316                                           res_argc, res_argv, res_argv_lens,
317                                           res_argv_types, ++conn->cmd_ident);
318     silc_client_packet_send(client, conn->sock, SILC_PACKET_COMMAND, 
319                             NULL, 0, NULL, NULL, res_cmd->data, res_cmd->len,
320                             TRUE);
321
322     silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, 
323                                 conn->cmd_ident, 
324                                 silc_client_get_clients_list_destructor,
325                                 silc_client_command_get_clients_list_callback, 
326                                 (void *)in);
327
328     silc_buffer_push(client_id_list, client_id_list->data - 
329                      client_id_list->head);
330     silc_buffer_free(res_cmd);
331     silc_free(res_argv);
332     silc_free(res_argv_lens);
333     silc_free(res_argv_types);
334     return;
335   }
336
337   silc_buffer_push(client_id_list, client_id_list->data - 
338                    client_id_list->head);
339
340   /* We have the clients in cache, get them and call the completion */
341   silc_client_command_get_clients_list_callback((void *)in, NULL);
342 }
343
344 /* The old style function to find client entry. This is used by the
345    library internally. If `query' is TRUE then the client information is
346    requested by the server. The pending command callback must be set
347    by the caller. */
348
349 SilcClientEntry silc_idlist_get_client(SilcClient client,
350                                        SilcClientConnection conn,
351                                        char *nickname,
352                                        char *server,
353                                        uint32 num,
354                                        int query)
355 {
356   SilcIDCacheEntry id_cache;
357   SilcIDCacheList list = NULL;
358   SilcClientEntry entry = NULL;
359
360   /* Find ID from cache */
361   if (!silc_idcache_find_by_name(conn->client_cache, nickname, &list)) {
362   identify:
363
364     if (query) {
365       char ident[512];
366       SilcClientCommandContext ctx;
367       
368       SILC_LOG_DEBUG(("Requesting Client ID from server"));
369       
370       /* No ID found. Do query from the server. The query is done by 
371          sending simple IDENTIFY command to the server. */
372       ctx = silc_client_command_alloc();
373       ctx->client = client;
374       ctx->conn = conn;
375       ctx->command = silc_client_command_find("IDENTIFY");
376       memset(ident, 0, sizeof(ident));
377       snprintf(ident, sizeof(ident), "IDENTIFY %s", nickname);
378       silc_parse_command_line(ident, &ctx->argv, &ctx->argv_lens, 
379                               &ctx->argv_types, &ctx->argc, 2);
380       ctx->command->cb(ctx, NULL);
381       
382       if (list)
383         silc_idcache_list_free(list);
384
385       return NULL;
386     }
387     return NULL;
388   }
389
390   if (!server && !num) {
391     /* Take first found cache entry */
392     if (!silc_idcache_list_first(list, &id_cache))
393       goto identify;
394
395     entry = (SilcClientEntry)id_cache->context;
396   } else {
397     /* Check multiple cache entries for match */
398     silc_idcache_list_first(list, &id_cache);
399     entry = (SilcClientEntry)id_cache->context;
400     
401     while (entry) {
402       if (server && entry->server && 
403           !strncasecmp(server, entry->server, strlen(server)))
404         break;
405       
406       if (num && entry->num == num)
407         break;
408
409       if (!silc_idcache_list_next(list, &id_cache)) {
410         entry = NULL;
411         break;
412       }
413
414       entry = (SilcClientEntry)id_cache->context;
415     }
416
417     /* If match weren't found, request it */
418     if (!entry)
419       goto identify;
420   }
421
422   if (list)
423     silc_idcache_list_free(list);
424
425   return entry;
426 }
427
428 /* Finds entry for client by the client's ID. Returns the entry or NULL
429    if the entry was not found. */
430
431 SilcClientEntry silc_client_get_client_by_id(SilcClient client,
432                                              SilcClientConnection conn,
433                                              SilcClientID *client_id)
434 {
435   SilcIDCacheEntry id_cache;
436
437   SILC_LOG_DEBUG(("Finding client by ID (%s)", 
438                   silc_id_render(client_id, SILC_ID_CLIENT)));
439
440   /* Find ID from cache */
441   if (!silc_idcache_find_by_id_one_ext(conn->client_cache, (void *)client_id, 
442                                        NULL, NULL, 
443                                        silc_hash_client_id_compare, NULL,
444                                        &id_cache))
445     return NULL;
446
447   SILC_LOG_DEBUG(("Found"));
448
449   return (SilcClientEntry)id_cache->context;
450 }
451
452 typedef struct {
453   SilcClient client;
454   SilcClientConnection conn;
455   SilcClientID *client_id;
456   SilcGetClientCallback completion;
457   void *context;
458   int found;
459 } *GetClientByIDInternal;
460
461 SILC_CLIENT_CMD_FUNC(get_client_by_id_callback)
462 {
463   GetClientByIDInternal i = (GetClientByIDInternal)context;
464   SilcClientEntry entry;
465
466   /* Get the client */
467   entry = silc_client_get_client_by_id(i->client, i->conn,
468                                        i->client_id);
469   if (entry) {
470     i->completion(i->client, i->conn, &entry, 1, i->context);
471     i->found = TRUE;
472   }
473 }
474
475 static void silc_client_get_client_by_id_destructor(void *context)
476 {
477   GetClientByIDInternal i = (GetClientByIDInternal)context;
478
479   if (i->found == FALSE)
480     i->completion(i->client, i->conn, NULL, 0, i->context);
481
482   if (i->client_id)
483     silc_free(i->client_id);
484   silc_free(i);
485 }
486
487 /* Same as above but will always resolve the information from the server.
488    Use this only if you know that you don't have the entry and the only
489    thing you know about the client is its ID. */
490
491 void silc_client_get_client_by_id_resolve(SilcClient client,
492                                           SilcClientConnection conn,
493                                           SilcClientID *client_id,
494                                           SilcGetClientCallback completion,
495                                           void *context)
496 {
497   SilcBuffer idp;
498   GetClientByIDInternal i = silc_calloc(1, sizeof(*i));
499
500   idp = silc_id_payload_encode(client_id, SILC_ID_CLIENT);
501   silc_client_send_command(client, conn, SILC_COMMAND_WHOIS, 
502                            ++conn->cmd_ident,
503                            1, 3, idp->data, idp->len);
504   silc_buffer_free(idp);
505
506   i->client = client;
507   i->conn = conn;
508   i->client_id = silc_id_dup(client_id, SILC_ID_CLIENT);
509   i->completion = completion;
510   i->context = context;
511       
512   /* Add pending callback */
513   silc_client_command_pending(conn, SILC_COMMAND_WHOIS, 
514                               conn->cmd_ident, 
515                               silc_client_get_client_by_id_destructor,
516                               silc_client_command_get_client_by_id_callback, 
517                               (void *)i);
518 }
519
520 /* Deletes the client entry and frees all memory. */
521
522 void silc_client_del_client_entry(SilcClient client, 
523                                   SilcClientEntry client_entry)
524 {
525   silc_free(client_entry->nickname);
526   silc_free(client_entry->username);
527   silc_free(client_entry->realname);
528   silc_free(client_entry->server);
529   silc_free(client_entry->id);
530   if (client_entry->send_key)
531     silc_cipher_free(client_entry->send_key);
532   if (client_entry->receive_key)
533     silc_cipher_free(client_entry->receive_key);
534   silc_free(client_entry->key);
535   silc_free(client_entry);
536 }
537
538 /* Removes client from the cache by the client entry. */
539
540 bool silc_client_del_client(SilcClient client, SilcClientConnection conn,
541                             SilcClientEntry client_entry)
542 {
543   bool ret = silc_idcache_del_by_context(conn->client_cache, client_entry);
544   silc_client_del_client_entry(client, client_entry);
545   return ret;
546 }
547
548 /* Removes channel from the cache by the channel entry. */
549
550 bool silc_client_del_channel(SilcClient client, SilcClientConnection conn,
551                              SilcChannelEntry channel)
552 {
553   bool ret = silc_idcache_del_by_context(conn->channel_cache, channel);
554   silc_free(channel->channel_name);
555   silc_free(channel->id);
556   silc_free(channel->key);
557   if (channel->channel_key)
558     silc_cipher_free(channel->channel_key);
559   if (channel->hmac)
560     silc_hmac_free(channel->hmac);
561   silc_client_del_channel_private_keys(client, conn, channel);
562   silc_free(channel);
563   return ret;
564 }
565
566 /* Finds entry for channel by the channel name. Returns the entry or NULL
567    if the entry was not found. It is found only if the client is joined
568    to the channel. */
569
570 SilcChannelEntry silc_client_get_channel(SilcClient client,
571                                          SilcClientConnection conn,
572                                          char *channel)
573 {
574   SilcIDCacheEntry id_cache;
575   SilcChannelEntry entry;
576
577   if (!silc_idcache_find_by_name_one(conn->channel_cache, channel, 
578                                      &id_cache))
579     return NULL;
580
581   entry = (SilcChannelEntry)id_cache->context;
582
583   return entry;
584 }
585
586 /* Finds entry for channel by the channel ID. Returns the entry or NULL
587    if the entry was not found. It is found only if the client is joined
588    to the channel. */
589
590 SilcChannelEntry silc_client_get_channel_by_id(SilcClient client,
591                                                SilcClientConnection conn,
592                                                SilcChannelID *channel_id)
593 {
594   SilcIDCacheEntry id_cache;
595   SilcChannelEntry entry;
596
597   if (!silc_idcache_find_by_id_one(conn->channel_cache, channel_id, 
598                                    &id_cache))
599     return NULL;
600
601   entry = (SilcChannelEntry)id_cache->context;
602
603   return entry;
604 }
605
606 typedef struct {
607   SilcClient client;
608   SilcClientConnection conn;
609   SilcChannelID *channel_id;
610   SilcGetChannelCallback completion;
611   void *context;
612   int found;
613 } *GetChannelByIDInternal;
614
615 SILC_CLIENT_CMD_FUNC(get_channel_by_id_callback)
616 {
617   GetChannelByIDInternal i = (GetChannelByIDInternal)context;
618   SilcChannelEntry entry;
619
620   /* Get the channel */
621   entry = silc_client_get_channel_by_id(i->client, i->conn,
622                                         i->channel_id);
623   if (entry) {
624     i->completion(i->client, i->conn, &entry, 1, i->context);
625     i->found = TRUE;
626   }
627 }
628
629 static void silc_client_get_channel_by_id_destructor(void *context)
630 {
631   GetChannelByIDInternal i = (GetChannelByIDInternal)context;
632
633   if (i->found == FALSE)
634     i->completion(i->client, i->conn, NULL, 0, i->context);
635
636   silc_free(i->channel_id);
637   silc_free(i);
638 }
639
640 /* Resolves channel information from the server by the channel ID. */
641
642 void silc_client_get_channel_by_id_resolve(SilcClient client,
643                                            SilcClientConnection conn,
644                                            SilcChannelID *channel_id,
645                                            SilcGetChannelCallback completion,
646                                            void *context)
647 {
648   SilcBuffer idp;
649   GetChannelByIDInternal i = silc_calloc(1, sizeof(*i));
650
651   idp = silc_id_payload_encode(channel_id, SILC_ID_CHANNEL);
652   silc_client_send_command(client, conn, SILC_COMMAND_IDENTIFY, 
653                            ++conn->cmd_ident,
654                            1, 5, idp->data, idp->len);
655   silc_buffer_free(idp);
656
657   i->client = client;
658   i->conn = conn;
659   i->channel_id = silc_id_dup(channel_id, SILC_ID_CHANNEL);
660   i->completion = completion;
661   i->context = context;
662       
663   /* Add pending callback */
664   silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, 
665                               conn->cmd_ident, 
666                               silc_client_get_channel_by_id_destructor,
667                               silc_client_command_get_channel_by_id_callback, 
668                               (void *)i);
669 }
670
671 /* Find channel entry by ID. This routine is used internally by the library. */
672
673 SilcChannelEntry silc_idlist_get_channel_by_id(SilcClient client,
674                                                SilcClientConnection conn,
675                                                SilcChannelID *channel_id,
676                                                int query)
677 {
678   SilcBuffer idp;
679   SilcChannelEntry channel;
680
681   channel = silc_client_get_channel_by_id(client, conn, channel_id);
682   if (channel)
683     return channel;
684
685   if (query) {
686     idp = silc_id_payload_encode(channel_id, SILC_ID_CHANNEL);
687     silc_client_send_command(client, conn, SILC_COMMAND_IDENTIFY, 
688                              ++conn->cmd_ident,
689                              1, 5, idp->data, idp->len);
690     silc_buffer_free(idp);
691   }
692
693   return NULL;
694 }
695
696 /* Finds entry for server by the server name. */
697
698 SilcServerEntry silc_client_get_server(SilcClient client,
699                                        SilcClientConnection conn,
700                                        char *server_name)
701 {
702   SilcIDCacheEntry id_cache;
703   SilcServerEntry entry;
704
705   if (!silc_idcache_find_by_name_one(conn->server_cache, server_name, 
706                                      &id_cache))
707     return NULL;
708
709   entry = (SilcServerEntry)id_cache->context;
710
711   return entry;
712 }
713
714 /* Finds entry for server by the server ID. */
715
716 SilcServerEntry silc_client_get_server_by_id(SilcClient client,
717                                              SilcClientConnection conn,
718                                              SilcServerID *server_id)
719 {
720   SilcIDCacheEntry id_cache;
721   SilcServerEntry entry;
722
723   if (!silc_idcache_find_by_id_one(conn->server_cache, (void *)server_id, 
724                                    &id_cache))
725     return NULL;
726
727   entry = (SilcServerEntry)id_cache->context;
728
729   return entry;
730 }
731
732 /* Removes server from the cache by the server entry. */
733
734 bool silc_client_del_server(SilcClient client, SilcClientConnection conn,
735                             SilcServerEntry server)
736 {
737   bool ret = silc_idcache_del_by_context(conn->server_cache, server);
738   silc_free(server->server_name);
739   silc_free(server->server_info);
740   silc_free(server->server_id);
741   silc_free(server);
742   return ret;
743 }