Merge commit 'origin/silc.1.1.branch'
[silc.git] / lib / silcclient / client_notify.c
index a492931b26fd131ed58040537c62c6a28a6f2791..2ccb574c1ac8a384d7fd6a36939e8a0c5b097e52 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 1997 - 2007 Pekka Riikonen
+  Copyright (C) 1997 - 2008 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
 
 /* Notify processing context */
 typedef struct {
-  SilcPacket packet;
-  SilcNotifyPayload payload;
-  SilcFSMThread fsm;
-  SilcChannelEntry channel;
+  SilcPacket packet;                 /* Notify packet */
+  SilcNotifyPayload payload;         /* Parsed notify payload */
+  SilcFSMThread fsm;                 /* Notify FSM thread */
+  SilcChannelEntry channel;          /* Channel entry being resolved */
+  SilcClientEntry client_entry;              /* Client entry being resolved */
+  SilcUInt32 resolve_retry;          /* Resolving retry counter */
 } *SilcClientNotify;
 
 /************************ Static utility functions **************************/
 
-/* Entry resolving callback.  This will continue processing the notify. */
+/* The client entires in notify processing are resolved if they do not exist,
+   or they are not valid.  We go to this callback after resolving where we
+   check if the client entry has become valid.  If resolving succeeded the
+   entry is valid but remains invalid if resolving failed.  This callback
+   will continue processing the notify.  We use this callback also with other
+   entry resolving. */
 
 static void silc_client_notify_resolved(SilcClient client,
                                        SilcClientConnection conn,
@@ -46,8 +53,21 @@ static void silc_client_notify_resolved(SilcClient client,
 {
   SilcClientNotify notify = context;
 
-  /* If no entries found, just finish the notify processing, a silent error */
-  if (!entries)
+  /* If entry is still invalid, resolving failed.  Finish notify processing. */
+  if (notify->client_entry && !notify->client_entry->internal.valid) {
+    /* If resolving timedout try it again many times. */
+    if (status != SILC_STATUS_ERR_TIMEDOUT || ++notify->resolve_retry > 1000) {
+      silc_fsm_next(notify->fsm, silc_client_notify_processed);
+
+      /* Unref client only in case of non-timeout error.  In case of timeout
+        occurred, the routine reprocessing the notify is expected not to
+        create new references of the entry. */
+      silc_client_unref_client(client, conn, notify->client_entry);
+    }
+  }
+
+  /* If no entries found, just finish the notify processing */
+  if (!entries && !notify->client_entry)
     silc_fsm_next(notify->fsm, silc_client_notify_processed);
 
   if (notify->channel) {
@@ -209,7 +229,7 @@ SILC_FSM_STATE(silc_client_notify)
     break;
   }
 
-  return SILC_FSM_YIELD;
+  return SILC_FSM_CONTINUE;
 }
 
 /* Notify processed, finish the packet processing thread */
@@ -278,8 +298,14 @@ SILC_FSM_STATE(silc_client_notify_invite)
 
   /* Get the channel entry */
   channel = silc_client_get_channel_by_id(client, conn, &id.u.channel_id);
-  if (!channel)
-    goto out;
+  if (!channel) {
+    /** Resolve channel */
+    SILC_FSM_CALL(silc_client_get_channel_by_id_resolve(
+                                         client, conn, &id.u.channel_id,
+                                         silc_client_notify_resolved,
+                                         notify));
+    /* NOT REACHED */
+  }
 
   /* If channel is being resolved handle notify after resolving */
   if (channel->internal.resolve_cmd_ident) {
@@ -298,7 +324,7 @@ SILC_FSM_STATE(silc_client_notify_invite)
 
   /* Find Client entry and if not found query it */
   client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-  if (!client_entry || !client_entry->nickname[0]) {
+  if (!client_entry || !client_entry->internal.valid) {
     /** Resolve client */
     silc_client_unref_client(client, conn, client_entry);
     notify->channel = channel;
@@ -364,17 +390,21 @@ SILC_FSM_STATE(silc_client_notify_join)
   if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
     goto out;
 
-  /* Find Client entry and if not found query it */
-  client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-  if (!client_entry || !client_entry->nickname[0] ||
+  /* Find client entry and if not found query it.  If we just queried it
+     don't do it again, unless some data (like username) is missing. */
+  client_entry = notify->client_entry;
+  if (!client_entry)
+    client_entry = silc_client_get_client(client, conn, &id.u.client_id);
+  if (!client_entry || !client_entry->internal.valid ||
       !client_entry->username[0]) {
     /** Resolve client */
-    silc_client_unref_client(client, conn, client_entry);
     notify->channel = channel;
+    notify->client_entry = client_entry;
     SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
                  silc_client_get_client_by_id_resolve(
-                                        client, conn, &id.u.client_id, NULL,
-                                        silc_client_notify_resolved,
+                                        client, conn, client_entry ?
+                                        &client_entry->id : &id.u.client_id,
+                                        NULL, silc_client_notify_resolved,
                                         notify));
     /* NOT REACHED */
   }
@@ -455,7 +485,8 @@ SILC_FSM_STATE(silc_client_notify_leave)
     goto out;
 
   /* Remove client from channel */
-  silc_client_remove_from_channel(client, conn, channel, client_entry);
+  if (!silc_client_remove_from_channel(client, conn, channel, client_entry))
+    goto out;
 
   /* Notify application. */
   NOTIFY(client, conn, type, client_entry, channel);
@@ -483,7 +514,7 @@ SILC_FSM_STATE(silc_client_notify_signoff)
   SilcNotifyType type = silc_notify_get_type(payload);
   SilcArgumentPayload args = silc_notify_get_args(payload);
   SilcClientEntry client_entry;
-  SilcChannelEntry channel;
+  SilcChannelEntry channel = NULL;
   unsigned char *tmp;
   SilcUInt32 tmp_len;
   SilcID id;
@@ -504,22 +535,23 @@ SILC_FSM_STATE(silc_client_notify_signoff)
   if (tmp && tmp_len > 128)
     tmp[128] = '\0';
 
+  if (packet->dst_id_type == SILC_ID_CHANNEL)
+    if (silc_id_str2id(packet->dst_id, packet->dst_id_len, SILC_ID_CHANNEL,
+                      &id.u.channel_id, sizeof(id.u.channel_id)))
+      channel = silc_client_get_channel_by_id(client, conn, &id.u.channel_id);
+
   /* Notify application */
-  NOTIFY(client, conn, type, client_entry, tmp);
+  if (client_entry->internal.valid)
+    NOTIFY(client, conn, type, client_entry, tmp, channel);
 
   /* Remove from channel */
-  if (packet->dst_id_type == SILC_ID_CHANNEL) {
-    if (silc_id_str2id(packet->dst_id, packet->dst_id_len, SILC_ID_CHANNEL,
-                      &id.u.channel_id, sizeof(id.u.channel_id))) {
-      channel = silc_client_get_channel_by_id(client, conn, &id.u.channel_id);
-      if (channel) {
-       silc_client_remove_from_channel(client, conn, channel, client_entry);
-       silc_client_unref_channel(client, conn, channel);
-      }
-    }
+  if (channel) {
+    silc_client_remove_from_channel(client, conn, channel, client_entry);
+    silc_client_unref_channel(client, conn, channel);
   }
 
   /* Delete client */
+  client_entry->internal.valid = FALSE;
   silc_client_del_client(client, conn, client_entry);
   silc_client_unref_client(client, conn, client_entry);
 
@@ -571,7 +603,7 @@ SILC_FSM_STATE(silc_client_notify_topic_set)
     /* NOT REACHED */
   }
 
-  /* Get ID */
+  /* Get ID of topic changer */
   if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
     goto out;
 
@@ -582,18 +614,26 @@ SILC_FSM_STATE(silc_client_notify_topic_set)
 
   if (id.type == SILC_ID_CLIENT) {
     /* Find Client entry */
-    client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-    if (!client_entry || !client_entry->nickname[0]) {
-      /** Resolve client */
-      silc_client_unref_client(client, conn, client_entry);
-      notify->channel = channel;
-      SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
-                   silc_client_get_client_by_id_resolve(
+    client_entry = notify->client_entry;
+    if (!client_entry) {
+      client_entry = silc_client_get_client(client, conn, &id.u.client_id);
+      if (!client_entry || !client_entry->internal.valid) {
+       /** Resolve client */
+       notify->channel = channel;
+       notify->client_entry = client_entry;
+       SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
+                     silc_client_get_client_by_id_resolve(
                                           client, conn, &id.u.client_id, NULL,
                                           silc_client_notify_resolved,
                                           notify));
-      /* NOT REACHED */
+       /* NOT REACHED */
+      }
     }
+
+    /* If client is not on channel, ignore this notify */
+    if (!silc_client_on_channel(channel, client_entry))
+      goto out;
+
     entry = client_entry;
   } else if (id.type == SILC_ID_SERVER) {
     /* Find Server entry */
@@ -661,9 +701,10 @@ SILC_FSM_STATE(silc_client_notify_nick_change)
   SilcNotifyType type = silc_notify_get_type(payload);
   SilcArgumentPayload args = silc_notify_get_args(payload);
   SilcClientEntry client_entry = NULL;
-  unsigned char *tmp, oldnick[128 + 1];
+  unsigned char *tmp, oldnick[256 + 1];
   SilcUInt32 tmp_len;
   SilcID id, id2;
+  SilcBool valid;
 
   SILC_LOG_DEBUG(("Notify: NICK_CHANGE"));
 
@@ -685,17 +726,12 @@ SILC_FSM_STATE(silc_client_notify_nick_change)
       SILC_ID_CLIENT_COMPARE(&id2.u.client_id, conn->local_id))
     goto out;
 
-  /* Find old Client entry */
+  /* Find old client entry.  If we don't have the entry, we ignore this
+     notify. */
   client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-  if (!client_entry || !client_entry->nickname[0]) {
-    /** Resolve client */
-    silc_client_unref_client(client, conn, client_entry);
-    SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
-                                        client, conn, &id.u.client_id, NULL,
-                                        silc_client_notify_resolved,
-                                        notify));
-    /* NOT REACHED */
-  }
+  if (!client_entry)
+    goto out;
+  valid = client_entry->internal.valid;
 
   /* Take the new nickname */
   tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
@@ -729,8 +765,10 @@ SILC_FSM_STATE(silc_client_notify_nick_change)
 
   silc_rwlock_unlock(client_entry->internal.lock);
 
-  /* Notify application */
-  NOTIFY(client, conn, type, client_entry, client_entry->nickname, oldnick);
+  /* Notify application, if client entry is valid.  We do not send nick change
+     notify for entries that were invalid (application doesn't know them). */
+  if (valid)
+    NOTIFY(client, conn, type, client_entry, oldnick, client_entry->nickname);
 
  out:
   /** Notify processed */
@@ -790,24 +828,32 @@ SILC_FSM_STATE(silc_client_notify_cmode_change)
     goto out;
   SILC_GET32_MSB(mode, tmp);
 
-  /* Get ID */
+  /* Get ID of who changed the mode */
   if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
     goto out;
 
   if (id.type == SILC_ID_CLIENT) {
     /* Find Client entry */
-    client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-    if (!client_entry || !client_entry->nickname[0]) {
-      /** Resolve client */
-      silc_client_unref_client(client, conn, client_entry);
-      notify->channel = channel;
-      SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
-                   silc_client_get_client_by_id_resolve(
+    client_entry = notify->client_entry;
+    if (!client_entry) {
+      client_entry = silc_client_get_client(client, conn, &id.u.client_id);
+      if (!client_entry || !client_entry->internal.valid) {
+       /** Resolve client */
+       notify->channel = channel;
+       notify->client_entry = client_entry;
+       SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
+                     silc_client_get_client_by_id_resolve(
                                           client, conn, &id.u.client_id, NULL,
                                           silc_client_notify_resolved,
                                           notify));
-      /* NOT REACHED */
+       /* NOT REACHED */
+      }
     }
+
+    /* If client is not on channel, ignore this notify */
+    if (!silc_client_on_channel(channel, client_entry))
+      goto out;
+
     entry = client_entry;
   } else if (id.type == SILC_ID_SERVER) {
     /* Find Server entry */
@@ -892,13 +938,15 @@ SILC_FSM_STATE(silc_client_notify_cmode_change)
   if (!(channel->mode & SILC_CHANNEL_MODE_ULIMIT))
     channel->user_limit = 0;
 
-  /* Save the new mode */
-  channel->mode = mode;
-
   /* Get the channel public key that was added or removed */
   tmp = silc_argument_get_arg_type(args, 7, &tmp_len);
   if (tmp)
-    silc_client_channel_save_public_keys(channel, tmp, tmp_len);
+    silc_client_channel_save_public_keys(channel, tmp, tmp_len, FALSE);
+  else if (channel->mode & SILC_CHANNEL_MODE_CHANNEL_AUTH)
+    silc_client_channel_save_public_keys(channel, NULL, 0, TRUE);
+
+  /* Save the new mode */
+  channel->mode = mode;
 
   silc_rwlock_unlock(channel->internal.lock);
 
@@ -973,7 +1021,7 @@ SILC_FSM_STATE(silc_client_notify_cumode_change)
 
   /* Find target Client entry */
   client_entry2 = silc_client_get_client_by_id(client, conn, &id2.u.client_id);
-  if (!client_entry2 || !client_entry2->nickname[0]) {
+  if (!client_entry2 || !client_entry2->internal.valid) {
     /** Resolve client */
     silc_client_unref_client(client, conn, client_entry2);
     SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
@@ -983,30 +1031,42 @@ SILC_FSM_STATE(silc_client_notify_cumode_change)
     /* NOT REACHED */
   }
 
+  /* If target client is not on channel, ignore this notify */
+  if (!silc_client_on_channel(channel, client_entry2))
+    goto out;
+
   /* Get the mode */
   tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
   if (!tmp)
     goto out;
   SILC_GET32_MSB(mode, tmp);
 
-  /* Get ID */
+  /* Get ID of mode changer */
   if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
     goto out;
 
   if (id.type == SILC_ID_CLIENT) {
     /* Find Client entry */
-    client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-    if (!client_entry || !client_entry->nickname[0]) {
-      /** Resolve client */
-      silc_client_unref_client(client, conn, client_entry);
-      notify->channel = channel;
-      SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
-                   silc_client_get_client_by_id_resolve(
+    client_entry = notify->client_entry;
+    if (!client_entry) {
+      client_entry = silc_client_get_client(client, conn, &id.u.client_id);
+      if (!client_entry || !client_entry->internal.valid) {
+       /** Resolve client */
+       notify->channel = channel;
+       notify->client_entry = client_entry;
+       SILC_FSM_CALL(channel->internal.resolve_cmd_ident =
+                     silc_client_get_client_by_id_resolve(
                                           client, conn, &id.u.client_id, NULL,
                                           silc_client_notify_resolved,
                                           notify));
-      /* NOT REACHED */
+       /* NOT REACHED */
+      }
     }
+
+    /* If client is not on channel, ignore this notify */
+    if (!silc_client_on_channel(channel, client_entry))
+      goto out;
+
     entry = client_entry;
   } else if (id.type == SILC_ID_SERVER) {
     /* Find Server entry */
@@ -1190,11 +1250,11 @@ SILC_FSM_STATE(silc_client_notify_kicked)
     /* NOT REACHED */
   }
 
-  /* Get Client ID */
+  /* Get the kicked Client ID */
   if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
     goto out;
 
-  /* Find Client entry */
+  /* Find client entry */
   client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
   if (!client_entry)
     goto out;
@@ -1205,7 +1265,7 @@ SILC_FSM_STATE(silc_client_notify_kicked)
 
   /* Find kicker's client entry and if not found resolve it */
   client_entry2 = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-  if (!client_entry2 || !client_entry2->nickname[0]) {
+  if (!client_entry2 || !client_entry2->internal.valid) {
     /** Resolve client */
     silc_client_unref_client(client, conn, client_entry);
     silc_client_unref_client(client, conn, client_entry2);
@@ -1222,8 +1282,10 @@ SILC_FSM_STATE(silc_client_notify_kicked)
   tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
 
   /* Remove kicked client from channel */
-  if (client_entry != conn->local_entry)
-    silc_client_remove_from_channel(client, conn, channel, client_entry);
+  if (client_entry != conn->local_entry) {
+    if (!silc_client_remove_from_channel(client, conn, channel, client_entry))
+      goto out;
+  }
 
   /* Notify application. */
   NOTIFY(client, conn, type, client_entry, tmp, client_entry2, channel);
@@ -1288,7 +1350,7 @@ SILC_FSM_STATE(silc_client_notify_killed)
     /* Find Client entry */
     client_entry2 = silc_client_get_client_by_id(client, conn,
                                                 &id.u.client_id);
-    if (!client_entry2 || !client_entry2->nickname[0]) {
+    if (!client_entry2 || !client_entry2->internal.valid) {
       /** Resolve client */
       silc_client_unref_client(client, conn, client_entry);
       silc_client_unref_client(client, conn, client_entry2);
@@ -1332,6 +1394,7 @@ SILC_FSM_STATE(silc_client_notify_killed)
   /* Delete the killed client */
   if (client_entry != conn->local_entry) {
     silc_client_remove_from_channels(client, conn, client_entry);
+    client_entry->internal.valid = FALSE;
     silc_client_del_client(client, conn, client_entry);
   }
 
@@ -1362,6 +1425,7 @@ SILC_FSM_STATE(silc_client_notify_server_signoff)
   SilcNotifyType type = silc_notify_get_type(payload);
   SilcArgumentPayload args = silc_notify_get_args(payload);
   SilcClientEntry client_entry;
+  SilcServerEntry server_entry = NULL;
   SilcDList clients;
   SilcID id;
   int i;
@@ -1372,6 +1436,13 @@ SILC_FSM_STATE(silc_client_notify_server_signoff)
   if (!clients)
     goto out;
 
+  /* Get server ID */
+  if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
+    goto out;
+
+  /* Get server, in case we have it cached */
+  server_entry = silc_client_get_server_by_id(client, conn, &id.u.server_id);
+
   for (i = 1; i < silc_argument_get_arg_num(args); i++) {
     /* Get Client ID */
     if (!silc_argument_get_decoded(args, i + 1, SILC_ARGUMENT_ID, &id, NULL))
@@ -1379,23 +1450,24 @@ SILC_FSM_STATE(silc_client_notify_server_signoff)
 
     /* Get the client entry */
     client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-    if (client_entry)
+    if (client_entry && client_entry->internal.valid)
       silc_dlist_add(clients, client_entry);
   }
 
-  /* Notify application.  We don't keep server entries so the server
-     entry is returned as NULL. The client's are returned as list. */
-  NOTIFY(client, conn, type, NULL, clients);
+  /* Notify application. */
+  NOTIFY(client, conn, type, server_entry, clients);
 
   /* Delete the clients */
   silc_dlist_start(clients);
   while ((client_entry = silc_dlist_get(clients))) {
     silc_client_remove_from_channels(client, conn, client_entry);
+    client_entry->internal.valid = FALSE;
     silc_client_del_client(client, conn, client_entry);
   }
 
  out:
   /** Notify processed */
+  silc_client_unref_server(client, conn, server_entry);
   silc_client_list_free(client, conn, clients);
   silc_fsm_next(fsm, silc_client_notify_processed);
   return SILC_FSM_CONTINUE;
@@ -1432,7 +1504,7 @@ SILC_FSM_STATE(silc_client_notify_error)
     if (!silc_argument_get_decoded(args, 2, SILC_ARGUMENT_ID, &id, NULL))
       goto out;
     client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-    if (client_entry) {
+    if (client_entry && client_entry != conn->local_entry) {
       silc_client_remove_from_channels(client, conn, client_entry);
       silc_client_del_client(client, conn, client_entry);
       silc_client_unref_client(client, conn, client_entry);
@@ -1462,7 +1534,6 @@ SILC_FSM_STATE(silc_client_notify_watch)
   SilcArgumentPayload args = silc_notify_get_args(payload);
   SilcClientEntry client_entry = NULL;
   SilcNotifyType ntype = 0;
-  SilcBool del_client = FALSE;
   unsigned char *pk, *tmp;
   SilcUInt32 mode, pk_len, tmp_len;
   SilcPublicKey public_key = NULL;
@@ -1474,9 +1545,9 @@ SILC_FSM_STATE(silc_client_notify_watch)
   if (!silc_argument_get_decoded(args, 1, SILC_ARGUMENT_ID, &id, NULL))
     goto out;
 
-  /* Find Client entry and if not found resolve it */
+  /* Find client entry and if not found resolve it */
   client_entry = silc_client_get_client_by_id(client, conn, &id.u.client_id);
-  if (!client_entry || !client_entry->nickname[0]) {
+  if (!client_entry || !client_entry->internal.valid) {
     /** Resolve client */
     silc_client_unref_client(client, conn, client_entry);
     SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
@@ -1530,19 +1601,12 @@ SILC_FSM_STATE(silc_client_notify_watch)
 
   client_entry->mode = mode;
 
-  /* If nickname was changed, remove the client entry unless the
-     client is on some channel */
-  /* XXX, why do we need to remove the client entry?? */
-  if (tmp && ntype == SILC_NOTIFY_TYPE_NICK_CHANGE &&
-      !silc_hash_table_count(client_entry->channels))
-    del_client = TRUE;
-  else if (ntype == SILC_NOTIFY_TYPE_SIGNOFF ||
-          ntype == SILC_NOTIFY_TYPE_SERVER_SIGNOFF ||
-          ntype == SILC_NOTIFY_TYPE_KILLED)
-    del_client = TRUE;
-
-  if (del_client) {
+  /* Remove client that left the network. */
+  if (ntype == SILC_NOTIFY_TYPE_SIGNOFF ||
+      ntype == SILC_NOTIFY_TYPE_SERVER_SIGNOFF ||
+      ntype == SILC_NOTIFY_TYPE_KILLED) {
     silc_client_remove_from_channels(client, conn, client_entry);
+    client_entry->internal.valid = FALSE;
     silc_client_del_client(client, conn, client_entry);
   }