updates.
[silc.git] / apps / irssi / src / silc / core / silc-core.c
index 7afa379ba7805c3267e8c7d38cbd72b9641a8090..8448245fb2693c3c8d7e43fe22522123c5ed42d5 100644 (file)
@@ -1,5 +1,26 @@
+/*
+
+  silc-core.c
+
+  Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
+
+  Copyright (C) 2001 Pekka Riikonen
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+  
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+*/
+
 #include "module.h"
 #include "chat-protocols.h"
+#include "args.h"
 
 #include "chatnets.h"
 #include "servers-setup.h"
 #include "silc-queries.h"
 #include "silc-nicklist.h"
 #include "version_internal.h"
+#include "version.h"
 
 #include "signals.h"
 #include "levels.h"
 #include "settings.h"
 #include "fe-common/core/printtext.h"
 #include "fe-common/core/fe-channels.h"
+#include "fe-common/core/keyboard.h"
+#include "fe-common/silc/module-formats.h"
 
-#define SILC_CLIENT_PUBLIC_KEY_NAME "public_key.pub"
-#define SILC_CLIENT_PRIVATE_KEY_NAME "private_key.prv"
-
-#define SILC_CLIENT_DEF_PKCS "rsa"
-#define SILC_CLIENT_DEF_PKCS_LEN 1024
-
-SilcClient silc_client;
-const char *silc_version_string = SILC_PROTOCOL_VERSION_STRING;
+/* Command line option variables */
+static bool opt_create_keypair = FALSE;
+static bool opt_debug = FALSE;
+static char *opt_pkcs = NULL;
+static char *opt_keyfile = NULL;
+static int opt_bits = 0;
 
 static int idletag;
 
+SilcClient silc_client = NULL;
+SilcClientConfig silc_config = NULL;
 extern SilcClientOperations ops;
+extern int silc_debug;
+#ifdef SILC_SIM
+/* SIM (SILC Module) table */
+SilcSimContext **sims = NULL;
+uint32 sims_count = 0;
+#endif
+
+static void silc_say(SilcClient client, SilcClientConnection conn,
+                    char *msg, ...);
+static void 
+silc_channel_message(SilcClient client, SilcClientConnection conn,
+                    SilcClientEntry sender, SilcChannelEntry channel,
+                    SilcMessageFlags flags, char *msg);
+static void 
+silc_private_message(SilcClient client, SilcClientConnection conn,
+                    SilcClientEntry sender, SilcMessageFlags flags,
+                    char *msg);
+static void silc_notify(SilcClient client, SilcClientConnection conn,
+                       SilcNotifyType type, ...);
+static void 
+silc_connect(SilcClient client, SilcClientConnection conn, int success);
+static void 
+silc_disconnect(SilcClient client, SilcClientConnection conn);
+static void 
+silc_command(SilcClient client, SilcClientConnection conn, 
+            SilcClientCommandContext cmd_context, int success,
+            SilcCommand command);
+static void 
+silc_command_reply(SilcClient client, SilcClientConnection conn,
+                  SilcCommandPayload cmd_payload, int success,
+                  SilcCommand command, SilcCommandStatus status, ...);
+static void 
+silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn,
+                               SilcSocketType conn_type, unsigned char *pk, 
+                               uint32 pk_len, SilcSKEPKType pk_type,
+                               SilcVerifyPublicKey completion, void *context);
+static void 
+silc_verify_public_key(SilcClient client, SilcClientConnection conn,
+                      SilcSocketType conn_type, unsigned char *pk, 
+                      uint32 pk_len, SilcSKEPKType pk_type,
+                      SilcVerifyPublicKey completion, void *context);
+static void silc_ask_passphrase(SilcClient client, SilcClientConnection conn,
+                               SilcAskPassphrase completion, void *context);
+static int 
+silc_get_auth_method(SilcClient client, SilcClientConnection conn,
+                    char *hostname, uint16 port,
+                    SilcProtocolAuthMeth *auth_meth,
+                    unsigned char **auth_data,
+                    uint32 *auth_data_len);
+static void 
+silc_failure(SilcClient client, SilcClientConnection conn, 
+            SilcProtocol protocol, void *failure);
+static int 
+silc_key_agreement(SilcClient client, SilcClientConnection conn,
+                  SilcClientEntry client_entry, char *hostname,
+                  int port,
+                  SilcKeyAgreementCallback *completion,
+                  void **context);
 
 static void silc_say(SilcClient client, SilcClientConnection conn,
                     char *msg, ...)
 {
-       SILC_SERVER_REC *server;
-       va_list va;
-       char *str;
+  SILC_SERVER_REC *server;
+  va_list va;
+  char *str;
+
+  server = conn == NULL ? NULL : conn->context;
+  
+  va_start(va, msg);
+  str = g_strdup_vprintf(msg, va);
+  printtext(server, NULL, MSGLEVEL_CRAP, "%s", str);
+  g_free(str);
+  va_end(va);
+}
 
-       server = conn == NULL ? NULL : conn->context;
+static void silc_say_error(char *msg, ...)
+{
+  va_list va;
+  char *str;
 
-       va_start(va, msg);
-       str = g_strdup_vprintf(msg, va);
-       printtext(server, "#silc", MSGLEVEL_CRAP, "%s", str);
-       g_free(str);
-       va_end(va);
+  va_start(va, msg);
+  str = g_strdup_vprintf(msg, va);
+  printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", str);
+
+  g_free(str);
+  va_end(va);
 }
 
-static void silc_channel_message(SilcClient c, SilcClientConnection conn,
-                                SilcClientEntry client,
-                                SilcChannelEntry channel, char *msg)
+/* Message for a channel. The `sender' is the nickname of the sender 
+   received in the packet. The `channel_name' is the name of the channel. */
+
+static void 
+silc_channel_message(SilcClient client, SilcClientConnection conn,
+                    SilcClientEntry sender, SilcChannelEntry channel,
+                    SilcMessageFlags flags, char *msg)
 {
-       SILC_SERVER_REC *server;
-       SILC_NICK_REC *nick;
-        SILC_CHANNEL_REC *chanrec;
-
-       server = conn == NULL ? NULL : conn->context;
-       chanrec = silc_channel_find_entry(server, channel);
-
-       nick = client == NULL ? NULL : silc_nicklist_find(chanrec, client);
-       signal_emit("message public", 6, server, msg,
-                   nick == NULL ? "(unknown)" : nick->nick,
-                   nick == NULL ? NULL : nick->host,
-                   chanrec->name, nick);
+  SILC_SERVER_REC *server;
+  SILC_NICK_REC *nick;
+  SILC_CHANNEL_REC *chanrec;
+  
+  server = conn == NULL ? NULL : conn->context;
+  chanrec = silc_channel_find_entry(server, channel);
+  
+  nick = silc_nicklist_find(chanrec, sender);
+
+  if (flags & SILC_MESSAGE_FLAG_ACTION)
+    ;
+  else if (flags & SILC_MESSAGE_FLAG_NOTICE)
+    ;
+  else
+    signal_emit("message public", 6, server, msg,
+               nick == NULL ? "[<unknown>]" : nick->nick,
+               nick == NULL ? NULL : nick->host,
+               chanrec->name, nick);
 }
 
-static void silc_private_message(SilcClient c, SilcClientConnection conn,
-                                SilcClientEntry client, char *msg)
-{
-       SILC_SERVER_REC *server;
+/* Private message to the client. The `sender' is the nickname of the
+   sender received in the packet. */
 
-       server = conn == NULL ? NULL : conn->context;
-       signal_emit("message private", 4, server, msg,
-                   client == NULL ? "(unknown)" : client->nickname,
-                   client == NULL ? NULL : client->username);
+static void 
+silc_private_message(SilcClient client, SilcClientConnection conn,
+                    SilcClientEntry sender, SilcMessageFlags flags,
+                         char *msg)
+{
+  SILC_SERVER_REC *server;
+  
+  server = conn == NULL ? NULL : conn->context;
+  signal_emit("message private", 4, server, msg,
+             sender->nickname ? sender->nickname : "[<unknown>]",
+             sender->username ? sender->username : NULL);
 }
 
+/* Notify message to the client. The notify arguments are sent in the
+   same order as servers sends them. The arguments are same as received
+   from the server except for ID's.  If ID is received application receives
+   the corresponding entry to the ID. For example, if Client ID is received
+   application receives SilcClientEntry.  Also, if the notify type is
+   for channel the channel entry is sent to application (even if server
+   does not send it). */
+
 typedef struct {
-       int type;
-       const char *name;
+  int type;
+  const char *name;
 } NOTIFY_REC;
 
 #define MAX_NOTIFY (sizeof(notifies)/sizeof(notifies[0]))
 static NOTIFY_REC notifies[] = {
-       { SILC_NOTIFY_TYPE_NONE,                NULL },
-       { SILC_NOTIFY_TYPE_INVITE,              "invite" },
-       { SILC_NOTIFY_TYPE_JOIN,                "join" },
-       { SILC_NOTIFY_TYPE_LEAVE,               "leave" },
-       { SILC_NOTIFY_TYPE_SIGNOFF,             "signoff" },
-       { SILC_NOTIFY_TYPE_TOPIC_SET,           "topic" },
-       { SILC_NOTIFY_TYPE_NICK_CHANGE,         "nick" },
-       { SILC_NOTIFY_TYPE_CMODE_CHANGE,        "cmode" },
-       { SILC_NOTIFY_TYPE_CUMODE_CHANGE,       "cumode" },
-       { SILC_NOTIFY_TYPE_MOTD,                "motd" }
+  { SILC_NOTIFY_TYPE_NONE,             NULL },
+  { SILC_NOTIFY_TYPE_INVITE,           "invite" },
+  { SILC_NOTIFY_TYPE_JOIN,             "join" },
+  { SILC_NOTIFY_TYPE_LEAVE,            "leave" },
+  { SILC_NOTIFY_TYPE_SIGNOFF,          "signoff" },
+  { SILC_NOTIFY_TYPE_TOPIC_SET,                "topic" },
+  { SILC_NOTIFY_TYPE_NICK_CHANGE,      "nick" },
+  { SILC_NOTIFY_TYPE_CMODE_CHANGE,     "cmode" },
+  { SILC_NOTIFY_TYPE_CUMODE_CHANGE,    "cumode" },
+  { SILC_NOTIFY_TYPE_MOTD,             "motd" },
+  { SILC_NOTIFY_TYPE_CHANNEL_CHANGE,   "channel_change" },
+  { SILC_NOTIFY_TYPE_SERVER_SIGNOFF,   "server_signoff" },
+  { SILC_NOTIFY_TYPE_KICKED,           "kick" },
+  { SILC_NOTIFY_TYPE_KILLED,           "kill" },
+  { SILC_NOTIFY_TYPE_UMODE_CHANGE,      "umode" },
+  { SILC_NOTIFY_TYPE_BAN,               "ban" },
 };
 
 static void silc_notify(SilcClient client, SilcClientConnection conn,
-                        SilcNotifyType type, ...)
+                       SilcNotifyType type, ...)
 {
-       SILC_SERVER_REC *server;
-       va_list va;
-
-       server = conn == NULL ? NULL : conn->context;
-       va_start(va, type);
-
-       if (type == SILC_NOTIFY_TYPE_NONE) {
-               /* some generic notice from server */
-               printtext(server, NULL, MSGLEVEL_CRAP, "%s",
-                         (char *) va_arg(va, char *));
-       } else if (type < MAX_NOTIFY) {
-               /* send signal about the notify event */
-               char signal[50];
-
-               g_snprintf(signal, sizeof(signal), "silc event %s",
-                          notifies[type].name);
-               signal_emit(signal, 2, server, va);
-       } else {
-               /* unknown notify */
-               printtext(server, NULL, MSGLEVEL_CRAP,
-                         "Unknown notify %d", type);
-       }
-       va_end(va);
+  SILC_SERVER_REC *server;
+  va_list va;
+  
+  server = conn == NULL ? NULL : conn->context;
+  va_start(va, type);
+  
+  if (type == SILC_NOTIFY_TYPE_NONE) {
+    /* Some generic notice from server */
+    printtext(server, NULL, MSGLEVEL_CRAP, "%s", (char *)va_arg(va, char *));
+  } else if (type < MAX_NOTIFY) {
+    /* Send signal about the notify event */
+    char signal[50];
+    g_snprintf(signal, sizeof(signal), "silc event %s", notifies[type].name);
+    signal_emit(signal, 2, server, va);
+  } else {
+    /* Unknown notify */
+    printtext(server, NULL, MSGLEVEL_CRAP, "Unknown notify type %d", type);
+  }
+
+  va_end(va);
 }
 
-static void silc_connect(SilcClient client, SilcClientConnection conn, int success)
-{
-       SILC_SERVER_REC *server = conn->context;
+/* Called to indicate that connection was either successfully established
+   or connecting failed.  This is also the first time application receives
+   the SilcClientConnection objecet which it should save somewhere. */
 
-       if (success) {
-               server->connected = TRUE;
-               signal_emit("event connected", 1, server);
-       } else {
-               server->connection_lost = TRUE;
-                server->conn->context = NULL;
-               server_disconnect(SERVER(server));
-       }
+static void 
+silc_connect(SilcClient client, SilcClientConnection conn, int success)
+{
+  SILC_SERVER_REC *server = conn->context;
+
+  if (success) {
+    server->connected = TRUE;
+    signal_emit("event connected", 1, server);
+  } else {
+    server->connection_lost = TRUE;
+    server->conn->context = NULL;
+    server_disconnect(SERVER(server));
+  }
 }
 
-static void silc_disconnect(SilcClient client, SilcClientConnection conn)
+/* Called to indicate that connection was disconnected to the server. */
+
+static void 
+silc_disconnect(SilcClient client, SilcClientConnection conn)
 {
-       SILC_SERVER_REC *server = conn->context;
+  SILC_SERVER_REC *server = conn->context;
 
-       server->conn->context = NULL;
-       server->conn = NULL;
-       server->connection_lost = TRUE;
-       server_disconnect(SERVER(server));
+  server->conn->context = NULL;
+  server->conn = NULL;
+  server->connection_lost = TRUE;
+  server_disconnect(SERVER(server));
 }
 
-static void silc_command(SilcClient client, SilcClientConnection conn,
-                        SilcClientCommandContext cmd_context, int success,
-                        SilcCommand command)
+/* Command handler. This function is called always in the command function.
+   If error occurs it will be called as well. `conn' is the associated
+   client connection. `cmd_context' is the command context that was
+   originally sent to the command. `success' is FALSE if error occured
+   during command. `command' is the command being processed. It must be
+   noted that this is not reply from server. This is merely called just
+   after application has called the command. Just to tell application
+   that the command really was processed. */
+
+static void 
+silc_command(SilcClient client, SilcClientConnection conn, 
+            SilcClientCommandContext cmd_context, int success,
+            SilcCommand command)
 {
 }
 
-static void silc_command_reply(SilcClient client, SilcClientConnection conn,
-                              SilcCommandPayload cmd_payload, int success,
-                              SilcCommand command,
-                              SilcCommandStatus status, ...)
+/* Client info resolving callback when JOIN command reply is received.
+   This will cache all users on the channel. */
+
+void silc_client_join_get_users(SilcClient client,
+                               SilcClientConnection conn,
+                               SilcClientEntry *clients,
+                               uint32 clients_count,
+                               void *context)
 {
-        SILC_SERVER_REC *server = conn->context;
-       SILC_CHANNEL_REC *chanrec;
-       va_list va;
-
-       va_start(va, status);
-
-       /*g_snprintf(signal, sizeof(signal), "silc command reply %s",
-                  silc_commands[type]);
-       signal_emit(signal, 2, server, va);*/
-
-       switch(command) {
-       case SILC_COMMAND_JOIN: {
-               char *channel, *mode;
-
-               channel = va_arg(va, char *);
-               (void)va_arg(va, SilcChannelEntry);
-               mode = silc_client_chmode(va_arg(va, unsigned int));
-
-               chanrec = silc_channel_find(server, channel);
-               if (chanrec != NULL && !success)
-                       channel_destroy(CHANNEL(chanrec));
-               else if (chanrec == NULL && success)
-                       chanrec = silc_channel_create(server, channel, TRUE);
-
-               g_free_not_null(chanrec->mode);
-               chanrec->mode = g_strdup(mode == NULL ? "" : mode);
-               signal_emit("channel mode changed", 1, chanrec);
-               break;
-       }
-       case SILC_COMMAND_NICK: {
-               SilcClientEntry client = va_arg(va, SilcClientEntry);
-                char *old;
-
-                old = g_strdup(server->nick);
-               server_change_nick(SERVER(server), client->nickname);
-               nicklist_rename_unique(SERVER(server),
-                                      server->conn->local_entry, server->nick,
-                                       client, client->nickname);
-
-               signal_emit("message own_nick", 4,
-                           server, server->nick, old, "");
-                g_free(old);
-               break;
+  SilcChannelEntry channel = (SilcChannelEntry)context;
+  SilcChannelUser chu;
+  SILC_SERVER_REC *server = conn->context;
+  SILC_CHANNEL_REC *chanrec;
+  SilcClientEntry founder = NULL;
+  NICK_REC *ownnick;
+
+  if (!clients)
+    return;
+
+  chanrec = silc_channel_find(server, channel->channel_name);
+  if (chanrec == NULL)
+    return;
+
+  silc_list_start(channel->clients);
+  while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
+    if (chu->mode & SILC_CHANNEL_UMODE_CHANFO)
+      founder = chu->client;
+    silc_nicklist_insert(chanrec, chu, FALSE);
+  }
+
+  ownnick = NICK(silc_nicklist_find(chanrec, conn->local_entry));
+  nicklist_set_own(CHANNEL(chanrec), ownnick);
+  signal_emit("channel joined", 1, chanrec);
+
+  if (chanrec->topic)
+    printformat_module("fe-common/silc", server, channel->channel_name,
+                      MSGLEVEL_CRAP, SILCTXT_CHANNEL_TOPIC,
+                      channel->channel_name, chanrec->topic);
+
+  fe_channels_nicklist(CHANNEL(chanrec), CHANNEL_NICKLIST_FLAG_ALL);
+
+  if (founder) {
+    if (founder == conn->local_entry)
+      printformat_module("fe-common/silc", 
+                        server, channel->channel_name, MSGLEVEL_CRAP,
+                        SILCTXT_CHANNEL_FOUNDER_YOU,
+                        channel->channel_name);
+    else
+      printformat_module("fe-common/silc", 
+                        server, channel->channel_name, MSGLEVEL_CRAP,
+                        SILCTXT_CHANNEL_FOUNDER,
+                        channel->channel_name, founder->nickname);
+  }
+}
+
+/* Command reply handler. This function is called always in the command reply
+   function. If error occurs it will be called as well. Normal scenario
+   is that it will be called after the received command data has been parsed
+   and processed. The function is used to pass the received command data to
+   the application. 
+
+   `conn' is the associated client connection. `cmd_payload' is the command
+   payload data received from server and it can be ignored. It is provided
+   if the application would like to re-parse the received command data,
+   however, it must be noted that the data is parsed already by the library
+   thus the payload can be ignored. `success' is FALSE if error occured.
+   In this case arguments are not sent to the application. `command' is the
+   command reply being processed. The function has variable argument list
+   and each command defines the number and type of arguments it passes to the
+   application (on error they are not sent). */
+
+static void 
+silc_command_reply(SilcClient client, SilcClientConnection conn,
+                  SilcCommandPayload cmd_payload, int success,
+                  SilcCommand command, SilcCommandStatus status, ...)
+
+{
+  SILC_SERVER_REC *server = conn->context;
+  SILC_CHANNEL_REC *chanrec;
+  va_list vp;
+
+  va_start(vp, status);
+
+  switch(command) {
+  case SILC_COMMAND_WHOIS:
+    {
+      char buf[1024], *nickname, *username, *realname;
+      int len;
+      uint32 idle, mode;
+      SilcBuffer channels;
+      
+      /* XXX should use irssi routines */
+      
+      if (status == SILC_STATUS_ERR_NO_SUCH_NICK ||
+         status == SILC_STATUS_ERR_NO_SUCH_CLIENT_ID) {
+       char *tmp;
+       tmp = silc_argument_get_arg_type(silc_command_get_args(cmd_payload),
+                                        3, NULL);
+       if (tmp)
+         client->ops->say(client, conn, "%s: %s", tmp,
+                          silc_client_command_status_message(status));
+       else
+         client->ops->say(client, conn, "%s",
+                          silc_client_command_status_message(status));
+       break;
+      }
+      
+      if (!success)
+       return;
+      
+      (void)va_arg(vp, SilcClientEntry);
+      nickname = va_arg(vp, char *);
+      username = va_arg(vp, char *);
+      realname = va_arg(vp, char *);
+      channels = va_arg(vp, SilcBuffer);
+      mode = va_arg(vp, uint32);
+      idle = va_arg(vp, uint32);
+      
+      memset(buf, 0, sizeof(buf));
+      
+      if (nickname) {
+       len = strlen(nickname);
+       strncat(buf, nickname, len);
+       strncat(buf, " is ", 4);
+      }
+       
+      if (username) {
+       strncat(buf, username, strlen(username));
+      }
+       
+      if (realname) {
+       strncat(buf, " (", 2);
+       strncat(buf, realname, strlen(realname));
+       strncat(buf, ")", 1);
+      }
+      
+      client->ops->say(client, conn, "%s", buf);
+      
+      if (channels) {
+       SilcDList list = silc_channel_payload_parse_list(channels);
+       if (list) {
+         SilcChannelPayload entry;
+         
+         memset(buf, 0, sizeof(buf));
+         strcat(buf, "on channels: ");
+         
+         silc_dlist_start(list);
+         while ((entry = silc_dlist_get(list)) != SILC_LIST_END) {
+           char *m = silc_client_chumode_char(silc_channel_get_mode(entry));
+           uint32 name_len;
+           char *name = silc_channel_get_name(entry, &name_len);
+           
+           if (m)
+             strncat(buf, m, strlen(m));
+           strncat(buf, name, name_len);
+           strncat(buf, " ", 1);
+           silc_free(m);
+         }
+
+         client->ops->say(client, conn, "%s", buf);
+         silc_channel_payload_list_free(list);
        }
-       case SILC_COMMAND_USERS: {
-               SilcChannelEntry channel;
-               SilcChannelUser user;
-                NICK_REC *ownnick;
-
-               channel = va_arg(va, SilcChannelEntry);
-               chanrec = silc_channel_find_entry(server, channel);
-               if (chanrec == NULL)
-                       break;
-
-               silc_list_start(channel->clients);
-               while ((user = silc_list_get(channel->clients)) != NULL)
-                       silc_nicklist_insert(chanrec, user, FALSE);
-
-                ownnick = NICK(silc_nicklist_find(chanrec, conn->local_entry));
-               nicklist_set_own(CHANNEL(chanrec), ownnick);
-                signal_emit("channel joined", 1, chanrec);
-               fe_channels_nicklist(CHANNEL(chanrec),
-                                    CHANNEL_NICKLIST_FLAG_ALL);
-               break;
+      }
+      
+      if (mode) {
+       if ((mode & SILC_UMODE_SERVER_OPERATOR) ||
+           (mode & SILC_UMODE_ROUTER_OPERATOR))
+         client->ops->say(client, conn, "%s is %s", nickname,
+                          (mode & SILC_UMODE_SERVER_OPERATOR) ?
+                          "Server Operator" :
+                          (mode & SILC_UMODE_ROUTER_OPERATOR) ?
+                          "SILC Operator" : "[Unknown mode]");
+       
+       if (mode & SILC_UMODE_GONE)
+         client->ops->say(client, conn, "%s is gone", nickname);
+      }
+      
+      if (idle && nickname)
+       client->ops->say(client, conn, "%s has been idle %d %s",
+                        nickname,
+                        idle > 60 ? (idle / 60) : idle,
+                        idle > 60 ? "minutes" : "seconds");
+    }
+    break;
+    
+  case SILC_COMMAND_WHOWAS:
+    {
+      char buf[1024], *nickname, *username, *realname;
+      int len;
+      
+      /* XXX should use irssi routines */
+      
+      if (status == SILC_STATUS_ERR_NO_SUCH_NICK ||
+         status == SILC_STATUS_ERR_NO_SUCH_CLIENT_ID) {
+       char *tmp;
+       tmp = silc_argument_get_arg_type(silc_command_get_args(cmd_payload),
+                                        3, NULL);
+       if (tmp)
+         client->ops->say(client, conn, "%s: %s", tmp,
+                          silc_client_command_status_message(status));
+       else
+         client->ops->say(client, conn, "%s",
+                          silc_client_command_status_message(status));
+       break;
+      }
+      
+      if (!success)
+       return;
+      
+      (void)va_arg(vp, SilcClientEntry);
+      nickname = va_arg(vp, char *);
+      username = va_arg(vp, char *);
+      realname = va_arg(vp, char *);
+      
+      memset(buf, 0, sizeof(buf));
+      
+      if (nickname) {
+       len = strlen(nickname);
+       strncat(buf, nickname, len);
+       strncat(buf, " was ", 5);
+      }
+      
+      if (username) {
+       strncat(buf, username, strlen(nickname));
+      }
+       
+      if (realname) {
+       strncat(buf, " (", 2);
+       strncat(buf, realname, strlen(realname));
+       strncat(buf, ")", 1);
+      }
+      
+      client->ops->say(client, conn, "%s", buf);
+    }
+    break;
+    
+  case SILC_COMMAND_INVITE:
+    {
+      SilcChannelEntry channel;
+      char *invite_list;
+      
+      if (!success)
+       return;
+      
+      /* XXX should use irssi routines */
+      
+      channel = va_arg(vp, SilcChannelEntry);
+      invite_list = va_arg(vp, char *);
+      
+      if (invite_list)
+       silc_say(client, conn, "%s invite list: %s", channel->channel_name,
+                invite_list);
+      else
+       silc_say(client, conn, "%s invite list not set", 
+                channel->channel_name);
+    }
+    break;
+
+  case SILC_COMMAND_JOIN: 
+    {
+      char *channel, *mode, *topic;
+      uint32 modei;
+      SilcChannelEntry channel_entry;
+      SilcBuffer client_id_list;
+      uint32 list_count;
+
+      channel = va_arg(vp, char *);
+      channel_entry = va_arg(vp, SilcChannelEntry);
+      modei = va_arg(vp, uint32);
+      (void)va_arg(vp, uint32);
+      (void)va_arg(vp, unsigned char *);
+      (void)va_arg(vp, unsigned char *);
+      (void)va_arg(vp, unsigned char *);
+      topic = va_arg(vp, char *);
+      (void)va_arg(vp, unsigned char *);
+      list_count = va_arg(vp, uint32);
+      client_id_list = va_arg(vp, SilcBuffer);
+
+      if (!success)
+       return;
+
+      chanrec = silc_channel_find(server, channel);
+      if (chanrec != NULL && !success)
+       channel_destroy(CHANNEL(chanrec));
+      else if (chanrec == NULL && success)
+       chanrec = silc_channel_create(server, channel, TRUE);
+      
+      if (topic) {
+       g_free_not_null(chanrec->topic);
+       chanrec->topic = *topic == '\0' ? NULL : g_strdup(topic);
+       signal_emit("channel topic changed", 1, chanrec);
+      }
+
+      mode = silc_client_chmode(modei, channel_entry);
+      g_free_not_null(chanrec->mode);
+      chanrec->mode = g_strdup(mode == NULL ? "" : mode);
+      signal_emit("channel mode changed", 1, chanrec);
+
+      /* Resolve the client information */
+      silc_client_get_clients_by_list(client, conn, list_count, client_id_list,
+                                     silc_client_join_get_users, 
+                                     channel_entry);
+      break;
+    }
+
+  case SILC_COMMAND_NICK: 
+    {
+      SilcClientEntry client = va_arg(vp, SilcClientEntry);
+      char *old;
+      
+      if (!success)
+       return;
+
+      old = g_strdup(server->nick);
+      server_change_nick(SERVER(server), client->nickname);
+      nicklist_rename_unique(SERVER(server),
+                            server->conn->local_entry, server->nick,
+                            client, client->nickname);
+      
+      signal_emit("message own_nick", 4, server, server->nick, old, "");
+      g_free(old);
+      break;
+    }
+    
+  case SILC_COMMAND_LIST:
+    {
+      char *topic, *name;
+      int usercount;
+      unsigned char buf[256], tmp[16];
+      int i, len;
+      
+      if (!success)
+       return;
+      
+      /* XXX should use irssi routines */
+      
+      (void)va_arg(vp, SilcChannelEntry);
+      name = va_arg(vp, char *);
+      topic = va_arg(vp, char *);
+      usercount = va_arg(vp, int);
+      
+      if (status == SILC_STATUS_LIST_START ||
+         status == SILC_STATUS_OK)
+       silc_say(client, conn, 
+                "  Channel                                  Users     Topic");
+      
+      memset(buf, 0, sizeof(buf));
+      strncat(buf, "  ", 2);
+      len = strlen(name);
+      strncat(buf, name, len > 40 ? 40 : len);
+      if (len < 40)
+       for (i = 0; i < 40 - len; i++)
+         strcat(buf, " ");
+      strcat(buf, " ");
+      
+      memset(tmp, 0, sizeof(tmp));
+      if (usercount) {
+       snprintf(tmp, sizeof(tmp), "%d", usercount);
+       strcat(buf, tmp);
+      }
+      len = strlen(tmp);
+      if (len < 10)
+       for (i = 0; i < 10 - len; i++)
+         strcat(buf, " ");
+      strcat(buf, " ");
+      
+      if (topic) {
+       len = strlen(topic);
+       strncat(buf, topic, len);
+      }
+      
+      silc_say(client, conn, "%s", buf);
+    }
+    break;
+    
+  case SILC_COMMAND_UMODE:
+    {
+      uint32 mode;
+      
+      if (!success)
+       return;
+      
+      mode = va_arg(vp, uint32);
+      
+      /* XXX todo */
+    }
+    break;
+    
+  case SILC_COMMAND_OPER:
+#if 0
+    if (status == SILC_STATUS_OK) {
+      conn->local_entry->mode |= SILC_UMODE_SERVER_OPERATOR;
+      if (app->screen->bottom_line->umode)
+       silc_free(app->screen->bottom_line->umode);
+      app->screen->bottom_line->umode = strdup("Server Operator");;
+      silc_screen_print_bottom_line(app->screen, 0);
+    }
+#endif
+    break;
+    
+  case SILC_COMMAND_SILCOPER:
+#if 0
+    if (status == SILC_STATUS_OK) {
+      conn->local_entry->mode |= SILC_UMODE_ROUTER_OPERATOR;
+      if (app->screen->bottom_line->umode)
+       silc_free(app->screen->bottom_line->umode);
+      app->screen->bottom_line->umode = strdup("SILC Operator");;
+      silc_screen_print_bottom_line(app->screen, 0);
+    }
+#endif
+    break;
+    
+  case SILC_COMMAND_USERS: 
+    {
+      SilcChannelEntry channel;
+      SilcChannelUser chu;
+      int line_len;
+      char *line;
+      
+      if (!success)
+       return;
+      
+      channel = va_arg(vp, SilcChannelEntry);
+      
+      /* There are two ways to do this, either parse the list (that
+        the command_reply sends (just take it with va_arg()) or just
+        traverse the channel's client list.  I'll do the latter.  See
+        JOIN command reply for example for the list. */
+      
+      silc_say(client, conn, "Users on %s", channel->channel_name);
+       
+      line = silc_calloc(1024, sizeof(*line));
+      line_len = 1024;
+      silc_list_start(channel->clients);
+      while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
+       SilcClientEntry e = chu->client;
+       int i, len1;
+       char *m, tmp[80];
+       
+       memset(line, 0, line_len);
+       
+       if (strlen(e->nickname) + strlen(e->server) + 100 > line_len) {
+         silc_free(line);
+         line_len += strlen(e->nickname) + strlen(e->server) + 100;
+         line = silc_calloc(line_len, sizeof(*line));
        }
+       
+       memset(tmp, 0, sizeof(tmp));
+       m = silc_client_chumode_char(chu->mode);
+       
+       strncat(line, " ", 1);
+       strncat(line, e->nickname, strlen(e->nickname));
+       strncat(line, e->server ? "@" : "", 1);
+       
+       len1 = 0;
+       if (e->server)
+         len1 = strlen(e->server);
+       strncat(line, e->server ? e->server : "", len1 > 30 ? 30 : len1);
+       
+       len1 = strlen(line);
+       if (len1 >= 30) {
+         memset(&line[29], 0, len1 - 29);
+       } else {
+         for (i = 0; i < 30 - len1 - 1; i++)
+           strcat(line, " ");
        }
-
-       va_end(va);
+       
+       if (e->mode & SILC_UMODE_GONE)
+         strcat(line, "  G");
+       else
+         strcat(line, "  H");
+       strcat(tmp, m ? m : "");
+       strncat(line, tmp, strlen(tmp));
+       
+       if (strlen(tmp) < 5)
+         for (i = 0; i < 5 - strlen(tmp); i++)
+           strcat(line, " ");
+       
+       strcat(line, e->username ? e->username : "");
+       
+       silc_say(client, conn, "%s", line);
+       
+       if (m)
+         silc_free(m);
+      }
+      
+      silc_free(line);
+    }
+    break;
+
+  case SILC_COMMAND_BAN:
+    {
+      SilcChannelEntry channel;
+      char *ban_list;
+      
+      if (!success)
+       return;
+      
+      /* XXX should use irssi routines */
+          
+      channel = va_arg(vp, SilcChannelEntry);
+      ban_list = va_arg(vp, char *);
+      
+      if (ban_list)
+       silc_say(client, conn, "%s ban list: %s", channel->channel_name,
+                ban_list);
+      else
+       silc_say(client, conn, "%s ban list not set", channel->channel_name);
+    }
+    break;
+    
+  case SILC_COMMAND_GETKEY:
+    {
+      SilcIdType id_type;
+      void *entry;
+      SilcPublicKey public_key;
+      unsigned char *pk;
+      uint32 pk_len;
+      
+      id_type = va_arg(vp, uint32);
+      entry = va_arg(vp, void *);
+      public_key = va_arg(vp, SilcPublicKey);
+      
+      pk = silc_pkcs_public_key_encode(public_key, &pk_len);
+      
+      if (id_type == SILC_ID_CLIENT) {
+       silc_verify_public_key_internal(client, conn, SILC_SOCKET_TYPE_CLIENT,
+                                       pk, pk_len, SILC_SKE_PK_TYPE_SILC,
+                                       NULL, NULL);
+      }
+      
+      silc_free(pk);
+    }
+    
+  case SILC_COMMAND_TOPIC:
+    {
+      SilcChannelEntry channel;
+      char *topic;
+      
+      if (!success)
+       return;
+      
+      channel = va_arg(vp, SilcChannelEntry);
+      topic = va_arg(vp, char *);
+      
+      /* XXX should use irssi routines */
+      
+      if (topic)
+       silc_say(client, conn, 
+                "Topic on channel %s: %s", channel->channel_name,
+                topic);
+    }
+    break;
+  }
+
+  va_end(vp);
 }
 
-static int silc_verify_server_key(SilcClient client, SilcClientConnection conn,
-                                 unsigned char *pk, unsigned int pk_len,
-                                 SilcSKEPKType pk_type)
-{
-       return TRUE;
-}
+/* Internal routine to verify public key. If the `completion' is provided
+   it will be called to indicate whether public was verified or not. */
 
-static unsigned char *silc_ask_passphrase(SilcClient client,
-                                         SilcClientConnection conn)
+typedef struct {
+  SilcClient client;
+  SilcClientConnection conn;
+  char *filename;
+  char *entity;
+  unsigned char *pk;
+  uint32 pk_len;
+  SilcSKEPKType pk_type;
+  SilcVerifyPublicKey completion;
+  void *context;
+} *PublicKeyVerify;
+
+static void verify_public_key_completion(const char *line, void *context)
 {
-       return NULL;
+  PublicKeyVerify verify = (PublicKeyVerify)context;
+
+  if (line[0] == 'Y' || line[0] == 'y') {
+    /* Call the completion */
+    if (verify->completion)
+      verify->completion(TRUE, verify->context);
+
+    /* Save the key for future checking */
+    silc_pkcs_save_public_key_data(verify->filename, verify->pk, 
+                                  verify->pk_len, SILC_PKCS_FILE_PEM);
+  } else {
+    /* Call the completion */
+    if (verify->completion)
+      verify->completion(FALSE, verify->context);
+
+    silc_say(verify->client, 
+            verify->conn, "Will not accept the %s key", verify->entity);
+  }
+
+  silc_free(verify->filename);
+  silc_free(verify->entity);
+  silc_free(verify);
 }
 
-static int silc_get_auth_method(SilcClient client, SilcClientConnection conn,
-                               char *hostname, unsigned short port,
-                               SilcProtocolAuthMeth *auth_meth,
-                               unsigned char **auth_data,
-                               unsigned int *auth_data_len)
+static void 
+silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn,
+                               SilcSocketType conn_type, unsigned char *pk, 
+                               uint32 pk_len, SilcSKEPKType pk_type,
+                               SilcVerifyPublicKey completion, void *context)
 {
-       return FALSE;
+  int i;
+  char file[256], filename[256], *fingerprint;
+  struct passwd *pw;
+  struct stat st;
+  char *entity = ((conn_type == SILC_SOCKET_TYPE_SERVER ||
+                  conn_type == SILC_SOCKET_TYPE_ROUTER) ? 
+                 "server" : "client");
+  PublicKeyVerify verify;
+
+  if (pk_type != SILC_SKE_PK_TYPE_SILC) {
+    silc_say(client, conn, "We don't support %s public key type %d", 
+            entity, pk_type);
+    if (completion)
+      completion(FALSE, context);
+    return;
+  }
+
+  pw = getpwuid(getuid());
+  if (!pw) {
+    if (completion)
+      completion(FALSE, context);
+    return;
+  }
+
+  memset(filename, 0, sizeof(filename));
+  memset(file, 0, sizeof(file));
+
+  if (conn_type == SILC_SOCKET_TYPE_SERVER ||
+      conn_type == SILC_SOCKET_TYPE_ROUTER) {
+    snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity, 
+            conn->sock->hostname, conn->sock->port);
+    snprintf(filename, sizeof(filename) - 1, "%s/.silc/%skeys/%s", 
+            pw->pw_dir, entity, file);
+  } else {
+    /* Replace all whitespaces with `_'. */
+    fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+    for (i = 0; i < strlen(fingerprint); i++)
+      if (fingerprint[i] == ' ')
+       fingerprint[i] = '_';
+    
+    snprintf(file, sizeof(file) - 1, "%skey_%s.pub", entity, fingerprint);
+    snprintf(filename, sizeof(filename) - 1, "%s/.silc/%skeys/%s", 
+            pw->pw_dir, entity, file);
+    silc_free(fingerprint);
+  }
+
+  /* Take fingerprint of the public key */
+  fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+
+  verify = silc_calloc(1, sizeof(*verify));
+  verify->client = client;
+  verify->conn = conn;
+  verify->filename = strdup(filename);
+  verify->entity = strdup(entity);
+  verify->pk = pk;
+  verify->pk_len = pk_len;
+  verify->pk_type = pk_type;
+  verify->completion = completion;
+  verify->context = context;
+
+  /* Check whether this key already exists */
+  if (stat(filename, &st) < 0) {
+    /* Key does not exist, ask user to verify the key and save it */
+
+    silc_say(client, conn, "Received %s public key", entity);
+    silc_say(client, conn, "Fingerprint for the %s key is", entity);
+    silc_say(client, conn, "%s", fingerprint);
+
+    keyboard_entry_redirect((SIGNAL_FUNC)verify_public_key_completion,
+                           "Would you like to accept the key (y/n)? ", 0,
+                           verify);
+    silc_free(fingerprint);
+    return;
+  } else {
+    /* The key already exists, verify it. */
+    SilcPublicKey public_key;
+    unsigned char *encpk;
+    uint32 encpk_len;
+
+    /* Load the key file */
+    if (!silc_pkcs_load_public_key(filename, &public_key, 
+                                  SILC_PKCS_FILE_PEM))
+      if (!silc_pkcs_load_public_key(filename, &public_key, 
+                                    SILC_PKCS_FILE_BIN)) {
+       silc_say(client, conn, "Received %s public key", entity);
+       silc_say(client, conn, "Fingerprint for the %s key is", entity);
+       silc_say(client, conn, "%s", fingerprint);
+       silc_say(client, conn, "Could not load your local copy of the %s key",
+                entity);
+       keyboard_entry_redirect((SIGNAL_FUNC)verify_public_key_completion,
+                               "Would you like to accept the key "
+                               "anyway (y/n)? ", 0,
+                               verify);
+       silc_free(fingerprint);
+       return;
+      }
+  
+    /* Encode the key data */
+    encpk = silc_pkcs_public_key_encode(public_key, &encpk_len);
+    if (!encpk) {
+      silc_say(client, conn, "Received %s public key", entity);
+      silc_say(client, conn, "Fingerprint for the %s key is", entity);
+      silc_say(client, conn, "%s", fingerprint);
+      silc_say(client, conn, "Your local copy of the %s key is malformed",
+              entity);
+      keyboard_entry_redirect((SIGNAL_FUNC)verify_public_key_completion,
+                             "Would you like to accept the key "
+                             "anyway (y/n)? ", 0,
+                             verify);
+      silc_free(fingerprint);
+      return;
+    }
+
+    /* Compare the keys */
+    if (memcmp(encpk, pk, encpk_len)) {
+      silc_say(client, conn, "Received %s public key", entity);
+      silc_say(client, conn, "Fingerprint for the %s key is", entity);
+      silc_say(client, conn, "%s", fingerprint);
+      silc_say(client, conn, "%s key does not match with your local copy",
+              entity);
+      silc_say(client, conn, 
+              "It is possible that the key has expired or changed");
+      silc_say(client, conn, "It is also possible that some one is performing "
+                      "man-in-the-middle attack");
+
+      /* Ask user to verify the key and save it */
+      keyboard_entry_redirect((SIGNAL_FUNC)verify_public_key_completion,
+                             "Would you like to accept the key "
+                             "anyway (y/n)? ", 0,
+                             verify);
+      silc_free(fingerprint);
+      return;
+    }
+
+    /* Local copy matched */
+    if (completion)
+      completion(TRUE, context);
+    silc_free(fingerprint);
+  }
 }
 
-static void silc_failure(SilcClient client, SilcClientConnection conn,
-                        SilcProtocol protocol, void *failure)
-{
-}
+/* Verifies received public key. The `conn_type' indicates which entity
+   (server, client etc.) has sent the public key. If user decides to trust
+   the key may be saved as trusted public key for later use. The 
+   `completion' must be called after the public key has been verified. */
 
-static int key_agreement(SilcClient client, SilcClientConnection conn,
-                        SilcClientEntry client_entry, char *hostname,
-                        int port)
+static void 
+silc_verify_public_key(SilcClient client, SilcClientConnection conn,
+                      SilcSocketType conn_type, unsigned char *pk, 
+                      uint32 pk_len, SilcSKEPKType pk_type,
+                      SilcVerifyPublicKey completion, void *context)
 {
-        return FALSE;
+  silc_verify_public_key_internal(client, conn, conn_type, pk,
+                                 pk_len, pk_type,
+                                 completion, context);
 }
 
-SilcClientOperations ops = {
-       silc_say,
-       silc_channel_message,
-       silc_private_message,
-       silc_notify,
-       silc_command,
-       silc_command_reply,
-       silc_connect,
-       silc_disconnect,
-       silc_get_auth_method,
-       silc_verify_server_key,
-       silc_ask_passphrase,
-       silc_failure,
-        key_agreement
-};
+/* Asks passphrase from user on the input line. */
 
-/* Loads public and private key from files. */
+typedef struct {
+  SilcAskPassphrase completion;
+  void *context;
+} *AskPassphrase;
 
-static void silc_client_create_key_pair(char *pkcs_name, int bits,
-                                       char *identifier,
-                                       SilcPublicKey *pub_key,
-                                       SilcPrivateKey *prv_key)
+static void ask_passphrase_completion(const char *passphrase, void *context)
 {
-       SilcPKCS pkcs;
-       SilcRng rng;
-       unsigned char *key;
-       unsigned int key_len;
-
-       rng = silc_rng_alloc();
-       silc_rng_init(rng);
-       silc_rng_global_init(rng);
-
-       silc_pkcs_alloc(pkcs_name, &pkcs);
-       pkcs->pkcs->init(pkcs->context, bits, rng);
-
-       /* Create public key */
-       key = silc_pkcs_get_public_key(pkcs, &key_len);
-       *pub_key = silc_pkcs_public_key_alloc(pkcs->pkcs->name, identifier,
-                                             key, key_len);
-
-       memset(key, 0, sizeof(key_len));
-       silc_free(key);
-
-       /* Create private key */
-       key = silc_pkcs_get_private_key(pkcs, &key_len);
-       *prv_key = silc_pkcs_private_key_alloc(pkcs->pkcs->name, key, key_len);
+  AskPassphrase p = (AskPassphrase)context;
+  p->completion((unsigned char *)passphrase, 
+               passphrase ? strlen(passphrase) : 0, p->context);
+  silc_free(p);
+}
 
-       memset(key, 0, sizeof(key_len));
-       silc_free(key);
+static void silc_ask_passphrase(SilcClient client, SilcClientConnection conn,
+                               SilcAskPassphrase completion, void *context)
+{
+  AskPassphrase p = silc_calloc(1, sizeof(*p));
+  p->completion = completion;
+  p->context = context;
 
-       silc_rng_free(rng);
-       silc_pkcs_free(pkcs);
+  keyboard_entry_redirect((SIGNAL_FUNC)ask_passphrase_completion,
+                         "Passphrase: ", ENTRY_REDIRECT_FLAG_HIDDEN, p);
 }
 
-static int read_keyfiles(SilcClient client, char *public_file,
-                        char *private_file)
+/* Find authentication method and authentication data by hostname and
+   port. The hostname may be IP address as well. The found authentication
+   method and authentication data is returned to `auth_meth', `auth_data'
+   and `auth_data_len'. The function returns TRUE if authentication method
+   is found and FALSE if not. `conn' may be NULL. */
+
+static int 
+silc_get_auth_method(SilcClient client, SilcClientConnection conn,
+                    char *hostname, uint16 port,
+                    SilcProtocolAuthMeth *auth_meth,
+                    unsigned char **auth_data,
+                    uint32 *auth_data_len)
 {
-       struct stat statbuf;
 
-       if (stat(public_file, &statbuf) != 0 ||
-           stat(private_file, &statbuf) != 0)
-               return FALSE;
+  /* XXX must resolve from configuration whether this connection has
+     any specific authentication data */
 
-       if (!silc_pkcs_load_private_key(private_file, &client->private_key,
-                                       SILC_PKCS_FILE_BIN) &&
-           !silc_pkcs_load_private_key(private_file, &client->private_key,
-                                       SILC_PKCS_FILE_PEM))
-               return FALSE;
+  *auth_meth = SILC_AUTH_NONE;
+  *auth_data = NULL;
+  *auth_data_len = 0;
+
+  return TRUE;
+}
 
-       if (!silc_pkcs_load_public_key(public_file, &client->public_key,
-                                      SILC_PKCS_FILE_PEM) &&
-           !silc_pkcs_load_public_key(public_file, &client->public_key,
-                                      SILC_PKCS_FILE_BIN))
-               return FALSE;
+/* Notifies application that failure packet was received.  This is called
+   if there is some protocol active in the client.  The `protocol' is the
+   protocol context.  The `failure' is opaque pointer to the failure
+   indication.  Note, that the `failure' is protocol dependant and application
+   must explicitly cast it to correct type.  Usually `failure' is 32 bit
+   failure type (see protocol specs for all protocol failure types). */
 
-       return TRUE;
+static void 
+silc_failure(SilcClient client, SilcClientConnection conn, 
+            SilcProtocol protocol, void *failure)
+{
+  if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_KEY_EXCHANGE) {
+    SilcSKEStatus status = (SilcSKEStatus)failure;
+    
+    if (status == SILC_SKE_STATUS_BAD_VERSION)
+      silc_say_error("You are running incompatible client version (it may be "
+                    "too old or too new)");
+    if (status == SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY)
+      silc_say_error("Server does not support your public key type");
+    if (status == SILC_SKE_STATUS_UNKNOWN_GROUP)
+      silc_say_error("Server does not support one of your proposed KE group");
+    if (status == SILC_SKE_STATUS_UNKNOWN_CIPHER)
+      silc_say_error("Server does not support one of your proposed cipher");
+    if (status == SILC_SKE_STATUS_UNKNOWN_PKCS)
+      silc_say_error("Server does not support one of your proposed PKCS");
+    if (status == SILC_SKE_STATUS_UNKNOWN_HASH_FUNCTION)
+      silc_say_error("Server does not support one of your proposed "
+                    "hash function");
+    if (status == SILC_SKE_STATUS_UNKNOWN_HMAC)
+      silc_say_error("Server does not support one of your proposed HMAC");
+    if (status == SILC_SKE_STATUS_INCORRECT_SIGNATURE)
+      silc_say_error("Incorrect signature");
+  }
+
+  if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_CONNECTION_AUTH) {
+    uint32 err = (uint32)failure;
+
+    if (err == SILC_AUTH_FAILED)
+      silc_say(client, conn, "Authentication failed");
+  }
 }
 
-static char *silc_create_identifier(SilcClient client)
+/* Asks whether the user would like to perform the key agreement protocol.
+   This is called after we have received an key agreement packet or an
+   reply to our key agreement packet. This returns TRUE if the user wants
+   the library to perform the key agreement protocol and FALSE if it is not
+   desired (application may start it later by calling the function
+   silc_client_perform_key_agreement). */
+
+static int 
+silc_key_agreement(SilcClient client, SilcClientConnection conn,
+                  SilcClientEntry client_entry, char *hostname,
+                  int port,
+                  SilcKeyAgreementCallback *completion,
+                  void **context)
 {
-       char hostname[256], *email, *ret;
+  char host[256];
 
-       if (gethostname(hostname, sizeof(hostname)) != 0)
-               hostname[0] = '\0';
+  /* We will just display the info on the screen and return FALSE and user
+     will have to start the key agreement with a command. */
 
-       email = g_strdup_printf("%s@%s", client->username, hostname);
-       ret = silc_pkcs_encode_identifier(client->username, hostname,
-                                         client->realname, email,
-                                         NULL, NULL);
-       g_free(email);
-       return ret;
-}
+  if (hostname) {
+    memset(host, 0, sizeof(host));
+    snprintf(host, sizeof(host) - 1, "(%s on port %d)", hostname, port); 
+  }
 
-static int load_keys(SilcClient client)
-{
-       char *public_file, *private_file;
-       char *identifier;
-
-       public_file = g_strdup_printf("%s/.irssi/%s", g_get_home_dir(),
-                                     SILC_CLIENT_PUBLIC_KEY_NAME);
-       private_file = g_strdup_printf("%s/.irssi/%s", g_get_home_dir(),
-                                      SILC_CLIENT_PRIVATE_KEY_NAME);
-
-       if (!read_keyfiles(client, public_file, private_file)) {
-               /* couldn't read key files, recreate them */
-               identifier = silc_create_identifier(client);
-               silc_client_create_key_pair(SILC_CLIENT_DEF_PKCS,
-                                           SILC_CLIENT_DEF_PKCS_LEN,
-                                           identifier,
-                                           &client->public_key,
-                                           &client->private_key);
-               silc_free(identifier);
-
-               silc_pkcs_save_public_key(public_file, client->public_key,
-                                         SILC_PKCS_FILE_PEM);
-               silc_pkcs_save_private_key(private_file, client->private_key,
-                                          NULL, SILC_PKCS_FILE_BIN);
-       }
+  silc_say(client, conn, "%s wants to perform key agreement %s",
+          client_entry->nickname, hostname ? host : "");
+
+  *completion = NULL;
+  *context = NULL;
 
-       g_free(public_file);
-       g_free(private_file);
-       return TRUE;
+  return FALSE;
 }
 
+/* SILC client operations */
+SilcClientOperations ops = {
+  silc_say,
+  silc_channel_message,
+  silc_private_message,
+  silc_notify,
+  silc_command,
+  silc_command_reply,
+  silc_connect,
+  silc_disconnect,
+  silc_get_auth_method,
+  silc_verify_public_key,
+  silc_ask_passphrase,
+  silc_failure,
+  silc_key_agreement,
+};
+
 static int my_silc_scheduler(void)
 {
-       silc_schedule_one(0);
-       return 1;
+  silc_schedule_one(0);
+  return 1;
 }
 
 static CHATNET_REC *create_chatnet(void)
 {
-        return g_malloc0(sizeof(CHATNET_REC));
+  return g_malloc0(sizeof(CHATNET_REC));
 }
 
 static SERVER_SETUP_REC *create_server_setup(void)
 {
-        return g_malloc0(sizeof(SERVER_SETUP_REC));
+  return g_malloc0(sizeof(SERVER_SETUP_REC));
 }
 
 static CHANNEL_SETUP_REC *create_channel_setup(void)
 {
-        return g_malloc0(sizeof(CHANNEL_SETUP_REC));
+  return g_malloc0(sizeof(CHANNEL_SETUP_REC));
 }
 
 static SERVER_CONNECT_REC *create_server_connect(void)
 {
-        return g_malloc0(sizeof(SILC_SERVER_CONNECT_REC));
+  return g_malloc0(sizeof(SILC_SERVER_CONNECT_REC));
 }
 
-/* Command line option variables */
-void silc_core_init(void)
+/* Checks user information and saves them to the config file it they
+   do not exist there already. */
+
+static void silc_init_userinfo(void)
 {
-       CHAT_PROTOCOL_REC *rec;
+  const char *set, *nick, *user_name;
+  char *str;   
+        
+  /* check if nick/username/realname wasn't read from setup.. */
+  set = settings_get_str("real_name");
+  if (set == NULL || *set == '\0') {
+    str = g_getenv("SILCNAME");
+    if (!str)
+      str = g_getenv("IRCNAME");
+    settings_set_str("real_name",
+                    str != NULL ? str : g_get_real_name());
+  }
+  /* username */
+  user_name = settings_get_str("user_name");
+  if (user_name == NULL || *user_name == '\0') {
+    str = g_getenv("SILCUSER");
+    if (!str)
+      str = g_getenv("IRCUSER");
+    settings_set_str("user_name",
+                    str != NULL ? str : g_get_user_name());
+    
+    user_name = settings_get_str("user_name");
+  }
+         
+  /* nick */
+  nick = settings_get_str("nick");
+  if (nick == NULL || *nick == '\0') {
+    str = g_getenv("SILCNICK");
+    if (!str)
+      str = g_getenv("IRCNICK");
+    settings_set_str("nick", str != NULL ? str : user_name);
+    
+    nick = settings_get_str("nick");
+  }
+                
+  /* alternate nick */
+  set = settings_get_str("alternate_nick");
+  if (set == NULL || *set == '\0') {
+    if (strlen(nick) < 9)
+      str = g_strconcat(nick, "_", NULL);
+    else { 
+      str = g_strdup(nick);
+      str[strlen(str)-1] = '_';
+    }
+    settings_set_str("alternate_nick", str);
+    g_free(str);
+  }
+
+  /* host name */
+  set = settings_get_str("hostname");
+  if (set == NULL || *set == '\0') {
+    str = g_getenv("SILCHOST");
+    if (!str)
+      str = g_getenv("IRCHOST");
+    if (str != NULL)
+      settings_set_str("hostname", str);
+  }
+}
 
-       silc_client = silc_client_alloc(&ops, NULL);
-       silc_client->username = g_strdup(settings_get_str("user_name"));
-       silc_client->hostname = silc_net_localhost();
-       silc_client->realname = g_strdup(settings_get_str("real_name"));
+/* Log callbacks */
 
-       if (!load_keys(silc_client)) {
-               idletag = -1;
-               return;
-       }
-
-       silc_client_init(silc_client);
+static void silc_log_info(char *message)
+{
+  fprintf(stderr, "%s\n", message);
+}
 
-       rec = g_new0(CHAT_PROTOCOL_REC, 1);
-       rec->name = "SILC";
-       rec->fullname = "Secure Internet Live Conferencing";
-       rec->chatnet = "silcnet";
+static void silc_log_warning(char *message)
+{
+  fprintf(stderr, "%s\n", message);
+}
 
-       rec->create_chatnet = create_chatnet;
-        rec->create_server_setup = create_server_setup;
-        rec->create_channel_setup = create_channel_setup;
-       rec->create_server_connect = create_server_connect;
+static void silc_log_error(char *message)
+{
+  fprintf(stderr, "%s\n", message);
+}
 
-       rec->server_connect = (SERVER_REC *(*) (SERVER_CONNECT_REC *))
-               silc_server_connect;
-       rec->channel_create =
-               (CHANNEL_REC *(*) (SERVER_REC *, const char *, int))
-                silc_channel_create;
-       rec->query_create =
-               (QUERY_REC *(*) (const char *, const char *, int))
-                silc_query_create;
+/* Init SILC. Called from src/fe-text/silc.c */
 
-       chat_protocol_register(rec);
-       g_free(rec);
+void silc_core_init(void)
+{
+  static struct poptOption options[] = {
+    { "create-key-pair", 'C', POPT_ARG_NONE, &opt_create_keypair, 0, 
+      "Create new public key pair", NULL },
+    { "pkcs", 0, POPT_ARG_STRING, &opt_pkcs, 0, 
+      "Set the PKCS of the public key pair", "PKCS" },
+    { "bits", 0, POPT_ARG_INT, &opt_bits, 0, 
+      "Set the length of the public key pair", "VALUE" },
+    { "show-key", 'S', POPT_ARG_STRING, &opt_keyfile, 0, 
+      "Show the contents of the public key", "FILE" },
+    { "debug", 'd', POPT_ARG_NONE, &opt_debug, 0,
+      "Enable debugging", NULL },
+    { NULL, '\0', 0, NULL }
+  };
+
+  args_register(options);
+}
 
-       silc_server_init();
-       silc_channels_init();
-       silc_queries_init();
+/* Finalize init. Called from src/fe-text/silc.c */
 
-       idletag = g_timeout_add(100, (GSourceFunc) my_silc_scheduler, NULL);
+void silc_core_init_finish(void)
+{
+  CHAT_PROTOCOL_REC *rec;
+
+  if (opt_create_keypair == TRUE) {
+    /* Create new key pair and exit */
+    silc_cipher_register_default();
+    silc_pkcs_register_default();
+    silc_hash_register_default();
+    silc_hmac_register_default();
+    silc_client_create_key_pair(opt_pkcs, opt_bits, 
+                               NULL, NULL, NULL, NULL, NULL);
+    exit(0);
+  }
+
+  if (opt_keyfile) {
+    /* Dump the key */
+    silc_cipher_register_default();
+    silc_pkcs_register_default();
+    silc_hash_register_default();
+    silc_hmac_register_default();
+    silc_client_show_key(opt_keyfile);
+    exit(0);
+  }
+
+  silc_debug = opt_debug;
+  silc_log_set_callbacks(silc_log_info, silc_log_warning,
+                        silc_log_error, NULL);
+
+  /* Do some irssi initializing */
+  settings_add_bool("server", "skip_motd", FALSE);
+  settings_add_str("server", "alternate_nick", NULL);
+  silc_init_userinfo();
+
+  /* Allocate SILC client */
+  silc_client = silc_client_alloc(&ops, NULL);
+
+  /* Load local config file */
+  silc_config = silc_client_config_alloc(SILC_CLIENT_HOME_CONFIG_FILE);
+
+  /* Get user information */
+  silc_client->username = g_strdup(settings_get_str("user_name"));
+  silc_client->hostname = silc_net_localhost();
+  silc_client->realname = g_strdup(settings_get_str("real_name"));
+
+  /* Register all configured ciphers, PKCS and hash functions. */
+  if (silc_config) {
+    silc_config->client = silc_client;
+    if (!silc_client_config_register_ciphers(silc_config))
+      silc_cipher_register_default();
+    if (!silc_client_config_register_pkcs(silc_config))
+      silc_pkcs_register_default();
+    if (!silc_client_config_register_hashfuncs(silc_config))
+      silc_hash_register_default();
+    if (!silc_client_config_register_hmacs(silc_config))
+      silc_hmac_register_default();
+  } else {
+    /* Register default ciphers, pkcs, hash funtions and hmacs. */
+    silc_cipher_register_default();
+    silc_pkcs_register_default();
+    silc_hash_register_default();
+    silc_hmac_register_default();
+  }
+
+  /* Check ~/.silc directory and public and private keys */
+  if (silc_client_check_silc_dir() == FALSE) {
+    idletag = -1;
+    return;
+  }
+
+  /* Load public and private key */
+  if (silc_client_load_keys(silc_client) == FALSE) {
+    idletag = -1;
+    return;
+  }
+
+  /* Initialize the SILC client */
+  if (!silc_client_init(silc_client)) {
+    idletag = -1;
+    return;
+  }
+
+  /* Register SILC to the irssi */
+  rec = g_new0(CHAT_PROTOCOL_REC, 1);
+  rec->name = "SILC";
+  rec->fullname = "Secure Internet Live Conferencing";
+  rec->chatnet = "silcnet";
+  rec->create_chatnet = create_chatnet;
+  rec->create_server_setup = create_server_setup;
+  rec->create_channel_setup = create_channel_setup;
+  rec->create_server_connect = create_server_connect;
+  rec->server_connect = (SERVER_REC *(*) (SERVER_CONNECT_REC *))
+    silc_server_connect; 
+  rec->channel_create = (CHANNEL_REC *(*) (SERVER_REC *, const char *, int))
+    silc_channel_create;
+  rec->query_create = (QUERY_REC *(*) (const char *, const char *, int))
+    silc_query_create;
+  
+  chat_protocol_register(rec);
+  g_free(rec);
+
+  silc_server_init();
+  silc_channels_init();
+  silc_queries_init();
+
+  idletag = g_timeout_add(50, (GSourceFunc) my_silc_scheduler, NULL);
 }
 
+/* Deinit SILC. Called from src/fe-text/silc.c */
+
 void silc_core_deinit(void)
 {
-       if (idletag != -1) {
-               signal_emit("chat protocol deinit", 1,
-                           chat_protocol_find("SILC"));
-
-               silc_server_deinit();
-               silc_channels_deinit();
-               silc_queries_deinit();
-
-               chat_protocol_unregister("SILC");
-
-               g_source_remove(idletag);
-       }
-
-       g_free(silc_client->username);
-       g_free(silc_client->realname);
-       silc_client_free(silc_client);
+  if (idletag != -1) {
+    signal_emit("chat protocol deinit", 1,
+               chat_protocol_find("SILC"));
+    
+    silc_server_deinit();
+    silc_channels_deinit();
+    silc_queries_deinit();
+    
+    chat_protocol_unregister("SILC");
+    
+    g_source_remove(idletag);
+  }
+  
+  g_free(silc_client->username);
+  g_free(silc_client->realname);
+  silc_client_free(silc_client);
 }