Merged from silc_1_0_branch.
[silc.git] / apps / irssi / src / silc / core / client_ops.c
index e41cd55a2fa93af1283f2643bab26db791831d59..b55b0d31a7c859a9aa96d21fb73d0cdce191311c 100644 (file)
 #include "signals.h"
 #include "levels.h"
 #include "settings.h"
+#include "ignore.h"
 #include "fe-common/core/printtext.h"
 #include "fe-common/core/fe-channels.h"
 #include "fe-common/core/keyboard.h"
+#include "fe-common/core/window-items.h"
 #include "fe-common/silc/module-formats.h"
 
+#include "core.h"
+
 static void 
 silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn,
-                               SilcSocketType conn_type, unsigned char *pk, 
-                               uint32 pk_len, SilcSKEPKType pk_type,
+                               const char *name, SilcSocketType conn_type, 
+                               unsigned char *pk, SilcUInt32 pk_len, 
+                               SilcSKEPKType pk_type,
                                SilcVerifyPublicKey completion, void *context);
 
+static void silc_get_umode_string(SilcUInt32 mode, char *buf, 
+                                 SilcUInt32 buf_size)
+{
+  if ((mode & SILC_UMODE_SERVER_OPERATOR) ||
+      (mode & SILC_UMODE_ROUTER_OPERATOR)) {
+    strcat(buf, (mode & SILC_UMODE_SERVER_OPERATOR) ?
+          "[server operator]" :
+          (mode & SILC_UMODE_ROUTER_OPERATOR) ?
+          "[SILC operator]" : "[unknown mode]");
+  }
+  if (mode & SILC_UMODE_GONE)
+    strcat(buf, " [away]");
+  if (mode & SILC_UMODE_INDISPOSED)
+    strcat(buf, " [indisposed]");
+  if (mode & SILC_UMODE_BUSY)
+    strcat(buf, " [busy]");
+  if (mode & SILC_UMODE_PAGE)
+    strcat(buf, " [page to reach]");
+  if (mode & SILC_UMODE_HYPER)
+    strcat(buf, " [hyper active]");
+  if (mode & SILC_UMODE_ROBOT)
+    strcat(buf, " [robot]");
+  if (mode & SILC_UMODE_ANONYMOUS)
+    strcat(buf, " [anonymous]");
+  if (mode & SILC_UMODE_BLOCK_PRIVMSG)
+    strcat(buf, " [blocks private messages]");
+  if (mode & SILC_UMODE_DETACHED)
+    strcat(buf, " [detached]");
+  if (mode & SILC_UMODE_REJECT_WATCHING)
+    strcat(buf, " [rejects watching]");
+  if (mode & SILC_UMODE_BLOCK_INVITE)
+    strcat(buf, " [blocks invites]");
+}
+
+/* print "nick appears as" message to every channel of a server */
+static void 
+silc_print_nick_change_channel(SILC_SERVER_REC *server, const char *channel,
+                             const char *newnick, const char *oldnick,
+                             const char *address)
+{
+  if (ignore_check(SERVER(server), oldnick, address,
+                  channel, newnick, MSGLEVEL_NICKS))
+    return;
+  
+  printformat_module("fe-common/silc", server, channel, MSGLEVEL_NICKS,
+                    SILCTXT_CHANNEL_APPEARS,
+                    oldnick, newnick, channel, address);
+}
+
+static void
+silc_print_nick_change(SILC_SERVER_REC *server, const char *newnick,
+                      const char *oldnick, const char *address)
+{
+  GSList *tmp, *windows;
+
+  /* Print to each channel/query where the nick is.
+     Don't print more than once to the same window. */
+  windows = NULL;
+    
+  for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+    CHANNEL_REC *channel = tmp->data;
+    WINDOW_REC *window = window_item_window((WI_ITEM_REC *) channel);
+
+    if (nicklist_find(channel, newnick) == NULL ||
+       g_slist_find(windows, window) != NULL)
+      continue;
+
+    windows = g_slist_append(windows, window);
+    silc_print_nick_change_channel(server, channel->visible_name,
+                                  newnick, oldnick, address);
+  }
+
+  g_slist_free(windows);
+}
+
 void silc_say(SilcClient client, SilcClientConnection conn,
              SilcClientMessageType type, char *msg, ...)
 {
@@ -73,52 +153,306 @@ void silc_say_error(char *msg, ...)
   va_end(va);
 }
 
+/* try to verify a message using locally stored public key data */
+int verify_message_signature(SilcClientEntry sender,
+                            SilcMessageSignedPayload sig,
+                            SilcMessagePayload message)
+{
+  SilcPublicKey pk;
+  char file[256], filename[256];
+  char *fingerprint, *fingerprint2;
+  unsigned char *pk_data;
+  SilcUInt32 pk_datalen;
+  struct stat st;
+  int ret = SILC_MSG_SIGNED_VERIFIED, i;
+
+  if (sig == NULL)
+    return SILC_MSG_SIGNED_UNKNOWN;
+
+  /* get public key from the signature payload and compare it with the
+     one stored in the client entry */
+  pk = silc_message_signed_get_public_key(sig, &pk_data, &pk_datalen);
+
+  if (pk != NULL) {
+    fingerprint = silc_hash_fingerprint(NULL, pk_data, pk_datalen);
+
+    if (sender->fingerprint) {
+      fingerprint2 = silc_fingerprint(sender->fingerprint,
+                                     sender->fingerprint_len);
+      if (strcmp(fingerprint, fingerprint2)) {
+        /* since the public key differs from the senders public key, the
+           verification _failed_ */
+        silc_pkcs_public_key_free(pk);
+        silc_free(fingerprint);
+        ret = SILC_MSG_SIGNED_UNKNOWN;
+      }
+      silc_free(fingerprint2);
+    }
+  } else if (sender->fingerprint)
+    fingerprint = silc_fingerprint(sender->fingerprint,
+                                  sender->fingerprint_len);
+  else
+    /* no idea, who or what signed that message ... */
+    return SILC_MSG_SIGNED_UNKNOWN;
+  
+  /* search our local client key cache */
+  for (i = 0; i < strlen(fingerprint); i++)
+    if (fingerprint[i] == ' ')
+      fingerprint[i] = '_';
+    
+  snprintf(file, sizeof(file) - 1, "clientkey_%s.pub", fingerprint);
+  snprintf(filename, sizeof(filename) - 1, "%s/clientkeys/%s", 
+          get_irssi_dir(), file);
+  silc_free(fingerprint);
+  
+  if (stat(filename, &st) < 0)
+    /* we don't have the public key cached ... use the one from the sig */
+    ret = SILC_MSG_SIGNED_UNKNOWN;
+  else {
+    SilcPublicKey cached_pk=NULL;
+
+    /* try to load the file */
+    if (!silc_pkcs_load_public_key(filename, &cached_pk, SILC_PKCS_FILE_PEM) &&
+       !silc_pkcs_load_public_key(filename, &cached_pk, 
+                                  SILC_PKCS_FILE_BIN)) {
+      printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
+                        SILCTXT_PUBKEY_COULD_NOT_LOAD, "client");
+      if (pk == NULL)
+       return SILC_MSG_SIGNED_UNKNOWN;
+      else
+       ret = SILC_MSG_SIGNED_UNKNOWN;
+    }
+
+    if (cached_pk) {
+      if (pk)
+        silc_pkcs_public_key_free(pk);
+      pk = cached_pk; 
+    }
+  }
+
+  /* the public key is now in pk, our "level of trust" in ret */
+  if ((pk) && silc_message_signed_verify(sig, message, pk, 
+                                        silc_client->sha1hash)!= SILC_AUTH_OK)
+    ret = SILC_MSG_SIGNED_FAILED;
+
+  if (pk)
+    silc_pkcs_public_key_free(pk);
+
+  return ret;
+}
+
 /* 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. */
 
 void silc_channel_message(SilcClient client, SilcClientConnection conn,
                          SilcClientEntry sender, SilcChannelEntry channel,
-                         SilcMessageFlags flags, char *msg)
+                         SilcMessagePayload payload,
+                         SilcMessageFlags flags, const unsigned char *message,
+                         SilcUInt32 message_len)
 {
   SILC_SERVER_REC *server;
   SILC_NICK_REC *nick;
   SILC_CHANNEL_REC *chanrec;
+  int verified = 0;
   
+  SILC_LOG_DEBUG(("Start"));
+
+  if (!message)
+    return;
+
   server = conn == NULL ? NULL : conn->context;
   chanrec = silc_channel_find_entry(server, channel);
   if (!chanrec)
     return;
   
   nick = silc_nicklist_find(chanrec, sender);
+  if (!nick) {
+    /* We didn't find client but it clearly exists, add it. */
+    SilcChannelUser chu = silc_client_on_channel(channel, sender);
+    if (chu)
+      nick = silc_nicklist_insert(chanrec, chu, FALSE);
+  }
+
+  /* If the messages is digitally signed, verify it, if possible. */
+  if (flags & SILC_MESSAGE_FLAG_SIGNED) {
+    if (!settings_get_bool("ignore_message_signatures")) {
+      SilcMessageSignedPayload sig = silc_message_get_signature(payload);
+      verified = verify_message_signature(sender, sig, payload);
+    } else {
+      flags &= ~SILC_MESSAGE_FLAG_SIGNED;
+    }
+  }
+  
+  if (flags & SILC_MESSAGE_FLAG_DATA) {
+    /* MIME object received, try to display it as well as we can */
+    char type[128], enc[128];
+    unsigned char *data;
+    SilcUInt32 data_len;
+
+    memset(type, 0, sizeof(type));
+    memset(enc, 0, sizeof(enc));
+    if (!silc_mime_parse(message, message_len, NULL, 0, type, sizeof(type) - 1,
+                        enc, sizeof(enc) - 1, &data, &data_len))
+      return;
+
+    /* Then figure out what we can display */
+    if (strstr(type, "text/") && !strstr(type, "text/t140") &&
+       !strstr(type, "text/vnd")) {
+      /* It is something textual, display it */
+      message = (const unsigned char *)data;
+    } else {
+      printformat_module("fe-common/silc", server, channel->channel_name,
+                        MSGLEVEL_CRAP, SILCTXT_MESSAGE_DATA,
+                        nick == NULL ? "[<unknown>]" : nick->nick, type);
+      message = NULL;
+    }
+  }
+
+  if (!message)
+    return;
 
+  /* FIXME: replace those printformat calls with signals and add signature
+            information to them (if present) */
   if (flags & SILC_MESSAGE_FLAG_ACTION)
     printformat_module("fe-common/silc", server, channel->channel_name,
                       MSGLEVEL_ACTIONS, SILCTXT_CHANNEL_ACTION, 
-                       nick == NULL ? "[<unknown>]" : nick->nick, msg);
+                       nick == NULL ? "[<unknown>]" : nick->nick, message);
   else if (flags & SILC_MESSAGE_FLAG_NOTICE)
     printformat_module("fe-common/silc", server, channel->channel_name,
                       MSGLEVEL_NOTICES, SILCTXT_CHANNEL_NOTICE, 
-                       nick == NULL ? "[<unknown>]" : nick->nick, msg);
-  else
-    signal_emit("message public", 6, server, msg,
-               nick == NULL ? "[<unknown>]" : nick->nick,
-               nick == NULL ? "" : nick->host == NULL ? "" : nick->host,
-               chanrec->name, nick);
+                       nick == NULL ? "[<unknown>]" : nick->nick, message);
+  else {
+    if (flags & SILC_MESSAGE_FLAG_UTF8 && !silc_term_utf8()) {
+      char tmp[256], *cp, *dm = NULL;
+
+      memset(tmp, 0, sizeof(tmp));
+      cp = tmp;
+      if (message_len > sizeof(tmp) - 1) {
+       dm = silc_calloc(message_len + 1, sizeof(*dm));
+       cp = dm;
+      }
+
+      silc_utf8_decode(message, message_len, SILC_STRING_LANGUAGE,
+                      cp, message_len);
+      if (flags & SILC_MESSAGE_FLAG_SIGNED)
+        signal_emit("message signed_public", 6, server, cp,
+                   nick == NULL ? "[<unknown>]" : nick->nick,
+                   nick == NULL ? "" : nick->host == NULL ? "" : nick->host,
+                   chanrec->name, verified);
+      else
+        signal_emit("message public", 6, server, cp,
+                   nick == NULL ? "[<unknown>]" : nick->nick,
+                   nick == NULL ? "" : nick->host == NULL ? "" : nick->host,
+                   chanrec->name, nick);
+      silc_free(dm);
+      return;
+    }
+
+    if (flags & SILC_MESSAGE_FLAG_SIGNED)
+      signal_emit("message signed_public", 6, server, message,
+                 nick == NULL ? "[<unknown>]" : nick->nick,
+                 nick == NULL ? "" : nick->host == NULL ? "" : nick->host,
+                 chanrec->name, verified);
+    else
+      signal_emit("message public", 6, server, message,
+                 nick == NULL ? "[<unknown>]" : nick->nick,
+                 nick == NULL ? "" : nick->host == NULL ? "" : nick->host,
+                 chanrec->name, nick);
+  }
 }
 
 /* Private message to the client. The `sender' is the nickname of the
    sender received in the packet. */
 
 void silc_private_message(SilcClient client, SilcClientConnection conn,
-                         SilcClientEntry sender, SilcMessageFlags flags,
-                         char *msg)
+                         SilcClientEntry sender, SilcMessagePayload payload,
+                         SilcMessageFlags flags,
+                         const unsigned char *message,
+                         SilcUInt32 message_len)
 {
   SILC_SERVER_REC *server;
+  char userhost[256];
+  int verified = 0;
   
+  SILC_LOG_DEBUG(("Start"));
+
   server = conn == NULL ? NULL : conn->context;
-  signal_emit("message private", 4, server, msg,
-             sender->nickname ? sender->nickname : "[<unknown>]",
-             sender->username ? sender->username : NULL);
+  memset(userhost, 0, sizeof(userhost));
+  if (sender->username)
+    snprintf(userhost, sizeof(userhost) - 1, "%s@%s",
+            sender->username, sender->hostname);
+
+  /* If the messages is digitally signed, verify it, if possible. */
+  if (flags & SILC_MESSAGE_FLAG_SIGNED) {
+    if (!settings_get_bool("ignore_message_signatures")) {
+      SilcMessageSignedPayload sig = silc_message_get_signature(payload);
+      verified = verify_message_signature(sender, sig, payload);
+    } else {
+      flags &= ~SILC_MESSAGE_FLAG_SIGNED;
+    }
+  }
+  
+  if (flags & SILC_MESSAGE_FLAG_DATA) {
+    /* MIME object received, try to display it as well as we can */
+    char type[128], enc[128];
+    unsigned char *data;
+    SilcUInt32 data_len;
+
+    memset(type, 0, sizeof(type));
+    memset(enc, 0, sizeof(enc));
+    if (!silc_mime_parse(message, message_len, NULL, 0, type, sizeof(type) - 1,
+                        enc, sizeof(enc) - 1, &data, &data_len))
+      return;
+
+    /* Then figure out what we can display */
+    if (strstr(type, "text/") && !strstr(type, "text/t140") &&
+       !strstr(type, "text/vnd")) {
+      /* It is something textual, display it */
+      message = (const unsigned char *)data;
+    } else {
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_MESSAGE_DATA,
+                        sender->nickname ? sender->nickname : "[<unknown>]",
+                        type);
+      message = NULL;
+    }
+  }
+
+  if (!message)
+    return;
+
+  if (flags & SILC_MESSAGE_FLAG_UTF8 && !silc_term_utf8()) {
+    char tmp[256], *cp, *dm = NULL;
+
+    memset(tmp, 0, sizeof(tmp));
+    cp = tmp;
+    if (message_len > sizeof(tmp) - 1) {
+      dm = silc_calloc(message_len + 1, sizeof(*dm));
+      cp = dm;
+    }
+
+    silc_utf8_decode(message, message_len, SILC_STRING_LANGUAGE,
+                    cp, message_len);
+    if (flags & SILC_MESSAGE_FLAG_SIGNED)
+      signal_emit("message signed_private", 5, server, cp,
+                 sender->nickname ? sender->nickname : "[<unknown>]",
+                 sender->username ? userhost : NULL, verified);
+    else
+      signal_emit("message private", 4, server, cp,
+                 sender->nickname ? sender->nickname : "[<unknown>]",
+                 sender->username ? userhost : NULL);
+    silc_free(dm);
+    return;
+  }
+
+  if (flags & SILC_MESSAGE_FLAG_SIGNED)
+    signal_emit("message signed_private", 5, server, message,
+               sender->nickname ? sender->nickname : "[<unknown>]",
+               sender->username ? userhost : NULL, verified);
+  else
+    signal_emit("message private", 4, server, message,
+               sender->nickname ? sender->nickname : "[<unknown>]",
+               sender->username ? userhost : NULL);
 }
 
 /* Notify message to the client. The notify arguments are sent in the
@@ -129,52 +463,573 @@ void silc_private_message(SilcClient client, SilcClientConnection conn,
    for channel the channel entry is sent to application (even if server
    does not send it). */
 
-typedef struct {
-  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_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" },
-};
-
 void silc_notify(SilcClient client, SilcClientConnection conn,
                 SilcNotifyType type, ...)
 {
-  SILC_SERVER_REC *server;
   va_list va;
-  
-  server = conn == NULL ? NULL : conn->context;
+  SILC_SERVER_REC *server;
+  SILC_CHANNEL_REC *chanrec;
+  SILC_NICK_REC *nickrec;
+  SilcClientEntry client_entry, client_entry2;
+  SilcChannelEntry channel, channel2;
+  SilcServerEntry server_entry;
+  SilcIdType idtype;
+  void *entry;
+  SilcUInt32 mode;
+  char buf[512];
+  char *name, *tmp;
+  GSList *list1, *list_tmp;
+
+  SILC_LOG_DEBUG(("Start"));
+
   va_start(va, type);
+
+  server = conn == NULL ? NULL : conn->context;
   
-  if (type == SILC_NOTIFY_TYPE_NONE) {
+  switch(type) {
+  case 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 {
+    break;
+
+  case SILC_NOTIFY_TYPE_INVITE:
+    /*
+     * Invited or modified invite list.
+     */
+
+    SILC_LOG_DEBUG(("Notify: INVITE"));
+
+    channel = va_arg(va, SilcChannelEntry);
+    name = va_arg(va, char *);
+    client_entry = va_arg(va, SilcClientEntry);
+
+    memset(buf, 0, sizeof(buf));
+    snprintf(buf, sizeof(buf) - 1, "%s@%s",
+            client_entry->username, client_entry->hostname);
+    signal_emit("message invite", 4, server, channel ? channel->channel_name :
+               name, client_entry->nickname, buf);
+    break;
+
+  case SILC_NOTIFY_TYPE_JOIN:
+    /*
+     * Joined channel.
+     */
+    SILC_LOG_DEBUG(("Notify: JOIN"));
+
+    client_entry = va_arg(va, SilcClientEntry);
+    channel = va_arg(va, SilcChannelEntry);
+
+    if (client_entry == server->conn->local_entry) {
+      /* You joined to channel */
+      chanrec = silc_channel_find(server, channel->channel_name);
+      if (chanrec != NULL && !chanrec->joined)
+       chanrec->entry = channel;
+    } else {
+      chanrec = silc_channel_find_entry(server, channel);
+      if (chanrec != NULL) {
+       SilcChannelUser chu = silc_client_on_channel(channel, client_entry);
+       if (chu)
+         nickrec = silc_nicklist_insert(chanrec, chu, TRUE);
+      }
+    }
+    
+    memset(buf, 0, sizeof(buf));
+    if (client_entry->username)
+    snprintf(buf, sizeof(buf) - 1, "%s@%s",
+            client_entry->username, client_entry->hostname);
+    signal_emit("message join", 4, server, channel->channel_name,
+               client_entry->nickname,
+               client_entry->username == NULL ? "" : buf);
+    break;
+
+  case SILC_NOTIFY_TYPE_LEAVE:
+    /*
+     * Left a channel.
+     */
+
+    SILC_LOG_DEBUG(("Notify: LEAVE"));
+
+    client_entry = va_arg(va, SilcClientEntry);
+    channel = va_arg(va, SilcChannelEntry);
+    
+    memset(buf, 0, sizeof(buf));
+    if (client_entry->username)
+      snprintf(buf, sizeof(buf) - 1, "%s@%s",
+              client_entry->username, client_entry->hostname);
+    signal_emit("message part", 5, server, channel->channel_name,
+               client_entry->nickname,  client_entry->username ? 
+               buf : "", client_entry->nickname);
+
+    chanrec = silc_channel_find_entry(server, channel);
+    if (chanrec != NULL) {
+      nickrec = silc_nicklist_find(chanrec, client_entry);
+      if (nickrec != NULL)
+       nicklist_remove(CHANNEL(chanrec), NICK(nickrec));
+    }
+    break;
+
+  case SILC_NOTIFY_TYPE_SIGNOFF:
+    /*
+     * Left the network.
+     */
+
+    SILC_LOG_DEBUG(("Notify: SIGNOFF"));
+
+    client_entry = va_arg(va, SilcClientEntry);
+    tmp = va_arg(va, char *);
+    
+    silc_server_free_ftp(server, client_entry);
+
+    /* Print only if we have the nickname.  If this cliente has just quit
+       when we were only resolving it, it is possible we don't have the
+       nickname. */
+    if (client_entry->nickname) {
+      memset(buf, 0, sizeof(buf));
+      if (client_entry->username)
+        snprintf(buf, sizeof(buf) - 1, "%s@%s",
+                client_entry->username, client_entry->hostname);
+      signal_emit("message quit", 4, server, client_entry->nickname,
+                 client_entry->username ? buf : "", 
+                 tmp ? tmp : "");
+    }
+
+    list1 = nicklist_get_same_unique(SERVER(server), client_entry);
+    for (list_tmp = list1; list_tmp != NULL; list_tmp = 
+          list_tmp->next->next) {
+      CHANNEL_REC *channel = list_tmp->data;
+      NICK_REC *nickrec = list_tmp->next->data;
+      
+      nicklist_remove(channel, nickrec);
+    }
+    break;
+
+  case SILC_NOTIFY_TYPE_TOPIC_SET:
+    /*
+     * Changed topic.
+     */
+
+    SILC_LOG_DEBUG(("Notify: TOPIC_SET"));
+
+    idtype = va_arg(va, int);
+    entry = va_arg(va, void *);
+    tmp = va_arg(va, char *);
+    channel = va_arg(va, SilcChannelEntry);
+    
+    chanrec = silc_channel_find_entry(server, channel);
+    if (chanrec != NULL) {
+      char tmp2[256], *cp, *dm = NULL;
+
+      g_free_not_null(chanrec->topic);
+      if (tmp && !silc_term_utf8() && silc_utf8_valid(tmp, strlen(tmp))) {
+       memset(tmp2, 0, sizeof(tmp2));
+       cp = tmp2;
+       if (strlen(tmp) > sizeof(tmp2) - 1) {
+         dm = silc_calloc(strlen(tmp) + 1, sizeof(*dm));
+         cp = dm;
+       }
+
+       silc_utf8_decode(tmp, strlen(tmp), SILC_STRING_LANGUAGE,
+                        cp, strlen(tmp));
+       tmp = cp;
+      }
+
+      chanrec->topic = *tmp == '\0' ? NULL : g_strdup(tmp);
+      signal_emit("channel topic changed", 1, chanrec);
+
+      silc_free(dm);
+    }
+    
+    if (idtype == SILC_ID_CLIENT) {
+      client_entry = (SilcClientEntry)entry;
+      memset(buf, 0, sizeof(buf));
+      snprintf(buf, sizeof(buf) - 1, "%s@%s",
+              client_entry->username, client_entry->hostname);
+      signal_emit("message topic", 5, server, channel->channel_name,
+                 tmp, client_entry->nickname, buf);
+    } else if (idtype == SILC_ID_SERVER) {
+      server_entry = (SilcServerEntry)entry;
+      signal_emit("message topic", 5, server, channel->channel_name,
+                 tmp, server_entry->server_name, 
+                 server_entry->server_name);
+    } else if (idtype == SILC_ID_CHANNEL) {
+      channel = (SilcChannelEntry)entry;
+      signal_emit("message topic", 5, server, channel->channel_name,
+                 tmp, channel->channel_name, channel->channel_name);
+    }
+    break;
+
+  case SILC_NOTIFY_TYPE_NICK_CHANGE:
+    /*
+     * Changed nickname.
+     */
+
+    SILC_LOG_DEBUG(("Notify: NICK_CHANGE"));
+
+    client_entry = va_arg(va, SilcClientEntry);
+    client_entry2 = va_arg(va, SilcClientEntry);
+
+    if (!strcmp(client_entry->nickname, client_entry2->nickname))
+      break;
+    
+    memset(buf, 0, sizeof(buf));
+    snprintf(buf, sizeof(buf) - 1, "%s@%s",
+            client_entry2->username, client_entry2->hostname);
+    nicklist_rename_unique(SERVER(server),
+                          client_entry, client_entry->nickname,
+                          client_entry2, client_entry2->nickname);
+    signal_emit("message nick", 4, server, client_entry2->nickname, 
+               client_entry->nickname, buf);
+    break;
+
+  case SILC_NOTIFY_TYPE_CMODE_CHANGE:
+    /*
+     * Changed channel mode.
+     */
+
+    SILC_LOG_DEBUG(("Notify: CMODE_CHANGE"));
+
+    idtype = va_arg(va, int);
+    entry = va_arg(va, void *);
+    mode = va_arg(va, SilcUInt32);
+    (void)va_arg(va, char *);
+    (void)va_arg(va, char *);
+    channel = va_arg(va, SilcChannelEntry);
+
+    tmp = silc_client_chmode(mode,
+                            channel->channel_key ? 
+                            silc_cipher_get_name(channel->channel_key) : "",
+                            channel->hmac ? 
+                            silc_hmac_get_name(channel->hmac) : "");
+    
+    chanrec = silc_channel_find_entry(server, channel);
+    if (chanrec != NULL) {
+      g_free_not_null(chanrec->mode);
+      chanrec->mode = g_strdup(tmp == NULL ? "" : tmp);
+      signal_emit("channel mode changed", 1, chanrec);
+    }
+    
+    if (idtype == SILC_ID_CLIENT) {
+      client_entry = (SilcClientEntry)entry;
+      printformat_module("fe-common/silc", server, channel->channel_name,
+                        MSGLEVEL_MODES, SILCTXT_CHANNEL_CMODE,
+                        channel->channel_name, tmp ? tmp : "removed all",
+                        client_entry->nickname);
+    } else if (idtype == SILC_ID_SERVER) {
+      server_entry = (SilcServerEntry)entry;
+      printformat_module("fe-common/silc", server, channel->channel_name,
+                        MSGLEVEL_MODES, SILCTXT_CHANNEL_CMODE,
+                        channel->channel_name, tmp ? tmp : "removed all",
+                        server_entry->server_name);
+    } else if (idtype == SILC_ID_CHANNEL) {
+      channel2 = (SilcChannelEntry)entry;
+      printformat_module("fe-common/silc", server, channel->channel_name,
+                        MSGLEVEL_MODES, SILCTXT_CHANNEL_CMODE,
+                        channel->channel_name, tmp ? tmp : "removed all",
+                        channel2->channel_name);
+    }
+
+    silc_free(tmp);
+    break;
+
+  case SILC_NOTIFY_TYPE_CUMODE_CHANGE:
+    /*
+     * Changed user's mode on channel.
+     */
+
+    SILC_LOG_DEBUG(("Notify: CUMODE_CHANGE"));
+
+    idtype = va_arg(va, int);
+    entry = va_arg(va, void *);
+    mode = va_arg(va, SilcUInt32);
+    client_entry2 = va_arg(va, SilcClientEntry);
+    channel = va_arg(va, SilcChannelEntry);
+
+    tmp = silc_client_chumode(mode);
+    chanrec = silc_channel_find_entry(server, channel);
+    if (chanrec != NULL) {
+      SILC_NICK_REC *nick;
+
+      if (client_entry2 == server->conn->local_entry)
+       chanrec->chanop = (mode & SILC_CHANNEL_UMODE_CHANOP) != 0;
+      
+      nick = silc_nicklist_find(chanrec, client_entry2);
+      if (nick != NULL) {
+       nick->op = (mode & SILC_CHANNEL_UMODE_CHANOP) != 0;
+       nick->founder = (mode & SILC_CHANNEL_UMODE_CHANFO) != 0;
+       signal_emit("nick mode changed", 2, chanrec, nick);
+      }
+    }
+
+    if (idtype == SILC_ID_CLIENT) {
+      client_entry = (SilcClientEntry)entry;
+      printformat_module("fe-common/silc", server, channel->channel_name,
+                        MSGLEVEL_MODES, SILCTXT_CHANNEL_CUMODE,
+                        channel->channel_name, client_entry2->nickname, 
+                        tmp ? tmp : "removed all",
+                        client_entry->nickname);
+    } else if (idtype == SILC_ID_SERVER) {
+      server_entry = (SilcServerEntry)entry;
+      printformat_module("fe-common/silc", server, channel->channel_name,
+                        MSGLEVEL_MODES, SILCTXT_CHANNEL_CUMODE,
+                        channel->channel_name, client_entry2->nickname, 
+                        tmp ? tmp : "removed all",
+                        server_entry->server_name);
+    } else if (idtype == SILC_ID_CHANNEL) {
+      channel2 = (SilcChannelEntry)entry;
+      printformat_module("fe-common/silc", server, channel->channel_name,
+                        MSGLEVEL_MODES, SILCTXT_CHANNEL_CUMODE,
+                        channel->channel_name, client_entry2->nickname, 
+                        tmp ? tmp : "removed all",
+                        channel2->channel_name);
+    }
+
+    if (mode & SILC_CHANNEL_UMODE_CHANFO)
+      printformat_module("fe-common/silc", 
+                        server, channel->channel_name, MSGLEVEL_CRAP,
+                        SILCTXT_CHANNEL_FOUNDER,
+                        channel->channel_name, client_entry2->nickname);
+
+    if (mode & SILC_CHANNEL_UMODE_QUIET && conn->local_entry == client_entry2)
+      printformat_module("fe-common/silc", 
+                        server, channel->channel_name, MSGLEVEL_CRAP,
+                        SILCTXT_CHANNEL_QUIETED, channel->channel_name);
+
+    silc_free(tmp);
+    break;
+
+  case SILC_NOTIFY_TYPE_MOTD:
+    /*
+     * Received MOTD.
+     */
+
+    SILC_LOG_DEBUG(("Notify: MOTD"));
+
+    tmp = va_arg(va, char *);
+
+    if (!settings_get_bool("skip_motd"))
+      printtext_multiline(server, NULL, MSGLEVEL_CRAP, "%s", tmp);
+    break;
+
+  case SILC_NOTIFY_TYPE_KICKED:
+    /*
+     * Someone was kicked from channel.
+     */
+
+    SILC_LOG_DEBUG(("Notify: KICKED"));
+
+    client_entry = va_arg(va, SilcClientEntry);
+    tmp = va_arg(va, char *);
+    client_entry2 = va_arg(va, SilcClientEntry);
+    channel = va_arg(va, SilcChannelEntry);
+
+    chanrec = silc_channel_find_entry(server, channel);
+  
+    if (client_entry == conn->local_entry) {
+      printformat_module("fe-common/silc", server, channel->channel_name,
+                        MSGLEVEL_CRAP, SILCTXT_CHANNEL_KICKED_YOU, 
+                        channel->channel_name, 
+                        client_entry ? client_entry2->nickname : "",
+                        tmp ? tmp : "");
+      if (chanrec) {
+       chanrec->kicked = TRUE;
+       channel_destroy((CHANNEL_REC *)chanrec);
+      }
+    } else {
+      printformat_module("fe-common/silc", server, channel->channel_name,
+                        MSGLEVEL_CRAP, SILCTXT_CHANNEL_KICKED, 
+                        client_entry->nickname, channel->channel_name, 
+                        client_entry2 ? client_entry2->nickname : "", 
+                        tmp ? tmp : "");
+
+      if (chanrec) {
+       SILC_NICK_REC *nickrec = silc_nicklist_find(chanrec, client_entry);
+       if (nickrec != NULL)
+         nicklist_remove(CHANNEL(chanrec), NICK(nickrec));
+      }
+    }
+    break;
+
+  case SILC_NOTIFY_TYPE_KILLED:
+    /*
+     * Someone was killed from the network.
+     */
+
+    SILC_LOG_DEBUG(("Notify: KILLED"));
+
+    client_entry = va_arg(va, SilcClientEntry);
+    tmp = va_arg(va, char *);
+    idtype = va_arg(va, int);
+    entry = va_arg(va, SilcClientEntry);
+  
+    if (client_entry == conn->local_entry) {
+      if (idtype == SILC_ID_CLIENT) {
+       client_entry2 = (SilcClientEntry)entry;
+       printformat_module("fe-common/silc", server, NULL,
+                          MSGLEVEL_CRAP, SILCTXT_CHANNEL_KILLED_YOU, 
+                          client_entry2 ? client_entry2->nickname : "",
+                          tmp ? tmp : "");
+      } else if (idtype == SILC_ID_SERVER) {
+       server_entry = (SilcServerEntry)entry;
+       printformat_module("fe-common/silc", server, NULL,
+                          MSGLEVEL_CRAP, SILCTXT_CHANNEL_KILLED_YOU, 
+                          server_entry->server_name, tmp ? tmp : "");
+      } else if (idtype == SILC_ID_CHANNEL) {
+       channel = (SilcChannelEntry)entry;
+       printformat_module("fe-common/silc", server, NULL,
+                          MSGLEVEL_CRAP, SILCTXT_CHANNEL_KILLED_YOU, 
+                          channel->channel_name, tmp ? tmp : "");
+      }
+    } else {
+      list1 = nicklist_get_same_unique(SERVER(server), client_entry);
+      for (list_tmp = list1; list_tmp != NULL; list_tmp = 
+            list_tmp->next->next) {
+       CHANNEL_REC *channel = list_tmp->data;
+       NICK_REC *nickrec = list_tmp->next->data;
+       nicklist_remove(channel, nickrec);
+      }
+
+      if (idtype == SILC_ID_CLIENT) {
+       client_entry2 = (SilcClientEntry)entry;
+       printformat_module("fe-common/silc", server, NULL,
+                          MSGLEVEL_CRAP, SILCTXT_CHANNEL_KILLED, 
+                          client_entry->nickname,
+                          client_entry2 ? client_entry2->nickname : "",
+                          tmp ? tmp : "");
+      } else if (idtype == SILC_ID_SERVER) {
+       server_entry = (SilcServerEntry)entry;
+       printformat_module("fe-common/silc", server, NULL,
+                          MSGLEVEL_CRAP, SILCTXT_CHANNEL_KILLED, 
+                          client_entry->nickname,
+                          server_entry->server_name, tmp ? tmp : "");
+      } else if (idtype == SILC_ID_CHANNEL) {
+       channel = (SilcChannelEntry)entry;
+       printformat_module("fe-common/silc", server, NULL,
+                          MSGLEVEL_CRAP, SILCTXT_CHANNEL_KILLED, 
+                          client_entry->nickname,
+                          channel->channel_name, tmp ? tmp : "");
+      }
+    }
+    break;
+
+  case SILC_NOTIFY_TYPE_CHANNEL_CHANGE:
+    break;
+
+  case SILC_NOTIFY_TYPE_SERVER_SIGNOFF:
+    {
+      /*
+       * Server has quit the network.
+       */
+      int i;
+      SilcClientEntry *clients;
+      SilcUInt32 clients_count;
+
+      SILC_LOG_DEBUG(("Notify: SIGNOFF"));
+      
+      (void)va_arg(va, void *);
+      clients = va_arg(va, SilcClientEntry *);
+      clients_count = va_arg(va, SilcUInt32);
+  
+      for (i = 0; i < clients_count; i++) {
+       memset(buf, 0, sizeof(buf));
+
+       /* Print only if we have the nickname.  If this client has just quit
+          when we were only resolving it, it is possible we don't have the
+          nickname. */
+       if (clients[i]->nickname) {
+         if (clients[i]->username)
+           snprintf(buf, sizeof(buf) - 1, "%s@%s",
+                    clients[i]->username, clients[i]->hostname);
+         signal_emit("message quit", 4, server, clients[i]->nickname,
+                     clients[i]->username ? buf : "", 
+                     "server signoff");
+       }
+
+       silc_server_free_ftp(server, clients[i]);
+       
+       list1 = nicklist_get_same_unique(SERVER(server), clients[i]);
+       for (list_tmp = list1; list_tmp != NULL; list_tmp = 
+              list_tmp->next->next) {
+         CHANNEL_REC *channel = list_tmp->data;
+         NICK_REC *nickrec = list_tmp->next->data;
+         nicklist_remove(channel, nickrec);
+       }
+      }
+    }
+    break;
+
+  case SILC_NOTIFY_TYPE_ERROR:
+    {
+      SilcStatus error = va_arg(va, int);
+
+      silc_say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
+               "%s", silc_get_status_message(error));
+    }
+    break;
+
+  case SILC_NOTIFY_TYPE_WATCH:
+    {
+      SilcNotifyType notify;
+
+      client_entry = va_arg(va, SilcClientEntry);
+      name = va_arg(va, char *);          /* Maybe NULL */
+      mode = va_arg(va, SilcUInt32);
+      notify = va_arg(va, int);
+
+      if (notify == SILC_NOTIFY_TYPE_NICK_CHANGE) {
+       if (name)
+         printformat_module("fe-common/silc", server, NULL,
+                            MSGLEVEL_CRAP, SILCTXT_WATCH_NICK_CHANGE,
+                            client_entry->nickname, name);
+       else
+         printformat_module("fe-common/silc", server, NULL,
+                            MSGLEVEL_CRAP, SILCTXT_WATCH_PRESENT,
+                            client_entry->nickname);
+      } else if (notify == SILC_NOTIFY_TYPE_UMODE_CHANGE) {
+       /* See if client was away and is now present */
+       if (!(mode & (SILC_UMODE_GONE | SILC_UMODE_INDISPOSED |
+                     SILC_UMODE_BUSY | SILC_UMODE_PAGE |
+                     SILC_UMODE_DETACHED)) &&
+           (client_entry->mode & SILC_UMODE_GONE ||
+            client_entry->mode & SILC_UMODE_INDISPOSED ||
+            client_entry->mode & SILC_UMODE_BUSY ||
+            client_entry->mode & SILC_UMODE_PAGE ||
+            client_entry->mode & SILC_UMODE_DETACHED)) {
+         printformat_module("fe-common/silc", server, NULL,
+                            MSGLEVEL_CRAP, SILCTXT_WATCH_PRESENT,
+                            client_entry->nickname);
+       }
+
+       if (mode) {
+         memset(buf, 0, sizeof(buf));
+         silc_get_umode_string(mode, buf, sizeof(buf) - 1);
+         printformat_module("fe-common/silc", server, NULL,
+                            MSGLEVEL_CRAP, SILCTXT_WATCH_UMODE_CHANGE,
+                            client_entry->nickname, buf);
+       }
+      } else if (notify == SILC_NOTIFY_TYPE_KILLED) {
+       printformat_module("fe-common/silc", server, NULL,
+                          MSGLEVEL_CRAP, SILCTXT_WATCH_KILLED,
+                          client_entry->nickname);
+      } else if (notify == SILC_NOTIFY_TYPE_SIGNOFF ||
+                notify == SILC_NOTIFY_TYPE_SERVER_SIGNOFF) {
+       printformat_module("fe-common/silc", server, NULL,
+                          MSGLEVEL_CRAP, SILCTXT_WATCH_SIGNOFF,
+                          client_entry->nickname);
+      } else if (notify == SILC_NOTIFY_TYPE_NONE) {
+       /* Client logged in to the network */
+       printformat_module("fe-common/silc", server, NULL,
+                          MSGLEVEL_CRAP, SILCTXT_WATCH_PRESENT,
+                          client_entry->nickname);
+      }
+    }
+    break;
+
+  default:
     /* Unknown notify */
     printformat_module("fe-common/silc", server, NULL,
                       MSGLEVEL_CRAP, SILCTXT_UNKNOWN_NOTIFY, type);
+    break;
   }
 
   va_end(va);
@@ -182,28 +1037,79 @@ void silc_notify(SilcClient client, SilcClientConnection conn,
 
 /* 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. */
+   the SilcClientConnection object which it should save somewhere. */
 
-void silc_connect(SilcClient client, SilcClientConnection conn, int success)
+void silc_connect(SilcClient client, SilcClientConnection conn,
+                 SilcClientConnectionStatus status)
 {
   SILC_SERVER_REC *server = conn->context;
 
-  if (success) {
+  if (!server || server->disconnected) {
+    silc_client_close_connection(client, conn);
+    return;
+  }
+
+  switch (status) {
+  case SILC_CLIENT_CONN_SUCCESS:
+    /* We have successfully connected to server */
     server->connected = TRUE;
     signal_emit("event connected", 1, server);
-  } else {
+    break;
+
+  case SILC_CLIENT_CONN_SUCCESS_RESUME:
+    /* We have successfully resumed old detached session */
+    server->connected = TRUE;
+    signal_emit("event connected", 1, server);
+
+    /* If we resumed old session check whether we need to update 
+       our nickname */
+    if (strcmp(server->nick, conn->local_entry->nickname)) {
+      char *old;
+      old = g_strdup(server->nick);
+      server_change_nick(SERVER(server), conn->local_entry->nickname);
+      nicklist_rename_unique(SERVER(server), 
+                            conn->local_entry, server->nick,
+                            conn->local_entry, conn->local_entry->nickname);
+      signal_emit("message own_nick", 4, server, server->nick, old, "");
+      g_free(old);
+    }
+    break;
+
+  default:
     server->connection_lost = TRUE;
-    server->conn->context = NULL;
+    if (server->conn)
+      server->conn->context = NULL;
     server_disconnect(SERVER(server));
+    break;
   }
 }
 
 /* Called to indicate that connection was disconnected to the server. */
 
-void silc_disconnect(SilcClient client, SilcClientConnection conn)
+void silc_disconnect(SilcClient client, SilcClientConnection conn,
+                    SilcStatus status, const char *message)
 {
   SILC_SERVER_REC *server = conn->context;
 
+  SILC_LOG_DEBUG(("Start"));
+
+  if (!server || server->connection_lost)
+    return;
+
+  if (server->conn && server->conn->local_entry) {
+    nicklist_rename_unique(SERVER(server),
+                          server->conn->local_entry, server->nick,
+                          server->conn->local_entry, 
+                          silc_client->username);
+    silc_change_nick(server, silc_client->username);
+  }
+
+  if (message)
+    silc_say(client, conn, SILC_CLIENT_MESSAGE_AUDIT,
+            "Server closed connection: %s (%d) %s",
+            silc_get_status_message(status), status,
+            message ? message : "");
+
   server->conn->context = NULL;
   server->conn = NULL;
   server->connection_lost = TRUE;
@@ -220,23 +1126,34 @@ void silc_disconnect(SilcClient client, SilcClientConnection conn)
    that the command really was processed. */
 
 void silc_command(SilcClient client, SilcClientConnection conn, 
-                 SilcClientCommandContext cmd_context, int success,
-                 SilcCommand command)
+                 SilcClientCommandContext cmd_context, bool success,
+                 SilcCommand command, SilcStatus status)
 {
   SILC_SERVER_REC *server = conn->context;
 
-  if (!success)
+  SILC_LOG_DEBUG(("Start"));
+
+  if (!success) {
+    silc_say_error("%s", silc_get_status_message(status));
     return;
+  }
+
+  switch (command) {
 
-  switch(command) {
   case SILC_COMMAND_INVITE:
-    printformat_module("fe-common/silc", server, NULL,
-                      MSGLEVEL_CRAP, SILCTXT_CHANNEL_INVITING,
-                      cmd_context->argv[2], 
-                      (cmd_context->argv[1][0] == '*' ?
-                       (char *)conn->current_channel->channel_name :
-                       (char *)cmd_context->argv[1]));
+    if (cmd_context->argc > 2)
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_CHANNEL_INVITING,
+                        cmd_context->argv[2], 
+                        (cmd_context->argv[1][0] == '*' ?
+                         (char *)conn->current_channel->channel_name :
+                         (char *)cmd_context->argv[1]));
+    break;
+
+  case SILC_COMMAND_DETACH:
+    server->no_reconnect = TRUE;
     break;
+
   default:
     break;
   }
@@ -248,16 +1165,20 @@ void silc_command(SilcClient client, SilcClientConnection conn,
 static void silc_client_join_get_users(SilcClient client,
                                       SilcClientConnection conn,
                                       SilcClientEntry *clients,
-                                      uint32 clients_count,
+                                      SilcUInt32 clients_count,
                                       void *context)
 {
   SilcChannelEntry channel = (SilcChannelEntry)context;
+  SilcHashTableList htl;
   SilcChannelUser chu;
   SILC_SERVER_REC *server = conn->context;
   SILC_CHANNEL_REC *chanrec;
   SilcClientEntry founder = NULL;
   NICK_REC *ownnick;
 
+  SILC_LOG_DEBUG(("Start, channel %s, %d users", channel->channel_name,
+                 silc_hash_table_count(channel->user_list)));
+
   if (!clients)
     return;
 
@@ -265,36 +1186,181 @@ static void silc_client_join_get_users(SilcClient client,
   if (chanrec == NULL)
     return;
 
-  silc_list_start(channel->clients);
-  while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
+  silc_hash_table_list(channel->user_list, &htl);
+  while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+    if (!chu->client->nickname)
+      continue;
     if (chu->mode & SILC_CHANNEL_UMODE_CHANFO)
       founder = chu->client;
     silc_nicklist_insert(chanrec, chu, FALSE);
   }
+  silc_hash_table_list_reset(&htl);
 
   ownnick = NICK(silc_nicklist_find(chanrec, conn->local_entry));
   nicklist_set_own(CHANNEL(chanrec), ownnick);
   signal_emit("channel joined", 1, chanrec);
+  chanrec->entry = channel;
+
+  if (chanrec->topic)
+    printformat_module("fe-common/silc", server, channel->channel_name,
+                      MSGLEVEL_CRAP, SILCTXT_CHANNEL_TOPIC,
+                      channel->channel_name, chanrec->topic);
+
+  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);
+      signal_emit("nick mode changed", 2, chanrec, ownnick);
+    } else
+      printformat_module("fe-common/silc", 
+                        server, channel->channel_name, MSGLEVEL_CRAP,
+                        SILCTXT_CHANNEL_FOUNDER,
+                        channel->channel_name, founder->nickname);
+  }
+}
+
+typedef struct {
+  SilcClient client;
+  SilcClientConnection conn;
+  void *entry;
+  SilcIdType id_type;
+  char *fingerprint;
+} *GetkeyContext;
+
+void silc_getkey_cb(bool success, void *context)
+{
+  GetkeyContext getkey = (GetkeyContext)context;
+  char *entity = (getkey->id_type == SILC_ID_CLIENT ? "user" : "server");
+  char *name = (getkey->id_type == SILC_ID_CLIENT ? 
+               ((SilcClientEntry)getkey->entry)->nickname :
+               ((SilcServerEntry)getkey->entry)->server_name);
+
+  if (success) {
+    printformat_module("fe-common/silc", NULL, NULL,
+                      MSGLEVEL_CRAP, SILCTXT_PUBKEY_VERIFIED, entity, name);
+  } else {
+    printformat_module("fe-common/silc", NULL, NULL,
+                      MSGLEVEL_CRAP, SILCTXT_PUBKEY_NOTVERIFIED,
+                      entity, name);
+  }
+
+  silc_free(getkey->fingerprint);
+  silc_free(getkey);
+}
+
+/* Parse an invite or ban list */
+void  silc_parse_inviteban_list(SilcClient client,
+                               SilcClientConnection conn,
+                               SILC_SERVER_REC *server, 
+                               SilcChannelEntry channel, 
+                               const char *list_type,
+                               SilcArgumentPayload list)
+{
+  unsigned char *tmp;
+  SilcUInt32 type, len;
+  SILC_CHANNEL_REC *chanrec = silc_channel_find_entry(server, channel);
+  int counter=0, resolving = FALSE;
+
+  if (!silc_argument_get_arg_num(list)) {
+    printformat_module("fe-common/silc", server,
+                      (chanrec ? chanrec->visible_name : NULL),
+                      MSGLEVEL_CRAP, SILCTXT_CHANNEL_NO_INVITEBAN_LIST,
+                      channel->channel_name, list_type);
+    return;
+  }
+  
+  printformat_module("fe-common/silc", server,
+                    (chanrec ? chanrec->visible_name : NULL),
+                    MSGLEVEL_CRAP, SILCTXT_CHANNEL_INVITEBAN_LIST,
+                    channel->channel_name, list_type);
+
+  /* parse the list */
+  tmp = silc_argument_get_first_arg(list, &type, &len);
+  while (tmp) {
+    switch (type) {
+      case 1:
+       {
+         /* an invite string */
+         char **list;
+         int i=0;
+               
+         if (tmp[len-1] == ',')
+           tmp[len-1] = '\0';
+         
+         list = g_strsplit(tmp, ",", -1);
+         while (list[i])
+           printformat_module("fe-common/silc", server,
+                              (chanrec ? chanrec->visible_name : NULL),
+                              MSGLEVEL_CRAP, SILCTXT_CHANNEL_INVITEBAN_STRING,
+                              ++counter, channel->channel_name, list_type,
+                              list[i++]);
+         g_strfreev(list);
+       }
+       break;
 
-  if (chanrec->topic)
-    printformat_module("fe-common/silc", server, channel->channel_name,
-                      MSGLEVEL_CRAP, SILCTXT_CHANNEL_TOPIC,
-                      channel->channel_name, chanrec->topic);
+      case 2:
+       {
+         /* a public key */
+         char *fingerprint, *babbleprint;
+
+         /* tmp is Public Key Payload, take public key from it. */
+         fingerprint = silc_hash_fingerprint(NULL, tmp + 4, len - 4);
+         babbleprint = silc_hash_babbleprint(NULL, tmp + 4, len - 4);
+         
+         printformat_module("fe-common/silc", server,
+                            (chanrec ? chanrec->visible_name : NULL),
+                            MSGLEVEL_CRAP, SILCTXT_CHANNEL_INVITEBAN_PUBKEY,
+                            ++counter, channel->channel_name, list_type,
+                            fingerprint, babbleprint);
+       }
+       break;
+       
+      case 3:
+       {
+         /* a client ID */
+         SilcClientID *client_id;
+         SilcClientEntry client_entry;
+         
+         client_id = silc_id_payload_parse_id(tmp, len, NULL);
+                         
+         if (client_id == NULL) {
+           silc_say_error("Invalid data in %s list encountered", list_type);
+           break;
+         }
 
-  fe_channels_nicklist(CHANNEL(chanrec), CHANNEL_NICKLIST_FLAG_ALL);
+         client_entry = silc_client_get_client_by_id(client, conn, client_id);
+
+         if (client_entry) {
+           printformat_module("fe-common/silc", server,
+                              (chanrec ? chanrec->visible_name : NULL),
+                              MSGLEVEL_CRAP, SILCTXT_CHANNEL_INVITEBAN_STRING,
+                              ++counter, channel->channel_name, list_type,
+                              client_entry->nickname);
+         } else {
+           resolving = TRUE;
+           silc_client_get_client_by_id_resolve(client, conn, client_id,
+                                                NULL, NULL, NULL);
+         }
 
-  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);
+         silc_free(client_id);
+       }
+       break;
+       
+      default:
+       /* "trash" */
+       silc_say_error("Unkown type in %s list: %u (len %u)",
+                      list_type, type, len);
+    }
+    tmp = silc_argument_get_next_arg(list, &type, &len);
   }
+
+  if (resolving)
+    printformat_module("fe-common/silc", server, 
+                      (chanrec ? chanrec->visible_name : NULL),
+                      MSGLEVEL_CRAP, SILCTXT_CHANNEL_INVITEBAN_REGET,
+                      list_type, channel->channel_name);
 }
 
 /* Command reply handler. This function is called always in the command reply
@@ -315,8 +1381,8 @@ static void silc_client_join_get_users(SilcClient client,
 
 void 
 silc_command_reply(SilcClient client, SilcClientConnection conn,
-                  SilcCommandPayload cmd_payload, int success,
-                  SilcCommand command, SilcCommandStatus status, ...)
+                  SilcCommandPayload cmd_payload, bool success,
+                  SilcCommand command, SilcStatus status, ...)
 
 {
   SILC_SERVER_REC *server = conn->context;
@@ -325,50 +1391,86 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
 
   va_start(vp, status);
 
+  SILC_LOG_DEBUG(("Start"));
+
   switch(command) {
   case SILC_COMMAND_WHOIS:
     {
-      char buf[1024], *nickname, *username, *realname;
-      uint32 idle, mode;
-      SilcBuffer channels;
+      char buf[1024], *nickname, *username, *realname, *nick;
+      unsigned char *fingerprint;
+      SilcUInt32 idle, mode;
+      SilcBuffer channels, user_modes;
+      SilcClientEntry client_entry;
+      SilcDList attrs;
       
-      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 (status == SILC_STATUS_ERR_NO_SUCH_NICK) {
+       /* Print the unknown nick for user */
+       unsigned char *tmp =
+         silc_argument_get_arg_type(silc_command_get_args(cmd_payload),
+                                    3, NULL);
        if (tmp)
          silc_say_error("%s: %s", tmp, 
-                        silc_client_command_status_message(status));
-       else
-         silc_say_error("%s", silc_client_command_status_message(status));
+                        silc_get_status_message(status));
        break;
-      }
-      
-      if (!success)
+      } else if (status == SILC_STATUS_ERR_NO_SUCH_CLIENT_ID) {
+       /* Try to find the entry for the unknown client ID, since we
+          might have, and print the nickname of it for user. */
+       SilcUInt32 tmp_len;
+       unsigned char *tmp =
+         silc_argument_get_arg_type(silc_command_get_args(cmd_payload),
+                                    2, &tmp_len);
+       if (tmp) {
+         SilcClientID *client_id = silc_id_payload_parse_id(tmp, tmp_len, 
+                                                            NULL);
+         if (client_id) {
+           client_entry = silc_client_get_client_by_id(client, conn,
+                                                       client_id);
+           if (client_entry && client_entry->nickname)
+             silc_say_error("%s: %s", client_entry->nickname,
+                            silc_get_status_message(status));
+           silc_free(client_id);
+         }
+       }
+       break;
+      } else if (!success) {
+       silc_say_error("WHOIS: %s", silc_get_status_message(status));
        return;
-      
-      (void)va_arg(vp, SilcClientEntry);
+      }
+
+      client_entry = 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);
+      mode = va_arg(vp, SilcUInt32);
+      idle = va_arg(vp, SilcUInt32);
+      fingerprint = va_arg(vp, unsigned char *);
+      user_modes = va_arg(vp, SilcBuffer);
+      attrs = va_arg(vp, SilcDList);
       
+      silc_parse_userfqdn(nickname, &nick, NULL);
       printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
-                        SILCTXT_WHOIS_USERINFO, nickname, username, 
-                        realname);
-
-      if (channels) {
-       SilcDList list = silc_channel_payload_parse_list(channels);
-       if (list) {
+                        SILCTXT_WHOIS_USERINFO, nickname, 
+                        client_entry->username, client_entry->hostname,
+                        nick, client_entry->nickname);
+      printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
+                        SILCTXT_WHOIS_REALNAME, realname);
+      silc_free(nick);
+
+      if (channels && user_modes) {
+       SilcUInt32 *umodes;
+       SilcDList list = silc_channel_payload_parse_list(channels->data,
+                                                        channels->len);
+       if (list && silc_get_mode_list(user_modes, silc_dlist_count(list),
+                                      &umodes)) {
          SilcChannelPayload entry;
+         int i = 0;
+
          memset(buf, 0, sizeof(buf));
          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;
+           SilcUInt32 name_len;
+           char *m = silc_client_chumode_char(umodes[i++]);
            char *name = silc_channel_get_name(entry, &name_len);
            
            if (m)
@@ -381,22 +1483,13 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
          printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
                             SILCTXT_WHOIS_CHANNELS, buf);
          silc_channel_payload_list_free(list);
+         silc_free(umodes);
        }
       }
       
       if (mode) {
        memset(buf, 0, sizeof(buf));
-
-       if ((mode & SILC_UMODE_SERVER_OPERATOR) ||
-           (mode & SILC_UMODE_ROUTER_OPERATOR)) {
-         strcat(buf, (mode & SILC_UMODE_SERVER_OPERATOR) ?
-                "Server Operator " :
-                (mode & SILC_UMODE_ROUTER_OPERATOR) ?
-                "SILC Operator " : "[Unknown mode] ");
-       }
-       if (mode & SILC_UMODE_GONE)
-         strcat(buf, "away");
-
+       silc_get_umode_string(mode, buf, sizeof(buf - 1));
        printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
                           SILCTXT_WHOIS_MODES, buf);
       }
@@ -410,9 +1503,58 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
        printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
                           SILCTXT_WHOIS_IDLE, buf);
       }
+
+      if (fingerprint) {
+       fingerprint = silc_fingerprint(fingerprint, 20);
+       printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
+                          SILCTXT_WHOIS_FINGERPRINT, fingerprint);
+       silc_free(fingerprint);
+      }
+
+      if (attrs)
+       silc_query_attributes_print(server, silc_client, conn, attrs,
+                                   client_entry);
     }
     break;
     
+  case SILC_COMMAND_IDENTIFY:
+    {
+      SilcClientEntry client_entry;
+      
+      if (status == SILC_STATUS_ERR_NO_SUCH_NICK) {
+       /* Print the unknown nick for user */
+       unsigned char *tmp =
+         silc_argument_get_arg_type(silc_command_get_args(cmd_payload),
+                                    3, NULL);
+       if (tmp)
+         silc_say_error("%s: %s", tmp, 
+                        silc_get_status_message(status));
+       break;
+      } else if (status == SILC_STATUS_ERR_NO_SUCH_CLIENT_ID) {
+       /* Try to find the entry for the unknown client ID, since we
+          might have, and print the nickname of it for user. */
+       SilcUInt32 tmp_len;
+       unsigned char *tmp =
+         silc_argument_get_arg_type(silc_command_get_args(cmd_payload),
+                                    2, &tmp_len);
+       if (tmp) {
+         SilcClientID *client_id = silc_id_payload_parse_id(tmp, tmp_len,
+                                                            NULL);
+         if (client_id) {
+           client_entry = silc_client_get_client_by_id(client, conn,
+                                                       client_id);
+           if (client_entry && client_entry->nickname)
+             silc_say_error("%s: %s", client_entry->nickname,
+                            silc_get_status_message(status));
+           silc_free(client_id);
+         }
+       }
+       break;
+      }
+
+      break;
+    }
+
   case SILC_COMMAND_WHOWAS:
     {
       char *nickname, *username, *realname;
@@ -424,14 +1566,12 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
                                         3, NULL);
        if (tmp)
          silc_say_error("%s: %s", tmp, 
-                        silc_client_command_status_message(status));
-       else
-         silc_say_error("%s", silc_client_command_status_message(status));
+                        silc_get_status_message(status));
        break;
-      }
-      
-      if (!success)
+      } else if (!success) {
+       silc_say_error("WHOWAS: %s", silc_get_status_message(status));
        return;
+      }
       
       (void)va_arg(vp, SilcClientEntry);
       nickname = va_arg(vp, char *);
@@ -447,67 +1587,85 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
   case SILC_COMMAND_INVITE:
     {
       SilcChannelEntry channel;
-      char *invite_list;
-      SilcArgumentPayload args;
-      int argc = 0;
+      SilcBuffer payload;
+      SilcArgumentPayload invite_list;
+      SilcUInt32 argc;
       
       if (!success)
        return;
       
       channel = va_arg(vp, SilcChannelEntry);
-      invite_list = va_arg(vp, char *);
-
-      args = silc_command_get_args(cmd_payload);
-      if (args)
-       argc = silc_argument_get_arg_num(args);
-
-      if (invite_list)
-       printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
-                          SILCTXT_CHANNEL_INVITE_LIST, channel->channel_name,
-                          invite_list);
-      else if (argc == 3)
-       printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
-                          SILCTXT_CHANNEL_NO_INVITE_LIST, 
-                          channel->channel_name);
+      payload = va_arg(vp, SilcBuffer);
+
+      if (payload) {
+       SILC_GET16_MSB(argc, payload->data);
+       invite_list = silc_argument_payload_parse(payload->data + 2, 
+                                                 payload->len - 2, argc);
+       if (invite_list) {
+         silc_parse_inviteban_list(client, conn, server, channel, 
+                                   "invite", invite_list);
+         silc_argument_payload_free(invite_list);
+       }
+      }
     }
     break;
 
   case SILC_COMMAND_JOIN: 
     {
       char *channel, *mode, *topic;
-      uint32 modei;
+      SilcUInt32 modei;
       SilcChannelEntry channel_entry;
       SilcBuffer client_id_list;
-      uint32 list_count;
+      SilcUInt32 list_count;
 
       if (!success)
        return;
 
       channel = va_arg(vp, char *);
       channel_entry = va_arg(vp, SilcChannelEntry);
-      modei = va_arg(vp, uint32);
-      (void)va_arg(vp, uint32);
+      modei = va_arg(vp, SilcUInt32);
+      (void)va_arg(vp, SilcUInt32);
       (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);
+      list_count = va_arg(vp, SilcUInt32);
       client_id_list = va_arg(vp, SilcBuffer);
 
       chanrec = silc_channel_find(server, channel);
       if (!chanrec)
-       chanrec = silc_channel_create(server, channel, TRUE);
+       chanrec = silc_channel_create(server, channel, channel, TRUE);
 
       if (topic) {
+       char tmp[256], *cp, *dm = NULL;
        g_free_not_null(chanrec->topic);
+
+       if (!silc_term_utf8() && silc_utf8_valid(topic, strlen(topic))) {
+         memset(tmp, 0, sizeof(tmp));
+         cp = tmp;
+         if (strlen(topic) > sizeof(tmp) - 1) {
+           dm = silc_calloc(strlen(topic) + 1, sizeof(*dm));
+           cp = dm;
+         }
+
+         silc_utf8_decode(topic, strlen(topic), SILC_STRING_LANGUAGE,
+                          cp, strlen(topic));
+         topic = cp;
+       }
+
        chanrec->topic = *topic == '\0' ? NULL : g_strdup(topic);
        signal_emit("channel topic changed", 1, chanrec);
+
+       silc_free(dm);
       }
 
       mode = silc_client_chmode(modei, 
-                               channel_entry->channel_key->cipher->name,
-                               channel_entry->hmac->hmac->name);
+                               channel_entry->channel_key ? 
+                               silc_cipher_get_name(channel_entry->
+                                                    channel_key) : "",
+                               channel_entry->hmac ? 
+                               silc_hmac_get_name(channel_entry->hmac) : "");
       g_free_not_null(chanrec->mode);
       chanrec->mode = g_strdup(mode == NULL ? "" : mode);
       signal_emit("channel mode changed", 1, chanrec);
@@ -516,23 +1674,44 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
       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;
+      SilcClientEntry client_entry = va_arg(vp, SilcClientEntry);
+      GSList *nicks;
       
       if (!success)
        return;
 
+      nicks = nicklist_get_same(SERVER(server), client_entry->nickname);
+      if (nicks != NULL) {
+       char buf[512];
+       SilcClientEntry collider, old;
+
+       old = ((SILC_NICK_REC *)(nicks->next->data))->silc_user->client;
+       collider = silc_client_get_client_by_id(client, conn,
+                                               old->id);
+       
+        memset(buf, 0, sizeof(buf));
+        snprintf(buf, sizeof(buf) - 1, "%s@%s",
+                collider->username, collider->hostname);
+       nicklist_rename_unique(SERVER(server),
+                              old, old->nickname,
+                              collider, collider->nickname);
+       silc_print_nick_change(server, collider->nickname,
+                              client_entry->nickname, buf);
+       g_slist_free(nicks);
+      }
+
       old = g_strdup(server->nick);
-      server_change_nick(SERVER(server), client->nickname);
+      server_change_nick(SERVER(server), client_entry->nickname);
       nicklist_rename_unique(SERVER(server),
                             server->conn->local_entry, server->nick,
-                            client, client->nickname);
-      
+                            client_entry, client_entry->nickname);
       signal_emit("message own_nick", 4, server, server->nick, old, "");
       g_free(old);
       break;
@@ -543,6 +1722,7 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
       char *topic, *name;
       int usercount;
       char users[20];
+      char tmp[256], *cp, *dm = NULL;
       
       if (!success)
        return;
@@ -551,35 +1731,73 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
       name = va_arg(vp, char *);
       topic = va_arg(vp, char *);
       usercount = va_arg(vp, int);
+
+      if (topic && !silc_term_utf8() &&
+         silc_utf8_valid(topic, strlen(topic))) {
+       memset(tmp, 0, sizeof(tmp));
+       cp = tmp;
+       if (strlen(topic) > sizeof(tmp) - 1) {
+         dm = silc_calloc(strlen(topic) + 1, sizeof(*dm));
+         cp = dm;
+       }
+
+       silc_utf8_decode(topic, strlen(topic), SILC_STRING_LANGUAGE,
+                        cp, strlen(topic));
+       topic = cp;
+      }
       
       if (status == SILC_STATUS_LIST_START ||
          status == SILC_STATUS_OK)
        printformat_module("fe-common/silc", server, NULL,
                           MSGLEVEL_CRAP, SILCTXT_LIST_HEADER);
 
-      snprintf(users, sizeof(users) - 1, "%d", usercount);
+      if (!usercount)
+       snprintf(users, sizeof(users) - 1, "N/A");
+      else
+       snprintf(users, sizeof(users) - 1, "%d", usercount);
       printformat_module("fe-common/silc", server, NULL,
                         MSGLEVEL_CRAP, SILCTXT_LIST,
                         name, users, topic ? topic : "");
+      silc_free(dm);
     }
     break;
     
   case SILC_COMMAND_UMODE:
     {
-      uint32 mode;
+      SilcUInt32 mode;
+      char *reason;
       
       if (!success)
        return;
       
-      mode = va_arg(vp, uint32);
+      mode = va_arg(vp, SilcUInt32);
       
-      if (mode & SILC_UMODE_SERVER_OPERATOR)
+      if (mode & SILC_UMODE_SERVER_OPERATOR &&
+         !(server->umode & SILC_UMODE_SERVER_OPERATOR))
        printformat_module("fe-common/silc", server, NULL,
                           MSGLEVEL_CRAP, SILCTXT_SERVER_OPER);
 
-      if (mode & SILC_UMODE_ROUTER_OPERATOR)
+      if (mode & SILC_UMODE_ROUTER_OPERATOR &&
+         !(server->umode & SILC_UMODE_ROUTER_OPERATOR))
        printformat_module("fe-common/silc", server, NULL,
                           MSGLEVEL_CRAP, SILCTXT_ROUTER_OPER);
+
+      if ((mode & SILC_UMODE_GONE) != (server->umode & SILC_UMODE_GONE)) {
+       if (mode & SILC_UMODE_GONE) {      
+         if ((server->away_reason != NULL) && (server->away_reason[0] != '\0'))
+           reason = g_strdup(server->away_reason);
+         else
+           reason = g_strdup("away");
+       } else
+         reason = g_strdup("");
+
+       silc_set_away(reason, server);
+
+       g_free(reason);
+      }
+
+      server->umode = mode;
+      signal_emit("user mode changed", 2, server, NULL);
     }
     break;
     
@@ -587,6 +1805,9 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
     if (!success)
       return;
 
+    server->umode |= SILC_UMODE_SERVER_OPERATOR;
+    signal_emit("user mode changed", 2, server, NULL);
+
     printformat_module("fe-common/silc", server, NULL,
                       MSGLEVEL_CRAP, SILCTXT_SERVER_OPER);
     break;
@@ -595,12 +1816,16 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
     if (!success)
       return;
 
+    server->umode |= SILC_UMODE_ROUTER_OPERATOR;
+    signal_emit("user mode changed", 2, server, NULL);
+
     printformat_module("fe-common/silc", server, NULL,
                       MSGLEVEL_CRAP, SILCTXT_ROUTER_OPER);
     break;
     
   case SILC_COMMAND_USERS: 
     {
+      SilcHashTableList htl;
       SilcChannelEntry channel;
       SilcChannelUser chu;
       
@@ -613,49 +1838,71 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
                         MSGLEVEL_CRAP, SILCTXT_USERS_HEADER,
                         channel->channel_name);
 
-      silc_list_start(channel->clients);
-      while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
+      silc_hash_table_list(channel->user_list, &htl);
+      while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
        SilcClientEntry e = chu->client;
        char stat[5], *mode;
+
+       if (!e->nickname)
+         continue;
        
        memset(stat, 0, sizeof(stat));
        mode = silc_client_chumode_char(chu->mode);
        if (e->mode & SILC_UMODE_GONE)
          strcat(stat, "G");
-       else
+       else if (e->mode & SILC_UMODE_INDISPOSED)
+         strcat(stat, "I");
+       else if (e->mode & SILC_UMODE_BUSY)
+         strcat(stat, "B");
+       else if (e->mode & SILC_UMODE_PAGE)
+         strcat(stat, "P");
+       else if (e->mode & SILC_UMODE_HYPER)
          strcat(stat, "H");
+       else if (e->mode & SILC_UMODE_ROBOT)
+         strcat(stat, "R");
+       else if (e->mode & SILC_UMODE_ANONYMOUS)
+         strcat(stat, "?");
+       else
+         strcat(stat, "A");
        if (mode)
          strcat(stat, mode);
 
        printformat_module("fe-common/silc", server, channel->channel_name,
                           MSGLEVEL_CRAP, SILCTXT_USERS,
-                          e->nickname, stat, e->username, 
+                          e->nickname, stat, 
+                          e->username ? e->username : "",
+                          e->hostname ? e->hostname : "",
                           e->realname ? e->realname : "");
        if (mode)
          silc_free(mode);
       }
+      silc_hash_table_list_reset(&htl);
     }
     break;
 
   case SILC_COMMAND_BAN:
     {
       SilcChannelEntry channel;
-      char *ban_list;
+      SilcBuffer payload;
+      SilcArgumentPayload ban_list;
+      SilcUInt32 argc;
       
       if (!success)
        return;
       
       channel = va_arg(vp, SilcChannelEntry);
-      ban_list = va_arg(vp, char *);
-      
-      if (ban_list)
-       printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
-                          SILCTXT_CHANNEL_BAN_LIST, channel->channel_name,
-                          ban_list);
-      else
-       printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
-                          SILCTXT_CHANNEL_NO_BAN_LIST, 
-                          channel->channel_name);
+      payload = va_arg(vp, SilcBuffer);
+
+      if (payload) {
+       SILC_GET16_MSB(argc, payload->data);
+       ban_list = silc_argument_payload_parse(payload->data + 2, 
+                                              payload->len - 2, argc);
+       if (ban_list) {
+         silc_parse_inviteban_list(client, conn, server, channel, 
+                                   "ban", ban_list);
+         silc_argument_payload_free(ban_list);
+       }
+      }
     }
     break;
     
@@ -665,24 +1912,63 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
       void *entry;
       SilcPublicKey public_key;
       unsigned char *pk;
-      uint32 pk_len;
+      SilcUInt32 pk_len;
+      GetkeyContext getkey;
+      char *name;
       
       if (!success)
        return;
       
-      id_type = va_arg(vp, uint32);
+      id_type = va_arg(vp, SilcUInt32);
       entry = va_arg(vp, void *);
       public_key = va_arg(vp, SilcPublicKey);
+
+      if (public_key) {
+       pk = silc_pkcs_public_key_encode(public_key, &pk_len);
+
+       getkey = silc_calloc(1, sizeof(*getkey));
+       getkey->entry = entry;
+       getkey->id_type = id_type;
+       getkey->client = client;
+       getkey->conn = conn;
+       getkey->fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+
+       name = (id_type == SILC_ID_CLIENT ? 
+               ((SilcClientEntry)entry)->nickname :
+               ((SilcServerEntry)entry)->server_name);
+
+       silc_verify_public_key_internal(client, conn, name,
+                                       (id_type == SILC_ID_CLIENT ?
+                                        SILC_SOCKET_TYPE_CLIENT :
+                                        SILC_SOCKET_TYPE_SERVER),
+                                       pk, pk_len, SILC_SKE_PK_TYPE_SILC,
+                                       silc_getkey_cb, getkey);
+       silc_free(pk);
+      } else {
+       printformat_module("fe-common/silc", server, NULL,
+                          MSGLEVEL_CRAP, SILCTXT_PUBKEY_NOKEY);
+      }
+    }
+    break;
+
+  case SILC_COMMAND_INFO:
+    {
+      SilcServerEntry server_entry;
+      char *server_name;
+      char *server_info;
+
+      if (!success)
+       return;
       
-      pk = silc_pkcs_public_key_encode(public_key, &pk_len);
-      
-      silc_verify_public_key_internal(client, conn, 
-                                     (id_type == SILC_ID_CLIENT ?
-                                      SILC_SOCKET_TYPE_CLIENT :
-                                      SILC_SOCKET_TYPE_SERVER),
-                                     pk, pk_len, SILC_SKE_PK_TYPE_SILC,
-                                     NULL, NULL);
-      silc_free(pk);
+      server_entry = va_arg(vp, SilcServerEntry);
+      server_name = va_arg(vp, char *);
+      server_info = va_arg(vp, char *);
+
+      if (server_name && server_info )
+       {
+         printtext(server, NULL, MSGLEVEL_CRAP, "Server: %s", server_name);
+         printtext(server, NULL, MSGLEVEL_CRAP, "%s", server_info);
+       }
     }
     break;
     
@@ -690,13 +1976,28 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
     {
       SilcChannelEntry channel;
       char *topic;
+      char tmp[256], *cp, *dm = NULL;
       
       if (!success)
        return;
       
       channel = va_arg(vp, SilcChannelEntry);
       topic = va_arg(vp, char *);
-      
+
+      if (topic && !silc_term_utf8() &&
+         silc_utf8_valid(topic, strlen(topic))) {
+       memset(tmp, 0, sizeof(tmp));
+       cp = tmp;
+       if (strlen(topic) > sizeof(tmp) - 1) {
+         dm = silc_calloc(strlen(topic) + 1, sizeof(*dm));
+         cp = dm;
+       }
+
+       silc_utf8_decode(topic, strlen(topic), SILC_STRING_LANGUAGE,
+                        cp, strlen(topic));
+       topic = cp;
+      }
+
       if (topic) {
        chanrec = silc_channel_find_entry(server, channel);
        if (chanrec) {
@@ -712,6 +2013,138 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
                           MSGLEVEL_CRAP, SILCTXT_CHANNEL_TOPIC_NOT_SET,
                           channel->channel_name);
       }
+      silc_free(dm);
+    }
+    break;
+
+  case SILC_COMMAND_WATCH:
+    break;
+  
+  case SILC_COMMAND_STATS:
+    {
+      SilcUInt32 starttime, uptime, my_clients, my_channels, my_server_ops,
+                my_router_ops, cell_clients, cell_channels, cell_servers,
+                clients, channels, servers, routers, server_ops, router_ops;
+      SilcUInt32 buf_len;
+      SilcBufferStruct buf;
+      unsigned char *tmp_buf;
+      char tmp[40];
+      const char *tmptime;
+      int days, hours, mins, secs;
+
+      if (!success)
+       return;
+
+      tmp_buf = va_arg(vp, unsigned char *);
+      buf_len = va_arg(vp, SilcUInt32);
+
+      if (!tmp_buf || !buf_len) {
+       printtext(server, NULL, MSGLEVEL_CRAP, "No statistics available");
+       return;
+      }
+
+      /* Get statistics structure */
+      silc_buffer_set(&buf, tmp_buf, buf_len);
+      silc_buffer_unformat(&buf,
+                          SILC_STR_UI_INT(&starttime),
+                          SILC_STR_UI_INT(&uptime),
+                          SILC_STR_UI_INT(&my_clients),
+                          SILC_STR_UI_INT(&my_channels),
+                          SILC_STR_UI_INT(&my_server_ops),
+                          SILC_STR_UI_INT(&my_router_ops),
+                          SILC_STR_UI_INT(&cell_clients),
+                          SILC_STR_UI_INT(&cell_channels),
+                          SILC_STR_UI_INT(&cell_servers),
+                          SILC_STR_UI_INT(&clients),
+                          SILC_STR_UI_INT(&channels),
+                          SILC_STR_UI_INT(&servers),
+                          SILC_STR_UI_INT(&routers),
+                          SILC_STR_UI_INT(&server_ops),
+                          SILC_STR_UI_INT(&router_ops),
+                          SILC_STR_END);
+
+      tmptime = silc_get_time(starttime);
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_STATS,
+                        "Local server start time", tmptime);
+
+      days = uptime / (24 * 60 * 60);
+      uptime -= days * (24 * 60 * 60);
+      hours = uptime / (60 * 60);
+      uptime -= hours * (60 * 60);
+      mins = uptime / 60;
+      uptime -= mins * 60;
+      secs = uptime;
+      snprintf(tmp, sizeof(tmp) - 1, "%d days %d hours %d mins %d secs",
+              days, hours, mins, secs);
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_STATS,
+                        "Local server uptime", tmp);
+
+      snprintf(tmp, sizeof(tmp) - 1, "%d", (int)my_clients);
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_STATS,
+                        "Local server clients", tmp);
+
+      snprintf(tmp, sizeof(tmp) - 1, "%d", (int)my_channels);
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_STATS,
+                        "Local server channels", tmp);
+
+      snprintf(tmp, sizeof(tmp) - 1, "%d", (int)my_server_ops);
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_STATS,
+                        "Local server operators", tmp);
+
+      snprintf(tmp, sizeof(tmp) - 1, "%d", (int)my_router_ops);
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_STATS,
+                        "Local router operators", tmp);
+
+      snprintf(tmp, sizeof(tmp) - 1, "%d", (int)cell_clients);
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_STATS,
+                        "Local cell clients", tmp);
+
+      snprintf(tmp, sizeof(tmp) - 1, "%d", (int)cell_channels);
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_STATS,
+                        "Local cell channels", tmp);
+
+      snprintf(tmp, sizeof(tmp) - 1, "%d", (int)cell_servers);
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_STATS,
+                        "Local cell servers", tmp);
+
+      snprintf(tmp, sizeof(tmp) - 1, "%d", (int)clients);
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_STATS,
+                        "Total clients", tmp);
+
+      snprintf(tmp, sizeof(tmp) - 1, "%d", (int)channels);
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_STATS,
+                        "Total channels", tmp);
+
+      snprintf(tmp, sizeof(tmp) - 1, "%d", (int)servers);
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_STATS,
+                        "Total servers", tmp);
+
+      snprintf(tmp, sizeof(tmp) - 1, "%d", (int)routers);
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_STATS,
+                        "Total routers", tmp);
+
+      snprintf(tmp, sizeof(tmp) - 1, "%d", (int)server_ops);
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_STATS,
+                          "Total server operators", tmp);
+
+      snprintf(tmp, sizeof(tmp) - 1, "%d", (int)router_ops);
+      printformat_module("fe-common/silc", server, NULL,
+                        MSGLEVEL_CRAP, SILCTXT_STATS,
+                        "Total router operators", tmp);
     }
     break;
 
@@ -720,16 +2153,14 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
   va_end(vp);
 }
 
-/* Internal routine to verify public key. If the `completion' is provided
-   it will be called to indicate whether public was verified or not. */
-
 typedef struct {
   SilcClient client;
   SilcClientConnection conn;
   char *filename;
   char *entity;
+  char *entity_name;
   unsigned char *pk;
-  uint32 pk_len;
+  SilcUInt32 pk_len;
   SilcSKEPKType pk_type;
   SilcVerifyPublicKey completion;
   void *context;
@@ -753,23 +2184,33 @@ static void verify_public_key_completion(const char *line, void *context)
       verify->completion(FALSE, verify->context);
 
     printformat_module("fe-common/silc", NULL, NULL,
-                      MSGLEVEL_CRAP, SILCTXT_PUBKEY_DISCARD, verify->entity);
+                      MSGLEVEL_CRAP, SILCTXT_PUBKEY_DISCARD, 
+                      verify->entity_name ? verify->entity_name :
+                      verify->entity);
   }
 
   silc_free(verify->filename);
   silc_free(verify->entity);
+  silc_free(verify->entity_name);
   silc_free(verify->pk);
   silc_free(verify);
 }
 
+/* Internal routine to verify public key. If the `completion' is provided
+   it will be called to indicate whether public was verified or not. For
+   server/router public key this will check for filename that includes the
+   remote host's IP address and remote host's hostname. */
+
 static void 
 silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn,
-                               SilcSocketType conn_type, unsigned char *pk, 
-                               uint32 pk_len, SilcSKEPKType pk_type,
+                               const char *name, SilcSocketType conn_type, 
+                               unsigned char *pk, SilcUInt32 pk_len, 
+                               SilcSKEPKType pk_type,
                                SilcVerifyPublicKey completion, void *context)
 {
   int i;
-  char file[256], filename[256], *fingerprint, *format;
+  char file[256], filename[256], filename2[256], *ipf, *hostf = NULL;
+  char *fingerprint, *babbleprint, *format;
   struct passwd *pw;
   struct stat st;
   char *entity = ((conn_type == SILC_SOCKET_TYPE_SERVER ||
@@ -794,14 +2235,32 @@ silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn,
   }
 
   memset(filename, 0, sizeof(filename));
+  memset(filename2, 0, sizeof(filename2));
   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);
+    if (!name) {
+      snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity, 
+              conn->sock->ip, conn->sock->port);
+      snprintf(filename, sizeof(filename) - 1, "%s/%skeys/%s", 
+              get_irssi_dir(), entity, file);
+      
+      snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity, 
+              conn->sock->hostname, conn->sock->port);
+      snprintf(filename2, sizeof(filename2) - 1, "%s/%skeys/%s", 
+              get_irssi_dir(), entity, file);
+      
+      ipf = filename;
+      hostf = filename2;
+    } else {
+      snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity, 
+              name, conn->sock->port);
+      snprintf(filename, sizeof(filename) - 1, "%s/%skeys/%s", 
+              get_irssi_dir(), entity, file);
+      
+      ipf = filename;
+    }
   } else {
     /* Replace all whitespaces with `_'. */
     fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
@@ -810,34 +2269,42 @@ silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn,
        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);
+    snprintf(filename, sizeof(filename) - 1, "%s/%skeys/%s", 
+            get_irssi_dir(), entity, file);
     silc_free(fingerprint);
+
+    ipf = filename;
   }
 
   /* Take fingerprint of the public key */
   fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+  babbleprint = silc_hash_babbleprint(NULL, pk, pk_len);
 
   verify = silc_calloc(1, sizeof(*verify));
   verify->client = client;
   verify->conn = conn;
-  verify->filename = strdup(filename);
+  verify->filename = strdup(ipf);
   verify->entity = strdup(entity);
-  verify->pk = silc_calloc(pk_len, sizeof(*verify->pk));
-  memcpy(verify->pk, pk, pk_len);
+  verify->entity_name = (conn_type != SILC_SOCKET_TYPE_CLIENT ?
+                        (name ? strdup(name) : strdup(conn->sock->hostname))
+                        : NULL);
+  verify->pk = silc_memdup(pk, pk_len);
   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) {
+  if (stat(ipf, &st) < 0 && (!hostf || stat(hostf, &st) < 0)) {
     /* Key does not exist, ask user to verify the key and save it */
 
     printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
-                      SILCTXT_PUBKEY_RECEIVED, entity);
+                      SILCTXT_PUBKEY_RECEIVED,verify->entity_name ? 
+                      verify->entity_name : entity);
     printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
                       SILCTXT_PUBKEY_FINGERPRINT, entity, fingerprint);
+    printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
+                      SILCTXT_PUBKEY_BABBLEPRINT, babbleprint);
     format = format_get_text("fe-common/silc", NULL, NULL, NULL,
                             SILCTXT_PUBKEY_ACCEPT);
     keyboard_entry_redirect((SIGNAL_FUNC)verify_public_key_completion,
@@ -849,35 +2316,45 @@ silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn,
     /* 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)) {
-       printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
-                          SILCTXT_PUBKEY_RECEIVED, entity);
-       printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
-                          SILCTXT_PUBKEY_FINGERPRINT, entity, fingerprint);
-       printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
-                          SILCTXT_PUBKEY_COULD_NOT_LOAD, entity);
-       format = format_get_text("fe-common/silc", NULL, NULL, NULL,
-                                SILCTXT_PUBKEY_ACCEPT_ANYWAY);
-       keyboard_entry_redirect((SIGNAL_FUNC)verify_public_key_completion,
-                               format, 0, verify);
-       g_free(format);
-       silc_free(fingerprint);
-       return;
-      }
-  
+    SilcUInt32 encpk_len;
+
+    /* Load the key file, try for both IP filename and hostname filename */
+    if (!silc_pkcs_load_public_key(ipf, &public_key, 
+                                  SILC_PKCS_FILE_PEM) &&
+       !silc_pkcs_load_public_key(ipf, &public_key, 
+                                  SILC_PKCS_FILE_BIN) &&
+       (!hostf || (!silc_pkcs_load_public_key(hostf, &public_key, 
+                                              SILC_PKCS_FILE_PEM) &&
+                   !silc_pkcs_load_public_key(hostf, &public_key, 
+                                              SILC_PKCS_FILE_BIN)))) {
+      printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
+                        SILCTXT_PUBKEY_RECEIVED,verify->entity_name ? 
+                        verify->entity_name : entity);
+      printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
+                        SILCTXT_PUBKEY_FINGERPRINT, entity, fingerprint);
+      printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
+                        SILCTXT_PUBKEY_BABBLEPRINT, babbleprint);
+      printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
+                        SILCTXT_PUBKEY_COULD_NOT_LOAD, entity);
+      format = format_get_text("fe-common/silc", NULL, NULL, NULL,
+                              SILCTXT_PUBKEY_ACCEPT_ANYWAY);
+      keyboard_entry_redirect((SIGNAL_FUNC)verify_public_key_completion,
+                             format, 0, verify);
+      g_free(format);
+      silc_free(fingerprint);
+      return;
+    }
+
     /* Encode the key data */
     encpk = silc_pkcs_public_key_encode(public_key, &encpk_len);
     if (!encpk) {
       printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
-                        SILCTXT_PUBKEY_RECEIVED, entity);
+                        SILCTXT_PUBKEY_RECEIVED,verify->entity_name ? 
+                        verify->entity_name : entity);
       printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
                         SILCTXT_PUBKEY_FINGERPRINT, entity, fingerprint);
+      printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
+                        SILCTXT_PUBKEY_BABBLEPRINT, babbleprint);
       printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
                         SILCTXT_PUBKEY_MALFORMED, entity);
       format = format_get_text("fe-common/silc", NULL, NULL, NULL,
@@ -892,9 +2369,12 @@ silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn,
     /* Compare the keys */
     if (memcmp(encpk, pk, encpk_len)) {
       printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
-                        SILCTXT_PUBKEY_RECEIVED, entity);
+                        SILCTXT_PUBKEY_RECEIVED,verify->entity_name ? 
+                        verify->entity_name : entity);
       printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
                         SILCTXT_PUBKEY_FINGERPRINT, entity, fingerprint);
+      printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
+                        SILCTXT_PUBKEY_BABBLEPRINT, babbleprint);
       printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
                         SILCTXT_PUBKEY_NO_MATCH, entity);
       printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
@@ -916,6 +2396,11 @@ silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn,
     if (completion)
       completion(TRUE, context);
     silc_free(fingerprint);
+    silc_free(verify->filename);
+    silc_free(verify->entity);
+    silc_free(verify->entity_name);
+    silc_free(verify->pk);
+    silc_free(verify);
   }
 }
 
@@ -927,10 +2412,10 @@ silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn,
 void 
 silc_verify_public_key(SilcClient client, SilcClientConnection conn,
                       SilcSocketType conn_type, unsigned char *pk, 
-                      uint32 pk_len, SilcSKEPKType pk_type,
+                      SilcUInt32 pk_len, SilcSKEPKType pk_type,
                       SilcVerifyPublicKey completion, void *context)
 {
-  silc_verify_public_key_internal(client, conn, conn_type, pk,
+  silc_verify_public_key_internal(client, conn, NULL, conn_type, pk,
                                  pk_len, pk_type,
                                  completion, context);
 }
@@ -945,13 +2430,15 @@ typedef struct {
 void ask_passphrase_completion(const char *passphrase, void *context)
 {
   AskPassphrase p = (AskPassphrase)context;
+  if (passphrase && passphrase[0] == '\0')
+    passphrase = NULL;
   p->completion((unsigned char *)passphrase, 
                passphrase ? strlen(passphrase) : 0, p->context);
   silc_free(p);
 }
 
 void silc_ask_passphrase(SilcClient client, SilcClientConnection conn,
-                               SilcAskPassphrase completion, void *context)
+                        SilcAskPassphrase completion, void *context)
 {
   AskPassphrase p = silc_calloc(1, sizeof(*p));
   p->completion = completion;
@@ -961,34 +2448,81 @@ void silc_ask_passphrase(SilcClient client, SilcClientConnection conn,
                          "Passphrase: ", ENTRY_REDIRECT_FLAG_HIDDEN, p);
 }
 
+typedef struct {
+  SilcGetAuthMeth completion;
+  void *context;
+} *InternalGetAuthMethod;
+
+/* Callback called when we've received the authentication method information
+   from the server after we've requested it. This will get the authentication
+   data from the user if needed. */
+
+static void silc_get_auth_method_callback(SilcClient client,
+                                         SilcClientConnection conn,
+                                         SilcAuthMethod auth_meth,
+                                         void *context)
+{
+  InternalGetAuthMethod internal = (InternalGetAuthMethod)context;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  switch (auth_meth) {
+  case SILC_AUTH_NONE:
+    /* No authentication required. */
+    (*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context);
+    break;
+  case SILC_AUTH_PASSWORD:
+    {
+      /* Check whether we find the password for this server in our
+        configuration.  If not, then don't provide so library will ask
+        it from the user. */
+      SERVER_SETUP_REC *setup = server_setup_find_port(conn->remote_host,
+                                                      conn->remote_port);
+      if (!setup || !setup->password) {
+       (*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context);
+       break;
+      }
+      
+      (*internal->completion)(TRUE, auth_meth, setup->password,
+                             strlen(setup->password), internal->context);
+    }
+    break;
+  case SILC_AUTH_PUBLIC_KEY:
+    /* Do not get the authentication data now, the library will generate
+       it using our default key, if we do not provide it here. */
+    /* XXX In the future when we support multiple local keys and multiple
+       local certificates we will need to ask from user which one to use. */
+    (*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context);
+    break;
+  }
+
+  silc_free(internal);
+}
+
 /* 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. */
 
-int silc_get_auth_method(SilcClient client, SilcClientConnection conn,
-                        char *hostname, uint16 port,
-                        SilcProtocolAuthMeth *auth_meth,
-                        unsigned char **auth_data,
-                        uint32 *auth_data_len)
+void silc_get_auth_method(SilcClient client, SilcClientConnection conn,
+                         char *hostname, SilcUInt16 port,
+                         SilcGetAuthMeth completion, void *context)
 {
-  bool ret = TRUE;
-  SILC_SERVER_REC *server = conn ? conn->context : NULL;
+  InternalGetAuthMethod internal;
 
-  /* XXX must resolve from configuration whether this connection has
-     any specific authentication data */
+  SILC_LOG_DEBUG(("Start"));
 
-  *auth_meth = SILC_AUTH_NONE;
-  *auth_data = NULL;
-  *auth_data_len = 0;
-
-  if (ret == FALSE) {
-    printformat_module("fe-common/silc", server, NULL,
-                      MSGLEVEL_MODES, SILCTXT_AUTH_METH_UNRESOLVED);
-  }
+  /* If we do not have this connection configured by the user in a
+     configuration file then resolve the authentication method from the
+     server for this session. */
+  internal = silc_calloc(1, sizeof(*internal));
+  internal->completion = completion;
+  internal->context = context;
 
-  return ret;
+  silc_client_request_authentication_method(client, conn, 
+                                           silc_get_auth_method_callback,
+                                           internal);
 }
 
 /* Notifies application that failure packet was received.  This is called
@@ -1001,6 +2535,8 @@ int silc_get_auth_method(SilcClient client, SilcClientConnection conn,
 void silc_failure(SilcClient client, SilcClientConnection conn, 
                  SilcProtocol protocol, void *failure)
 {
+  SILC_LOG_DEBUG(("Start"));
+
   if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_KEY_EXCHANGE) {
     SilcSKEStatus status = (SilcSKEStatus)failure;
     
@@ -1028,10 +2564,13 @@ void silc_failure(SilcClient client, SilcClientConnection conn,
     if (status == SILC_SKE_STATUS_INCORRECT_SIGNATURE)
       printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
                         SILCTXT_KE_INCORRECT_SIGNATURE);
+    if (status == SILC_SKE_STATUS_INVALID_COOKIE)
+      printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
+                        SILCTXT_KE_INVALID_COOKIE);
   }
 
   if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_CONNECTION_AUTH) {
-    uint32 err = (uint32)failure;
+    SilcUInt32 err = (SilcUInt32)failure;
 
     if (err == SILC_AUTH_FAILED)
       printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, 
@@ -1046,14 +2585,15 @@ void silc_failure(SilcClient client, SilcClientConnection conn,
    desired (application may start it later by calling the function
    silc_client_perform_key_agreement). */
 
-int silc_key_agreement(SilcClient client, SilcClientConnection conn,
-                      SilcClientEntry client_entry, char *hostname,
-                      int port,
-                      SilcKeyAgreementCallback *completion,
-                      void **context)
+bool silc_key_agreement(SilcClient client, SilcClientConnection conn,
+                       SilcClientEntry client_entry, const char *hostname,
+                       SilcUInt16 port, SilcKeyAgreementCallback *completion,
+                       void **context)
 {
   char portstr[12];
 
+  SILC_LOG_DEBUG(("Start"));
+
   /* We will just display the info on the screen and return FALSE and user
      will have to start the key agreement with a command. */
 
@@ -1061,10 +2601,10 @@ int silc_key_agreement(SilcClient client, SilcClientConnection conn,
     snprintf(portstr, sizeof(portstr) - 1, "%d", port);
 
   if (!hostname)
-    printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_NOTICES,
+    printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP,
                       SILCTXT_KEY_AGREEMENT_REQUEST, client_entry->nickname);
   else
-    printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_NOTICES,
+    printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP,
                       SILCTXT_KEY_AGREEMENT_REQUEST_HOST, 
                       client_entry->nickname, hostname, portstr);
 
@@ -1074,6 +2614,86 @@ int silc_key_agreement(SilcClient client, SilcClientConnection conn,
   return FALSE;
 }
 
+/* Notifies application that file transfer protocol session is being
+   requested by the remote client indicated by the `client_entry' from
+   the `hostname' and `port'. The `session_id' is the file transfer
+   session and it can be used to either accept or reject the file
+   transfer request, by calling the silc_client_file_receive or
+   silc_client_file_close, respectively. */
+
+void silc_ftp(SilcClient client, SilcClientConnection conn,
+             SilcClientEntry client_entry, SilcUInt32 session_id,
+             const char *hostname, SilcUInt16 port)
+{
+  SILC_SERVER_REC *server;
+  char portstr[12];
+  FtpSession ftp = NULL;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  server = conn->context;
+
+  silc_dlist_start(server->ftp_sessions);
+  while ((ftp = silc_dlist_get(server->ftp_sessions)) != SILC_LIST_END) {
+    if (ftp->client_entry == client_entry &&
+       ftp->session_id == session_id) {
+      server->current_session = ftp;
+      break;
+    }
+  }
+  if (ftp == SILC_LIST_END) {
+    ftp = silc_calloc(1, sizeof(*ftp));
+    ftp->client_entry = client_entry;
+    ftp->session_id = session_id;
+    ftp->send = FALSE;
+    ftp->conn = conn;
+    silc_dlist_add(server->ftp_sessions, ftp);
+    server->current_session = ftp;
+  }
+
+  if (hostname) 
+    snprintf(portstr, sizeof(portstr) - 1, "%d", port);
+
+  if (!hostname)
+    printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP,
+                      SILCTXT_FILE_REQUEST, client_entry->nickname);
+  else
+    printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP,
+                      SILCTXT_FILE_REQUEST_HOST, 
+                      client_entry->nickname, hostname, portstr);
+}
+
+/* Delivers SILC session detachment data indicated by `detach_data' to the
+   application.  If application has issued SILC_COMMAND_DETACH command
+   the client session in the SILC network is not quit.  The client remains
+   in the network but is detached.  The detachment data may be used later
+   to resume the session in the SILC Network.  The appliation is
+   responsible of saving the `detach_data', to for example in a file.
+
+   The detachment data can be given as argument to the functions
+   silc_client_connect_to_server, or silc_client_add_connection when
+   creating connection to remote server, inside SilcClientConnectionParams
+   structure.  If it is provided the client library will attempt to resume
+   the session in the network.  After the connection is created
+   successfully, the application is responsible of setting the user
+   interface for user into the same state it was before detaching (showing
+   same channels, channel modes, etc).  It can do this by fetching the
+   information (like joined channels) from the client library. */
+
+void
+silc_detach(SilcClient client, SilcClientConnection conn,
+            const unsigned char *detach_data, SilcUInt32 detach_data_len)
+{
+  char file[256];
+
+  /* Save the detachment data to file. */
+
+  memset(file, 0, sizeof(file));
+  snprintf(file, sizeof(file) - 1, "%s/session", get_irssi_dir());
+  silc_file_writefile(file, detach_data, detach_data_len);
+}
+
+
 /* SILC client operations */
 SilcClientOperations ops = {
   silc_say,
@@ -1089,4 +2709,6 @@ SilcClientOperations ops = {
   silc_ask_passphrase,
   silc_failure,
   silc_key_agreement,
+  silc_ftp,
+  silc_detach,
 };