Started implementing protocol version 1.1 and narrowing down
[silc.git] / lib / silcclient / client_notify.c
index 5e13bf72dae90ce0c3860c576b47d06796dac597..797b464e8deaba7ce547ae69c48c4285238bd04c 100644 (file)
 #include "silcclient.h"
 #include "client_internal.h"
 
+/* Context used for resolving client, channel and server info. */
 typedef struct {
-  SilcPacketContext *packet;
+  void *packet;
   void *context;
   SilcSocketConnection sock;
 } *SilcClientNotifyResolve;
 
+SILC_TASK_CALLBACK(silc_client_notify_check_client)
+{ 
+  SilcClientNotifyResolve res = (SilcClientNotifyResolve)context;
+  SilcClientConnection conn = res->context;
+  SilcClient client = conn->client;
+  SilcClientID *client_id = res->packet;
+  silc_client_get_client_by_id_resolve(client, conn, client_id, NULL, NULL);
+  silc_free(client_id);
+  silc_free(res);
+}
+
 /* Called when notify is received and some async operation (such as command)
    is required before processing the notify message. This calls again the
    silc_client_notify_by_server and reprocesses the original notify packet. */
@@ -72,7 +84,7 @@ static void silc_client_notify_by_server_resolve(SilcClient client,
   res->context = client;
   res->sock = silc_socket_dup(conn->sock);
 
-  /* For client resolving use WHOIS, and oterhwise use IDENTIFY */
+  /* For client resolving use WHOIS, and otherwise use IDENTIFY */
   if (id_type == SILC_ID_CLIENT) {
     silc_client_command_register(client, SILC_COMMAND_WHOIS, NULL, NULL,
                                 silc_client_command_reply_whois_i, 0,
@@ -292,6 +304,20 @@ void silc_client_notify_by_server(SilcClient client,
       silc_free(chu);
     }
 
+    /* Some client implementations actually quit network by first doing
+       LEAVE and then immediately SIGNOFF.  We'll check for this by doing 
+       check for the client after 5 - 14 seconds.  If it is not valid after
+       that we'll remove the client from cache. */
+    if (!silc_hash_table_count(client_entry->channels)) {
+      SilcClientNotifyResolve res = silc_calloc(1, sizeof(*res));
+      res->context = conn;
+      res->packet = silc_id_dup(client_id, SILC_ID_CLIENT);
+      silc_schedule_task_add(client->schedule, 0,
+                            silc_client_notify_check_client, conn,
+                            (5 + (silc_rng_get_rn16(client->rng) % 9)),
+                            0, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL);
+    }
+
     /* Notify application. The channel entry is sent last as this notify
        is for channel but application don't know it from the arguments
        sent by server. */
@@ -453,41 +479,71 @@ void silc_client_notify_by_server(SilcClient client,
     if (!client_id)
       goto out;
 
-    /* Find Client entry and if not found resolve it */
-    client_entry2 = silc_client_get_client_by_id(client, conn, client_id);
-    if (!client_entry2) {
-      /* Resolve the entry information */
-      silc_client_notify_by_server_resolve(client, conn, packet, 
-                                          SILC_ID_CLIENT, client_id);
-
-      /* Add the new entry even though we resolved it. This is because we
-        want to replace the old entry with the new entry here right now. */
-      client_entry2 = 
-       silc_client_add_client(client, conn, NULL, NULL, NULL, 
-                              silc_id_dup(client_id, SILC_ID_CLIENT), 
-                              client_entry->mode);
+    /* From protocol version 1.1 we get the new nickname in notify as well,
+       so we don't have to resolve it.  Do it the hard way if server doesn't
+       send it to us. */
+    tmp = silc_argument_get_arg_type(args, 3, NULL);
+    if (tmp) {
+      /* Protocol version 1.1 */
+
+      /* Create new client entry, and save all old information with the
+        new nickname and client ID */
+      client_entry2 = silc_client_add_client(client, conn, NULL, NULL, 
+                                            client_entry->realname,
+                                            silc_id_dup(client_id, 
+                                                        SILC_ID_CLIENT), 0);
+      if (!client_entry2)
+       goto out;
 
-      /* Replace old ID entry with new one on all channels. */
-      silc_client_replace_from_channels(client, conn, client_entry,
-                                       client_entry2);
+      if (client_entry->server)
+       client_entry2->server = strdup(client_entry->server);
+      if (client_entry->username)
+       client_entry2->username = strdup(client_entry->username);
+      if (client_entry->hostname)
+       client_entry2->hostname = strdup(client_entry->hostname);
+      silc_client_update_client(client, conn, client_entry2, tmp, NULL, NULL,
+                               client_entry->mode);
     } else {
+      /* Protocol version 1.0 */
+
+      /* Find client entry and if not found resolve it */
+      client_entry2 = silc_client_get_client_by_id(client, conn, client_id);
+      if (!client_entry2) {
+       /* Resolve the entry information */
+       silc_client_notify_by_server_resolve(client, conn, packet, 
+                                            SILC_ID_CLIENT, client_id);
+
+       /* Add the new entry even though we resolved it. This is because we
+          want to replace the old entry with the new entry here right now. */
+       client_entry2 = 
+         silc_client_add_client(client, conn, NULL, NULL, NULL, 
+                                silc_id_dup(client_id, SILC_ID_CLIENT), 
+                                client_entry->mode);
+
+       /* Replace old ID entry with new one on all channels. */
+       silc_client_replace_from_channels(client, conn, client_entry,
+                                         client_entry2);
+       break;
+      }
+
       if (client_entry2 != conn->local_entry)
        silc_client_nickname_format(client, conn, client_entry2);
+    }
 
-      /* Remove the old from cache */
-      silc_idcache_del_by_context(conn->client_cache, client_entry);
-
-      /* Replace old ID entry with new one on all channels. */
-      silc_client_replace_from_channels(client, conn, client_entry,
-                                       client_entry2);
+    /* Remove the old from cache */
+    silc_idcache_del_by_context(conn->client_cache, client_entry);
+    
+    /* Replace old ID entry with new one on all channels. */
+    silc_client_replace_from_channels(client, conn, client_entry,
+                                     client_entry2);
 
-      /* Notify application */
-      client->internal->ops->notify(client, conn, type, 
-                                   client_entry, client_entry2);
+    /* Notify application */
+    client->internal->ops->notify(client, conn, type, 
+                                 client_entry, client_entry2);
+    
+    /* Free old client entry */
+    silc_client_del_client_entry(client, conn, client_entry);
 
-      /* Free data */
-      silc_client_del_client_entry(client, conn, client_entry);
-    }
     break;
 
   case SILC_NOTIFY_TYPE_CMODE_CHANGE:
@@ -769,9 +825,10 @@ void silc_client_notify_by_server(SilcClient client,
     if (!channel)
       break;
 
-    /* Get the kicker */
+    /* From protocol version 1.1 we get the kicker's client ID as well */
     tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
     if (tmp) {
+      silc_free(client_id);
       client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
       if (!client_id)
        goto out;
@@ -814,36 +871,60 @@ void silc_client_notify_by_server(SilcClient client,
     break;
 
   case SILC_NOTIFY_TYPE_KILLED:
-    /*
-     * A client (maybe me) was killed from the network.
-     */
-
-    SILC_LOG_DEBUG(("Notify: KILLED"));
+    {
+      /*
+       * A client (maybe me) was killed from the network.
+       */
+      char *comment;
+      SilcUInt32 comment_len;
 
-    /* Get Client ID */
-    tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
-    if (!tmp)
-      goto out;
+      SILC_LOG_DEBUG(("Notify: KILLED"));
 
-    client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
-    if (!client_id)
-      goto out;
+      /* Get Client ID */
+      tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
+      if (!tmp)
+       goto out;
 
-    /* Find Client entry */
-    client_entry = silc_client_get_client_by_id(client, conn, client_id);
-    if (!client_entry)
-      goto out;
+      client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
+      if (!client_id)
+       goto out;
 
-    /* Get comment */
-    tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
+      /* Find Client entry */
+      client_entry = silc_client_get_client_by_id(client, conn, client_id);
+      if (!client_entry)
+       goto out;
 
-    /* Notify application. */
-    client->internal->ops->notify(client, conn, type, client_entry, tmp);
+      /* Get comment */
+      comment = silc_argument_get_arg_type(args, 2, &comment_len);
+
+      /* From protocol version 1.1 we get killer's client ID as well */
+      tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
+      if (tmp) {
+       silc_free(client_id);
+       client_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
+       if (!client_id)
+         goto out;
+
+       /* Find killer's client entry and if not found resolve it */
+       client_entry2 = silc_client_get_client_by_id(client, conn, client_id);
+       if (!client_entry2) {
+         silc_client_notify_by_server_resolve(client, conn, packet, 
+                                              SILC_ID_CLIENT, client_id);
+         goto out;
+       } else {
+         if (client_entry2 != conn->local_entry)
+           silc_client_nickname_format(client, conn, client_entry2);
+       }
+      }
 
-    if (client_entry != conn->local_entry)
-      /* Remove the client from all channels and free it */
-      silc_client_del_client(client, conn, client_entry);
+      /* Notify application. */
+      client->internal->ops->notify(client, conn, type, client_entry, 
+                                   comment, client_entry2);
 
+      if (client_entry != conn->local_entry)
+       /* Remove the client from all channels and free it */
+       silc_client_del_client(client, conn, client_entry);
+    }
     break;
     
   case SILC_NOTIFY_TYPE_SERVER_SIGNOFF: