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