updates.
[silc.git] / apps / silcd / server.c
index 6037be57522704dd83a81afe3122fa6092c13e3e..e8874aeca14a87cee0bc703ea1da3eafc6cb3eae 100644 (file)
@@ -2532,30 +2532,29 @@ void silc_server_free_client_data(SilcServer server,
      to the network before removing the client entry. */
   silc_server_packet_queue_purge(server, sock);
 
-  if (!client->id)
-    return;
-
-  /* Send SIGNOFF notify to routers. */
-  if (notify && !server->standalone && server->router)
-    silc_server_send_notify_signoff(server, server->router->connection,
-                                   server->server_type == SILC_SERVER ?
-                                   FALSE : TRUE, client->id, signoff);
-
-  /* Remove client from all channels */
-  if (notify)
-    silc_server_remove_from_channels(server, NULL, client,
-                                    TRUE, (char *)signoff, TRUE);
-  else
-    silc_server_remove_from_channels(server, NULL, client,
-                                    FALSE, NULL, FALSE);
+  if (client->id) {
+    /* Send SIGNOFF notify to routers. */
+    if (notify && !server->standalone && server->router)
+      silc_server_send_notify_signoff(server, server->router->connection,
+                                     server->server_type == SILC_SERVER ?
+                                     FALSE : TRUE, client->id, signoff);
+
+    /* Remove client from all channels */
+    if (notify)
+      silc_server_remove_from_channels(server, NULL, client,
+                                      TRUE, (char *)signoff, TRUE);
+    else
+      silc_server_remove_from_channels(server, NULL, client,
+                                      FALSE, NULL, FALSE);
 
-  /* Check if anyone is watching this nickname */
-  if (server->server_type == SILC_ROUTER)
-    silc_server_check_watcher_list(server, client, NULL,
-                                  SILC_NOTIFY_TYPE_SIGNOFF);
+    /* Check if anyone is watching this nickname */
+    if (server->server_type == SILC_ROUTER)
+      silc_server_check_watcher_list(server, client, NULL,
+                                    SILC_NOTIFY_TYPE_SIGNOFF);
 
-  /* Remove this client from watcher list if it is */
-  silc_server_del_from_watcher_list(server, client);
+    /* Remove this client from watcher list if it is */
+    silc_server_del_from_watcher_list(server, client);
+  }
 
   /* Update statistics */
   server->stat.my_clients--;
@@ -2729,9 +2728,9 @@ void silc_server_free_sock_user_data(SilcServer server,
 void silc_server_remove_from_channels(SilcServer server,
                                      SilcSocketConnection sock,
                                      SilcClientEntry client,
-                                     int notify,
-                                     char *signoff_message,
-                                     int keygen)
+                                     bool notify,
+                                     const char *signoff_message,
+                                     bool keygen)
 {
   SilcChannelEntry channel;
   SilcChannelClientEntry chl;
@@ -2744,6 +2743,8 @@ void silc_server_remove_from_channels(SilcServer server,
     return;
 
   clidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
+  if (!clidp)
+    notify = FALSE;
 
   /* Remove the client from all channels. The client is removed from
      the channels' user list. */
@@ -2751,22 +2752,15 @@ void silc_server_remove_from_channels(SilcServer server,
   while (silc_hash_table_get(&htl, NULL, (void *)&chl)) {
     channel = chl->channel;
 
-    /* Remove channel from client's channel list */
-    silc_hash_table_del(client->channels, channel);
-
-    /* Remove channel if there is no users anymore */
+    /* Remove channel if this is last client leaving the channel, unless
+       the channel is permanent. */
     if (server->server_type == SILC_ROUTER &&
        silc_hash_table_count(channel->user_list) < 2) {
-      if (channel->rekey)
-       silc_schedule_task_del_by_context(server->schedule, channel->rekey);
-      if (silc_idlist_del_channel(server->local_list, channel))
-       server->stat.my_channels--;
-      else
-        silc_idlist_del_channel(server->global_list, channel);
+      silc_server_channel_delete(server, channel);
       continue;
     }
 
-    /* Remove client from channel's client list */
+    silc_hash_table_del(client->channels, channel);
     silc_hash_table_del(channel->user_list, chl->client);
     channel->user_count--;
 
@@ -2780,7 +2774,8 @@ void silc_server_remove_from_channels(SilcServer server,
     server->stat.my_chanclients--;
 
     /* If there is not at least one local user on the channel then we don't
-       need the channel entry anymore, we can remove it safely. */
+       need the channel entry anymore, we can remove it safely, unless the
+       channel is permanent channel */
     if (server->server_type != SILC_ROUTER &&
        !silc_server_channel_has_local(channel)) {
       /* Notify about leaving client if this channel has global users. */
@@ -2792,37 +2787,12 @@ void silc_server_remove_from_channels(SilcServer server,
                                           signoff_message, signoff_message ?
                                           strlen(signoff_message) : 0);
 
-      if (channel->rekey)
-       silc_schedule_task_del_by_context(server->schedule, channel->rekey);
-
-      if (channel->founder_key) {
-       /* The founder auth data exists, do not remove the channel entry */
-       SilcChannelClientEntry chl2;
-       SilcHashTableList htl2;
-
-       channel->disabled = TRUE;
-
-       silc_hash_table_list(channel->user_list, &htl2);
-       while (silc_hash_table_get(&htl2, NULL, (void *)&chl2)) {
-         silc_hash_table_del(chl2->client->channels, channel);
-         silc_hash_table_del(channel->user_list, chl2->client);
-         channel->user_count--;
-         silc_free(chl2);
-       }
-       silc_hash_table_list_reset(&htl2);
-       continue;
-      }
-
-      /* Remove the channel entry */
-      if (silc_idlist_del_channel(server->local_list, channel))
-       server->stat.my_channels--;
-      else
-        silc_idlist_del_channel(server->global_list, channel);
+      silc_schedule_task_del_by_context(server->schedule, channel->rekey);
+      silc_server_channel_delete(server, channel);
       continue;
     }
 
-    /* Send notify to channel about client leaving SILC and thus
-       the entire channel. */
+    /* Send notify to channel about client leaving SILC and channel too */
     if (notify)
       silc_server_send_notify_to_channel(server, NULL, channel, FALSE,
                                         SILC_NOTIFY_TYPE_SIGNOFF,
@@ -2831,10 +2801,10 @@ void silc_server_remove_from_channels(SilcServer server,
                                         signoff_message, signoff_message ?
                                         strlen(signoff_message) : 0);
 
+    /* Re-generate channel key if needed */
     if (keygen && !(channel->mode & SILC_CHANNEL_MODE_PRIVKEY)) {
-      /* Re-generate channel key */
       if (!silc_server_create_channel_key(server, channel, 0))
-       goto out;
+       continue;
 
       /* Send the channel key to the channel. The key of course is not sent
         to the client who was removed from the channel. */
@@ -2844,7 +2814,6 @@ void silc_server_remove_from_channels(SilcServer server,
     }
   }
 
- out:
   silc_hash_table_list_reset(&htl);
   silc_buffer_free(clidp);
 }
@@ -2855,44 +2824,33 @@ void silc_server_remove_from_channels(SilcServer server,
    last client leaves the channel. If `notify' is FALSE notify messages
    are not sent. */
 
-int silc_server_remove_from_one_channel(SilcServer server,
-                                       SilcSocketConnection sock,
-                                       SilcChannelEntry channel,
-                                       SilcClientEntry client,
-                                       int notify)
+bool silc_server_remove_from_one_channel(SilcServer server,
+                                        SilcSocketConnection sock,
+                                        SilcChannelEntry channel,
+                                        SilcClientEntry client,
+                                        bool notify)
 {
   SilcChannelClientEntry chl;
   SilcBuffer clidp;
 
-  SILC_LOG_DEBUG(("Start"));
+  SILC_LOG_DEBUG(("Removing %s from channel %s",
+                 silc_id_render(client->id, SILC_ID_CLIENT), 
+                 channel->channel_name));
 
   /* Get the entry to the channel, if this client is not on the channel
      then return Ok. */
   if (!silc_hash_table_find(client->channels, channel, NULL, (void *)&chl))
     return TRUE;
 
-  /* Remove the client from the channel. The client is removed from
-     the channel's user list. */
-
-  clidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
-
-  /* Remove channel from client's channel list */
-  silc_hash_table_del(client->channels, chl->channel);
-
-  /* Remove channel if there is no users anymore */
+  /* Remove channel if this is last client leaving the channel, unless
+     the channel is permanent. */
   if (server->server_type == SILC_ROUTER &&
       silc_hash_table_count(channel->user_list) < 2) {
-    if (channel->rekey)
-      silc_schedule_task_del_by_context(server->schedule, channel->rekey);
-    if (silc_idlist_del_channel(server->local_list, channel))
-      server->stat.my_channels--;
-    else
-      silc_idlist_del_channel(server->global_list, channel);
-    silc_buffer_free(clidp);
+    silc_server_channel_delete(server, channel);
     return FALSE;
   }
 
-  /* Remove client from channel's client list */
+  silc_hash_table_del(client->channels, chl->channel);
   silc_hash_table_del(channel->user_list, chl->client);
   channel->user_count--;
 
@@ -2905,8 +2863,13 @@ int silc_server_remove_from_one_channel(SilcServer server,
   silc_free(chl);
   server->stat.my_chanclients--;
 
+  clidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
+  if (!clidp)
+    notify = FALSE;
+
   /* If there is not at least one local user on the channel then we don't
-     need the channel entry anymore, we can remove it safely. */
+     need the channel entry anymore, we can remove it safely, unless the
+     channel is permanent channel */
   if (server->server_type != SILC_ROUTER &&
       !silc_server_channel_has_local(channel)) {
     /* Notify about leaving client if this channel has global users. */
@@ -2915,34 +2878,9 @@ int silc_server_remove_from_one_channel(SilcServer server,
                                         SILC_NOTIFY_TYPE_LEAVE, 1,
                                         clidp->data, clidp->len);
 
+    silc_schedule_task_del_by_context(server->schedule, channel->rekey);
+    silc_server_channel_delete(server, channel);
     silc_buffer_free(clidp);
-
-    if (channel->rekey)
-      silc_schedule_task_del_by_context(server->schedule, channel->rekey);
-
-    if (channel->founder_key) {
-      /* The founder auth data exists, do not remove the channel entry */
-      SilcChannelClientEntry chl2;
-      SilcHashTableList htl2;
-
-      channel->disabled = TRUE;
-
-      silc_hash_table_list(channel->user_list, &htl2);
-      while (silc_hash_table_get(&htl2, NULL, (void *)&chl2)) {
-       silc_hash_table_del(chl2->client->channels, channel);
-       silc_hash_table_del(channel->user_list, chl2->client);
-       channel->user_count--;
-       silc_free(chl2);
-      }
-      silc_hash_table_list_reset(&htl2);
-      return FALSE;
-    }
-
-    /* Remove the channel entry */
-    if (silc_idlist_del_channel(server->local_list, channel))
-      server->stat.my_channels--;
-    else
-      silc_idlist_del_channel(server->global_list, channel);
     return FALSE;
   }
 
@@ -3603,6 +3541,7 @@ void silc_server_announce_get_channel_topic(SilcServer server,
 
 void silc_server_announce_get_channel_users(SilcServer server,
                                            SilcChannelEntry channel,
+                                           SilcBuffer *channel_modes,
                                            SilcBuffer *channel_users,
                                            SilcBuffer *channel_users_modes)
 {
@@ -3611,12 +3550,45 @@ void silc_server_announce_get_channel_users(SilcServer server,
   SilcBuffer chidp, clidp;
   SilcBuffer tmp;
   int len;
-  unsigned char mode[4];
+  unsigned char mode[4], *fkey = NULL;
+  SilcUInt32 fkey_len = 0;
+  char *hmac;
 
   SILC_LOG_DEBUG(("Start"));
 
-  /* Now find all users on the channel */
   chidp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
+
+  /* CMODE notify */
+  clidp = silc_id_payload_encode(server->id, SILC_ID_SERVER);
+  SILC_PUT32_MSB(channel->mode, mode);
+  hmac = channel->hmac ? (char *)silc_hmac_get_name(channel->hmac) : NULL;
+  if (channel->founder_key)
+    fkey = silc_pkcs_public_key_encode(channel->founder_key, &fkey_len);
+  tmp = 
+    silc_server_announce_encode_notify(SILC_NOTIFY_TYPE_CMODE_CHANGE,
+                                      6, clidp->data, clidp->len,
+                                      mode, sizeof(mode),
+                                      NULL, 0,
+                                      hmac, hmac ? strlen(hmac) : 0,
+                                      channel->passphrase,
+                                      channel->passphrase ?
+                                      strlen(channel->passphrase) : 0,
+                                      fkey, fkey_len);
+  len = tmp->len;
+  *channel_modes =
+    silc_buffer_realloc(*channel_modes,
+                       (*channel_modes ?
+                        (*channel_modes)->truelen + len : len));
+  silc_buffer_pull_tail(*channel_modes,
+                       ((*channel_modes)->end -
+                        (*channel_modes)->data));
+  silc_buffer_put(*channel_modes, tmp->data, tmp->len);
+  silc_buffer_pull(*channel_modes, len);
+  silc_buffer_free(tmp);
+  silc_buffer_free(clidp);
+  silc_free(fkey);
+
+  /* Now find all users on the channel */
   silc_hash_table_list(channel->user_list, &htl);
   while (silc_hash_table_get(&htl, NULL, (void *)&chl)) {
     clidp = silc_id_payload_encode(chl->client->id, SILC_ID_CLIENT);
@@ -3640,10 +3612,13 @@ void silc_server_announce_get_channel_users(SilcServer server,
 
     /* CUMODE notify for mode change on the channel */
     SILC_PUT32_MSB(chl->mode, mode);
+    if (chl->mode & SILC_CHANNEL_UMODE_CHANFO && channel->founder_key)
+      fkey = silc_pkcs_public_key_encode(channel->founder_key, &fkey_len);
     tmp = silc_server_announce_encode_notify(SILC_NOTIFY_TYPE_CUMODE_CHANGE,
-                                            3, clidp->data, clidp->len,
+                                            4, clidp->data, clidp->len,
                                             mode, 4,
-                                            clidp->data, clidp->len);
+                                            clidp->data, clidp->len,
+                                            fkey, fkey_len);
     len = tmp->len;
     *channel_users_modes =
       silc_buffer_realloc(*channel_users_modes,
@@ -3656,7 +3631,7 @@ void silc_server_announce_get_channel_users(SilcServer server,
     silc_buffer_put(*channel_users_modes, tmp->data, tmp->len);
     silc_buffer_pull(*channel_users_modes, len);
     silc_buffer_free(tmp);
-
+    silc_free(fkey);
     silc_buffer_free(clidp);
   }
   silc_hash_table_list_reset(&htl);
@@ -3670,6 +3645,7 @@ void silc_server_announce_get_channel_users(SilcServer server,
 void silc_server_announce_get_channels(SilcServer server,
                                       SilcIDList id_list,
                                       SilcBuffer *channels,
+                                      SilcBuffer **channel_modes,
                                       SilcBuffer *channel_users,
                                       SilcBuffer **channel_users_modes,
                                       SilcUInt32 *channel_users_modes_c,
@@ -3728,10 +3704,14 @@ void silc_server_announce_get_channels(SilcServer server,
                                            sizeof(**channel_users_modes) *
                                            (i + 1));
        (*channel_users_modes)[i] = NULL;
+       *channel_modes = silc_realloc(*channel_modes,
+                                     sizeof(**channel_modes) * (i + 1));
+       (*channel_modes)[i] = NULL;
        *channel_ids = silc_realloc(*channel_ids,
                                    sizeof(**channel_ids) * (i + 1));
        (*channel_ids)[i] = NULL;
        silc_server_announce_get_channel_users(server, channel,
+                                              &(*channel_modes)[i], 
                                               channel_users,
                                               &(*channel_users_modes)[i]);
        (*channel_ids)[i] = channel->id;
@@ -3766,7 +3746,7 @@ void silc_server_announce_channels(SilcServer server,
                                   unsigned long creation_time,
                                   SilcSocketConnection remote)
 {
-  SilcBuffer channels = NULL, channel_users = NULL;
+  SilcBuffer channels = NULL, *channel_modes = NULL, channel_users = NULL;
   SilcBuffer *channel_users_modes = NULL;
   SilcBuffer *channel_topics = NULL;
   SilcUInt32 channel_users_modes_c = 0;
@@ -3776,7 +3756,8 @@ void silc_server_announce_channels(SilcServer server,
 
   /* Get channels and channel users in local list */
   silc_server_announce_get_channels(server, server->local_list,
-                                   &channels, &channel_users,
+                                   &channels, &channel_modes,
+                                   &channel_users,
                                    &channel_users_modes,
                                    &channel_users_modes_c,
                                    &channel_topics,
@@ -3785,7 +3766,8 @@ void silc_server_announce_channels(SilcServer server,
   /* Get channels and channel users in global list */
   if (server->server_type != SILC_SERVER)
     silc_server_announce_get_channels(server, server->global_list,
-                                     &channels, &channel_users,
+                                     &channels, &channel_modes,
+                                     &channel_users,
                                      &channel_users_modes,
                                      &channel_users_modes_c,
                                      &channel_topics,
@@ -3804,6 +3786,28 @@ void silc_server_announce_channels(SilcServer server,
     silc_buffer_free(channels);
   }
 
+  if (channel_modes) {
+    int i;
+
+    for (i = 0; i < channel_users_modes_c; i++) {
+      if (!channel_modes[i])
+        continue;
+      silc_buffer_push(channel_modes[i],
+                      channel_modes[i]->data -
+                      channel_modes[i]->head);
+      SILC_LOG_HEXDUMP(("channel modes"), channel_modes[i]->data,
+                      channel_modes[i]->len);
+      silc_server_packet_send_dest(server, remote,
+                                  SILC_PACKET_NOTIFY, SILC_PACKET_FLAG_LIST,
+                                  channel_ids[i], SILC_ID_CHANNEL,
+                                  channel_modes[i]->data,
+                                  channel_modes[i]->len,
+                                  FALSE);
+      silc_buffer_free(channel_modes[i]);
+    }
+    silc_free(channel_modes);
+  }
+
   if (channel_users) {
     silc_buffer_push(channel_users, channel_users->data - channel_users->head);
     SILC_LOG_HEXDUMP(("channel users"), channel_users->data,