Added new SILC_STATUS_ERR_TIMEDOUT status type.
[silc.git] / apps / irssi / src / silc / core / client_ops.c
index a4008b78c8882fa13f610e7f8adee69dfe35e743..8a98a779fc26af134d6024d30e46830317c31550 100644 (file)
@@ -39,6 +39,7 @@
 #include "fe-common/silc/module-formats.h"
 
 #include "core.h"
+#include "blob.h"
 
 static void 
 silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn,
@@ -47,6 +48,40 @@ silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn,
                                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]");
+}
+
 void silc_say(SilcClient client, SilcClientConnection conn,
              SilcClientMessageType type, char *msg, ...)
 {
@@ -76,6 +111,20 @@ void silc_say_error(char *msg, ...)
   va_end(va);
 }
 
+void silc_emit_mime_sig(SILC_SERVER_REC *server, SILC_CHANNEL_REC *channel,
+                       const char *data, SilcUInt32 data_len,
+                       const char *encoding, const char *type,
+                       const char *nick)
+{
+  BLOB_REC blob;
+
+  blob_fill(&blob);
+  blob.octets = data_len;
+  blob.data = (char *)data;
+
+  signal_emit("mime", 6, server, channel, &blob, encoding, type, nick);
+}
+
 /* 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. */
 
@@ -106,6 +155,33 @@ void silc_channel_message(SilcClient client, SilcClientConnection conn,
       nick = silc_nicklist_insert(chanrec, chu, FALSE);
   }
 
+  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 {
+      silc_emit_mime_sig(server, chanrec, data, data_len, 
+                        enc, type, nick->nick);
+      message = NULL;
+    }
+  }
+
+  if (!message)
+    return;
+
   if (flags & SILC_MESSAGE_FLAG_ACTION)
     printformat_module("fe-common/silc", server, channel->channel_name,
                       MSGLEVEL_ACTIONS, SILCTXT_CHANNEL_ACTION, 
@@ -114,11 +190,32 @@ void silc_channel_message(SilcClient client, SilcClientConnection conn,
     printformat_module("fe-common/silc", server, channel->channel_name,
                       MSGLEVEL_NOTICES, SILCTXT_CHANNEL_NOTICE, 
                        nick == NULL ? "[<unknown>]" : nick->nick, message);
-  else
+  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);
+      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;
+    }
+
     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
@@ -139,6 +236,53 @@ void silc_private_message(SilcClient client, SilcClientConnection conn,
   if (sender->username)
     snprintf(userhost, sizeof(userhost) - 1, "%s@%s",
             sender->username, sender->hostname);
+
+  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 {
+      silc_emit_mime_sig(server, NULL, data, data_len, 
+                        enc, type, sender->nickname);
+      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);
+    signal_emit("message private", 4, server, cp,
+               sender->nickname ? sender->nickname : "[<unknown>]",
+               sender->username ? userhost : NULL);
+    silc_free(dm);
+    return;
+  }
+
   signal_emit("message private", 4, server, message,
              sender->nickname ? sender->nickname : "[<unknown>]",
              sender->username ? userhost : NULL);
@@ -165,7 +309,7 @@ void silc_notify(SilcClient client, SilcClientConnection conn,
   SilcIdType idtype;
   void *entry;
   SilcUInt32 mode;
-  char userhost[512];
+  char buf[512];
   char *name, *tmp;
   GSList *list1, *list_tmp;
 
@@ -192,11 +336,11 @@ void silc_notify(SilcClient client, SilcClientConnection conn,
     name = va_arg(va, char *);
     client_entry = va_arg(va, SilcClientEntry);
 
-    memset(userhost, 0, sizeof(userhost));
-    snprintf(userhost, sizeof(userhost) - 1, "%s@%s",
+    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, userhost);
+               name, client_entry->nickname, buf);
     break;
 
   case SILC_NOTIFY_TYPE_JOIN:
@@ -223,13 +367,13 @@ void silc_notify(SilcClient client, SilcClientConnection conn,
       }
     }
     
-    memset(userhost, 0, sizeof(userhost));
+    memset(buf, 0, sizeof(buf));
     if (client_entry->username)
-    snprintf(userhost, sizeof(userhost) - 1, "%s@%s",
+    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 ? "" : userhost);
+               client_entry->username == NULL ? "" : buf);
     break;
 
   case SILC_NOTIFY_TYPE_LEAVE:
@@ -242,13 +386,13 @@ void silc_notify(SilcClient client, SilcClientConnection conn,
     client_entry = va_arg(va, SilcClientEntry);
     channel = va_arg(va, SilcChannelEntry);
     
-    memset(userhost, 0, sizeof(userhost));
+    memset(buf, 0, sizeof(buf));
     if (client_entry->username)
-      snprintf(userhost, sizeof(userhost) - 1, "%s@%s",
+      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 ? 
-               userhost : "", client_entry->nickname);
+               buf : "", client_entry->nickname);
     
     chanrec = silc_channel_find_entry(server, channel);
     if (chanrec != NULL) {
@@ -270,12 +414,12 @@ void silc_notify(SilcClient client, SilcClientConnection conn,
     
     silc_server_free_ftp(server, client_entry);
     
-    memset(userhost, 0, sizeof(userhost));
+    memset(buf, 0, sizeof(buf));
     if (client_entry->username)
-      snprintf(userhost, sizeof(userhost) - 1, "%s@%s",
+      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 ? userhost : "", 
+               client_entry->username ? buf : "", 
                tmp ? tmp : "");
     
     list1 = nicklist_get_same_unique(SERVER(server), client_entry);
@@ -309,17 +453,17 @@ void silc_notify(SilcClient client, SilcClientConnection conn,
     
     if (idtype == SILC_ID_CLIENT) {
       client_entry = (SilcClientEntry)entry;
-      memset(userhost, 0, sizeof(userhost));
-      snprintf(userhost, sizeof(userhost) - 1, "%s@%s",
+      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, userhost);
+                 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 {
+    } 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);
@@ -335,15 +479,18 @@ void silc_notify(SilcClient client, SilcClientConnection conn,
 
     client_entry = va_arg(va, SilcClientEntry);
     client_entry2 = va_arg(va, SilcClientEntry);
+
+    if (!strcmp(client_entry->nickname, client_entry2->nickname))
+      break;
     
-    memset(userhost, 0, sizeof(userhost));
-    snprintf(userhost, sizeof(userhost) - 1, "%s@%s",
+    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, userhost);
+               client_entry->nickname, buf);
     break;
 
   case SILC_NOTIFY_TYPE_CMODE_CHANGE:
@@ -385,7 +532,7 @@ void silc_notify(SilcClient client, SilcClientConnection conn,
                         MSGLEVEL_MODES, SILCTXT_CHANNEL_CMODE,
                         channel->channel_name, tmp ? tmp : "removed all",
                         server_entry->server_name);
-    } else {
+    } else if (idtype == SILC_ID_CHANNEL) {
       channel2 = (SilcChannelEntry)entry;
       printformat_module("fe-common/silc", server, channel->channel_name,
                         MSGLEVEL_MODES, SILCTXT_CHANNEL_CMODE,
@@ -439,7 +586,7 @@ void silc_notify(SilcClient client, SilcClientConnection conn,
                         channel->channel_name, client_entry2->nickname, 
                         tmp ? tmp : "removed all",
                         server_entry->server_name);
-    } else {
+    } else if (idtype == SILC_ID_CHANNEL) {
       channel2 = (SilcChannelEntry)entry;
       printformat_module("fe-common/silc", server, channel->channel_name,
                         MSGLEVEL_MODES, SILCTXT_CHANNEL_CUMODE,
@@ -454,6 +601,11 @@ void silc_notify(SilcClient client, SilcClientConnection conn,
                         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;
 
@@ -518,13 +670,27 @@ void silc_notify(SilcClient client, SilcClientConnection conn,
 
     client_entry = va_arg(va, SilcClientEntry);
     tmp = va_arg(va, char *);
-    client_entry2 = va_arg(va, SilcClientEntry);
+    idtype = va_arg(va, int);
+    entry = va_arg(va, SilcClientEntry);
   
     if (client_entry == conn->local_entry) {
-      printformat_module("fe-common/silc", server, NULL,
-                        MSGLEVEL_CRAP, SILCTXT_CHANNEL_KILLED_YOU, 
-                        client_entry2 ? client_entry2->nickname : "",
-                        tmp ? tmp : "");
+      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 = 
@@ -534,11 +700,26 @@ void silc_notify(SilcClient client, SilcClientConnection conn,
        nicklist_remove(channel, nickrec);
       }
 
-      printformat_module("fe-common/silc", server, NULL,
-                        MSGLEVEL_CRAP, SILCTXT_CHANNEL_KILLED, 
-                        client_entry->nickname,
-                        client_entry2 ? client_entry2->nickname : "",
-                        tmp ? tmp : "");
+      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;
 
@@ -561,12 +742,12 @@ void silc_notify(SilcClient client, SilcClientConnection conn,
       clients_count = va_arg(va, SilcUInt32);
   
       for (i = 0; i < clients_count; i++) {
-       memset(userhost, 0, sizeof(userhost));
+       memset(buf, 0, sizeof(buf));
        if (clients[i]->username)
-         snprintf(userhost, sizeof(userhost) - 1, "%s@%s",
+         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 ? userhost : "", 
+                   clients[i]->username ? buf : "", 
                    "server signoff");
 
        silc_server_free_ftp(server, clients[i]);
@@ -582,6 +763,73 @@ void silc_notify(SilcClient client, SilcClientConnection conn,
     }
     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,
@@ -596,29 +844,55 @@ void silc_notify(SilcClient client, SilcClientConnection conn,
    or connecting failed.  This is also the first time application receives
    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 (!server && !success) {
-    silc_client_close_connection(client, NULL, conn);
+  if (!server || server->disconnected) {
+    silc_client_close_connection(client, conn);
     return;
   }
 
-  if (success) {
+  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;
     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;
 
@@ -635,6 +909,12 @@ void silc_disconnect(SilcClient client, SilcClientConnection conn)
     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;
@@ -651,17 +931,19 @@ 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;
 
   SILC_LOG_DEBUG(("Start"));
 
-  if (!success)
+  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,
@@ -692,6 +974,9 @@ static void silc_client_join_get_users(SilcClient client,
   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;
 
@@ -712,6 +997,7 @@ static void silc_client_join_get_users(SilcClient client,
   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,
@@ -780,8 +1066,8 @@ void silc_getkey_cb(bool success, void *context)
 
 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;
@@ -798,7 +1084,7 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
       char buf[1024], *nickname, *username, *realname, *nick;
       unsigned char *fingerprint;
       SilcUInt32 idle, mode;
-      SilcBuffer channels;
+      SilcBuffer channels, user_modes;
       SilcClientEntry client_entry;
       
       if (status == SILC_STATUS_ERR_NO_SUCH_NICK) {
@@ -808,7 +1094,7 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
                                     3, NULL);
        if (tmp)
          silc_say_error("%s: %s", tmp, 
-                        silc_client_command_status_message(status));
+                        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
@@ -825,16 +1111,16 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
                                                        client_id);
            if (client_entry && client_entry->nickname)
              silc_say_error("%s: %s", client_entry->nickname,
-                            silc_client_command_status_message(status));
+                            silc_get_status_message(status));
            silc_free(client_id);
          }
        }
        break;
-      }
-      
-      if (!success)
+      } else if (!success) {
+       silc_say_error("WHOIS: %s", silc_get_status_message(status));
        return;
-      
+      }
+
       client_entry = va_arg(vp, SilcClientEntry);
       nickname = va_arg(vp, char *);
       username = va_arg(vp, char *);
@@ -843,6 +1129,7 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
       mode = va_arg(vp, SilcUInt32);
       idle = va_arg(vp, SilcUInt32);
       fingerprint = va_arg(vp, unsigned char *);
+      user_modes = va_arg(vp, SilcBuffer);
       
       silc_parse_userfqdn(nickname, &nick, NULL);
       printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP,
@@ -853,16 +1140,20 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
                         SILCTXT_WHOIS_REALNAME, realname);
       silc_free(nick);
 
-      if (channels) {
+      if (channels && user_modes) {
+       SilcUInt32 *umodes;
        SilcDList list = silc_channel_payload_parse_list(channels->data,
                                                         channels->len);
-       if (list) {
+       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));
            SilcUInt32 name_len;
+           char *m = silc_client_chumode_char(umodes[i++]);
            char *name = silc_channel_get_name(entry, &name_len);
            
            if (m)
@@ -875,22 +1166,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);
       }
@@ -925,7 +1207,7 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
                                     3, NULL);
        if (tmp)
          silc_say_error("%s: %s", tmp, 
-                        silc_client_command_status_message(status));
+                        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
@@ -942,7 +1224,7 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
                                                        client_id);
            if (client_entry && client_entry->nickname)
              silc_say_error("%s: %s", client_entry->nickname,
-                            silc_client_command_status_message(status));
+                            silc_get_status_message(status));
            silc_free(client_id);
          }
        }
@@ -963,12 +1245,12 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
                                         3, NULL);
        if (tmp)
          silc_say_error("%s: %s", tmp, 
-                        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 *);
@@ -1061,8 +1343,8 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
 
   case SILC_COMMAND_NICK: 
     {
-      SilcClientEntry client = va_arg(vp, SilcClientEntry);
       char *old;
+      SilcClientEntry client = va_arg(vp, SilcClientEntry);
       
       if (!success)
        return;
@@ -1179,8 +1461,20 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
        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);
 
@@ -1314,6 +1608,137 @@ silc_command_reply(SilcClient client, SilcClientConnection conn,
     }
     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;
+
   }
 
   va_end(vp);
@@ -1592,6 +2017,8 @@ 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);
@@ -1766,6 +2193,13 @@ 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)
@@ -1797,6 +2231,37 @@ void silc_ftp(SilcClient client, SilcClientConnection conn,
                       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,
@@ -1813,4 +2278,5 @@ SilcClientOperations ops = {
   silc_failure,
   silc_key_agreement,
   silc_ftp,
+  silc_detach,
 };