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