2 silc-channels.c : irssi
4 Copyright (C) 2000 - 2001 Timo Sirainen
5 Pekka Riikonen <priikone@poseidon.pspt.fi>
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #include "net-nonblock.h"
25 #include "net-sendbuffer.h"
35 #include "channels-setup.h"
37 #include "silc-servers.h"
38 #include "silc-channels.h"
39 #include "silc-queries.h"
40 #include "silc-nicklist.h"
41 #include "window-item-def.h"
43 #include "fe-common/core/printtext.h"
44 #include "fe-common/silc/module-formats.h"
46 SILC_CHANNEL_REC *silc_channel_create(SILC_SERVER_REC *server,
47 const char *name, int automatic)
49 SILC_CHANNEL_REC *rec;
51 g_return_val_if_fail(server == NULL || IS_SILC_SERVER(server), NULL);
52 g_return_val_if_fail(name != NULL, NULL);
54 rec = g_new0(SILC_CHANNEL_REC, 1);
55 rec->chat_type = SILC_PROTOCOL;
56 rec->name = g_strdup(name);
59 channel_init((CHANNEL_REC *) rec, automatic);
63 static void sig_channel_destroyed(SILC_CHANNEL_REC *channel)
65 if (!IS_SILC_CHANNEL(channel))
68 if (channel->server != NULL && !channel->left && !channel->kicked) {
69 /* destroying channel record without actually
70 having left the channel yet */
71 silc_command_exec(channel->server, "PART", channel->name);
75 static void silc_channels_join(SILC_SERVER_REC *server,
76 const char *channels, int automatic)
79 SILC_CHANNEL_REC *chanrec;
81 list = g_strsplit(channels, ",", -1);
82 for (tmp = list; *tmp != NULL; tmp++) {
83 chanrec = silc_channel_find(server, *tmp);
87 silc_command_exec(server, "JOIN", *tmp);
93 static void sig_connected(SILC_SERVER_REC *server)
95 if (IS_SILC_SERVER(server))
96 server->channels_join = (void *) silc_channels_join;
99 /* "server quit" signal from the core to indicate that QUIT command
102 static void sig_server_quit(SILC_SERVER_REC *server, const char *msg)
104 if (IS_SILC_SERVER(server) && server->conn && server->conn->sock)
105 silc_command_exec(server, "QUIT", msg);
108 /* Find Irssi channel entry by SILC channel entry */
110 SILC_CHANNEL_REC *silc_channel_find_entry(SILC_SERVER_REC *server,
111 SilcChannelEntry entry)
115 g_return_val_if_fail(IS_SILC_SERVER(server), NULL);
117 for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
118 SILC_CHANNEL_REC *rec = tmp->data;
120 if (rec->entry == entry)
127 /* PART (LEAVE) command. */
129 static void command_part(const char *data, SILC_SERVER_REC *server,
132 SILC_CHANNEL_REC *chanrec;
135 if (!IS_SILC_SERVER(server) || !server->connected)
136 cmd_return_error(CMDERR_NOT_CONNECTED);
138 if (!strcmp(data, "*") || *data == '\0') {
139 if (!IS_SILC_CHANNEL(item))
140 cmd_return_error(CMDERR_NOT_JOINED);
144 chanrec = silc_channel_find(server, data);
146 cmd_return_error(CMDERR_CHAN_NOT_FOUND);
148 memset(userhost, 0, sizeof(userhost));
149 snprintf(userhost, sizeof(userhost) - 1, "%s@%s",
150 server->conn->local_entry->username,
151 server->conn->local_entry->hostname);
152 signal_emit("message part", 5, server, chanrec->name,
153 server->nick, userhost, "");
155 silc_command_exec(server, "LEAVE", chanrec->name);
158 channel_destroy(CHANNEL(chanrec));
161 /* ME local command. */
163 static void command_me(const char *data, SILC_SERVER_REC *server,
166 SILC_CHANNEL_REC *chanrec;
167 char *tmpcmd = "ME", *tmp;
169 unsigned char **argv;
170 SilcUInt32 *argv_lens, *argv_types;
173 if (!IS_SILC_SERVER(server) || !server->connected)
174 cmd_return_error(CMDERR_NOT_CONNECTED);
176 if (!IS_SILC_CHANNEL(item))
177 cmd_return_error(CMDERR_NOT_JOINED);
179 /* Now parse all arguments */
180 tmp = g_strconcat(tmpcmd, " ", data, NULL);
181 silc_parse_command_line(tmp, &argv, &argv_lens,
182 &argv_types, &argc, 2);
186 cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
188 chanrec = silc_channel_find(server, item->name);
190 cmd_return_error(CMDERR_CHAN_NOT_FOUND);
192 /* Send the action message */
193 silc_client_send_channel_message(silc_client, server->conn,
194 chanrec->entry, NULL,
195 SILC_MESSAGE_FLAG_ACTION,
196 argv[1], argv_lens[1], TRUE);
198 printformat_module("fe-common/silc", server, chanrec->entry->channel_name,
199 MSGLEVEL_ACTIONS, SILCTXT_CHANNEL_OWNACTION,
200 server->conn->local_entry->nickname, argv[1]);
202 for (i = 0; i < argc; i++)
204 silc_free(argv_lens);
205 silc_free(argv_types);
208 /* ACTION local command. Same as ME but takes the channel as mandatory
211 static void command_action(const char *data, SILC_SERVER_REC *server,
214 SILC_CHANNEL_REC *chanrec;
215 char *tmpcmd = "ME", *tmp;
217 unsigned char **argv;
218 SilcUInt32 *argv_lens, *argv_types;
221 if (!IS_SILC_SERVER(server) || !server->connected)
222 cmd_return_error(CMDERR_NOT_CONNECTED);
224 if (!IS_SILC_CHANNEL(item))
225 cmd_return_error(CMDERR_NOT_JOINED);
227 /* Now parse all arguments */
228 tmp = g_strconcat(tmpcmd, " ", data, NULL);
229 silc_parse_command_line(tmp, &argv, &argv_lens,
230 &argv_types, &argc, 3);
234 cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
236 chanrec = silc_channel_find(server, argv[1]);
238 cmd_return_error(CMDERR_CHAN_NOT_FOUND);
240 /* Send the action message */
241 silc_client_send_channel_message(silc_client, server->conn,
242 chanrec->entry, NULL,
243 SILC_MESSAGE_FLAG_ACTION,
244 argv[2], argv_lens[2], TRUE);
246 printformat_module("fe-common/silc", server, chanrec->entry->channel_name,
247 MSGLEVEL_ACTIONS, SILCTXT_CHANNEL_OWNACTION,
248 server->conn->local_entry->nickname, argv[2]);
250 for (i = 0; i < argc; i++)
252 silc_free(argv_lens);
253 silc_free(argv_types);
256 /* NOTICE local command. */
258 static void command_notice(const char *data, SILC_SERVER_REC *server,
261 SILC_CHANNEL_REC *chanrec;
262 char *tmpcmd = "ME", *tmp;
264 unsigned char **argv;
265 SilcUInt32 *argv_lens, *argv_types;
268 if (!IS_SILC_SERVER(server) || !server->connected)
269 cmd_return_error(CMDERR_NOT_CONNECTED);
271 if (!IS_SILC_CHANNEL(item))
272 cmd_return_error(CMDERR_NOT_JOINED);
274 /* Now parse all arguments */
275 tmp = g_strconcat(tmpcmd, " ", data, NULL);
276 silc_parse_command_line(tmp, &argv, &argv_lens,
277 &argv_types, &argc, 2);
281 cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
283 chanrec = silc_channel_find(server, item->name);
285 cmd_return_error(CMDERR_CHAN_NOT_FOUND);
287 /* Send the action message */
288 silc_client_send_channel_message(silc_client, server->conn,
289 chanrec->entry, NULL,
290 SILC_MESSAGE_FLAG_NOTICE,
291 argv[1], argv_lens[1], TRUE);
293 printformat_module("fe-common/silc", server, chanrec->entry->channel_name,
294 MSGLEVEL_NOTICES, SILCTXT_CHANNEL_OWNNOTICE,
295 server->conn->local_entry->nickname, argv[1]);
297 for (i = 0; i < argc; i++)
299 silc_free(argv_lens);
300 silc_free(argv_types);
303 /* AWAY local command. Sends UMODE command that sets the SILC_UMODE_GONE
306 static void command_away(const char *data, SILC_SERVER_REC *server,
311 if (!IS_SILC_SERVER(server) || !server->connected)
312 cmd_return_error(CMDERR_NOT_CONNECTED);
315 /* Remove any possible away message */
316 silc_client_set_away_message(silc_client, server->conn, NULL);
319 printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
322 /* Set the away message */
323 silc_client_set_away_message(silc_client, server->conn, (char *)data);
326 printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
327 SILCTXT_SET_AWAY, data);
330 signal_emit("away mode changed", 1, server);
332 silc_command_exec(server, "UMODE", set ? "+g" : "-g");
336 int type; /* 1 = msg, 2 = channel */
338 SILC_SERVER_REC *server;
341 /* Key agreement callback that is called after the key agreement protocol
342 has been performed. This is called also if error occured during the
343 key agreement protocol. The `key' is the allocated key material and
344 the caller is responsible of freeing it. The `key' is NULL if error
345 has occured. The application can freely use the `key' to whatever
346 purpose it needs. See lib/silcske/silcske.h for the definition of
347 the SilcSKEKeyMaterial structure. */
349 static void keyagr_completion(SilcClient client,
350 SilcClientConnection conn,
351 SilcClientEntry client_entry,
352 SilcKeyAgreementStatus status,
353 SilcSKEKeyMaterial *key,
356 KeyInternal i = (KeyInternal)context;
359 case SILC_KEY_AGREEMENT_OK:
360 printformat_module("fe-common/silc", i->server, NULL, MSGLEVEL_CRAP,
361 SILCTXT_KEY_AGREEMENT_OK, client_entry->nickname);
364 /* Set the private key for this client */
365 silc_client_del_private_message_key(client, conn, client_entry);
366 silc_client_add_private_message_key_ske(client, conn, client_entry,
367 NULL, key, i->responder);
368 printformat_module("fe-common/silc", i->server, NULL, MSGLEVEL_CRAP,
369 SILCTXT_KEY_AGREEMENT_PRIVMSG,
370 client_entry->nickname);
371 silc_ske_free_key_material(key);
376 case SILC_KEY_AGREEMENT_ERROR:
377 printformat_module("fe-common/silc", i->server, NULL, MSGLEVEL_CRAP,
378 SILCTXT_KEY_AGREEMENT_ERROR, client_entry->nickname);
381 case SILC_KEY_AGREEMENT_FAILURE:
382 printformat_module("fe-common/silc", i->server, NULL, MSGLEVEL_CRAP,
383 SILCTXT_KEY_AGREEMENT_FAILURE, client_entry->nickname);
386 case SILC_KEY_AGREEMENT_TIMEOUT:
387 printformat_module("fe-common/silc", i->server, NULL, MSGLEVEL_CRAP,
388 SILCTXT_KEY_AGREEMENT_TIMEOUT, client_entry->nickname);
391 case SILC_KEY_AGREEMENT_ABORTED:
392 printformat_module("fe-common/silc", i->server, NULL, MSGLEVEL_CRAP,
393 SILCTXT_KEY_AGREEMENT_ABORTED, client_entry->nickname);
404 /* Local command KEY. This command is used to set and unset private
405 keys for channels, set and unset private keys for private messages
406 with remote clients and to send key agreement requests and
407 negotiate the key agreement protocol with remote client. The
408 key agreement is supported only to negotiate private message keys,
409 it currently cannot be used to negotiate private keys for channels,
410 as it is not convenient for that purpose. */
413 SILC_SERVER_REC *server;
419 /* Callback to be called after client information is resolved from the
422 static void silc_client_command_key_get_clients(SilcClient client,
423 SilcClientConnection conn,
424 SilcClientEntry *clients,
425 SilcUInt32 clients_count,
428 KeyGetClients internal = (KeyGetClients)context;
431 printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Unknown nick: %s",
433 silc_free(internal->data);
434 silc_free(internal->nick);
439 signal_emit("command key", 3, internal->data, internal->server,
442 silc_free(internal->data);
443 silc_free(internal->nick);
447 static void command_key(const char *data, SILC_SERVER_REC *server,
450 SilcClientConnection conn;
451 SilcClientEntry *entrys, client_entry = NULL;
452 SilcUInt32 entry_count;
453 SilcChannelEntry channel_entry = NULL;
454 char *nickname = NULL, *tmp;
455 int command = 0, port = 0, type = 0;
456 char *hostname = NULL;
457 KeyInternal internal = NULL;
459 unsigned char **argv;
460 SilcUInt32 *argv_lens, *argv_types;
461 char *bindhost = NULL;
463 if (!server || !IS_SILC_SERVER(server) || !server->connected)
464 cmd_return_error(CMDERR_NOT_CONNECTED);
468 /* Now parse all arguments */
469 tmp = g_strconcat("KEY", " ", data, NULL);
470 silc_parse_command_line(tmp, &argv, &argv_lens, &argv_types, &argc, 7);
474 cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
477 if (!strcasecmp(argv[1], "msg"))
479 if (!strcasecmp(argv[1], "channel"))
483 cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
486 if (argv[2][0] == '*') {
487 nickname = strdup("*");
489 /* Parse the typed nickname. */
490 if (!silc_parse_userfqdn(argv[2], &nickname, NULL)) {
491 printformat_module("fe-common/silc", server, NULL,
492 MSGLEVEL_CRAP, SILCTXT_BAD_NICK, argv[2]);
496 /* Find client entry */
497 entrys = silc_client_get_clients_local(silc_client, conn, nickname,
498 argv[2], &entry_count);
500 KeyGetClients inter = silc_calloc(1, sizeof(*inter));
501 inter->server = server;
502 inter->data = strdup(data);
503 inter->nick = strdup(nickname);
505 silc_client_get_clients(silc_client, conn, nickname, argv[2],
506 silc_client_command_key_get_clients, inter);
509 client_entry = entrys[0];
515 /* Get channel entry */
518 if (argv[2][0] == '*') {
519 if (!conn->current_channel) {
521 cmd_return_error(CMDERR_NOT_JOINED);
523 name = conn->current_channel->channel_name;
528 channel_entry = silc_client_get_channel(silc_client, conn, name);
529 if (!channel_entry) {
531 cmd_return_error(CMDERR_NOT_JOINED);
536 if (!strcasecmp(argv[3], "set")) {
540 if (type == 1 && client_entry) {
541 /* Set private message key */
543 silc_client_del_private_message_key(silc_client, conn, client_entry);
546 silc_client_add_private_message_key(silc_client, conn, client_entry,
550 TRUE : FALSE), FALSE);
552 silc_client_add_private_message_key(silc_client, conn, client_entry,
556 TRUE : FALSE), FALSE);
558 /* Send the key to the remote client so that it starts using it
560 silc_client_send_private_message_key(silc_client, conn,
562 } else if (type == 2) {
563 /* Set private channel key */
564 char *cipher = NULL, *hmac = NULL;
566 if (!(channel_entry->mode & SILC_CHANNEL_MODE_PRIVKEY)) {
567 printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
568 SILCTXT_CH_PRIVATE_KEY_NOMODE,
569 channel_entry->channel_name);
578 if (!silc_client_add_channel_private_key(silc_client, conn,
583 printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
584 SILCTXT_CH_PRIVATE_KEY_ERROR,
585 channel_entry->channel_name);
589 printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
590 SILCTXT_CH_PRIVATE_KEY_ADD,
591 channel_entry->channel_name);
599 if (!strcasecmp(argv[3], "unset")) {
602 if (type == 1 && client_entry) {
603 /* Unset private message key */
604 silc_client_del_private_message_key(silc_client, conn, client_entry);
605 } else if (type == 2) {
606 /* Unset channel key(s) */
607 SilcChannelPrivateKey *keys;
608 SilcUInt32 keys_count;
612 silc_client_del_channel_private_keys(silc_client, conn,
616 number = atoi(argv[4]);
617 keys = silc_client_list_channel_private_keys(silc_client, conn,
623 if (!number || number > keys_count) {
624 silc_client_free_channel_private_keys(keys, keys_count);
628 silc_client_del_channel_private_key(silc_client, conn, channel_entry,
630 silc_client_free_channel_private_keys(keys, keys_count);
638 if (!strcasecmp(argv[3], "list")) {
642 SilcPrivateMessageKeys keys;
643 SilcUInt32 keys_count;
647 keys = silc_client_list_private_message_keys(silc_client, conn,
652 /* list the private message key(s) */
653 if (nickname[0] == '*') {
654 printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
655 SILCTXT_PRIVATE_KEY_LIST);
656 for (k = 0; k < keys_count; k++) {
657 memset(buf, 0, sizeof(buf));
658 strncat(buf, " ", 2);
659 len = strlen(keys[k].client_entry->nickname);
660 strncat(buf, keys[k].client_entry->nickname, len > 30 ? 30 : len);
662 for (i = 0; i < 30 - len; i++)
666 len = strlen(keys[k].cipher);
667 strncat(buf, keys[k].cipher, len > 14 ? 14 : len);
669 for (i = 0; i < 14 - len; i++)
674 strcat(buf, "<hidden>");
676 strcat(buf, "*generated*");
678 silc_say(silc_client, conn, SILC_CLIENT_MESSAGE_INFO, "%s", buf);
681 printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
682 SILCTXT_PRIVATE_KEY_LIST_NICK,
683 client_entry->nickname);
684 for (k = 0; k < keys_count; k++) {
685 if (keys[k].client_entry != client_entry)
688 memset(buf, 0, sizeof(buf));
689 strncat(buf, " ", 2);
690 len = strlen(keys[k].client_entry->nickname);
691 strncat(buf, keys[k].client_entry->nickname, len > 30 ? 30 : len);
693 for (i = 0; i < 30 - len; i++)
697 len = strlen(keys[k].cipher);
698 strncat(buf, keys[k].cipher, len > 14 ? 14 : len);
700 for (i = 0; i < 14 - len; i++)
705 strcat(buf, "<hidden>");
707 strcat(buf, "*generated*");
709 silc_say(silc_client, conn, SILC_CLIENT_MESSAGE_INFO, "%s", buf);
713 silc_client_free_private_message_keys(keys, keys_count);
715 } else if (type == 2) {
716 SilcChannelPrivateKey *keys;
717 SilcUInt32 keys_count;
721 keys = silc_client_list_channel_private_keys(silc_client, conn,
725 printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
726 SILCTXT_CH_PRIVATE_KEY_LIST,
727 channel_entry->channel_name);
732 for (k = 0; k < keys_count; k++) {
733 memset(buf, 0, sizeof(buf));
734 strncat(buf, " ", 2);
736 len = strlen(keys[k]->cipher->cipher->name);
737 strncat(buf, keys[k]->cipher->cipher->name, len > 16 ? 16 : len);
739 for (i = 0; i < 16 - len; i++)
743 len = strlen(silc_hmac_get_name(keys[k]->hmac));
744 strncat(buf, silc_hmac_get_name(keys[k]->hmac), len > 16 ? 16 : len);
746 for (i = 0; i < 16 - len; i++)
750 strcat(buf, "<hidden>");
752 silc_say(silc_client, conn, SILC_CLIENT_MESSAGE_INFO, "%s", buf);
755 silc_client_free_channel_private_keys(keys, keys_count);
761 /* Send command is used to send key agreement */
762 if (!strcasecmp(argv[3], "agreement")) {
768 port = atoi(argv[5]);
770 internal = silc_calloc(1, sizeof(*internal));
771 internal->type = type;
772 internal->server = server;
775 if (settings_get_bool("use_auto_addr")) {
777 hostname = (char *)settings_get_str("auto_public_ip");
779 /* If the hostname isn't set, treat this case as if auto_public_ip wasn't
782 if ((hostname) && (*hostname == '\0')) {
786 bindhost = (char *)settings_get_str("auto_bind_ip");
788 /* if the bind_ip isn't set, but the public_ip IS, then assume then
789 * public_ip is the same value as the bind_ip.
791 if ((bindhost) && (*bindhost == '\0')) {
794 port = settings_get_int("auto_bind_port");
796 } /* if use_auto_addr */
800 /* Start command is used to start key agreement (after receiving the
801 key_agreement client operation). */
802 if (!strcasecmp(argv[3], "negotiate")) {
808 port = atoi(argv[5]);
810 internal = silc_calloc(1, sizeof(*internal));
811 internal->type = type;
812 internal->server = server;
816 silc_say(silc_client, conn, SILC_CLIENT_MESSAGE_INFO,
817 "Usage: /KEY msg|channel <nickname|channel> "
818 "set|unset|agreement|negotiate [<arguments>]");
822 if (command == 4 && client_entry) {
823 printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
824 SILCTXT_KEY_AGREEMENT, argv[2]);
825 internal->responder = TRUE;
826 silc_client_send_key_agreement(
827 silc_client, conn, client_entry, hostname,
829 settings_get_int("key_exchange_timeout_secs"),
830 keyagr_completion, internal);
836 if (command == 5 && client_entry && hostname) {
837 printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
838 SILCTXT_KEY_AGREEMENT_NEGOTIATE, argv[2]);
839 internal->responder = FALSE;
840 silc_client_perform_key_agreement(silc_client, conn, client_entry,
841 hostname, port, keyagr_completion,
850 /* Lists locally saved client and server public keys. */
852 static void command_listkeys(const char *data, SILC_SERVER_REC *server,
858 void silc_channels_init(void)
860 signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
861 signal_add("server connected", (SIGNAL_FUNC) sig_connected);
862 signal_add("server quit", (SIGNAL_FUNC) sig_server_quit);
864 command_bind("part", MODULE_NAME, (SIGNAL_FUNC) command_part);
865 command_bind("me", MODULE_NAME, (SIGNAL_FUNC) command_me);
866 command_bind("action", MODULE_NAME, (SIGNAL_FUNC) command_action);
867 command_bind("notice", MODULE_NAME, (SIGNAL_FUNC) command_notice);
868 command_bind("away", MODULE_NAME, (SIGNAL_FUNC) command_away);
869 command_bind("key", MODULE_NAME, (SIGNAL_FUNC) command_key);
870 command_bind("listkeys", MODULE_NAME, (SIGNAL_FUNC) command_listkeys);
872 silc_nicklist_init();
875 void silc_channels_deinit(void)
877 signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
878 signal_remove("server connected", (SIGNAL_FUNC) sig_connected);
879 signal_remove("server quit", (SIGNAL_FUNC) sig_server_quit);
881 command_unbind("part", (SIGNAL_FUNC) command_part);
882 command_unbind("me", (SIGNAL_FUNC) command_me);
883 command_unbind("action", (SIGNAL_FUNC) command_action);
884 command_unbind("notice", (SIGNAL_FUNC) command_notice);
885 command_unbind("away", (SIGNAL_FUNC) command_away);
886 command_unbind("key", (SIGNAL_FUNC) command_key);
887 command_unbind("listkeys", (SIGNAL_FUNC) command_listkeys);
889 silc_nicklist_deinit();