updates.
[silc.git] / apps / silcd / idlist.c
1 /*
2
3   idlist.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 1997 - 2002 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 "serverincludes.h"
23 #include "idlist.h"
24 #include "server_internal.h"
25
26 /******************************************************************************
27
28                              Common functions
29
30 ******************************************************************************/
31
32 /* This function is used to add keys and stuff to common ID entry data
33    structure. */
34
35 void silc_idlist_add_data(void *entry, SilcIDListData idata)
36 {
37   SilcIDListData data = (SilcIDListData)entry;
38   data->send_key = idata->send_key;
39   data->receive_key = idata->receive_key;
40   data->hmac_send = idata->hmac_send;
41   data->hmac_receive = idata->hmac_receive;
42   data->psn_send = idata->psn_send;
43   data->psn_receive = idata->psn_receive;
44   data->hash = idata->hash;
45   data->public_key = idata->public_key;
46   memcpy(data->fingerprint, idata->fingerprint, sizeof(data->fingerprint));
47   data->rekey = idata->rekey;
48   data->last_receive = idata->last_receive;
49   data->last_sent = idata->last_sent;
50   data->status = idata->status;
51
52   data->created = time(0);      /* Update creation time */
53 }
54
55 /* Free's all data in the common ID entry data structure. */
56
57 void silc_idlist_del_data(void *entry)
58 {
59   SilcIDListData idata = (SilcIDListData)entry;
60   if (idata->send_key)
61     silc_cipher_free(idata->send_key);
62   if (idata->receive_key)
63     silc_cipher_free(idata->receive_key);
64   if (idata->rekey) {
65     if (idata->rekey->send_enc_key) {
66       memset(idata->rekey->send_enc_key, 0, idata->rekey->enc_key_len);
67       silc_free(idata->rekey->send_enc_key);
68     }
69     silc_free(idata->rekey);
70   }
71   if (idata->hmac_send)
72     silc_hmac_free(idata->hmac_send);
73   if (idata->hmac_receive)
74     silc_hmac_free(idata->hmac_receive);
75   if (idata->hash)
76     silc_hash_free(idata->hash);
77   if (idata->public_key)
78     silc_pkcs_public_key_free(idata->public_key);
79 }
80
81 /* Purges ID cache */
82
83 SILC_TASK_CALLBACK_GLOBAL(silc_idlist_purge)
84 {
85   SilcIDListPurge i = (SilcIDListPurge)context;
86
87   SILC_LOG_DEBUG(("Start"));
88
89   silc_idcache_purge(i->cache);
90   silc_schedule_task_add(i->schedule, 0, 
91                          silc_idlist_purge,
92                          (void *)i, i->timeout, 0,
93                          SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW);
94 }
95
96 /******************************************************************************
97
98                           Server entry functions
99
100 ******************************************************************************/
101
102 /* Add new server entry. This adds the new server entry to ID cache and
103    returns the allocated entry object or NULL on error. This is called
104    when new server connects to us. We also add ourselves to cache with
105    this function. */
106
107 SilcServerEntry 
108 silc_idlist_add_server(SilcIDList id_list, 
109                        char *server_name, int server_type,
110                        SilcServerID *id, SilcServerEntry router,
111                        void *connection)
112 {
113   SilcServerEntry server;
114
115   SILC_LOG_DEBUG(("Adding new server entry"));
116
117   server = silc_calloc(1, sizeof(*server));
118   server->server_name = server_name;
119   server->server_type = server_type;
120   server->id = id;
121   server->router = router;
122   server->connection = connection;
123
124   if (!silc_idcache_add(id_list->servers, server->server_name, 
125                         (void *)server->id, (void *)server, 0, NULL)) {
126     silc_free(server);
127     return NULL;
128   }
129
130   return server;
131 }
132
133 /* Finds server by Server ID */
134
135 SilcServerEntry
136 silc_idlist_find_server_by_id(SilcIDList id_list, SilcServerID *id,
137                               bool registered, SilcIDCacheEntry *ret_entry)
138 {
139   SilcIDCacheEntry id_cache = NULL;
140   SilcServerEntry server;
141
142   if (!id)
143     return NULL;
144
145   SILC_LOG_DEBUG(("Server ID (%s)",
146                   silc_id_render(id, SILC_ID_SERVER)));
147
148   if (!silc_idcache_find_by_id_one(id_list->servers, (void *)id, 
149                                    &id_cache))
150     return NULL;
151
152   server = (SilcServerEntry)id_cache->context;
153
154   if (ret_entry)
155     *ret_entry = id_cache;
156
157   if (server && registered && 
158       !(server->data.status & SILC_IDLIST_STATUS_REGISTERED))
159     return NULL;
160
161   SILC_LOG_DEBUG(("Found"));
162
163   return server;
164 }
165
166 /* Find server by name */
167
168 SilcServerEntry
169 silc_idlist_find_server_by_name(SilcIDList id_list, char *name,
170                                 bool registered, SilcIDCacheEntry *ret_entry)
171 {
172   SilcIDCacheEntry id_cache = NULL;
173   SilcServerEntry server;
174
175   SILC_LOG_DEBUG(("Server by name `%s'", name));
176
177   if (!silc_idcache_find_by_name_one(id_list->servers, name, &id_cache))
178     return NULL;
179
180   server = (SilcServerEntry)id_cache->context;
181   
182   if (ret_entry)
183     *ret_entry = id_cache;
184
185   if (server && registered &&
186       !(server->data.status & SILC_IDLIST_STATUS_REGISTERED))
187     return NULL;
188
189   SILC_LOG_DEBUG(("Found"));
190
191   return server;
192 }
193
194 /* Find server by connection parameters, hostname and port */
195
196 SilcServerEntry
197 silc_idlist_find_server_by_conn(SilcIDList id_list, char *hostname,
198                                 int port, bool registered,
199                                 SilcIDCacheEntry *ret_entry)
200 {
201   SilcIDCacheList list = NULL;
202   SilcIDCacheEntry id_cache = NULL;
203   SilcServerEntry server = NULL;
204   SilcSocketConnection sock;
205  
206   SILC_LOG_DEBUG(("Server by hostname %s and port %d", hostname, port));
207
208   if (!silc_idcache_get_all(id_list->servers, &list))
209     return NULL;
210
211   if (!silc_idcache_list_first(list, &id_cache)) {
212     silc_idcache_list_free(list);
213     return NULL;
214   }
215
216   while (id_cache) {
217     server = (SilcServerEntry)id_cache->context;
218     sock = (SilcSocketConnection)server->connection;
219     
220     if (sock && ((sock->hostname && !strcasecmp(sock->hostname, hostname)) ||
221                  (sock->ip && !strcasecmp(sock->ip, hostname)))
222         && sock->port == port)
223       break;
224
225     id_cache = NULL;
226     server = NULL;
227
228     if (!silc_idcache_list_next(list, &id_cache))
229       break;
230   }
231   
232   silc_idcache_list_free(list);
233
234   if (ret_entry)
235     *ret_entry = id_cache;
236
237   if (server && registered &&
238       !(server->data.status & SILC_IDLIST_STATUS_REGISTERED))
239     return NULL;
240
241   SILC_LOG_DEBUG(("Found"));
242
243   return server;
244 }
245
246 /* Replaces old Server ID with new one */ 
247
248 SilcServerEntry
249 silc_idlist_replace_server_id(SilcIDList id_list, SilcServerID *old_id,
250                               SilcServerID *new_id)
251 {
252   SilcIDCacheEntry id_cache = NULL;
253   SilcServerEntry server;
254
255   if (!old_id || !new_id)
256     return NULL;
257
258   SILC_LOG_DEBUG(("Replacing Server ID"));
259
260   if (!silc_idcache_find_by_id_one(id_list->servers, (void *)old_id, 
261                                    &id_cache))
262     return NULL;
263
264   server = (SilcServerEntry)id_cache->context;
265
266   /* Remove the old entry and add a new one */
267
268   silc_idcache_del_by_id(id_list->servers, (void *)server->id);
269
270   silc_free(server->id);
271   server->id = new_id;
272
273   silc_idcache_add(id_list->servers, server->server_name, server->id, 
274                    server, 0, NULL);
275
276   SILC_LOG_DEBUG(("Found"));
277
278   return server;
279 }
280
281 /* Removes and free's server entry from ID list */
282
283 int silc_idlist_del_server(SilcIDList id_list, SilcServerEntry entry)
284 {
285   SILC_LOG_DEBUG(("Start"));
286
287   if (entry) {
288     /* Remove from cache */
289     if (entry->id)
290       if (!silc_idcache_del_by_id(id_list->servers, (void *)entry->id))
291         return FALSE;
292
293     /* Free data */
294     silc_free(entry->server_name);
295     silc_free(entry->id);
296
297     memset(entry, 'F', sizeof(*entry));
298     silc_free(entry);
299     return TRUE;
300   }
301
302   return FALSE;
303 }
304
305 /******************************************************************************
306
307                           Client entry functions
308
309 ******************************************************************************/
310
311 /* Add new client entry. This adds the client entry to ID cache system
312    and returns the allocated client entry or NULL on error.  This is
313    called when new client connection is accepted to the server. If The
314    `router' is provided then the all server routines assume that the client
315    is not directly connected local client but it has router set and is
316    remote.  If this is the case then `connection' must be NULL.  If, on the
317    other hand, the `connection' is provided then the client is assumed
318    to be directly connected local client and `router' must be NULL. */
319
320 SilcClientEntry
321 silc_idlist_add_client(SilcIDList id_list, char *nickname, char *username, 
322                        char *userinfo, SilcClientID *id, 
323                        SilcServerEntry router, void *connection,
324                        int expire)
325 {
326   SilcClientEntry client;
327
328   SILC_LOG_DEBUG(("Adding new client entry"));
329
330   client = silc_calloc(1, sizeof(*client));
331   client->nickname = nickname;
332   client->username = username;
333   client->userinfo = userinfo;
334   client->id = id;
335   client->router = router;
336   client->connection = connection;
337   client->channels = silc_hash_table_alloc(3, silc_hash_ptr, NULL,
338                                            NULL, NULL, NULL, NULL, TRUE);
339
340   if (!silc_idcache_add(id_list->clients, nickname, (void *)client->id, 
341                         (void *)client, expire, NULL)) {
342     silc_hash_table_free(client->channels);
343     silc_free(client);
344     return NULL;
345   }
346
347   return client;
348 }
349
350 /* Free client entry. This free's everything and removes the entry
351    from ID cache. Call silc_idlist_del_data before calling this one. */
352
353 int silc_idlist_del_client(SilcIDList id_list, SilcClientEntry entry)
354 {
355   SILC_LOG_DEBUG(("Start"));
356
357   if (entry) {
358     /* Remove from cache */
359     if (entry->id)
360       if (!silc_idcache_del_by_context(id_list->clients, entry))
361         return FALSE;
362
363     /* Free data */
364     silc_free(entry->nickname);
365     silc_free(entry->username);
366     silc_free(entry->userinfo);
367     silc_free(entry->id);
368     silc_hash_table_free(entry->channels);
369
370     memset(entry, 'F', sizeof(*entry));
371     silc_free(entry);
372
373     return TRUE;
374   }
375
376   return FALSE;
377 }
378
379 /* Returns all clients matching requested nickname. Number of clients is
380    returned to `clients_count'. Caller must free the returned table. */
381
382 int silc_idlist_get_clients_by_nickname(SilcIDList id_list, char *nickname,
383                                         char *server, 
384                                         SilcClientEntry **clients,
385                                         SilcUInt32 *clients_count)
386 {
387   SilcIDCacheList list = NULL;
388   SilcIDCacheEntry id_cache = NULL;
389
390   SILC_LOG_DEBUG(("Start"));
391
392   if (!silc_idcache_find_by_name(id_list->clients, nickname, &list))
393     return FALSE;
394
395   *clients = silc_realloc(*clients, 
396                           (silc_idcache_list_count(list) + *clients_count) * 
397                           sizeof(**clients));
398
399   silc_idcache_list_first(list, &id_cache);
400   (*clients)[(*clients_count)++] = (SilcClientEntry)id_cache->context;
401
402   while (silc_idcache_list_next(list, &id_cache))
403     (*clients)[(*clients_count)++] = (SilcClientEntry)id_cache->context;
404   
405   silc_idcache_list_free(list);
406   
407   SILC_LOG_DEBUG(("Found total %d clients", *clients_count));
408
409   return TRUE;
410 }
411
412 /* Returns all clients matching requested nickname hash. Number of clients
413    is returned to `clients_count'. Caller must free the returned table. */
414
415 int silc_idlist_get_clients_by_hash(SilcIDList id_list, char *nickname,
416                                     SilcHash md5hash,
417                                     SilcClientEntry **clients,
418                                     SilcUInt32 *clients_count)
419 {
420   SilcIDCacheList list = NULL;
421   SilcIDCacheEntry id_cache = NULL;
422   unsigned char hash[32];
423   SilcClientID client_id;
424   char nick[128 + 1];
425
426   SILC_LOG_DEBUG(("Start"));
427
428   memset(nick, 0, sizeof(nick));
429   silc_to_lower(nickname, nick, sizeof(nick) - 1);
430   silc_hash_make(md5hash, nick, strlen(nick), hash);
431
432   /* As the Client ID is hashed in the ID cache by hashing only the hash
433      from the Client ID, we can do a lookup with only the hash not the
434      other parts of the ID and get all the clients with that hash, ie.
435      with that nickname, as the hash is from the nickname. */
436   memset(&client_id, 0, sizeof(client_id));
437   memcpy(&client_id.hash, hash, sizeof(client_id.hash));
438   if (!silc_idcache_find_by_id(id_list->clients, &client_id, &list))
439     return FALSE;
440
441   *clients = silc_realloc(*clients, 
442                           (silc_idcache_list_count(list) + *clients_count) * 
443                           sizeof(**clients));
444
445   silc_idcache_list_first(list, &id_cache);
446   (*clients)[(*clients_count)++] = (SilcClientEntry)id_cache->context;
447
448   while (silc_idcache_list_next(list, &id_cache))
449     (*clients)[(*clients_count)++] = (SilcClientEntry)id_cache->context;
450   
451   silc_idcache_list_free(list);
452   
453   SILC_LOG_DEBUG(("Found total %d clients", *clients_count));
454
455   return TRUE;
456 }
457
458 /* Finds client by Client ID */
459
460 SilcClientEntry
461 silc_idlist_find_client_by_id(SilcIDList id_list, SilcClientID *id,
462                               bool registered, SilcIDCacheEntry *ret_entry)
463 {
464   SilcIDCacheEntry id_cache = NULL;
465   SilcClientEntry client;
466
467   if (!id)
468     return NULL;
469
470   SILC_LOG_DEBUG(("Client ID (%s)", 
471                   silc_id_render(id, SILC_ID_CLIENT)));
472
473   /* Do extended search since the normal ID comparison function for
474      Client ID's compares only the hash from the Client ID and not the
475      entire ID. The silc_hash_client_id_compare compares the entire
476      Client ID as we want to find one specific Client ID. */
477   if (!silc_idcache_find_by_id_one_ext(id_list->clients, (void *)id, 
478                                        NULL, NULL, 
479                                        silc_hash_client_id_compare, NULL,
480                                        &id_cache))
481     return NULL;
482
483   client = (SilcClientEntry)id_cache->context;
484
485   if (ret_entry)
486     *ret_entry = id_cache;
487
488   if (client && registered &&
489       !(client->data.status & SILC_IDLIST_STATUS_REGISTERED))
490     return NULL;
491
492   SILC_LOG_DEBUG(("Found"));
493
494   return client;
495 }
496
497 /* Replaces old Client ID with new one */
498
499 SilcClientEntry
500 silc_idlist_replace_client_id(SilcServer server,
501                               SilcIDList id_list, SilcClientID *old_id,
502                               SilcClientID *new_id, const char *nickname)
503 {
504   SilcIDCacheEntry id_cache = NULL;
505   SilcClientEntry client;
506
507   if (!old_id || !new_id)
508     return NULL;
509
510   SILC_LOG_DEBUG(("Replacing Client ID"));
511
512   /* Do extended search since the normal ID comparison function for
513      Client ID's compares only the hash from the Client ID and not the
514      entire ID. The silc_hash_client_id_compare compares the entire
515      Client ID as we want to find one specific Client ID. */
516   if (!silc_idcache_find_by_id_one_ext(id_list->clients, (void *)old_id, 
517                                        NULL, NULL, 
518                                        silc_hash_client_id_compare, NULL,
519                                        &id_cache))
520     return NULL;
521
522   client = (SilcClientEntry)id_cache->context;
523
524   /* Remove the old entry and add a new one */
525
526   if (!silc_idcache_del_by_context(id_list->clients, client))
527     return NULL;
528
529   /* Check if anyone is watching this nickname */
530   if (server->server_type == SILC_ROUTER)
531     silc_server_check_watcher_list(server, client, nickname,
532                                    SILC_NOTIFY_TYPE_NICK_CHANGE);
533
534   silc_free(client->id);
535   silc_free(client->nickname);
536   client->id = new_id;
537   client->nickname = nickname ? strdup(nickname) : NULL;
538
539   if (!silc_idcache_add(id_list->clients, client->nickname, client->id, 
540                         client, 0, NULL))
541     return NULL;
542
543   SILC_LOG_DEBUG(("Replaced"));
544
545   return client;
546 }
547
548 /* Client cache entry destructor that is called when the cache is purged. */
549
550 void silc_idlist_client_destructor(SilcIDCache cache,
551                                    SilcIDCacheEntry entry)
552 {
553   SilcClientEntry client;
554
555   client = (SilcClientEntry)entry->context;
556   if (client) {
557     silc_free(client->nickname);
558     silc_free(client->username);
559     silc_free(client->userinfo);
560     silc_free(client->id);
561     silc_hash_table_free(client->channels);
562
563     memset(client, 'F', sizeof(*client));
564     silc_free(client);
565   }
566 }
567
568 /******************************************************************************
569
570                           Channel entry functions
571
572 ******************************************************************************/
573
574 /* Add new channel entry. This add the new channel entry to the ID cache
575    system and returns the allocated entry or NULL on error. */
576
577 SilcChannelEntry
578 silc_idlist_add_channel(SilcIDList id_list, char *channel_name, int mode,
579                         SilcChannelID *id, SilcServerEntry router,
580                         SilcCipher channel_key, SilcHmac hmac,
581                         int expire)
582 {
583   SilcChannelEntry channel;
584
585   SILC_LOG_DEBUG(("Adding new channel entry"));
586
587   channel = silc_calloc(1, sizeof(*channel));
588   channel->channel_name = channel_name;
589   channel->mode = mode;
590   channel->id = id;
591   channel->router = router;
592   channel->channel_key = channel_key;
593   channel->hmac = hmac;
594   channel->created = time(0);
595   if (!channel->hmac)
596     if (!silc_hmac_alloc(SILC_DEFAULT_HMAC, NULL, &channel->hmac)) {
597       silc_free(channel);
598       return NULL;
599     }
600
601   channel->user_list = silc_hash_table_alloc(3, silc_hash_ptr, NULL, NULL,
602                                              NULL, NULL, NULL, TRUE);
603
604   if (!silc_idcache_add(id_list->channels, channel->channel_name, 
605                         (void *)channel->id, (void *)channel, expire, NULL)) {
606     silc_hmac_free(channel->hmac);
607     silc_hash_table_free(channel->user_list);
608     silc_free(channel);
609     return NULL;
610   }
611
612   return channel;
613 }
614
615 /* Foreach callbcak to free all users from the channel when deleting a
616    channel entry. */
617
618 static void silc_idlist_del_channel_foreach(void *key, void *context,
619                                             void *user_context)
620 {
621   SilcChannelClientEntry chl = (SilcChannelClientEntry)context;
622
623   SILC_LOG_DEBUG(("Removing client %s from channel %s",
624                   chl->client->nickname, chl->channel->channel_name));
625
626   /* Remove the context from the client's channel hash table as that
627      table and channel's user_list hash table share this same context. */
628   silc_hash_table_del(chl->client->channels, chl->channel);
629   silc_free(chl);
630 }
631
632 /* Free channel entry.  This free's everything. */
633
634 int silc_idlist_del_channel(SilcIDList id_list, SilcChannelEntry entry)
635 {
636   SILC_LOG_DEBUG(("Start"));
637
638   if (entry) {
639     /* Remove from cache */
640     if (entry->id)
641       if (!silc_idcache_del_by_id(id_list->channels, (void *)entry->id))
642         return FALSE;
643
644     /* Free all client entrys from the users list. The silc_hash_table_free
645        will free all the entries so they are not freed at the foreach 
646        callback. */
647     silc_hash_table_foreach(entry->user_list, silc_idlist_del_channel_foreach,
648                             NULL);
649     silc_hash_table_free(entry->user_list);
650
651     /* Free data */
652     silc_free(entry->channel_name);
653     silc_free(entry->id);
654     silc_free(entry->topic);
655     if (entry->channel_key)
656       silc_cipher_free(entry->channel_key);
657     if (entry->key) {
658       memset(entry->key, 0, entry->key_len / 8);
659       silc_free(entry->key);
660     }
661     silc_free(entry->cipher);
662     if (entry->hmac)
663       silc_hmac_free(entry->hmac);
664     silc_free(entry->hmac_name);
665     silc_free(entry->rekey);
666     if (entry->founder_key)
667       silc_pkcs_public_key_free(entry->founder_key);
668
669     memset(entry, 'F', sizeof(*entry));
670     silc_free(entry);
671     return TRUE;
672   }
673
674   return FALSE;
675 }
676
677 /* Finds channel by channel name. Channel names are unique and they
678    are not case-sensitive. */
679
680 SilcChannelEntry
681 silc_idlist_find_channel_by_name(SilcIDList id_list, char *name,
682                                  SilcIDCacheEntry *ret_entry)
683 {
684   SilcIDCacheEntry id_cache = NULL;
685
686   SILC_LOG_DEBUG(("Channel by name %s", name));
687
688   if (!silc_idcache_find_by_name_one(id_list->channels, name, &id_cache))
689     return NULL;
690
691   if (ret_entry)
692     *ret_entry = id_cache;
693
694   SILC_LOG_DEBUG(("Found"));
695
696   return id_cache->context;
697 }
698
699 /* Finds channel by Channel ID. */
700
701 SilcChannelEntry
702 silc_idlist_find_channel_by_id(SilcIDList id_list, SilcChannelID *id,
703                                SilcIDCacheEntry *ret_entry)
704 {
705   SilcIDCacheEntry id_cache = NULL;
706   SilcChannelEntry channel;
707
708   if (!id)
709     return NULL;
710
711   SILC_LOG_DEBUG(("Channel ID (%s)",
712                   silc_id_render(id, SILC_ID_CHANNEL)));
713
714   if (!silc_idcache_find_by_id_one(id_list->channels, (void *)id, &id_cache))
715     return NULL;
716
717   channel = (SilcChannelEntry)id_cache->context;
718
719   if (ret_entry)
720     *ret_entry = id_cache;
721
722   SILC_LOG_DEBUG(("Found"));
723
724   return channel;
725 }
726
727 /* Replaces old Channel ID with new one. This is done when router forces
728    normal server to change Channel ID. */
729
730 SilcChannelEntry
731 silc_idlist_replace_channel_id(SilcIDList id_list, SilcChannelID *old_id,
732                                SilcChannelID *new_id)
733 {
734   SilcIDCacheEntry id_cache = NULL;
735   SilcChannelEntry channel;
736
737   if (!old_id || !new_id)
738     return NULL;
739
740   SILC_LOG_DEBUG(("Replacing Channel ID"));
741
742   if (!silc_idcache_find_by_id_one(id_list->channels, (void *)old_id, 
743                                    &id_cache))
744     return NULL;
745
746   channel = (SilcChannelEntry)id_cache->context;
747
748   /* Remove the old entry and add a new one */
749
750   silc_idcache_del_by_id(id_list->channels, (void *)channel->id);
751
752   silc_free(channel->id);
753   channel->id = new_id;
754
755   silc_idcache_add(id_list->channels, channel->channel_name, channel->id, 
756                    channel, 0, NULL);
757
758   SILC_LOG_DEBUG(("Replaced"));
759
760   return channel;
761 }
762
763 /* Returns channels from the ID list. If the `channel_id' is NULL then
764    all channels are returned. */
765
766 SilcChannelEntry *
767 silc_idlist_get_channels(SilcIDList id_list, SilcChannelID *channel_id,
768                          SilcUInt32 *channels_count)
769 {
770   SilcIDCacheList list = NULL;
771   SilcIDCacheEntry id_cache = NULL;
772   SilcChannelEntry *channels = NULL;
773   int i = 0;
774
775   SILC_LOG_DEBUG(("Start"));
776
777   if (!channel_id) {
778     if (!silc_idcache_get_all(id_list->channels, &list))
779       return NULL;
780
781     channels = silc_calloc(silc_idcache_list_count(list), sizeof(*channels));
782     
783     i = 0;
784     silc_idcache_list_first(list, &id_cache);
785     channels[i++] = (SilcChannelEntry)id_cache->context;
786     
787     while (silc_idcache_list_next(list, &id_cache))
788       channels[i++] = (SilcChannelEntry)id_cache->context;
789     
790     silc_idcache_list_free(list);
791   } else {
792     if (!silc_idcache_find_by_id_one(id_list->channels, channel_id, &id_cache))
793       return NULL;
794
795     i = 1;
796     channels = silc_calloc(1, sizeof(*channels));
797     channels[0] = (SilcChannelEntry)id_cache->context;
798   }
799
800   if (channels_count)
801     *channels_count = i;
802
803   return channels;
804 }